Reputation: 401
I want to use a for comprehension in Elixir for looping n times and accumulating some result for each time.
An example:
for i <- 0..n, y <- 1..3, do: y
This will loop 1 time when n is 0. I tried several other ways to get an empty list when n is 0, but can't see how to do it without using verbose constructs, which defeats the whole purpose of the comprehension. I realize this is because the range is inclusive. But I guess there must be a way to get exclusive ranges, or some other way to achieve empty for comprehensions while still being as readable. I just couldn't find it.
I want to know whether using a comprehension for this is reasonable in Elixir. If I have to make a long expression within the comprehension, or make my own helper functions, I'll just stick to other methods.
Upvotes: 1
Views: 698
Reputation: 1591
0..n
is a inclusive range. Mathematically speaking you have [0, n]
and you need [0, n)
.
As I see it,there are at least two ways of approaching what you're intending to do.
The first one is to define a function that implements a exclusive range like:
defmodule Util do
def range(from..to), do: range(from, to)
def range(n, n), do: []
def range(from, to) when to < 0, do: from..(to + 1) |> Enum.to_list()
def range(from, to) when to >= 0, do: from..(to - 1) |> Enum.to_list()
end
This function always returns a list, never a range, just to keep consistency:
import Util
for _ <- range(0..n), y <- 1..3, do: y
Or
import Util
for _ <- range(0, n), y <- 1..3, do: y
The other solution is to avoid the comprehension at all:
1..3 |> List.duplicate(n) |> Enum.flat_map(&(&1))
I hope this helps.
Upvotes: 3
Reputation: 961
The reason why your comprehension doesn't return an empty list is because the range 0..0
is not empty. This is apparent if you run 0..0 |> Enum.map(fn x -> x end)
; you'll get back [0]
. The x..y
syntax is shorthand for "give me a set of numbers starting with x
and ending with y
"; unsurprisingly, a list of all integers starting from zero and ending with zero contains a single number - zero - and therefore so should 0..0
.
In other words, 0..n
(when converted to a List) will always have a length of n + 1
. Thus, so will a comprehension over that list.
If you want to exclude the first item in an Enumerable, you can use Enum.drop/2
for that. Thus, to perform the second comprehension n
times instead of n + 1
times (which I think is your objective based on what you've written), the following ought to do the trick (assuming n
is defined, of course):
for i <- (0..n |> Enum.drop(1)), y <- 1..3, do: y
Upvotes: 2