morten
morten

Reputation: 401

For comprehensions in Elixir when n = 0

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

Answers (2)

Alex de Sousa
Alex de Sousa

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

YellowApple
YellowApple

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

Related Questions