使用 Enum.find_value 合并地图列表

Merge list of map using Enum.find_value

根据id合并列表

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

price =
  [
    %{price: ".00", p_id: 7},
    %{price: ".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

如果前面的列表后面引用为productlatterprice,当后者没有p_id对应的元素时出现问题前一个中的元素,一个可能与

product =
  [
    %{id: 7, name: "A", count: 1},
    %{id: 8, name: "B", count: 1},
    %{id: 9, name: "C", count: 0}
  ]
price =
  [
    %{price: ".95", p_id: 8},
    %{price: ".00", p_id: 7}
  ]

在那种情况下,Enum.map/2 会引发,但不会在 Enum.find/2 上引发,它会在找不到元素时愉快地返回 nil,但在 a) 内部函数子句模式匹配上和 b) 在 .price 访问时 Enum.find/2 returns 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 调用。为避免这种情况,可以使用后一种代码并添加显式 sink-all 子句来处理此问题。这个版本在健壮性和容易出错方面是最好的:

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: ".00"},
#    %{count: 1, id: 8, name: "B", price: nil},
#    %{count: 0, name: "C"}
#  ]