Объединение записей на основе столбца с помощью awk / join

Вот два взаимоисключающих цикла sed:

sed -ne'p;/ 12 * 31 /!d;:n' -e'n;//!bn' <<""
YEAR MONTH DAY RES
1971 1     1   245
1971 1     2   587
...
1971 12    31  685
1971 1     1   245
1971 1     2   587
...
1971 12    31  685
1972 1     1   549
1972 1     2   746
...
1972 12    31  999
1972 1     1   933
1972 1     2   837
...
1972 12    31  343

YEAR MONTH DAY RES
1971 1     1   245
1971 1     2   587
...
1971 12    31  685
1972 1     1   549
1972 1     2   746
...
1972 12    31  999

В основном sed имеет два состояния - print и eat. В первом состоянии - состоянии print - sed автоматически prints каждую входную строку, затем проверяет ее на соответствие шаблону / 12 * 31 /. Если текущее место шаблона ! не совпадает, он dудаляется и sed втягивает следующую строку ввода и начинает скрипт заново сверху - с команды print, не пытаясь выполнить ничего, что следует за командой delete.

Когда строка ввода does соответствует / 12 * 31 /, однако, sed переходит ко второй половине сценария - циклу eat. Сначала он определяет ветвь : метки с именем n; затем перезаписывает текущее пространство шаблона входной строкой next, а затем сравнивает текущее пространство шаблона с // последнего совпавшего шаблона. Поскольку предыдущая совпавшая строка была перезаписана строкой next, первая итерация этого цикла eat не совпадает, и каждый раз ! не sed bвозвращается к метке :n, чтобы получить входную строку next и снова сравнить ее с // последним совпавшим шаблоном.

Когда, наконец, будет найдено еще одно совпадение - примерно 365 next строк спустя - sed автоматически -not напечатает его, когда завершит свой сценарий, втянет следующую входную строку и снова начнет сверху с команды print в своем первом состоянии. Таким образом, каждое состояние цикла будет переходить в следующее на том же ключе и делать как можно меньше действий для поиска следующего ключа.

Обратите внимание, что весь сценарий завершается без вызова ни одной процедуры редактирования, и что он требует только компиляции единственного regexp. Получившийся автомат очень прост - он понимает только

и [^123 ]. Более того, по крайней мере половина сравнений, скорее всего, будет сделана без компиляций, потому что единственный адрес, на который ссылается цикл eat, вообще пустой //. Поэтому sed может полностью завершить этот цикл одним вызовом regexec() на каждую строку ввода. sed may делает подобное и для цикла print.


timed


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

dash <<""
    d=0 D=31 IFS=: set 1970 1
    while   case  "$*:${d#$D}" in (*[!:]) ;;
            ($(($1^($1%4)|(d=0))):1:)
                     D=29 set $1 2;;
            (*:1:)   D=28 set $1 2;;
            (*[3580]:)
                     D=30 set $1 $(($2+1));;
            (*:)     D=31 set $(($1+!(t<730||(t=0)))) $(($2%12+1))
            esac
    do      printf  '%-6d%-4d%-4d%d\n' "$@" $((d+=1)) $((t+=1))
    done|   head    -n1000054 >/tmp/dates

dash <<<''  6.62s user 6.95s system 166% cpu 8.156 total

Это помещает миллион с лишним строк в /tmp/dates и удваивает вывод для каждого из годов 1970 - 3338. Файл выглядит так:

tail -n1465 </tmp/dates | head; echo; tail </tmp/dates

3336  12  27  728
3336  12  28  729
3336  12  29  730
3336  12  30  731
3336  12  31  732
3337  1   1   1
3337  1   2   2
3337  1   3   3
3337  1   4   4
3337  1   5   5

3338  12  22  721
3338  12  23  722
3338  12  24  723
3338  12  25  724
3338  12  26  725
3338  12  27  726
3338  12  28  727
3338  12  29  728
3338  12  30  729
3338  12  31  730

... во всяком случае, часть его.

Затем я попробовал использовать различные команды:

for  cmd in "sort -uVk1,3" \
            "sed -ne'p;/ 12 * 31 /!d;:n' -e'n;//!bn'" \
            "awk '"'{u=$1 $2 $3 $4;if (!a[u]++) print;}'\'
do   eval   "time ($cmd|wc -l)" </tmp/dates
done

500027
( sort -uVk1,3 | wc -l; ) \
1.85s user 0.11s system 280% cpu 0.698 total

500027
( sed -ne'p;/ 12 * 31 /!d;:n' -e'n;//!bn' | wc -l; ) \
0.64s user 0.09s system 110% cpu 0.659 total

500027
( awk '{u=$1 $2 $3 $4;if (!a[u]++) print;}' | wc -l; ) \
1.46s user 0.15s system 104% cpu 1.536 total

Команды sort и sed выполнились менее чем за половину времени awk - и эти результаты были типичными. Я выполнил их несколько раз. Похоже, что все команды также выписывают правильное количество строк - и поэтому, вероятно, все они работают.

