Найти все кортежи в списке кортежей - PullRequest
1 голос
/ 01 мая 2020

Я учусь во втором семестре магистра по информатике и прохожу курс по программированию распределенных систем. Поэтому мы должны представлять еженедельные упражнения, которые также включают упражнения по кодированию на эрланге.

Поскольку это уже вторая неделя курса, мы только начали с Эрланга, и это первое упражнение, где мы должны реализовать 6 функций в модуле. Первые 5 функций я мог бы легко выполнить самостоятельно, однако я полностью перегружен 6-й. Для этого мы должны написать функцию, которая принимает 2 входа: список кортежей, представляющих пары ключ-значение, и список, содержащий ключи для поиска. Затем предполагается, что функция будет искать во всем списке все вхождения этих ключей и возвращать их.

Поскольку это первое упражнение на Erlang предназначено для ознакомления с основными понятиями языка c Это означает, что мы должны решать эти задачи, а не использовать рекурсию вместо чего-то вроде списков: макс.

Я смог реализовать работающую функцию для предыдущей задачи, которая просто выполняла поиск в списке ключей. Значение пары кортежей для одного ключа и возвращено первый результат. Реализация этого показалась довольно простой, но для расширения этой задачи я попробовал так много вещей, которые не сработали, что я даже не знаю, что попробовать дальше.

В настоящее время я экспериментировал с этим подходом:

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). До сих пор я также читаю «Узнай немного Эрланга», но не нашел какой-то конкретной части, где я думал, что это может помочь в решении этой проблемы.

РЕДАКТИРОВАТЬ

Я сейчас придумал этот фрагмент кода:

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).

На мой взгляд, это выглядит многообещающе, так как уменьшенная версия уже возвращает {d, 3} для этого вызова функции warmup:find_all([d, x, c], [{c, 5}, {z, 7}, {d, 3}, {a, 1}]).. При отладке с помощью erlang:display() для различных значений я мог видеть, что он зацикливается на первом ключе 4 раза, а также уменьшает ValueTail до последнего значения, а затем переходит к следующему ключу. Однако я запутался, почему тогда Values все еще содержит только последнее значение {a, 1}, поскольку я думал, что рекурсия возвращается к верхнему уровню своих вызовов, где список все еще должен содержать все значения?

Ответы [ 2 ]

3 голосов
/ 02 мая 2020

Вопрос длинный, поэтому для ясности, вот формулировка проблемы: напишите функцию, которая принимает список пар пары ключ-значение и список ключей и, используя рекурсию, возвращает список каждой пары, чей ключ соответствует любой из указанных ключей. Учитывая это постановку задачи, мы можем написать верхнюю часть нашего модуля - назовем его 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. Ключ заголовка списка пар совпадает с ключом в заголовке списка ключей: добавьте пару в аккумулятор и рекурсивно ищите остальные пары для одного и того же ключа.
  2. Ключ заголовка списка пар не совпадает с ключом в заголовке списка ключей: рекурсивно ищет остальные пары для того же ключа.
  3. Список ключей пуст: возвращает аккумулятор.
  4. Список пар пуст: наша рекурсия в конечном итоге прибывает сюда пройдясь по списку пар; рекурсивно ищите весь список пар для каждого из оставшихся ключей.

В последнем случае выше, наша рекурсия может привести к случаю, когда мы проверим все пары, таким образом опустошив наш список пар, но есть еще ключи для поиска; это означает, что нам нужно где-то сохранить исходный список пар, чтобы возобновить поиск с помощью следующего ключа. Один из способов сделать это - добавить еще один аргумент, который является исходным списком пар:

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:

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]);

Обратите внимание, что это немного менее эффективно, чем предваряющий, хотя.

0 голосов
/ 03 мая 2020

Вы можете попробовать использовать генераторы списков для поиска кортежей в списках.

  • Найти кортежи с ключом / значением в простых списках:
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}]

...