merging array items in ruby - 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

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

Intersections and Unions in Ruby for sets with repeated elements

How do we get intersections and unions in Ruby for sets that repeat elements.
# given the sets
a = ["A", "B", "B", "C", "D", "D"]
b = ["B", "C", "D", "D", "D", "E"]
# A union function that adds repetitions
union(a, b)
=> ["A", "B", "B", "C", "D", "D", "D", "E"]
# An intersection function that adds repetitions
intersection(a, b)
=> ["B", "C", "D", "D"]
The &, and | operators seem to ignore repetitions and duplicates, as written in the documentation.
# union without duplicates
a | b
=> ["A", "B", "C", "D", "E"]
# intersections without duplicates
a & b
=> ["B", "C", "D"]
def union(a,b)
(a|b).flat_map { |s| [s]*[a.count(s), b.count(s)].max }
end
union(a,b)
# => ["A", "B", "B", "C", "D", "D", "D", "E"]
def intersection(a,b)
(a|b).flat_map { |s| [s]*[a.count(s), b.count(s)].min }
end
intersection(a,b)
#=> ["B", "C", "D", "D"]
Building upon Cary Swoveland's answer, you could create a temporary hash to count the number of occurrences of each member in each array: (I've generalized the number of arguments)
def multiplicities(*arrays)
m = Hash.new { |h, k| h[k] = Array.new(arrays.size, 0) }
arrays.each_with_index { |ary, idx| ary.each { |x| m[x][idx] += 1 } }
m
end
multiplicities(a, b)
#=> {"A"=>[1, 0], "B"=>[2, 1], "C"=>[1, 1], "D"=>[2, 3], "E"=>[0, 1]}
Implementing union and intersection is straight forward:
def union(*arrays)
multiplicities(*arrays).flat_map { |x, m| Array.new(m.max, x) }
end
def intersection(*arrays)
multiplicities(*arrays).flat_map { |x, m| Array.new(m.min, x) }
end
union(a, b) #=> ["A", "B", "B", "C", "D", "D", "D", "E"]
intersection(a, b) #=> ["B", "C", "D", "D"]
With this approach each array has to be traversed only once.

palindrome partition ruby no output

