Ruby 关于 Array 在 each 中 delete 问题?!

mimosa · 2012年06月15日 · 最后由 zw963 回复于 2012年06月23日 · 6802 次阅读
skus = [
{:size=>"L(成人)", :color=>"灰色", :outer_id=>"BML21B0603000L", :quantity=>"3"}, 
{:size=>"M(成人)", :color=>"灰色", :outer_id=>"BML21B0603000M", :quantity=>"3"}, 
{:size=>"S(成人)", :color=>"灰色", :outer_id=>"BML21B0603000S", :quantity=>"2"}, 
{:size=>"XL(成人)", :color=>"灰色", :outer_id=>"BML21B060300XL", :quantity=>"1"}
] 
skus.each do |sku|
  Sku.create(sku) # 创建
  skus.delete(sku)
end
=> [{:size=>"M(成人)", :color=>"灰色", :outer_id=>"BML21B0603000M", :quantity=>"3"}, {:size=>"XL(成人)", :color=>"灰色", :outer_id=>"BML21B060300XL", :quantity=>"1"}] 

经常通过数组,进行数据插入,为了检测业务逻辑中的各种遗漏,每成功后会从原数组中做 delete 操作,但发现会直接影响 each 的循环次数,以至于更大的遗漏;

大家有什么好的方法吗?

先 dup 一下啊

a=[1,2,3,4,5,6,7,8,9]
b = a.dup
 => [1, 2, 3, 4, 5, 6, 7, 8, 9] 
 a.each do |i|
   b.delete(i)
 end
 => [2, 4, 6, 8] 
a=[1,2,3,4,5,6,7,8,9]
b = a.select{|x| x>5} #[6,7,8,9]

木桶故事么,倒着删

a=[1,2,3,4,5,6,7,8,9]
a.delete_if {|x| ...} 

我现在的做法是:

def process_sync(skus)
      skus_data = {}
      skus.each do |sku|
        key = sku['num_iid']
        skus_data[key] = [] unless skus_data.has_key?(key)
        skus_data[key] << sku
      end
      item_ids = skus_data.keys
      Item.any_in(num_iid: item_ids).each do |item| # 商品
        skus = skus_data[item.num_iid]
        updated   = [] # 更新
        unchanged = [] # 无变化
        item.skus.each do |sku|
          skus.each do |data|
            if sku.sku_id == data['sku_id']
                if data['modified'] > sku.modified_at
                  sku.update_attributes(data) 
                  updated << data
                else
                  unchanged << data
                end
            end
          end
        end
        created = skus - ( updated + unchanged ) # 新增
        unless created.empty?
          created.each do |data|
            item.skus.create(data)
          end
        end
        puts "Sku.process_sync==============(#{item.num_iid})==============提示"
        puts "本次同步,共获取 #{skus.count} 单品,其中 新增 #{created.count},更新 #{updated.count},无变化 #{unchanged.count} 单品"
        skus_data.delete(item.num_iid)
      end
      unless skus_data.empty?
        puts "Sku.process_sync============================提示"
        puts skus_data
      end
    end

4 楼正解,select! 和 delete_if 都是会遍历数组的,由代码块的返回值决定是否保留数组项

‘’‘ a=[1,2,3,4,5,6,7,8,9] => [1, 2, 3, 4, 5, 6, 7, 8, 9] a.each do |i| a.delete(i) end => [2, 4, 6, 8]

’‘’ 我初学 ruby,我说下我的看法,首先咱们看下 each 和 map 的区别,each 后的代码块不管处理啥都会返回原数组,Map 将返回处理过的数组。

好,我们看下代码,第一个元素是 1,你操作了删除 1,那么这个时候 a=[2,3,4,5,6,7,8,9],each 内代码块以为 a 的索引应该到 a[1] 也就是第二个,那么就把 3 删掉,以此下去。

所以看到的结果是【2,4,6,8】,这个结果就是 现在 a 数组的原始数据了。

我是这么理解的。 另外要防止这样的情况,有很多办法啊,比如你自己定义方法然后携带 Block,最简单就是 复制一份 a 数组,然后从 a 开始 循环,删除 B 数组 ''' a=[1,2,3,4,5,6,7,8,9] b=a => [1, 2, 3, 4, 5, 6, 7, 8, 9] a.each do |i| b.delete(i) end '''

4L 的作法也对的

7 楼正解

delete_if + 1

所有的语言都要尽量避免在循环内部修大幅改循环继续或停止的算法和条件。 C/Java 的循环基本上就是 ++ 或者 --

如果一个 loop 写成这样,就很难搞

for(int i = 0; i < 100;i++){
  int x,y,z
  if( ... ){
    ....
    i+= 2x-y+z^
  }else{
   .....
   i-= x+3y
  }
 i-=2
} 

你的例子中,a 内部的元素就是循环继续或停止的条件。

不然很头疼的,比如跟你的例子相似,改一个地方就麻烦了:

a=[1,2,3,4,5,6,7,8,9]
a.each do |i|
  a << i
end

在对一个集合迭代操作时,删除集合内元素,这肯定会出错的。

#1 楼 @hooopo

二楼是正解

a.replace [] 不就删完了嘛

飘过..... 遍历老数组,把要删的东西,先放到一个新数组里面, 然后遍历新数组,删除老数组的元素。 不过这样性能比较弱。

我觉得更好的思路是, 把你想要的东西,放到一个新数组里面, 而不是删除你不想要的东西。

支持 delete_if,我的习惯是有现成的方法,坚决使用现成的方法。除非真的需要考虑性能的问题。这里的 delete_if 不但简洁,写出的代码可读性很强,性能也是很好啊。 另外,如果需要在循环里面做其他事情,支持@hooopo 的做法。 对于 hash 的类似情况,我还会选择 hash.keys.each do; ; end;这种写法。 同意@Anleb 对于 ruby 里面的 each 方式,在循环体里面做自身的元素增减操作都有可能出现 surprise。

谢谢大家的热心回答,#14 楼 @zlx_star 总结的很好,受教了~~

#10 楼 @hisea

哇~ 刚发现,你这段代码真不是一般的神奇...

a=[1,2,3,4,5,6,7,8,9]
a.each do |i|
  a << i
end
需要 登录 后方可回复, 如果你还没有账号请 注册新账号