views:

521

answers:

10

I have a file which contains several thousand numbers, each on it's own line:

34
42
11
6
2
99
...

I'm looking to write a script which will print the sum of all numbers in the file. I've got a solution, but it's not very efficient. (It takes several minutes to run.) I'm looking for a more efficient solution. Any suggestions?

+1  A: 

I don't know if you can get a lot better than this, considering you need to read through the whole file.

$sum = 0;
while(<>){
   $sum += $_;
}
print $sum;
Stefan Kendall
what's the $_ mean?
Mark Roberts
Very readable. For perl. But yeah, it's going to have to be something like that...
dmckee
`$_` is the default variable. The line input operator, `<>`, puts it's result in there by default when you use `<>` in `while`.
brian d foy
@Mark, `$_` is the topic variable--it works like the 'it'. In this case `<>` assigns each line to it. It gets used in a number of places to reduce code clutter and help with writing one-liners. The script says "Set the sum to 0, read each line and add it to the sum, then print the sum."
daotoad
@Stefan, with warnings and strictures off, you can skip declaring and initializing `$sum`. Since this is so simple, you can even use a statement modifier `while`: `$sum += $_ while <>; print $sum;`
daotoad
+17  A: 

You can use awk:

awk '{ sum += $1 } END { print sum }' file
Ayman Hourieh
I stick my tongue out at you for having fast fingers...
dmckee
+15  A: 

For a Perl one-liner, it's basically the same thing as the awk solution in Ayman Hourieh's answer:

 % perl -nle '$sum += $_ } END { print $sum'

If you're curious what Perl one-liners do, you can deparse them:

 %  perl -MO=Deparse -nle '$sum += $_ } END { print $sum'

The result is a more verbose version of the program, in a form that no one would ever write on their own:

BEGIN { $/ = "\n"; $\ = "\n"; }
LINE: while (defined($_ = <ARGV>)) {
    chomp $_;
    $sum += $_;
}
sub END {
    print $sum;
}
-e syntax OK

Just for giggles, I tried this with a file containing 1,000,000 numbers (in the range 0 - 9,999). On my Mac Pro, it returns virtually instantaneously. That's too bad, because I was hoping using mmap would be really fast, but it's just the same time:

use 5.010;
use File::Map qw(map_file);

map_file my $map, $ARGV[0];

$sum += $1 while $map =~ m/(\d+)/g;

say $sum;
brian d foy
Wow, that shows a _deep_ understanding on what code -nle actually wraps around the string you give it. My initial thought was that you shouldn't post while intoxicated but then I noticed who you were and remembered some of your other Perl answers :-)
paxdiablo
-n and -p just put characters around the argument to -e, so you can use those characters for whatever you want. We have a lot of one-liners that do interesting things with that in _Effective Perl Programming_ (which is about to hit the shelves).
brian d foy
Nice, what are these non-matching curly braces about?
-n adds the `while { }` loop around your program. If you put `} ... {` inside, then you have `while { } ... { }`. Evil? Slightly.
jrockway
A: 

Pash (aka PowerShell for Linux :)):

(gc "input.txt" | measure -sum).Sum > "output.txt"
ssg
A: 
echo $(for i in $(cat numbers.txt); do echo -n "$i+"; done ; echo 0) | bc

I am curious to see how it performs (it might reach memory limits).

Dave Jarvis
On my Mac Pro for 1,000,000 numbers between 0 and 9999, this ran in about 15 seconds, where my Perl solution was 0.3 seconds, and the awk solution was 0.3 seconds.
brian d foy
@brian: Thank you! I thought the bash solution would have died sooner than 1,000,000 numbers (due to command line limitations). Guess not.
Dave Jarvis
That's not straight Bash. `cat` and `bc` are external executables.
Dennis Williamson
@Dennis: You are correct.
Dave Jarvis
A: 

Just to be ridiculous:

cat f | tr "\n" "+" | perl -pne chop | R --vanilla --slave
frankc
This one eventually died with "Error: evaluation nested too deeply: infinite recursion / options(expressions=)?" for my tests. I would have thought R could do this all by itself.
brian d foy
Haha nice solution.
+2  A: 

This is straight Bash:

sum=0
while read -r line
do
    (( sum += line ))
done < file
echo $sum
Dennis Williamson
this willl work as long as there are no decimals
A: 

I have not tested this but it should work:

cat f | tr "\n" "+" | sed 's/+$/\n/' | bc

You might have to add "\n" to the string before bc (like via echo) if bc doesn't treat EOF and EOL...

DVK
It doesn't work. `bc` issues a syntax error because of the trailing "+" and lack of newline at the end. This will work and it eliminates a useless use of `cat`: `{ tr "\n" "+" | sed 's/+$/\n/'| bc; } < numbers2.txt` *or* `<numbers2.txt tr "\n" "+" | sed 's/+$/\n/'| bc`
Dennis Williamson
`tr "\n" "+" <file | sed 's/+$/\n/' | bc`
ghostdog74
A: 
sed ':a;N;s/\n/+/;ta' file|bc
+1  A: 

Here's another one-liner

( echo 0 ; sed 's/$/ +/' foo ; echo p ) | dc

This assumes the numbers are integers. If you need decimals, try

( echo 0 2k ; sed 's/$/ +/' foo ; echo p ) | dc

Adjust 2 to the number of decimals needed.

lhf