Hi I'm doing the palindrome partition problem using recursion. This problem is return all possible palindrome partitions of a given string input.
Input: "aab"Output: [["aa", "b"], ["a", "a", "b"]]
A palindrome partition definition: given a string S, a partition is a set of substrings, each containing one or more characters, such that every substring is a palindrome
My code is below. The issue I'm having is that the result array never gets correctly populated. From a high level I feel like my logic makes sense, but when I try to debug it I'm not really sure what is going on.
def partition(string)
result = []
output = []
dfs(string, 0, output, result)
result
end
def dfs(string, start, output, result)
if start == string.length
result << output
return
end
(start..string.length-1).to_a.each do |i|
if is_palindrome(string, start, i)
output << string[start..(i-start+1)]
dfs(string, i+1, output, result)
output.pop
end
end
end
def is_palindrome(string, start, end_value)
result = true
while start < end_value do
result = false if string[start] != string[end_value]
start += 1
end_value -= 1
end
result
end
puts partition("aab")
Yes, you do want to use recursion. I haven't analyzed your code carefully, but I see one problem is the following in the method dfs:
if start == string.length
result << output
return
end
If the if condition is satisfied, return without an argument will return nil. Perhaps you want return result.
Here is a relatively compact, Ruby-like way of writing it.
def pps(str)
return [[]] if str.empty?
(1..str.size).each_with_object([]) do |i,a|
s = str[0,i]
next unless is_pal?(s)
pps(str[i..-1]).each { |b| a << [s, *b] }
end
end
def is_pal?(str)
str == str.reverse
end
pps "aab"
#=> [["a", "a", "b"],
# ["aa", "b"]]
pps "aabbaa"
#=> [["a", "a", "b", "b", "a", "a"],
# ["a", "a", "b", "b", "aa"],
# ["a", "a", "bb", "a", "a"],
# ["a", "a", "bb", "aa"],
# ["a", "abba", "a"],
# ["aa", "b", "b", "a", "a"],
# ["aa", "b", "b", "aa"],
# ["aa", "bb", "a", "a"],
# ["aa", "bb", "aa"],
# ["aabbaa"]]
pps "aabbbxaa"
#=> [["a", "a", "b", "b", "b", "x", "a", "a"],
# ["a", "a", "b", "b", "b", "x", "aa"],
# ["a", "a", "b", "bb", "x", "a", "a"],
# ["a", "a", "b", "bb", "x", "aa"],
# ["a", "a", "bb", "b", "x", "a", "a"],
# ["a", "a", "bb", "b", "x", "aa"],
# ["a", "a", "bbb", "x", "a", "a"],
# ["a", "a", "bbb", "x", "aa"],
# ["aa", "b", "b", "b", "x", "a", "a"],
# ["aa", "b", "b", "b", "x", "aa"],
# ["aa", "b", "bb", "x", "a", "a"],
# ["aa", "b", "bb", "x", "aa"],
# ["aa", "bb", "b", "x", "a", "a"],
# ["aa", "bb", "b", "x", "aa"],
# ["aa", "bbb", "x", "a", "a"],
# ["aa", "bbb", "x", "aa"]]
pps "abcdefghijklmnopqrstuvwxyz"
#=> [["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
# "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]]
The best way of understanding how this recursion works is add some puts statements and re-run it.
def pps(str)
puts "\nstr=#{str}"
return [[]] if str.empty?
rv = (1..str.size).each_with_object([]) do |i,a|
s = str[0,i]
puts "i=#{i}, a=#{a}, s=#{s}, is_pal?(s)=#{is_pal?(s)}"
next unless is_pal?(s)
pps(str[i..-1]).each { |b| puts "b=#{b}, [s,*b]=#{[s,*b]}"; a << [s, *b] }
puts "a after calling pps=#{a}"
end
puts "rv=#{rv}"
rv
end
pps "aab"
str=aab
i=1, a=[], s=a, is_pal?(s)=true
str=ab
i=1, a=[], s=a, is_pal?(s)=true
str=b
i=1, a=[], s=b, is_pal?(s)=true
str=
b=[], [s,*b]=["b"]
a after calling pps=[["b"]]
rv=[["b"]]
b=["b"], [s,*b]=["a", "b"]
a after calling pps=[["a", "b"]]
i=2, a=[["a", "b"]], s=ab, is_pal?(s)=false
rv=[["a", "b"]]
b=["a", "b"], [s,*b]=["a", "a", "b"]
a after calling pps=[["a", "a", "b"]]
i=2, a=[["a", "a", "b"]], s=aa, is_pal?(s)=true
str=b
i=1, a=[], s=b, is_pal?(s)=true
str=
b=[], [s,*b]=["b"]
a after calling pps=[["b"]]
rv=[["b"]]
b=["b"], [s,*b]=["aa", "b"]
a after calling pps=[["a", "a", "b"], ["aa", "b"]]
i=3, a=[["a", "a", "b"], ["aa", "b"]], s=aab, is_pal?(s)=false
rv=[["a", "a", "b"], ["aa", "b"]]
#=> [["a", "a", "b"], ["aa", "b"]]

ruby array custom combinations

