# Subtract second element of element of list from other list if the first elements are equal

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.

• If l2 contains a pair {{a,b},c} that l1 has, then will it be at the same index as it is in l1? Feb 10, 2017 at 11:39
• @Myridium No, it won't. Feb 10, 2017 at 11:45
• @Garmekain In that case, are the {a,b} within each list guaranteed to be unique? Feb 10, 2017 at 11:46
• @MartinEnder There won't be any equal elements within a single list, but {a,b} of l1 might appear on l2. Feb 10, 2017 at 11:50

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

(* {{{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 ...[] and ...[] only for readability. Too many brackets tend to confuse me.

• After clarification from the OP, the MapThread version won't work, I think. Feb 10, 2017 at 11:51
• @MartinEnder You are right. I got confused with all the "element of element of" in the title and description. Feb 10, 2017 at 12:54
• @Szabolcs. very elegant solution. Thanks for posting it. I was completely oblivious of Merge . +1 Feb 11, 2017 at 1:34

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}}


{#[[1, 1]], #[[1, 2]] + If[Length@# == 2, #[[2, 2]], 0]} & /@