How to merge embedded array with array in Ruby - ruby

I have arrays and nested arrays, such that:
a = [1,2]
b = [[3,4],[5,6]]
c = [7,8]
What's the best way to create
d = [[1,2],[3,4],[5,6],[7,8]]
in Ruby?
UPDATE:
The goal is to create a method below:
def foo([a,b,c])
--some logic that iterates through each array--
end

def foo(xss)
xss.flat_map { |xs| xs.first.is_a?(Array) ? xs : [xs] }
en
foo([a, b, c]) #=> [[1, 2], [3, 4], [5, 6], [7, 8]]

This will work assuming inputs are always arrays. Might have some side effects if any input is formatted differently.
def foo(*args)
args.map {|a| a.first.is_a?(Array) ? a : [a]}.inject([], :+)
end
foo([1,2], [[3,4],[5,6]], [7,8]) #=> [[1, 2], [3, 4], [5, 6], [7, 8]]

def foo(a,b,c)
final = []
a.first.is_a?(Array) ? a.each { |x| final << x } : final << a
b.first.is_a?(Array) ? b.each { |x| final << x } : final << b
c.first.is_a?(Array) ? c.each { |x| final << x } : final << c
final
end

I'd do :
a = [1,2]
b = [[3,4],[5,6]]
c = [7,8]
final_ary = [a,b,c].inject([]) do |out_ary,ary|
if ary.first.is_a?(Array)
out_ary.concat(ary)
else
out_ary.push(ary)
end
end
final_ary
# => [[1, 2], [3, 4], [5, 6], [7, 8]]

def foo (*args)
args.flatten.each_slice(2).to_a
end
foo(a,b,c) # => [[1, 2], [3, 4], [5, 6], [7, 8]]

A general solution:
def extract(arr, result=[])
return result if arr.empty?
arr.each {|e| (e.first.is_a? Array) ? result=extract(e,result) : result << e}
end
p extract( [ [1,2],[3,4,5],[6,7] ] )
#=> [[1, 2], [3, 4, 5], [6, 7]]
p extract( [ [1,2],[ [3,4],[5,6] ],[7,8]] )
#=> [[1, 2], [3, 4], [5, 6], [7, 8]]
p extract( [ [1,2],[ [3,4],[ [5,6],[7,8] ] ],[9,10,11] ] )
#=> [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10, 11]]
p extract( [ [1,2],[ [3,4],[ [5,6],[ [7,8],[9,10] ] ] ],[11,12] ])
#=> [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12]]

Related

Ruby, remove super-arrays

