Разделите массив Ruby в соответствии с полосами внутри него - PullRequest
8 голосов
/ 24 февраля 2009

Резюме: Основной вопрос здесь, как я обнаружил, заключается в том, можно ли передать блок кода в массив Ruby, который фактически сократит содержимое этого массива до другого, а не одиночное значение (как делает inject ). Короткий ответ - нет".

Я принимаю ответ, который говорит это. Спасибо Squeegy за отличную циклическую стратегию, позволяющую выделять полосы из массива.

Задача: Уменьшить элементы массива, не проходя через него явно.
Входные данные: Все целые числа от -10 до 10 (кроме 0) упорядочены в случайном порядке.
Желаемый результат: Массив, представляющий полосы положительных или отрицательных чисел. Например, -3 представляет три последовательных отрицательных числа. 2 представляет два последовательных положительных числа.

Пример сценария:

original_array = (-10..10).to_a.sort{rand(3)-1}
original_array.reject!{|i| i == 0} # remove zero

streaks = (-1..1).to_a # this is a placeholder.  
# The streaks array will contain the output.
# Your code goes here, hopefully without looping through the array

puts "Original Array:"
puts original_array.join(",")
puts "Streaks:"
puts streaks.join(",")
puts "Streaks Sum:"
puts streaks.inject{|sum,n| sum + n}

Пример выходных данных:

Original Array:
3,-4,-6,1,-10,-5,7,-8,9,-3,-7,8,10,4,2,5,-2,6,-1,-9
Streaks:
1,-2,1,-2,1,-1,1,-2,5,-1,1,-2
Streaks Sum:
0


Original Array:
-10,-9,-8,-7,-6,-5,-4,-3,-2,-1,1,2,3,4,5,6,7,8,9,10
Streaks:
-10,10
Streaks Sum:
0

Обратите внимание на несколько вещей:

  • Массив штрихов имеет чередующиеся положительные и отрицательные значения.
  • Сумма массива elements streaks всегда равна 0 (как и сумма оригинала).
  • Сумма абсолютных значений массива штрихов всегда равна 20.

Надеюсь, это понятно!

Редактировать: Я понимаю, что такие конструкции, как отклонить! на самом деле цикл по массиву в фоновом режиме. Я не исключаю зацикливание, потому что я подлый человек. Просто хочу узнать о языке. Если явная итерация необходима, это нормально.

Ответы [ 5 ]

11 голосов
/ 24 февраля 2009

Хорошо, вот версия в одну строку, если вам нравится больше:

streaks = original_array.inject([]) {|a,x| (a.empty? || x * a[-1] < 0 ? a << 0 : a)[-1] += x <=> 0; a}

И если даже инъекция слишком тупая для вас, вот действительно глупый способ:

  streaks = eval "[#{original_array.join(",").gsub(/((\-\d+,?)+|(\d+,?)+)/) {($1[0..0] == "-" ? "-" : "") + $1.split(/,/).size.to_s + ","}}]"

Но я думаю, что совершенно очевидно, что вам лучше с чем-то гораздо более простым:

streaks = []
original_array.each do |x|
  xsign = (x <=> 0)
  if streaks.empty? || x * streaks[-1] < 0
    streaks << xsign
  else
    streaks[-1] += xsign
  end
end

В дополнение к тому, что «циклическая» версия намного проще для понимания и поддержки, она работает примерно на две трети времени от версии с инъекцией и примерно в шестой части от версии eval / regexp.

PS: Вот еще одна потенциально интересная версия:

a = [[]]
original_array.each do |x|
  a << [] if x * (a[-1][-1] || 0) < 0
  a[-1] << x
end
streaks = a.map {|aa| (aa.first <=> 0) * aa.size}

При этом используется два прохода: сначала строится массив массивов штрихов, а затем преобразуется массив массивов в массив размеров со знаком. В Ruby 1.8.5 это на самом деле немного быстрее, чем в версии для инъекций выше (хотя в Ruby 1.9 это немного медленнее), но скучный цикл все еще самый быстрый.

6 голосов
/ 24 февраля 2009
new_array = original_array.dup
<Squeegy's answer, using new_array>

Та да! Нет зацикливания исходного массива. Хотя внутри dup это MEMCPY, которую, я полагаю, можно считать циклом на уровне ассемблера?

http://www.ruby -doc.org / Doxygen / 1.8.4 / array_8c-source.html

РЕДАКТИРОВАТЬ: ;)

4 голосов
/ 24 февраля 2009
original_array.each do |num|
  if streaks.size == 0
    streaks << num
  else
    if !((streaks[-1] > 0) ^ (num > 0))
      streaks[-1] += 1
    else
      streaks << (num > 0 ? 1 : -1)
    end
  end
end

Волшебство здесь - оператор ^ xor.

true ^ false  #=> true
true ^ true   #=> false
false ^ false #=> false

Таким образом, если последнее число в массиве находится на той же стороне нуля, что и обрабатываемое число, добавьте его в строку, в противном случае добавьте его в массив строк, чтобы начать новую строку. Обратите внимание, что синус true ^ true возвращает false, мы должны отрицать все выражение.

1 голос
/ 17 февраля 2016

Начиная с Ruby 1.9, существует гораздо более простой способ решения этой проблемы:

original_array.chunk{|x| x <=> 0 }.map{|a,b| a * b.size }

Enumerable.chunk сгруппирует все последовательные элементы массива вместе с помощью вывода блока:

>> original_array.chunk{|x| x <=> 0 }
=> [[1, [3]], [-1, [-4, -6]], [1, [1]], [-1, [-10, -5]], [1, [7]], [-1, [-8]], [1, [9]], [-1, [-3, -7]], [1, [8, 10, 4, 2, 5]], [-1, [-2]], [1, [6]], [-1, [-1, -9]]]

Это почти то же самое, что запрашивает OP, за исключением того, что полученные группы должны быть подсчитаны для получения массива окончательных полос.

1 голос
/ 26 марта 2011

Более жестокое обращение, а-ля Гленн Макдональд, только другое:

runs = original_array.map do |e|
  if e < 0
    '-'
  else
    '+'
  end
end.join.scan(/-+|\++/).map do |t|
  "#{t[0..0]}#{t.length}".to_i
end

p original_array
p runs
# => [2, 6, -4, 9, -8, -3, 1, 10, 5, -7, -1, 8, 7, -2, 4, 3, -5, -9, -10, -6]
# => [2, -1, 1, -2, 3, -2, 2, -1, 2, -4]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...