【问题标题】:Find all tuples within a list of tuples查找元组列表中的所有元组
【发布时间】:2020-08-16 08:06:56
【问题描述】:

我目前正在攻读计算机科学硕士的第二学期,并正在学习分布式系统编程课程。因此,我们应该提交每周练习,其中还包括 Erlang 编码练习。

由于这是课程的第二周,我们才刚刚开始使用 Erlang,这是第一个练习,我们应该在一个模块中实现 6 个函数。前 5 个功能我自己可以轻松完成,但第 6 个功能完全让我不知所措。为此,我们应该编写一个接受 2 个输入的函数:一个表示键值对的元组列表和一个包含要搜索的键的列表。然后该函数应该在整个列表中搜索这些键的所有出现并返回它们。

因为第一个 Erlang 练习是为了让我们熟悉该语言的基本概念,这意味着我们应该通过使用递归而不是 list:max 之类的东西来解决这些任务。

我能够为之前的任务实现一个工作函数,它只是在键值对元组列表中搜索一个键并返回第一个结果。那个实现看起来很容易,但是为了扩展这个任务,我尝试了很多没有用的东西,我什至不知道接下来要尝试什么。

目前我尝试了这种方法:

find_all(Keys, Values) ->
  AllFindings = [],
  lists:foreach(
    fun(Key) ->
      lists:foreach(
        fun(Value) ->
          {X, Y} = Value,
          case X == Key of
            true -> AllFindings:append(Value);
            false -> []
          end
        end,
        Values
      )
    end,
    Keys
  ),
  AllFindings.

这样做的问题是,要么我需要做一些事情,比如将值附加到最初创建的列表中(这给了我这个错误:Warning: invalid module and/or function name; this call will always fail,而且我不确定这是否可能以这种方式我打算让它工作,因为它需要变量 AllFindings 来改变它的值)或者我需要一种方法来保存这些值以供以后使用,这样我就可以在稍后将所有值放在一起时将它们全部输出。

但我不确定如何正确实现这一目标。

我之前尝试实现的方法是这样的,使用递归,但没有按照我想要的方式工作(此版本中的一些值输出仅用于“调试”以查看变量函数处于什么状态的哪个值):

find_all(Keys = [KeyHead | KeyTail], Values = [ValueHead | ValueTail]) ->
  Tuples = [X || X = {KeyHead, Y} <- [ValueHead]],
  Tuples,
  ValueTail,
  case Tuples /= [] of
    true -> Tuples
  end,
  case ValueTail /= [] of
    true -> find_all(Keys, ValueTail);
    false ->
      case KeyTail /= [] of
        true -> find_all(KeyTail, Values);
        false -> find_all(KeyTail, ValueTail)
      end
  end.

还有:

find_all([], []) -> [];
find_all([KeyHead | KeyTail], [ValueHead | ValueTail]) ->
  case ValueHead of
    {KeyHead, V} -> V;
    {_, V} -> find_all(KeyTail, ValueHead);
    _ -> find_all(KeyHead, ValueTail)
  end.

我真的很感谢任何关于解决这个问题的建议,无论是通过建议一些代码还是通过将我指向相应的文献,因为对我而言,关于 Erlang 的文献/论坛似乎非常稀少且难以找到(尤其是与流行的语言,如 Java 或 Python)。到目前为止,我还在阅读“Learn You Some Erlang”,但没有遇到任何我认为它可能有助于解决这个问题的特定部分。

编辑

我现在想出了这段代码:

find_all(Keys, Values) ->
  while(Keys, Values).

while([], []) -> [];
while(Keys = [KeyHead | KeyTail], Values = [ValueHead | ValueTail]) ->
  NumberOfKeys = length(Keys),
  LengthOfValues = length(Values),
  {K, V} = ValueHead,
  erlang:display(Keys), erlang:display(Values),
  case NumberOfKeys > 1 of
    true -> case LengthOfValues > 1 of
              true -> case K =:= KeyHead of
                        true -> [ValueHead | find_all(Keys, ValueTail)];
                        false -> [find_all(Keys, ValueTail)]
                      end;
              false -> case K =:= KeyHead of
                         true -> [ValueHead];
                         false -> []
                       end
            end;
    false -> case LengthOfValues > 1 of
               true -> case K =:= KeyHead of
                         true -> [ValueHead | find_all(Keys, ValueTail)];
                         false -> [find_all(Keys, ValueTail)]
                       end;
               false -> case K =:= KeyHead of
                          true -> [ValueHead];
                          false -> []
                        end
             end
  end,
  while(KeyTail, Values).

