【问题标题】:How do I find the unique number in an array and return only that number in ruby?如何在数组中找到唯一数字并在 ruby​​ 中仅返回该数字?
【发布时间】:2021-04-24 02:03:11
【问题描述】:

有一个包含一些数字的数组。除了一个,所有数字都相等。我正在尝试获得这种类型的东西:

find_uniq([ 1, 1, 1, 2, 1, 1 ]) == 2

find_uniq([ 0, 0, 0.55, 0, 0 ]) == 0.55

我试过了:

def find_uniq(arr)
   arr.uniq.each{|e| arr.count(e)}
end

它为我提供了数组中的两个不同值,但我不确定如何选择唯一的值。我可以使用某种计数吗?谢谢!

这行得通:

def find_uniq(arr)
  return nil if arr.size < 3
  if arr[0] != arr[1]
    return arr[1] == arr[2] ? arr[0] : arr[1]
  end
  arr.each_cons(2) { |x, y| return y if y != x }
end

感谢 pjs 和 Cary Swoveland。

【问题讨论】:

  • 你可以使用某种计数,但肯定有更快的算法,只需要遍历数组一次。

标签: arrays ruby methods unique


【解决方案1】:

我会这样做:

[ 1, 1, 1, 2, 1, 1 ]
  .tally                    # { 1=>5, 2=>1 }
  .find { |_, v| v == 1 }   # [2, 1]
  .first                    # 2

或者按照 3limin4t0r 的建议:

[ 1, 1, 1, 2, 1, 1 ]
  .tally                    # { 1=>5, 2=>1 }
  .invert[1]                # { 5=>1, 1=>2 } => 2

【讨论】:

  • 或者.tally.invert[1]
  • 啊,我一直在寻找那个方法,但不记得它的名字了。谢谢,@3limin4t0r
  • 好一个! (Da winna!)你说你会做类似你展示的东西,但没有告诉我们你实际上会做什么。 (我正在开展一项运动,以消除该论坛的类似之类的东西。)
  • 哈哈,知道了@CarySwoveland。以后我会尽量避免写类似的东西。这会很困难,因为在建议解决方案的背景下,这是一个常见的德语习语。
【解决方案2】:

以下内容不使用计数,并且会在找到唯一项目时使搜索短路。首先,如果数组的元素少于 3 个,则返回 nil,因为在这种情况下无法回答问题。如果该检查通过,它将通过比较相邻值来工作。它会预先检查前两个元素是否相等——如果不相等,它会检查第三个元素以查看哪个不同。否则,它遍历数组并返回它找到的第一个不相等的值。如果数组中没有可区分元素,则返回nil

def find_uniq(arr)
  return nil if arr.size < 3
  if arr[0] == arr[1]
    arr.each.with_index do |x, i|
      i += 1
      return arr[i] if arr[i] != x
    end
  elsif arr[1] == arr[2]
    arr[0]
  else
    arr[1]
  end
end

这也适用于非数字数组,例如find_uniq(%w(c c c d c c c c))


感谢 Cary Swoveland 提醒我有关 each_cons 的信息。这可以大大加强解决方案:

def find_uniq(arr)
  return nil if arr.size < 3
  if arr[0] != arr[1]
    return arr[1] == arr[2] ? arr[0] : arr[1]
  end
  arr.each_cons(2) { |x, y| return y if y != x }
end

【讨论】:

  • 不错的一个!检查前两个元素后,您可以编写arr.each_cons(2).find { |x,y| y != x }.last
【解决方案3】:

您的代码可以通过使用find 而不是each 来修复:

def find_uniq(arr)
  arr.uniq.find { |e| arr.count(e) == 1 }
end

然而这是非常低效的,因为uniq 需要迭代整个集合。找到唯一值后,count 将根据 uniq 结果中值的位置对 arr 集合再迭代 1 或 2 次(假设只有两个唯一值)。

对于简单的解决方案,我建议查看answer of spickermann,它只迭代整个集合一次。

对于您的特定场景,您可以通过短路计数从技术上提高性能。如果计数包含 2 个不同的值并且至少计数了 3 个项目,则这是通过手动计数和中断循环来完成的。

def find_uniq(arr)
  tally = Hash.new(0)
  arr.each_with_index do |item, index|
    break if tally.size == 2 && index >= 3
    tally[item] += 1
  end
  tally.invert[1]
end

