Как я могу быстро сложить все числа в файле?


196

У меня есть файл, который содержит несколько тысяч номеров, каждое в своей строке:

34
42
11
6
2
99
...

Я ищу написать скрипт, который будет печатать сумму всех чисел в файле. У меня есть решение, но оно не очень эффективно. (Запуск занимает несколько минут.) Я ищу более эффективное решение. Какие-либо предложения?


5
Каким было ваше медленное решение? Может быть, мы можем помочь вам понять, что было медленным в этом. :)
Брайан Д. Фой

4
@ Brian D Foy, я слишком смущен, чтобы опубликовать это. Я знаю, почему это медленно. Это потому, что я вызываю «cat filename | head -n 1», чтобы получить верхнее число, добавляю его к промежуточному итогу и вызываю «cat filename | tail ...», чтобы удалить верхнюю строку для следующей итерации ... I есть много, чтобы узнать о программировании !!!
Марк Робертс

6
Это ... очень систематично. Очень ясно и прямо, и я люблю это для всего, что это ужасная мерзость. Я полагаю, он построен из инструментов, которые вы знали, когда начинали, верно?
dmckee --- котенок экс-модератора

4
полная
копия

@MarkRoberts Должно быть, вам понадобилось много времени, чтобы решить это. Это очень умный метод решения проблем, и о, так неправильно. Это похоже на классический случай чрезмерного обдумывания. Некоторые из решений Glen Jackman представляют собой сценарии сценариев (и два из них являются чисто оболочкой, в которой не используются такие вещи, как awkи bc). Все они закончили добавлять миллион чисел менее чем за 10 секунд. Взгляните на них и посмотрите, как это можно сделать в чистой оболочке.
Дэвид В.

Ответы:


113

Для однострочника Perl это в основном то же самое, что и awkрешение в ответе Аймана Хури :

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

Если вам интересно, что делают однострочники Perl, вы можете отменить их:

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

В результате получается более подробная версия программы в форме, которую никто никогда не написал бы самостоятельно:

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

Просто для хихиканья, я попробовал это с файлом, содержащим 1 000 000 номеров (в диапазоне 0 - 9 999). На моем Mac Pro он возвращается практически мгновенно. Это очень плохо, потому что я надеялся, что использование mmapбудет очень быстрым, но это в то же время:

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

map_file my $map, $ARGV[0];

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

say $sum;

4
Вау, это показывает глубокое понимание того, какой код -nle фактически оборачивает строку, которую вы ему даете. Сначала я думал, что вы не должны публиковать сообщения в состоянии алкогольного опьянения, но потом я заметил, кто вы, и вспомнил некоторые другие ваши ответы на Perl :-)
paxdiablo

-n и -p просто помещают символы вокруг аргумента -e, чтобы вы могли использовать эти символы для чего угодно. У нас есть много строк, которые делают интересные вещи с этим в Эффективном программировании на Perl (которое скоро появится на полках).
Брайан Д. Фой

5
Хорошо, что это за несоответствующие фигурные скобки?
Франк

17
-n добавляет while { }цикл вокруг вашей программы. Если вы положили } ... {внутрь, то у вас есть while { } ... { }. Злой? Слегка.
jrockway

5
Большой бонус за выделение -MO=Deparseопции! Хотя бы по отдельной теме.
Конни

375

Вы можете использовать awk:

awk '{ sum += $1 } END { print sum }' file


1
С -F '\t'опцией, если ваши поля содержат пробелы и разделены вкладками.
Итан Фурман

5
Пожалуйста, отметьте это как лучший ответ. Это также работает, если вы хотите суммировать первое значение в каждой строке внутри файла TSV (значения, разделенные табуляцией).
Андреа

100

Ни одно из решений до сих пор не используется paste. Вот один из них:

paste -sd+ filename | bc

В качестве примера вычислим Σn, где 1 <= n <= 100000:

$ seq 100000 | paste -sd+ | bc -l
5000050000

(Для любопытных, вывел seq nбы последовательность чисел от 1до nзаданного положительного числа n.)


1
Очень хорошо! И легко запомнить
Брендан Магуайр

1
seq 100000 | paste -sd+ - | bc -lна Mac OS X Bash. И это, безусловно, самое сладкое и самое необычное решение!
Симо А.

2
@SimoA. Я голосую за то, что мы используем термин unixiest вместо unixest, потому что самое сексуальное решение всегда самое необычное;)
Коннор

