192

How do I join two strings in a list with a space, like:

["StringA", "StringB"]

becomes

"StringA StringB"
thiagofm
  • 5,693
  • 4
  • 20
  • 26

9 Answers9

279

If you just want to join some arbitrary list:

"StringA" <> " " <> "StringB"

or just use string interpolation:

 "#{a} #{b}"

If your list size is arbitrary:

Enum.join(["StringA", "StringB"], " ")

... all of the solutions above will return

"StringA StringB"
thiagofm
  • 5,693
  • 4
  • 20
  • 26
  • 39
    The alternative syntax using the pipeline operator: `["StringA", "StringB"] |> Enum.join " "` – Ryan Cromwell Dec 31 '13 at 02:26
  • 15
    You should avoid the pipeline operator when you don't actually have a need to pipe operations. – Carlos Melo Aug 05 '16 at 14:57
  • 3
    @EdMelo Care to elaborate on why? Technically you never truly "need" to pipe operations, since the same behavior could be achieved by nesting function calls. – Schrockwell Aug 06 '16 at 09:56
  • 8
    @Schrockwell yeah, "should" was too much. What I mean is that this case you have no gain in readability so a plain function call would make thinks more explicit. – Carlos Melo Aug 06 '16 at 10:13
  • 6
    You should use as much of the Elixir language as you can in order to demonstrate to potential employers that you know it. So I'd use all of the solutions above in the same file. – rodmclaughlin Jun 03 '18 at 07:47
  • @edmelo not really true, it depends on everyone's perspective .. i feel code is more readable when pipes are there.. ;) – Yatender Singh Dec 31 '18 at 10:30
  • `<>` operator is around times (benchmarked) faster than `Enum.join` – Alvise Susmel Mar 22 '19 at 11:44
64

If what you have is an arbitrary list, then you can use Enum.join, but if it's for just two or three, explicit string concatenation should be easier to read

"StringA" <> " " <> "StringB"

However, often you don't need to have it as a single string in memory if you're going to output it through e.g. the network. In that case, it can be advantageous to use an iolist (a specific type of a deep list), which saves you from copying data. For example,

iex(1)> IO.puts(["StringA", " ", "StringB"])
StringA StringB
:ok

Since you would have those strings as variables somewhere, by using a deep list, you avoid allocating a whole new string just to output it elsewhere. Many functions in elixir/erlang understand iolists, so you often wouldn't need to do the extra work.

Carlos Martín Nieto
  • 5,207
  • 1
  • 15
  • 16
11

How each method handles nil

There are a number of methods, but knowing how it handles nil values can determine which method you should choose.

This will throw an error

iex(4)> "my name is " <> "adam"
"my name is adam"

iex(1)> "my name is " <> nil
** (ArgumentError) expected binary argument in <> operator but got: nil
    (elixir) lib/kernel.ex:1767: Kernel.wrap_concatenation/3
    (elixir) lib/kernel.ex:1758: Kernel.extract_concatenations/2
    (elixir) lib/kernel.ex:1754: Kernel.extract_concatenations/2
    (elixir) expanding macro: Kernel.<>/2
    iex:1: (file)

This will just insert a blank "" string:

iex(1)> "my name is #{nil}"
"my name is "

As will this:

iex(3)> Enum.join(["my name is", nil], " ")
"my name is "

Also consider types. With <> you don't get any free casting:

iex(5)> "my name is " <> 1
** (ArgumentError) expected binary argument in <> operator but got: 1
    (elixir) lib/kernel.ex:1767: Kernel.wrap_concatenation/3
    (elixir) lib/kernel.ex:1758: Kernel.extract_concatenations/2
    (elixir) lib/kernel.ex:1754: Kernel.extract_concatenations/2
    (elixir) expanding macro: Kernel.<>/2
    iex:5: (file)

iex(5)> "my name is #{1}"
"my name is 1"

