How to flatten innermost array - ruby

I have this array:
[[["a", "c"], "e"],
[["a", "c"], "f"],
[["a", "c"], "g"],
[["a", "d"], "e"],
[["a", "d"], "f"],
[["a", "d"], "g"],
[["b", "c"], "e"],
[["b", "c"], "f"],
[["b", "c"], "g"],
[["b", "d"], "e"],
[["b", "d"], "f"],
[["b", "d"], "g"]]
I would like to turn it into this:
[["a", "c", "e"],
["a", "c", "f"],
["a", "c", "g"],
["a", "d", "e"],
["a", "d", "f"],
["a", "d", "g"],
["b", "c", "e"],
["b", "c", "f"],
["b", "c", "g"],
["b", "d", "e"],
["b", "d", "f"],
["b", "d", "g"]]
How can I do this with Ruby? I have looked at flatten by it seems to work from the outside in, not inside out.

You could use flatten and map:
ar.map! {|i| i.flatten}
# => [["a", "c", "e"],
# ["a", "c", "f"],
# ["a", "c", "g"],
# ["a", "d", "e"],
# ["a", "d", "f"],
# ["a", "d", "g"],
# ["b", "c", "e"],
# ["b", "c", "f"],
# ["b", "c", "g"],
# ["b", "d", "e"],
# ["b", "d", "f"],
# ["b", "d", "g"]]
Another one-liner would be :
ar.map!(&:flatten)
# => [["a", "c", "e"],
# ["a", "c", "f"],
# ["a", "c", "g"],
# ["a", "d", "e"],
# ["a", "d", "f"],
# ["a", "d", "g"],
# ["b", "c", "e"],
# ["b", "c", "f"],
# ["b", "c", "g"],
# ["b", "d", "e"],
# ["b", "d", "f"],
# ["b", "d", "g"]]

or try arr.each {|i| i.flatten!}

Related

Add elements of an array to a specific index of each array within an array of arrays

I have an array of arrays that serves as a table of data, and am trying to add an extra array as though adding an extra column to the table.
For simplicity, suppose the first array is a
a = [["a", "b", "c"], ["e", "f", "g"], ["i", "j", "k"]]
and the second array is b
b = ["d", "h", "l"]
the desired output is:
c = [["a", "b", "c", "d"], ["e", "f", "g", "h"], ["i", "j", "k", "l"]]
I have tried using + and some attempts at using map but cannot get it
You can zip them together which will create array elements like [["a", "b", "c"], "d"] and then just flatten each element.
a.zip(b).map(&:flatten)
#=> [["a", "b", "c", "d"], ["e", "f", "g", "h"], ["i", "j", "k", "l"]]
Answer improved as per Cary's comment. I think he's done Ruby stuff before.
a.zip(b).map { |arr,e| arr + [e] }
#=> [["a", "b", "c", "d"],
# ["e", "f", "g", "h"],
# ["i", "j", "k", "l"]]
The intermediate calculation is as follows.
a.zip(b)
#=> [[["a", "b", "c"], "d"],
# [["e", "f", "g"], "h"],
# [["i", "j", "k"], "l"]]
See Array#zip.
You can use #each_with_index combined with #map to iterate over the array a and append respective elements of array b
> a.each_with_index.map{|e, i| e | [b[i]] }
=> [["a", "b", "c", "d"], ["e", "f", "g", "h"], ["i", "j", "k", "l"]]

How to make all possible pairs from an array

a = ["a","b","c"]
a.each_cons(2).to_a # => [["a", "b"], ["b", "c"]]
I want three possible pairs from this array
You can use Array#permutation to generate sub-arrays with all combination.
a.permutation(2).to_a
# => [["a", "b"], ["a", "c"], ["b", "a"], ["b", "c"], ["c", "a"], ["c", "b"]]
Following this you can pick 3 random arrays using Array#sample (assuming you want to pick random sub-arrays). Here:
a.permutation(2).to_a.sample(3)
# => [["c", "b"], ["c", "a"], ["b", "c"]]
Try this:
a = ["a","b","c"]
a.permutation(2).to_a.take(3)
# => [["a", "b"], ["a", "c"], ["b", "a"]]
a = ["a","b","c"]
a.combination(2).to_a # => [["a", "b"], ["a", "c"], ["b", "c"]]