86

Просто для интереса давайте проверим это:

$ for ((i=0; i<1000000; i++)) ; do echo $RANDOM; done > random_numbers

$ time perl -nle '$sum += $_ } END { print $sum' random_numbers
16379866392

real    0m0.226s
user    0m0.219s
sys     0m0.002s

$ time awk '{ sum += $1 } END { print sum }' random_numbers
16379866392

real    0m0.311s
user    0m0.304s
sys     0m0.005s

$ time { { tr "\n" + < random_numbers ; echo 0; } | bc; }
16379866392

real    0m0.445s
user    0m0.438s
sys     0m0.024s

$ time { s=0;while read l; do s=$((s+$l));done<random_numbers;echo $s; }
16379866392

real    0m9.309s
user    0m8.404s
sys     0m0.887s

$ time { s=0;while read l; do ((s+=l));done<random_numbers;echo $s; }
16379866392

real    0m7.191s
user    0m6.402s
sys     0m0.776s

$ time { sed ':a;N;s/\n/+/;ta' random_numbers|bc; }
^C

real    4m53.413s
user    4m52.584s
sys 0m0.052s

Я прервал Sed Run через 5 минут


Я нырял в и это быстро

$ time lua -e 'sum=0; for line in io.lines() do sum=sum+line end; print(sum)' < random_numbers
16388542582.0

real    0m0.362s
user    0m0.313s
sys     0m0.063s

и пока я обновляю это, ruby:

$ time ruby -e 'sum = 0; File.foreach(ARGV.shift) {|line| sum+=line.to_i}; puts sum' random_numbers
16388542582

real    0m0.378s
user    0m0.297s
sys     0m0.078s

Прислушайтесь к совету Эда Мортона: используя $1

$ time awk '{ sum += $1 } END { print sum }' random_numbers
16388542582

real    0m0.421s
user    0m0.359s
sys     0m0.063s

против использования $0

$ time awk '{ sum += $0 } END { print sum }' random_numbers
16388542582

real    0m0.302s
user    0m0.234s
sys     0m0.063s

18
+1: для того, чтобы придумать кучу решений и сравнить их.
Дэвид В.

time cat random_numbers | paste -sd + | bc -l реальный 0m0.317s пользователь 0m0.310s sys 0m0.013s
rafi wiener

это должно быть примерно идентично trрешению.
Гленн Джекман

4
Ваш сценарий awk должен выполняться немного быстрее, если вы используете $0вместо, $1так как awk выполняет разбиение поля (что, очевидно, занимает время), если какое-либо поле специально упоминается в сценарии, но не иначе.
Эд Мортон

21

Другой вариант заключается в использовании jq:

$ seq 10|jq -s add
55

-s( --slurp) читает строки ввода в массив.


1
Это потрясающий инструмент для таких быстрых задач, о которых почти забыли. спасибо
Джон


7

Вот еще один вкладыш

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

Это предполагает, что числа являются целыми числами. Если вам нужны десятичные дроби, попробуйте

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

Отрегулируйте 2 до необходимого количества десятичных знаков.


6

Я предпочитаю использовать GNU datamash для таких задач, потому что он более краткий и разборчивый, чем perl или awk. Например

datamash sum 1 < myfile

где 1 обозначает первый столбец данных.


1
Это не является стандартным компонентом, так как я не вижу его в моей установке Ubuntu. Хотелось бы увидеть его в тестах, хотя.
Стивен легко развлекается


5

Я предпочитаю использовать R для этого:

$ R -e 'sum(scan("filename"))'

Я фанат R для других приложений, но это не очень хорошо для производительности. Файловый ввод / вывод является серьезной проблемой. Я проверил передачу аргументов в скрипт, который можно ускорить с помощью пакета vroom. Я опубликую более подробную информацию, когда буду тестировать некоторые другие сценарии на том же сервере.
Том Келли

4
cat nums | perl -ne '$sum += $_ } { print $sum'

(так же, как ответ Брайана Д. Фоя, без «КОНЦА»)


