csokun
February 14, 2023, 12:50pm
1
I managed to write a simple script to distribute cards to players.
num_of_cards_per_player = 2
num_of_players = 2
cards = ["Ace", 1, 2, 3, 4, 5, 6, 7]
initial_state = %{players: %{}, cards: cards}
1..num_of_cards_per_player
|> Enum.reduce(initial_state, fn _, proxy ->
1..num_of_players
|> Enum.reduce(proxy, fn i, acc ->
[h | tail] = acc.cards
acc
|> Map.update(:players, acc.players, fn existing ->
existing |> Map.update("player_#{i}", [h], fn items -> [h | items] end)
end)
|> Map.put(:cards, tail)
end)
end)
# output
# %{cards: [4, 5, 6, 7], players: %{"player_1" => [2, "Ace"], "player_2" => [3, 1]}}
I wonder if is a better way to do it.
hlx
February 14, 2023, 1:01pm
2
Maybe with for
for _i <- 1..num_of_cards_per_player, j <- 1..num_of_players, reduce: initial_state do
acc ->
[h | tail] = acc.cards
acc
|> Map.update(:players, acc.players, fn existing ->
existing |> Map.update("player_#{j}", [h], fn items -> [h | items] end)
end)
|> Map.put(:cards, tail)
end
1 Like
jerdew
February 14, 2023, 2:06pm
3
update_in
can help with nested puts/updates:
acc
|> update_in([:players, "player_#{j}"], fn
nil -> [h]
items -> [h | items]
end)
destructuring sometimes helps with code management:
for _ <- 1..num_of_cards_per_player, j <- 1..num_of_players, reduce: initial_state do
%{cards: [card | remaining_cards], players: players} ->
updated_players = Map.update(players, "player_#{j}", [card], fn deck -> [card | deck] end)
%{cards: remaining_cards, players: updated_players}
end
Note that none of the solutions so far handle the draw deck being empty (they all expect to match a non-empty [head | tail]
shape)
1 Like
al2o3cr
February 14, 2023, 2:25pm
4
num_of_cards_per_player = 2
num_of_players = 2
cards = ["Ace", 1, 2, 3, 4, 5, 6, 7]
{to_deal, rest} = Enum.split(cards, num_of_cards_per_player * num_of_players)
new_players =
to_deal
|> Stream.chunk_every(num_of_players)
|> Stream.take(num_of_cards_per_player)
|> Enum.zip_reduce([], fn cards, acc -> [Enum.reverse(cards) | acc] end)
|> Enum.reverse()
|> Enum.with_index(1)
|> Map.new(fn {cards, id} -> {"player_#{id}", cards} end)
%{cards: rest, players: new_players}
I dunno about “better”, but it was interesting to see a place where zip_reduce
solved the problem.
This uses chunk_every
to collect groups containing num_of_players
cards, then zip_reduce
to collect “all the first card”, “all the second card”, “all the third card”, etc
3 Likes
Enum.zip_with
would be even cleaner.
1 Like
csokun
February 15, 2023, 1:14am
6
Now, I can see the power of for
comprehension and update_in
.
This is cool! thanks @jerdew @hlx
csokun
February 15, 2023, 1:44am
7
It is a bit challenging at first, but after I replaced Stream
with Enum
and append |> dbg()
to_deal #=> ["Ace", 1, 2, 3]
|> Enum.chunk_every(num_of_players) #=> [["Ace", 1], [2, 3]]
|> Enum.take(num_of_cards_per_player) #=> [["Ace", 1], [2, 3]]
|> Enum.zip_reduce([], fn cards, acc -> [Enum.reverse(cards) | acc] end) #=> [[3, 1], [2, "Ace"]]
|> Enum.reverse() #=> [[2, "Ace"], [3, 1]]
|> Enum.with_index(1) #=> [{[2, "Ace"], 1}, {[3, 1], 2}]
|> Map.new(fn {cards, id} -> {"player_#{id}", cards} end) #=> %{"player_1" => [2, "Ace"], "player_2" => [3, 1]}
Cool!
csokun:
|> Enum.zip_reduce([], fn cards, acc -> [Enum.reverse(cards) | acc] end) #=> [[3, 1], [2, "Ace"]]
|> Enum.reverse() #=> [[2, "Ace"], [3, 1]]
That part could be
|> Enum.zip_with(fn cards -> cards end)
1 Like