【问题标题】:Reduce list of maps in one map减少一张地图中的地图列表
【发布时间】:2019-11-22 12:13:58
【问题描述】:

我有一个这样的地图列表:

[
  %{"000000000 000000000000 00000000 ": %{}},
  %{AM01: %{"C4" => "11111111", "C5" => "1"}},
  %{AM04: %{"C2" => "22222222", "C6" => "2"}}
]

如何在一张地图中减少此地图列表,如下所示?

%{
   "000000000 000000000000 00000000 ": %{},
    AM01: %{"C4" => "11111111", "C5" => "1"},
    AM04: %{"C2" => "22222222", "C6" => "2"}
}

生成这个地图列表的代码是这样的:

for segment <- Enum.filter(String.split(message, ["\x02", "\x1d", "\x1e", "\x03"]), fn x -> x != "" end) do
    [head | tail] = Enum.filter(String.split(segment, "\x1c"), fn x -> x != "" end)
    %{String.to_atom(head) => Map.new(tail, &String.split_at(&1, 2))}
end

【问题讨论】:

  • 您的消息是固定宽度和完全相同的结构吗?如果是这样,请发布一个。
  • 是的。如果此消息是 NCPDP 标准中的消息。 AM01 或 AM04 是段。 Map 包含该段的信息,每组的前两个字母“\x1c”标识信息的类型。唯一的细节是第一段是一个标题,它不包含地图,但我没有找到另一种方法。
  • 你能发布标准的链接吗?
  • 文档大得离谱,但我可以发布。在理想情况下,消息的最终结果将是:%{ HEADER: "000000000 000000000000 00000000", AM01: %{"C4" => "11111111", "C5" => "1"}, AM04: %{ "C2" => "22222222", "C6" => "2"} }
  • 你能描述一下格式吗,例如前两个字节是长度标头,接下来的 4 个字节是...

标签: elixir list-comprehension


【解决方案1】:
for map <- maps, into: %{} do
  [key] = Map.keys(map)
  {key, map[key]}
end

=>  
%{
  "000000000 000000000000 00000000 ": %{},
  AM01: %{"C4" => "11111111", "C5" => "1"},
  AM04: %{"C2" => "22222222", "C6" => "2"}
}

【讨论】:

    【解决方案2】:

    你可以直接这样做:

    message = "\x02\x1d0000 0000 \x1dAM01\x1cC41111\x1c\x1c\x1cC51\x1eAM04\x1cC22222\x1cC62\x1e\x03"
    
    for segment <- String.split(message, ["\x02", "\x1d", "\x1e", "\x03"], trim: true), into: %{} do
      [head|tail] = String.split(segment, "\x1c", trim: true)
      {
        String.to_atom(head), 
        Map.new(tail, &String.split_at(&1, 2))
      }
    end
    

    输出:

    %{
      "0000 0000 ": %{},
      AM01: %{"C4" => "1111", "C5" => "1"},
      AM04: %{"C2" => "2222", "C6" => "2"}
    }
    

    【讨论】:

      【解决方案3】:

      在理想情况下,消息的最终结果是:

      %{ HEADER: "000000000 000000000000 00000000 ", 
         AM01: %{"C4" => "11111111", "C5" => "1"}, 
         AM04: %{"C2" => "22222222", "C6" => "2"} 
      }
      

      给你:

      message = "\x02\x1d0000 0000 \x1dAM01\x1cC41111\x1c\x1c\x1cC51\x1eAM04\x1cC22222\x1cC62\x1e\x03"
      
      [header|segments] = String.split(message, ["\x02", "\x1d", "\x1e", "\x03"], trim: true)
      
      for segment <- segments, into: %{HEADER: header} do
        [head|tail] = String.split(segment, "\x1c", trim: true)
        {
          String.to_atom(head), 
          Map.new(tail, &String.split_at(&1, 2))
        }
      end
      

      输出:

      %{
        AM01: %{"C4" => "1111", "C5" => "1"},
        AM04: %{"C2" => "2222", "C6" => "2"},
        HEADER: "0000 0000 "
      }
      

      顺便说一句,Map.new() 位是骗人的。

      【讨论】:

        【解决方案4】:

        对这样的任务使用String.split/2 是一种极其低效、不雅且非英语的方法。 Erlang(以及 Elixir),作为电信的孩子,在解决这些任务方面特别出色。

        它们都将通过递归解析数据、标记上的模式匹配来解决。

        由于您没有发布真实输入的示例,我无法提出一个可行的示例,但方法应该是这样的:

        defmodule Parse do
          @input "\x1cHHheader\x1cAAaa segment\x1cBBbbsegment"
        
          def parse("", {{typ, txt}, map}), do: Map.put(map, typ, txt)
          def parse(<<"\x1c", type :: binary-size(2), rest :: binary>>, {{typ, txt}, map}),
            do: parse(rest, {{type, ""}, Map.put(map, typ, txt)})
          def parse(<<c :: binary-size(1), rest :: binary>>, {{typ, txt}, map}),
            do: parse(rest, {{typ, txt <> c}, map})
        
          def test(input \\ @input), do: parse(input, {{nil, ""}, %{}})
        end
        

        并像这样使用它:

        Parse.test
        #⇒ %{"AA" => "aa segment", "BB" => "bbsegment", "HH" => "header"}
        

        当然,真正的代码会更复杂,你需要对许多不同的子句进行模式匹配,但我敢打赌这个想法很清楚。

        NB我没有测试这段代码,但它应该可以开箱即用。

        请注意,与String.split/2 相比,这种方法还有另一个优势——它可以处理无限流。

        【讨论】:

          【解决方案5】:

          使用String.split/2 来完成这样的任务是一个非常 无效、不雅和不拘泥的方法。

          Elixir 的 String.split() 调用 Erlang 的 binary:split()

          -module(my).
          -compile([export_all]).
          
          go() ->
              Input = <<"\x1cHHheader\x1cAAaa segment\x1cBBbbsegment">>,
              binary:split(Input, <<"\x1c">>, [global, trim_all]).
          

          在外壳中:

          7> c(my).  
          my.erl:2: Warning: export_all flag enabled - all functions will be exported
          {ok,my}
          
          8> my:go().
          [<<"HHheader">>,<<"AAaa segment">>,<<"BBbbsegment">>]
          

          而且,我认为binary:split() 不仅有效,而且简短的单行器比相当混乱的多子句函数定义更优雅,并且简短的单行器更易于维护,而且绝对是好的 erlang。

          【讨论】:

          • 除了它非常容易出错(有可能会出现无声的假阴性而不是爆炸),String.split/2 的方法需要 两个 遍历输入和无缘无故地创建一个中间冗余数组。我不确定在多子句函数中究竟是什么让您感到困惑,但是字符串头部的模式匹配也支持流处理,这意味着它不需要将整个字符串加载到内存中。在某些情况下,它会成为String.split/2 的阻碍。
          • 从单个控制序列的例子中可能不清楚,但是当有几十个时,可能与以前的交叉依赖,向多子句添加一个新的控制序列就像添加一样简单一个新的条款。这里要重构整个代码。
          猜你喜欢
          • 2021-06-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-09-28
          • 1970-01-01
          • 1970-01-01
          • 2018-02-27
          • 2020-08-12
          相关资源
          最近更新 更多