Возьмите сумму, основанную на определенных столбцах.

Возьмите сумму, основанную на определенных столбцах.

У меня огромная куча данных — около 30–40 миллионов элементов данных. Нам нужно обработать эти файлы и отправить другой команде интерфейса.

Ниже представлен мой формат файла, который мы получаем

c1  c2  c3  c4  c5  c6
A   B   C   D   5   s
A   B   C   D   4   s
A   B   E   F   5   s
A   B   E   F   8   S
C   D   E   F   9   S

Мне нужно распечатать в моем выходном файле все столбцы. Поскольку это связано с использованием GPRS, нам нужно сгруппировать пос1 - с4и затем, если все совпадает, нам нужно суммироватьс5и распечатать все в выходном файле.

Ниже приведен пример выходного файла.

c1  c2  c3  c4  c5  c6
A   B   C   D   9   s
A   B   E   F   13  s
C   D   E   F   9   s

Я также слышал, что этот рабочий процесс выполняется намного быстрее в Perl, чем в скриптах Unix.

решение1

Другое perlрешение, похожее на ответ @terdon, но с лучшим форматом вывода:

$ perl -alne '
    (print && next) if $. == 1;   
    $h{"@F[0..3]"}{s} += $F[4];
    $h{"@F[0..3]"}{t}  = $F[5];
    END {
        for (keys %h) {
            printf "%-4s%-4s%-4s%-4s%-4s%-4s",split(" ",$_),$h{$_}{s},$h{$_}{t};                        
            printf "\n";
        }
    }' file
c1  c2  c3  c4  c5  c6
A   B   E   F   13  S   
A   B   C   D   9   s   
C   D   E   F   9   S

решение2

Что касается выбора инструментов: обычно, чем более специализирован инструмент, тем он быстрее. Так что каналы, включающие tr, cut, grep, , и т. д. , как правило, быстрее, чем sort, , , . Но это, конечно, во многом зависит и от задачи. Если вы читаете, что Perl быстрее, то либо вы неправильно поняли, либо сравнение было с циклом оболочки, который обрабатывает одну строку за раз (это определенно будет медленно для файлов с миллионами строк).sedawkperlpythonruby

Если ваши входные данные представлены в форме, где строки для объединения являются последовательными, то awk — хороший выбор (в sed нет разумного способа выполнять сложения).

awk -v OFS='\t' '                      # use tabs to separate output fields
    NR==1 {print; next}                # keep the first line intact
    function flush () {                # function to print a completed sum
        if (key != "") print previous, sum, more;
        sum=0
    }
    {key = $1 OFS $2 OFS $3 OFS $4}    # break out the comparison key
    key!=previous {flush()}            # if the comparison key has changed, print the accumulated sum
    {previous=key; sum+=$5; more=$6}   # save the current line
    END {flush()}                      # print the last 
'

Если строки не последовательны, вы можете сделать их таковыми с помощью сортировки. Типичные sortреализации высоко оптимизированы и быстрее, чем манипулирование структурами данных в языках высокого уровня.

sort | awk …

Это предполагает, что ваши разделители столбцов согласованы, например, всегда табуляция. Если это не так, либо предварительно обработайте ввод, чтобы сделать их таковыми, либо используйте sort -k1,1 -k2,2 -k3,3 -k4,4для сравнения этих конкретных полей, не принимая во внимание разделители.

решение3

Это может вам помочь начать:

perl -ane '$h{"@F[0 .. 3]"} += $F[4] }{ print "$_ $h{$_}\n" for keys %h' input-file

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

решение4

Если я правильно вас понял, вы хотите что-то вроде этого:

$ perl -lane 'if($.>1){$k{"@F[0..3]"}{sum}+=$F[4]; $k{"@F[0..3]"}{last}=$F[5]}
              else{print "@F"}
              END{
                foreach (keys(%k)){ print "$_ $k{$_}{sum} $k{$_}{last}"}
              }' file
c1 c2 c3 c4 c5 c6
C D E F 9 S
A B E F 13 S
A B C D 9 s

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

Объяснение

  • perl -lane: -lудаляет символы новой строки из конца каждой строки и добавляет их к каждому printоператору. aразбивает каждую входную строку на поля по пробелам и сохраняет поля в массиве @F. nСредствапрочитайте входной файл построчно и примените скрипт, заданный-e.

Вот тот же однострочный текст в форме прокомментированного скрипта:

#!/usr/bin/env perl

## This is the equivalent of perl -ne
## in the one-liner. It iterates through
## the input file.
while (<>) {
    
    ## This is what the -a flag does
    my @F=split(/\s+/);
    ## $. is the current line number.
    ## This simply tests whether we are on the
    ## first line or not.
    if ($.>1) {
    ## @F[0..3] is an array slice. It holds fields 1 through 4.
    ## The slice is used as a key for the hash %k and the 5th
    ## field is summed to $k{slice}{sum} while the last column is 
    ## saved as $k{slice}{last}.
    $k{"@F[0..3]"}{sum}+=$F[4]; $k{"@F[0..3]"}{last}=$F[5];
    }

    ## If this is the first line, print the fields.
    ## I am using print "@F" instead of a simple print 
    ## so that all lines are formatted in the same way.
    else {
    print "@F\n";
    }
}

## This is the same as the END{} block
## in the one liner. It will be run after
## the whole file has been read.

## For each of the keys of the hash %k
foreach (keys(%k)){ 
    ## Print the key ($_, a special variable in Perl),
    ## the value of $k{$key}{sum} (the summed values),
    ## and the last column.
    print "$_ $k{$_}{sum} $k{$_}{last}\n"
}


    

Связанный контент