Iterate over array of array - ruby

I have an array of arrays like the following:
=> [[1,2,3,4,5],[6,7,8,9,10],[11,12,13,14,15]]
I want to rearrange it by order of elements in the inner array, e.g.:
=> [[1,6,11],[2,7,12],[3,8,13],[4,9,14],[5,10,15]]
How can I achieve this?
I know I can iterate an array of arrays like
array1.each do |bla,blo|
#do anything
end
But the side of inner arrays isn't fixed.

p [[1,2,3,4,5],[6,7,8,9,10],[11,12,13,14,15]].transpose
#=> [[1, 6, 11], [2, 7, 12], [3, 8, 13], [4, 9, 14], [5, 10, 15]]

use transpose method on Array
a = [[1,2,3,4,5],[6,7,8,9,10],[11,12,13,14,15]]
a.transpose
#=> [[1, 6, 11], [2, 7, 12], [3, 8, 13], [4, 9, 14], [5, 10, 15]]
Note that this only works if the arrays are of all the same length.
If you want to handle transposing arrays that have different lengths to each other, something like this should do it
class Array
def safe_transpose
max_size = self.map(&:size).max
self.dup.map{|r| r << nil while r.size < max_size; r}.transpose
end
end
and will yield the following
a = [[1,2,3,4,5],[6,7,8,9,10],[11,12,13,14,15,16]]
a.safe_transpose
#=> [[1, 6, 11], [2, 7, 12], [3, 8, 13], [4, 9, 14], [5, 10, 15], [nil, nil, 16]]

Related

Repeating a loop when it reaches the end

I am trying to conceptualize the iteration of two loops
numbers_array = [1,2,3,4,5,6,7,8,9,10]
add_to_array = [1,2,3,4]
While the numbers_array iterates, add_to_array iterates simultaneously adding both elements together at the same time. The caveat is once add_to_array reaches the end, it starts over adding its element to the next index in numbers_array. So at numbers_array[4] we would be adding add_to_array[0] then adding numbers_array[5] to add_to_array[1] and so on. This process would repeat until we reach the end of the numbers_array.
Any input would be greatly appreciated!
You are looking for Enumerable#zip and Enumerable#cycle:
numbers_array = [1,2,3,4,5,6,7,8,9,10]
#⇒ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
add_to_array = [1,2,3,4]
#⇒ [1, 2, 3, 4]
numbers_array.zip(add_to_array.cycle)
#⇒ [[1, 1], [2, 2], [3, 3], [4, 4], [5, 1],
# [6, 2], [7, 3], [8, 4], [9, 1], [10, 2]]
Now do whatever you want with the array returned. E.g. to reduce the zipped result summing elements, map ro Enumerable#sum:
numbers_array.zip(add_to_array.cycle).map(&:sum)
#⇒ [2, 4, 6, 8, 6, 8, 10, 12, 10, 12]
It works by using the % operator to cycle through the indexes.
numbers_array = [1,2,3,4,5,6,7,8,9,10]
add_to_array = [1,2,3,4]
numbers_array.map.with_index do |n, i|
n + add_to_array[i % add_to_array.length]
end
A cool method that's similar, if you didn't want to start over at the next array, would be .zip
https://apidock.com/ruby/Array/zip
add_to_array.zip(*numbers_array.each_slice(add_to_array.size)).
map { |a| a.sum { |e| e.to_i } }
#=> [16, 20, 13, 16]
e.to_i is needed to convert nil values to zeros. See NilClass#to_i.
Another option:
numbers_array.map { |e| e + add_to_array.rotate!.last }
# => [2, 4, 6, 8, 6, 8, 10, 12, 10, 12]
Drawback: add_to_array is mutated by rotate!

Find and return the longest array in a nested array with its size

