ruby - 当值发生变化时将数组拆分为子数组并且 ignore/delete 该值

ruby - split an array into sub arrays when a value changes and ignore/delete that value

我想将以下数组拆分为子数组,以便子数组在 1 开始和结束时开始和结束...

a=[1,1,0,0,1,0,1,1,1]

所以我最终将其作为一个新数组...

=>  [[1,1],[1],[1,1,1]]

有人有什么想法吗...?

您可以通过多种方式完成此操作。一种方法是将数组转换为字符串,拆分组并将其重新映射为数组(忽略任何空组):

a=[1,1,0,0,1,0,1,1,1]
a.join.split(/0/).map {|group| group.split(//).map(&:to_i) unless group == ''}.compact
#=> [[1,1],[1],[1,1,1]]

我喜欢许多不同的答案!所以我花了一些时间来测试其中的一些。

以下是我的处理方式:

new_array = a.each_with_object([ [] ]) {|i, n| i == 1 ? ( n.last << i) : (n.last.empty? ? true : (n << []))}

#each_with_object 方法允许我迭代数组,同时使用一个对象来存储我沿途收集的任何数据(该对象被分配给变量 n,它代表 'new_array').

在这种方法中,我将数据收集到嵌套数组 [ [] ] 的数组中,在识别到最后一个嵌套数组 n.last << i 后添加 1,并添加一个新的空嵌套数组 n << [] 如果数据不是我想要收集的(并且现有的嵌套数组不为空)。

我使用两个内联 if:else 语句,使用简写:

condition ? do_if_true : do_if_false

对一些答案进行基准测试

在我的 MacBook Pro 上测试了一些答案,我的方法似乎是迄今为止最快的...但也许我有偏见。

关于报告的注意事项:结果以秒为单位。越少越快。

两个最好的结果在粗体

包含 10 个项目、100,000 次迭代的数组的报告:

                    user     system      total        real

@tykowale 的方法 0.210000 0.000000 0.210000 (0.209799)

@infused 的方法 1.300000 0.010000 1.310000 ( 1.304084)

@CarySwoveland 的方法 0.830000 0.000000 0.830000 ( 0.839012)

@Myst 的方法 0.170000 0.000000 0.170000 (0.169915)

@Sid 的方法 0.590000 0.000000 0.590000 ( 0.595671)

包含 100 个项目、10,000 次迭代的数组的报告:

                    user     system      total        real

@tykowale 的方法 0.160000 0.000000 0.160000 (0.155997)

@infused 的方法 1.030000 0.000000 1.030000 ( 1.030392)

@CarySwoveland 的方法 0.420000 0.010000 0.430000 ( 0.424801)

@Myst 的方法 0.150000 0.000000 0.150000 (0.143403)

@Sid 的方法 0.260000 0.000000 0.260000 ( 0.255548)

包含 1,000 个项目、1,000 次迭代的数组的报告:

                    user     system      total        real

@tykowale 的方法 0.150000 0.000000 0.150000 (0.160459)

@infused 的方法 1.030000 0.000000 1.030000 ( 1.033616)

@CarySwoveland 的方法 0.310000 0.000000 0.310000 ( 0.312325)

@Myst 的方法 0.130000 0.000000 0.130000 (0.133339)

@Sid 的方法 0.210000 0.000000 0.210000 ( 0.217960)

包含 10,000 个项目、100 次迭代的数组的报告:

                    user     system      total        real

@tykowale 的方法 0.250000 0.000000 0.250000 ( 0.252399)

@infused 的方法 1.020000 0.000000 1.020000 ( 1.017766)

@CarySwoveland 的方法 0.320000 0.000000 0.320000 ( 0.321452)

@Myst 的方法 0.130000 0.000000 0.130000 (0.128247)

@Sid 的方法 0.210000 0.000000 0.210000 (0.212489)

基准代码

以下是用于基准测试的脚本:

module Enumerable
  def split_by
    result = [a=[]]
    each{ |o| yield(o) ? (result << a=[]) : (a << o) }
    result.pop if a.empty?
    result.delete_if { |x| x.empty? }
    result
  end
end

require 'benchmark'

[10, 100, 1000, 10000].each do |items|

  a = (Array.new(items) { rand 2 })
  cycles = 1_000_000 / items

  puts "report for array with #{items} items, #{cycles} iterations:"

  Benchmark.bm do |bm|
    bm.report("@tykowale's approach") {cycles.times { a.split_by {|x| x == 0} } }
    bm.report("@infused's approach") {cycles.times { a.join.split(/0/).map {|group| group.split(//).map(&:to_i) unless group == ''}.compact } }
    bm.report("@CarySwoveland's approach") { cycles.times { a.chunk(&:itself).select { |a| a.first==1 }.map(&:last) } }
    bm.report("@Myst's approach") { cycles.times { a.each_with_object([[]]) {|i, n| i == 1 ? ( n.last << i) : (n.last.empty? ? true : (n << [])) } } }
    bm.report("@Sid's approach") { cycles.times { a.chunk {|x| x==1 || nil}.map{|y,ys| ys} } }
  end
end

这是使用 Enumerable#chunk 的方法:

a.chunk { |n| n==1 }.select(&:first).map(&:last)
  #=> [[1, 1], [1], [1, 1, 1]] 

还有一个,使用Enumerable#slice_when,在v2.2中引入:

a.slice_when { |bef,aft| bef!=aft }.reject { |e| e.first != 1 }
  #=> [[1, 1], [1], [1, 1, 1]]

你可以猴子把它修补成可枚举的,然后把它传递给一个块,这样它就可以用于你想要的任何数字或表达式

module Enumerable
  def split_by
    result = [a=[]]
    each{ |o| yield(o) ? (result << a=[]) : (a << o) }
    result.delete_if { |a| a.empty? }
  end
end

a=[1,1,0,0,1,0,1,1,1]

p a.split_by {|x| x == 0}
#=> [[1,1],[1],[1,1,1]]

Split array into sub-arrays based on value

中找到(大部分)

编辑:更改了删除空集的工作方式 result.pop if a.empty? 并从末尾删除了不必要的结果行

最简单和最易读的方式可能是:

a.chunk {|x| x==1 || nil}.map(&:last)
  #=> [[1, 1], [1], [1, 1, 1]]

如果您可以在 Rails 上使用 Ruby,您可以使用更简单的解决方案:

a.split(0).reject(&:empty?)
  #=> [[1, 1], [1], [1, 1, 1]]
a.join.split('0').select {|b| b if not b.empty?}.map {|c| c.split(//).map{|d| d.to_i}}