5
\$\begingroup\$

Starting from an array of sub-arrays, each sub-array is a 2 elements key|value pair. Example

input = [["A", 5], ["B", 2], ["A", 12], ["B", -2], ["C", 7]]

Convert this into a hash that has distinct keys from above, with values summed up, so the above would be:

output = {"A" => 17, "B" => 0, "C" => 7}
\$\endgroup\$
  • \$\begingroup\$ Not to nit-pick, but the title of this challenge is a little misleading. An array of arrays of key|value pairs would imply higher complexity than I think you intended. Maybe change to Create a hash from an array of key|value pairs? \$\endgroup\$ – ardnew Sep 12 '12 at 2:54
  • 1
    \$\begingroup\$ Is this intended to be restricted to a very small number of languages? \$\endgroup\$ – Peter Taylor Sep 12 '12 at 6:41
  • 6
    \$\begingroup\$ Any winning criteria? \$\endgroup\$ – manatwork Sep 12 '12 at 8:00

15 Answers 15

2
\$\begingroup\$

Ruby

input.each_with_object(Hash.new(0)) { |(k, v), h| h[k] += v }

(see http://api.rubyonrails.org/classes/Enumerable.html#method-i-each_with_object)

|improve this answer|||||
\$\endgroup\$
2
\$\begingroup\$

Perl

Hopefully this thread won't get tagged code-golf

use strict;
use warnings;

my $a = [["A", 5], ["B", 2], ["A", 12], ["B", -2], ["C", 7]];

my %h = ();

map { $h{shift @$_} += shift @$_ } @$a;

while (my ($k, $v) = each %h)
{
  print "$k = $v $/";
}

Output:

$ perl cg-arraysum.pl 
A = 17 
C = 7 
B = 0
|improve this answer|||||
\$\endgroup\$
1
\$\begingroup\$

k

This can probably be shortened from 17 chars but:

{+/'(*|+x)(=*+x)}

Example:

k)x:(("A";5);("B";2);("A";12);("B";-2);("C";7))
k){+/'(*|+x)(=*+x)}x
"ABC"!17 0 7

or with output in q output format:

A| 17
B| 0
C| 7
|improve this answer|||||
\$\endgroup\$
1
\$\begingroup\$

There's more than one way to do it (Perl)

Preparation:

$input = [["A", 5], ["B", 2], ["A", 12], ["B", -2], ["C", 7]];

I have three Perl snippets to solve the problem because the obvious code is kinda boring. It looks like this:

$output1{$_->[0]} += $_->[1] for @$input;

I'm a fan of list expressions, so here's a single list expression, I had to use map and his friend grep a lot:

%output2 = map {
    $c = $_ => sum map $_->[1] => grep $_->[0] eq $c => @$input
} keys %{{map @$_ => @$input}};

Many years I tried to find a non-trivial use-case for List::Utils reduce function which folds list elements with a given function, and finally here it is. A second single list expression, btw. \o/

$output3 = reduce {
    $a = {@$a} if ref $a eq 'ARRAY'; $a->{$b->[0]} += $b->[1]; $a;
} @$input;
|improve this answer|||||
\$\endgroup\$
  • 1
    \$\begingroup\$ i like your first solution using the for loop instead of my map. ignoring the return value of map is ugly. it also seems to be the most practical of the three \$\endgroup\$ – ardnew Sep 13 '12 at 1:52
  • \$\begingroup\$ @ardnew yep, you're right, calling map in void context is considered evil. Glad you like the first solution, the second and third one are just for fun. :) \$\endgroup\$ – memowe Sep 13 '12 at 2:04
1
\$\begingroup\$

C

Assumes keys are 'A'..'Z', assumes values are numbers, assumes input format is exactly spaced/formatted as specified in example.

char*p;c[99],m[99];
main(int i,char**v){
    for(p=v[1]+1;*p==91;){
        c[p[2]]+=atoi(p+5);
        m[p[2]]=1;
        for(;*p-93;p++);
        p+=p[1]-44?1:3;
    }
    printf("{");
    for(p="";++i<99;)if(m[i])printf("%s\"%c\" => %d",p,i,c[i]),p=", ";
    printf("}\n");
}

Run as:

./a.out '[["A", 5], ["B", 2], ["A", 12], ["B", -2], ["C", 7]]'

Output:

{"A" => 17, "B" => 0, "C" => 7}
|improve this answer|||||
\$\endgroup\$
  • \$\begingroup\$ hehe, I admire your treatment of the input as a string and not simply using a proper key-value array structure \$\endgroup\$ – ardnew Sep 13 '12 at 14:32
0
\$\begingroup\$

R - 20 chars

aggregate(.~k,x,sum)

where x is the input defined here:

x <- data.frame(k = c("A", "B", "A", "B", "C"),
                v = c(5, 2, 12, -2, 7))
x
#     k     v
# 1   A     5
# 2   B     2
# 3   A    12
# 4   B    -2
# 5   C     7

The output:

aggregate(.~k,x,sum)
#   k  v
# 1 A 17
# 2 B  0
# 3 C  7
|improve this answer|||||
\$\endgroup\$
0
\$\begingroup\$

Mathematica

In Mathematica the input array would be represented as n = {{"A", 5}, {"B", 2}, {"A", 12}, {"B", -2}, {"C", 7}};.

Code

{#[[1, 1]], Total@##[[All, 2]]} & /@ GatherBy[n, First]

{{"A", 17}, {"B", 0}, {"C", 7}}


Explanation

GatherBy[n, First] gathers the data by the first element in each subarray (sublist).

{{{"A", 5}, {"A", 12}}, {{"B", 2}, {"B", -2}}, {{"C", 7}}}

Total@##[[All, 2]] sums the second elements for each string, #[[1, 1]] (in this case, for "A", "B", and "C").

|improve this answer|||||
\$\endgroup\$
0
\$\begingroup\$

Python 52 43

{X:sum(y for Y,y in a if Y==X)for X,x in a}

The input array is in a

This is fairly inefficient because it calculates the sum of all the 'A' keys for every occurrence of the 'A' key, but it works and since there aren't really any winning criteria I decided to golf my answer as much as possible.

Edit: Removed some unnecessary brackets and indices per recommendation from Daniero

|improve this answer|||||
\$\endgroup\$
  • \$\begingroup\$ You don't need the square brackets inside sum(). Also, by "unpacking" (?) two values out of a, you don't need the indices: {X:sum(y for Y,y in a if Y==X)for X,x in a} is 9 chars shorter (untested as neither this nor the original did work with my local python 2.5.2; It seems legit though). \$\endgroup\$ – daniero Sep 12 '12 at 17:44
  • \$\begingroup\$ @Daniero okay, ill look into that. Also, dict comprehensions were introduced in python 2.7 (i think) so thats probably why it doesn't work on your 2.5 version \$\endgroup\$ – Matt Sep 12 '12 at 18:01
  • \$\begingroup\$ @Daniero that works for me. I didn't know you could do list comprehensions without brackets like that. \$\endgroup\$ – Matt Sep 12 '12 at 18:05
  • \$\begingroup\$ That would explain it; I knew I had been doing dict comprehensions before! About the brackets: If you wrap the comprehension in parentheses like (x for x in a) you get a <generator object <genexpr> at 0x107784eb0>, So I guess that's what's being sent to sum(). Or it's just a shortcut notation. \$\endgroup\$ – daniero Sep 12 '12 at 18:33
0
\$\begingroup\$

Python

output = {}; 
for x,y in input:
    output[x] = y + ( output.get(x) or 0 )

Thanks for @Daniero's comment.

|improve this answer|||||
\$\endgroup\$
  • \$\begingroup\$ That doesn't meet the specification, and it's frankly quite crude. This one does what it's supposed to, and, with shortened variable names, it's shorter than another python answer here: h={};for x,y in a:d[x]=y+(d.get(x)or 0) - the ';' needs to be replaced by a linebreak. \$\endgroup\$ – daniero Sep 12 '12 at 18:10
  • 1
    \$\begingroup\$ @Daniero even shorter: d={};for x,y in a:d[x]=y+(d.get(x,0)) \$\endgroup\$ – Matt Sep 13 '12 at 11:46
0
\$\begingroup\$

ruby, 40 (or 38)

h=Hash.new(0);input.map{|k,v|h[k]+=v}
h

h contains the result. The 38-char solution creates the wanted result, the 40 characters solution returns the result.

The result as complete testcode:

input = [["A", 5], ["B", 2], ["A", 12], ["B", -2], ["C", 7]]
output = {"A" => 17, "B" => 0, "C" => 7}

h=Hash.new(0);input.map{|k,v|h[k]+=v}
p h == output #true

A little variant (but with the same size):

h={};input.map{|k,v|h[k]=(h[k]||0)+v}
|improve this answer|||||
\$\endgroup\$
0
\$\begingroup\$

TXR

@(do (let ((h (hash :equal-based)))
       (each ((entry '(("A" 5) ("B" 2) ("A" 12) ("B" -2) ("C" 7))))
         (inc [h (first entry) 0] (second entry)))
       (format t "~a\n" h)))

Run:

$ txr sumhash.txr
#H((:equal-based) ("A" 17) ("B" 0) ("C" 7))

What if we take the question literally? The question says that the input is this:

input = [["A", 5], ["B", 2], ["A", 12], ["B", -2], ["C", 7]]

and that the output looks like this:

output = {"A" => 17, "B" => 0, "C" => 7}

Fair enough. We can make a program to transform that exact input to that exact output, rather than massaging the data to fit the syntax of our programming language.

input = [@(coll :vars (key value))["@key", @{value /[+\-]?[0-9]+/}]@(last)]@(end)
@(bind (keyout valout) @(let ((h (hash :equal-based)))
                          (each* ((k key)
                                  (v [mapcar (op int-str @1 10) value]))
                             (inc [h k 0] v))
                          '(,(hash-keys h) ,(hash-values h))))
@(output)
output = {@(rep)"@keyout" => @valout, @(last)"@keyout" => @valout@(end)}
@(end)

Run:

$ txr sumhash2.txr -   # input read from stdin
input = [["A", 5], ["B", 2], ["A", 12], ["B", -2], ["C", 7]]
<user types Ctrl-D>
output = {"A" => 17, "B" => 0, "C" => 7}

Empty case:

$ txr sumhash2.txr -
input = []
<Ctrl-D>
output = []

Invalid input:

$ txr sumhash2.txr
foo
<Ctrl-D>
false

Word false is output, termination status is failed.

Simplified version in which hashing is worked into data acquisition (side effects during pattern matching):

@(bind h @(hash :equal-based))
input = [@(coll)["@key", @{value /[+\-]?[0-9]+/}]@\
                @(do (inc [h key 0] (int-str value 10)))@\
                @(last)]@(end)
@(bind (keyout valout) (@(hash-keys h) @(hash-values h)))
@(output)
output = {@(rep)"@keyout" => @valout, @(last)"@keyout" => @valout@(end)}
@(end)
|improve this answer|||||
\$\endgroup\$
0
\$\begingroup\$

Javascript 79

function f(a){r={};for(e in a){i=a[e][0];j=a[e][1];r[i]=r[i]?r[i]+j:j}return r}

Test : f([["A", 5], ["B", 2], ["A", 12], ["B", -2], ["C", 7]])
Outputs : Object {A: 17, B: 0, C: 7}

|improve this answer|||||
\$\endgroup\$
0
\$\begingroup\$

Swift 4

Optimized for clarity, not character count. Replace the input as needed or pass it in some other way (e.g. through an I/O class).

import Darwin // or Glibc if on Linux

let input = [["A", 5], ["B", 2], ["A", 12], ["B", -2], ["C", 7]]

var output: [String: Int] = [:]

for pair in input {
    guard let key = pair[0] as? String else {
        fputs("Input error: key '\(pair[0])' is not a string\n", __stderrp)
        exit(1)
    }
    if output[key] == nil { // the first of this key
        output[key] = 0 // have to add to something, so put in 0
    }
    output[key]! += pair[1] as? Int ?? 0
}

for pair in output.sorted(by: <) {
    print("\(pair.key): \(pair.value)")
}

Bonus!

  • Error handling:
    • If you input keys that aren't strings, it will give an error message and exit.
    • If you input values that aren't integers, it will silently assume they're 0.
  • The output is sorted.
|improve this answer|||||
\$\endgroup\$
0
\$\begingroup\$

Java 8

Here's an approach that uses streams:

import java.util.Map;
import java.util.HashMap;
import java.util.stream.Stream;
import java.util.stream.Collectors;

public static Map<?, Integer> collect(Object[][] pairs) {
    return Stream.of(pairs)
        .collect(Collectors.groupingBy(
            pair -> pair[0],
            HashMap::new,
            Collectors.reducing(
                0,
                pair -> (Integer) pair[1],
                Integer::sum
            )
        ))
    ;
}

Uses a collector that groups elements into a map based on their first element, with duplicate values reduced by addition.

Or there's the classic loop version:

public static Map<?, Integer> collect(Object[][] pairs) {
    Map<Object, Integer> map = new HashMap<>();
    for (Object[] pair : pairs) {
        Object k = pair[0];
        int v = (Integer) pair[1];
        map.put(k, map.getOrDefault(k, 0) + v);
    }
    return map;
}
|improve this answer|||||
\$\endgroup\$
-1
\$\begingroup\$

Java - 71

<T> Map m(final T[][] i) {
  return new HashMap() {
    { for (T[] p : i) put(p[0], p[1]); }
  };
}

The following...

System.out.println(m(new Object[][] {
  { "A", 5 }, { "B", 2 }, { "A", 12 }, { "B", -2 }, { "C", 7 }
}));

produces:

{A=12, B=-2, C=7}

|improve this answer|||||
\$\endgroup\$
  • 1
    \$\begingroup\$ You need to read the spec more carefully. Your implementation will only store the last read value of the input array (compare your outputs to the example). \$\endgroup\$ – ardnew Sep 13 '12 at 14:34

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