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]
假设我有一个对象:
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]