[Feature #20625] ツリーやリスト構造のデータを走査する Object#chain_of メソッドを追加する提案

2024/07/10に公開

[Feature #20625] Object#chain_of

  • 次のように要素が連鎖しているツリーやリストのような構造をたどるようなことはある
# ファイルシステムの構造を指定してファイルやディレクトリを取得する
def breadcrumbs(root)
  crumbs = []
  current = root

  while current
    crumbs << current
    current = current.parent_dir
  end

  crumbs
end
  • このような走査を行う Object#chain_of メソッドを追加する提案
class Object
  def chain_of(&block)
    chain = []
    current = self

    while current
      chain << current
      current = block.call(current)
    end

    chain
  end
end
  • これを利用すると以下のようなリンク構造を走査することができる
class ListNode
  attr_accessor :value, :parent

  def initialize(value, parent = nil)
    @value = value
    @parent = parent
  end

  def ancestors
    chain_of(&:parent).tap(&:shift)
  end
end

root = ListNode.new("root")
child1 = ListNode.new("child1", root)
child2 = ListNode.new("child2", child1)

# pp child2.ancestors.map(&:value)
# => ["child1", "root"]
  • 要は『ブロックが次の要素を返して、それが nil になるまで走査を繰り返す』みたいな挙動になる感じですね
    • 今回でいうと『 #parentnil になるまで繰り返す』みたいな挙動になる
  • この挙動に近いのが Enumerator.produce でこっちは『ブロックを呼び出し続ける』みたいな挙動になる感じですね
  • 個人的には便利なケースもありそうだなあ、と思いつつ Object に直接生やしちゃうのはやや過剰な気がする
  • ちなみに Enumerator.produce を利用すると以下のように記述することはできます
pp Enumerator.produce(child2) { _1.parent or raise StopIteration }.map(&:value)
# => ["child2", "child1", "root"]
  • raise StopIteration の部分がやや冗長なので以下のように Enumerator にメソッドを追加してもいいのかも
class Enumerator
  def self.chain_of(initial = nil, &block)
    produce(initial) { block.call(_1) or raise StopIteration }
  end
end

pp Enumerator.chain_of(child2, &:parent).map(&:value)
# => ["child2", "child1", "root"]
  • 個人的には Enumerator に定義する方が好み
GitHubで編集を提案

Discussion