Ruby #to_enum: каков наилучший способ извлечь исходный объект из перечислителя? - PullRequest
1 голос
/ 05 февраля 2020

Предположим, у меня есть объект:

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

И я преобразовываю его в перечислитель:

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

Теперь я хочу получить свой объект обратно из перечислителя. Я нашел способ сделать это, но он кажется излишне заумным (не говоря уже о довольно fr agile):

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, используя regex для извлечения идентификатора исходного объекта из результирующей строки, преобразования его в целое число (и деления на 2) и использования ObjectSpace._id2ref для преобразования идентификатора в ссылку на мой объект. Ужасные вещи.

Мне трудно поверить, что это самый простой способ выполнить эту работу, но некоторые часы поисковика мне ничего не показали. Есть ли простой способ извлечь объект после наложения Enumerator вокруг него с помощью #to_enum, или это в значительной степени способ сделать это?

Редактировать:

Как говорит Амадан ниже (и высоко ценится, Амадан), это может быть проблемой XY, и мне, возможно, придется пересмотреть свое решение. Я немного объясню, как я сюда попал.

(мета) вариант использования: у меня (переменное) количество объектов в массиве. Каждый из объектов представляет массив целых чисел (все одинакового размера) в качестве атрибута, отсортированного по убыванию. Я хочу итерировать массивы каждого из объектов одновременно, находя объект или объекты с самым высоким целым числом, не совпадающим в массиве другого объекта.

Казалось, что внешняя итерация была хорошим способом go сделать это потому, что одновременная внутренняя итерация нескольких объектов, которые должны знать о промежуточных результатах итераций друг друга, также довольно быстро появляется. Но когда я нашел перечислитель, содержащий объект с массивом с наибольшим значением, мне нужно вернуть объект, который он переносит.

Очень хорошо может быть лучший способ go, чем использование счетчиков, когда это является требованием. Однако фактический процесс итерации и выбора довольно прост при их использовании.

Итак. Применяемый вариант использования: количество покерных рук, у которых рука не лучше, чем у старшей карты. Найдите выигрышную комбинацию. «Выигрышная рука» - это рука с наивысшей картой, не имеющая ранга другой руки. (Костюмы не имеют значения.) Если все карты совпадают в двух или более раздачах, верните эти комбинации в массив.

«Пример минимальной воспроизводимости»:

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"]]]

Это «работает» но я, конечно, согласен с Амаданом, что это взлом Может быть, интересный и поучительный, но все же взломать. TIA для любых предложений.

Ответы [ 2 ]

2 голосов
/ 06 февраля 2020

Я не уверен, почему все эти разговоры о перечислениях.

Я предполагаю, что руки находятся в формате, подобном следующему:

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]
1 голос
/ 05 февраля 2020

Как уже упоминалось в комментариях, нет способа надежно извлечь базовый объект, потому что перечислитель не всегда имеет его.
Хотя ваше решение будет работать для этого конкретного c случая, я бы предложил придумать другой подход, который не будет зависеть от деталей реализации объекта перечислителя.

Одним из возможных решений будет передача экземпляра 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
...