Finding the product of a variable number of Ruby arrays - ruby

I'm looking to find all combinations of single items from a variable number of arrays. How do I do this in Ruby?
Given two arrays, I can use Array.product like this:
groups = []
groups[0] = ["hello", "goodbye"]
groups[1] = ["world", "everyone"]
combinations = groups[0].product(groups[1])
puts combinations.inspect
# [["hello", "world"], ["hello", "everyone"], ["goodbye", "world"], ["goodbye", "everyone"]]
How could this code work when groups contains a variable number of arrays?

groups = [
%w[hello goodbye],
%w[world everyone],
%w[here there]
]
combinations = groups.first.product(*groups.drop(1))
p combinations
# [
# ["hello", "world", "here"],
# ["hello", "world", "there"],
# ["hello", "everyone", "here"],
# ["hello", "everyone", "there"],
# ["goodbye", "world", "here"],
# ["goodbye", "world", "there"],
# ["goodbye", "everyone", "here"],
# ["goodbye", "everyone", "there"]
# ]

Related

Ruby hash with multiple comma separated values to array of hashes with same keys

What is the most efficient and pretty way to map this:
{name:"cheese,test", uid:"1,2"}
to this:
[ {name:"cheese", uid:"1"}, {name:"test", uid:"2"} ]
should work dinamically for example with: { name:"cheese,test,third", uid:"1,2,3" } or {name:"cheese,test,third,fourth", uid:"1,2,3,4", age:"9,8,7,6" }
Finally I made this:
hash = {name:"cheese,test", uid:"1,2"}
results = []
length = hash.values.first.split(',').length
length.times do |i|
results << hash.map {|k,v| [k, v.split(',')[i]]}
end
results.map{|e| e.to_h}
It is working, but i am not pleased with it, has to be a cleaner and more 'rubyst' way to do this
def splithash(h)
# Transform each element in the Hash...
h.map do |k, v|
# ...by splitting the values on commas...
v.split(',').map do |vv|
# ...and turning these into individual { k => v } entries.
{ k => vv }
end
end.inject do |a,b|
# Then combine these by "zip" combining each list A to each list B...
a.zip(b)
# ...which will require a subsequent .flatten to eliminate nesting
# [ [ 1, 2 ], 3 ] -> [ 1, 2, 3 ]
end.map(&:flatten).map do |s|
# Then combine all of these { k => v } hashes into one containing
# all the keys with associated values.
s.inject(&:merge)
end
end
Which can be used like this:
splithash(name:"cheese,test", uid:"1,2", example:"a,b")
# => [{:name=>"cheese", :uid=>"1", :example=>"a"}, {:name=>"test", :uid=>"2", :example=>"b"}]
It looks a lot more convoluted at first glance, but this handles any number of keys.
I would likely use transpose and zip like so:
hash = {name:"cheese,test,third,fourth", uid:"1,2,3,4", age:"9,8,7,6" }
hash.values.map{|x| x.split(",")}.transpose.map{|v| hash.keys.zip(v).to_h}
#=> [{:name=>"cheese", :uid=>"1", :age=>"9"}, {:name=>"test", :uid=>"2", :age=>"8"}, {:name=>"third", :uid=>"3", :age=>"7"}, {:name=>"fourth", :uid=>"4", :age=>"6"}]
To break it down a bit (code slightly modified for operational clarity):
hash.values
#=> ["cheese,test,third,fourth", "1,2,3,4", "9,8,7,6"]
.map{|x| x.split(",")}
#=> [["cheese", "test", "third", "fourth"], ["1", "2", "3", "4"], ["9", "8", "7", "6"]]
.transpose
#=> [["cheese", "1", "9"], ["test", "2", "8"], ["third", "3", "7"], ["fourth", "4", "6"]]
.map do |v|
hash.keys #=> [[:name, :uid, :age], [:name, :uid, :age], [:name, :uid, :age], [:name, :uid, :age]]
.zip(v) #=> [[[:name, "cheese"], [:uid, "1"], [:age, "9"]], [[:name, "test"], [:uid, "2"], [:age, "8"]], [[:name, "third"], [:uid, "3"], [:age, "7"]], [[:name, "fourth"], [:uid, "4"], [:age, "6"]]]
.to_h #=> [{:name=>"cheese", :uid=>"1", :age=>"9"}, {:name=>"test", :uid=>"2", :age=>"8"}, {:name=>"third", :uid=>"3", :age=>"7"}, {:name=>"fourth", :uid=>"4", :age=>"6"}]
end
Input
hash={name:"cheese,test,third,fourth", uid:"1,2,3,4", age:"9,8,7,6" }
Code
p hash
.transform_values { |v| v.split(',') }
.map { |k, v_arr| v_arr.map { |v| [k, v] }
}
.transpose
.map { |array| array.to_h }
Output
[{:name=>"cheese", :uid=>"1", :age=>"9"}, {:name=>"test", :uid=>"2", :age=>"8"}, {:name=>"third", :uid=>"3", :age=>"7"}, {:name=>"fourth", :uid=>"4", :age=>"6"}]
We are given
h = { name: "cheese,test", uid: "1,2" }
Here are two ways to create the desired array. Neither construct arrays that are then converted to hashes.
#1
First compute
g = h.transform_values { |s| s.split(',') }
#=> {:name=>["cheese", "test"], :uid=>["1", "2"]}
then compute
g.first.last.size.times.map { |i| g.transform_values { |v| v[i] } }
#=> [{:name=>"cheese", :uid=>"1"}, {:name=>"test", :uid=>"2"}]
Note
a = g.first
#=> [:name, ["cheese", "test"]]
b = a.last
#=> ["cheese", "test"]
b.size
#=> 2
#2
This approach does not convert the values of the hash to arrays.
(h.first.last.count(',')+1).times.map do |i|
h.transform_values { |s| s[/(?:\w+,){#{i}}\K\w+/] }
end
#=> [{:name=>"cheese", :uid=>"1"}, {:name=>"test", :uid=>"2"}]
We have
a = h.first
#=> [:name, "cheese,test"]
s = a.last
#=> "cheese,test"
s.count(',')+1
#=> 2
We can express the regular expression in free-spacing mode to make it self-documenting.
/
(?: # begin a non-capture group
\w+, # match one or more word characters followed by a comma
) # end the non-capture group
{#{i}} # execute the preceding non-capture group i times
\K # discard all matches so far and reset the start of the match
\w+ # match one or more word characters
/x # invoke free-spacing regex definition mode

Search an array for a string and its substrings in ruby

I want to search an array for a certain string and(!) its substrings. For example my array is:
array = ["hello", "hell", "goodbye", "he"]
So when I search for "hello" and its substrings (but only from the beginning: "he", "hell", "hello"), it should return
=> ["hello", "hell", "he"]
What I've tried so far: Using a regular expression with the #grep and/or the #include? method like this:
array.grep("hello"[/\w+/])
or
array.select {|i| i.include?("hello"[/\w+/])}
but in both cases it only returns
=> ["hello"]
By the way, if I try array.select{|i| i.include?("he")} it works but like I said I want it the other way around: searching for "hello" and give me all results including the substrings from the beginning.
require "abbrev"
arr = ["hello", "hell", "goodbye", "he"]
p arr & ["hello"].abbrev.keys # => ["hello", "hell", "he"]
array = ["hello", "hell", "goodbye", "he", "he"]
# define search word:
search = "hello"
# find all substrings of this word:
substrings = (0..search.size - 1).each_with_object([]) { |i, subs| subs << search[0..i] }
#=> ["h", "he", "hel", "hell", "hello"]
# find intersection between array and substrings(will exclude duplicates):
p array & substrings
#=> ["hello", "hell", "he"]
# or select array elements that match any substring(will keep duplicates):
p array.select { |elem| substrings.include?(elem) }
#=> ["hello", "hell", "he", "he"]
I'd use String#[] :
array = ["hello", "hell", "goodbye", "he", "he"]
search = "hello"
array.select { |s| search[/\A#{s}/] }
# => ["hello", "hell", "he", "he"]
Turn all the characters other than h in hello to optional.
> array = ["hello", "hell", "goodbye", "he"]
> array.select{|i| i[/^he?l?l?o?/]}
=> ["hello", "hell", "he"]
You could still use a regular expression like this
#define Array
arr = ["hello", "hell", "goodbye", "he"]
#define search term as an Array of it's characters
search = "hello".split(//)
#=> ['h','e','l','l','o']
#deem the first as manditory search.shift
#the rest are optional ['e?','l?','l?','o?'].join
search = search.shift << search.map{|a| "#{a}?"}.join
#=> "he?l?l?o?"
#start at the beginning of the string \A
arr.grep(/\A#{search}/)
#=> ["hello", "hell", "he"]
Just as the question reads:
array.select { |w| "hello" =~ /^#{w}/ }
#=> ["hello", "hell", "he"]
Use array#keep_if
array = ["hello", "hell", he"]
substrings = array.keep_if{|a| a.start_with?('h')}
=> ["hello", "hell", "he"]

placing sub arrays in neat rows

I am currently working on a large array of names:
large_array = ["Bob","Joel","John","Smith","Kevin","Will","Stanley","George"] #and so on
I split it into sub arrays like so:
large_array.each_slice(2).to_a #=> [["Bob", "Joel"],["John,"Smith"],["Kevin", "Will"],["Stanley","George"]]
My question is how do I make the sub arrays appear neatly on top of each other in rows like this:
["Bob", "Joel"]
["John,"Smith"]
["Kevin","Will"]
["Stanley","George"]
large_array.each_slice(2) {|a| puts a.inspect}
# ["Bob", "Joel"]
# ["John", "Smith"]
# ["Kevin", "Will"]
# ["Stanley", "George"]
# => nil
You call that "neat"? This is what I call "neat":
enum = large_array.each_slice(2)
fname_max = enum.map { |f,_| f.size }.max + 3
lname_max = enum.map { |_,l| l.size }.max + 1
enum.each { |f,l|
puts "[\"#{ (f+'",').ljust(fname_max) }\"#{ (l+'"').ljust(lname_max) }]" }
#-> ["Bob", "Joel" ]
# ["John", "Smith" ]
# ["Kevin", "Will" ]
# ["Stanley", "George"]
Here's another way to write your "neat":
enum = large_array.to_enum
loop do
puts [enum.next, enum.next].to_s
end
#-> ["Bob", "Joel"]
# ["John", "Smith"]
# ["Kevin", "Will"]
# ["Stanley", "George"]
This works fine for "large" arrays, but for "huge" arrays (more than eight elements), you may wish to change the operative line to:
puts [enum.next, enum.next].to_s.tinyfy
for display purposes. That will print the following:
["Bob", "Joel"]
["John", "Smith"]
["Kevin", "Will"]
["Stanley", "George"]

Ruby symbols to#proc with puts

I'm learning ruby for 2 weeks but i have a problem you probably now that
words = ["hello", "world", "test"]
words.map do |word|
word.upcase
end
is equal to
words = ["hello", "world", "test"]
words.map(&:upcase)
but why that
m = method(:puts)
words.map(&m)
work ??
Object#method gets a Method object from the given symbol. method(:puts) gets the Method object corresponding to Kernel#puts.
The unary & operator calls the to_proc under the hood:
method(:puts).respond_to?(:to_proc) # => true
to_proc in Method returns a Proc equivalent to { |x| method_name(x) }:
[1, 2, 3].map &method(:String) # => ["1", "2", "3"]
[1, 2, 3].map { |x| String(x) } # => ["1", "2", "3"]
You are using the Object#method to pull the method off Kernel and store it in a variable "m".
The & operator calls Method#to_proc. Stating all this explicitly:
irb(main):009:0> m = Kernel.method(:puts); ["hello", "world", "test"].map{|word| m.to_proc.call(word)}
hello
world
test
=> [nil, nil, nil]

Move elements of an array to a different array in Ruby

Simple ruby question. Lets say I have an array of 10 strings and I want to move elements at array[3] and array[5] into a totally new array. The new array would then only have the two elements I moved from the first array, AND the first array would then only have 8 elements since two of them have been moved out.
Use Array#slice! to remove the elements from the first array, and append them to the second array with Array#<<:
arr1 = ['Foo', 'Bar', 'Baz', 'Qux']
arr2 = []
arr2 << arr1.slice!(1)
arr2 << arr1.slice!(2)
puts arr1.inspect
puts arr2.inspect
Output:
["Foo", "Baz"]
["Bar", "Qux"]
Depending on your exact situation, you may find other methods on array to be even more useful, such as Enumerable#partition:
arr = ['Foo', 'Bar', 'Baz', 'Qux']
starts_with_b, does_not_start_with_b = arr.partition{|word| word[0] == 'B'}
puts starts_with_b.inspect
puts does_not_start_with_b.inspect
Output:
["Bar", "Baz"]
["Foo", "Qux"]
a = (0..9).map { |i| "el##{i}" }
x = [3, 5].sort_by { |i| -i }.map { |i| a.delete_at(i) }
puts x.inspect
# => ["el#5", "el#3"]
puts a.inspect
# => ["el#0", "el#1", "el#2", "el#4", "el#6", "el#7", "el#8", "el#9"]
As noted in comments, there is some magic to make indices stay in place. This can be avoided by first getting all the desired elements using a.values_at(*indices), then deleting them as above.
Code:
arr = ["null","one","two","three","four","five","six","seven","eight","nine"]
p "Array: #{arr}"
third_el = arr.delete_at(3)
fifth_el = arr.delete_at(4)
first_arr = arr
p "First array: #{first_arr}"
concat_el = third_el + "," + fifth_el
second_arr = concat_el.split(",")
p "Second array: #{second_arr}"
Output:
c:\temp>C:\case.rb
"Array: [\"null\", \"one\", \"two\", \"three\", \"four\", \"five\", \"six\", \"s
even\", \"eight\", \"nine\"]"
"First array: [\"null\", \"one\", \"two\", \"four\", \"six\", \"seven\", \"eight
\", \"nine\"]"
"Second array: [\"three\", \"five\"]"
Why not start deleting from the highest index.
arr = ['Foo', 'Bar', 'Baz', 'Qux']
index_array = [2, 1]
new_ary = index_array.map { |index| arr.delete_at(index) }
new_ary # => ["Baz", "Bar"]
arr # => ["Foo", "Qux"]
Here's one way:
vals = arr.values_at *pulls
arr = arr.values_at *([*(0...arr.size)] - pulls)
Try it.
arr = %w[Now is the time for all Rubyists to code]
pulls = [3,5]
vals = arr.values_at *pulls
#=> ["time", "all"]
arr = arr.values_at *([*(0...arr.size)] - pulls)
#=> ["Now", "is", "the", "for", "Rubyists", "to", "code"]
arr = %w[Now is the time for all Rubyists to code]
pulls = [5,3]
vals = arr.values_at *pulls
#=> ["all", "time"]
arr = arr.values_at *([*(0...arr.size)] - pulls)
#=> ["Now", "is", "the", "for", "Rubyists", "to", "code"]

Resources