質問

I've written my own Tree class which includes Enumerable. Tree then provides an #each function. As a consequence, it is able to automatically acquire all the Enumerable functions like #map, #select, #find, et cetera. This all works in my code so far.

Here's the problem. When I wrote #each for my Tree, I gave #each an argument which is the name of the tree traversal algorithm to use, such as :pre_order or :breadth_first. But now when I call #map or #inject or #any? et cetera, it can only use the default traversal algorithm. Is there any way I can pass this argument through the other Enumerable functions? Here are my criteria;

  • I need to be able to use any traversal algorithm for any Enumerable function. This is very important because trees can have very different performance for different algorithms.
  • I don't want to rewrite every single Enumerable function to pass this argument to #each; that defeats the purpose of the module.

Here's an abbreviated version of my code;

class Tree
  include Enumerable
  ...

  # Overwrite #each, and give it the algorithm argument.
  def each(algorithm = :pre_order, &block)
    if TRAVERSAL_ALGORITHMS.include? algorithm
      self.send(algorithm, &block)
    else
      self.method_missing(algorithm)
    end
  end

  def pre_order(&block)
    yield self
    self.branches.each do |branch|
      branch.pre_order(&block)
    end
  end

  def post_order(&block)
    ...
  end

  def breadth_first(&block)
    ...
  end

end

I want to call things like this;

tree.find(13, :breadth_first)
tree.any?(:post_order) do |node|
  node.root >= 10
end
役に立ちましたか?

解決

I am so silly.

The method #enum_for gives me all the power here. I can implement Charlie's syntax of

tree.breadth_first.find(13)

by adding the conventional line

return self.enum_for(__method__) unless block_given?

in each of my traversal methods. The tree.breadth_first will return an Enumerator which enumerates according to the breadth-first algorithm; any Enumerable method called on that will use that enumeration internally.

他のヒント

Possible answer: set an instance variable for which algorithm to use. The code would then look like this;

class Tree
  include Enumerable
  attr_accessor :traversal_algorithm

  def initialize(...)
    @traversal_algorithm = :pre_order
    ...
  end

  ...

  def each(&block)
    if TRAVERSAL_ALGORITHMS.include? @algorithm
      self.send(@algorithm, &block)
    else
      self.method_missing(@algorithm)
    end
  end

end

and the algorithm would be set like this;

tree.traversal_algorithm = :breadth_first
tree.find(13)

tree.traversal_algorithm = :post_order
tree.any? do |node|
  node.root >= 10
end

This seems like a possibly terrible idea.

I would follow Charlie's suggestion and use proxy objects:

class Tree
  class PreOrderEnumerator
    include Enumerable

    attr_reader :tree

    def initialize(tree)
      @tree = tree
    end

    def each(&block)
      yield tree
      tree.branches.each do |branch|
        branch.pre_order(&block)
      end
    end
  end

  def pre_order
    PreOrderEnumerator.new(self)
  end
end

Which you can then use like so:

tree.pre_order.any? {|node| node.root > 10 }
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top