在我看来,它看起来很有希望,因为它的较小版本已经为这个函数调用 warmup:find_all([d, x, c], [{c, 5}, {z, 7}, {d, 3}, {a, 1}]). 返回 {d, 3}。当使用erlang:display() 调试不同的值时,我可以看到它在第一个键上循环了4 次,并且还将ValueTail 减少到最后一个值,然后转到下一个键。但是我很困惑为什么Values 仍然只包含最后一个值{a, 1},因为我认为递归会回到它的调用的顶层,其中列表仍然应该包含所有值?

【问题讨论】:

  • 请描述(举例)应该输入什么,应该输出什么以及为什么不能从输入输出

标签: erlang tuples key key-value


【解决方案1】:

您可以尝试使用列表生成器在列表中查找元组。

  • 在简单列表中查找具有键/值的元组:
1> [X || {_, _} = X <- [{a, 1}, 1, [2], {b, 2}, {c, 3}, 4]].
[{a,1},{b,2},{c,3}]
  • 在嵌套列表中查找元组:
1> Data = [[d, x, c], [{c, 5}, {z, 7}, {d, 3}, {a, 1}, 1, [1, 2, {x, 1}, {j, 1}]]].
[[d,x,c],[{c,5},{z,7},{d,3},{a,1},1,[1,2,{x,1},{j,1}]]]
2> [X || {_, _} = X <- lists:flatten(Data)].
[{c,5},{z,7},{d,3},{a,1},{x,1},{j,1}]
  • 在不绑定嵌套列表中元组元素数量的情况下查找元组:
1> Data = [[d, x, c], [{c, 5, 5}, {z, 7}, {d, 3, 3}, {a, 1}, 1, [1, 2, {x, 1, 1}, {j, 1}]]].
[[d,x,c],
 [{c,5,5},{z,7},{d,3,3},{a,1},1,[1,2,{x,1,1},{j,1}]]]
2> [X || X <- lists:flatten(Data), is_tuple(X)].
[{c,5,5},{z,7},{d,3,3},{a,1},{x,1,1},{j,1}]

