Считайте уникальные связанные значения в awk (или perl)

Я уже нашел «Как распечатать инкрементное количество вхождений уникальных значений в столбце 1» , что похоже на мой вопрос, но ответа недостаточно для моих целей.

Сначала позвольте мне просто проиллюстрировать, что я хочу сделать:

# Example input apple abc jkl apple xyz jkl apple abc xyz apple qrs xyz apple abc jkl banana abc lmno banana lmnop xyz banana lmnopq jkl banana abc jkl banana lmnop pqrs banana abcdefg tuv cucumber abc lmno cucumber abc jkl cucumber abc xyz cucumber abcd jkl cucumber abc jkl cucumber abc lmno # Desired output apple 3 2 banana 4 5 cucumber 2 3 

Итак, для каждого отдельного значения поля 1 напечатайте это поле и количество уникальных связанных значений для поля 2, а затем для поля 3.

Ввод сортируется по первому полю, но сортировка по другим полям запрещена (и не принесет пользы, так как нужно обрабатывать 2-й и 3-й поля).

Я бы скорее сделал это в awk ; это, вероятно, намного проще в perl, и мне интересно узнать, как это сделать, но я имею дело с awk-скриптом, и я бы предпочел не переписывать все это.

Я придумал один метод, который работает , но довольно длинный и кажется очень хриплым для меня. Я отправлю это в качестве ответа (когда я вернусь в офис), но мне бы хотелось увидеть какие-либо на самом деле хорошие подходы. (Я не считаю себя «хорошим»).

5 Solutions collect form web for “Считайте уникальные связанные значения в awk (или perl)”

С awk :

 awk 'function p(){print l,c,d; delete a; delete b; c=d=0} NR!=1&&l!=$1{p()} ++a[$2]==1{c++} ++b[$3]==1{d++} {l=$1} END{p()}' file 

Объяснение:

  • function p() : определяет функцию p() , которая печатает значения и удаляет используемые переменные и массивы.
  • NR!=1&&l!=$1 если его не первая строка и переменная l равны первому полю $1 , затем запустите функцию p() .
  • ++a[$2]==1{c++} если приращение значения элемента массива с индексом $2 равно 1 , тогда это значение сначала видно и, следовательно, увеличивает c переменную. ++ перед элементом возвращает новое значение, поэтому вызывает приращение перед сравнением с 1 .
  • ++b[$3]==1{d++} то же, что и выше, но с третьим полем и переменной d .
  • {l=$1} l в первое поле (для следующей итерации выше)
  • END{p()} после обработки последней строки awk должен распечатать значения для последнего блока

С учетом вашего ввода выход:

 apple 3 2 banana 4 5 cucumber 2 3 

Мне нравятся имена пробелов и описательных переменных. Что тут еще можно сказать? Прошло некоторое время с тех пор, как я написал много awk , я даже забыл о -f на shebang. Однако, поскольку я сделал это, я действительно чувствовал, что я был в дзэн этого. Код Haiku.

Мне нравится это решение, потому что есть минимальная закодированная логика. Только два для циклов, повторяющихся по индексам массива. Нет 3-х шаговых шагов for циклов, без операторов if , без явных сопоставлений значений. Все эти вещи статистически коррелируют с дефектами программного обеспечения (ошибки). Интересно, что нет явных назначений и только одна математическая операция, приращение на счет. Я думаю, что это все указывает на максимальное использование языковых функций.

Я чувствую, что что-то может отсутствовать, но я еще не смог найти в нем никаких дыр.

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

 #!/usr/bin/awk -f function count(seen, unique_count) { for (ij in seen) { split(ij, fields, SUBSEP) ++unique_count[fields[1]] } } { seen2[$1,$2] seen3[$1,$3] } END { count(seen2, count2) count(seen3, count3) for (i in count3) { print i, count2[i], count3[i] } } 

аннотирование