Мне нравится это, но не могли бы вы объяснить фигурные скобки? Странно видеть} без {и наоборот.
барабанная дробь

1
@drumfire смотрите ответ @brian d foy выше, perl -MO=Deparseчтобы увидеть, как Perl анализирует программу. или документы для perlrun : perldoc.perl.org/perlrun.html (поиск -n). Perl переносит ваш код с помощью {}, если вы используете -n, ​​чтобы он стал полноценной программой.
edibleEnergy

4

Более кратко:

# Ruby
ruby -e 'puts open("random_numbers").map(&:to_i).reduce(:+)'

# Python
python -c 'print(sum(int(l) for l in open("random_numbers")))'

В моей системе преобразование в плавающее число кажется в два раза быстрее (320 против 640 мс). time python -c "print(sum([float(s) for s in open('random_numbers','r')]))"
user12719


3

Просто для удовольствия, давайте сделаем это с PDL , математическим движком Perl!

perl -MPDL -E 'say rcols(shift)->sum' datafile

rcolsсчитывает столбцы в матрицу (в данном случае 1D) и sum(неожиданно) суммирует все элементы матрицы.


Как исправить Не удается найти файл PDL.pm в @INC (может потребоваться установить модуль PDL) (@INC содержит: / etc / perl /usr/local/lib/x86_64-linux-gnu/perl/5.22.1? )) для прикола конечно =)
Фортран

1
Сначала нужно установить PDL, это не собственный модуль Perl.
Джоэл Бергер

3

Вот решение с использованием Python с выражением генератора. Протестировано с миллионами номеров на моем старом сыром ноутбуке.

time python -c "import sys; print sum((float(l) for l in sys.stdin))" < file

real    0m0.619s
user    0m0.512s
sys     0m0.028s

3
Простое понимание списка с именованной функцией - хороший пример использования map():map(float, sys.stdin)
sevko

3

Я не мог просто пройти мимо ... Вот мой лайнер на Хаскеле. Это на самом деле вполне читабельно:

sum <$> (read <$>) <$> lines <$> getContents

К сожалению, его нельзя ghci -eпросто запустить, поэтому для него нужна основная функция, печать и компиляция.

main = (sum <$> (read <$>) <$> lines <$> getContents) >>= print

Для уточнения, мы читаем весь вход ( getContents), разделить его lines, readкак числа и sum. <$>is fmapоператор - мы используем его вместо обычного приложения функции, потому что уверен, что все это происходит в IO. readнужно дополнительное fmap, потому что оно тоже есть в списке.

$ ghc sum.hs
[1 of 1] Compiling Main             ( sum.hs, sum.o )
Linking sum ...
$ ./sum 
1
2
4
^D
7

Вот странное обновление, чтобы оно работало с поплавками:

main = ((0.0 + ) <$> sum <$> (read <$>) <$> lines <$> getContents) >>= print
$ ./sum 
1.3
2.1
4.2
^D
7.6000000000000005


2

Запуск R скриптов

Я написал R-скрипт для получения аргументов имени файла и суммирования строк.

#! /usr/local/bin/R
file=commandArgs(trailingOnly=TRUE)[1]
sum(as.numeric(readLines(file)))

Это можно ускорить с помощью пакета «data.table» или «vroom» следующим образом:

#! /usr/local/bin/R
file=commandArgs(trailingOnly=TRUE)[1]
sum(data.table::fread(file))
#! /usr/local/bin/R
file=commandArgs(trailingOnly=TRUE)[1]
sum(vroom::vroom(file))

Бенчмаркинг

Те же данные бенчмаркинга, что и у @glenn jackman .

for ((i=0; i<1000000; i++)) ; do echo $RANDOM; done > random_numbers

По сравнению с приведенным выше вызовом R запуск R 3.5.0 в качестве сценария сопоставим с другими методами (на том же сервере Linux Debian).

$ time R -e 'sum(scan("random_numbers"))'  
 0.37s user
 0.04s system
 86% cpu
 0.478 total

R скрипт с readLines

$ time Rscript sum.R random_numbers
  0.53s user
  0.04s system
  84% cpu
  0.679 total

R скрипт с data.table

$ time Rscript sum.R random_numbers     
 0.30s user
 0.05s system
 77% cpu
 0.453 total

