Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Pedra Filosofal 1

notebooks/modulo_3/03_solucao.livemd

Pedra Filosofal 1

A implementação

defmodule Desafio do
  @moduledoc """
  Este é o módulo que deverá ser preenchido
  com a sua solução.

  É permitido criar quantas funções privadas for necessário,
  mas a única função pública deverá ser a função `split/2`
  que está definida como placeholder abaixo.
  """

  @spec split(
          lista_de_compras :: [
            {item :: String.t(), quantidade :: pos_integer(),
             preco_unitario_centavos :: pos_integer()}
          ],
          emails :: [String.t()]
        ) :: [%{String.t() => non_neg_integer()}]
  def split(lista_de_compras, emails) do
    emails = Enum.uniq(emails)

    valor_total = calculate_total_value(lista_de_compras)

    num_emails = length(emails)

    valor_por_pessoa = div(valor_total, num_emails)
    resto = rem(valor_total, num_emails)

    emails
    |> Enum.with_index(fn email, idx ->
      if idx < resto do
        {email, valor_por_pessoa + 1}
      else
        {email, valor_por_pessoa}
      end
    end)
    |> Map.new()

    # emails
    # |> Map.new(fn email -> {email, valor_por_pessoa} end)
    # |> distribute_rem(emails, resto)
  end

  defp calculate_total_value(list) do
    Enum.reduce(list, 0, fn {_nome, quantidade, valor_unitario}, acc ->
      acc + quantidade * valor_unitario
    end)

    # for {_nome, quantidade, valor_unitario} <- list, reduce: 0 do
    #   acc -> 
    #     acc + quantidade * valor_unitario
    # end

    # list
    # |> Enum.map(fn {_nome, quantidade, valor_unitario} -> quantidade * valor_unitario end)
    # |> Enum.sum()
  end

  defp distribute_rem(map, _emails, 0), do: map

  defp distribute_rem(map, [email | t], resto) do
    valor = map[email]

    modified_map = Map.put(map, email, valor + 1)

    distribute_rem(modified_map, t ++ [email], resto - 1)
  end
end
# Aqui faremos alguns testes básicos
import ExUnit.Assertions

# Lista de emails com valores repetidos
emails = ~w(
  paulo@email.com
  valente@email.com
  teste@email.com
  valente@email.com
  paulo@email.com
  valente@email.com
)

# Primeiro, vamos validar os casos em que o valor total é menor ou igual ao número de emails
assert %{"paulo@email.com" => 1, "valente@email.com" => 0, "teste@email.com" => 0} ==
         Desafio.split([{"banana", 1, 1}, {"maçã", 0, 10}], emails)

assert %{"paulo@email.com" => 1, "valente@email.com" => 1, "teste@email.com" => 0} ==
         Desafio.split([{"banana", 2, 1}, {"maçã", 0, 10}], emails)

assert %{"paulo@email.com" => 1, "valente@email.com" => 1, "teste@email.com" => 1} ==
         Desafio.split([{"banana", 1, 1}, {"maçã", 1, 2}], emails)

# Caso em que há mais de um centavo de resto
assert %{"paulo@email.com" => 2, "valente@email.com" => 2, "teste@email.com" => 1} ==
         Desafio.split([{"banana", 1, 1}, {"maçã", 1, 2}, {"uva", 2, 1}], emails)

# Teste de erro de arredondamento

assert %{
         "1@email.com" => 1,
         "2@email.com" => 1,
         "3@email.com" => 1,
         "4@email.com" => 1,
         "5@email.com" => 1,
         "6@email.com" => 1,
         "7@email.com" => 1,
         "8@email.com" => 0,
         "9@email.com" => 0,
         "10@email.com" => 0,
         "11@email.com" => 0
       } == Desafio.split([{"banana", 7, 1}], Enum.map(1..11, &amp;"#{&amp;1}@email.com"))