Rich_Morin
Formatting a list of strings - am I missing anything?
I recently wrote some Elixir to format a list of strings (eg, author names) for output. I think it’s reasonably idiomatic, but I probably missed a few tricks. Suggestions, anyone?
-r
It should work like this…
iex(0)> list_str []
""
iex(1)> list_str [1]
1
iex(2)> list_str [1,2]
"1 and 2"
iex(3)> list_str [1,2,3]
"1, 2, and 3"
And now, the code… (ducks)
@doc """
Join a list of strings into a (mostly) comma-delimited string.
"""
def list_str( [] ), do: ""
def list_str( [ one ] ), do: one
def list_str( [ one, two ] ), do: "#{ one } and #{ two }"
def list_str(inp_list) do
[ last | base_list ] = Enum.reverse(inp_list)
base_str = base_list
|> Enum.reverse()
|> Enum.join(", ")
"#{ base_str }, and #{ last }"
end
Most Liked
blatyo
Should probably be:
def list_str( [ one ] ), do: to_string(one)
Otherwise, looks fine to me.
blatyo
They should be the same. I personally tend to use to_string instead of interpolation, when it’s only the value that I’m converting to a string. I’d use interpolation when I want to convert the values to a string and concatenate with something else.
peerreynders
When it comes to serial commas the comma before the “and” is optional - it just can’t be there if you have less than three items.
So I guess recursion isn’t idiomatic?
def list_str([]),
do: ""
def list_str([one]),
do: to_string(one)
def list_str(list),
do: list_str(list, [])
defp list_str([], [h, n | t]),
do: list_to_str(t, to_string(n) <> " and " <> to_string(h))
defp list_str([h | t], rest),
do: list_str(t, [h | rest])
defp list_to_str([], str),
do: str
defp list_to_str([h | t], str),
do: list_to_str(t, to_string(h) <> ", " <> str)
Rich_Morin
Thank you for that tour de force, Peer. However, it doesn’t produce the requested result, so a picky client might complain and a picky test suite would certainly fail. Do you have a version that would pass?
As I’m sure you realize, omission of the Oxford comma can lead to ambiguity. To cite a well known example, “I’d like to thank my parents, God and Mother Teresa…” So, pedants like me don’t consider it to be optional. (Then again, I also use a lot of optional parentheses and spaces in my code…)
Leaving that issue aside, I wonder about the use of <>. Is this version
to_string(n) <> " and " <> to_string(h)
actually better for some reason (e.g., faster) than this version?
"#{ n } and #{ h }"
Finally, although your recursive approach is most impressive, does it have any other benefits (aside from its stunning clarity and simplicity) that you can suggest? Inquiring gnomes need to mine…
yawaramin
If you’re outputting this with e.g. IO.puts or send_resp in Phoenix, you don’t have to output interpolated strings as results, you can output iolists. That’s the main trick that I would use. The second trick is to try to unify the handling of ‘x and y’ and ‘x, y, and z’ by realizing that the latter case reduces to the former case if you see that ‘x, y,’ in the latter corresponds to ‘x’ in the former. My version:
defmodule Test do
def list_str([]), do: ""
def list_str([x]), do: Integer.to_string(x)
def list_str([x1, x2]) when is_integer(x1) do
list_str([[Integer.to_string(x1), " "], x2])
end
def list_str([x1, x2]), do: [x1, "and ", Integer.to_string(x2)]
def list_str(xs) do
{init, [last]} = Enum.split(xs, -1) # Traverses twice
list_str([Enum.map(init, &append_comma/1), last])
end
defp append_comma(x), do: [Integer.to_string(x), ", "]
end
[] |> Test.list_str |> IO.puts
[1] |> Test.list_str |> IO.puts
[1, 2] |> Test.list_str |> IO.puts
[1, 2, 3] |> Test.list_str |> IO.puts








