我需要一个数组并返回true
它是否包含三次或更多的元素.如果数组包含少于三次的元素,则需要返回false
.我猜我需要使用这个count
方法,但我不知道要放在我的块中.我写了以下代码:
def got_three?(array) array.count {|x|} end got_three? ([1, 2, 2, 2, 4, 5, 4, 6, 7])
有人可以帮忙吗?
您可以使用#count
,但是您还需要另一个原语,因为您要检查每个元素.在这种情况下,#any?
就足够了.
def got_three?(array) array.any? { |y| array.count(y) >= 3 } end
这将返回数组中出现的任何元素是否至少出现三次.
虽然我喜欢Silvio的答案,但它可能会多次迭代阵列.
根据数组的大小以及使用此方法的频率,它可能会损害性能 - 尤其是在用于服务器应用程序时,例如在Rails中.
以下可能不太可读,但应该更好:
def has_three?(array) h = (Thread.current[:_has_three_buffer] ||= Hash.new(0)).clear array.each do |i| h[i] += 1 return true if h[i] >= 3 end false end
关于方法优化的快速解释.
速度:
该方法使用哈希来映射每个元素重复的次数.这允许更少的迭代.
该方法不再迭代数组一次,停止true
响应可用的时刻.
此方法将读取阵列中至少3个元素.最多,此方法将遍历数组迭代一次.
内存和对象分配(开销):
创建一个Hash来映射数组是一个昂贵的开销,可以(并且应该)最小化.
开销是由与对象创建和初始化(包括初始内存分配)相关的性能价格引起的.
基准测试表明,当三重物体在阵列内更深时(即位于第三个位置之后),开销非常值得.
此方法为每个线程创建一个Hash,只要调用该方法,就会使用该Hash.这是一种比使用Mutex更好的方法,它可以提供线程安全性但防止并行并发(即使用JRuby时).
Hash使用Thread.current
内存存储在当前线程中.
Hash:Thread.current[:_has_three_buffer] ||= Hash.new(0)
将保留在线程的内存中,直到线程退出,这样就可以避免主要的开销,并用访问Hash的较小开销替换,并使用"清除"它的内存clear
.
这Hash.new(0)
意味着哈希将返回0
而不是nil
缺少元素.
基准测试不同的解决方案让我感到惊讶.Silvio的解决方案并不像我担心的那么慢,Jon的解决方案比我想象的要慢......
我也对解决方案使用的内存感到惊讶.我的方法使用了比我想象的更多的内存,但与Wand的方法相比,它仍然是优化的
以下是使用某些样本数组进行100,000次迭代的测量.
前两个基准测试是一个10项长的数组,其中三元组(对象出现三次)在开头(由于它的开销导致我的答案处于劣势)或者结束(某些答案由于迭代).
- 数组(10项):[1,1,1,2,3,4,5,6,7,8]
silvio 0.040000 0.000000 0.040000(0.043519)
myst 0.080000 0.000000 0.080000(0.074734)
jon 0.240000 0.010000 0.250000(0.252569)
魔杖0.260000 0.030000 0.290000(0.289101)
- 数组(10项):[8,7,6,5,4,3,2,1,1,1]
silvio 0.310000 0.000000 0.310000(0.313565)
myst 0.300000 0.000000 0.300000(0.298405)
jon 0.530000 0.010000 0.540000(0.534697)
魔杖0.310000 0.030000 0.340000(0.350704)
接下来,我测量了一个10项长阵列,其中三联体位于大约中间位置(从项目编号5开始).
我还回顾了100项迭代的10项长阵列评论的近似内存使用情况.内存审查包括用于基准测试和构建报告的内存,它在任何方面都不准确......但它可能显示了一些内容......
- 数组(10项):[1,2,3,4,5,5,5,8,9,10]
使用的内存(通过打印数组):4KB - 显示这是不准确的
silvio 0.200000 0.000000 0.200000(0.200012)
使用的内存(silvio):4KB
myst 0.210000 0.010000 0.220000(0.214936)
使用的内存(myst):5,944KB
jon 0.420000 0.000000 0.420000(0.422354)
使用的内存(jon):18,364KB
魔杖0.300000 0.030000 0.330000(0.342656)
使用的内存(魔杖):118,496KB(~118MB!)
正如您所看到的,在查看短数组(10个项目)时,差异非常小,可能不值得用于优化更长数组方法的内存.
接下来我做了同样的事情(中间的三重奏),有50个项目的长数组...这是迭代的差异真的出现的时候.也就是说,虽然我的答案使用了大约1.5秒,但Silvio的方法花费了大约7.5秒 - 大约慢了5倍.
- 数组(50项):[1 ... 47,50,50,50]
silvio 7.560000 0.010000 7.570000(7.570565)
myst 1.480000 0.010000 1.490000(1.487640)
jon 8.290000 0.020000 8.310000(8.316714)
魔杖1.550000 0.150000 1.700000(1.700455)
然而,Jon指出基准测试非常无菌,除了三元组之外没有重复的对象,所以接下来我用100个项目对数组进行基准测试,其中每个项目重复两次(除了三元组).三胞胎在中间.
这是Jon的开销(使用unique
)显示它的优势.虽然乔恩的方法花了大约8.6秒,但西尔维奥的进近达到了大约14秒.魔杖和我都保持在个位数,我的答案是最快的 - 约1.2秒.
我还回顾了迭代和Benchmarking本身导致的大致内存增长......
一些解决方案使用了大量内存.也就是说,Wand的漂亮解决方案为100个长项目(100K迭代)构建了~0.5GB(!),而Jon的解决方案使用了~50MB,我的解决方案可以达到~5MB,而Silvio的解决方案似乎根本没有使用任何内存(这可能是一个小故障,显示内存测试有多么不准确).
同样,内存使用不准确.
- 数组(100项):[1..23,1..23,24,25,25,25,26..50,26..50]
使用的内存(通过打印数组):4KB - 显示这是不准确的
silvio 14.090000 0.010000 14.100000(14.100915)
使用的内存(silvio):0KB
myst 1.170000 0.010000 1.180000(1.182665)
使用的内存(myst):5,992KB
jon 8.570000 0.020000 8.590000(8.592982)
使用的内存(jon):51720KB
魔杖1.940000 0.160000 2.100000(2.093121)
使用的内存(魔杖):569,264KB(~569MB!)
基准代码:
require 'benchmark' def silvio_got_three?(array) array.any? { |y| array.count(y) >= 3 } end def myst_has_three?(array) h = (Thread.current[:_has_three_buffer] ||= Hash.new(0)).clear # h = Hash.new(0) array.each do |i| h[i] += 1 return true if h[i] >= 3 end false end def jon_has_three?(array) array.uniq.any? { |x| array.count(x) >= 3 } end def wand_has_three?(array) array.group_by {|i| i }.any? {|_, v| v.size >= 3} end def get_total_memory_used `ps -o rss= -p #{Process.pid}`.to_i end # warm-up myst_has_three? [0] Benchmark.bm do |bm| a = [1,1,1] + (2..8).to_a repeat = 100_000 GC.disable puts "Array (#{a.length} items): #{a.to_s}" bm.report('silvio') { repeat.times { silvio_got_three?(a) } } bm.report('myst') { repeat.times { myst_has_three?(a) } } bm.report('jon') { repeat.times { jon_has_three?(a) } } bm.report('wand') { repeat.times { wand_has_three?(a) } } a.reverse! puts "Array (#{a.length} items): #{a.to_s}" bm.report('silvio') { repeat.times { silvio_got_three?(a) } } bm.report('myst') { repeat.times { myst_has_three?(a) } } bm.report('jon') { repeat.times { jon_has_three?(a) } } bm.report('wand') { repeat.times { wand_has_three?(a) } } a = (1..4).to_a + [5,5,5] + (8..10).to_a mem = get_total_memory_used puts "Array (#{a.length} items): #{a.to_s}" puts "Memory used (by printing the Array): #{get_total_memory_used - mem}KB - showing this isn't accurate" mem = get_total_memory_used bm.report('silvio') { repeat.times { silvio_got_three?(a) } } puts "Memory used (silvio): #{get_total_memory_used - mem}KB" mem = get_total_memory_used bm.report('myst') { repeat.times { myst_has_three?(a) } } puts "Memory used (myst): #{get_total_memory_used - mem}KB" mem = get_total_memory_used bm.report('jon') { repeat.times { jon_has_three?(a) } } puts "Memory used (jon): #{get_total_memory_used - mem}KB" mem = get_total_memory_used bm.report('wand') { repeat.times { wand_has_three?(a) } } puts "Memory used (wand): #{get_total_memory_used - mem}KB" mem = get_total_memory_used a = (1..47).to_a + [50,50,50] puts "Array (#{a.length} items): #{a.to_s}" bm.report('silvio') { repeat.times { silvio_got_three?(a) } } bm.report('myst') { repeat.times { myst_has_three?(a) } } bm.report('jon') { repeat.times { jon_has_three?(a) } } bm.report('wand') { repeat.times { wand_has_three?(a) } } a = (1..23).to_a*2 + [24,25,25,25] + (26..50).to_a*2 mem = get_total_memory_used puts "Array (#{a.length} items): [1..23,1..23,24, 25,25,25, 26..50,26..50]" puts "Memory used (by printing the Array): #{get_total_memory_used - mem}KB - showing this isn't accurate" mem = get_total_memory_used bm.report('silvio') { repeat.times { silvio_got_three?(a) } } puts "Memory used (silvio): #{get_total_memory_used - mem}KB" mem = get_total_memory_used bm.report('myst') { repeat.times { myst_has_three?(a) } } puts "Memory used (myst): #{get_total_memory_used - mem}KB" mem = get_total_memory_used bm.report('jon') { repeat.times { jon_has_three?(a) } } puts "Memory used (jon): #{get_total_memory_used - mem}KB" mem = get_total_memory_used bm.report('wand') { repeat.times { wand_has_three?(a) } } puts "Memory used (wand): #{get_total_memory_used - mem}KB" mem = get_total_memory_used GC.start end; nil
Silvio的回答是有效的,但正如Myst指出的那样,它将使用相同的参数多次迭代数组.
如果没有Myst解决方案的复杂性,这应该会更好一些:
def has_three?(array) array.uniq.any? { |x| array.count(x) >= 3 } end
这种微小的差异意味着您只会为每个唯一元素迭代一次数组,而不是对每个重复元素运行额外的检查.