iex(7)> Enum.join(["my name is", 1], " ")
"my name is 1"

Performance in practice seems roughly the same:

iex(22)> :timer.tc(fn -> Enum.each(1..10_000_000, fn _ -> "my name is " <> "adam" end) end)
{8023855, :ok}
iex(23)> :timer.tc(fn -> Enum.each(1..10_000_000, fn _ -> "my name is " <> "adam" end) end)
{8528052, :ok}
iex(24)> :timer.tc(fn -> Enum.each(1..10_000_000, fn _ -> "my name is " <> "adam" end) end)
{7778532, :ok}
iex(25)> :timer.tc(fn -> Enum.each(1..10_000_000, fn _ -> "my name is #{"adam"}" end) end)
{7620582, :ok}
iex(26)> :timer.tc(fn -> Enum.each(1..10_000_000, fn _ -> "my name is #{"adam"}" end) end)
{7782710, :ok}
iex(27)> :timer.tc(fn -> Enum.each(1..10_000_000, fn _ -> "my name is #{"adam"}" end) end)
{7743727, :ok}

So, really depends on if you want to crash or not when the interpolated values are nil or the wrong type.

atomkirk
  • 3,701
  • 27
  • 30
10

Answering for completeness, you can also use String interpolation:

iex(1)> [a, b] = ["StringA", "StringB"]
iex(2)> "#{a} #{b}"
"StringA StringB"
Sheharyar
  • 73,588
  • 21
  • 168
  • 215
  • 1
    This is very useful, except for the common case where you want to eliminate spaces if one of the strings is nil - but `Enum.join(["StringA", nil], " ") == "StringA "` not `"StringA"` That's what I'm looking for - a really clean way to handle that case. – Steve Mar 10 '21 at 03:35
7

It depends on what you're trying to do. If you're just trying to write out to a new variable, then just use either:

  • String interpolation

    a = "StringA"
    b = "StringB"
    "#{a} #{b}"
    
  • String concatentation: "StringA" <> " " <> "StringB

  • Enum.join(): ["StringA", "StringB"] |> Enum.join(" ")

However, as Uri mentioned, IOLists can also be used:

["StringA", " ", "StringB"] |> IO.iodata_to_binary

IOLists are actually going to be the most performant if you need to care about resource consumption. Big Nerd Ranch has a good write-up on performance gains w/ IOLists.

6

If you were OK with adding a space in your list you could treat it as an iolist:

["StringA", " ", "StringB"] |> IO.iodata_to_binary # "StringA StringB"

This gives you some performances boosts as you're not duplicating any of the strings in memory.

Uri
  • 2,306
  • 2
  • 24
  • 25
3

An Enum.reduce would work too for your example no ?

iex(4)> Enum.reduce(["StringA", "StringB"], fn(x, acc) -> x <> " " <> acc end) "StringB StringA"

Muhammad Lukman Low
  • 8,177
  • 11
  • 44
  • 54
  • Yes, but it needs a reverse Enum.reduce(["a", "b", "c"] |> Enum.reverse, fn(x, acc) -> x <> " " <> acc end) "a b c" – Andrei Sura Oct 22 '17 at 22:37
  • personally think this is the best answer because it generalizes to other cases where reduce can be used. Speaks to the idea of "do.call" in R. – Thomas Browne Mar 24 '18 at 15:36
1

Consider using an IO List, if you have ["String1", "string2"] and you use iolist_to_binary/1 on it then you will copy those strings into a new string. If you have an IO list you can just output it in most cases and it will concatenate it on the port. And this is the key thing, the runtime will not need to make copies of the data so it is much more efficient than concatenation.

Zachary K
  • 3,205
  • 1
  • 29
  • 36
-1

You could also do 'string A' ++ ' ' ++ 'string B'

Maqbool
  • 197
  • 2
  • 9
Vatsala
  • 889
  • 1
  • 8
  • 22