【问题标题】:Choosing unique items from a list, using recursion使用递归从列表中选择唯一项
【发布时间】:2013-03-15 08:29:36
【问题描述】:

作为昨天问题的后续Erlang: choosing unique items from a list, using recursion

在 Erlang 中,假设我想从给定列表中选择所有 唯一 项,例如

List = [foo, bar, buzz, foo].

我使用了你的code examples 导致

NewList = [bar, buzz].

我将如何在 Erlang 中进一步操作 NewList

例如,假设我不仅要从List 中选择所有唯一项,还要计算NewList 中所有结果项的字符总数?

【问题讨论】:

  • 感谢您到目前为止的回答。但是,我不想再次手动输入列表,即我对字符计数功能(这是次要的)不太感兴趣,但更多的是我如何“继承”使原始列表唯一的结果到自动使用结果进行进一步的操作,无论是计数字符还是其他。谢谢
  • 嗯,NewList 是一个变量,使用它就可以了。你能详细说明一下吗?
  • 当然。假设我的列表是 List = [foo, bar, buzz, foo]。我想要总数。所有独特项目的字符。进一步假设我使用了 CODE uniques(L) -> uniques(L, [], [])。独特的([],_,Acc)->列表:反向(Acc); uniques([X | Rest], Seen, Acc) -> case lists:member(X, Seen) of true -> uniques(Rest, Seen, lists:delete(X, Acc));假 -> 独特(休息,[X | 看到],[X | Acc])结束。 CODE 所以得到独特的物品。我将如何修改此代码 smippet 以获得否。字符数(仅作为示例)?
  • uniques_and_chars(L)-> Uniques = uniques(L), num_chars(Uniques)。

标签: recursion erlang


【解决方案1】:

在函数式编程中,我们有频繁出现的模式,它们应该有自己的名称和支持函数。使用最广泛的两个是mapfold(有时是reduce)。这两个构成了列表操作的基本构建块,通常无需编写专用的递归函数。

地图

map 函数按顺序遍历列表,生成一个新列表,其中每个元素都是将函数应用于原始列表中相应元素的结果。以下是典型的map 的实现方式:

map(Fun, [H|T]) -> % recursive case
    [Fun(H)|map(Fun, T)];
map(_Fun, []) -> % base case
    [].

这是递归函数的完美介绍性示例;粗略地说,函数子句要么是递归情况(导致调用自身具有较小的问题实例)或基本情况(没有递归拨打电话)。

那么你如何使用map?请注意,第一个参数Fun 应该是一个函数。在 Erlang 中,可以内联声明匿名函数(有时称为 lambdas)。例如,对列表中的每个数字求平方,生成一个平方列表:

map(fun(X) -> X*X end, [1,2,3]). % => [1,4,9]

这是Higher-order programming 的示例。

请注意,map 是 Erlang 标准库的一部分,名称为 lists:map/2

折叠

map 在一个列表和另一个列表之间创建 1:1 元素映射,fold 的目的是在累积单个结果(例如总和)的同时将某些函数应用于列表的每个元素。正确的折叠(将其视为“向右”会有所帮助)可能如下所示:

foldr(Fun, Acc, [H|T]) -> % recursive case
    foldr(Fun, Fun(H, Acc), T);
foldr(_Fun, Acc, []) -> % base case
    Acc.

使用这个函数,我们可以对列表的元素求和:

foldr(fun(X, Sum) -> Sum + X, 0, [1,2,3,4,5]). %% => 15

请注意,foldrfoldl 都是 Erlang 标准库的一部分,在 lists 模块中。

虽然可能不是很明显,但可以单独使用 mapfold 解决一大类常见的列表操作问题。

递归思考

一开始编写递归算法可能会让人望而生畏,但当你习惯它时,它就会变得很自然。遇到问题时,您应该确定两件事:

  1. 如何将问题分解为更小的实例?为了使递归有用,递归调用必须将较小的问题作为其参数,否则函数将永远不会终止。
  2. 什么是基本情况,即终止标准?

至于1),考虑计算一个列表的元素的问题。这怎么可能分解成更小的子问题呢?好吧,这样想:给定一个非空列表,它的第一个元素(头)是 X,余数(尾)是 Y,它的长度是 1 + Y 的长度。由于 Y 小于列表 [X |Y],我们已经成功地解决了这个问题。

继续列表示例,我们什么时候停止?好吧,最终,尾巴将是空的。我们回到基本情况,即空列表的长度为零的定义。您会发现为各种情况编写函数子句非常类似于为字典编写定义:

%% Definition:
%% The length of a list whose head is H and whose tail is T is
%% 1 + the length of T.
length([H|T]) ->
    1 + length(T);
%% Definition: The length of the empty list ([]) is zero.
length([]) ->
    0.

【讨论】:

  • 精湛的解释。非常感谢。正是我需要的。
  • 试图将您的指导应用于特定示例,但仍然有点不确定:假设我的列表是List = [a, b, c, a].,我需要总数。仅限所有独特项目的字符。进一步假设我现在使用uniques(L) -> uniques(L, [], []). uniques([], _, Acc) -> lists:reverse(Acc); uniques([X | Rest], Seen, Acc) -> case lists:member(X, Seen) of true -> uniques(Rest, Seen, lists:delete(X, Acc)); false -> uniques(Rest, [X | Seen], [X | Acc]) end. 来获取独特的物品。我将如何更改此代码 smippet 以获得否。字符(仅作为示例)?非常非常感谢
  • 嗯,uniques 的输出是另一个列表,对吧?将该列表作为输入传递给下面的 William Cummings 建议的折叠。 :) 结合lists 中的功能可以非常强大。您可以将其视为从一个 Unix 命令到另一个命令的管道输出。
【解决方案2】:

您可以使用折叠对结果列表进行递归。为简单起见,我将您的原子转换为字符串(您可以使用 list_to_atom/1 执行此操作):

1> NewList = ["bar", "buzz"].
["bar","buzz"]
2> L = lists:foldl(fun (W, Acc) -> [{W, length(W)}|Acc] end, [], NewList).        
[{"buzz",4},{"bar",3}]

这会返回一个 proplist,您可以像这样访问:

3> proplists:get_value("buzz", L).
4

【讨论】:

  • 请注意,您正在使用 fold 重新发明 map,因为您正在获取一个列表并使用 1:1 元素映射构建一个新列表。
【解决方案3】:

如果您想自己构建递归而不是使用列表:

count_char_in_list([], Count) ->
    Count;
count_char_in_list([Head | Tail], Count) ->
    count_char_in_list(Tail, Count + length(Head)). % a string is just a list of numbers

然后:

1> test:count_char_in_list(["bar", "buzz"], 0).
7

【讨论】:

    猜你喜欢
    • 2013-03-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-12
    • 2020-06-23
    • 1970-01-01
    • 2012-01-24
    • 1970-01-01
    相关资源
    最近更新 更多