sort и sed были довольно хороши - с sed в целом на волосок впереди - по времени завершения для каждого запуска, но sort делает больше фактической работы для достижения своих результатов, чем любая из двух других команд. Она выполняет параллельные задания для выполнения своей задачи и получает большую пользу от моего многоядерного процессора. awk и sed оба используют одноядерный процессор в течение всего времени обработки.

Здесь приведены результаты стандартного, современного GNU sed, но я пробовал и другой. На самом деле, я пробовал все три команды с другими двоичными файлами, но только команда sed действительно работала с моими инструментами heirloom. Остальные, как я полагаю, из-за нестандартного синтаксиса, просто вылетали с ошибкой, не успев начать работу.

Хорошо использовать стандартный синтаксис, когда это возможно - так вы можете свободно использовать более простые, отточенные и эффективные реализации во многих случаях:

PATH=/usr/heirloom/bin/posix2001:$PATH; time ...

500027
( sed -ne'p;/ 12 * 31 /!d;:n' -e'n;//!bn' | wc -l; ) \
0.31s user 0.12s system 136% cpu 0.318 total
3
07.03.2018, 00:30
3 ответа

Con tu enfoque tienes que usar joindos veces (o cambiar tu enfoque para hacerlo con una sola joininvocación):

  • imprime las líneas comunes y las líneas no emparejables de file1conjoin -t'|' -e0 -a1 -o 1.2,1.3,1.5,2.5 <(<file1 awk -F'|' '{print $1"-"$2"|"$0}' | sort -t'|' -k1,1) <(<file2 awk -F'|' '{print $1"-"$2"|"$0}' | sort -t'|' -k1,1)
  • imprime las líneas no emparejables de file2conjoin -t'|' -e0 -v2 -o 2.2,2.3,1.5,2.5 <(<file1 awk -F'|' '{print $1"-"$2"|"$0}' | sort -t'|' -k1,1) <(<file2 awk -F'|' '{print $1"-"$2"|"$0}' | sort -t'|' -k1,1)

Puede hacer lo mismo con una sola invocación awk, almacenando $4en dos matrices indexadas por, p. $1|$2y luego en el bloque ENDiterando sobre cada índice de matriz, comparándolos e imprimiendo en consecuencia:

awk -F'|' 'NR==FNR{z[$1"|"$2]=$4;next}{x[$1"|"$2]=$4}
END{for (j in x){if (!(j in z)){print j, "0", x[j]}};
for (i in z){if (i in x){print i, z[i], x[i]} else {print i, z[i], "0"}}
}' OFS="|"  file1 file2
3
27.01.2020, 21:12

Otro awkenfoque:

awk -F'|' 'NR==FNR{f1[$1FS$2]=$NF;next} {f2[$1FS$2]=$NF} 
    END{for (x in f1){print x,f1[x],f2[x]?f2[x]:0; delete f2[x]};
        for (y in f2) print y, 0, f2[y]
}' file[12] OFS='|'

Explicación:

  • NR==FNR{f1[$1FS$2]=$NF;next}, esto se ejecutará solo para el archivo 1 y con la combinación de teclas de $1FS$2almacenará el valor de la última columna $NFen una matriz llamadaf1(FSque se sustituirá con |como el campo F de awk S operador ).
  • {f2[$1FS$2]=$NF}, igual que arriba pero esto se ejecutará solo para el archivo 2
  • for (x in f1){print x,f1[x],f2[x]?f2[x]:0; delete f2[x]}, haga un bucle dentro de la matriz f1e imprima la clave (x), su valor en el archivo 1 f1[x]y si hay la misma clave del archivo 1 en el archivo 2, imprímala también, de lo contrario imprima0(Condición ternaria utilizadaf2[x]?f2[x]:0), después de eso también estamos eliminando el registro de la misma clave del archivo2 con delete f2[x].
  • for (y in f2) print y, 0, f2[y], ahora la matriz f2tiene registros que existen solo en el archivo2, por lo que estamos imprimiendo su clave (y), 0porque no existe en el archivo1 y su valor en el archivo2 f2[y].
2
27.01.2020, 21:12

Lo siguiente reemplaza el primer |en ambos archivos con@(usa un carácter que no aparece en ninguna otra parte del archivo ), realiza el joiny luego cambia el @de vuelta al original |. De esta forma, creamos un nuevo campo de combinación delimitado|-que consta de las columnas 1 y 2 de los archivos originales.

join -t'|' -e0 -a1 -a2 -o0,1.3,2.3 \
    <( sed 's/|/@/' file1 | sort )  \
    <( sed 's/|/@/' file2 | sort ) |
tr '@' '|'

En la especificación del campo de salida (-o), un cero representa el campo de combinación y la columna 3 en cualquiera de los archivos es en realidad la columna 4 de los datos originales.

Para los archivos de entrada dados, esto genera

1111|AAA|50|10
1111|BBB|30|0
1111|CCC|0|20
2222|BBB|10|0
3333|AAA|0|40
2
27.01.2020, 21:12

Теги

Похожие вопросы