在Ruby中从变量创建一个不包含nil值的数组

4

如何最自然地创建一个数组,从多个变量中筛选出不是 nil 的值?

给出以下这些变量:

a = 1
b = nil
c = 3

我想创建一个数组ary:
ary #=> [1, 3]

我可以使用Array#compact

ary = [a, b, c].compact
ary #=> [1, 3]

但是仅仅为了删除特定元素而将所有内容放在数组中并不合适。

另一方面,使用if语句会产生更多的代码:

ary = []
ary << a if a
ary << b if b
ary << c if c
ary #=> [1, 3]

是否有其他或更好的方法,使用上述任何一种方法有什么优缺点?


PS:false 不一定要考虑。变量可以是真值(数字/字符串/数组/哈希),也可以是 nil


你的关注点是需要两次循环遍历值,还是仅仅因为听起来不好? - ndnenkov
@ndn 这不是技术原因。如果我已经有一个数组,我会使用 compact。但由于我正在创建一个新数组,我认为最好不要一开始就添加 nil 值。但这只是我的个人意见 :-) - Stefan
你必须要么在代码中明确地逐个列出它们,要么创建一个集合并以某种方式循环遍历它们。无论有什么语法糖,我认为都没有其他选择。如果这个方法被称为non_nil_values,你会感到满意吗? - ndnenkov
1
#compact! 在我看来是最好的方法,每个人都能一眼看出它只是这样做的,并且作为标准库的一部分,它的开销很小。虽然我不确定,但它可能比 #<<if 的流甚至更快。 - Borsunho
1
@undur_gongor 是的,这可能会减少一些视觉杂乱,但从实现的角度来看,它只是将 if 移动到一个方法中。 - Stefan
显示剩余4条评论
3个回答

4

如果您关心性能,最好的方法可能是使用破坏性 #compact! 来避免为第二个数组分配内存。


2
你是对的,compact 会返回一个新的数组。另一方面,compact! 可能会返回 nil,因此你需要将赋值和方法调用分开,即 ary = [a, b, c]; ary.compact! - Stefan
@Stefan 在这种情况下,#compact! 永远不会返回 nil,因为您有一定的变量集,所以保证数组不会为空。 - Borsunho
2
[1, 2, 3].compact! 返回 nil,这是 预期的“如果没有进行任何更改,则返回 nil” - Stefan

2
我希望有一种方法可以在创建数组时“跳过”nil值。但是经过一段时间的思考,我意识到由于Ruby处理多个值的方式,这是不可能实现的。Ruby没有“值列表”的概念,多个值总是表示为一个数组。
如果你分配多个值,Ruby会创建一个数组:
ary = 1, nil, 3
#=> [1, nil, 3]

对于接受可变数量参数的方法同样适用:

def foo(*args)
  args
end

foo(1, nil, 3)
#=> [1, nil, 3]

即使我使用类方法new_without_nilArray进行修补,结果仍然如下所示:
def Array.new_without_nil(*values)
  values.compact!
  values
end

这只是将代码移动到其他地方。

一切皆为对象

从面向对象的角度来看,nil没有什么特别之处 - 它就像任何其他对象一样。因此,删除nil与删除1没有区别。

另一方面,当编写面向对象的代码时,我试图避免使用一堆if语句。我更喜欢向对象发送消息。

关于“优点或缺点”:

[...]compact / compact!

  • 创建完整的数组并根据需要缩小
  • 短代码,通常适合一行
  • 很容易被识别
  • 每个项目只计算一次
  • 更快(已编译的C代码)

[...]<<if语句

  • 创建空数组并根据需要增长
  • 长代码,每个项目占一行
  • 目的可能不太明显
  • 项目可以轻松注释/取消注释
  • 每个项目计算两次
  • 较慢(解释的Ruby代码)

结论:

我将使用compact,这可能是显而易见的。


不确定你所说的“没有值的‘列表’概念”是什么意思。从技术上讲,数组就是列表。还是指链表?既然无论哪种方式都需要使用相同的线性空间和时间,那真的重要吗?另外,我很好奇,你是否对这两个解决方案进行了基准测试?直觉上,我认为随着数组长度的增加,“if”解决方案会更快(假设有足够的“nil”值)。虽然它看起来比较丑陋。 - ndnenkov
@ndn 这就是为什么我在“list”中加了引号。我只是想指出,在Ruby中,可变参数总是会创建一个额外的数组对象。因此,一个方法 def foo(*args) 不会接收多个参数,而是接收单个参数:即数组。从元素中创建新数组是毫无意义的,因为你已经有了一个数组。 - Stefan
关于基准测试:compact 似乎更快。我认为这是因为 if 语句必须逐个解释的原因。 - Stefan

0

这里有一个使用哈希的解决方案。

将这些值放入数组中:

a = 1; b = nil; c = 3; d = nil; e = 10;
ary = [a, b, c, d, e]

结果中有两个空项,需要使用compact来删除这两个“nil”项。

然而,将相同的变量添加到哈希表中:

a = 1; b = nil; c = 3; d = nil; e = 10;
hash = {a => nil, b => nil, c => nil, d => nil, e => nil}

结果中只有一个“nil”项,可以通过hash.delete(nil)轻松删除。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接