【问题标题】:Implementation of split_binary function of ErlangErlang的split_binary函数的实现
【发布时间】:2021-02-11 00:06:17
【问题描述】:

我是 Erlang 世界的新手。我正在尝试实现函数 split_binary。该函数将 (list, index) 作为输入,并根据索引将列表拆分为两个列表。

split(Lst, N) when N>=list:lenght(Lst) -> Lst;
split(Lst, N) when N<list:lenght(Lst) -> splitHelper(list:reverse(Lst), 0, N, []).

splitHelper([H|T], X, N, Acc) ->
  if
    X>=N ->
      (list:reverse([H|T]), list:reverse(Acc));
    X<N ->
      splitHelper(T, X+1, N, [H|Acc])
  end.

如何改进我的代码?

【问题讨论】:

    标签: erlang


    【解决方案1】:

    我是 Erlang 世界的新手。我正在尝试实现该功能 拆分二进制。该函数作为输入(列表,索引)并拆分 根据索引将列表分为两个列表。

    根据split_binary/2 的erlang 文档,这两个参数是binary,它不是一个列表,以及您要在其中拆分二进制文件的bytes 的数量。

    首先,您需要对什么是二进制文件有一个基本的了解。二进制是一个字节序列,其中每个字节是 8 位,表示某个整数,例如

    0010 0001

    这是33。下面是一个二进制示例:

    <<1, 2, 3>>
    

    当您不为每个整数指定大小时,默认情况下每个整数将占用一个字节。如果您希望 2 占用两个字节,即 0000 0000 0000 0010,即 16 位,那么您可以这样写:

    <<1, 2:16, 3>>
    

    shell 将显示为:

     <<1,0,2,3>>
    

    嗯?那个 0 是从哪里来的? shell逐字节显示二进制,整数0000 0000 0000 0010的第一个字节是0000 0000,也就是0。

    接下来,您可以像处理列表一样单步执行二进制文件,从二进制文件的前面一次提取任意数量的位。碰巧split_binary/2 每次从二进制文件的头部提取 8 位或 1 个字节。

    学习如何逐步执行二进制文件有几个技巧:

    1. 对于列表,[] 表示一个空列表,对于二进制文件,&lt;&lt;&gt;&gt; 表示一个空二进制文件。

    2. 对于列表,您编写 [Head|Tail] 以提取列表的头部,对于二进制文件,您编写 &lt;&lt;Bits:3, Rest/binary&gt;&gt; 以从二进制文件的前面提取 3 位。在您的情况下,您需要从二进制文件的前面提取 8 位。

    以下是您可以执行的操作的示例:

    -module(a).
    -compile(export_all).
    
    split_b(Bin, N) ->
        split_b(Bin, N, _Acc = <<>>).
    
    split_b(     Bin,               _N = 0, Acc) -> [Acc, Bin];
    split_b(<<Bits:8, Rest/binary>>, N,     Acc) ->
        split_b(Rest, N-1, <<Acc/binary, Bits>>).
    

    在外壳中:

    40> c(a).
    a.erl:2: Warning: export_all flag enabled - all functions will be exported
    {ok,a}
    
    41> a:split_b(<<5,6,7>>, 1).
    [<<5>>,<<6,7>>]
    
    42> a:split_b(<<5,6,7>>, 2).
    [<<5,6>>,<<7>>]
    

    请注意,在构建二进制文件时,二进制文件的一个段可以是另一个二进制文件:

    23> Bin = <<1, 2, 3>>.        
    <<1,2,3>>
    
    24> Acc = <<Bin/binary, 4>>.
    <<1,2,3,4>>
    

    如果你真的想实现lists:split/2,你可以这样做:

    -module(a).
    -compile(export_all).
    
    split_l(N, List) -> 
        split_l(N, List, _Acc=[]).
    
    split_l(_N=0, List, Acc) ->
        [lists:reverse(Acc), List];
    split_l(N, [H|T], Acc) -> 
        split_l(N-1, T, [H|Acc]).
    

    在外壳中:

    2> c(a).
    a.erl:2: Warning: export_all flag enabled - all functions will be exported
    {ok,a}
    
    3> a:split_l(1, [10, 20, 30]).
    ["\n",[20,30]]
    
    4> shell:strings(false).
    true
    
    5> a:split_l(1, [10, 20, 30]).
    [[10],[20,30]]
    
    6> a:split_l(2, [10, 20, 30]).
    [[10,20],[30]]
    

    【讨论】:

      【解决方案2】:

      我认为@7stud 的答案是最好的,但我想添加一些关于你的代码的小细节,而不是实际检查它是否有效……

      • list:lenght/1 不存在(除非您还创建了自己的 list 模块。

        • 如果您创建了自己的list 模块,则不能在警卫中使用它。那里只允许使用 BIF。
        • 如果您尝试使用stdlib 的函数来检查列表的长度,那么您应该使用erlang:length/1 或只使用length/1
      • 在 Erlang 中使用snake_case(例如split_helper)而不是camelCase(例如splitHelper)更惯用模块名称、函数名称和一般原子。 p>

      • 您可以直接使用模式匹配,而不是编写 if 作为函数的唯一表达式……

         split_helper([H|T], X, N, Acc) when X > N ->
           (list:reverse([H|T]), list:reverse(Acc));
         split_helper([H|T], X, N, Acc) when X<N ->
           split_helper(T, X+1, N, [H|Acc]).
        
      • 元组用大括号而不是圆括号表示:{list:reverse([H|T]),…。顺便说一句......这应该阻止你的代码编译。错误应该看起来像syntax error before: ','

      • 另外,您可能已经编写了自己的 list 模块,但如果没有,并且如果您尝试使用 stdlib 功能,则它是 lists:reverse/1 而不是 list:reverse/1

      最后,除此之外,我强烈建议您为您的代码编写一些简单的测试。 This article 可以帮到你。

      【讨论】:

        猜你喜欢
        • 2023-03-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-10-14
        • 1970-01-01
        • 2012-06-24
        • 2012-09-28
        • 2018-09-27
        相关资源
        最近更新 更多