Найти строки без соответствующих номеров в качестве предыдущей строки - PullRequest
2 голосов
/ 07 июня 2019

Я пытаюсь найти в файле строки, номера которых не находятся в предыдущей строке. Этот файл имеет около 400000 строк. Это пример входного файла:

320 5120
240 326 5120
240 326 5120
241 333 514
240 326 5120
240 326 5120
320 5120
240
100 112
240 326 5120
240 326 5120
320 5120 

Ожидаемые выходные результаты:

241 333 514
240 326 5120
240
100 112
240 326 5120

Пока я мог найти эту команду:

$ awk '!seen[$1]++' file 

320 5120
240 326 5120
241 333 514
100 112

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

Ответы [ 6 ]

3 голосов
/ 07 июня 2019

Программа командной строки Perl ("one" -liner), предполагающая наличие в файле не чисел, а чисел

perl -wnE'
    @n = /([0-9]+)/g; 
    say "@n" if not grep { exists $seen_nums{$_} } @n; 
    %seen_nums = map { $_ => 1 } @n
' data.txt

Это печатает желаемый вывод. Также печатается самая первая строка (правильно). Так как программа разбирает строки на числа, она может использоваться для файлов с заголовками, только текстовыми строками (комментариями?) И т. Д.

Но если данные обязательно содержат только цифры, то мы можем использовать -a переключатель Perl , с которым слова в каждой строке доступны в массиве @F. Также немного сжался, чтобы вписаться в линию

perl -wlanE'grep exists $n{$_}, @F or say; %n = map { $_=>1 } @F' data.txt

Краткое описание переключателей (см. Документы, связанные выше)

  • -w включает предупреждения

  • -l снимает символ новой строки и может добавить его обратно, с еще несколькими тонкостями

  • -a включает «автоматическое разделение» (при использовании с -n или -p), так что в программе доступно @F, которое содержит слова в строке. На более новых Perls это также устанавливает -n

  • -n Критично для обработки файлов или STDIN - открывает ресурс и настраивает цикл по строкам. Запустите с -MO=Deparse, чтобы увидеть, что он делает

  • -E -e - это то, что заставляет его оценивать все между следующими кавычками как код Perl. С капиталом (E) он также включает функцию s, которую я использую в основном для say. (У этого есть недостатки, так как он включает все функции и делает вещи обратно несовместимыми.)


Примечание: первая строка может быть опущена путем добавления условия $.!=2 к отпечатку

1 голос
/ 07 июня 2019

Вот решение awk:

$ awk 'NR>1{p=1; for (i=1;i<=NF;i++){if($i in a)p=0}} {delete a; for (i=1;i<=NF;i++)a[$i]} p' file
241 333 514
240 326 5120
240
100 112
240 326 5120

Как это работает

  • NR>1{...}

    Выполнять команды в фигурных скобках для всех, кроме первой строки. Эти команды:

    • p=1

      Инициализировать p в true (ненулевое)

    • for (i=1;i<=NF;i++){if($i in a)p=0}

      Если какое-либо поле является ключом в массиве a, тогда установите p в false (ноль).

  • delete a

    Удалить массив a.

  • for (i=1;i<=NF;i++)a[$i]

    Создать ключ в массиве a для каждого поля в текущей строке.

  • p

    Если p истинно, выведите строку.

Многострочная версия

Или для тех, кто предпочитает, чтобы их код распределялся по нескольким строкам:

awk '
    NR>1{
        p=1
        for (i=1;i<=NF;i++){
            if($i in a)p=0}
        }
    {
        delete a
        for (i=1;i<=NF;i++)
            a[$i]
    }

    p' file
1 голос
/ 07 июня 2019

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

Это включает в себя печать первой строки, как отметил Шон, что может потребоваться.Если нет, просто исключите строку print join(... в коде.

#!/usr/bin/perl
use strict;
use warnings;
use List::Util 'any';

open my $fh, '<', 'f0.txt' or die $!;

my @nums = split ' ', <$fh>;

my %seen = map{ $_ => 1} @nums;

print join(' ', @nums), "\n"; # print the first line

while (<$fh>) {
    @nums = split;
    print unless any {$seen{$_}} @nums;
    %seen = map{ $_ => 1} @nums;
}

close $fh or die $!;

Вывод:

320 512
241 333 514
240 326 512
240
100 112
240 326 512
1 голос
/ 07 июня 2019

Вот Perl с одной строкой:

$ perl -M-warnings -lane 'print unless @F ~~ %prev; %prev = map { $_ => 1 } @F;' input.txt
320 512
241 333 514
240 326 512
240
100 112
240 326 512

Он использует нахмуренный оператор smart match во имя краткости.При использовании smartmatch ARRAY ~~ HASH возвращает true, если какие-либо элементы массива являются ключами в хэше, что идеально подходит для этого варианта использования.Если бы это был автономный скрипт, а не однострочный, я бы, вероятно, использовал другой подход.

(Есть ли причина, по которой первая строка вашего примера ввода не находится в ожидаемом выводе, хотяэто соответствует критерию?)

0 голосов
/ 07 июня 2019

Учитывая ваш обновленный ввод:

$ awk '$0 !~ p; {gsub(/ /,"|"); p="(^| )("$0")( |$)"}' file
241 333 514
240 326 5120
240
100 112
240 326 5120

Вышеприведенное просто преобразует предыдущую строку, прочитанную в регулярное выражение, например (^| )(320|5120)( |$), а затем выполняет сравнение регулярного выражения, чтобы увидеть, соответствует ли текущая строка ей, и печатает текущую строку, если она не соответствует измененной предыдущей строке. Этот подход приведет к ложным совпадениям, если ваши поля содержат метасимволы RE, которые, очевидно, не ваши, так как они состоят из всех цифр

0 голосов
/ 07 июня 2019

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

  • предыдущая строка сохраняется в переменной t
  • , если какое-либо из полей соответствует предыдущей строке, мы можем перейти к следующей строке.

Это делается следующим образом:

$ awk '{for(i=1;i<=NF;++i) if (FS t FS ~ FS $i FS) {t=$0; next}; t=$0}1'
320 512
241 333 514
240 326 512
240
100 112
240 326 512

Хитрость в том, чтобы заставить его работать, чтобы убедиться, что линия начинается и останавливается с разделителем полей.Если бы мы выполнили тест t ~ $i, мы могли бы сопоставить число 25 с числом 255. Но, убедившись, что все числа находятся между разделителями полей, мы можем просто выполнить тест FS t FS ~ FS $i FS.

примечание: если вы не хотите печатать первую строку, замените последнюю 1 на (FNR>1)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...