Merge multiple arrays using zip - ruby

This may be a silly one. But I am not able to figure it out.
names = ['Fred', 'John', 'Mark']
age = [27, 40, 25]
location = ['Sweden', 'Denmark', 'Poland']
names.zip(age)
#Outputs => [["Fred", 27], ["John", 40], ["Mark", 25]]
But I need to output it with the 3rd array (location) with it.
#Expected Output => [["Fred", 27,"Sweden"], ["John", 40,"Denmark"], ["Mark", 25,"Poland"]]
The most important condition here is that, there may be any number of arrays but the output should form from the first element of each array and encapsulate inside another array.
Thanks for any help.

Try passing multiple arguments to zip:
names.zip(age, location)
# => [["Fred", 27, "Sweden"], ["John", 40, "Denmark"], ["Mark", 25, "Poland"]]

Related

Why can I execute a = *ary.flatten but not simply *ary.flatten?

ary = = [[4, 8], [15, 16], [23, 42]]
In irb, why can I execute
a = *ary.flatten # => [4,8,15,16,23,42]
but not simply
*ary.flatten
which gives me an error:
SyntaxError: (irb):97: syntax error, unexpected '\n', expecting '='
from /usr/bin/irb:12:in `<main>'
I could also execute
a,b,c,d,e,f = *ary.flatten
without a problem, and the returned value after I press enter is
=> [4, 8, 15, 16, 23, 42]
with a,b,c,d,e,f being now of class Fixnum.
So, what does
*ary.flatten
return after all? Seems like it should return the individual elements of ary (what exactly is this object it returns?), that I can assign to something: either a (which somehow automatically becomes an Array) or separate individual variables a,b,c,d,e,f each of which is now a Fixnum.
Also,
a = *ary.flatten.class # => [Array]
b = ary.flatten.class # => Array
What is the difference between [Array] and Array? (perhaps I should make this a separate question but I can only post every 90min and I want to know now!)
The splat transforms an array into a list of objects as if you had written them out explicitly using commas. So, obviously, it can only be used where such a list of objects can be used.
a = 4, 8, 15, 16, 23, 42
is legal, it's a parallel assignment.
4, 8, 15, 16, 23, 42
is not legal, it's a SyntaxError.
So, what does
*ary.flatten
return after all? Seems like it should return the individual elements of ary (what exactly is this object it returns?),
It doesn't return an object. It is a syntactic construct that allows you to take an array and use it as if you had written out the elements by hand one-by-one.
a = *ary.flatten.class # => [Array]
b = ary.flatten.class # => Array
What is the difference between [Array] and Array?
Array is a class, [Array] is an array with a single element, which is the class Array.

Convert Table to Multidimensional Hash in Ruby

I know that there must be some simple and elegant way to do this, but I'm drawing a blank.
I have a table (or group of key value pairs)
id,val
64664,68
64665,65
64666,53
64667,68
64668,6
64668,27
64668,33
64669,12
In most cases there is one value per id. In some cases there are multiples.
I want to end up with each id with multiple values represented as an array of those values
something like this:
[ 64664 => 68,
64665 => 65,
64666 => 53,
64668 =>[6,27,33],
64669 => 12
]
Any brilliant ideas?
You can use Hash#merge to merge two hashes. Using Enumerable#inject, you can get what you want.
tbl = [
[64664, 68],
[64665, 65],
[64666, 53],
[64667, 68],
[64668, 6],
[64668, 27],
[64668, 33],
[64669, 12],
]
# Convert the table to array of hashes
hashes = tbl.map { |id, val|
{id => val}
}
# Merge the hashes
hashes.inject { |h1, h2|
h1.merge(h2) { |key,old,new|
(old.is_a?(Array) ? old : [old]) << new
}
}
# => {64664=>68, 64665=>65, 64666=>53, 64667=>68, 64668=>[6, 27, 33], 64669=>12}
values = [
[64664, 68],
[64665, 65],
[64666, 53],
[64667, 68],
[64668, 6],
[64668, 27],
[64668, 33],
[64669, 12],
]
# When key not present, create new empty array as default value
h = Hash.new{|h,k,v| h[k]=[]}
values.each{|(k,v)| h[k] << v}
p h #=>{64664=>[68], 64665=>[65], 64666=>[53], 64667=>[68], 64668=>[6, 27, 33], 64669=>[12]}

How to remove outside array from joining two separate arrays with collect

I have an object called Grade with two attributes material and strength.
Grade.all.collect { |g| g.material }
#=> [steel, bronze, aluminium]
Grade.all.collect { |g| g.strength }
#=> [75, 22, 45]
Now I would like to combine both to get the following output:
[steel, 75], [bronze, 22], [aluminium, 45]
I currently do this
Grade.all.collect{|e| e.material}.zip(Grade.all.collect{|g| g.strength})
#=> [[steel, 75], [bronze, 22], [aluminium, 45]]
Note: I do not want the outside array [[steel, 75], [bronze, 22], [aluminium, 45]]
Any thoughts?
Splat the array to a mere list.
*Grade.all.collect{ |g| [g.material, g.strength] }

How to Print to Different Files on the Fly?

How can I print the contents of a dynamically generated and sorted array to different files based on their content?
For example, let's say we have the following multi-dimensional array which is sorted by the second column
[ ['Steph', 'Allen', 29], ['Jon', 'Doe', 30], ['Jane', 'Doe', 30], ['Tom', 'Moore', 28] ]
The goal is to have 3 files:
last_name-Allen.txt <-- Contains Steph Allen 29
last_name-Doe.txt <-- Contains Jon Doe 30 Jane Doe 30
last_name-Moore.txt <-- Contains Tom Moore 28
ar = [ ['Steph', 'Allen', 29], ['Jon', 'Doe', 30], ['Jane', 'Doe', 30], ['Tom', 'Moore', 28] ]
grouped = ar.group_by{|el| el[1] }
# {"Allen"=>[["Steph", "Allen", 29]], "Doe"=>[["Jon", "Doe", 30], ["Jane", "Doe", 30]], "Moore"=>[["Tom", "Moore", 28]]}
grouped.each do |last_name, record|
File.open("last_name-#{last_name}.txt",'w') do |f|
f.puts record.join(' ')
end
end
If you wanted to do this in Groovy, you could use the groupBy method to get a map based on surname like so:
// Start with your list
def list = [ ['Steph', 'Allen', 29], ['Jon', 'Doe', 30], ['Jane', 'Doe', 30], ['Tom', 'Moore', 28] ]
// Group it by the second element...
def grouped = list.groupBy { it[ 1 ] }
println grouped
prints
[Allen:[[Steph, Allen, 29]], Doe:[[Jon, Doe, 30], [Jane, Doe, 30]], Moore:[[Tom, Moore, 28]]]
Then, iterate through this map, opening a new file for each surname and writing the content in (tab separated in this example)
grouped.each { surname, contents ->
new File( "last_name-${surname}.txt" ).withWriter { out ->
contents.each { person ->
out.writeLine( person.join( '\t' ) )
}
}
}
In ruby:
array.each{|first, last, age| open("last_name-#{last}.txt", "a"){|io| io.write([first, last, age, nil].join(" ")}}
It adds an extra space at the end of the file. This is to keep the space when there is another entity to be added.
use a hash with last name as the keys, then iterate over the hash and write each key/value pair to its own file.
In Groovy you can do this:
def a = ​[['Steph', 'Allen', 29], ['Jon', 'Doe', 30], ['Jane', 'Doe', 30], ['Tom', 'Moore', 28]]
a.each {
def name = "last_name-${it[1]}.txt"
new File(name) << it.toString()
}
Probably there is shorter (groovier) way to do this.
You can create a hash of with "second column" as key and value as "file handle". If you get the key in hash, just fetch file handle and write, else create new file handle and insert in hash.
This answer is in Ruby:
# hash which opens appropriate file on first access
files = Hash.new { |surname| File.open("last_name-#{surname}.txt", "w") }
list.each do |first, last, age|
files[last].puts [first, last, age].join(" ")
end
# closes all the file handles
files.values.each(&:close)

Ruby: How do I convert a string (ARGV) representation of integers and ranges to an array of integers

In Ruby, how can I take an array of tokens representing either integers or ranges and parse them into an array of integers that include each integer and each element in each range?
Example: Given input [ "5", "7-10", "24", "29-31"]
I'd like to produce output [ 5, 7, 8, 9, 10, 24, 29, 30, 31 ]
Thanks.
[ "5", "7-10", "24", "29-31"].map{|x| x.split("-").map{|val| val.to_i}}.map{ |y| Range.new(y.first, y.last).to_a}.flatten
Well, this might need a bit of work, actually. I'll take a crack at it now:
def parse_argv_list(list)
number_list = []
list.each do |item|
if item.include?('-')
bounds = item.split('-')
number_list.push((bounds[0].to_i..bounds[1].to_i).to_a)
else
number_list.push(item.to_i)
end
end
number_list.flatten
end
Something like the following should work. Just pass your input into the method and get an array of integers out. I've kept it intentionally verbose so you can see the logic.
Edit: I've added comments to the code.
def generate_output(input)
output = []
input.each do |element|
if element.include?("-")
# If the number is a range, split it
split = element.split("-")
# Take our split and turn it into a Ruby Range object, then an array
output << (split[0].to_i..split[1].to_i).to_a
else
# If it's not a range, just add it to our output array
output << element.to_i
end
end
# Since our ranges will add arrays within the output array, calling flatten
# on it will make it one large array with all the values in it.
return output.flatten
end
Running this code on your example input produces your example output, so I believe it's spot on.
>> [ "5", "7-10", "24", "29-31"].map{|x|x.gsub!(/-/,"..");x[".."]?(eval x).to_a : x.to_i}.flatten
=> [5, 7, 8, 9, 10, 24, 29, 30, 31]

Resources