# Like a list comprehension but with several lines

It frequently happens that I have a loop with ~10 lines. I’d like to return some value at the end of each iteration, collect that in an array, and then plot it. I can pre-allocate some array, or append to an array as the loop goes…but I’d like to do something like a list comprehension that just returns an array, without me worrying about its size or bothering to append. When the operation is something simple, and not 10 lines of code, I usually just do a list comprehension. How does one do a list comprehension when the thing you want to do to each iterate is 10 lines of code?

It seem like a do block might work for this but I’ve been messing around without success. Here’s a highly contrived MWE.

using Random
its = 10:2:30
out = []
for i in its
t = randperm(i)
s = maximum(t)
u = rand(s)[1]
append!(out,u)
end
plot(out)


I’d like to do this without using the ‘temporary’ out array

Something like:

its = collect(10:2:30)
?? do i
t = randperm(i)
s = maximum(t)
u = rand(s)[1]
end |> plot


This seems to work. Would love feedback…

using Random
its = 10:2:30
map(its) do i
t = randperm(i)
s = maximum(t)
u = rand(s)[1]
end |> plot

4 Likes

I like that idea – I will have to use it in my own code!

Have you seen Chain · Julia Packages?

You could do this:

using Random
using Chain
its = 10:2:30
@chain its begin
randperm.(_)
maximum.(_)
rand.(_)[1]
end

1 Like

This code is pretty confusing. As far as I can tell, it’s equivalent to just rand().

Hello Gus!

I was pondering a similar problem when I posted this suggestion. Based on this answer, your contrived loop can be rewritten in list comprehension syntax as follows:

out = [rand(s)[1]
for i in 10:2:30
for t = Ref(randperm(i))
for s = Ref(maximum(t))]


Whether you prefer this, the for loop or the map syntax is a matter of taste.

HTH.

1 Like

Similar to the answer above, you can directly translate this to a comprehension (but no need to introduce artificial iterations, a begin or let block will let you pack multiple lines):

[ let
t = randperm(i)
s = maximum(t)
u = rand(s)[1]
end for i in its ]

5 Likes

Here’s one option, similar to a let block:

[(t = randperm(i);
s = maximum(t);
rand(s)[1]) for i in its]


Or, on one line:

[(t = randperm(i); s = maximum(t); rand(s)[1]) for i in its]


(first ∘ rand ∘ maximum ∘ randperm).(its)
its .|> randperm .|> maximum .|> rand .|> first
(i -> (t=randperm(i); s=maximum(t); rand(s)[1])).(its)

2 Likes

As everything is an expression in Julia, you can just use a let block:

[let
t = randperm(i)
s = maximum(t)
rand(s)[1]
end
for i in 10:2:30]

2 Likes

I prefer placing locals on the same line:

[let t=randperm(i), s=maximum(t)
rand(s)[1]
end
for i = 10:2:30]


this way, when this comprehension is in local scope, it doesn’t inadvertently capture and overwrite any locals named t or s in its parent scope.

1 Like