How to accumulate the value in a loop

Hi I am trying to loop through a list and accumulate the data by changing the values a bit.
My map is simple : %{"hostname" => "my_host_name", "level" => nil}
Below is the code I am trying with:

 msg = Enum.reduce(Map.values(source), [], fn(k, acc) ->
    if k do
        msg2 = msg2 <> String.replace_suffix(Enum.join(List.wrap(k), " "), "\n", "") <>" "
        msg2 = msg2 <> "-" <>" "
 IO.inspect msg              

I am expecting the result : “my_host_name -” in msg object.

Could anyone suggest me a way to achieve it?
Thanks in advance…

Do You want to add the values of the map?

I’m totally lost trying to figure out your goal. Why are you just reducing just the values of the map? Why are you concatenating with msg2 inside your if blocks when that has not been defined before? What is going on with the List.wrap and Enum.join and String.replace_suffix?

Is that what You want?

iex> source = %{"hostname" => "my_host_name", "level" => nil}
iex> source |> Map.values |> &1 || "-") |> Enum.join(" ") 
"my_host_name -"

or this same shorter?

iex> source |> Map.values |> Enum.reduce("", fn x, acc -> acc <> (x || "-") <> " "  end)

kokolegorille Thanks for the reply… I am trying loop through a list, use them as keys for fetching values of another map and concatenate the resultant value as string… For example:
list = ["key1,“key2”…] and map={“key1”: “value1”, “key2”: “value2”…}. Json is nested so I need to flatten it out. hence using List.wrap … I would like to get the answer as “value1 value2…”

gregvaughn thanks for the reply. msg2 is the result holder. I am new to Elixir so I might be doing something wrong… But I am trying to loop through the values of the map and do some operation on them and return a new object

Would that work for you?

list = ["key1", "key2"]
map = %{"key1" => "value1", "key2" => "value2"}

(for key <- list, do: map[key])
|> Enum.reject(&is_nil/1)
|> Enum.join(" ")

#=> "value1 value2"


(for key <- list, {:ok, value} = Map.fetch(map, key), do: value) |> Enum.join(" ")
# or
(for key <- list, value = Map.get(map, key), not is_nil(value), do: value) |> Enum.join(" ")

because comprehensions are considered to be efficient


|>, &1))
|> Stream.reject(&is_nil/1)
|> Enum.join(" ")

might be even more efficient since it supposedly iterates over the list only once.


Thanks… But I would like to replace nil with ‘-’

I would do it like this probably

list = ["key1", "key2", "key3", "key4"]
map = %{"key1" => "value1", "key2" => "value2", "key4" => "value4"}

|> key ->
  case Map.get(map, key) do
    nil -> "-"
    value -> value
|> Enum.join(" ")

#=> "value1 value2 - value4"

Here’s another alternative starting with Map.take/2 and using multiple matching heads in an anonymous function just to show different ways of approaching the problem.

list = ["key1", "key2", "key3"]
map = %{"key1" => "value1", "key2" => "value2", "key3" => nil}

|> Map.take(list)
|> Map.values
|> nil -> "-"; x -> x end)
|> Enum.join(" ")

"value1 value2 -"

I guess the main difference in how to approach the problem is that you were trying to build up the string in pieces in an Enum.reduce. That can certainly be done, but it’s more “Elixir-ish” to think of transforming lists and in the end combining into a string

EDIT Actually, don’t do what I did above if you care about the order of the values in the string because you can’t trust the ordering of Map.values

Yes… ordering is what I am concerned about…

I’d kind of be surprised otherwise :slight_smile: Your initial code to reduce over the values has the same flaw. That last example from @idi527 is what you should use.

To add on previous @idi527 answer, You can make it shorter by using Map.get/3

list |> Map.get(map, &1, "-")) |> Enum.join(" ")
1 Like

Thanks gregvaughn… This works perfect to my usecase

Thank you all for your replies… :slight_smile: I got the solution

I thought that at one point too, but since the key exists, but the value is nil, Map.get/3 is still going to give you nil

1 Like

Oops, that is so true :slight_smile: