Идентификация строк с дублирующимися записями в столбце 1 и обмен столбцами, если они найдены

Вопрос или проблема

У нас есть следующий текстовый файл:

172.55.34.48 172.55.33.95
172.55.32.163 172.55.34.48
172.55.32.163 172.55.33.95

Что нам нужно сделать, так это поменять местами два столбца, если в первом столбце найдено значение, которое уже встречалось ранее в этом столбце. Для каждого такого значения (в данном случае это IP-адреса) максимальное количество повторений составляет два.

В приведённом примере нам нужно поменять 172.55.33.95 местами с 172.55.32.163, так как 172.55.32.163 уже был найден в предыдущей строке.

Я попробовал

awk 'prev && ($1 != prev) {print seen[prev]} {seen[$1] = $0; prev = $1} END {print seen[$1]}' /tmp/new.txt 

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

Ответ может быть таким простым, как эта программа awk:

$ awk '{found[$1]++; if (found[$1]>1) {buf=$1; $1=$2; $2=buf}} 1' input.txt 
172.55.34.48 172.55.33.95
172.55.32.163 172.55.34.48
172.55.33.95 172.55.32.163

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

Для этого значение первого столбца сохраняется в буфере, заменяется значением второго столбца, а значение второго столбца в свою очередь заменяется сохранённым значением. В конце текущая строка выводится с учётом всех изменений (если таковые имеются), что и означает кажущийся “блуждающим” 1.

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

awk '{f[$1]++; if (f[$1]>1) {if (f[$2]>0) {next}; buf=$1; $1=$2; $2=buf; f[$1]++; f[$2]--}} 1' input.txt

На Perl вы могли бы сделать:

perl -lae 'BEGIN {$, = " "} scalar(grep {$_ eq $F[0]} @buf) ? print(reverse(@F)) : do {print(@F); push(@buf, $F[0])}' input
  • -l: автоматически убирает и добавляет разделитель полей по умолчанию (\n) перед и после каждой оценки скрипта, переданного в -e;
  • -a: автоматически разбивает каждую строку по разделителю полей ввода по умолчанию ( ).

Скрипт установит разделитель выходных полей на , затем для каждой строки он проверит, если первое поле уже хранится в @buf.

Если это так, он развернёт текущую запись и напечатает её; в противном случае он напечатает текущую запись и добавит первое поле в @buf.

% perl -lae 'BEGIN {$, = " "} scalar(grep {$_ eq $F[0]} @buf) ? print(reverse(@F)) : do {print(@F); push(@buf, $F[0])}' input
172.55.34.48 172.55.33.95
172.55.32.163 172.55.34.48
172.55.33.95 172.55.32.163

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

$ awk 'seen[$1]++{t=$2; $2=$1; $1=t} 1' file
172.55.34.48 172.55.33.95
172.55.32.163 172.55.34.48
172.55.33.95 172.55.32.163

или, возможно, вы хотите:

$ awk 'seen[$1]++{t=$2; $2=$1; $1=t; seen[$1]++} 1' file
172.55.34.48 172.55.33.95
172.55.32.163 172.55.34.48
172.55.33.95 172.55.32.163

или:

$ awk 'seen[$1]++{t=$2; $2=$1; $1=t; seen[$2]--; seen[$1]++} 1' file
172.55.34.48 172.55.33.95
172.55.32.163 172.55.34.48
172.55.33.95 172.55.32.163

или:

$ awk '$1==prev{t=$2; $2=$1; $1=t} {prev=$1} 1' file
172.55.34.48 172.55.33.95
172.55.32.163 172.55.34.48
172.55.33.95 172.55.32.163

Ответ или решение

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

Решение на awk

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

awk '{found[$1]++; if (found[$1]>1) {if (found[$2]>0) {next}; buf=$1; $1=$2; $2=buf; found[$1]++; found[$2]--}} 1' input.txt

Пояснение:

  • found[$1]++ — увеличиваем счетчик для текущего значения первого столбца.
  • if (found[$1]>1) — проверяем, встречалось ли это значение ранее.
  • Если значение во втором столбце ($2) уже встречалось, считаем, что нужно пропустить эту строку, чтобы избежать создания новых дублирующихся значений.
  • В противном случае, производим обмен значений в столбцах.
  • 1 в конце командного блока awk заставляет awk напечатать каждую строку, включая модифицированные.

Решение на Perl

В Perl можно использовать следующий скрипт для реализации аналогичной логики:

perl -lae 'BEGIN {$, = " "} scalar(grep {$_ eq $F[0]} @buf) ? print(reverse(@F)) : do {print(@F); push(@buf, $F[0])}' input.txt

Пояснение:

  • BEGIN {$, = " "} — устанавливает разделитель вывода на пробел.
  • scalar(grep {$_ eq $F[0]} @buf) — проверяет, встречается ли первое значение в массиве @buf.
  • Если оно уже существует, выводится обратный порядок полей, а если нет, текущая строка печатается и значение добавляется в @buf.

Заключение

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

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

Оцените материал
Добавить комментарий

Капча загружается...