如何删除前3个重复值并返回包含其余值的数组?

时间:2014-02-09 03:46:29

标签: ruby

鉴于这些数组,如何在保持数组中的第四个或第五个的同时删除三次出现的值?

[1,5,1,1,1] # => [1,5]
[3,3,3,2,3] # => [3,2]
[3,4,5,3,3] # => [4,5]
[1,1,1,1,1] # => [1,1]
[1,2,2,4,5] # => [1,2,2,4,5]

这是我尝试过的:

array = [1,5,1,1,1]
top3 = array.select { |x| array.count(x) >= 3 }[0..2]
last2 = array - top3

这种策略(和类似的)似乎只有三个重复但不是四个或五个时才有用。这个问题有优雅的解决方案吗?

更新:谢谢你的惊人答案。作为一个开始的rubyist,我从分析每个响应中学到了很多东西。我的问题来自于对一个骰子计划的Ruby Koan挑战。这是我用Abdo的建议实现的完整解决方案。我确信有更有效的方法来实现该程序:)

def score(dice)
  a,b,c,d,e = dice
  array = [a,b,c,d,e]
  total = 0
  triples = array.select {|x| array.count(x) >= 3}[0..2]
  singles = array.group_by{|i| i}.values.map{ |a|
       a.length > 2 ? a[0, a.length - 3] : a
      }.inject([], :+)

# Calculate values for triples
# 1 * 3 = 1000pts
# 2 * 3 = 200pts
# 3 * 3 = 300pts
# 4 * 3 = 400pts
# 5 * 3 = 500pts
# 6 * 3 = 600pts
case triples[0]
  when 1 then total += triples[0]*1000
  when (2..6) then total += triples[0]*100
end

# Calculate values for singles:
# 1s = 100pts each
# 5s = 50pts each
singles.include? (1) ? singles.select {|x| x == 1 }.each {|x| total += x*100 } : total
singles.include? (5) ? singles.select {|x| x == 5 }.each {|x| total += x*10 } : total

return total
end

puts score([5,1,1, 5, 6]) # 300 points
puts score([]) # 0 points
puts score([1,1,1,5,1]) # 1150 points
puts score([2,3,4,6,2]) # 0 points
puts score([3,4,5,3,3]) # 350 points
puts score([1,5,1,2,4]) # 250 points

6 个答案:

答案 0 :(得分:2)

array = [1,5,1,1,1]
occurrence = {}

array.select do|a|
  if(array.count(a) > 3)
    occurrence[a] ||= []
    occurrence[a] << a
    occurrence[a].count > 3
  else
    true
  end
end

PS:此解决方案保留原始数组中元素的顺序

答案 1 :(得分:2)

当数组大小很大时,这是一个更快的解决方案: (我避免使用count,因为它会在内部循环中循环遍历数组)

arr.inject({}) { 
  |h, i| h[i] ||= 0; h[i] += 1; h 
}.collect_concat {|k,v| [k] * (v > 2 ? v - 3 : v) }

这是与其他工作解决方案的果味比较:

arr = 1000.times.collect { rand(100) }.shuffle

require 'fruity'

compare do
  vimsha { 
    occurrence = {}; 
    arr.select do|a|
      if(arr.count(a) > 3)
        occurrence[a] ||= []
        occurrence[a] << a
        occurrence[a].count > 3
      else
       true
      end
    end
  }

  caryswoveland {
    arr.uniq.reduce([]) {|a,e| a + [e]*((cnt=arr.count(e)) > 2 ? cnt-3 : cnt)}
  }

  aruprakshit {
    num_to_del = arr.find { |e| arr.count(e) >= 3 }
    if !num_to_del.nil?
     3.times do
      ind = arr.index { |e| e == num_to_del }
      arr.delete_at(ind)
     end
    end 
    arr
  }
  # edited as suggested by @CarySwoveland
  abdo {
    arr.each_with_object(Hash.new {|h,k| h[k]=[]}) {|i,h| h[i] += 1
    }.collect_concat { |k,v| [k] * (v > 2 ? v - 3 : v) }
  }

  broisatse { 
      arr.group_by{|i| i}.values.map{ |a| 
       a.length > 2 ? a[0, a.length - 3] : a 
      }.inject([], :+)  
  }
end

以下是比较结果:

Running each test 64 times. Test will take about 48 seconds.
broisatse is faster than abdo by 30.000000000000004% ± 10.0% 
abdo is faster than aruprakshit by 4x ± 1.0 (results differ: ...)
aruprakshit is similar to caryswoveland (results differ: ...)
caryswoveland is similar to vimsha (results differ: ...)

注意:我在方法之外使用了@ aruprakshit的代码,所以我们不会在方法调用本身中浪费时间。

当阵列的大小进一步增加时:

arr = 1000.times.collect { rand(1000) }.shuffle

我们得到:

abdo is faster than broisatse by 3x ± 1.0
broisatse is faster than aruprakshit by 6x ± 10.0
aruprakshit is faster than caryswoveland by 2x ± 1.0
caryswoveland is similar to vimsha 

答案 2 :(得分:1)

另一种方式,假设不需要保留订单(这与提问者的评论一致):

array = [1,2,4,1,2,1,2,1,1,4]

array.uniq.reduce([]) {|a,e| a + [e]*((cnt=array.count(e)) > 2 ? cnt-3 : cnt)}
  #=> [1, 1, 4, 4]

答案 3 :(得分:0)

尝试类似:

a.group_by{|i| i}.values.map{|a| a[0, a.length % 3]}.inject([], :+)

这将从阵列中删除所有三元组。如果您只想删除第一个三元组,请执行以下操作:

a.group_by{|i| i}.values.map{|a| a.length > 2 ? a[0, a.length - 3] : a }.inject([], :+)

注意:这可能会破坏数组的顺序:

[1,2,1,2,3]           #=> [1,1,2,2,3]

如果您需要保留订单,请告诉我,如果需要,如果超过三个,则需要删除哪些元素,例如:该怎么说:[1,1,2,1,1,] - [1,2][2,1]

答案 4 :(得分:0)

x.group_by{|i| i }.values.select{|a| a.size >= 3 }.each{|a| c=[3,a.size].min; x.delete_if{|e| a[0]==e && (c-=1)>=0 } }

它会从[3,a.size].min所在的输入a[0]中删除xa次出现,例如[1,1,1,1] x = [1,2,1,1,1]

答案 5 :(得分:0)

我会这样做:

def del_first_three(a)
  num_to_del = a.find { |e| a.count(e) >= 3 }
  return a if num_to_del.nil?
  3.times do
    ind = a.index { |e| e == num_to_del }
    a.delete_at(ind)
  end
  a
end

del_first_three([3,4,5,3,3]) # => [4, 5]
del_first_three([1,5,1,1,1]) # => [5, 1]
del_first_three([1,2,2,4,5]) # => [1, 2, 2, 4, 5]