Я думаю, одна уникальная особенность этого скрипта заключается в том, что seen2 и seen3 не содержат данных, а только индексов. Это потому, что мы только подсчитываем уникальные значения, поэтому единственное, что имеет значение, это то, что значения были видны, нам все равно, сколько раз они происходят.

 #!/usr/bin/awk -f 

Функция count принимает массив, seen , проиндексированный двумя значениями полей, встречающимися во входных записях, либо полями 1 и 2, либо полями 1 и 3, и возвращает массив, внутренне называемый unique_count индексированный первым полем, содержащий подсчеты уникального поля значения для столбца, накопленного вторым индексом:

 function count(seen, unique_count) { 

Функция count выполняет итерацию по индексам seen массива:

  for (ij in seen) { 

Разделение индекса на два исходных значения, поле 1 и поле 2 или поле 3:

  split(ij, fields, SUBSEP) 

Увеличение счетчика для элемента, проиндексированного по полю 1:

  ++unique_count[fields[1]] } } 

В каждой встречной строке мы создаем пустой элемент массива, если он еще не существует, индексируется по первому полю и либо второе, либо третье поле. Сохраните отдельный массив ( seen2 и seen3 ) для каждого подсчитанного номера поля. Для каждого уникального значения в данном столбце (2 или 3) будет только один элемент массива:

 { seen2[$1,$2] seen3[$1,$3] } 

В конце данных подсчитайте количество уникальных полей, видимых для каждого столбца:

 END { 

Передайте массивы, накопленные от входа, к функции count и получите count2 или count3 заполненные уникальными подсчетами полей.

  count(seen2, count2) count(seen3, count3) 

Шаг через любой из count2 или count3 массивов (не имеет значения, так как все они имеют первое поле каждой строки) и распечатывают поле 1, а затем подсчитывают уникальные значения для каждой строки, содержащей поле 1:

  for (i in count3) { print i, count2[i], count3[i] } } 

Версия с одним лайнером

 awk 'function c(s,u){for(x in s){split(x,f,SUBSEP); ++u[f[1]];}} {a[$1,$2]; b[$1,$3];} END {c(a,d); c(b,e); for(i in d){print i,d[i],e[i];}}' 
 perl -lane 'undef $h{ $F[0] }[ $_ - 1 ]{ $F[$_] } for 1,2 }{ for my $k (keys %h) { print join " ", $k, map scalar keys $_, @{ $h{$k} } }' < input 

В принципе, вы создаете хэш следующим образом:

  'apple' => [ { 'abc' => undef, 'xyz' => undef, 'qrs' => undef }, { 'jkl' => undef, 'xyz' => undef } ], 'banana' => [ { 'abcdefg' => undef, 'lmnop' => undef, 'lmnopq' => undef, 'abc' => undef }, { 'lmno' => undef, 'pqrs' => undef, 'tuv' => undef, 'jkl' => undef, 'xyz' => undef } ], 'cucumber' => [ { 'abcd' => undef, 'abc' => undef }, { 'lmno' => undef, 'jkl' => undef, 'xyz' => undef } ] 

А потом просто пересчитайте ключи для каждого внутреннего хэша.

Я, вероятно, займусь чем-то вроде этого:

 #!/usr/bin/env perl use strict; use warnings; use XML::Twig; my %count_of_col2; my %count_of_col3; #iterate data while (<DATA>) { #split on whitespace my ( $key, $col2, $col3 ) = split; #update counts $count_of_col2{$key}{$col2}++; $count_of_col3{$key}{$col3}++; } foreach my $key ( sort keys %count_of_col2 ) { print join( "\t", $key, #keys gives us all the elements - we use scalar to count them. scalar keys %{ $count_of_col2{$key} }, scalar keys %{ $count_of_col3{$key} } ), "\n"; } __DATA__ apple abc jkl apple xyz jkl apple abc xyz apple qrs xyz apple abc jkl banana abc lmno banana lmnop xyz banana lmnopq jkl banana abc jkl banana lmnop pqrs banana abcdefg tuv cucumber abc lmno cucumber abc jkl cucumber abc xyz cucumber abcd jkl cucumber abc jkl cucumber abc lmno 

