从Erlang列表中移除倒数第二个元素的方法

huangapple go评论46阅读模式
英文:

How to remove the next to last element from the list in Erlang

问题

我有一个任务:将列表的第二个和第五个元素添加到列表的末尾,并删除倒数第二个元素。我需要在不使用lists模块的情况下完成这个任务,只使用递归。

我有一段代码,可以通过索引找到元素,从而解决了找到第二个和第五个元素的问题(对我来说也不是理想的解决方案,因为它使用了相同的lists:nth函数)。

-module(task).

-export([remove_and_add/1]).

remove_and_add(List) ->
    remove_penultimate(List) ++ [nth(2, List)] ++ [nth(5, List)].

nth(1, [H|_]) ->
    H;
nth(N, [_|T]) ->
    nth(N - 1, T).

但我不知道如何在没有列表长度的情况下删除倒数第二个元素(如何实现remove_penultimate函数)。

英文:

I have a task: "Add second and fifth elements of list to the end of the list and remove the penultimate element". I need to do this without using the module lists, just using recursion.

I have code which finds an element by index, which solves the problem of finding the second and fifth element (also not an ideal solution for me, because it's the same lists:nth function).

-module(task).

-export([remove_and_add/1]).

remove_and_add(List) ->
    remove_penultimate(List) ++ [nth(2, List)] ++ [nth(5, List)].

nth(1, [H|_]) ->
    H;
nth(N, [_|T]) ->
    nth(N - 1, T).

But I don't understand how to remove the penultimate element without a length (how to implement remove_penultimate function).

答案1

得分: 3

remove_penultimate(List) ++ [nth(2, List)] ++ [nth(5, List)].

只有中级到高级的 Erlang 程序员才知道如何正确使用 `++`。我建议你完全忽略它,在你的代码中不要使用它。

如果你不能使用 lists 模块,那么每个初学者需要编写的第一个函数是一个 `reverse(List)` 函数。除了是最有用的函数之一外,它还会教会你如何向函数的参数变量中添加一个“累加器”变量,以存储你想要的任何数据。这是一个例子:

    go(List) ->
        go(List, []).

    go([H|T], Acc) ->  %% Acc 从一个空列表开始,可以用来存储数据
        %% 在这里可能做一些操作
        go(T, [H|Acc]);  %% 在 Acc 列表中存储数据
    go([], Acc) -> Acc.

你可以使用上面的技巧向函数的参数中添加尽可能多的变量,例如:

    go(List) ->
        go(List, 1, none, none, []).

    go([H|T], N, X, Y, Acc) -> ...

检查这个函数:

    show_previous(List) ->
        show_previous(List, none).

    show_previous([], Prev) ->  %% 函数的参数中添加了一个名为 Prev 的变量,并且它从 none 开始。
        io:format("current: end of list, previous: ~w~n", [Prev]);
    show_previous([Last], Prev) ->  %% 这个子句只匹配一个元素的列表。
        io:format("current: ~w, penultimate: ~w~n", [Last, Prev]),
    show_previous([H|T], Prev) ->
        io:format("current: ~w, prev: ~w~n", [H, Prev]),
        show_previous(T, H).

请注意,在 Erlang 中,具有相同名称但参数变量数量不同(称为函数的“arity”)的函数是完全不同的函数,所以 `show_previous/1`  `show_previous/2` 是完全不同的函数。

下面是另一个你可以做的例子:

    show([]) ->
        io:format("!No more elements!~n");
    show([X, Y]) ->  %% 只匹配具有两个元素的列表
        io:format("penultimate: ~w, last: ~w~n", [X, Y]),
        show([Y]);
    show([H|T]) ->
        io:format("current: ~w~n", [H]),
        show(T).  %% 通过一次减少一个元素的列表,
                  %% 列表最终会变成一个有两个元素的列表

最后,这是一个展示 Erlang 中模式匹配有多强大的例子:

    show(List) ->
        show(List, 1).

    show([H|T], 3) ->
        io:format("The third element in the list is: ~w~n", [H]),
        show(T, 4);
    show([H|T], Index) ->
        io:format("The element at index positon ~w is ~w~n", [Index, H]),
        show(T, Index+1);
    show([], _Index) ->
        ok.

 Erlang 中,函数的参数可以包含常量,比如 `3` 或原子 `hello` 或列表 `[1, 2, 3]`,而在像 Python  Ruby 这样的语言中,函数中的所有参数必须是变量。

> 但我不明白如何在没有长度的情况下去除倒数第二个元素。

你可以通过对所有元素进行递归并计数来获得长度。编写自己的 `length()` 函数是很好的练习。它非常简单。但请记住,尽可能少地对列表进行递归更有效率,你应该能够通过一次递归列表,然后对列表进行翻转(这需要再次对列表进行递归)来解决原始问题。

另一种去除倒数第二个元素的方法是先翻转列表,然后问自己,“如何移除列表的第二个元素?”

我想出了一种方法,它在原始列表上进行了一次遍历,当到达列表末尾时,它翻转了 `Acc` 列表并将其返回。它将这个列表:

    [1, 2, 3, 4, 5, 6, 7]

转换为这个列表:

    [1, 3, 4, 7, 2, 5]

我会将这个操作描述为从列表中移除倒数第二个元素,并将第二个和第五个元素移到列表的末尾。复制第二个和第五个元素并将这些副本添加到列表末尾可以以类似的方式解决。 
英文:
    remove_penultimate(List) ++ [nth(2, List)] ++ [nth(5, List)].

Only intermediate to advanced erlang programmers know how to use ++ correctly. I suggest you completely ignore it and omit using it in any of your code.

If you can't use the lists module, then the first function every beginner needs to write is a reverse(List) function. Beside being one of the most useful functions, that will teach you the trick of how to add an "accumulator" variable to a function's parameter variables to store whatever data you want in it. Here is an example:

    go(List) ->
go(List, []).
go([H|T], Acc) ->  %%  Acc starts off as a blank list, which can be used to store data
%% maybe do something here
go(T, [H|Acc]);  %% storing data in the Acc list
go([], Acc) -> Acc.

You can add as many variables as you need to a function's parameters using the trick above, for instance:

go(List) ->
go(List, 1, none, none, []).
go([H|T], N, X, Y, Acc) -> ...

Examine this function:

    show_previous(List) ->
show_previous(List, none).
show_previous([], Prev) ->  %% A variable called Prev has been added to the function's parameters, and it starts off with the value none.
io:format("current: end of list, previous: ~w~n", [Prev]);
show_previous([Last], Prev) ->  %% This clause only matches a list with one element.
io:format("current: ~w, penultimate: ~w~n", [Last, Prev]),
show_previous([H|T], Prev) ->
io:format("current: ~w, prev: ~w~n", [H, Prev]),
show_previous(T, H).

Note that in erlang, a function with the same name but with a different number of parameter variables (known as the "arity" of a function) is a completely different function, so show_previous/1 and show_previous/2 are completely different functions.

Here's another example of what you can do:

show([]) ->
io:format("!No more elements!~n");
show([X, Y]) ->  %% only matches a list with two elements
io:format("penultimate: ~w, last: ~w~n", [X, Y]),
show([Y]);
show([H|T]) ->
io:format("current: ~w~n", [H]),
show(T).  %% By whittling the list down one element at a time,
%% the list will eventually become a 2 element list

Finally, here is an example that shows how powerful pattern matching is in erlang:

show(List) ->
show(List, 1).
show([H|T], 3) ->
io:format("The third element in the list is: ~w~n", [H]),
show(T, 4);
show([H|T], Index) ->
io:format("The element at index positon ~w is ~w~n", [Index, H]),
show(T, Index+1);
show([], _Index) ->
ok.

In erlang, a function's parameters can contain constants, like 3 or the atom hello or the list [1, 2, 3], while in languages like python or ruby, all the parameters in a function must be variables.

> But I don't understand how to remove the penultimate element without a length.

You could always get the length by recursing over all the elements and counting them. It would be good practice to write your own length() function. It's very simple. Remember though, it's more efficient to recurse over a list as few times as possible, and you should be able to solve your original problem by recursing over the list once, then reversing the list (which requires recursing over the list again).

Another possible approach to removing the penultimate element is to reverse the list, then ask yourself, "How do I remove the second element of a list?"

I came up with a solution that traverses the original list once, and when it comes to the end of the list, it reverses the Acc list and returns it. It takes this list:

 [1, 2, 3, 4, 5, 6, 7]

and returns this list:

 [1,3,4,7,2,5]

I would describe that operation as removing the penultimate element in the list and moving the 2nd and 5th elements to the end of the list. Copying the 2nd and 5th elements and adding the copies to the end of the list would be solved in a similar fashion.

huangapple
  • 本文由 发表于 2023年2月19日 19:54:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/75499954.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定