Creating sublists in a for comprehension

Hello all,

I’m going through the open source beta version of the Dockyard Academy curriculum (GitHub - DockYard-Academy/beta_curriculum) and I’m doing some exercises about for comprehensions.

In one of these exercises I have to convert the following nested Enum.map into a comprehension with three generators:

Enum.map(1..3, fn a -> Enum.map(1..3, fn b -> {a, b} end) end)
# Result:
[[{1, 1}, {1, 2}, {1, 3}], [{2, 1}, {2, 2}, {2, 3}], [{3, 1}, {3, 2}, {3, 3}]]

My best guess has only two generators and results in one list without sublists:

for a <- 1..3, b <- 1..3, do: {a, b}
# Result:
[{1, 1}, {1, 2}, {1, 3}, {2, 1}, {2, 2}, {2, 3}, {3, 1}, {3, 2}, {3, 3}]

I assume the third generator (the one I am still missing) will create the 3 sublists. But I have no clue how this generator would look like. Any ideas?

I believe you need multiple comprehensions.

for a <- 1..3 do a end
|> IO.inspect
# [1,2,3]
for a <- 1..3, b <-1..3 do {a,b} end
|> IO.inspect
#[{1,1}, {1,2}, {1,3}, {2,1}, {2,2}, {2,3}, {3,1}, {3,2}, {3,3}]
for a <-1..3 do
  for b <- 1..3 do {a,b} end
end
|> IO.inspect
# [[{1,1}, {1,2}, {1,3}], [{2,1}, {2,2}, {2,3}], [{3,1}, {3,2}, {3,3}]]
4 Likes

I think this can be done with only two generators (mental mapping: 1 map = 1 for, nested map = nested for), I wonder how can one convert those two maps into three for-s?

1 Like

Thank you for the answers! I did not even think about the possibility of nesting the for’s, so I learned something new :grinning:

I still wonder how it can be done with three generators in one comprehension, like they asked. Is it even possible?

I don’t think so, seems to be an error.

1 Like

I think it is possible with a for reduce
You can build the tuples from b,c and depending on the value of a push it to an accumulator that starts with [[],[],[]]

1 Like

You are right, it can absolutely be possible with a for with reduce and two “zipped” fors as given originally. I always forget the existence of the reduce option of for comprehensions.

Reduce is not a generator, that would still only be two generators.

Is for…reduce not a generator?

The a <- list part of for is called a generator if one wants to be correct. Doesn’t matter if there’s a reduce or not.

1 Like

yeah I had to look up the definition of generator. I am embarrassed to admit that I didn’t know what a generator meant in Elixir until now. So TIL :slight_smile:

only 1 for - bit hacky but it works - the case can be replaced with something that better scales to longer lists (put_in + Access.at ?)

for x <- 1..3, y <- 1..3, reduce: [[], [], []] do
  [a, b, c] -> case x do
    1 -> [a ++[{x, y}], b, c]
    2 -> [a, b ++ [{x, y}], c]
    3 -> [a, b, c ++ [{x, y}]]
  end
end

I’m not sure whether to be proud or ashamed of myself :person_shrugging:

iex(40)> for x <- 1..3, row = (for y <- 1..3, do: {x,y}), z <- 1..3, x == z, do: row
[[{1, 1}, {1, 2}, {1, 3}], [{2, 1}, {2, 2}, {2, 3}], [{3, 1}, {3, 2}, {3, 3}]]
2 Likes

previous version but without case

for x <- 3..1, y <- 3..1, reduce: [[], [], []] do
  acc -> 
    current = Enum.at(acc, x - 1)
    new = [{x, y} | current]
    List.replace_at(acc, x - 1, new)
end
1 Like

I had initially misread the conditions, which was “one” comprehension with three generators. I thought nested would work, I don’t really think any meaningful way makes it possible to convert that with that condition and either it’s an error, or I’d really like to know the answer (and take that course!).

I’ve messaged the creator of the bootcamp and asked him if the exercise is correct. :slightly_smiling_face:

1 Like

The wording was indeed incorrect - no specific amount of generators is needed to solve the exercise. It will be changed with a PR. Thanks everyone for your help!

2 Likes