What is a good way to get from this:
['a','b','c',['d1','d2']]
to this:
[['a','b','c','d1']['a','b','c','d2']]
another example, from this:
[['a1','a2'],'b',['c1','c2']]
to this:
[['a1','b','c1'],['a1','b','c2'],['a2','b','c1'],['a2','b','c2']]
edit 1:
Sorry for the confusion and thanks for response so far, the individual contents of the array items doesn't matter but the order must be preserved. The method needs to work for both example because the nested array can be in any position of the outer array, and the nested array can have more the 2 elements.
It's sort of like a regex with multiple or conditions
ab(c|d)
expand to match abc and abd
It is a bit hard to know exactly what you want, but this produces something quite similar:
# Create a list:
a = [['a1','a2'],'b',['c1','c2']]
# Split it into sub-arrays and single values:
list, other = a.partition{|x|x.is_a? Array}
# Split the array in order to get the product:
list_first, list_rest = list.pop, list
# Get the product and add the others_values:
p list_first.product(*list_rest).map{|list| list+other}
#=> [["c1", "a1", "b"], ["c1", "a2", "b"], ["c2", "a1", "b"], ["c2", "a2", "b"]]
1st:
arr1 = ['a','b','c',['d1','d2']]
*a, b = arr1
# ["a", "b", "c", ["d1", "d2"]]
a
# ["a", "b", "c"]
b
# ["d1", "d2"]
b.map{|x| a+[x]}
# [["a", "b", "c", "d1"], ["a", "b", "c", "d2"]]
and 2nd:
a, b, c = [["a1", "a2"], "b", ["c1", "c2"] ]
a.product c
#=> [["a1", "c1"], ["a1", "c2"], ["a2", "c1"], ["a2", "c2"]]
a.product(c).map{|x| x<<b}
#=> [["a1", "c1", "b"], ["a1", "c2", "b"], ["a2", "c1", "b"], ["a2", "c2", "b"]]
#or little less readable:
a.product(c).map{|x| [ x[0], b, x[1] ]}
# [["a1", "b", "c1"], ["a1", "b", "c2"], ["a2", "b", "c1"], ["a2", "b", "c2"]]
hirolau got me really close, here is what I ended with so the order is preserved:
# given a sample list
sample = [['a','b'],'c','d',['e','f'],'g',['h','i']]
# partition out the arrays
arrs, non_arrays = sample.partition {|sample| sample.is_a? Array}
# work out all possible products
first_elem, *the_rest = arrs
products = first_elem.product(*the_rest)
# finally swap it back in to get all valid combinations with order preserved
combinations = []
products.each do |p|
combinations << sample.map {|elem| elem.is_a?(Array) ? p.shift : elem}
end
# combinations
=> [["a", "c", "d", "e", "g", "h"],
["a", "c", "d", "e", "g", "i"],
["a", "c", "d", "f", "g", "h"],
["a", "c", "d", "f", "g", "i"],
["b", "c", "d", "e", "g", "h"],
["b", "c", "d", "e", "g", "i"],
["b", "c", "d", "f", "g", "h"],
["b", "c", "d", "f", "g", "i"]]

Dynamically Generate Multi-Dimensional Array in Ruby

I'm trying to build a multidimensional array dynamically. What I want is basically this (written out for simplicity):
b = 0
test = [[]]
test[b] << ["a", "b", "c"]
b += 1
test[b] << ["d", "e", "f"]
b += 1
test[b] << ["g", "h", "i"]
This gives me the error: NoMethodError: undefined method `<<' for nil:NilClass. I can make it work by setting up the array like
test = [[], [], []]
and it works fine, but in my actual usage, I won't know how many arrays will be needed beforehand. Is there a better way to do this? Thanks
No need for an index variable like you're using. Just append each array to your test array:
irb> test = []
=> []
irb> test << ["a", "b", "c"]
=> [["a", "b", "c"]]
irb> test << ["d", "e", "f"]
=> [["a", "b", "c"], ["d", "e", "f"]]
irb> test << ["g", "h", "i"]
=> [["a", "b", "c"], ["d", "e", "f"], ["g", "h", "i"]]
irb> test
=> [["a", "b", "c"], ["d", "e", "f"], ["g", "h", "i"]]
Don't use the << method, use = instead:
test[b] = ["a", "b", "c"]
b += 1
test[b] = ["d", "e", "f"]
b += 1
test[b] = ["g", "h", "i"]
Or better still:
test << ["a", "b", "c"]
test << ["d", "e", "f"]
test << ["g", "h", "i"]
Here is a simple example if you know the size of array you are creating.
#OneDArray=[1,2,3,4,5,6,7,8,9]
p_size=#OneDArray.size
c_loop=p_size/3
puts "c_loop is #{c_loop}"
left=p_size-c_loop*3
#TwoDArray=[[],[],[]]
k=0
for j in 0..c_loop-1
puts "k is #{k} "
for i in 0..2
#TwoDArray[j][i]=#OneDArray[k]
k+=1
end
end
result will be #TwoDArray= [[1,2,3].[3,4,5],[6,7,8]]

Resources