Объединить список карты, используя Enum.find_value - PullRequest
0 голосов
/ 30 апреля 2020

Для объединения списков на основе идентификатора

products =
  [
    %{id: 7, name: "A", count: 1},
    %{id: 8, name: "B", count: 1},
    %{name: "C", count: 0}
  ]

price =
  [
    %{price: "$10.00", p_id: 7},
    %{price: "$29.95", p_id: 10},
  ]

, в настоящее время использующего

Enum.map(products, &Map.put(&1, :price, Enum.find(price, fn %{p_id: pid} -> pid == &1.id end).price))

Но возникает ошибка, если id отсутствует в списке продуктов.

(KeyError) key :id not found in: %{count: 1, name: "A"} 

Как использовать Enum.find_value для того же?

1 Ответ

0 голосов
/ 30 апреля 2020

Если первый список упоминается позже как product, latter как price, и проблема возникает, когда у последнего нет элемента с p_id, соответствующего одному из элементов в первом, можно go с

product =
  [
    %{id: 7, name: "A", count: 1},
    %{id: 8, name: "B", count: 1},
    %{id: 9, name: "C", count: 0}
  ]
price =
  [
    %{price: "$14.95", p_id: 8},
    %{price: "$10.00", p_id: 7}
  ]

В этом случае Enum.map/2 будет повышаться, но не на Enum.find/2, который с радостью возвращает nil, когда элемент не может быть найден, но на a) шаблоне предложения внутренней функции совпадать и б) при .price доступе, когда Enum.find/2 возвращает nil.

Непростым решением было бы устранить обе проблемы, используя Access вместо точечной нотации:

Enum.map(
  products,
  &Map.put(
    &1,
    :price,
    #                        ⇓⇓⇓⇓⇓⇓⇓⇓              ⇓⇓⇓⇓⇓⇓⇓⇓
    Enum.find(price, fn p -> p[:p_id] == &1.id end)[:price]
  )
)

Другим способом было бы правильно найти элемент и обработать его соответствующим образом

Enum.map(
  products, fn %{id: id} = product ->
    product_price =
      price
      |> Enum.find(&match?(%{p_id: ^id}, &1))
      |> case do
        %{price: price} -> price
        _ -> nil
      end
    Map.put(product, :price, product_price)
  end)

OTOH, если id отсутствует в product, оно поднимается KeyError из-за &1.id звонка. чтобы избежать этого, можно использовать последний код и добавить явное предложение приемника-все для обработки этого. Эта версия является лучшей с точки зрения надежности и подвержена ошибкам:

Enum.map(
  products, fn
    %{id: id} = product ->
      product_price =
        price
        |> Enum.find(&match?(%{p_id: ^id}, &1))
        |> case do
          %{price: price} -> price
          _ -> nil # no price presented
        end
      Map.put(product, :price, product_price)

    product -> product # no id presented
  end)
#⇒ [
#    %{count: 1, id: 7, name: "A", price: "$10.00"},
#    %{count: 1, id: 8, name: "B", price: nil},
#    %{count: 0, name: "C"}
#  ]
...