【讨论】:

    【解决方案4】:

    对于除微型数组之外的所有数组,此方法的速度都有效地达到Enumerable#find

    def find_uniq(arr)
      multi = arr[0,3].partition { |e| e == arr.first }
                      .sort_by { |e| -e.size }.first.first
      arr.find { |e| e != multi }
    end
    
    find_uniq [1, 1, 1, 2, 1, 1]       #=> 2
    find_uniq [0, 0, 0.55, 0, 0]       #=> 0.55
    find_uniq [:pig, :pig, :cow, :pig] #=> :cow
    

    问题的措辞暗示该数组至少包含三个元素。它当然不能为空或有两个元素。 (如果它可以包含一个元素,则添加保护子句return arr.first if arr.size == 1。)

    我检查前三个元素以确定具有重复项的对象,我将其分配给变量multi。然后我可以使用findfind 相当快,部分原因是它短路(当它达到匹配时停止枚举数组)。

    如果

    arr = [1, 1, 1, 2, 1, 1]

    然后

    a = arr[0,3].partition { |e| e == arr.first }.sort_by { |e| -e.size }
      #=> [[1, 1, 1], []]
    multi = a.first.first
      #=> 1
    

    如果有以下情况:

    arr = [2, 1, 1, 1, 1, 1]
    arr = [1, 2, 1, 1, 1, 1]
    arr = [1, 1, 2, 1, 1, 1]
    

    然后申请

    a = arr[0,3].partition { |e| e == arr.first }.sort_by { |e| -e.size }
      #=> [[1, 1], [2]]
    multi = a.first.first
      #=> 1
    

    让我们比较一下已经提供的解决方案的计算性能。

    def spickermann1(arr)
      arr.tally.find { |_, v| v == 1 }.first
    end
    
    def spickermann2(arr)
      arr.tally.invert[1]
    end
    
    def spickermann3(arr)
      arr.tally.min_by(&:last).first
    end
    
    def pjs(arr)
      if arr[0] == arr[1]
        arr.each.with_index do |x, i|
          i += 1
          return arr[i] if arr[i] != x
        end
      elsif arr[1] == arr[2]
        arr[0]
      else
        arr[1]
      end
    end
    

    我没有包含@3limin4t0r 的解决方案,因为作者承认它的效率相对较低。但是,我确实包括了@spikermann 答案的两种变体,一种是@3limin4t0r 在评论中提出的(“spickermann2”)。

    require 'benchmark'
    
    def test(n)
      puts "\nArray size = #{n}"
      arr = Array.new(n-1,0) << 1
      Benchmark.bm do |x|
        x.report("Cary")         { find_uniq(arr) }
        x.report("spickermann1") { spickermann1(arr) }
        x.report("spickermann2") { spickermann2(arr) }
        x.report("spickermann3") { spickermann3(arr) }
        x.report("PJS")          { pjs(arr) }
      end
    end
    
    test 100
    Array size = 100
                     user     system      total        real
    Cary          0.000032   0.000009   0.000041 (  0.000029)
    spickermann1  0.000022   0.000015   0.000037 (  0.000019)
    spickermann2  0.000017   0.000002   0.000019 (  0.000016)
    spickermann3  0.000019   0.000002   0.000021 (  0.000018)
    PJS           0.000042   0.000025   0.000067 (  0.000034)
    
    test 10_000
    Array size = 10_000
                     user     system      total        real
    Cary          0.001101   0.000091   0.001192 (  0.001119)
    spickermann1  0.000699   0.000096   0.000795 (  0.000716)
    spickermann2  0.000794   0.000071   0.000865 (  0.000896)
    spickermann3  0.000776   0.000081   0.000857 (  0.000781)
    PJS           0.001140   0.000113   0.001253 (  0.001300)
    
    test 1_000_000
    Array size = 1_000_000
                     user     system      total        real
    Cary          0.061148   0.000787   0.061935 (  0.063022)
    spickermann1  0.043598   0.000474   0.044072 (  0.044590)
    spickermann2  0.044909   0.000663   0.045572 (  0.046371)
    spickermann3  0.042907   0.000210   0.043117 (  0.043162)
    PJS           0.072766   0.000226   0.072992 (  0.073168)
    

    我将@spickermann 的答案的明显优势归因于Enumerable#tally 没有要评估的块(例如,与我的答案中的Enumerable#find 不同)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-05
      • 2023-01-28
      • 2012-01-08
      • 1970-01-01
      相关资源
      最近更新 更多