4

I have two lists

l1 = {{{1,2},1},{{3,4},2},{{1,3},3}};
l2 = {{{1,2},2},{{3,5},2},{{1,3},3}};

My goal is to have a function that, if the first element of an element of l1 is the same as the first element of an element of l2, it has to subtract the second element of the element of l2 to the one on l1. If not, just append to the list.

For example, the goal here would be to return

{{{1,2},-1},{{3,4},2},{{3,5},2},{{1,3},0}};

I do know how to do this with for and while, but I feel that that's not the mathematica way, as I want it.

EDIT:

The lengths of l1 and l2 might vary and the first pair could be any real number. The second element it is indeed a positive integer.

Garmekain
  • 329
  • 1
  • 7

3 Answers3

8

This is one way, but it is likely not the best:

a1 = Rule @@@ l1;
a2 = Rule @@@ l2;

sub[{x_, y_}] := x - y
sub[{x_}] := x

Merge[{a1, a2}, sub]
(* <|{1, 2} -> -1, {3, 4} -> 2, {1, 3} -> 0, {3, 5} -> 2|> *)

This solution does not care about the position of elements in the two lists. It just looks for "equivalent" elements. It also doesn't allow equivalent elements within the same list.


Note: The below version is based on a misreading of the question and only compares elements that are at the same position within l1 and l2.

If we want to do it precisely the way you described it, we can use MapThread:

Clear[combine]
combine[{key_, val1_}, {key_, val2_}] := {key, val1 - val2} (* same key *)    
combine[a_, b_] := Sequence[a, b] (* handle the rest of cases, i.e. different keys *)

MapThread[combine, {l1, l2}]
(* {{{1, 2}, -1}, {{3, 4}, 2}, {{3, 5}, 2}, {{1, 3}, 0}} *)

This only compares elements in the same position within the two lists.

If you wish to write combine as an inline function, you need to prevent the splicing of Sequence into If by using Unevaluated:

If[First[#1] == First[#2], 
 {First[#1], Last[#1] - Last[#2]}, 
 Unevaluated@Sequence[#1, #2]
] &

I used First and Last instead of ...[[1]] and ...[[2]] only for readability. Too many brackets tend to confuse me.

Szabolcs
  • 234,956
  • 30
  • 623
  • 1,263
5

For the example you gave, with positive integer pairs as the first element and no duplication:

l1 = {{{1,2},1},{{3,4},2},{{1,3},3}};
l2 = {{{1,2},2},{{3,5},2},{{1,3},3}};

SetSystemOptions["SparseArrayOptions" -> {"TreatRepeatedEntries" -> Subtract}];

SparseArray[Rule @@@ Join[l1, l2]] // ArrayRules // Most

List @@@ %
{{1, 2} -> -1, {1, 3} -> 0, {3, 4} -> 2, {3, 5} -> 2}

{{{1, 2}, -1}, {{1, 3}, 0}, {{3, 4}, 2}, {{3, 5}, 2}}

Reference: Additive SparseArray Assembly

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
0

This is an answer, but certainly, not the most efficient.

{#[[1, 1]], #[[1, 2]] + If[Length@# == 2, #[[2, 2]], 0]} & /@ 
 GatherBy[Join[l1, 
   Table[{i[[1]], -i[[2]]}, {i, l2}]], First]
Garmekain
  • 329
  • 1
  • 7