Convert multiple level array to single level array?

I want to convert this array
[[["b", "c"], ["c", "d"]], [["v", "e"], ["r", "g"]]]
into
[["b", "c"], ["c", "d"], ["v", "e"], ["r", "g"]]
How can I convert this ?
Array#flatten takes an optional level:
The optional level argument determines the level of recursion to flatten
Example:
[[["b", "c"], ["c", "d"]], [["v", "e"], ["r", "g"]]].flatten(1)
#=> => [["b", "c"], ["c", "d"], ["v", "e"], ["r", "g"]]
arr = []
a = [[["b", "c"], ["c", "d"]], [["v", "e"], ["r", "g"]]]
a.map{|x| x.map{|y| arr << y}}
puts arr

Why does using a regex and .scan produce these results?

>> "aaaaaafbfbfsjjseew".scan(/(.)/)
=> [["a"], ["a"], ["a"], ["a"], ["a"], ["a"], ["f"], ["b"], ["f"], ["b"], ["f"], ["s"], ["j"], ["j"], ["s"], ["e"], ["e"], ["w"]]
>> "aaaaaafbfbfsjjseew".scan(/((.))/)
=> [["a", "a"], ["a", "a"], ["a", "a"], ["a", "a"], ["a", "a"], ["a", "a"], ["f", "f"], ["b", "b"], ["f", "f"], ["b", "b"], ["f", "f"], ["s", "s"], ["j", "j"], ["j", "j"], ["s", "s"], ["e", "e"], ["e", "e"], ["w", "w"]]
>> "aaaaaafbfbfsjjseew".scan(/((.)\2*)/)
=> [["aaaaaa", "a"], ["f", "f"], ["b", "b"], ["f", "f"], ["b", "b"], ["f", "f"], ["s", "s"], ["jj", "j"], ["s", "s"], ["ee", "e"], ["w", "w"]]
>> "aaaaaafbfbfsjjseew".scan(/((.)\1*)/)
=> [["a", "a"], ["a", "a"], ["a", "a"], ["a", "a"], ["a", "a"], ["a", "a"], ["f", "f"], ["b", "b"], ["f", "f"], ["b", "b"], ["f", "f"], ["s", "s"], ["j", "j"], ["j", "j"], ["s", "s"], ["e", "e"], ["e", "e"], ["w", "w"]]
>> "aaaaaafbfbfsjjseew".scan(/((.)\3*)/)
=> [["a", "a"], ["a", "a"], ["a", "a"], ["a", "a"], ["a", "a"], ["a", "a"], ["f", "f"], ["b", "b"], ["f", "f"], ["b", "b"], ["f", "f"], ["s", "s"], ["j", "j"], ["j", "j"], ["s", "s"], ["e", "e"], ["e", "e"], ["w", "w"]]
From the fine manual:
str.scan(pattern) → array
[...]
If the pattern contains groups, each individual result is itself an array containing one entry per group.
This one:
"aaaaaafbfbfsjjseew".scan(/(.)/)
has a group so you get an array of arrays: each individual result is a single element array.
The next one:
"aaaaaafbfbfsjjseew".scan(/((.))/)
has two groups which happen to have the same value so you get two identical elements in your individual result arrays.
The third one:
"aaaaaafbfbfsjjseew".scan(/((.)\2*)/)
again contains two groups but also contains a back-reference to the inner group so the outer group (AKA the first group) gobbles up duplicates and you get ["aaaaaa", "a"], ["jj", "j"], and ["ee", "e"].
The fourth one:
"aaaaaafbfbfsjjseew".scan(/((.)\1*)/)
just tries to switch the back-reference to the outer group but \1 isn't defined inside group 1 so it is equivalent to /((.))/.
The fifth one:
"aaaaaafbfbfsjjseew".scan(/((.)\3*)/)
tries to refer to a non-existant group (group 3 when there are only two groups) so it behaves the same as /((.))/.
"aaaaaafbfbfsjjseew".scan(/(.)/) means the string can be splitted into individual array of strings.
Here parenthesis tells that it's an array, and the .-symbol in parenthesis represents number of characters in that individual string of array.
If we write for suppose "hellovenkat".scan(/(...)/) , this results
[["hel"],["lov"],["enk"]]. It doesn't give the last index, because it can not contain three characters.
If we give "hello venkat".scan(/(...)/), this results as following.
Ans: [["hel"], ["lo "], ["ven"], ["kat"]].

