Reputation: 1370
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
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
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