Учитывая генератор:
myVec1 = rand(0:4, 2)
myVec2 = rand(0:4, 8)
myGen = (val1 + val2 for val1 in myVec1, val2 in myVec2)
Так что это в основном матрица с 2 столбцами.
Это можно увидеть, используя collect(myGen)
.
Как я могу создать генератор, который выдает 2 значения за вызов (в основном столбец)?
Концептуально, что-то эквивалентное:
for myCol in eachcol(collect(myGen))
@show myCol;
end
Просто без явного выделения матрицы.
Могу ли я обернуть myGen
для следующего случая:
for value1, value2 in myGen
dosomethingelse1(value1, value2)
end
Итак, я ищу способ создать генератор, который возвращает 2 (или более?) последовательных значения сразу и может использоваться для этого в цикле.
Я уточнил, что означает 2 значения. Итак, в основном мы создаем «2D-массив» в генераторе, и я хотел бы получить доступ ко всему срезу сразу. Я мог бы сделать это с помощью eachcol
и eachrow
для реального массива, но как насчет генератора?
Вот тестовый пример:
myVec1 = rand(0:4, 2);
myVec2 = rand(0:4, 800);
@btime begin
myMat = [val1 + val2 for val1 in myVec1, val2 in myVec2];
outVec = [sum(myCol) for myCol in eachcol(myMat)];
end
@btime begin
myGen = (val1 + val2 for val1 in myVec1, val2 in myVec2);
outVec = [sum(myCol) for myCol in Iterators.partition(myGen, 2)];
end
Решение @Bogumil Kamiński действительно работает, но на практике по какой-то причине оно создает больше выделений, а мотивация заключалась в том, чтобы их уменьшить.
Вы в основном пропускаете скобки при деструктурировании кортежа значений во втором цикле. Чтобы быть более подробным, вы можете просто вернуть два значения (кортеж) в свою функцию dosomething
. Например:
function dosomething(element)
secondElement = element^2
element, secondElement
end
И затем вы можете использовать цикл, деструктурируя возвращаемое значение, например:
for (value1, value2) in myGen
dosomethingelse(value1, value2)
end
Если вам нужен полный рабочий пример:
myArray = [1, 2, 3]
function dosomething(element)
secondElement = element^2
element, secondElement
end
myGen = (dosomething(myElement) for myElement in myArray)
function dosomethingelse(value1, value2)
println("Value 1: $value1 \nValue 2: $value2 \n")
end
for (value1, value2) in myGen
dosomethingelse(value1, value2)
end
Это поддерживается непосредственно синтаксисом Julia. Итерация генераторов кортежей работает так же, как интерактивная обработка атомарных значений.
Например, вы можете попробовать:
for (a,b) in ((x, 3x) for x in 1:4)
println("a=$a, b=$b")
end
Я обновил вопрос, чтобы уточнить. Дело не в том, что вызываемая функция возвращает 2 значения, а в том, что генератор работает как 2D.
Вы имеете в виду декартово произведение, которое можно получить с помощью IterTools.product
?
Я предполагаю, что вы хотите что-то вроде:
julia> for (x1, x2) in Iterators.partition(1:10, 2)
@show x1, x2
end
(x1, x2) = (1, 2)
(x1, x2) = (3, 4)
(x1, x2) = (5, 6)
(x1, x2) = (7, 8)
(x1, x2) = (9, 10)
Если это то, что вы хотите, то Iterators.partition
— это функция, которую вы можете использовать.
Обновлено: если у вас есть два потока источников, используйте zip
:
julia> for (x1, x2) in zip(1:5, 6:10)
@show x1, x2
end
(x1, x2) = (1, 6)
(x1, x2) = (2, 7)
(x1, x2) = (3, 8)
(x1, x2) = (4, 9)
(x1, x2) = (5, 10)
Редактировать 2: мое первое решение уже работает для вашего случая:
julia> collect(myGen)
2×8 Matrix{Int64}:
3 7 5 4 6 3 4 5
1 5 3 2 4 1 2 3
julia> for (x1, x2) in Iterators.partition(myGen, 2)
@show x1, x2
end
(x1, x2) = (3, 1)
(x1, x2) = (7, 5)
(x1, x2) = (5, 3)
(x1, x2) = (4, 2)
(x1, x2) = (6, 4)
(x1, x2) = (3, 1)
(x1, x2) = (4, 2)
(x1, x2) = (5, 3)
Я обновил свой вопрос. Подойдет ли еще?
Я обновил ответ, но я все еще не на 100%, если это то, что вы хотите. Не могли бы вы поделиться рабочим примером ваших исходных данных и ожидаемым результатом?
Я создал простой кейс. Вы можете посмотреть на это? Трюк с zip
не сработает.
Это не работает, так как мое первое решение, кажется, делает то, что вы хотели в этом случае.
Действительно работает, но почему-то не уменьшает количество аллокаций. Есть идеи?
Проблема в том, что ваш генератор myGen
не является стабильным с самого начала (вы можете проверить это, обернув свой код в функцию и используя @code_warntype
). Если вам нужен быстрый код, используйте вместо этого явный цикл for
и не используйте генератор.
Вы уверены, что это так? См. stackoverflow.com/a/71752338.
Комментарий, который вы связали, правильный. Однако, похоже, что Iterators.partition
не только выделяет - как там было прокомментировано - но и по какой-то причине является нестабильным по типу (и это то, что я видел ранее, и теперь я перепроверил и снова вижу это).
Хотя другие ответы в некотором роде являются более общими, основываясь на информации, добавленной OP в свои правки, более эффективным с точки зрения памяти вариантом будет использование вложенных генераторов. Что-то вроде:
function solution_nested(v1, v2)
myGen = ((val1 + val2 for val1 in v1) for val2 in v2)
[sum(myCol) for myCol in myGen]
end
Когда вы тестируете решения, вам следует избегать использования глобальных переменных и желательно заключать решение в функцию, чтобы у Джулии было достаточно возможностей для оптимизации кода.
Это решение дает ожидаемый результат только одного распределения:
julia> @btime solution_nested(myVec1, myVec2);
1.856 μs (1 allocation: 6.38 KiB)
Поэтому, хотя это решение не совсем соответствует названию, оно, похоже, соответствует тому, что вы описываете. Мы используем ленивую последовательность ленивых столбцов. Причина того, что Iterators.partition
работает медленно и неэффективно с памятью, заключается в том, что он фактически выделяет промежуточные векторы значений в разделе: https://github.com/JuliaLang/julia/blob/dacf9d65aff4668b8fff25957d9aaa2cf03868c8/base/iterators.jl#L1232.
Является ли единственное распределение в вашем решении распределением вывода?
Я обновил вопрос, чтобы уточнить. Дело не в том, что вызываемая функция возвращает 2 значения, а в том, что генератор работает как 2D.