Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I detect recursive arrays and hashes?

How can I detect arrays or hashes that include a recursive structure like a, b, and c below?

  • Simplest instance of recursive array

    a = []
    a[0] = a
    a # => [[...]]
    
  • Recursion cycle/depth is not one

    b = [[], :foo]
    b[0][0] = b
    b # => [[[...]], :foo]
    
  • Recursion at a non-root level

    c = [a, :foo]
    c # => [[...], :foo]
    
like image 203
sawa Avatar asked Apr 11 '14 02:04

sawa


2 Answers

I love recursion.

Here's a decent way, iterating through everything and keeping a hash of objects you have seen (for fast lookup)

class Object
  def is_recursive?(known = {})
    false
  end
end

module Enumerable
  def is_recursive?(known = {})
    return true if known.include?(self)
    known[self] = true
    begin
      any? do |*args|
        args.any?{|item| item.is_recursive?(known)}
      end
    ensure
      known[self] = false
    end
  end
end

x = []; x << x
p x.is_recursive? # => true
p ({x => 42}).is_recursive? # => true
p [{foo: x}].is_recursive? # => true
p [[[[[[:foo], {bar: [42]}]]]]].is_recursive? # => false

Mind you, this is a bit rough and you could run into trouble. For example you'd have endless loops with [1..Float::INFINITY].is_recursive?, although that's easily salvable with

class Range
  def is_recursive?(known = {})
    false # optimization
  end
end
like image 147
Marc-André Lafortune Avatar answered Sep 21 '22 01:09

Marc-André Lafortune


You can't flatten a recursive array, so you could check it by:

begin
  a.flatten
rescue ArgumentError => e
  e.to_s =~ /recursive/
end
like image 25
xdazz Avatar answered Sep 20 '22 01:09

xdazz