R скрипт с vroom

$ time Rscript sum.R random_numbers     
  0.54s user 
  0.11s system
  93% cpu
  0.696 total

Сравнение с другими языками

Для справки здесь, как некоторые другие методы, предложенные на том же оборудовании

Python 2 (2.7.13)

$ time python2 -c "import sys; print sum((float(l) for l in sys.stdin))" < random_numbers 
 0.27s user 0.00s system 89% cpu 0.298 total

Python 3 (3.6.8)

$ time python3 -c "import sys; print(sum((float(l) for l in sys.stdin)))" < random_number
0.37s user 0.02s system 98% cpu 0.393 total

Рубин (2.3.3)

$  time ruby -e 'sum = 0; File.foreach(ARGV.shift) {|line| sum+=line.to_i}; puts sum' random_numbers
 0.42s user
 0.03s system
 72% cpu
 0.625 total

Perl (5.24.1)

$ time perl -nle '$sum += $_ } END { print $sum' random_numbers
 0.24s user
 0.01s system
 99% cpu
 0.249 total

Awk (4.1.4)

$ time awk '{ sum += $0 } END { print sum }' random_numbers
 0.26s user
 0.01s system
 99% cpu
 0.265 total
$ time awk '{ sum += $1 } END { print sum }' random_numbers
 0.34s user
 0.01s system
 99% cpu
 0.354 total

C (clang версия 3.3; gcc (Debian 6.3.0-18) 6.3.0)

 $ gcc sum.c -o sum && time ./sum < random_numbers   
 0.10s user
 0.00s system
 96% cpu
 0.108 total

Обновление с дополнительными языками

Луа (5.3.5)

$ time lua -e 'sum=0; for line in io.lines() do sum=sum+line end; print(sum)' < random_numbers 
 0.30s user 
 0.01s system
 98% cpu
 0.312 total

tr (8.26) должен быть рассчитан в bash, несовместим с zsh

$time { { tr "\n" + < random_numbers ; echo 0; } | bc; }
real    0m0.494s
user    0m0.488s
sys 0m0.044s

sed (4.4) должен быть рассчитан в bash, несовместим с zsh

$  time { head -n 10000 random_numbers | sed ':a;N;s/\n/+/;ta' |bc; }
real    0m0.631s
user    0m0.628s
sys     0m0.008s
$  time { head -n 100000 random_numbers | sed ':a;N;s/\n/+/;ta' |bc; }
real    1m2.593s
user    1m2.588s
sys     0m0.012s

примечание: кажется, что вызовы sed работают быстрее в системах с большим объемом доступной памяти (обратите внимание на меньшие наборы данных, используемые для тестирования sed)

Юлия (0.5.0)

$ time julia -e 'print(sum(readdlm("random_numbers")))'
 3.00s user 
 1.39s system 
 136% cpu 
 3.204 total
$  time julia -e 'print(sum(readtable("random_numbers")))'
 0.63s user 
 0.96s system 
 248% cpu 
 0.638 total

Обратите внимание, что, как и в R, методы файлового ввода-вывода имеют разную производительность.


2

C ++ "однострочник":

#include <iostream>
#include <iterator>
#include <numeric>
using namespace std;

int main() {
    cout << accumulate(istream_iterator<int>(cin), istream_iterator<int>(), 0) << endl;
}

1

Еще один для развлечения

sum=0;for i in $(cat file);do sum=$((sum+$i));done;echo $sum

или только другой удар

s=0;while read l; do s=$((s+$l));done<file;echo $s

Но решение awk, вероятно, лучше, поскольку оно наиболее компактно.


1

С всегда побеждает по скорости:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv) {
    ssize_t read;
    char *line = NULL;
    size_t len = 0;
    double sum = 0.0;

    while (read = getline(&line, &len, stdin) != -1) {
        sum += atof(line);
    }

    printf("%f", sum);
    return 0;
}

Время для чисел 1М (та же машина / вход, что и у моего ответа на Python):

$ gcc sum.c -o sum && time ./sum < numbers 
5003371677.000000
real    0m0.188s
user    0m0.180s
sys     0m0.000s

1
Лучший ответ! Лучшая скорость)
Fortran

1

С рубином:

