Лучший способ чередовать два перечисления в ruby?

Я ищу более элегантный способ смешивания двух наборов результатов SQL с заданным соотношением. Внутри каждого из них я хочу, чтобы они обрабатывались в том же порядке, в котором они появляются, но я хочу чередовать обработку для достижения желаемого сочетания.

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

def combine_enums_with_ratio(enum_a, enum_b, desired_ratio)
  a_count = 1
  b_count = 1
  a_finished = false
  b_finished = false
  loop do
    ratio_so_far = a_count / b_count.to_f
    if !a_finished && (b_finished || ratio_so_far <= desired_ratio)
      begin
        yield enum_a.next
        a_count += 1
      rescue StopIteration
        a_finished = true
      end
    end

    if !b_finished && (a_finished || ratio_so_far > desired_ratio)
      begin
        yield enum_b.next
        b_count += 1
      rescue StopIteration
        b_finished = true
      end
    end

    break if a_finished && b_finished
  end
end

Стыдно, потому что это явно написано в очень императивном стиле. Выглядит не очень рубиново. Может быть, есть способ использовать один из хороших методов декларативного цикла ruby, за исключением того, что они, похоже, не работают, удерживая открытыми два перечисления, подобные этому. Итак, я считаю, что мне осталось спасать исключение как часть потока управления, подобного этому, который кажется очень грязным. Мне не хватает метода Java hasNext().

Есть ли способ лучше?

Я нашел похожий вопрос о сравнении перечислений: Ruby — элегантное сравнение двух перечислителей. Некоторые компактные ответы, но не особенно решающие, и моя проблема, связанная с неравными длинами и неравным доходом, кажется более сложной.

Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
2
0
135
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вот более короткий и общий подход:

def combine_enums_with_ratio(ratios)
  return enum_for(__method__, ratios) unless block_given?

  counts = ratios.transform_values { |value| Rational(1, value) }

  until counts.empty?
    begin
      enum, _ = counts.min_by(&:last)
      yield enum.next
      counts[enum] += Rational(1, ratios[enum])
    rescue StopIteration
      counts.delete(enum)
    end
  end
end

Вместо двух перечислений требуется хэш из enum => ratio пар.

Сначала он создает хэш counts, используя обратное отношение, то есть enum_a => 3, enum_b => 2 становится:

counts = { enum_a => 1/3r, enum_b => 1/2r }

Затем в цикле он извлекает минимальное значение хеша, которое в приведенном выше примере равно enum_a. Он дает свое значение next и увеличивает значение отношения counts:

counts[enum_a] += 1/3r

counts #=> {:enum_a=>(2/3), :enum_b=>(1/2)}

На следующей итерации enum_b имеет наименьшее значение, поэтому его значение next будет получено, а его отношение будет увеличено:

counts[enum_b] += 1/2r

counts #=> {:enum_a=>(2/3), :enum_b=>(1/1)}

Если вы продолжите увеличивать enum_a на (1/3) и enum_b на (1/2), коэффициент доходности их элементов будет 3:2.

Наконец, предложение rescue обрабатывает перечисления, в которых заканчиваются элементы. Если это произойдет, это перечисление будет удалено из хэша counts.

Как только хэш counts станет пустым, цикл остановится.

Пример использования с 3 перечислениями:

enum_a = (1..10).each
enum_b = ('a'..'f').each
enum_c = %i[foo bar baz].each

combine_enums_with_ratio(enum_a => 3, enum_b => 2, enum_c => 1).to_a
#=> [1, "a", 2, 3, "b", :foo, 4, "c", 5, 6, "d", :bar, 7, "e", 8, 9, "f", :baz, 10]
#    <--------------------->  <--------------------->  <--------------------->
#             3:2:1                    3:2:1                     3:2:1

Хороший! Я полагаю, мне должно было прийти в голову, что цикл по набору входных перечислений может оказаться короче, чем весь мой код «a» и «b». Это довольно аккуратно!

Harry Wood 14.12.2020 18:45

Другие вопросы по теме