Сравните два списка карт и верните новый преобразованный список (эликсир) - PullRequest
0 голосов
/ 29 июня 2019

У меня есть два списка карт а и б.

a = [
%{"school" => "a", "class" => 1, "student" => "jane doe"},
%{"school" => "b", "class" => 9, "student" => "jane1 doe"},
%{"school" => "c", "class" => 6, "student" => "jane doe2"}
]

b = [
%{"choice" => "arts", "class" => 1, "school" => "a"},
%{"choice" => "science", "class" => 9, "school" => "a"},
%{"choice" => "maths", "class" => 6, "school" => "b"}
]

Я хочу иметь возможность сравнить два списка и создать список с элементами следующей структуры

desired_result = [
%{
"school" => "a",
"class" => 1,
"student" => "jane doe" or nil (if student exists only in list b but not in a),
"choices" => ["arts"] or [] (if student exists only in list a but not in b),
"is_common" => yes(if the student exists in both lists) OR only list a OR only list b
}
]

Я пытался использовать Enum.into и Enum.member? функции, и я смог достичь 60% решения, которое я хочу.

Enum.into(a, [], fn item ->
      if Enum.member?(b, %{
        "school" => item["school"],
        "class" => item["class"]
      }) do
        %{
       "school" => item["school"],
       "class" => item["class"],
       "student" => item["student"],
       "choices" => [],
       "is_common" => "yes"
     }
   else
     %{
       "school" => item["school"],
       "class" => item["class"],
       "student" => item["student"],
       "choices" => [],
       "is_common" => "only list a"
     }
   end
    end)

Проблема с вышесказанным заключается в том, что он охватывает случаи общих в обоих списках и те, которые есть только в списке а; но он не охватывает те, которые есть только в списке б. Кроме того, я не смог найти способ получить значение выбора в моем конечном результате из списка b (как вы можете видеть, я оставил значение «choice» как []). Как охватить все три случая и получить список в нужной структуре со значениями?

Ответы [ 2 ]

1 голос
/ 30 июня 2019

Давайте начнем с получения голого результата из того, что у вас есть. Я предполагаю, что пара school + class - это то, что определяет уникальность.

[a, b]
|> Enum.map(fn list ->
  Enum.group_by(list, & {&1["class"], &1["school"]})
end)
|> Enum.reduce(
  &Map.merge(&1, &2, fn _, [v1], [v2] -> [Map.merge(v1, v2)] end))
|> Enum.map(fn {_, [v]} -> v end)
#⇒ [
#   %{"choice" => "arts", "class" => 1, "school" => "a", "student" => "jane doe"},
#   %{"choice" => "maths", "class" => 6, "school" => "b"},
#   %{"class" => 6, "school" => "c", "student" => "jane doe2"},
#   %{"choice" => "science", "class" => 9, "school" => "a"},
#   %{"class" => 9, "school" => "b", "student" => "jane1 doe"}
# ]

Не стесняйтесь запускать вышеуказанное предложение за предложением, чтобы просмотреть все связанные преобразования.

Приведенный выше список гарантирует уникальность %{"school" => any(), "class" => any()} среди элементов списка. Теперь просто итерируйте и обновляйте элементы в соответствии с вашими потребностями.

0 голосов
/ 30 июня 2019

Я пойду с другим подходом, пытаясь пройти через оба списка, используя хвостовую рекурсию.

Чтобы использовать этот подход, мы должны гарантировать, что оба списка a и b будут упорядочены по полям, которые позволяют нам сопоставить, в данном случае school и class.

Это необходимо, потому что во время хвостовой рекурсии мы будем сопоставлять списки на лету, и обязательно будет гарантировать, что если мы оставляем несопоставленный элемент a, невозможно найти совпадение b позже

# With this both lists will be ordered  ascendently  by school and class fields. 
ordered_a = Enum.sort(a,  &((&1["school"] < &2["school"]) || (&1["class"] <= &2["class"] )))
ordered_b = Enum.sort(b,  &((&1["school"] < &2["school"]) || (&1["class"] <= &2["class"] )))

При этом оба списка будут упорядочены по возрастанию school и классу fields.

Пойдемте с трудной частью. Теперь нам нужно подумать о прохождении двух упорядоченных списков. Рекурсия будет выполнена через функцию match_lists.

