Generate a list of structs from a list

lists
structs

#1

Hello,

A beginner’s question:
I already have code that gives me a list of surnames of unknown length named player_names. The values are for example ['Oberhagner', 'Windberger', 'Glaser', 'Gschwenner', 'Makatsch']

I also have a struct ‘Player’ with surname:

defmodule Player do
  defstruct surname: nil,
            forename: nil,
            age: nil
end

To put the surname into a Player struct I can do this:

player_xy = Map.put(%Player{}, :surname, Enum.at(player_names, xy))

where xy is the index of my player_names list.

But I want to have a list of those structs. I want to create a list of players that have the surname of the given list.

players = Enum.into(player_names, [], fn x -> Map.put(%Player{}, :surname, Enum.at(player_names, 1)) end)

That works unless that all elements of players now have the surname of the first element of the list of player names (‘Oberhagner’). That’s clear as my code selects the first element (“1”).

But with what should I replace the “1” in my code? Is there an internal counter how often the function x has already be called that I can use?

Or do I have to do that completely different? Thinking in functional programming is still very hard for me.

Thanks for your help.


#2

:wave:

Have you tried mapping through the list?


#3

As @idiot says, using Enum.map/2 is probably the easiest thing here, or a comprehension.

PS, as a hint, you also can use %Player{surname: surname} to initialise your struct, thats more efficient than using Map.put/3.

PPS: structs = for surname <- surnames, do: …
PPPS: structs = Enum.map(surnames, fn surname -> … end)


#4

Thanks.

This is my code now:

players = Enum.map(player_names, fn surname ->
      %Player{surname: surname}
    end)

#5

Ok, that works fine with one list (of surnames). Now I have two (and later more) lists. As I have one for surnames, I also have one for firstnames (and one for ages, etc.).

So the 1st struct should have element 1 from list surname as surname and element 1 from list firstname as firstname.

Can you please point me to the right direction again?

My approach would be: when I create the struct for a surname I give every struct also an ID (starting with 1 increasing) and then updating the struct with the firstname via get_and_update/3. But how would I create the (increasing) ID?


#6

If all lists have the same number of elements and appear in the same order, you might be able to make some use of Enum.zip/1 and /2.


#7

Thanks a lot.

My code is now:

    players_info = Enum.zip([lastnames, firstnames, ages])
    players = Enum.map(players_info, fn players_info ->
      %Player{lastname: elem(players_info, 0), firstname: elem(players_info, 1), age: elem(players_info, 2)}
    end)

#8

Have you considered pattern matching?


#9

Hm, I cannot see how?


#10

In the anonymous function, match on the tuple and bind individual names for its elements rather than calling elem/2.


#11

@Nefcairon this is what @NobbZ is talking about:

players_info = Enum.zip([lastnames, firstnames, ages])
players = Enum.map(players_info, fn {lastname, firstname, age} ->
  %Player{lastname: lastname, firstname: firstname, age: age}
end)

Edit: you could also use pipe operator to make less assignments, although there is not a huge legibility difference:

[lastnames, firstnames, ages]
|> Enum.zip()
|> Enum.map(fn {lastname, firstname, age} ->
  %Player{lastname: lastname, firstname: firstname, age: age}
end)

#12

@NobbZ, @kelvinst : Thanks. Gives me new insights :slight_smile:
It seems I should learn about functions again…


#13

… and perhaps recursion

# file: plauer.exs
defmodule Player do
  defstruct [:id, :surname, :firstname, :age]

  def zip(surnames, firstnames, ages, start_id \\ 1),
    do: zip(surnames, firstnames, ages, start_id, [])

  defp zip([surname | s_rest], [firstname | f_rest], [age | a_rest], id, players) do
    zip(s_rest, f_rest, a_rest, id + 1, [
          %Player{id: id, surname: surname, firstname: firstname, age: age} | players
        ])
  end

  defp zip(_, _, _, _, players) do
    :lists.reverse(players)
  end
end

firstnames = ["One", "Two", "Three"]
surnames = ["First", "Second", "Third"]
ages = [25, 26, 27]
start_id = 100

players = Player.zip(surnames, firstnames, ages, start_id)
IO.puts("#{inspect(players)}")
$ elixir player.exs
[
  %Player{age: 25, firstname: "One", id: 100, surname: "First"},
  %Player{age: 26, firstname: "Two", id: 101, surname: "Second"},
  %Player{age: 27, firstname: "Three", id: 102, surname: "Third"}
]