如何检查一个数组的值是否包含一个或多个特定的值?

23

我想知道一个数组中是否包含一个或多个特定的值。例如,如下所示:

[1,2,3,4,5,6].include?([4,1])  # => true
[4,1,6,2].include?([4,1])  # => true
[3,4,7].include?([4,1])  # => false
当然,"include?"方法只能检查一个值。是否有一种方法可以检查多个值?

你在这里使用了哪个Ruby版本? 我每次都得到false。 irb(main):006:0> [1,2,3,4,5,6].include?([4,1]) => false irb(main):007:0> [4,1,6,2].include?([4,1]) => false irb(main):008:0> [3,4,7].include?([4,1]) - Dhanu Gurung
12个回答

61
>> [1,2,3,4,5,6] & [4,1]
=> [1, 4]
>> [1,2,3,4,5,6] & [7,9]
=> []
>>

1
很棒的方法,kurami。谢谢! - sjsc
只是做个记录,这意味着“&”是集合交集运算符,返回两个数组中共同元素的集合。 - Chim Kan
需要注意的一个陷阱是订单可能会被调换。为了检查A是否是B的子集,请使用A&B == A; 如果B和A的顺序不同,则B&A == A并不总是有效。 - Jim Pedid
要获取操作符所指示的布尔结果,请使用any?,如下所示:([1,2,3,4,5,6] & [4,1]).any? #=> true - spyle

26

这是一个集合操作。Set 是标准库中的一部分。

require 'set'

a = Set[1,2,3,4,5,6]
b = Set[4,1]

b.subset? a
#=> true

对我非常有效,谢谢。只想提一下,Set [1] .proper_subset? Set [1] 将返回false。因此,如果您不能保证将处理多个值,则Set [1] .subset?Set [1] 将返回true,就像上面的示例返回true一样。 - johnnyx25
请注意,如果您将数组转换为集合,则此过程可能会非常缓慢,相比之下,(a & b).size == b.size 似乎是最快的方法。 - mrbrdo
@mrbrdo 虽然这是正确的,但有时您确实想要一个Set(例如具有自动去重功能),在这种情况下,Array本来就不应该被构建! 在我看来,标准库类经常被忽视。 - Mark Thomas
我只是在说,在这种情况下使用set的性能要差得多。只有当你从一开始就使用set时,才能真正受益于它,但如果你将数组转换为set,这个操作通常比你实际想要执行的操作花费更长的时间,所以在这种情况下不值得。但是,Set在其他情况下非常好用,我有时也会使用它。只需要注意性能方面的考虑。 - mrbrdo
1
无论速度快慢,当您需要确保时,这是完美的方法,也是我选择的预期答案。 - dimitarvp

17

编辑:我支持Mark Thomas的备选解决方案,该解决方案使用了核心Set

虽然我的解决方案更严格地回答了如何使用数组来解决这个问题,但sjsc可能会从审查自己的情况并探索使用集合的选项中受益。

有很多使用数组的有效原因(保持顺序,允许重复),对于这些原因,下面的解决方案仍然足够,但如果没有涉及到这些内容,则sjsc实际上可能会从使用Set而不是Array中受益,从这个角度来看,Mark的解决方案在语义上更加优越。


我不知道是否有任何库方法可以做到这一点,但编写自己的函数并不难。

class Array
  def subset?(a)
    (self - a).length == 0
  end
end

我相信有计算效率更高的方法来完成这个任务,但这应该可以做到你想要的。
对数组进行交集运算基本上是一样的。
class Array
  def subset?(a)
    (self & a).length == length
  end
end

在这个层面进行优化并不能够帮助太多,但是你不想做的是多次比较数组:

class Array
  # don't do this
  def subset?(a)
    (self & a) == a
  end
end

太棒了的方法。谢谢Steven! - sjsc
1
不需要重新发明轮子,因为Set类已经在核心库中了。请看我的回答。 - Mark Thomas
谢谢,马克。是我的错误,在回答问题之前我没有做好研究,去寻找“set”类。 - Steven

6
@Schwartzie的方法的一个简单而粗略的扩展:
larger_array = [1,2,3,4,5,6]
smaller_array = [4,1]
smaller_array.all? {|smaller_array_item| larger_array.include?(smaller_array_item)}

5

根据Kurumi和Spyle的建议,这是我的测试:

([1,2,3,4,5,6] & [4,1]).any? #=> true

然而,.any? 会将任何对象转换为true。

([1,2,3,4,5,6] & [6,7]).any? #=> true

所以我认为这里可能有一个可行的方法:

([1,2,3,4,5,6] & [6,7]).length == [6,7].length #=> false

( bigger_array & smaller_array ).length == smaller_array.length


3