I want to write a function that takes in a nested array and return the size of the longest array and itself.
max_with_size([]) # [0, []]
max_with_size([2,3,4]) # [3, [2, 3, 4]]
max_with_size([1,[2,3,4]]) # [3, [2, 3, 4]]
max_with_size([[5,[6],[7,8,9],10,11]]) # [5, [5, [6], [7, 8, 9], 10, 11]]
max_with_size([[1,[2,3,4]],[[[5,[6],[7,8,9],10,11]]]]) # [5, [5, [6], [7, 8, 9], 10, 11]]
So far I've got this
def max_with_size (ary)
max_size = ary.size
max_ary = ary
ary.each { |elem|
if elem.is_a? Array
if elem.size > max_size
max_size = max_with_size(elem)[0]
max_ary = max_with_size(elem)[1]
end
end
}
[max_size, max_ary]
end
It works fine for the first 4 cases, but the 5th fails and only delivers this
max_with_size([[1,[2,3,4]],[[[5,[6],[7,8,9],10,11]]]]) # [2, [[1, [2, 3, 4]], [[[5, [6], [7, 8, 9], 10, 11]]]]]
How can I achieve the wanted result?
The following code should print the desired result. I explained code with Inline comments.
#Initialize #max to empty array, #max is an array with two elements, like this: [max_array_size, max_array]
#max = []
def max_with_size(array)
# when #max is empty or when array size is greater than what is store in #max, store array size and array contents in #max
(#max = [array.size, array]) if #max.empty? || (#max[0] < array.size)
#Iterate through each element in array
array.each do |x|
#Skip to next element if x is not an array
next unless x.is_a? Array
#Recursively find max of array x
max_with_size(x)
end
#max
end
Code
def max_arr(arr)
[arr, *arr.each_with_object([]) {|e,a| a << max_arr(e) if e.is_a?(Array) && e.any?}].
max_by(&:size)
end
Examples
examples = [[],
[2,3,4],
[1,[2,3,4]],
[[5,[6],[7,8,9],10,11]],
[[1,[2,3,4]],[[[5,[6],[7,8,9],10,11]]]],
[1, [2, [3, 4, [6, 7, 8, 9, 10], [11, 12]], 13]]]
examples.each do |arr|
a = max_arr(arr)
puts "\n#{arr}\n \#=> #{a.size}, #{a}"
end·
[]
#=> 0, []
[2, 3, 4]
#=> 3, [2, 3, 4]
[1, [2, 3, 4]]
#=> 3, [2, 3, 4]
[[5, [6], [7, 8, 9], 10, 11]]
#=> 5, [5, [6], [7, 8, 9], 10, 11]
[[1, [2, 3, 4]], [[[5, [6], [7, 8, 9], 10, 11]]]]
#=> 5, [5, [6], [7, 8, 9], 10, 11]
[1, [2, [3, 4, [6, 7, 8, 9, 10], [11, 12]], 13]]
#=> 5, [6, 7, 8, 9, 10]

Sum arrays by index using functional programming

I have several equally sized arrays containing numbers (matrix), and I want to sum them all by their index (matrix columns).
For example, if I have:
data = [[1, 2, 3, 4], [5, 6, 7, 8]]
I want to get the result:
column_totals = [6, 8, 10, 12]
I understand how to do this imperatively, but how would I do this using functional programming? (Preferably, using built in Enumerable methods.) I wasn't very happy with any of the functional solutions I came up with.
I ended up using the Matrix class:
require 'matrix'
data = [[1, 2, 3, 4], [5, 6, 7, 8]]
matrix = Matrix[*data]
# Added sum method to Vector class.
matrix.column_vectors.map { |column| column.sum }
I'm happy enough with that solution, but am frustrated that I couldn't wrap my mind around a good functional solution without relying on the Matrix class.
Specifically, I was tripped up on the step to transform this:
data = [[1, 2, 3, 4], [5, 6, 7, 8]]
into this:
columns = [[1, 5], [2, 6], [3, 7], [4, 8]]
Any reason to not use Array#transpose?
data.transpose
# => [[1, 5], [2, 6], [3, 7], [4, 8]]
Alternatively, if you only want to use Enumerable methods to iterate, you can do
columns = data.inject(Array.new(data.first.length){[]}) { |matrix,row|
row.each_with_index { |e,i| matrix[i] << e }
matrix }
# => [[1, 5], [2, 6], [3, 7], [4, 8]]
or
columns = data.flatten.group_by.with_index { |e,i| i % data[0].size }.values
# => [[1, 5], [2, 6], [3, 7], [4, 8]]
To sum:
columns.map { |row| row.inject :+ }
# => [6, 8, 10, 12]
Thirdly, if you don't need the intermediate columns:
data.inject { |s,r| s.zip(r).map { |p| p.inject :+ } }
# => [6, 8, 10, 12]
You could use Array#transpose, as #Matt hinted, and then sum the arrays inside:
data.transpose.map {|a| a.reduce(:+) }

How can I combine elements of an array that have a common element?

I want to take an array of number pairs:
[[2, 3], [2, 15], [3, 15], [7, 8], [8, 7], [11, 15]]
and if two of these number pairs have a number in common, combine them and display the unique values, ultimately creating this array:
[[2, 3, 15, 11], [7, 8]]
Order of the numbers in the output array is not important. What would be the best solution?
The answer was corrected following Howard's comment.
[[2, 3], [2, 15], [3, 2], [3, 15], [7, 8], [8, 7], [11, 15], [15, 2], [15, 3], [15, 11]]
.each_with_object([]) do |e1, a|
a.select{|e2| (e1 & e2).any?}.each{|e2| e1.concat(a.delete(e2))}
e1.uniq!
a.push(e1)
end
# => [[8, 7], [15, 11, 3, 2]]
It's really just another mapping and sorting problem,:
array = [[2, 3], [2, 15], [3, 2], [3, 15], [7, 8], [8, 7], [11, 15], [15, 2], [15, 3], [15, 11]]
array.map{|a| array.each{|x| a |= x if (x & a).any?}; a.sort}.uniq
#=> [[2, 3, 11, 15], [7, 8]]
Not as elegant as #sawa's solution - but works for all the cases:
def includes? item, groups
groups.each{|sub|
return true if sub.include?(item)
}
false
end
def add groups, match, item
groups.each{|sub|
if sub.include?(match)
sub << item
sub.uniq!
return
end
}
end
def iterate arr
groups = []
grp = []
arr.each do |a,b|
grp = [a,b] if grp.empty?
if includes? a, groups
add groups, a, b
elsif includes? b, groups
add groups, b, a
else
groups << [a,b]
end
end
groups
end
arr = [[2, 3], [2, 15], [3, 2], [3, 15], [7, 8], [8, 7], [11, 15], [15, 2], [15, 3], [15, 11]]
p iterate(arr)
OUTPUT
[[2, 3, 15, 11], [7, 8]]
A recursive solution:
def doit(a,b)
return a unless b
h = b.group_by {|e| (a[-1] & e).any?}
h[true] ? a[-1] |= h[true].flatten : a << h[false].pop
doit(a, h[false])
end
arr = [[2, 3], [2, 15], [3, 2], [3, 15], [16, 21], [7, 8], [8, 7], \
[21, 44], [11, 15], [15, 2], [15, 3], [15, 11], [8, 9]]
doit([arr.pop], arr) # => [[8, 9, 7], [15, 11, 2, 3], [21, 44, 16]]

Remove/squash entries in a vertical hash

I have a grid that represents an X, Y matrix, stored as a hash here.
Some points on the X Y matrix may have values (as type string), and some may not.
A typical grid could look like this:
{[9, 5]=>"Alaina", [10, 3]=>"Courtney", [11, 1]=>"Gladys", [8, 7]=>"Alford", [14, 11]=>"Lesley", [17, 2]=>"Lawson", [0, 5]=>"Katrine", [2, 1]=>"Tyra", [3, 3]=>"Fredy", [1, 7]=>"Magnus", [6, 9]=>"Nels", [7, 11]=>"Kylie", [11, 0]=>"Kellen", [10, 2]=>"Johan", [14, 10]=>"Justice", [0, 4]=>"Barton", [2, 0]=>"Charley", [3, 2]=>"Magnolia", [1, 6]=>"Maximo", [7, 10]=>"Olga", [19, 5]=>"Isadore", [16, 3]=>"Delfina", [17, 1]=>"Noe", [20, 11]=>"Francis", [10, 5]=>"Creola", [9, 3]=>"Bulah", [8, 1]=>"Lempi", [11, 7]=>"Raquel", [13, 11]=>"Jace", [1, 5]=>"Garth", [3, 1]=>"Ernest", [2, 3]=>"Malcolm", [0, 7]=>"Alejandrin", [7, 9]=>"Marina", [6, 11]=>"Otilia", [16, 2]=>"Hailey", [20, 10]=>"Brandt", [8, 0]=>"Madeline", [9, 2]=>"Leanne", [13, 10]=>"Jenifer", [1, 4]=>"Humberto", [3, 0]=>"Nicholaus", [2, 2]=>"Nadia", [0, 6]=>"Abigail", [6, 10]=>"Zola", [20, 5]=>"Clementina", [23, 3]=>"Alvah", [19, 11]=>"Wallace", [11, 5]=>"Tracey", [8, 3]=>"Hulda", [9, 1]=>"Jedidiah", [10, 7]=>"Annetta", [12, 11]=>"Nicole", [2, 5]=>"Alison", [0, 1]=>"Wilma", [1, 3]=>"Shana", [3, 7]=>"Judd", [4, 9]=>"Lucio", [5, 11]=>"Hardy", [19, 10]=>"Immanuel", [9, 0]=>"Uriel", [8, 2]=>"Milton", [12, 10]=>"Elody", [5, 10]=>"Alexanne", [1, 2]=>"Lauretta", [0, 0]=>"Louvenia", [2, 4]=>"Adelia", [21, 5]=>"Erling", [18, 11]=>"Corene", [22, 3]=>"Haskell", [11, 11]=>"Leta", [10, 9]=>"Terrence", [14, 1]=>"Giuseppe", [15, 3]=>"Silas", [12, 5]=>"Johnnie", [4, 11]=>"Aurelie", [5, 9]=>"Meggie", [2, 7]=>"Phoebe", [0, 3]=>"Sister", [1, 1]=>"Violet", [3, 5]=>"Lilian", [18, 10]=>"Eusebio", [11, 10]=>"Emma", [15, 2]=>"Theodore", [14, 0]=>"Cassidy", [4, 10]=>"Edmund", [2, 6]=>"Claire", [0, 2]=>"Madisen", [1, 0]=>"Kasey", [3, 4]=>"Elijah", [17, 11]=>"Susana", [20, 1]=>"Nicklaus", [21, 3]=>"Kelsie", [10, 11]=>"Garnett", [11, 9]=>"Emanuel", [15, 1]=>"Louvenia", [14, 3]=>"Otho", [13, 5]=>"Vincenza", [3, 11]=>"Tate", [2, 9]=>"Beau", [5, 7]=>"Jason", [6, 1]=>"Jayde", [7, 3]=>"Lamont", [4, 5]=>"Curt", [17, 10]=>"Mack", [21, 2]=>"Lilyan", [10, 10]=>"Ruthe", [14, 2]=>"Georgianna", [4, 4]=>"Nyasia", [6, 0]=>"Sadie", [16, 11]=>"Emil", [21, 1]=>"Melba", [20, 3]=>"Delia", [3, 10]=>"Rosalee", [2, 8]=>"Myrtle", [7, 2]=>"Rigoberto", [14, 5]=>"Jedidiah", [13, 3]=>"Flavie", [12, 1]=>"Evie", [8, 9]=>"Olaf", [9, 11]=>"Stan", [20, 2]=>"Judge", [5, 5]=>"Cassie", [7, 1]=>"Gracie", [6, 3]=>"Armando", [4, 7]=>"Delia", [3, 9]=>"Marley", [16, 10]=>"Robyn", [2, 11]=>"Richie", [12, 0]=>"Gilberto", [13, 2]=>"Dedrick", [9, 10]=>"Liam", [5, 4]=>"Jabari", [7, 0]=>"Enola", [6, 2]=>"Lela", [3, 8]=>"Jade", [2, 10]=>"Johnson", [15, 5]=>"Willow", [12, 3]=>"Fredrick", [13, 1]=>"Beau", [9, 9]=>"Carlie", [8, 11]=>"Daisha", [6, 5]=>"Declan", [4, 1]=>"Carolina", [5, 3]=>"Cruz", [7, 7]=>"Jaime", [0, 9]=>"Anthony", [1, 11]=>"Esta", [13, 0]=>"Shaina", [12, 2]=>"Alec", [8, 10]=>"Lora", [6, 4]=>"Emely", [4, 0]=>"Rodger", [5, 2]=>"Cedrick", [0, 8]=>"Collin", [1, 10]=>"Armani", [16, 5]=>"Brooks", [19, 3]=>"Eleanora", [18, 1]=>"Alva", [7, 5]=>"Melissa", [5, 1]=>"Tabitha", [4, 3]=>"Aniya", [6, 7]=>"Marc", [1, 9]=>"Marjorie", [0, 11]=>"Arvilla", [19, 2]=>"Adela", [7, 4]=>"Zakary", [5, 0]=>"Emely", [4, 2]=>"Alison", [1, 8]=>"Lorenz", [0, 10]=>"Lisandro", [17, 5]=>"Aylin", [18, 3]=>"Giles", [19, 1]=>"Kyleigh", [8, 5]=>"Mary", [11, 3]=>"Claire", [10, 1]=>"Avis", [9, 7]=>"Manuela", [15, 11]=>"Chesley", [18, 2]=>"Kristopher", [24, 3]=>"Zola", [8, 4]=>"Pietro", [10, 0]=>"Delores", [11, 2]=>"Timmy", [15, 10]=>"Khalil", [18, 5]=>"Trudie", [17, 3]=>"Rafael", [16, 1]=>"Anthony"}
What I need to do though, is basically remove all the empty entries.
Let's say [17,3] => Raphael does not have an element in front of if (let's say - no [16,3] exists) then [17,3] should become [16,3] etc.
So basically all empty items will be popped off the vertical (row) structure of the hash.
Are there functions I should have a look at or is there an easy squash-like method that would just remove blanks and adjust and move other items?
Thanks in advance for your help.
Here's my shot at it (probably not the fastest possible):
data.group_by{|k,v| k[1]}.inject({}){|a,(k,v)|
v.sort_by{|i| i[0][0]}.each_with_index{|elem,i|
a[[i,elem[0][1]]] = elem[1]
}
a
}
And here's input subset suitable for testing:
{[9, 5]=>"Alaina", [10, 3]=>"Courtney", [11, 5]=>"Gladys", [8, 5]=>"Alford"}
which should result with:
{[0, 5]=>"Alford", [1, 5]=>"Alaina", [2, 5]=>"Gladys", [0, 3]=>"Courtney"}
Step-by-step explanation (you can experiment in irb):
We need group-by because we should process each row separately, so first we group records by row, making a hash of arrays (let's call each of these arrays r)
data.group_by{|k,v| k[1]}
# => {5=>[[[9, 5], "Alaina"], [[11, 5], "Gladys"], [[8, 5], "Alford"]], 3=>[[[10, 3], "Courtney"]]}
inject is there so we can create output hash, we start with the empty one and we'll add "transformed" element at a time. Transformation is simply replacing the column indexes with "compacted" indexes, so the idea is to somehow map array [8, 9, 11] to [0, 1, 2] (when processing row 5). That's why we need to sort array r (let's call the sorted array rs):
r = [[[9, 5], "Alaina"], [[11, 5], "Gladys"], [[8, 5], "Alford"]]
rs = r.sort_by{|i| i[0][0]}
#=> [[[8, 5], "Alford"], [[9, 5], "Alaina"], [[11, 5], "Gladys"]]
And then assign each element in rs an index, starting from 0. We use each_with_index for that, so that in elem, i we get pairs such as
[[8, 5], "Alford"], 0
[[9, 5], "Alaina"], 1
[[11, 5], "Gladys"], 2
and now we have all needed numbers to fill in the resulting hash a:
a[[i,elem[0][1]]] = elem[1]
so
a[[0,5]] = 'Alford'
a[[1,5]] = 'Alaina'
etc.
We need to return a (accumulator) from a block for inject to work properly.
That should be it. :)
Ok... I think i get what you want it to do; Here is how I would do it:
data = # the hash in the format above
rows = [] # a new array
# then place each row in the entry of rows corresponding to it's y value at
# the index of it's x value
data.each_pair {|key, value| rows[key[1]] ||= []; rows[key[1]][key[0]] = value }
# compact the x values (if there is an adjacent lower x available move to it)
# this is what i believe you meant in your example
rows.map(&:compact!)
# clear out the old data
data = {}
# fill the hash back up using the new compacted data
rows.each_with_index { |xs, y| xs.each_with_index { |entry, x| data[[x,y]] = entry } }
tested it on your data (along with adding some more difficult x values) and it seems to work :)
Roja

Resources