merging array items in ruby

Given an array of arrays
[["B", "C", "E", "F"], ["A", "B", "C", "D"], ["G"]]
What is the simplest way to merge the array items that contain members that are shared by any two or more arrays items. For example the above should be
[["A", "B", "C", "D","E", "F"], ["G"]] since "B" and "C" are shared by the first and second array items.
Here are some more test cases.
[["B", "C", "E", "F"], ["A", "B", "C", "D"], ["F", "G"]]
=> [["A", "B", "C", "D", "E", "F", "G"]]
[["B", "C", "E", "F"], ["A", "B", "C", "D"], ["G"], ["G", "H"]]
=> [["A", "B", "C", "D", "E", "F"], ["G", "H,"]]
Here is my quick version which can be optimized I am sure :)
# array = [["B", "C", "E", "F"], ["A", "B", "C", "D"], ["G"]]
# array = [["B", "C", "E", "F"], ["A", "B", "C", "D"], ["F", "G"]]
array = [["B", "C", "E", "F"], ["A", "B", "C", "D"], ["G"], ["G", "H"]]
array.collect! do |e|
t = e
e.each do |f|
array.each do |a|
if a.index(f)
t = t | a
end
end
end
e = t.sort
end
p array.uniq
Edit: Martin DeMello code was fixed.
When running Martin DeMello code (the accepted answer) I get:
[["B", "C", "E", "F"], ["A", "B", "C", "D"], ["F", "G"]] =>
[["B", "C", "E", "F", "A", "D", "G"], ["A", "B", "C", "D"], ["F", "G"]]
and
[["B", "C", "E", "F"], ["A", "B", "C", "D"], ["G"], ["G", "H"]] =>
[["B", "C", "E", "F", "A", "D"], ["A", "B", "C", "D"], ["G", "H"], ["G", "H"]]
which does not seem to meet your spec.
Here is my approach using a few of his ideas:
a = [["B", "C", "E", "F"], ["A", "B", "C", "D"], ["F", "G"]]
b = [["B", "C", "E", "F"], ["A", "B", "C", "D"], ["G"], ["G", "H"]]
def reduce(array)
h = Hash.new {|h,k| h[k] = []}
array.each_with_index do |x, i|
x.each do |j|
h[j] << i
if h[j].size > 1
# merge the two sub arrays
array[h[j][0]].replace((array[h[j][0]] | array[h[j][1]]).sort)
array.delete_at(h[j][1])
return reduce(array)
# recurse until nothing needs to be merged
end
end
end
array
end
puts reduce(a).to_s #[["A", "B", "C", "D", "E", "F", "G"]]
puts reduce(b).to_s #[["A", "B", "C", "D", "E", "F"], ["G", "H"]]
Different algorithm, with a merge-as-you-go approach rather than taking two passes over the array (vaguely influenced by the union-find algorithm). Thanks for a fun problem :)
A = [["A", "G"],["B", "C", "E", "F"], ["A", "B", "C", "D"], ["B"], ["H", "I"]]
H = {}
B = (0...(A.length)).to_a
def merge(i,j)
A[j].each do |e|
if H[e] and H[e] != j
merge(i, H[e])
else
H[e] = i
end
end
A[i] |= A[j]
B[j] = i
end
A.each_with_index do |x, i|
min = A.length
x.each do |j|
if H[j]
merge(H[j], i)
else
H[j] = i
end
end
end
out = B.sort.uniq.map {|i| A[i]}
p out
Not the simplest ,may be the longest :)
l = [["B", "C", "E", "F"], ["A", "B", "C", "D"], ["G"]]
puts l.flatten.inject([[],[]]) {|r,e| if l.inject(0) {|c,a| if a.include?(e) then c+1 else c end} >= 2 then r[0] << e ; r[0].uniq! else r[1] << e end ; r}.inspect
#[["B", "C"], ["E", "F", "A", "D", "G"]]
l = [["B", "C", "E", "F"], ["A", "B","C", "D"], ["G"]]
p l.inject([]){|r,e|
r.select{|i|i&e!=[]}==[]&&(r+=[e])||(r=r.map{|i|(i&e)!=nil&&(i|e).sort||i})
}
im not sure about your cond.
The simplest way to do it would be to take the powerset of an array (a set containing every possible combination of elements of the array), throw out any of the resulting sets if they don't have a common element, flatten the remaining sets and discard subsets and duplicates.
Or at least it would be if Ruby had proper Set support. Actually doing this in Ruby is horribly inefficient and an awful kludge:
power_set = array.inject([[]]){|c,y|r=[];c.each{|i|r<<i;r<<i+[y]};r}.reject{|x| x.empty?}
collected_powerset = power_set.collect{|subset| subset.flatten.uniq.sort unless
subset.inject(subset.last){|acc,a| acc & a}.empty?}.uniq.compact
collected_powerset.reject{|x| collected_powerset.any?{|c| (c & x) == x && x.length < c.length}}
Power set operation comes from here.
Straightforward rather than clever. It's destructive of the original array. The basic idea is:
go down the list of arrays, noting which array each element appears in
for every entry in this index list that shows an element in more than one array, merge all those arrays into the lowest-indexed array
when merging two arrays, replace the lower-indexed array with the merged result, and the higher-indexed array with a pointer to the lower-indexed array.
It's "algorithmically cheaper" than intersecting every pair of arrays, though the actual running speed will depend on what ruby hands over to the C layer.
a = [["B", "C", "E", "F"], ["A", "B", "C", "D"], ["G"], ["G", "H"]]
h = Hash.new {|h,k| h[k] = []}
a.each_with_index {|x, i| x.each {|j| h[j] << i}}
b = (0...(a.length)).to_a
h.each_value do |x|
x = x.sort_by {|i| b[i]}
if x.length > 1
x[1..-1].each do |i|
b[i] = [b[i], b[x[0]]].min
a[b[i]] |= a[i]
end
end
end
a = b.sort.uniq.map {|i| a[i]}
def merge_intersecting(input, result=[])
head = input.first
tail = input[1..-1]
return result if tail.empty?
intersection = tail.select { |arr| !(head & arr).empty? }
unless intersection.empty?
merged = head | intersection.flatten
result << merged.sort
end
merge_intersecting(tail, result)
end
require 'minitest/spec'
require 'minitest/autorun'
describe "" do
it "merges input array" do
input = [["B", "C", "E", "F"], ["A", "B", "C", "D"], ["F", "G"]]
output = [["A", "B", "C", "D", "E", "F", "G"]]
merge_intersecting(input).must_equal output
end
it "merges input array" do
input = [["B", "C", "E", "F"], ["A", "B", "C", "D"], ["G"], ["G", "H"]]
output = [["A", "B", "C", "D", "E", "F"], ["G", "H"]]
merge_intersecting(input).must_equal output
end
end

Resources