'Create a generator which returns two values at once in Julia
Given a generator:
myVec1 = rand(0:4, 2)
myVec2 = rand(0:4, 8)
myGen = (val1 + val2 for val1 in myVec1, val2 in myVec2)
This is basically a matrix with 2 columns. It can be seen by using collect(myGen)
.
How can I create a generator which yields two values per call (basically a column)?
Conceptually, something equivalent of:
for myCol in eachcol(collect(myGen))
@show myCol;
end
Just without any explicit allocation of the matrix.
Can I wrap myGen
for the following case:
for value1, value2 in myGen
dosomethingelse1(value1, value2)
end
In other words, I am after a way to create a generator which returns 2 (or more?) consecutive values at once and can be used in a loop to do so.
So basically, we create a 2D array in the generator and I'd like to access the whole slice at once. I could do it with eachcol
and eachrow
for actual array, but what about the generator?
Here is a test case:
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
The solution by @Bogumił Kamiński indeed works, yet in practice, for some reason, it creates more allocations while the motivation was to reduce it.
Solution 1:[1]
Although the other answers are in some ways more general based on the information added by OP in their edits a more memory efficient option would be using nested generators. Something like:
function solution_nested(v1, v2)
myGen = ((val1 + val2 for val1 in v1) for val2 in v2)
[sum(myCol) for myCol in myGen]
end
When you test the solutions you should avoid using global variables and preferably wrap the solution in a function so you give Julia sufficient opportunity to optimize the code.
This solution gives the expected result of only one allocation:
julia> @btime solution_nested(myVec1, myVec2);
1.856 ?s (1 allocation: 6.38 KiB)
So while this solution does not quite fit the title it seems to fit what you are describing. We use a lazy sequence of lazy columns. The reason Iterators.partition
is slow and memory inefficient is that it actually allocates the intermediate vectors of values in a partition: https://github.com/JuliaLang/julia/blob/dacf9d65aff4668b8fff25957d9aaa2cf03868c8/base/iterators.jl#L1232 .
Solution 2:[2]
This is supported directly by Julia syntax. Iterating over generators of tuples works like interating over atomic values.
For an example you could try:
for (a,b) in ((x, 3x) for x in 1:4)
println("a=$a, b=$b")
end
Solution 3:[3]
I assume you want something like:
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)
If this is what you want then Iterators.partition
is a function you can use.
Edit: If you have two streams of sources use 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)
Edit 2: my first solution works already for your case:
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)
Solution 4:[4]
You're basically missing brackets when destructuring the tuple of values in the second loop. To be more detailed, you can just return two values (a tuple) in your dosomething
function. For example:
function dosomething(element)
secondElement = element^2
element, secondElement
end
And then you can use the loop by destructuring the return value, like so:
for (value1, value2) in myGen
dosomethingelse(value1, value2)
end
If you want a full working example:
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
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Przemyslaw Szufel |
Solution 3 | |
Solution 4 | Odilf |