Как разбить массив на условие на смежные элементы на ограниченное количество разделов
Скажем, у меня есть массив чисел, например.
ary = [1, 3, 6, 7, 10, 9, 11, 13, 7, 24]
Я хотел бы разделить массив между первой точкой, где меньшее число следует за большим. Мой вывод должен быть:
[[1, 3, 6, 7, 10], [9, 11, 13, 7, 24]]
Я пробовал slice_when
, и он довольно близок:
ary.slice_when { |i, j| i > j }.to_a
#=> [[1, 3, 6, 7, 10], [9, 11, 13], [7, 24]]
Но он также разбивается между 13
и 7
, поэтому мне нужно присоединиться к оставшимся массивам:
first, *rest = ary.slice_when { |i, j| i > j }.to_a
[first, rest.flatten(1)]
#=> [[1, 3, 6, 7, 10], [9, 11, 13, 7, 24]]
который выглядит немного громоздким. Также кажется неэффективным продолжать сравнивать элементы, когда совпадение уже найдено.
Я ищу общее решение, основанное на произвольном условии. Наличие числовых элементов и i > j
является просто примером.
Есть ли лучший способ приблизиться к этому?
Ответы
Ответ 1
Еще одна альтернатива:
index = ary.each_cons(2).find_index { |i, j| i > j }
[ary[0..index], ary[index + 1..-1]]
#=> [[1, 3, 6, 7, 10], [9, 11, 13, 7, 24]]
Я считаю, что пространство O (n) и время O (n)
Benchmark:
Warming up --------------------------------------
anthony 63.941k i/100ms
steve_t 98.000 i/100ms
tadman 123.000 i/100ms
sergio 75.477k i/100ms
hoffm 101.000 i/100ms
Calculating -------------------------------------
anthony 798.456k (± 4.0%) i/s - 4.028M in 5.053175s
steve_t 985.736 (± 5.0%) i/s - 4.998k in 5.083188s
tadman 1.229k (± 4.1%) i/s - 6.150k in 5.010877s
sergio 948.357k (± 3.7%) i/s - 4.755M in 5.020931s
hoffm 1.013k (± 2.9%) i/s - 5.151k in 5.089890s
Comparison:
sergio: 948357.4 i/s
anthony: 798456.2 i/s - 1.19x slower
tadman: 1229.5 i/s - 771.35x slower
hoffm: 1012.9 i/s - 936.30x slower
steve_t: 985.7 i/s - 962.08x slower
для эталона:
require 'benchmark/ips'
def anthony(ary)
index = ary.each_cons(2).find_index { |i, j| i > j }
[ary[0..index], ary[index + 1..-1]]
end
def steve_t(ary)
break_done = false
ary.slice_when { |i, j| (break_done = i > j) unless break_done }.to_a
end
def tadman(ary)
ary.each_with_object([[],[]]) do |v, a|
a[a[1][-1] ? 1 : (a[0][-1]&.>(v) ? 1 : 0)] << v
end
end
def sergio(ary)
break_point = ary.each_cons(2).with_index do |(a, b), idx|
break idx if b < a # plug your block here
end + 1
[
ary.take(break_point),
ary.drop(break_point)
]
end
def split(ary)
split_done = false
ary.chunk_while do |i, j|
split_done || !(split_done = yield(i, j))
end.to_a
end
def hoffm(ary)
split(ary) { |i, j| i > j }
end
ary = Array.new(10_000) { rand(1..100) }
Benchmark.ips do |x|
# Configure the number of seconds used during
# the warmup phase (default 2) and calculation phase (default 5)
x.config(:time => 5, :warmup => 2)
# Typical mode, runs the block as many times as it can
x.report("anthony") { anthony(ary) }
x.report("steve_t") { steve_t(ary) }
x.report("tadman") { tadman(ary) }
x.report("sergio") { sergio(ary) }
x.report("hoffm") { hoffm(ary) }
# Compare the iterations per second of the various reports!
x.compare!
end
Увлекательно, что #take
и #drop
из ответа @sergio немного быстрее, чем Array#[range..range]
, они оба используют один и тот же метод c, поэтому я не могу его объяснить.
Ответ 2
Вероятно, лучший способ сделать это, но моя первая мысль...
break_done = false
ary.slice_when { |i, j| (break_done = i > j) unless break_done }.to_a
#=> [[1, 3, 6, 7, 10], [9, 11, 13, 7, 24]]
Ответ 3
Я не уверен, что вы найдете это более изящным, но он предотвращает маневр раскола и воссоединения:
def split(ary)
split_done = false
ary.slice_when do |i, j|
!split_done && (split_done = yield(i, j))
end.to_a
end
Использование:
ary = [1, 3, 6, 7, 10, 9, 11, 13, 7, 24]
split(ary){ |i, j| i > j }
#=> [[1, 3, 6, 7, 10], [9, 11, 13, 7, 24]]
Обновление
Некоторые могут найти этот вариант более читабельным. #chunk_while
является обратным к #split_when
, а затем я просто применил закон Де Моргана к блоку.
def split(ary)
split_done = false
ary.chunk_while do |i, j|
split_done || !(split_done = yield(i, j))
end.to_a
end
Ответ 4
Здесь другая версия. Не особенно элегантный или эффективный, но достаточно эффективен (см. Комментарии).
break_point = ary.each_cons(2).with_index do |(a, b), idx|
break idx if b < a # plug your block here
end + 1
[
ary.take(break_point),
ary.drop(break_point)
] # => [[1, 3, 6, 7, 10], [9, 11, 13, 7, 24]]
Ответ 5
Это не должно быть такой трудной проблемой, как это доказывает. slice_when
не принимает максимальное количество срезов в качестве аргумента, но если это было бы легко,
Здесь один оптимизирован вокруг двух разделов:
def slice_into_two(ary)
ary.each_with_object([[],[]]) do |v, a|
a[a[1][-1] ? 1 : (a[0][-1]&.>(v) ? 1 : 0)] << v
end
end
Ответ 6
Первый подход
Просто подумал, что я опубликую этот путь, перечислитель в перечислителе, создающий раздел. Первая ветвь if (как и другие, такие как тадман) реализована в случае пустого массива.
arr = [1, 3, 6, 7, 10, 9, 11, 13, 7, 24]
Enumerator.new { |y|
if arr.empty?
y << []
else
enum = arr.each
a = enum.next
#collect elements until rule is broken
arr1 = loop.with_object([a]) { |_,o|
break o if enum.peek < a
o << a = enum.next
}
#collect remainder of elements
arr2 = loop.with_object([]) { |_,o| o << enum.next }
#incase the rule is never met; just return arr elements
arr2 == [] ? arr.each { |e| y << e } : y << arr1; y << arr2
}.entries
#=> [[1, 3, 6, 7, 10], [9, 11, 13, 7, 24]]
Второй подход
Это несколько получается из подхода тадмана, то есть раздел предопределен и опустошен и заполнен соответствующим образом.
arr = [1, 3, 6, 7, 10, 9, 11, 13, 7, 24]
loop.with_object([[],arr.dup]) { |_,o|
if o.last == []
break o
elsif o.last[0] < o.last[1]
o.first << o.last.shift
else
o.first << o.last.shift
break o
end
}
#=> [[1, 3, 6, 7, 10], [9, 11, 13, 7, 24]]
Цитирование через массив (хотя и дубликат), возвращающий секционированный массив, как только правило прерывается.
Ответ 7
first, *last = ary
first = [first]
while last.any? && first.last <= last.first do
first << last.shift
end
[first, last]
#=> [[1, 3, 6, 7, 10], [9, 11, 13, 7, 24]]
По-другому:
f = ary.lazy.slice_when { |i, j| i > j }.first
#=> [1, 3, 6, 7, 10]
[f, ary[f.size..-1]]
#=> [[1, 3, 6, 7, 10], [9, 11, 13, 7, 24]]