我正在寻找一种更优雅的方法,将两个具有给定比率的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-优雅地比较两个枚举器。一些紧凑的答案,但并不是特别能解决它,我的问题涉及不相等的长度和不相等的屈服似乎更棘手。
这里有一个更短、更通用的方法:
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