8

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?

kglr
  • 394,356
  • 18
  • 477
  • 896
Paul
  • 83
  • 3
  • Duplicate of: http://mathematica.stackexchange.com/q/4332/5 Also related: http://mathematica.stackexchange.com/q/16115/5 – rm -rf Dec 18 '12 at 00:39
  • @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. – Mike Honeychurch Dec 18 '12 at 00:43

5 Answers5

12
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

Mike Honeychurch
  • 37,541
  • 3
  • 85
  • 158
  • Perfect! I was unaware of the GatherBy function and, yes, using it with more than one column was not obvious to this new user. – Paul Dec 18 '12 at 00:48
5
 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}}

kglr
  • 394,356
  • 18
  • 477
  • 896
2
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@#]

E.O.
  • 1,213
  • 1
  • 12
  • 16
2

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:

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

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}}
Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371