Ruby delete_if一个数组子集

时间:2017-03-03 10:40:53

标签: arrays ruby

我有一个数组,我想对该数组的子集(任何n个项)执行delete_if(并在内存中修改数组)

使用完整数组我可以做

array.delete_if do |item|
  should_be_deleted?(item)
end

如果我想限制前n个项目,则以下工作

array.take(n).delete_if do |item|
  should_be_deleted?(item)
end

因为它将创建一个新数组并在该新数组上执行delete_if

是否有替代方法,例如只删除前n个项目的take_and_delete_if(如果每个块的块返回true)?

编辑:

我希望从数组abc处理3个块(并在执行操作后从数组中删除)

by_batch_of(3, until: (proc { a.empty? })) do 
  # This sets an instance variable @by = 3, and will iterate as long as `a` has any item
  process_from_a # Will move @by items in a to either array b or c or fail
  process_from_b # Will move @by items in b to c or fail
  process_from_c # Should move items or fail and put back in a
end

样品处理方法

process_from_a(by: @by)
  a.take_and_delete_if(by: by) do |item| # The +take_and_delete_if+ methods is the one I need
  b << item if reason1
  c << item if reason2
  reason1 or reason2 # Delete if the item was moved away
end

性能是我正在寻找的

示例

a = [1,2,3,4,5,6,7,8,9]
b = []
c = []

第1批3

  • process_from_a(by:3)

    a = [3,4,5,6,7,8,9] # 3 failed so delete_if returned false, it remains in the array (order doesn't matter)
    b = [1] # 1 moved to b
    c = [2] # 2 moved to c
    
  • process_from_b

    a = [3,4,5,6,7,8,9]
    b = []
    c = [1,2] # 2 moved to c
    
  • process_from_c

    a = [3,4,5,6,7,8,9,1] # 1 was rejected in a
    b = []
    c = [] # 1,2 processed from c
    

下一次迭代将例如来自a等的处理[3,4,5]

性能

假设我的数组非常大(10k,100k),并且我想要批量处理10个流程项。我不想要昂贵的解决方案来过滤前10个项目并使用index < 10删除整个数组...

5 个答案:

答案 0 :(得分:1)

逻辑可能如下所示:

array.delete_if do |item|
  next if should_be_skipped?(item)
  should_be_deleted?(item)
end

示例:

a = [1,2,3,4,5,6]
a.delete_if do |item|
  next if item == 2 # would skip 2 because we want so
  item % 2 == 0     # would remove all even numbers (except for 2)
end
#=> [1, 2, 3, 5]

只是澄清一下:答案是相当笼统的,只是为了向OP展示如何处理此类案件的想法。

修改

对于特定情况,要跳过4个您要使用的第一个元素:

a = [1,2,3,4,5,6,7,8]
a.delete_if.with_index do |item, index|
  index > 3 && item.even?
end
#=> [1, 2, 3, 4, 5, 7]

答案 1 :(得分:1)

你可能需要这样的东西吗?

[1,2,3,4,5,6,7,8].delete_if.with_index{|e,i| i<3} # => [4, 5, 6, 7, 8] 

索引范围为0..2的项目已删除

答案 2 :(得分:1)

您可以使用方法#shift删除第一个n元素,例如:

> a = [1, 2, 3, 4, 5]
 => [1, 2, 3, 4, 5] 
 > a.shift(3)
 => [1, 2, 3] 
 > a
 => [4, 5]

答案 3 :(得分:1)

应该可以对子集中的过滤元素执行in place replacement

a = (0..10000).to_a;
a[0, 100] = a[0, 100].delete_if(&:odd?)

基准:

require 'benchmark/ips'

Benchmark.ips do |x|
  x.report("with_index")  { (0..10000).to_a.delete_if.with_index { |k, i| k.odd? && i < 100 } }
  x.report("slice") { a = (0..10000).to_a; a[0, 100] = a[0, 100].delete_if(&:odd?) }

  x.compare!
end

在MRI Ruby 2.4.0p0上提供这些结果:

Warming up --------------------------------------
          with_index    58.000  i/100ms
               slice   273.000  i/100ms
Calculating -------------------------------------
          with_index    602.354  (± 6.6%) i/s -      3.016k in   5.033200s
               slice      2.775k (±10.0%) i/s -     13.923k in   5.075605s

Comparison:
               slice:     2774.9 i/s
          with_index:      602.4 i/s - 4.61x  slower

答案 4 :(得分:0)

def skip_then_test(arr, nbr_to_skip)
  arr.delete_if { |item| (nbr_to_skip -= 1) < 0 && item.even? }
end

skip_then_test [2,3,4,5,6,7,8], 0
  #=> [3, 5, 7] 
skip_then_test [2,3,4,5,6,7,8], 1
  #=> [2, 3, 5, 7] 
skip_then_test [2,3,4,5,6,7,8], 2
  #=> [2, 3, 5, 7] 
skip_then_test [2,3,4,5,6,7,8], 3
  #=> [2, 3, 4, 5, 7]

arr = [2,3,4,5,6,7,8]  
skip_then_test arr, 4
  #=> [2, 3, 4, 5, 7]
arr 
  #=> [2, 3, 4, 5, 7]

另一种方式如下。

def skip_then_test(arr, nbr_to_skip)
  arr.replace(arr[0, nbr_to_skip] + arr[nbr_to_skip..-1].delete_if(&:even?))
end

arr = [2,3,4,5,6,7,8]
skip_then_test arr, 3
  #=> [2, 3, 4, 5, 7] 
arr
  #=> [2, 3, 4, 5, 7]