如果元素在数组中出现三次以上,则返回布尔值

时间:2015-12-20 01:15:43

标签: ruby

如果数组包含三次或更多元素,我需要取一个数组并返回true。如果数组包含少于三次的元素,则需要返回false。我猜我需要使用count方法,但我不知道要放在我的块中。我写了以下代码:

def got_three?(array)
  array.count {|x|}
end

got_three? ([1, 2, 2, 2, 4, 5, 4, 6, 7])

有人可以帮忙吗?

4 个答案:

答案 0 :(得分:7)

您可以使用#count,但您还需要另一个原语,因为您要检查每个元素。在这种情况下,#any?就足够了。

def got_three?(array)
  array.any? { |y| array.count(y) >= 3 }
end

返回数组中出现的任何元素是否至少出现三次。

答案 1 :(得分:5)

虽然我喜欢西尔维奥的答案,但它可能会多次迭代阵列。

根据数组的大小以及使用此方法的频率,可能会影响性能 - 尤其是在用于服务器应用程序时,例如在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

修改

关于方法优化的快速解释。

  1. <强>速度

    该方法使用Hash来映射每个元素重复的次数。这样可以减少迭代次数。

    该方法不再迭代数组一次,停止true响应可用的时刻。

    此方法将读取阵列中至少3个元素。最多,此方法将遍历数组迭代一次。

  2. 内存和对象分配(开销):

    创建一个Hash来映射数组是一个昂贵的开销,可以(并且应该)最小化。

    开销是由与对象创建和初始化相关的性价格(包括初始内存分配)引起的。

    基准测试表明,当三重物体在阵列中较深时(即位于第三个位置之后),开销非常值得。

    此方法为每个线程创建一个Hash,无论何时调用该方法都将使用该Hash。这是一种比使用Mutex更好的方法,它可以提供线程安全但防止并行并发(即使用JRuby时)。

    使用Thread.current的内存存储将哈希存储在当前线程中。

    Hash:Thread.current[:_has_three_buffer] ||= Hash.new(0)将保留在线程的内存中,直到线程退出,这样可以避免主要的开销,并替换为访问Hash和&#34;结算&#34;使用clear的内存。

    Hash.new(0)表示对于缺少的元素,哈希将返回0而不是nil

  3. 更新的基准

    对不同的解决方案进行基准测试让我感到惊讶。西尔维奥的解决方案并不像我担心的那么缓慢,乔恩的解决方案比我想象的慢......

    我也对解决方案使用的内存感到惊讶。我的方法使用了比我想象的更多的内存,但与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&# 39; s解决方案似乎根本没有使用内存(这可能是一个小故障,显示内存测试有多么不准确)。

    同样,内存使用并不准确。

      

    - 数组(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
    

答案 2 :(得分:5)

西尔维奥的答案会有效,但正如Myst所指出的那样,它将使用相同的参数多次迭代数组。

如果没有Myst的解决方案的复杂性,这应该会更好一些:

def has_three?(array)
  array.uniq.any? { |x| array.count(x) >= 3 }
end

这种微小的差异意味着您只会为每个唯一元素迭代一次数组,而不是对每个重复元素运行额外的检查。

答案 3 :(得分:2)

你也可以这样做:

print [{x:y} for x,z in scrabble_scores for y in z]