【问题标题】:How to count consecutive numbers in an array?如何计算数组中的连续数字?
【发布时间】:2019-05-07 03:10:38
【问题描述】:

如果我有一个数组:

array = [1,2,2,2,2,5,5,1,1,1,3,3,3,3,2,2,2,2,2,2,2]

我希望能够识别长度大于 3 的连续匹配数字。并映射连续数字的起始索引。上述数组的示例输出为:

consecutive_numbers = [
  {starting_index: 1, value: 2, length: 4},
  {starting_index: 10, value: 3, length: 4},
  {starting_index: 14, value: 2, length: 7}
]

值可以相同,但连续序列'必须互斥。看到有2个值为2的hash,但是它们的起始索引不同。

到目前为止我的尝试......看起来像这样:

array.each_cons(3).with_index.select{|(a,b,c), i| 
  [a,b,c].uniq.length == 1
}

但这会返回:

[[[2, 2, 2], 1], [[2, 2, 2], 2], [[1, 1, 1], 7], [[3, 3, 3], 10], [[3, 3, 3], 11], [[2, 2, 2], 14], [[2, 2, 2], 15], [[2, 2, 2], 16], [[2, 2, 2], 17], [[2, 2, 2], 18]]

但这会返回重叠的结果。

【问题讨论】:

  • 你自己有没有尝试过?
  • @Loocid 是的,感谢您的提问,我已将其添加到问题中

标签: ruby-on-rails arrays ruby


【解决方案1】:
array.each_with_index.
      chunk(&:first).
      select { |_,a| a.size > 3 }.
      map { |n,a| { starting_index: a.first.last, value: n, length: a.size } }
  #=> [{:starting_index=> 1, :value=>2, :length=>4},
  #    {:starting_index=>10, :value=>3, :length=>4},
  #    {:starting_index=>14, :value=>2, :length=>7}] 

步骤如下。

e = array.each_with_index.chunk(&:first)
  #=> #<Enumerator: #<Enumerator::Generator:0x00005b1944253c18>:each> 

我们可以将此枚举器转换为数组,以查看它将生成的元素并传递给它的块。

e.to_a
  #=> [[1, [[1, 0]]],
  #    [2, [[2, 1], [2, 2], [2, 3], [2, 4]]],
  #    [5, [[5, 5], [5, 6]]],
  #    [1, [[1, 7], [1, 8], [1, 9]]],
  #    [3, [[3, 10], [3, 11], [3, 12], [3, 13]]],
  #    [2, [[2, 14], [2, 15], [2, 16], [2, 17], [2, 18], [2, 19], [2, 20]]]] 

继续,

c = e.select { |_,a| a.size > 3 }
  #=> [[2, [[2, 1], [2, 2], [2, 3], [2, 4]]],
  #    [3, [[3, 10], [3, 11], [3, 12], [3, 13]]],
  #    [2, [[2, 14], [2, 15], [2, 16], [2, 17], [2, 18], [2, 19], [2, 20]]]] 
c.map { |n,a| { starting_index: a.first.last, value: n, length: a.size } }
  #=> [{:starting_index=> 1, :value=>2, :length=>4},
  #    {:starting_index=>10, :value=>3, :length=>4},
  #    {:starting_index=>14, :value=>2, :length=>7}] 

这是另一种方式。

array.each_with_index.with_object([]) do |(n,i),arr|
  if arr.any? && arr.last[:value] == n
    arr.last[:length] += 1
  else
    arr << { starting_index: i, value: n, length: 1 }
  end
end.select { |h| h[:length] > 3 }
  #=> [{:starting_index=> 1, :value=>2, :length=>4},
  #    {:starting_index=>10, :value=>3, :length=>4},
  #    {:starting_index=>14, :value=>2, :length=>7}]     

【讨论】:

  • .each_with_object([]) 在这种情况下只是 map
  • @engineersmnky,谢谢你告诉我我的盲点。
【解决方案2】:

你可以chunk_while每对元素相等:

p array.chunk_while { |a, b| a == b }.to_a
# [[1], [2, 2, 2, 2], [5, 5], [1, 1, 1], [3, 3, 3, 3], [2, 2, 2, 2, 2, 2, 2]]

您选择具有 3 个或更多元素的数组。

之后,使用then,您可以yield self,因此您可以访问数组数组,您可以使用它来获取starting_index

[1,2,2,2,2,5,5,1,1,1,3,3,3,3,2,2,2,2,2,2,2].chunk_while(&:==).then do |this|
  this.each_with_object([]).with_index do |(e, memo), index|
    memo << { starting_index: this.to_a[0...index].flatten.size, value: e.first, length: e.size }
  end