ruby -e "File.read('file.txt').split.inject(0){|mem, obj| mem += obj.to_f}"

Другой вариант (когда ввод из STDIN) ruby -e'p readlines.map(&:to_f).reduce(:+)'.
Нисетама

0

Я не знаю, сможете ли вы стать намного лучше, чем это, учитывая, что вам нужно прочитать весь файл.

$sum = 0;
while(<>){
   $sum += $_;
}
print $sum;

1
Очень читабельно. Для перл. Но да, это должно быть что-то в этом роде ...
dmckee --- котенок экс-модератора

$_переменная по умолчанию. Оператор ввода строки, <>по умолчанию помещает свой результат туда, когда вы используете <>в while.
Брайан Д. Фой

1
@Mark, $_это переменная темы - она ​​работает как 'it'. В этом случае <> присваивает ему каждую строку. Он используется во многих местах, чтобы уменьшить беспорядок в коде и помочь с написанием однострочников. Сценарий говорит: «Установите сумму на 0, прочитайте каждую строку и добавьте ее к сумме, затем напечатайте сумму».
daotoad

1
@Stefan, при отключенных предупреждениях и ограничениях вы можете пропустить объявление и инициализацию $sum. Поскольку это так просто, вы даже можете использовать модификатор оператора while:$sum += $_ while <>; print $sum;
daotoad

0

Я не проверял это, но это должно работать:

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

Возможно, вам придется добавить «\ n» в строку перед bc (например, через echo), если bc не обрабатывает EOF и EOL ...


2
Не работает bcвыдает синтаксическую ошибку из-за завершающего "+" и отсутствия новой строки в конце. Это сработает и исключит бесполезное использование cat: { tr "\n" "+" | sed 's/+$/\n/'| bc; } < numbers2.txt или <numbers2.txt tr "\n" "+" | sed 's/+$/\n/'| bc
приостановлено до дальнейшего уведомления.

tr "\n" "+" <file | sed 's/+$/\n/' | bc
ghostdog74

0

Вот еще один:

open(FIL, "a.txt");

my $sum = 0;
foreach( <FIL> ) {chomp; $sum += $_;}

close(FIL);

print "Sum = $sum\n";

0

Вы можете сделать это с помощью Alacon - утилиты командной строки для базы данных Alasql .

Он работает с Node.js, поэтому вам нужно установить Node.js а затем пакет Alasql :

Для расчета суммы из файла TXT вы можете использовать следующую команду:

> node alacon "SELECT VALUE SUM([0]) FROM TXT('mydata.txt')"

0

Не проще заменить все новые строки на +, добавить 0и отправить их Rubyинтерпретатору?

(sed -e "s/$/+/" file; echo 0)|irb

Если у вас его нет irb, вы можете отправить его на адрес bc, но вы должны удалить все новые строки, кроме последнего (из echo). Лучше использовать trдля этого, если у вас нет докторской степени sed.

(sed -e "s/$/+/" file|tr -d "\n"; echo 0)|bc

0

На ходу:

package main

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
)

func main() {
    scanner := bufio.NewScanner(os.Stdin)
    sum := int64(0)
    for scanner.Scan() {
        v, err := strconv.ParseInt(scanner.Text(), 10, 64)
        if err != nil {
            fmt.Fprintf(os.Stderr, "Not an integer: '%s'\n", scanner.Text())
            os.Exit(1)
        }
        sum += v
    }
    fmt.Println(sum)
}

Что такое "64"? «10» Я полагаю, это база?
Питер К

Да, 10 является базой. 64 - это число битов, если результирующее int не может быть представлено с таким количеством битов, возвращается ошибка. См. Golang.org/pkg/strconv/#ParseInt
dwurf

0

Баш вариант

raw=$(cat file)
echo $(( ${raw//$'\n'/+} ))

$ wc -l file
10000 file

$ time ./test
323390

real    0m3,096s
user    0m3,095s
sys     0m0,000s

0

В оболочке, использующей awk, я использовал приведенный ниже скрипт для этого:

    #!/bin/bash


total=0;

for i in $( awk '{ print $1; }' <myfile> )
do
 total=$(echo $total+$i | bc )
 ((count++))
done
echo "scale=2; $total " | bc
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.