【讨论】:

    【解决方案2】:

    这个问题很长,所以为了清楚起见,这里是问题陈述:编写一个函数,它接受一个键值对元组列表和一个键列表,并使用递归返回一个键匹配的每个对的列表任何给定的键。鉴于这个问题陈述,我们可以编写模块的顶部——我们称之为keyfinder——来导出一个find/2函数:

    -module(keyfinder).
    -export([find/2]).
    

    现在,让我们考虑一些琐碎的案例:

    1. 空对列表:返回一个空列表。
    2. 空键列表:返回一个空列表。

    我们可以用模式匹配来写这两种情况:

    find([], _) -> []; % no pairs
    find(_, []) -> []; % no keys
    

    接下来,让我们考虑剩下的情况,我们有对和键:给定 n 个键,我们必须搜索对列表 n 次,并保留每个我们找到的匹配。要跟踪匹配,我们可以使用累加器列表,从空开始。也许我们可以为此使用find/3,其中额外的参数是累加器:

    find(Pairs, Keys) ->
        find(Pairs, Keys, []).
    

    我们希望find/3 递归调用自身以查找所有匹配项,因此让我们考虑find/3 必须处理的情况:

    1. pairs 列表头部的 key 与 keys 列表头部的 key 匹配: 将 pair 添加到累加器中并递归搜索其余的 pair 以获得相同的 key。
    2. pairs 列表头部的 key 与 keys 列表头部的 key 不匹配: 递归地在其余的 pair 中搜索相同的 key。
    3. 键列表为空:返回累加器。
    4. pairs 列表是空的: 我们的递归最终通过遍历pairs 列表到达这里;递归地在整个对列表中搜索剩余的每个键。

    在上面的最后一种情况下,我们的递归可能导致我们检查了所有对,从而清空了我们的对列表,但还有更多的键要搜索;这意味着我们需要将原始配对列表保留在某个地方,以便使用下一个键重新开始搜索。一种方法是添加另一个参数,即原始对列表:

    find(Pairs, Keys) ->
        find(Pairs, Keys, Pairs, []).
    

    这使得我们的递归函数 find/4 代替了 find/3,并且我们将原始对列表原封不动地传递给每个 find/4 调用。

    find/4 处理上述四种情况:

    %% We exhausted the list of keys, so return the results.
    find(_, [], _, Results) -> Results;
    
    %% We exhausted the list of pairs, so search for the rest of the keys.
    find([], [_|Keys], OriginalPairs, Results) ->
        find(OriginalPairs, Keys, OriginalPairs, Results);
    
    %% Our pair matches our key, so add the pair to the accumulator and continue the search.
    find([{Key,_}=Pair|Pairs], [Key|_]=Keys, OriginalPairs, Results) ->
        find(Pairs, Keys, OriginalPairs, [Pair|Results]);
    
    %% No match, continue the search.
    find([_|Pairs], Keys, OriginalPairs, Results) ->
        find(Pairs, Keys, OriginalPairs, Results).
    

    最有趣的例子是第三个子句,我们在函数头中使用模式匹配来匹配对中的键与键列表头部的键。当匹配发生时,我们对find/4 的递归调用会传递一个新的累加器,该累加器由新找到的对组成,作为新累加器的头部,原始累加器作为尾部。该函数子句和最后一个子句都使用对列表的尾部作为递归 find/4 调用的第一个参数。

    完整的模块:

    -module(keyfinder).
    -export([find/2]).
    
    find([], _) -> [];
    find(_, []) -> [];
    find(Pairs, Keys) ->
        find(Pairs, Keys, Pairs, []).
    
    find(_, [], _, Results) -> Results;
    find([], [_|Keys], OriginalPairs, Results) ->
        find(OriginalPairs, Keys, OriginalPairs, Results);
    find([{Key,_}=Pair|Pairs], [Key|_]=Keys, OriginalPairs, Results) ->
        find(Pairs, Keys, OriginalPairs, [Pair|Results]);
    find([_|Pairs], Keys, OriginalPairs, Results) ->
        find(Pairs, Keys, OriginalPairs, Results).
    

    让我们编译它并在 Erlang shell 中尝试一下:

    1> c(keyfinder).
    c(keyfinder).
    {ok,keyfinder}
    2> keyfinder:find([],[]).
    keyfinder:find([],[]).
    []
    3> keyfinder:find([{a,1}],[]).
    keyfinder:find([{a,1}],[]).
    []
    4> keyfinder:find([],[a]).
    keyfinder:find([],[a]).
    []
    5> keyfinder:find([{a,1}],[a]).
    keyfinder:find([{a,1}],[a]).
    [{a,1}]
    6> keyfinder:find([{a,1},{a,2}],[a]).
    keyfinder:find([{a,1},{a,2}],[a]).
    [{a,2},{a,1}]
    7> keyfinder:find([{a,1},{a,2}],[a,b]).
    keyfinder:find([{a,1},{a,2}],[a,b]).
    [{a,2},{a,1}]
    8> keyfinder:find([{a,1},{b,2}],[a,b]).
    keyfinder:find([{a,1},{b,2}],[a,b]).
    [{b,2},{a,1}]
    9> keyfinder:find([{a,1},{b,2},{c,3}],[a,b]).
    keyfinder:find([{a,1},{b,2},{c,3}],[a,b]).
    [{b,2},{a,1}]
    10> keyfinder:find([{a,1},{b,2},{c,3}],[a,b,c,d,e]).
    keyfinder:find([{a,1},{b,2},{c,3}],[a,b,c,d,e]).
    [{c,3},{b,2},{a,1}]
    

    似乎按预期工作。

    请注意,结果列表的顺序是从找到的最后一个匹配项到第一个匹配项,这是因为我们将每个结果都添加到累加器列表中。如果您更喜欢倒序,并且如果您被允许使用lists 模块,您可以更改find/4 的第一个子句以在返回之前反转结果:

    find(_, [], _, Results) -> lists:reverse(Results);
    

    如果您不允许使用 lists 模块,那么您可以通过将每一对附加到累加器列表来避免反转结果的需要:

    find([{Key,_}=Pair|Pairs], [Key|_]=Keys, OriginalPairs, Results) ->
        find(Pairs, Keys, OriginalPairs, Results++[Pair]);
    

    但请注意,这比预先添加的效率要低一些。

    【讨论】:

    • 感谢您提供如此详细而深入的答案,这可能是我在 SO 上见过的最详细的答案。这个解决方案完美无缺,我可以轻松地按照您解释的每个步骤进行操作,并认为它肯定有助于我更好地理解 erlang 的结构。非常感谢您花费大量时间(很可能已经花费了这么多时间)来帮助我!
    猜你喜欢
    • 2022-01-06
    • 2015-07-06
    • 2016-12-21
    • 2011-01-12
    • 1970-01-01
    • 1970-01-01
    • 2016-07-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多