end.select { |e| e[:length] > 3 }

# [{:starting_index=>1, :value=>2, :length=>4},
#  {:starting_index=>10, :value=>3, :length=>4},
#  {:starting_index=>14, :value=>2, :length=>7}]

对于starting_index,获取元素到当前索引(不包含),将它们展平,得到元素总数。

值,因为数组中的每个数组都有相同的元素,可以是任何值,长度,是“主”数组中当前数组的长度。

【讨论】:

  • 起始索引是错误的,因为它应该基于每个 OP 的原始数组中的索引。
  • 已修复,谢谢@engineersmnky,我还没有意识到这一点。
【解决方案3】:

这是另一种选择..


array
     .zip(0..)
     .slice_when { |a, b| a.first != b.first }
     .map { |a| { starting_index: a.first.last, value: a.first.first, length: a.size } }
     .reject { |h| h[:length] < 3 }

#=> [{:starting_index=>1, :value=>2, :length=>4}, {:starting_index=>7, :value=>1, :length=>3}, {:starting_index=>10, :value=>3, :length=>4}, {:starting_index=>14, :value=>2, :length=>7}]

【讨论】:

  • 由于我注意到您对 2.6 方法(特别是 then)的关注,这个 array.zip((0...array.size)) 可以只是 array.zip(0...) 感谢Endless Ranges
  • @engineersmnky :) 捡到了!
【解决方案4】:

嗯,最明显(可能也是最快)的方法是遍历数组并手动计算所有内容:

array = [1,2,2,2,2,5,5,1,1,1,3,3,3,3,2,2,2,2,2,2,2]
array_length_pred = array.length.pred

consecutive_numbers = []

starting_index = 0
value = array.first
length = 1

array.each_with_index do |v, i|
  if v != value || i == array_length_pred
    length += 1 if i == array_length_pred && value == v

    if length >= 3
      consecutive_numbers << {
        starting_index: starting_index,
        value: value,
        length: length
      }
    end

    starting_index = i
    value = v
    length = 1
    next
  end

  length += 1
end

p consecutive_numbers

# [{:starting_index=>1, :value=>2, :length=>4},
# {:starting_index=>7, :value=>1, :length=>3},
# {:starting_index=>10, :value=>3, :length=>4},
# {:starting_index=>14, :value=>2, :length=>7}]

【讨论】:

  • 正如我所说,这是最明显的方式。这与 风格 无关,而与解决问题和性能有关(与其他答案 atm 相比,速度快 2.5 倍 - 3.5 倍)。
  • 当你看到 C 语言中的人有多优秀时,他们的衡量标准是他们在程序中使用逻辑的能力有多好,当你看到 Ruby 中的人有多优秀时,就是看他们有多优秀在使用预定义的功能。 Ruby 就是这样设计的。
  • 这一切都取决于你的需求,如果你不关心性能,那么当然,用那漂亮的一行代码,否则你会怎么做? Ruby 不会强迫您使用预定义的方法。
【解决方案5】:

您可以改为使用字符串。

在这里,我将数组强制转换为字符串:

input_sequence = [1,2,2,2,2,5,5,1,1,1,3,3,3,3,2,2,2,2,2,2,2].join

我使用正则表达式对连续字符进行分组:

groups = input_sequence.gsub(/(.)\1*/).to_a
#=> ["1", "2222", "55", "111", "3333", "2222222"]

现在我可以在输入字符串中搜索组作为子字符串:

groups.map do |group|
  {
    starting_index: input_sequence.index(group), 
    value: group[0].to_i,
    length: group.length
  }
end.reject { |group| group[:length] <= 3 }

#=> [{:starting_index=>1, :value=>2, :length=>4},
     {:starting_index=>7, :value=>1, :length=>3},
     {:starting_index=>10, :value=>3, :length=>4},
     {:starting_index=>14, :value=>2, :length=>7}]

这里还有改进的空间——我正在为一个对象创建很多中间对象——但我想我会提供一种不同的方法。

【讨论】:

  • 如果有一个大于 9 的数字或浮点数,这种方法可能会出现问题,不是吗?
  • @ecki,你是一位轻描淡写的大师。顺便说一句,你忘了提到负数。
猜你喜欢
  • 2019-11-18
  • 1970-01-01
  • 1970-01-01
  • 2018-02-10
  • 1970-01-01
  • 2011-07-12
  • 1970-01-01
  • 1970-01-01
  • 2014-02-14
相关资源
最近更新 更多