我喜欢kurumi的回答,但是再提供一个:

>> set1 = [1,2,3,4,5,6]
[
    [0] 1,
    [1] 2,
    [2] 3,
    [3] 4,
    [4] 5,
    [5] 6
]
>> set2 = [4,1]
[
    [0] 4,
    [1] 1
]
>> set1.any?{ |num| set2.include?(num) }
true
>> set2 = [8,9]
[
    [0] 8,
    [1] 9
]
>> set1.any?{ |num| set2.include?(num) }
false

3

[1,2,3,4,5,6].include?(4) and [1,2,3,4,5,6].include?(1)有什么问题吗?


谢谢Schwartzie。你知道有没有更有效的方法吗? - sjsc
非常感谢你尝试帮助Schwartzie。再次感谢你。 - sjsc
谢谢,Schwartzie!我不知道“-”的内部细节,所以它可能不是最佳方法,而且它的中等多项式平均和最坏情况复杂度让我有点担心。如果我们想要更丑陋但更有效率,我们只需在找到一个元素“self”不在“a”中时立即停止检查器。然而,我不知道Ruby解释器是否会自动执行此操作。 - Steven
1
@Schwartzie 的 include? 方法无法正常工作,如果你使用的是 [1,2,3,4,5,6].include?([1,4]) 这样的语法,而这正是所需的。 - Ross
@Ross,你说的Array.include?只接受一个标量参数是正确的,但我的建议是使用逻辑运算符AND获取两个Array.include?调用的并集。 - Schwartzie

2
我的结论是,减法方法通常很好,但实际的Set对象非常快,因为它们显然针对这种类型的计算进行了优化。
使用此脚本:https://gist.github.com/1996001 我得到了以下基准测试结果(在Ruby 1.9.2p290上):
SUBTRACTION
- subset
  0.180000   0.000000   0.180000 (  0.189767)
- partial subset
  0.170000   0.000000   0.170000 (  0.178700)
- non subset
  0.180000   0.000000   0.180000 (  0.177606)

INTERSECTION
- subset
  0.190000   0.000000   0.190000 (  0.194149)
- partial subset
  0.190000   0.000000   0.190000 (  0.191253)
- non subset
  0.190000   0.000000   0.190000 (  0.195798)

SET
- subset
  0.050000   0.000000   0.050000 (  0.048634)
- partial subset
  0.040000   0.000000   0.040000 (  0.045927)
- non subset
  0.050000   0.010000   0.060000 (  0.052925)

我认为这相当惊人,特别是如果你查看源代码:

# File 'lib/set.rb', line 204

def subset?(set)
  set.is_a?(Set) or raise ArgumentError, "value must be a set"
  return false if set.size < size
  all? { |o| set.include?(o) }
end

来源: http://rubydoc.info/stdlib/set/1.9.2/Set#subset%3F-instance_method

此方法用于检查给定的集合是否为另一个集合的子集。如果所提供的集合是另一个集合的子集,则返回true,否则返回false。

是的,但是从数组转换为集合的速度不是很快,通常你需要考虑到这一点。 - mrbrdo
也许吧。虽然我不会在每次迭代中将数组转换为集合,但我确实会在每个集合测试中将其转换一次,因此它完全被该级别的集合操作所掩盖。如果您要执行如此大量的集合操作,则应该执行相同的操作,即先转换为Set(或从Set开始),然后执行重复操作,而不是不断在Set和Array之间进行转换。 - Anthony Michael Cook

1

@kurumi 是对的,但我想补充一下,当我只需要数组的子集时(通常是哈希键),我有时会使用这个小扩展:

class Hash
  # Usage { :a => 1, :b => 2, :c => 3}.except(:a) -> { :b => 2, :c => 3}
  def except(*keys)
    self.reject { |k,v|
      keys.include? k
    }
  end

  # Usage { :a => 1, :b => 2, :c => 3}.only(:a) -> {:a => 1}
  def only(*keys)
    self.dup.reject { |k,v|
      !keys.include? k
    }
  end
end

class Array
  def except(*values)
    self.reject { |v|
      values.include? v
    }
  end

  def only(*values)
    self.reject { |v|
      !values.include? v
    }
  end
end

这将非常有用。真的很感谢你把所有东西都打出来!非常感谢你,scragz。 - sjsc
在标准的 Ruby 中,Array.except 的作用是 -
Array.only 的作用是 &
例如:[1,2,3] - [1,3] => [3] 和 [1,2,3] & [1,2] => [1,2]
- Evan Larkin

1

简单而最佳的方法:

([4,1] - [1,2,3,4,5,6]).empty? # => true

([4,1] - [4,1,6,2]).empty? # => true

([4,1] - [3,4,7]).empty? # => false


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