This is perhaps not the most efficient way of forming the disjoint arrays, but it does produce the desired result. The proof that it works is easily established by contradiction.
arr = [[0,2], [1,3], [4,6], [7,9], [6,8], [5,7], [2,4], [3,7], [10,11]]
require 'set'
sets = arr.map(&:to_set)
#=> [#<Set: {0, 2}>, #<Set: {1, 3}>, #<Set: {4, 6}>, #<Set: {7, 9}>, #<Set: {6, 8}>,
# #<Set: {5, 7}>, #<Set: {2, 4}>, #<Set: {3, 7}>, #<Set: {10, 11}>]
loop do
break if sets.size == 1
set1, set2 = sets.combination(2).find { |set1,set2| (set1 & set2).any? }
break if set1.nil?
set1.replace(set1 | set2)
sets.delete(set2)
end
sets.map(&:to_a)
#=> [[0, 2, 4, 6, 8], [1, 3, 7, 9, 5], [10, 11]]
I’ve used sets instead of arrays to speed the union and intersection calculations.
The steps can be illustrated by including some puts
statements.
sets = arr.map(&:to_set)
loop do
puts "(#{sets.size} sets at beginning of loop"
puts " #{sets}"
puts " break as sets.size == 1" if sets.size == 1
break if sets.size == 1
set1, set2 = sets.combination(2).find { |set1,set2| (set1 & set2).any? }
if set1.nil?
puts " After find, set1 = nil, so break" if set1.nil?
else
puts " After find, set1 = #{set1}"
puts " set2 = #{set2}"
end
break if set1.nil?
set1.replace(set1 | set2)
sets.delete(set2)
puts " sets after set1 |= set2 and sets.delete(set2)"
puts " #{sets}"
end
sets.map(&:to_a)
prints the following.
(9) sets at beginning of loop
[#<Set: {0, 2}>, #<Set: {1, 3}>, #<Set: {4, 6}>, #<Set: {7, 9}>, #<Set: {6, 8}>,
#<Set: {5, 7}>, #<Set: {2, 4}>, #<Set: {3, 7}>, #<Set: {10, 11}>]
After find, set1 = #<Set: {0, 2}>
set2 = #<Set: {2, 4}>
sets after set1 |= set2 and sets.delete(set2)
[#<Set: {0, 2, 4}>, #<Set: {1, 3}>, #<Set: {4, 6}>, #<Set: {7, 9}>,
#<Set: {6, 8}>, #<Set: {5, 7}>, #<Set: {3, 7}>, #<Set: {10, 11}>]
(8) sets at beginning of loop
[#<Set: {0, 2, 4}>, #<Set: {1, 3}>, #<Set: {4, 6}>, #<Set: {7, 9}>,
#<Set: {6, 8}>, #<Set: {5, 7}>, #<Set: {3, 7}>, #<Set: {10, 11}>]
After find, set1 = #<Set: {0, 2, 4}>
set2 = #<Set: {4, 6}>
sets after set1 |= set2 and sets.delete(set2)
[#<Set: {0, 2, 4, 6}>, #<Set: {1, 3}>, #<Set: {7, 9}>, #<Set: {6, 8}>,
#<Set: {5, 7}>, #<Set: {3, 7}>, #<Set: {10, 11}>]
(7) sets at beginning of loop
[#<Set: {0, 2, 4, 6}>, #<Set: {1, 3}>, #<Set: {7, 9}>, #<Set: {6, 8}>,
#<Set: {5, 7}>, #<Set: {3, 7}>, #<Set: {10, 11}>]
After find, set1 = #<Set: {0, 2, 4, 6}>
set2 = #<Set: {6, 8}>
sets after set1 |= set2 and sets.delete(set2)
[#<Set: {0, 2, 4, 6, 8}>, #<Set: {1, 3}>, #<Set: {7, 9}>, #<Set: {5, 7}>,
#<Set: {3, 7}>, #<Set: {10, 11}>]
(6) sets at beginning of loop
[#<Set: {0, 2, 4, 6, 8}>, #<Set: {1, 3}>, #<Set: {7, 9}>, #<Set: {5, 7}>,
#<Set: {3, 7}>, #<Set: {10, 11}>]
After find, set1 = #<Set: {1, 3}>
set2 = #<Set: {3, 7}>
sets after set1 |= set2 and sets.delete(set2)
[#<Set: {0, 2, 4, 6, 8}>, #<Set: {1, 3, 7}>, #<Set: {7, 9}>, #<Set: {5, 7}>,
#<Set: {10, 11}>]
(5) sets at beginning of loop
[#<Set: {0, 2, 4, 6, 8}>, #<Set: {1, 3, 7}>, #<Set: {7, 9}>, #<Set: {5, 7}>,
#<Set: {10, 11}>]
After find, set1 = #<Set: {1, 3, 7}>
set2 = #<Set: {7, 9}>
sets after set1 |= set2 and sets.delete(set2)
[#<Set: {0, 2, 4, 6, 8}>, #<Set: {1, 3, 7, 9}>, #<Set: {5, 7}>, #<Set: {10, 11}>]
(4) sets at beginning of loop
[#<Set: {0, 2, 4, 6, 8}>, #<Set: {1, 3, 7, 9}>, #<Set: {5, 7}>, #<Set: {10, 11}>]
After find, set1 = #<Set: {1, 3, 7, 9}>
set2 = #<Set: {5, 7}>
sets after set1 |= set2 and sets.delete(set2)
[#<Set: {0, 2, 4, 6, 8}>, #<Set: {1, 3, 7, 9, 5}>, #<Set: {10, 11}>]
(3) sets at beginning of loop
[#<Set: {0, 2, 4, 6, 8}>, #<Set: {1, 3, 7, 9, 5}>, #<Set: {10, 11}>]
After find, set1 = nil, so break
sets.map(&:to_a)
#=> [[0, 2, 4, 6, 8], [1, 3, 7, 9, 5], [10, 11]]