4
$\begingroup$

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.

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

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.

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

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

$\endgroup$
0
$\begingroup$

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]
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.