placing sub arrays in neat rows - ruby

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"]

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

Questions on implementing hashes in ruby

I'm new to ruby, I am solving a problem that involves hashes and key. The problem asks me to Implement a method, #pet_types, that accepts a hash as an argument. The hash uses people's # names as keys, and the values are arrays of pet types that the person owns. My question is about using Hash#each method to iterate through each num inside the array. I was wondering if there's any difference between solving the problem using hash#each or hash.sort.each?
I spent several hours coming up different solution and still to figure out what are the different approaches between the 2 ways of solving the problem below.
I include my code in repl.it: https://repl.it/H0xp/6 or you can see below:
# Pet Types
# ------------------------------------------------------------------------------
# Implement a method, #pet_types, that accepts a hash as an argument. The hash uses people's
# names as keys, and the values are arrays of pet types that the person owns.
# Example input:
# {
# "yi" => ["dog", "cat"],
# "cai" => ["dog", "cat", "mouse"],
# "venus" => ["mouse", "pterodactyl", "chinchilla", "cat"]
# }
def pet_types(owners_hash)
results = Hash.new {|h, k| h[k] = [ ] }
owners_hash.sort.each { |k, v| v.each { |pet| results[pet] << k } }
results
end
puts "-------Pet Types-------"
owners_1 = {
"yi" => ["cat"]
}
output_1 = {
"cat" => ["yi"]
}
owners_2 = {
"yi" => ["cat", "dog"]
}
output_2 = {
"cat" => ["yi"],
"dog" => ["yi"]
}
owners_3 = {
"yi" => ["dog", "cat"],
"cai" => ["dog", "cat", "mouse"],
"venus" => ["mouse", "pterodactyl", "chinchilla", "cat"]
}
output_3 = {
"dog" => ["cai", "yi"],
"cat" => ["cai", "venus", "yi"],
"mouse" => ["cai", "venus"],
"pterodactyl" => ["venus"],
"chinchilla" => ["venus"]
}
# method 2
# The 2nd and 3rd method should return a hash that uses the pet types as keys and the values should
# be a list of the people that own that pet type. The names in the output hash should
# be sorted alphabetically
# switched_hash = Hash.new()
# owners_hash.each do |owner, pets_array|
# pets_array.each do |pet|
# select_owners = owners_hash.select { |owner, pets_array|
owners_hash[owner].include?(pet) }
# switched_hash[pet] = select_owners.keys.sort
# end
# end
# method 3
#switched_hash
# pets = Hash.new {|h, k| h[k] = [ ] } # WORKS SAME AS: pets = Hash.new( Array.new )
# owners = owners_hash.keys.sort
# owners.each do |owner|
# owners_hash[owner].each do |pet|
# pets[pet] << owner
# end
# end
# pets
# Example output:
# output_3 = {
# "dog" => ["cai", "yi"],
# "cat" => ["cai", "venus", "yi"], ---> (sorted alphabetically!)
# "mouse" => ["cai", "venus"],
# "pterodactyl" => ["venus"],
# "chinchilla" => ["venus"]
# }
I used a hash data structure in my program to first solve this problem. Then I tried to rewrite it using the pet_hash. And my final codes is the following:
def pet_types(owners_hash)
pets_hash = Hash.new { |k, v| v = [] }
owners_hash.each do |owner, pets|
pets.each do |pet|
pets_hash[pet] += [owner]
end
end
pets_hash.values.each(&:sort!)
pets_hash
end
puts "-------Pet Types-------"
owners_1 = {
"yi" => ["cat"]
}
output_1 = {
"cat" => ["yi"]
}
owners_2 = {
"yi" => ["cat", "dog"]
}
output_2 = {
"cat" => ["yi"],
"dog" => ["yi"]
}
owners_3 = {
"yi" => ["dog", "cat"],
"cai" => ["dog", "cat", "mouse"],
"venus" => ["mouse", "pterodactyl", "chinchilla", "cat"]
}
output_3 = {
"dog" => ["cai", "yi"],
"cat" => ["cai", "venus", "yi"],
"mouse" => ["cai", "venus"],
"pterodactyl" => ["venus"],
"chinchilla" => ["venus"]
}
puts pet_types(owners_1) == output_1
puts pet_types(owners_2) == output_2
puts pet_types(owners_3) == output_3
Hash#sort has the same effect (at least for my basic test) as Hash#to_a followed by Array#sort.
hash = {b: 2, a: 1}
hash.to_a.sort # => [[:a, 1, [:b, 2]]
hash.sort # => the same
Now let's look at #each, both on Hash and Array.
When you provide two arguments to the block, that can handle both cases. For the hash, the first argument will be the key and the second will be the value. For the nested array, the values essentially get splatted out to the args:
[[:a, 1, 2], [:b, 3, 4]].each { |x, y, z| puts "#{x}-#{y}-#{z}" }
# => a-1-2
# => b-3-4
So basically, you should think of Hash#sort to be a shortcut to Hash#to_a followed by Array#sort, and recognize that #each will work the same on a hash as a hash converted to array (a nested array). In this case, it doesn't matter which approach you take. Clearly if you need to sort iteration by the keys then you should use sort.

Extracting Data from array of hashes Ruby

Given that I have the following array of hashes
#response = { "0"=>{"forename_1"=>"John", "surname_1"=>"Smith"},
"1"=>{"forename_1"=>"Chris", "surname_1"=>"Jenkins"},
"2"=>{"forename_1"=>"Billy", "surname_1"=>"Bob"},
"Status" => 100
}
I am looking to create an array of the forename_1 and surname_1 values combined, so desired output would be
["John Smith", "Chris Jenkins", "Billy Bob"]
So I can get this far, but need further assistance
# Delete the Status as not needed
#response.delete_if { |k| ["Status"].include? k }
#response.each do |key, value|
puts key
#This will print out 0 1 2
puts value
# This will print {"forename_1"=>"John", "surname_1"=>"Smith"}, "{"forename_1"=>"Chris", "surname_1"=>"Jenkins"}, "{"forename_1"=>"Billy", "surname_1"=>"Bob"}
puts value.keys
# prints ["forename_1", "surname_1"], ["forename_1", "surname_1"], ["forename_1", "surname_1"]
puts value.values
# prints ["John", "Smith"], ["Chris", "Jenkins"], ["Billy", "Bob"]
value.map { |v| v["forename_1"] }
# However i get no implicit conversion of String into Integer error
end
What am i doing wrong here?
Thanks
Another way :
#response.values.grep(Hash).map { |t| t.values.join(' ')}
What you have to do is to get the values of the #response hash, filter out what is not an instance of Hash, and then join together the forename and the surname, I would do something like this:
#response.values.grep(Hash).map { |h| "#{h['forename_1']} #{h['surname_1']}" }
# => ["John Smith", "Chris Jenkins", "Billy Bob"]
#response.values.map{ |res|
[res["forename_1"] , res["surname_1"]].join(' ') if res.is_a?(Hash)
}.compact

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"]

Finding the product of a variable number of Ruby arrays

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"]
# ]

Resources