Elixir,将新项目合并到列表中

Elixir,将新项目合并到列表中,elixir,Elixir,一般来说,对于长生不老药和函数式编程来说是新的。我希望将一个新项目合并到现有项目的列表中。当列表中已经存在新项的键时,我需要更新列表中相应的项,否则我会将新项添加到列表中 我已经提出了以下建议,但似乎有点笨重,有没有更好的方法 非常感谢 defmodule Test.LineItem do defstruct product_id: nil, quantity: nil end defmodule Test do alias Test.LineItem def main do

一般来说,对于长生不老药和函数式编程来说是新的。我希望将一个新项目合并到现有项目的列表中。当列表中已经存在新项的键时,我需要更新列表中相应的项,否则我会将新项添加到列表中

我已经提出了以下建议,但似乎有点笨重,有没有更好的方法

非常感谢

defmodule Test.LineItem do
  defstruct product_id: nil, quantity: nil
end

defmodule Test do
  alias Test.LineItem

  def main do
    existing_items = [
      %LineItem{product_id: 1, quantity: 123},
      %LineItem{product_id: 2, quantity: 234},
      %LineItem{product_id: 3, quantity: 345}
    ]

    IO.puts "*** SHOULD BE 3 ITEMS, QUANTITY OF 123, 244, 345 ***"
    new_item = %{product_id: 2, quantity: 10}
    Enum.each merge(existing_items, new_item), &IO.inspect(&1)

    IO.puts "*** SHOULD BE 4 ITEMS, QUANTITY OF 10, 123, 234, 345 ***"
    new_item = %{product_id: 4, quantity: 10}
    Enum.each merge(existing_items, new_item), &IO.inspect(&1)
    :ok
  end

  def merge(existing_items, new_item) do
    existing_items = existing_items |> Enum.map(&Map.from_struct/1)

    lines = Enum.map(existing_items, fn(x) ->
      if x.product_id == new_item.product_id do
        %{product_id: x.product_id, quantity: x.quantity + new_item.quantity}
      else
        x
      end
    end)

    unless Enum.find(lines, &(Map.get(&1, :product_id)==new_item.product_id)) do
      [new_item | lines]
    else
      lines
    end
  end
end

你可以用地图来做这个

map = %{
  1 => %LineItem{product_id: 1, quantity: 123},
  2 => %LineItem{product_id: 2, quantity: 234},
  3 => %LineItem{product_id: 3, quantity: 345}
}

# update existing item:
item = %LineItem{product_id: 2, quantity: 10}
map = Map.update(map, item.product_id, item, fn old_item -> 
    %{old_item | quantity: old_item.quantity + item.quantity} 
end)

# you can define a helper function so that you don't have to manually type the key
def upsert(map, %LineItem{} = item) do
  Map.update(map, item.product_id, item, fn old_item ->
    %{old_item | quantity: old_item.quantity + item.quantity}
  end)
end

# insert new item:
item =%LineItem{product_id: 4, quantity: 10} 
map = upsert(map, item)
然后,如果您需要列表中的项目,您可以

Map.values(map)

当然,使用这个解决方案,您最终会将id复制为密钥。

我想您没有重复的产品id

不更改结构,我建议使用

首先,使用Enum.find_index而不是Enum.find来获取exist indexif,然后更新它

  def merge(existing_items, new_item) do
    existing_items = existing_items |> Enum.map(&Map.from_struct/1)

    case Enum.find_index(existing_items, &(Map.get(&1, :product_id)==new_item.product_id)) do
      nil ->
        [new_item | existing_items]
      index ->
      List.update_at(existing_items, index, fn x ->
        %{product_id: x.product_id, quantity: x.quantity + new_item.quantity}
      end)
    end
  end

你的解决方案非常接近。它可以通过两种不同的方式进行清理:

无需从结构转换为映射 您可以先执行查找 下面是我要做的:

def merge(existing_items, new_item) do
  if Enum.any?(existing_items, &(&1.product_id == new_item.product_id)) do
    Enum.map(existing_items, fn existing_item ->
      if existing_item.product_id == new_item.product_id do
        %{existing_item | quantity: existing_item.quantify + new_item.quantity}
      else
        existing_item
      end
    end)
  else
    [new_item | existing_items]
  end
end

为了清晰起见,可以将地图更新%{…|…}移动到它自己的函数。

Nope。当新物品的产品id已经存在于现有地图中时,他需要添加数量。@YongHaoHu,哦,是的,完全错过了那个部分。对我来说,使用updateAs编辑答案,使用外部键是很奇怪的。@YongHaoHu,对于映射,键查找具有O1复杂性,而对于Enum.find_索引,在最坏的情况下必须迭代所有项。然后,您必须再次遍历它们以更新值。因此,对于大量的数据,映射的效率要高得多。但是如果他想使用这个外部密钥,他甚至可以使用product_id作为map的密钥,这对我来说很好。FWIW如果它给出的输出是正确的,我觉得你的解决方案很好。感谢3个伟大的建议,非常感谢!