Bilden Sie eine Summe basierend auf bestimmten Spalten

Bilden Sie eine Summe basierend auf bestimmten Spalten

Ich habe einen riesigen Datenberg – etwa 30 bis 40 Millionen Datenelemente. Wir müssen diese Dateien verarbeiten und an ein anderes Schnittstellenteam senden.

Unten ist mein Dateiformat, das wir erhalten

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

Ich muss in meiner Ausgabedatei alle Spalten drucken. Da dies mit der GPRS-Nutzung zusammenhängt, müssen wir nachc1 – c4und wenn dann alles passt, müssen wir diec5, und drucken Sie alles in der Ausgabedatei.

Unten sehen Sie ein Beispiel für eine Ausgabedatei.

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

Ich habe auch gehört, dass dieser Arbeitsablauf in Perl viel schneller funktioniert als mit Unix-Skripten.

Antwort1

Eine andere perlLösung, ähnlich der Antwort von @terdon, aber mit besserer Formatausgabe:

$ 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

Antwort2

Zur Auswahl der Tools: Normalerweise ist ein Tool umso schneller, je spezialisierter es ist. Pipes mit tr, cut, grep, sort, usw. sind also tendenziell schneller als sedwelche tendenziell schneller sind als awkwelche tendenziell schneller sind als perl, python, ruby. Aber das hängt natürlich auch stark von der Aufgabe ab. Wenn Sie lesen, dass Perl schneller ist, dann haben Sie entweder etwas falsch gelesen oder der Vergleich bezog sich auf eine Shell-Schleife, die jeweils eine Zeile verarbeitet (was bei Dateien mit Millionen von Zeilen definitiv langsam sein wird).

Wenn Ihre Eingabe in einem Format erfolgt, bei dem die zusammenzuführenden Zeilen aufeinander folgen, ist awk eine gute Wahl (es gibt keine vernünftige Möglichkeit, Additionen in sed durchzuführen).

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 
'

Wenn die Zeilen nicht aufeinander folgen, können Sie sie durch Sortieren aufeinander folgen lassen. Typische sortImplementierungen sind hoch optimiert und schneller als die Manipulation von Datenstrukturen in höheren Programmiersprachen.

sort | awk …

Dies setzt voraus, dass Ihre Spaltentrennzeichen einheitlich sind, z. B. immer ein Tabulator. Wenn dies nicht der Fall ist, verarbeiten Sie die Eingabe entweder vorab, um sie einheitlich zu machen, oder verwenden Sie sort -k1,1 -k2,2 -k3,3 -k4,4zum Vergleichen dieser spezifischen Felder, ohne die Trennzeichen zu berücksichtigen.

Antwort3

Dies könnte Ihnen den Einstieg erleichtern:

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

Die letzte Spalte wird nicht gedruckt, da Sie nicht angegeben haben, was damit geschehen soll. Außerdem wird die Kopfzeile nicht richtig verarbeitet, aber das sollte sich leicht beheben lassen.

Antwort4

Wenn ich Sie richtig verstehe, möchten Sie so etwas:

$ 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

Dadurch bleiben Ihre Spalten nicht ausgerichtet. Ich weiß nicht, ob das für Sie ein Problem darstellt. Die Kopfzeile wird jedoch korrekt verarbeitet und sollte die gewünschte Ausgabe erzeugen.

Erläuterung

  • perl -lane: Das -lentfernt Zeilenumbrüche am Ende jedes Strings und fügt sie jeder printAnweisung hinzu. Das ateilt jede Eingabezeile in Felder mit Leerzeichen auf und speichert die Felder im Array @F. Das nbedeutetLesen Sie die Eingabedatei Zeile für Zeile und wenden Sie das von-e.

Hier ist derselbe Einzeiler in kommentierter Skriptform:

#!/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"
}


    

verwandte Informationen