If I have an array of arrays, A, and want to get rid of all arrays in A who also have a sub-array in A, how would I do that. In this context, array_1 is a sub-array of array_2 if array_1 - array_2 = []. In the case that multiple arrays are simply rearranged versions of the same elements, bonus points if you can get rid of all but one of them, but you can handle this however you want if it's easier.
In python, I could easily use comprehension, with A being a set of frozen sets :
A = {a for a in A if all(b-a for b in A-{a})}
Is there a simple way to write this in ruby? I don't care if the order of A or it's arrays are preserved at all. Also, in my program, none of the arrays have duplicate elements, if that makes things any easier/faster.
Example
A = [[1,6],[1,2],[2,4],[3,5],[1,3,6],[2,3,6]]
# [1,6] is a subarray of [1,3,6], so [1,3,6] should be removed
remove_super_arrays(A)
> A = [[1,6],[1,2],[2,4],[3,5],[2,3,6]]
A = [[1,2,4],[2,3,4],[1,4,5],[2,6]]
# although there is overlap, there are no subarrays, so nothing should be removed
remove_super_arrays(A)
> A = [[1,2,4],[2,3,4],[1,4,5],[2,6]]
A = [[1],[2,1,3],[2,4],[1,4]]
# [1] is a subarray of [2,1,3] and [1,4]
remove_super_arrays(A)
> A = [[1],[2,4]]
Code
def remove_super_arrays(arr)
order = arr.each_with_index.to_a.to_h
arr.sort_by(&:size).reject.with_index do |a,i|
arr[0,i].any? { |aa| (aa.size < a.size) && (aa-a).empty? }
end.sort_by { |a| order[a] }
end
Examples
remove_super_arrays([[1,6],[1,2],[2,4],[3,5],[1,3,6],[2,3,6]] )
#=> [[1,6],[1,2],[2,4],[3,5],[2,3,6]]
remove_super_arrays([[1,2,4],[2,3,4],[1,4,5],[2,6]])
#=> [[1,2,4],[2,3,4],[1,4,5],[2,6]]
remove_super_arrays([[1],[2,1,3],[2,4],[1,4]])
#=> [[1],[2,4]]
Explanation
Consider the first example.
arr = [[1,6],[1,2],[2,4],[3,5],[1,3,6],[2,3,6]]
We first save the positions of the elements of a
order = arr.each_with_index.to_a.to_h # save original order
#=> {[1, 6]=>0, [1, 2]=>1, [2, 4]=>2, [3, 5]=>3, [1, 3, 6]=>4, [2, 3, 6]=>5}
Then reject elements of arr:
b = arr.sort_by(&:size)
#=> [[1, 6], [1, 2], [2, 4], [3, 5], [1, 3, 6], [2, 3, 6]]
c = b.reject.with_index do |a,i|
arr[0,i].any? { |aa| (aa.size < a.size) && (aa-a).empty? }
end
#=> [[1, 6], [1, 2], [2, 4], [3, 5], [2, 3, 6]]
Lastly, reorder c to correspond to the original ordering of the elements of arr.
c.sort_by { |a| order[a] }
#=> [[1, 6], [1, 2], [2, 4], [3, 5], [2, 3, 6]]
which in this case happens to be the same order as the elements of c.
Let's look more carefully at the calculation of c:
enum1 = b.reject
#=> #<Enumerator: [[1, 6], [1, 2], [2, 4], [3, 5], [1, 3, 6],
# [2, 3, 6]]:reject>
enum2 = enum1.with_index
#=> #<Enumerator: #<Enumerator: [[1, 6], [1, 2], [2, 4], [3, 5],
# [1, 3, 6], [2, 3, 6]]:reject>:with_index>
The first element is generated by the enumerator enum2 and passed to the block and assigned as values of the block variables:
a, i = enum2.next
#=> [[1, 6], 0]
a #=> [1, 6]
i #=> 0
The block calculation is then performed:
d = arr[0,i]
#=> []
d.any? { |aa| (aa.size < a.size) && (aa-a).empty? }
#=> false
so a[0] is not rejected. The next pair passed to the block by enum2 is [[1, 2], 1]. That value is retained as well, but let's skip ahead to the last element passed to the block by enum2:
a, i = enum2.next
#=> [[1, 2], 1]
a, i = enum2.next
#=> [[2, 4], 2]
a, i = enum2.next
#=> [[3, 5], 3]
a, i = enum2.next
#=> [[1, 3, 6], 4]
a #=> [1, 3, 6]
i #=> 4
Perform the block calculation:
d = arr[0,i]
#=> [[1, 6], [1, 2], [2, 4], [3, 5]]
d.any? { |aa| (aa.size < a.size) && (aa-a).empty? }
#=> true
As true is returned, a is rejected. In the last calculation the first element of d is passed to the block and the following calculation is performed:
aa = [1, 6]
(aa.size < a.size)
#=> 2 < 3 => true
(aa-a).empty?
#=> ([1, 6] - [1, 3, 6]).empty? => [].empty? => true
As true && true #=> true, a ([1, 3, 6]) is rejected.
Alternative calculation
The following is a closer match to the OP's Python equivalent, but less efficient:
def remove_super_arrays(arr)
arr.select do |a|
(arr-[a]).all? { |aa| aa.size > a.size || (aa-a).any? }
end
end
or
def remove_super_arrays(arr)
arr.reject do |a|
(arr-[a]).any? { |aa| (aa.size < a.size) && (aa-a).empty? }
end
end
This was a nice exercise for me. I have used the logic from here.
My code iterates over each subarray (except the first), then there is the magic substraction using the first index, when it is empty the other array contained both numbers.
def remove_super_arrays(arr)
arr.each_with_index.with_object([]) do |(sub_array, index), result|
next if index == 0
result << sub_array unless (arr.first - sub_array).empty?
end.unshift(arr.first)
end
arr = [[1,6],[1,2],[2,4],[3,5],[1,3,6],[2,3,6]]
p remove_super_arrays(arr)
#=> [[1, 6], [1, 2], [2, 4], [3, 5], [2, 3, 6]]

Groovy for Rubyists - #each_slice

