9
votes

I am learning Elixir and found myself having to concat multiple lists together where I have a beginning, middle, and end. Simplified example:

a = [1,2]
b = [3,4]
c = [5,6]
a ++ b ++ c
> [1, 2, 3, 4, 5, 6]

I am doing this thousands of times in a stream and want to be Elixirific about it.

Part 1)

I wrote a function to handle this, there is probably something that does this for me, but I don't see it. Should I be using built-in Elixir function for this?

def append(front, back) when is_list(front) when is_list(back) do
  front
  |> Enum.reverse
  |> Enum.reduce(back, &([&1 | &2]))
end

Or should I have just done this and it will become more natural for me over time?

[1, 2]
|> Enum.reverse 
|> Enum.reduce([3, 4], &([&1 | &2]))
|> Enum.reverse
|> Enum.reduce([5, 6], &([&1 | &2]))

Part 2)

Does the order in which I call the parts together matter?

Way 1:
[1, 2]
|> append([3, 4])
|> append([5, 6])

...
Way 2:
end = append([3, 4], [5, 6])
append([1, 2], end)

Will the intermediary list be re-used in both scenarios since both are appending header pointers?

Any help on this would be great, cheers.

1

1 Answers

16
votes

No matter what approach you use, you will end up at cloning at least all lists except the last one, since you cannot append to a linked list without cloning it. So if you know you want to concatenate the lists (i.e. you cannot modify your code to accept a nested list like [a, b, c]), I would suggest a ++ b ++ c since ++ is implemented in Erlang's C code, which should be pretty much as fast as possible, and definitely faster than manually concatenating with Enum.reverse/1 and Enum.reduce/3.

Here's a tiny microbenchmark comparing a ++ b ++ c vs a |> append(b) |> append(c):

defmodule BasicBench do
  use Benchfella

  bench "++", [a: gen(), b: gen(), c: gen()] do
    a ++ b ++ c
  end

  bench "append", [a: gen(), b: gen(), c: gen()] do
    a |> append(b) |> append(c)
  end

  def append(front, back) when is_list(front) when is_list(back) do
    front
    |> Enum.reverse
    |> Enum.reduce(back, &([&1 | &2]))
  end

  def gen, do: Enum.to_list(1..1000)
end

and the output:

benchma iterations   average time
++          100000   10.22 µs/op
append       20000   75.79 µs/op