Деструктивное преобразование массива в массив в Ruby

Скажем, у меня есть два значительных массива, a и b.

Я хочу иметь b=a+b, но мне на самом деле не нужен a впоследствии, и я бы предпочел сохранить память, чтобы избежать подкачки.

Я подумал о том, чтобы просто использовать:

b.unshift( *a.slice!(0..-1) )

Но у вышеизложенного есть обратная сторона - по-прежнему хранится копия a до завершения процедуры. Другой вариант:

while ! a.empty?() do 
       b.unshift( a.pop() )
end

Хотя это не изящно и, возможно, даже медленнее (это итерации, я не знаю, насколько быстро работает оператор *, возможно, он на более низком уровне), это сводит к минимуму промежуточный объем памяти.

Есть что-нибудь более элегантное?

А как насчет b = a.concat(b)?

Stefan 21.03.2018 13:17

Откуда вы знаете, что Ruby не будет выделять N копий a (и, следовательно, тратить эту память), пока вы добавляете элементы один за другим? Если вы делать заботитесь о памяти, вам необходимо профилировать память для кода и посмотреть, сколько объектов выделяет каждый вариант.

Sergio Tulentsev 21.03.2018 16:01

@Stefan, тогда это будет b = a.concat(b); a = [], то же самое, что и b.unshift(*a); a = []. Я считаю, что это не совсем то, о чем просил @ user1134991.

MOPO3OB 21.03.2018 17:10
Структурированный массив Numpy
Структурированный массив Numpy
Однако в реальных проектах я чаще всего имею дело со списками, состоящими из нескольких типов данных. Как мы можем использовать массивы numpy, чтобы...
T - 1Bits: Генерация последовательного массива
T - 1Bits: Генерация последовательного массива
По мере того, как мы пишем все больше кода, мы привыкаем к определенным способам действий. То тут, то там мы находим код, который заставляет нас...
Что такое деструктуризация массива в JavaScript?
Что такое деструктуризация массива в JavaScript?
Деструктуризация позволяет распаковывать значения из массивов и добавлять их в отдельные переменные.
1
3
95
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Is there something more elegant?

Используя как unshift, так и pop:

a = [1,2,3]
b = [4,5,6]

b.unshift(*a.pop(a.size))

p a, b
# []
# [1, 2, 3, 4, 5, 6]

Или, если вы не хотите использовать *:

a = [1,2,3]
b = [4,5,6]

b.unshift(a.pop(a.size)).flatten!

p a, b
# []
# [1, 2, 3, 4, 5, 6]

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

tadman 21.03.2018 19:12

@tadman "наверное"? Вы не совсем понимаете, как это работает? Позвольте мне вам это объяснить. a.pop(a.size) вернет весь массив a, а также удалит все элементы из массива a. То, что происходит с памятью, определяется реализацией Ruby. Оба варианта, описанные в ответе, полностью освобождают память для массива a при использовании Ruby по умолчанию.

MOPO3OB 21.03.2018 20:56

Это «вероятно» не тот способ, которым вы должны что-то объяснять кому-либо, особенно тем, кто ответил на тысячи вопросов о Ruby. Я говорю, вероятно, только потому, что в разных реализациях, даже в разных версиях Ruby есть различия в том, как это обрабатывается, но это ни в коем случае не является гарантированным «лучшим» решением.

tadman 21.03.2018 21:37

@tadman, если вы настолько опытны в «ответах на тысячи вопросов о Ruby», то вам следовало бы протестировать мои решения, прежде чем сказать, что «это ... создает мусор гораздо больше, чем первоначальная попытка ...». Поступив так, вы бы поняли, что это неправда. Более того, способ обработки не изменился, по крайней мере, с Ruby 1.9.3 до текущей версии.

MOPO3OB 22.03.2018 00:40

