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]

And have you considered broadcasting?

(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