Примечание. Поддерживается только два столбца. Вы можете делать произвольные числа, если хотите, но тогда вам, вероятно, понадобится внешний массив.

Как и было обещано, вот такой подход, который я сделал , прежде чем писать этот вопрос. Это работает, и, возможно, это хороший подход, но он казался слишком запутанным для этой, казалось бы, простой задачи. Сейчас кажется, что это не так уж плохо. 🙂

 function printcounts() { printf "%s", currentf1 for (i = 2; i <= 3; i++ ) { printf "%s", FS countuniq [ i ] } printf "\n" } function resetvars() { delete already_seen_value for ( i = 2; i <= 3; i++ ) { countuniq [ i ] = 0 } } $1 != currentf1 { if ( NR != 1 ) { printcounts() } currentf1 = $1 resetvars() } { for ( i = 2; i <= 3; i++ ) { if ( ! already_seen_value [ i ":" $i ] ) { already_seen_value [ i ":" $i ] ++ countuniq [ i ] ++ } } } END { printcounts() } 

С изменениями, основанными на ответе хаоса :

 function printcounts() { printf "%s", currentf1 for (i = 2; i <= 3; i++ ) { printf "%s", FS countuniq [ i ] + 0 } printf "\n" # Reset vars delete seenthis delete countuniq } NR != 1 && currentf1 != $1 { printcounts() } { for ( i = 2; i <= 3; i++ ) { if ( ++ seenthis [ i ":" $i ] == 1 ) { countuniq [ i ] ++ } } currentf1 = $1 } END { printcounts() } 

( + 0 в функции printcounts состоит в том, чтобы гарантировать, что число всегда печатается, так как фактический пример использования включает разделитель поля запятой и игнорирование пустых полей, поэтому на самом деле возможно нулевое количество.)

  • Скопируйте файлы, созданные сегодня, без команды FIND и SFTP на другой сервер
  • Хотя цикл для целых чисел с пользовательским вводом
  • Использование Sed с регулярным выражением
  • Скрипт для автоматического входа ssh в определенный порт
  • Запустите несколько экранов и прикрепите их для разделения изображения
  • Разберите несколько тысяч строк txt в строки и столбцы
  • Команда SSH ведет себя по-разному в Expect Script
  • Сценарий оболочки для чтения вывода команды
  • Bash Script: нет такого файла или каталога при определении переменной через источник vars.txt
  • Найти и обновить запись cron, используя скрипт
  • Как работает awk?! ++?
  • Interesting Posts

    Почему «du -b» показывает другой размер, чем «правый щелчок» ⇨ «свойства» внутри файловых браузеров?

    Как заменить элемент <title> во многих файлах HTML?

    где в Ubuntu 14.04 для установки env vars будет использоваться по умолчанию для всех процессов и демонов

    sudo бесконечно зависает без запроса пароля

    Получение отказа в доступе при попытке добавить текст в файл с помощью sudo

    Блоки туннелей IPsec через некоторое время без ошибок. Где найти детали?

    linux + fstab + cifs

    Как обнаружить и остановить пользователя от удаления определенного файла в Linux?

    Как показать только скрытые каталоги, а затем найти скрытые файлы отдельно

    Конфигурация ssh от Ubuntu 12.04 не работает 14.04.4

    Как ввести символ степени в X11 (с использованием раскладки английской клавиатуры по умолчанию)?

    Установите LXQt на Centos 7 (из EPEL)

    Мастер-сценарий среды, основанный на $ LOGNAME для запуска любого вызова оболочки

    Найти только сообщения от symlink

    Продолжить цикл с помощью клавиатуры

    Linux и Unix - лучшая ОС в мире.