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

时间:2018-01-24 03:07:40

标签: elixir

新的Elixir和一般的函数式编程。我希望将新项目合并到现有项目列表中。当"键"新项目已经存在于列表中,我需要更新列表中的相应项目,否则我将新项目添加到列表中。

我已经提出了下面的内容,但似乎有点笨拙,有没有更好的方法来做这个?

非常感谢!

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

3 个答案:

答案 0 :(得分:0)

您可以使用地图。

google.maps.event.addListener(marker, 'click', () => {
      this.ngZone.run(() => {
        infowindow.setContent(place.name);
        infowindow.open(this.map,this.infowindow);
      });

Map.update/4 documentation

然后,如果你需要项目列表,你可以

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)

但是当然使用这个解决方案,你最终会将ID重复为密钥。

答案 1 :(得分:0)

我想你没有重复prodct_id

不更改您的结构,我建议使用List.update_at

首先,使用Enum.find_index而不是Enum.find来获取现有索引(如果有),然后只需更新它。

  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

答案 2 :(得分:0)

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

  1. 无需从struct转换为map
  2. 您可以先执行查找
  3. 以下是我要做的事情:

    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
    

    为清晰起见,地图更新%{... | ...}可以移动到自己的功能。