Профилирование памяти этих фрагментов и фрагментов OP поможет убедить аудиторию в том, что иногда это работает лучше. На первый взгляд, оба этих фрагмента выделяют как минимум один новый массив a.size. Примерно так же, как код в вопросе.

Sergio Tulentsev 22.03.2018 16:38

@SergioTulentsev согласился. Я нашел первый комментарий, оставленный г-ном @tadman, неуместным, потому что в нем говорилось, что мой код работает хуже, чем оригинал, но на самом деле это не так. Перед тем, как это сказать, не было проведено никакого анализа. Несомненно, мистер @tadman очень опытен, но этот комментарий был непрофессиональным. Обратите внимание, что я никогда не говорил, что отрицаю тот факт, что b += a лучше работает с памятью. Мой ответ на вопрос «Есть ли что-нибудь более элегантное?» начинается со слов «Использование как unshift, так и pop ...».

MOPO3OB 22.03.2018 17:02

взял на себя смелость указать точный вопрос, на который вы отвечали.

Sergio Tulentsev 22.03.2018 17:10

@ MOPO3OB Я сделал это наблюдение, потому что у меня было скрытое подозрение, что это может быть шагом назад. Вы можете оспорить это, но словесно ругать кого-то за просто скептицизм - это действительно неуместно. Я уже обращался к запросам, связанным с производительностью, и иногда самая быстрая вещь очень удивительна из-за внутренней оптимизации Ruby. Проблема, с которой я столкнулся с этим решением, заключается в том, что постепенно расширяет целевой массив, что может привести к нескольким более крупным выделениям. Операция конкатенации может выделить новый размер один раз.

tadman 22.03.2018 18:49

@tadman, почему вы говорите, что он расширяет целевой массив постепенно?

MOPO3OB 22.03.2018 18:54

Это то, что делает unshift, он добавляет по одному элементу за раз. Если бы вы делали это с массивом длиной 1e6, для которого потребовалось бы несколько расширений размера этого массива, каждое из которых является новым выделением + копией существующих данных (указателей). Большинство динамических массивов изменяют размер с шагом 1,5-2x. Это означает, что для перехода от длины 0 к длине 1 000 000 требуется около 19–39 распределений.

tadman 22.03.2018 18:56

@tadman это делает? Разве ary_memcpy0(ary, 0, argc, argv, target_ary); ARY_SET_LEN(ary, len + argc); не делает это за один шаг?

MOPO3OB 22.03.2018 19:00

Я спрашиваю, откуда у начальной операции unshift достаточно контекста, чтобы Ruby знал, что это первая из миллиона несмещений, которые вы собираетесь выполнить. Очевидно, что это не так, поэтому он просто расширяет массив постепенно, используя обычные правила. При выполнении concat он точно знает, какой длины должен быть новый массив, так что да, тогда он может расширяется за один раз.

tadman 22.03.2018 19:02

Я думаю, что и concat, и unshift знают, какой длины будет новый массив. Они оба используют RARRAY_LEN() для расчета этого.

MOPO3OB 22.03.2018 19:13

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

Лучший подход - сделать b += a, позволить a выпасть из области видимости, а затем позволить сборщику мусора разобраться с этим.

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

Были времена, когда МРТ вообще не высвобождала память (ее куча только увеличивалась, а не сокращалась). Думаю, сейчас лучше. :)

Sergio Tulentsev 22.03.2018 16:41

@SergioTulentsev Ах, те 1,8 дня, когда нам приходилось обрабатывать и снимать их, потому что они стали слишком толстыми и старыми.

tadman 22.03.2018 18:52

Было только до 1.8? Моя память нечеткая, но я совершенно уверен, что 1.9 тоже вела себя так.

Sergio Tulentsev 23.03.2018 14:25

@SergioTulentsev 1.8 был настолько плох, что существовал "Enterprise Ruby" для решения этой проблемы. 1.9 сделал большие улучшения, 2.0 в значительной степени лучше.

tadman 24.03.2018 02:38

Другие вопросы по теме