2016-05-05 1 views
1

Я ищу, чтобы найти все строки CSV-файла, содержащие дубликаты данных в двух или более полях этой строки (т. Е. Найти все строки без уникальных данных в каждом поле.)Каков наиболее эффективный способ поиска строк CSV, не содержащих повторяющихся записей в полях этой строки (исключая пробел)?

Например, у меня есть следующий CSV файл:

John,Smith,Smith,21 
Mary,Jones,Smith,32 
John,42,42,42 
Henry,Brown,Jones,31 
Mary,,,21 

Я хочу следующие строки для печати:

John,Smith,Smith,21 
John,42,42,42 

Эти строки печатаются, так как данные в одном поле этих строк происходит в другое поле. Обратите внимание, что «Mary ,,, 21» не было напечатано, даже если оно содержит повторяющиеся пустые поля.

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

ответ

5

Использование perl:

perl -F, -lane 'my %s; print if grep { $s{$_}++ } @F' 

Применение:

  • -F, установить разделитель поля для ,
  • -l для автоматической обработки переводы строки
  • -a в Autosplit
  • -n, чтобы обернуть его в while (<>) { цикле.
  • -e, чтобы указать код для exec.

Входящие данные Autosplit на , в @F и мы используем %s хэш, чтобы определить, есть ли боян.

Если — на основе вашего комментария — вам нужно пропустить пустые поля (которые будут засчитаны простофилями):

perl -F, -lane 'my %s; print if grep { /./ ? $s{$_}++ :() } @F' 

Это включает в себя тройной оператор, чтобы проверить, если поле пусто.

Тестирование с ОС Windows (что не совсем то же самое, из-за кавычек):

C:\Users\me>perl -F, -lane "my %s; print qq{line matches:$_} if grep { /./ ? $s{$_}++ :() } @F" 
line matches:John,Smith,Smith,21 
line matches:John,42,42,42 

Если написано обычное письмо, это выглядит примерно так:

#!/usr/bin/env perl 
use strict; 
use warnings; 

while (my $line = <DATA>) { 
    my %seen; 
    chomp($line); 
    my @fields = split /,/, $line; 
    if (grep { /./ and $seen{$_}++ } @fields) { 
     print $line,"\n"; 
    } 
} 

__DATA__ 
John,Smith,Smith,21 
Mary,Jones,Smith,32 
John,42,42,42 
Henry,Brown,Jones,31 
Mary,,,21 

Вы можете использовать Text::CSV модуль, чтобы разобрать его, но я бы предложил не делать этого, если вы не являетесь конкретно, касающийся цитирования/встраиваемых строк и т. д.

.:

#!/usr/bin/env perl 
use strict; 
use warnings; 

use Data::Dumper; 
use Text::CSV; 

my $csv = Text::CSV -> new ({sep_char => ',', eol => "\n", binary => 1}); 

while (my $row = $csv -> getline (\*DATA)) { 
    my %seen; 
    if (grep { /./ and $seen{$_}++ } @$row) { 
     print join ",", @$row, "\n"; 
    } 
} 

__DATA__ 
John,Smith,Smith,21 
Mary,Jones,Smith,32 
John,42,42,42 
Henry,Brown,Jones,31 
Mary,,,21 
1

Использование AWK вы можете сделать:

awk -F, '{delete a; for (i=1;i<=NF;i++) if ($i!="") if ($i in a) {print; next} else a[$i]}' file 

John,Smith,Smith,21 
John,42,42,42 
+0

Это прекрасно работает, за исключением соответствия на пустые поля. Я извиняюсь! Я должен был быть более конкретным. Есть ли способ сделать это без соответствия пустым полям? – Jake

+0

Этого можно легко позаботиться, добавив условие 'if ($ i! =" ")'. Проверьте обновленный ответ. – anubhava

0

Если вы хотите решение Perl, который можно интегрировать в больший сценарий (и не совсем так близко напоминают шум линии), и корректно обрабатывает данные в формате CSV, где поле содержит запятую, Я бы использовать Text::CSV модуль:

#!/usr/bin/perl 
use strict; 
use warnings; 
use Text::CSV; 

my $file = shift or die "Usage: $0 <file>\n"; 

open my $fh, '<', $file or die "Cannot open $file: $!\n"; 

my $csv = Text::CSV->new(); 

while (my $row = $csv->getline($fh)) { 
    my %h; 
    $h{$_}++ for @{$row}; 
    for my $dup_field (grep { $h{$_} > 1 } keys %h) { 
     if (length $dup_field) { 
      print $csv->string(); 
      next; 
     } 
    } 
} 
0

Если вам нравится Perl и регулярные выражения, то это выглядит хорошо:
perl -ne 'print if /(?:^|,)([^,]+),(?:.*,)?\1(?:,|$)/'

Если вам нужны объяснения:
([^,]+) соответствует «слову» (в этом контексте я использую «слово» для обозначения «данных строки»), а \1 будет видеть, повторяется ли оно. (?:.*,)? позволяет использовать другие слова между повторами ваших данных. И, наконец, (?:^|,) и (?:,|$) убедитесь, что два повторяющихся слова одинаковы, и никто не является подстрокой другого.

1
$ awk -F, '{delete seen; for (i=1;i<=NF;i++) if (($i!="") && seen[$i]++) { print; next } }' file 
John,Smith,Smith,21 
John,42,42,42