In ruby I have #each_slice...
(1..10).each_slice(3).to_a
=> [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
I'm looking for an elegant way to do the same in Groovy and this is all I got so far as a beginner:
arr = []
list = (1..10) as Queue
while(!list.isEmpty()) {
sub_arr = []
3.times { sub_arr << list.poll() }
sub_arr.removeAll([null])
arr << sub_arr
}
arr
Result: [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
You need collate in groovy
(1..10).collate(3)

Ruby - array flatten

I have an n-depth array where n is a variable greater than or equal to 2:
[[1,1],[[1,1],[1,1]]]
I want to flatten this array to have exactly 2 depth, like this:
[[1,1],[1,1],[1,1]]
Can anyone think of a good way to achieve that?
This should do it.
def flatten_after_first(arr)
arr.flat_map { |a| a.first.is_a?(Array) ? a.map(&:flatten) : [a] }
end
flatten_after_first [[1,1],[[1,1],[1,1]]]
#=> [[1, 1], [1, 1], [1, 1]]
flatten_after_first [[1,1], [[2,2], [2,2]], [[[3,3], [3,3]], [[3,3], [3,3]]]]
#=> [[1, 1], [2, 2], [2, 2], [3, 3, 3, 3], [3, 3, 3, 3]]
May be this will help
def flat(array)
array.each do |item|
if item.is_a?(Array) && item.flatten.count != item.count
flat(item)
else
$arr << item
end
end
end
###
$arr = []
collection = [[1, 1], [[1, 1], [1, 1], [[1, 2], [1, 2, 3]]]]
flat(collection)
puts $arr.inspect
=> [[1, 1], [1, 1], [1, 1], [1, 2], [1, 2, 3]]
$arr = []
collection = [[1,1],[[[1,1],[1,1]],[1,1]]]
flat(collection)
$arr
=> [[1, 1], [1, 1], [1, 1], [1, 1]]
Try this:
def depth_first_flatten array
result = []
array.each do |element|
if element.first.is_a? Array
result += deph(element)
else
result << element
end
end
result
end
# array = [[1,2],[[3,4],[5,6]]]
# depth_first_flatten(array)
#
# OUTPUT: [[1, 2], [3, 4], [5, 6]]

divide an array into multiple arrays, each array in which the number of units is determined by the size

For example,i have an array :[2,4,6,7,9,12,1],i want to divide it by the size [2,2,3]
the output that i want is:[[2,4],[6,7],[9,12,1]]
i have tried:
a=[2,4,6,7,9,12,1]
b=[2,2,3]
c=[]
b.each{|m|c<<a.shift(m)}
c
is there an easier way to do it?
You can use Enumerable#map:
a = [2,4,6,7,9,12,1]
b = [2,2,3]
c = b.map { |m| a.shift(m) }
c
# => [[2, 4], [6, 7], [9, 12, 1]]
The way looks correct, you can do it more succinctly using map instead of each:
c = b.map{|m| a.shift(m)}
Or, using &method shorthand:
c = b.map(&a.method(:shift))
# => [[2, 4], [6, 7], [9, 12, 1]]
Here are two ways that do not destroy the original array, as Array#shift does:
a=[2,4,6,7,9,12,1]
b=[2,2,3]
Method #1
cum = 0
b.map { |n| a[cum..(cum+=n)-1] }
#=> [[2, 4], [6, 7], [9, 12, 1]]
Method #2
cum = 0
b.map { |n| a.values_at(*(cum..(cum+=n)-1)) }
#=> [[2, 4], [6, 7], [9, 12, 1]]
You can have:
r = [2, 4, 6, 7, 9, 12, 1]
s = [2, 2, 3].map do |e|
r.shift(e)
end
p s
Output:
[[2, 4], [6, 7], [9, 12, 1]]
this will work
2.1.0 :029 > a=[2,4,6,7,9,12,1]
=> [2, 4, 6, 7, 9, 12, 1]
2.1.0 :030 > b = [2,2,3]
=> [2, 2, 3]
2.1.0 :031 > b.inject([]){|result, value| result << a.take(value) }
=> [[2, 4], [2, 4], [2, 4, 6]]
2.1.0 :032 >

Array of arrays into array of hashes

i want to convert in ruby
[[1, 1], [2, 3], [3, 5], [4, 1], [1, 2], [2, 3], [3, 5], [4, 1]]
into
[{1=>1}, {2=>3}, {3=>5}, {4=>1}, {1=>2}, {2=>3}, {3=>5}, {4=>1}]
and after this to obtain sum of all different keys:
{1=>3,2=>6,3=>10,4=>2}
For the second question
sum = Hash.new(0)
original_array.each{|x, y| sum[x] += y}
sum # => {1 => 3, 2 => 6, 3 => 10, 4 => 2}
Functional approach:
xs = [[1, 1], [2, 3], [3, 5], [4, 1], [1, 2], [2, 3], [3, 5], [4, 1]]
Hash[xs.group_by(&:first).map do |k, pairs|
[k, pairs.map { |x, y| y }.inject(:+)]
end]
#=> {1=>3, 2=>6, 3=>10, 4=>2}
Using Facets is much simpler thanks to the abstractions map_by (a variation of group_by) and mash (map + Hash):
require 'facets'
xs.map_by { |k, v| [k, v] }.mash { |k, vs| [k, vs.inject(:+)] }
#=> {1=>3, 2=>6, 3=>10, 4=>2}
You don't need the intermediate form.
arrays = [[1, 1], [2, 3], [3, 5], [4, 1], [1, 2], [2, 3], [3, 5], [4, 1]]
aggregate = arrays.each_with_object Hash.new do |(key, value), hash|
hash[key] = hash.fetch(key, 0) + value
end
aggregate # => {1=>3, 2=>6, 3=>10, 4=>2}
arr= [[1, 1], [2, 3], [3, 5], [4, 1], [1, 2], [2, 3], [3, 5], [4, 1]]
final = Hash.new(0)
second_step = arr.inject([]) do |arr,inner|
arr << Hash[*inner]
final[inner.first] += inner.last
arr
end
second_step
#=> [{1=>1}, {2=>3}, {3=>5}, {4=>1}, {1=>2}, {2=>3}, {3=>5}, {4=>1}]
final
#=> {1=>3, 2=>6, 3=>10, 4=>2}
if you directly only need the last step
arr.inject(Hash.new(0)){|hash,inner| hash[inner.first] += inner.last;hash}
=> {1=>3, 2=>6, 3=>10, 4=>2}

Resources