8
$\begingroup$

I have a list of the form:

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

that I would like to sort such that I calculate the sum over the third column whenever the first two columns are equal. I.e. in this case the output would be

{{1, 0, 1 + 3 + 0}, {2, 1, 2 + (-1)}, {2, 4, 2}} 

or

 {{1, 0, 4},{2, 1, 1},{2, 4, 2}}

Is there an elegant way to do this?

$\endgroup$
  • $\begingroup$ Duplicate of: mathematica.stackexchange.com/q/4332/5 Also related: mathematica.stackexchange.com/q/16115/5 $\endgroup$ – rm -rf Dec 18 '12 at 0:39
  • $\begingroup$ @rm-rf Looking at your first link the principle is the same. However gathering based on both first and second columns, as opposed to just the first, is probably not obvious to a new user. Keeping the first 2 columns together also means that you have to wrap Flatten around the mapped function. $\endgroup$ – Mike Honeychurch Dec 18 '12 at 0:43
  • $\begingroup$ See also: (17734) $\endgroup$ – Mr.Wizard May 9 '16 at 12:19
12
$\begingroup$
Flatten[{#[[1, {1, 2}]], Total[#[[All, 3]]]}] & /@ GatherBy[yourList, #[[{1, 2}]] &]

(*  {{1, 0, 4}, {2, 1, 1}, {2, 4, 2}} *)

How this works:

GatherBy[yourList, #[[{1, 2}]] &]

this gathers elements of your list based on matching the first and second elements in each sub-list (#[[{1, 2}]] &)

Once common elements are gathered (collected) you can total/sum all the third elements by mapping them onto Total

$\endgroup$
  • $\begingroup$ Perfect! I was unaware of the GatherBy function and, yes, using it with more than one column was not obvious to this new user. $\endgroup$ – Paul Dec 18 '12 at 0:48
5
$\begingroup$
 lists = {{1, 0, 1}, {2, 1, 2}, {1, 0, 3}, {2, 4, 2}, {2, 1, -1}, {1, 0, 0}}

Reap/Sow:

 Reap[Sow[#[[3]], {#[[{1, 2}]]}] & /@ lists, _,  Append[#1, Total@#2] &][[2]]

ReplaceRepeated:

 lists //. {a___, {x_, y_, z_}, b___, {x_, y_, w_}, c___} :> {a, {x, y, z + w}, b, c}

Cases + DeleteDuplicates:

 DeleteDuplicates@Cases[lists, {w : PatternSequence[_, _], _} :>
         {w, Total@(Last /@ Cases[lists, {w, _}])}]

all give

{{1, 0, 4}, {2, 1, 1}, {2, 4, 2}}

Update: In versions 10+, you can also use Merge:

KeyValueMap[Append]@Merge[Tr][Most @ # -> Last @ #& /@ lists]

{{1, 0, 4}, {2, 1, 1}, {2, 4, 2}}

$\endgroup$
  • $\begingroup$ +1 for the ReplaceRepeated. $\endgroup$ – image_doctor Dec 18 '12 at 10:25
2
$\begingroup$
Total@#/PadLeft[{1}, 3, Length@#] & /@ GatherBy[list, #[[{1, 2}]] &]

This approach is basically the same as Mike's. However, instead of summing only the last element it sums all the elements. To correct for the extra summing PadLeft[{1}, 3, Length@#] is used to divide by the number of times it was added where the {1} is included to keep the last element the same. The approach can be generalized to lists of length $n$ by replacing PadLeft[{1}, 3, Length@#] with PadLeft[{1}, Length@#[[1]], Length@#]

$\endgroup$
  • 1
    $\begingroup$ You might give a bit of explanation as to what your code is doing and why it works. $\endgroup$ – Brett Champion Dec 18 '12 at 4:57
2
$\begingroup$

GroupBy, introduced in Mathemtaica 10, is applicable here and cleaner than GatherBy:

in = {{1, 0, 1}, {2, 1, 2}, {1, 0, 3}, {2, 4, 2}, {2, 1, -1}, {1, 0, 0}};

Append @@@ Normal @ GroupBy[in, Most -> Last, Tr]
{{1, 0, 4}, {2, 1, 1}, {2, 4, 2}}

See also:

$\endgroup$
1
$\begingroup$

Since GatherBy and Sow/Reap are already shown I'll drop back to "first principles" as it were and use more basic language.

dat = {{1, 0, 1}, {2, 1, 2}, {1, 0, 3}, {2, 4, 2}, {2, 1, -1}, {1, 0, 0}};

f[dat_] :=
 Module[{key},
   key[__] = 0;
   key[#, #2] += #3 & @@@ dat;
   Most @ DownValues @ key /. (_@_@x__ :> y_) :> {x, y}
 ]

f @ dat
{{1, 0, 4}, {2, 1, 1}, {2, 4, 2}}
$\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.