在Ruby中比较数组中的两个元素

7

我在使用方法比较数组中两个元素时遇到了麻烦。我发现逻辑非常简单,只需要使用嵌套循环即可,但这可能不是 Ruby 的最佳用法。

例如,确定数组是否有任何一对数字等于 0:

def pairs(array)
  i = 0
  while i < array.length
    y = i + 1
    while y < array.length
      if array[i] + array[y] == 0
        return true
      end
      y += 1
    end
    i += 1
  end
  return false
end

如果我想要检查数组中的两个元素是否相同,我会使用相同的逻辑,只不过设置为:if array[i] == to array[y]...。
有人能提供更好的解决方法吗?

一对数字不能等于零。它们可以都是零,它们的和、差、积或商可以为零,其中一个模另一个可以等于零,...在阅读了您的代码之后(我不应该必须这样做才能理解问题),我看到您的意思是这两个数字的总和为零。 - Cary Swoveland
4个回答

14

通常情况下,你可以将英语规范直接翻译成Ruby。

在你的第一个问题中,你询问是否有任何两个元素的组合加起来等于零。当你想知道可枚举集合中的任何元素是否符合某一条件时,可以使用Enumerable#any?方法。如果你想要处理数组中的元素组合,可以使用Array#combination方法。对于求和,你可以使用Enumerable#sum,如果你想知道一个数字是否为零,你可以使用Numeric#zero?

因此,你的第一个问题可能的实现如下:

ary.combination(2).any? { _1.sum.zero? }

你的第二个问题可以通过这个来回答:

ary.combination(2).any? {|a, b| a == b }

在这两种情况下,当然还有其他方法可以实现。例如,在第一种情况下,我们可以注意到两个数字求和为零的唯一方式是其中一个数是另一个数的相反数。

请注意,在循环中通常会出现的任何问题都不会发生在这里。没有偏移量错误,没有栅栏错误,没有错误的终止条件,没有超出数组末尾的迭代,因为这里没有循环。


2
感谢Array#combination - Aetherus

2

你会喜欢Ruby,部分原因是你通常不需要使用索引来提取或设置数组的元素。

以下是一种获得所需结果的方法。最后我将介绍一种替代计算方法,当数组很大时可以考虑使用。

代码

def pairs(arr)
  arr.map { |e| e < 0 ? -e : e }.
      group_by(&:itself).
      select { |_,v| v.size > 1 }.
      keys
end 

例子

pairs [1, 8, -2, 12, -15, 5, 3]           #=> []
pairs [1, 8, -2, 12, -15, 5, 3, 2, -3, 1] #=> [1, 2, 3] 

如果你想让第二个例子返回 [[1, -1], [2, -2], [3, -3]] (虽然我不知道有什么意义),将该方法的倒数第二行替换为:

map { |k,_| [k, -k] }

解释

以下是相关IT技术的步骤:

arr = [1, 8, -2, 12, -15, 5, 3, 2, -3, 1]

are:

a = arr.map { |e| e < 0 ? -e : e }
  #=> [1, 8, 2, 12, 15, 5, 3, 2, 3, 1] 
b = a.group_by(&:itself)
  #=> {1=>[1, 1], 8=>[8], 2=>[2, 2], 12=>[12], 15=>[15], 5=>[5], 3=>[3, 3]} 

在Ruby v2.2中,我们得到了Object#itself。对于早期版本,请使用:

b = a.group_by { |e| e }

继续:
c = b.select { |_,v| v.size > 1 }
  #=> {1=>[1, 1], 2=>[2, 2], 3=>[3, 3]} 
c.keys
  #=> [1, 2, 3] 

这行代码:

select { |_,v| v.size > 1 }

可以编写为:

select { |k,v| v.size > 1 }

其中kv代表“键”和“值”,但由于在块计算中未使用k,因此通常将k替换为本地变量_(是的,它是一个变量 - 在IRB中尝试一下),主要是为了告诉读者该参数未在块中使用。

如果arr = [1, 1, -1, -1, 2, -2],这将返回[1,2]。如果您希望它返回[1,1,2],则必须采用不同的方法。

大数组的替代计算方法

如果数组(大小为n)很大,则可以通过首先将数组转换为集合来将计算复杂度从O(n 2 )减少到几乎O(n)(“几乎”将在下面解释):

require 'set'

arr = [3, 5, -7, 4, 2, 7, -6, 1, 0]
st = arr.to_set
  #=> #<Set: {3, 5, -7, 4, 2, 7, -6, 1, 0}>

我们可以计算如下:
arr.find { |n| st.include?(-n) }
  #=> -7

从数组构建集合的时间复杂度为O(n)。集合查找(st.include?(-n))等同于哈希查找(即计算给定键kh[k]),几乎是常数时间,即O(1)。


1

如果你只想计算数组中元素的出现次数,你可以使用Enumerable#count

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

现在,如果您想查看数组中是否有重复项,您可以使用uniq方法。
def has_duplicates?(arr)
  arr.uniq.length == arr.length
end

不相关于问题。 - Aetherus
@Aetherus 我不明白为什么这不相关。 第一部分通过一个例子解释了如何判断数组中是否有多个元素,a.count(0) 可以返回所需的结果。(这是第一个问题) 第二部分确定两个数组元素是否相等,这与检查数组中是否有重复项是相同的。(这是第二个问题) - xlembouras

1

这是我的方法来回答你的第一个问题,并提供另一种返回配对结果的方法:

def pairs?(arr)
    arr.inject(false){|c, v| c || (arr.include?(-v) && v > 0)}
end

def get_pairs(arr)
    arr.collect{|val| [val, -val] if arr.include?(-val) && val > 0}.reject(&:nil?)
end

arr = [1, 8, -2, 12, -15, 5, 3]
p pairs?(arr)
p get_pairs(arr)

arr = [1, 8, -2, 12, -15, 5, 3, 2, -3, 1]
p pairs?(arr)
p get_pairs(arr)

输出:

false
[]
true
[[3, -3], [2, -2]]

但是当有一对零时,它们就无法工作。你可以明确处理这种情况,或者也许有人能够提供更好的解决方案。


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