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\$
3
  • \$\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, 2012 at 2:54
  • 1
    \$\begingroup\$ Is this intended to be restricted to a very small number of languages? \$\endgroup\$ Sep 12, 2012 at 6:41
  • 6
    \$\begingroup\$ Any winning criteria? \$\endgroup\$
    – manatwork
    Sep 12, 2012 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)

\$\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
\$\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
\$\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;
\$\endgroup\$
2
  • 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, 2012 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, 2012 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}
\$\endgroup\$
1
  • \$\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, 2012 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
\$\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").

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

\$\endgroup\$
4
  • \$\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, 2012 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, 2012 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, 2012 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, 2012 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.

\$\endgroup\$
2
  • \$\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, 2012 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, 2012 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}
\$\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)
\$\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}

\$\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.
\$\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;
}
\$\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}

\$\endgroup\$
1
  • 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, 2012 at 14:34

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