I would do the following.
def circular_dependencies?(arr)
(2..arr.size).any? { |n| arr.combination(n).any? { |comb| is_cycle?(comb) } }
end
def is_cycle?(comb)
a = comb.flat_map { |s| s.split /\s+=>\s+/ }.rotate
a.each_slice(2).all? { |l,f| l==f }
end
arr = ["a =>", "b => c", "c => f", "d => a", "e =>", "f => b"]
circular_dependencies?(arr)
#=> true
arr = ["a =>", "b => c", "c => f", "d => a", "e =>", "f => a"]
circular_dependencies?(arr)
#=> false
When
arr = ["a =>", "b => c", "c => f", "d => a", "e =>", "f => b"]
and n = 3
enum = arr.combination(n)
#=> #<Enumerator: ["a =>", "b => c", "c => f", "d => a", "e =>",
# "f => a"]:combination(3)>
We can convert this enumerator to an array to see the elements that will be passed to it's block:
enum.to_a
#=> [["a =>", "b => c", "c => f"],
# ["a =>", "b => c", "d => a"],
# ["a =>", "b => c", "e =>"],
# ["a =>", "b => c", "f => b"],
# ["a =>", "c => f", "d => a"],
# ["a =>", "c => f", "e =>"],
# ["a =>", "c => f", "f => b"],
# ["a =>", "d => a", "e =>"],
# ["a =>", "d => a", "f => b"],
# ["a =>", "e =>", "f => b"],
# ["b => c", "c => f", "d => a"],
# ["b => c", "c => f", "e =>"],
# ** ["b => c", "c => f", "f => b"],
# ["b => c", "d => a", "e =>"],
# ["b => c", "d => a", "f => b"],
# ["b => c", "e =>", "f => b"],
# ["c => f", "d => a", "e =>"],
# ["c => f", "d => a", "f => b"],
# ["c => f", "e =>", "f => b"],
# ["d => a", "e =>", "f => b"]]
When the combination
comb = ["b => c", "c => f", "f => b"]
is passed to is_comb?
we compute
a = comb.flat_map { |s| s.split /\s+=>\s+/ }
#=> ["b", "c", "c", "f", "f", "b"]
b = a.rotate
#=> ["c", "c", "f", "f", "b", "b"]
enum = a.each_slice(2)
#=> #<Enumerator: ["c", "c", "f", "f", "b", "b"]:each_slice(2)>
enum.to_a
#=> [["c", "c"], ["f", "f"], ["b", "b"]]
enum.all? { |l,f| l==f }
#=> true