Jake
Jake

Reputation: 1370

Ruby nested iteration to match strings within an array

I'm trying to figure out from an array of strings how to compare strings based on characters. So for example there's the following:

arr = [4, "string", "gnirts", "strign", "ta", "atc"]

In this case "ta" and "atc" do not match the other strings. But "string", "gnirts", "strign" match.

My first thought would be to break the array apart, do a length check. Then compare the strings and keep the first present.

I know I don't care about the 4. It's just indicating the number of strings so I can do an arr.shift(1).

I know I could do something like string.chars.sort but how do I compare the strings within the arrays?

I was thinking something like:

arr.each_with_index do |value, index|
 index.each do |item|
  if value.chars.sort == item
   return value
 end
end

That definitely does not work.

What I'm wanting to end up seeing would then have a sort on the array so I'd end up with atc, gnirts, and string (because the others match against it and string comes first).

So how do I ultimately compare strings within an array and keep the first one numerically?

EDIT: Input would be something like [4, "string", "gnirts", "strign", "ta", "atc"] Output array would be

["atc", "ta", "string"]

So matching would keep the first present and then sort on those not matched.

Upvotes: 0

Views: 208

Answers (2)

Cary Swoveland
Cary Swoveland

Reputation: 110675

require 'set'

arr.grep(String).uniq { |obj| obj.each_char.to_set }.sort_by(&:size)
  #=> ["ta", "atc", "string"] 

The doc for Array#uniq states, "self is traversed in order, and the first occurrence is kept.", meaning that, among "string", "gnirts" and "strign", "string" has the smallest index in the array (1), so it is the one that is retained by uniq.

One could replace obj.each_char.to_set with obj.each_char.sort but the latter is less efficient if the array is large.

Replace grep(String) with drop(1) if it is known that there is exactly one non-string and it is the first element of the array.

Upvotes: 3

max pleaner
max pleaner

Reputation: 26768

input = [4, "string", "gnirts", "strign", "ta", "atc"]

input.
  drop(1).
  group_by { |str| str.chars.sort.join }.
  values.
  map(&:first)

# => ["string", "ta", "atc"]

Going through line by line:

input.

  drop(1).
  # => ["string", "gnirts", "strign", "ta", "atc"]]

  group_by { |str| str.chars.sort.join }.
  # => {
  #      "ginrst" => ["string", "gnirts", "strign"],
  #      "at"     => ["ta"],
  #      "act"    => ["atc"]
  #    }

  values.
  # => [
  #       ["string", "gnirts", "strign"],
  #       ["ta"],
  #       ["atc"]
  #    ]

  map(&:first)
  # => ["string", "ta", "atc"]

As lacostenycoder mentioned you may want to reverse the output to match what's expected.

It is possibly to do this using other Enumerable methods such as reduce, each, etc. however group_by is more idiomatic and I would recommend reading up on Ruby's Enumerable class because you can get a lot done with method chaining, if you know all the methods that are available to you.

Upvotes: 4

Related Questions