Самый элегантный однострочный оболочка unix для суммирования списка чисел произвольной точности?

Что касается целочисленного добавления однострочных, существует несколько предложенных решений для сценариев оболочки.
однако при более внимательном рассмотрении каждого из выбранных решений существуют неотъемлемые ограничения:

  • awk будут задыхаться при произвольной точности и целочисленном размере (он ведет себя как C-like, afterall)
  • bc одни скорее будут недовольны сколь угодно длинными входами: (sed 's/$/+\\/g';echo 0)|bc

Понимая, что могут быть проблемы с переносимостью поверх всех платформ (см. [1] [2]), что нежелательно, существует ли общее решение, которое является победителем как практичности, так и краткости?

Подсказка: SunOS и MacOSX - примеры, когда переносимость будет проблемой.
Fi. может ли команда dc разрешать обрабатывать сколь угодно большие значения 2 ^ n, целочисленные или иначе, входы?

[1] awk: qaru.site/info/13024/... или qaru.site/info/13024/... или Печать длинных целых чисел в awk

[2] bc: Bash команда для подсчета столбца чисел

Ответ 1

Обычно я использую paste -sd+|bc:

$ time seq 1 20000000 | paste -sd+|bc
200000010000000

real    0m10.092s
user    0m10.854s
sys     0m0.481s

(Для строгого соответствия Posix paste должен быть предоставлен явный аргумент: paste -sd+ -|bc. По-видимому, это необходимо с реализацией BSD paste, установленной по умолчанию в OS X.)

Однако это приведет к сбою для больших входов, потому что bc буферизует все выражение в памяти перед его оценкой. В моей системе bc закончилась память, пытаясь добавить 100 миллионов номеров, хотя она смогла сделать 70 миллионов. Но другие системы могут иметь меньшие емкости.

Так как bc имеет переменные, вы можете избежать длинных строк, повторяя добавление к переменной вместо создания одного длинного выражения. Это (насколько я знаю) 100% совместимый с Posix, но есть 3-кратное ограничение времени:

$ time seq 1 20000000|sed -e's/^/s+=/;$a\' -es|bc
200000010000000

real    0m29.224s
user    0m44.119s
sys     0m0.820s

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

$ time seq 1 100000000 |
> IFS=+ xargs sh -c 'echo "$*"' _ | bc | paste -sd+ | bc
5000000050000000

real    1m0.289s
user    1m31.297s
sys     0m19.233s

Количество строк ввода, используемых каждой оценкой xargs, будет варьироваться от системы к системе, но обычно оно будет в сотнях, и это может быть намного больше. Очевидно, что вызовы xargs | bc могут быть скованы произвольно для увеличения емкости.

Возможно, необходимо ограничить размер расширения xargs, используя переключатель -s, в системах, где ARG_MAX превышает емкость команды bc. Помимо эксперимента, чтобы установить ограничение на bc, нет никакого переносимого способа установить, какой может быть этот предел, но он должен быть не менее LINE_MAX, который, как гарантируется, должен быть не менее 2048. Даже с 100- которые позволят уменьшить коэффициент в 20 раз, поэтому цепочка из 10 xargs|bc будет обрабатывать более 10 13 добавок, если вы готовы подождать пару месяцев для этого, чтобы завершить.

В качестве альтернативы построению большого конвейера с фиксированной длиной вы можете использовать функцию для рекурсивного вывода вывода из xargs|bc до тех пор, пока не будет создано только одно значение:

radd () { 
    if read a && read b; then
        { printf '%s\n%s\n' "$a" "$b"; cat; } |
          IFS=+ xargs -s $MAXLINE sh -c 'echo "$*"' _ |
          bc | radd
    else
        echo "$a"
    fi
}

Если вы используете очень консервативное значение для MAXLINE, это довольно медленно, но с правдоподобными большими значениями оно не намного медленнее, чем простое решение paste|bc:

$ time seq 1 20000000 | MAXLINE=2048 radd
200000010000000

real    1m38.850s
user    0m46.465s
sys     1m34.503s

$ time seq 1 20000000 | MAXLINE=60000 radd 
200000010000000

real    0m12.097s
user    0m17.452s
sys     0m5.090s

$ time seq 1 100000000 | MAXLINE=60000 radd 
5000000050000000

real    1m3.972s
user    1m31.394s
sys     0m27.946s

Как и решения bc, я приурочил некоторые другие возможности. Как показано выше, при вводе 20 миллионов номеров paste|bc заняло 10 секунд. Это почти идентично времени, используемому при добавлении 20 миллионов номеров с помощью

gawk -M '{s+=$0} END{print s}'

Языки программирования, такие как python и perl, оказались быстрее:

# 9.2 seconds to sum 20,000,000 integers
python -c $'import sys\nprint(sum(int(x) for x in sys.stdin))'
# 5.1 seconds
perl -Mbignum -lne '$s+=$_; END{print $s}'

Я не смог протестировать dc -f - -e '[+z1<r]srz1<rp' на больших входах, поскольку его производительность представляется квадратичной (или хуже); он суммировал 25 тысяч номеров за 3 секунды, но потребовалось 19 секунд, чтобы собрать 50 тысяч и 90 секунд, чтобы сделать 100 тысяч.

Несмотря на то, что bc не самый быстрый, а ограничения памяти требуют неудобных обходных решений, у него есть преимущество в разработке кода в системах, совместимых с Posix, без необходимости установки расширенных версий любой стандартной утилиты (awk) или языки программирования, не требуемые Posix (perl и python).

Ответ 2

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

$ seq 1 20000000 | gawk -M '{s+=$0} END{print s}'
200000010000000

Или Perl с bignum включен:

$ seq 1 20000000 | perl -Mbignum -lne '$s+=$_; END{print $s}'
200000010000000

Ответ 3

Оптимальное решение для dc(1) суммирует входы по мере их чтения:

$ jot 1000000 | sed '2,$s/$/+/;$s/$/p/' | dc
500000500000

Ответ 4

$ seq 1000|(sum=0;while read num; do sum=`echo $sum+$num|bc -l`;done;echo $sum) 500500

Кроме того, этот игрок не получит выигрыш в верхней скорости, однако он:

  • oneliner, да.
  • портативный
  • добавляет списки любой длины
  • добавляет числа любой точности (каждая длина номера ограничена только MAXLINE)
  • не полагается на внешние инструменты, такие как python/perl/awk/R и т.д.

с растяжкой, вы также можете назвать ее элегантной;-) давай, ребята, покажите лучший способ сделать это!

Ответ 5

Кажется, что следующее трюк:

$ seq 1000|dc -f - -e '[+z1<r]srz1<rp'
500500

но является ли это оптимальным решением?