我有一个Ruby元素数组

nums = [1,1,1,2,2,3]

如果元素在数组中出现两次以上,则任务是就地删除一些重复项.

我编写了以下代码:

nums = [1,1,1,2,2,3]

def remove_elements(nums)
  nums.delete_if{ |num| nums.count(num) > 2 } #reject! returns the same result
end

所以我预料到了这个结果

[2,2,3]

但得到

[2,3]

但是,reject方法返回预期的数组

def remove_elements(nums)
  nums.reject{ |num| nums.count(num) > 2 } # returns [2,2,3]
end

推荐答案

这是由于delete_if/reject!(及其对应的keep_if/select!)在内部的工作方式.让我们看看另一个例子,它在每个步骤打印nums的同时删除偶数:

nums = [0, 1, 2, 3, 4, 5, 6]

def remove_elements(nums)
  nums.reject! do |num|
    p nums: nums
    num.even?
  end
end

p result: remove_elements(nums)

输出:

{:nums=>[0, 1, 2, 3, 4, 5, 6]}
{:nums=>[0, 1, 2, 3, 4, 5, 6]}
{:nums=>[1, 1, 2, 3, 4, 5, 6]}
{:nums=>[1, 1, 2, 3, 4, 5, 6]}
{:nums=>[1, 3, 2, 3, 4, 5, 6]}
{:nums=>[1, 3, 2, 3, 4, 5, 6]}
{:nums=>[1, 3, 5, 3, 4, 5, 6]}
{:result=>[1, 3, 5]}

正如你所看到的,nums正在以一种看似奇怪的方式进行修改.但这里到底发生了什么?

Ruby的实现没有创建临时数组,而是重用现有数组来存储结果.当它遍历数组时,它将每个应该保留的元素(块返回falsy的结果)复制到前面,复制任何现有的元素.最后,数组被截断到其最终大小:(ASCII艺术在前面)

[0, 1, 2, 3, 4, 5, 6]
 ┌──┘
[1, 1, 2, 3, 4, 5, 6]
    ┌─────┘
[1, 3, 2, 3, 4, 5, 6]
       ┌────────┘
[1, 3, 5, 3, 4, 5, 6]

[1, 3, 5]
 ───┬───
  result

注意,135在数组中中间出现两次.这就是为什么你的nums.count不能像预期的那样工作--你在重新构造数组的时候计算元素.


要解决这个问题,您可以提前计算元素,例如通过tally:

nums = [1, 1, 1, 2, 2, 3]

counts = nums.tally
#=> {1=>3, 2=>2, 3=>1}

nums.delete_if { |num| counts[num] > 2 }
#=> [2, 2, 3]

这也更快,因为您不必为了计数而再次遍历整个数组的每个元素.

Ruby-on-rails相关问答推荐

HTTP:MimeNegotiation::InvalidType(html不是有效的MIME类型):""

刺激不添加侦听器以搜索表单输入

在 Rails 7 中,可以将导入映射与 js Bundle 解决方案一起使用,还是它们是专有的?

使用rails form_for时带有_path的未定义方法

在 rails 3 中设置记录器

如果 URL 不存在,请将 http(s) 添加到 URL?

rails:防止闪烁消息显示两次

如何将自定义过滤器添加到 Active Admin?

没有要加载的文件 - readline

在 Windows 上安装特定的 Ruby on Rails 版本

如何在 Rails 应用程序中使用长 id?

Rails:仅当值存在时如何验证格式?

rmagick 和 OS X Lion

获取磁盘上 ActiveStorage 文件的路径

Rails:带参数的 URL/路径

如何为模型添加属性?

Rails 4模块的未初始化常量

如何为 rspec 设置 ENV 变量?

Ruby on Rails:如何在 select_tag 中使用默认占位符?

如何创建一个 ruby​​ Hello 世界?