У нас может быть 6 возможных комбинаций заголовков:

  1. [MATCH] Поля school и class в Head обоих списков совпадают, поэтому они совпадают. В этом случае мы создаем новый элемент и добавляем его в аккумулятор. При следующем вызове мы просто передаем хвост обоих списков.
  2. [UNMATCHED B] Head элемент a впереди Head элемент b, это поле school (или поле class, если school то же самое) имеет большее значение , Это означает, что для текущего Head элемента списка b нет совпадений, так как список a уже впереди. Таким образом, непревзойденный элемент b будет построен и добавлен в аккумулятор. При следующем вызове мы только что прошли хвост b, но полный a list.
  3. [UNMATCHED A] То же, что в пункте 2, но относительно списка a. Элемент Head списка b опережает элемент Head списка a. Это означает, что для элемента Head в a нет соответствия, поскольку Head в b уже впереди. Непревзойденный элемент a будет построен и добавлен в аккумулятор.
  4. [UNMATCHED B] Список a пуст. Непревзойденный B будет сгенерирован с Head из b и добавлен в аккумулятор.
  5. [UNMATCHED A] Список b пуст. Непревзойденный A будет сгенерирован с Head из a и добавлен в аккумулятор.
  6. [END] Оба списка пусты. Рекурсия закончилась, и аккумулятор будет возвращен.
def match_lists(a, b, acc \\ [] )

# Case: Element in both lists  
def match_lists(
      [%{"school" => school, "class" => class, "student" => student} | rest_a],
      [%{"school" => school, "class" => class, "choice" => choice} | rest_b],
      acc
    ) do
  element = build(school, class, student, [choice], true)
  match_lists(rest_a, rest_b, [element | acc])
end

# Case: Element only in list B case. So it is a B case
def match_lists(
      [%{"school" => school_a, "class" => class_a} | _] = a,
      [%{"school" => school_b, "class" => class_b, "choice" => choice} | rest_b],
      acc
    )
    when school_a > school_b or class_a > class_b do
  element = build(school_b, class_b, nil, [choice], "only_list_b")
  match_lists(a, rest_b, [element | acc])
end

# Case: No more elementes in A. So It is a B case
def match_lists([], [%{"school" => school, "class" => class, "choice" => choice} | rest_b], acc) do
  element = build(school, class, nil, [choice], "only_list_b")
  match_lists([], rest_b, [element | acc])
end

# Case: Element only in list A
def match_lists(
      [%{"school" => school_a, "class" => class_a, "student" => student} | rest_a],
      [%{"school" => school_b, "class" => class_b} | _] = b,
      acc
    )
    when school_b > school_a or class_b > class_a do
  element = build(school_a, class_a, student, [], "only_list_a")
  match_lists(rest_a, b, [element | acc])
end

# Case: No more elementes in B. So It is an uncommon A case
def match_lists([%{"school" => school, "class" => class, "student" => student} | rest_a], [], acc) do
  element = build(school, class, student, [], "only_list_a")
  match_lists(rest_a, [], [element | acc])
end

def match_lists([], [], acc) do
  acc
end

defp build(school, class, student, choices, is_common) do
  %{
    "school" => school,
    "class" => class,
    "student" => student,
    "choices" => choices,
    "is_common" => is_common
  }
end
iex(1)> match_lists(ordered_a, ordered_b)
[
  %{
    "choices" => [],
    "class" => 6,
    "is_common" => "only_list_a",
    "school" => "c",
    "student" => "jane doe2"
  },
  %{
    "choices" => [],
    "class" => 9,
    "is_common" => "only_list_a",
    "school" => "b",
    "student" => "jane1 doe"
  },
  %{
    "choices" => ["maths"],
    "class" => 6,
    "is_common" => "only_list_b",
    "school" => "b",
    "student" => nil
  },
  %{
    "choices" => ["science"],
    "class" => 9,
    "is_common" => "only_list_b",
    "school" => "a",
    "student" => nil
  },
  %{
    "choices" => ["arts"],
    "class" => 1,
    "is_common" => true, 
    "school" => "a",
    "student" => "jane doe"
  }
]

Надеюсь, это поможет.

...