Ruby#to_enum:从枚举器中提取原始对象的最佳方法是什么?

Ruby #to_enum: what's the best way to extract the original object from the enumerator?

假设我有一个对象:

obj = Object.new  #<Object:0x00007fbe36b4db28>

然后我将其转换为枚举器:

obj_enum = obj.to_enum  #<Enumerator: #<Object:0x00007fbe36b4db28>:each>

现在我想从枚举器中取回我的对象​​。我找到了一种方法,但它似乎不必要地深奥(更不用说非常脆弱):

extracted_obj = ObjectSpace._id2ref(
  obj_enum.inspect.match(/0x[0-9a-f]*/).values_at(0)[0].to_i(16)/2
)
p obj.equal? extracted_obj # => true

如果不清楚,我正在检查 Enumerator 对象,使用正则表达式从结果字符串中提取原始对象的 ID,将其转换为整数(并除以 2),然后使用 ObjectSpace._id2ref 将 id 转换为对我的对象的引用。丑陋的东西。

我很难相信这是完成这项工作的最简单方法,但几个小时的谷歌搜索并没有向我透露任何信息。在用 #to_enum 包裹一个 Enumerator 之后,是否有一种简单的方法来提取一个对象,或者这几乎就是这样做的方法?

编辑:

正如 Amadan 在下面所说的(非常感谢,Amadan),这可能是一个 XY 问题,我可能不得不重新考虑我的解决方案。我将解释一下我是如何来到这里的。

(元)用例:我在数组中有(可变)数量的对象。每个对象都公开一个整数数组(所有大小都相同)作为属性,从高到低排序。我想同时迭代每个对象的数组,找到具有与另一个对象数组不匹配的最高整数的一个或多个对象。

外部迭代似乎是实现此目的的好方法,因为同时内部迭代的多个对象必须了解彼此迭代的中间结果,因此也可以很快得到结果。但是当我找到包含具有最高值数组的对象的枚举器时,我需要 return 它包装的对象。

当需要时,可能有比使用枚举器更好的方法。然而,实际的迭代和选择过程在使用它们时是非常微不足道的。

所以。应用用例:没有比大牌更好的牌的数量。找到获胜的手。 "winning hand" 是手牌中最高的牌,没有被另一手匹配。 (花色无关紧要。)如果所有牌在两手或更多手牌中都匹配,则 return 这些手牌排列成一个数组。

"Minimal reproducible example":

class Hand
  attr_reader :cards

  def initialize(cards)
    @cards = cards.sort.reverse
  end

  def each
    @cards.each { |card| yield(card.first) }
  end
end

class Poker
  def initialize(hands)
    @hands = hands.map { |hand| Hand.new(hand) }
  end

  def high_cards
    hand_enums = @hands.map(&:to_enum)
    loop do
      max_rank = hand_enums.map(&:peek).max
      hand_enums.delete_if { |enum| enum.peek != max_rank }
      hand_enums.each(&:next)
    end
    hand_enums.map { |e| from_enum(e).cards }
  end

  def from_enum(enum)
    ObjectSpace._id2ref(
      enum.inspect.match(/0x[0-9a-f]*/).values_at(0)[0].to_i(16) / 2
    )
  end
end

hands = [
  [[10, "D"], [3, "C"], [8, "C"], [7, "C"], [9, "D"]],
  [[10, "D"], [8, "S"], [7, "S"], [9, "H"], [2, "H"]],
  [[9, "C"], [8, "H"], [9, "S"], [4, "C"], [7, "D"]]
]

game = Poker.new(hands)
p game.high_cards # => [[[10, "D"], [9, "D"], [8, "C"], [7, "C"], [3, "C"]]]

这 "works," 但我当然同意 Amadan 的观点,即它是 hack。一个有趣和有启发性的,也许,但同样是一个黑客。 TIA 的任何建议。

正如评论中已经提到的,没有办法可靠地检索底层对象,因为枚举器并不总是有一个。
虽然您的解决方案适用于这种特定情况,但我建议提出另一种不依赖于枚举器对象实现细节的方法。

可能的解决方案之一是将 Hand 的实例与枚举器一起传递。

class Poker
  def high_cards(hands)
    hand_enums = hands.map { |hand| [hand, hand.to_enum] }

    loop do
      max_rank = hand_enums.map(&:last).map(&:peek).max
      hand_enums.delete_if {|enum| enum.last.peek != max_rank }

      hand_enums.each {|pair| pair.last.next}
    end

    hand_enums.map(&:first)
  end
end

另一种更面向对象的方法是引入自定义Enumerator并以更显式的方式公开底层对象:

class HandEnumerator < Enumerator
  attr_reader :hand

  def initialize(hand, &block)
    @hand = hand
    super(block)
  end
end

class Hand
  def to_enum
    HandEnumerator.new(self) { |yielder| @cards.each { |card| yielder << card.first }}
  end

  # To satisfy interface of enumerator creation
  def enum_for 
    to_enum
  end
end

class Poker
  def high_cards(hands)
    hand_enums = hands.map(&:to_enum)

    loop do
      max_rank = hand_enums.map(&:peek).max
      hand_enums.delete_if {|enum| enum.peek != max_rank }

      hand_enums.each(&:next)
    end

    hand_enums.map(&:hand)
  end
end

我不确定为什么要谈论枚举。

我假设手牌的格式与此类似:

hands = [Hand.new([[12, "H"], [10, "H"], [8, "D"], [3, "D"], [2, "C"]]),
         Hand.new([[10, "D"], [9, "H"], [3, "C"], [2, "D"], [2, "H"]]),
         Hand.new([[12, "D"], [10, "S"], [8, "C"], [3, "S"], [2, "H"]]),
         Hand.new([[12, "C"], [9, "S"], [8, "C"], [8, "S"], [8, "S"]])]

并且您想获取第 0 个和第 2 个元素。根据您的断言,我还假设手已经排序。据我所知,这就是所有需要的,因为数组是按字典顺序比较的:

max_ranks = hands.map { |hand| hand.cards.map(&:first) }.max
max_hands = hands.select { |hand| hand.cards.map(&:first) == max_ranks }

或者,使用 group_by(更好一点,因为它不需要计算两次排名):

hands_by_ranks = hands.group_by { |hand| hand.cards.map(&:first) }
max_hands = hands_by_ranks[hands_by_ranks.keys.max]