Adicionando palavras 'exception' à regra do script perl matching titles

2

Eu tenho usado este script perl (graças a Jeff Schaller) para combinar 3 ou mais palavras nos campos de título de dois arquivos csv separados como respondido aqui:

Correspondência de 3 ou mais palavras de campos em arquivos csv separados

O script é:

#!/usr/bin/env perl

my @csv2 = ();
open CSV2, "<csv2" or die;
@csv2=<CSV2>;
close CSV2;

my %csv2hash = ();
for (@csv2) {
  chomp;
  my ($title) = $_ =~ /^.+?,\s*([^,]+?),/; #/ match the title 
  $csv2hash{$_} = $title;
}

open CSV1, "<csv1" or die;
while (<CSV1>) {
  chomp;
  my ($title) = $_ =~ /^.+?,\s*([^,]+?),/; #/ match the title 
  my @titlewords = split /\s+/, $title;    #/ get words
  my $desired = 3;
  my $matched = 0;
  foreach my $csv2 (keys %csv2hash) {
    my $count = 0;
    my $value = $csv2hash{$csv2};
    foreach my $word (@titlewords) {
      ++$count if $value =~ /\b$word\b/i;
      last if $count >= $desired;
    }
    if ($count >= $desired) {
      print "$csv2\n";
      ++$matched;
    }
  }
  print "$_\n" if $matched;
}
close CSV1;

Desde então, percebi que gostaria de ignorar certas palavras entre os títulos e não classificá-las como palavras correspondentes. Eu tenho usado o sed para removê-los antes que os arquivos csv sejam comparados, mas isso não é ideal, pois perco dados no processo. Como posso adicionar palavras que seriam consideradas exceções a esse script perl? Por exemplo, digamos que eu quisesse que o script ignorasse as três palavras separadas and if e the ao corresponder os títulos para que fossem exceções à regra.

    
por nmh 24.05.2016 / 17:45

2 respostas

2

Depois da linha

my @titlewords = split /\s+/, $title;    #/ get words

adicione o código para remover as palavras da matriz:

my @new;
foreach my $t (@titlewords){
    push(@new, $t) if $t !~ /^(and|if|the)$/i;
}
@titlewords = @new;
    
por 24.05.2016 / 18:07
0

Isso é muito parecido com a resposta do @ meuh, mas em vez de adicionar um foreach loop após a linha split , você só precisa adicionar uma linha lá, usando a função perl grep ou sua map function:

@titlewords = grep (!/^(and|if|the)$/i, @titlewords);

ou

@titlewords = map { /^(and|if|the)$/i ? () : $_ } @titlewords;

Veja perldoc -f grep e perldoc -f map para mais detalhes sobre essas funções e as diferenças entre elas. Eles são comumente usados (especialmente map ) em muitos scripts perl , então vale a pena dedicar um tempo para entender o que eles fazem e aprender como eles funcionam.

BTW, não NÃO use #!/usr/bin/env perl . Usar env como isso é ruim o suficiente com python e ruby scripts (onde está, infelizmente, a convenção), mas está completamente quebrado para perl scripts e definitivamente NÃO é a maneira convencional de executá-los. / p>

perl tem muitas opções de linha de comando que alteram seu comportamento de maneira significativa, dependendo do tipo de programa que você está tentando escrever. Usar env para executar um interpretador como perl destrói completamente a capacidade de passar opções de linha de comando ao interpretador (porque env não oferece suporte a ele. env nem foi projetado para ser usado para essa finalidade, fazer isso é apenas um hack feio que tira proveito de um efeito colateral do propósito real de env - que é definir variáveis de ambiente antes de executar um programa).

Use #!/usr/bin/perl (ou qualquer que seja o caminho para o seu interpretador perl ).

Aqui está outro script perl que faz o que você quer - mas este usa os módulos Class::CSV e List::Compare , bem como dois Hashes-of-Arrays para comparar os arquivos CSV:

#! /usr/bin/perl

use strict;
use warnings;

use Class::CSV;
use List::Compare;

sub parse_csv($%) {
  my($filename,$tw) = @_;

  # exclude the following word list and the "empty word"
  my @exceptions = qw(and if the);
  my $exceptions = '^(\s*|' . join('|',@exceptions) . ')$';


  my $csv = Class::CSV->parse(
      filename => $filename,
      fields   => [qw/id title num1 num2/]
  );

  # build a hash-of-arrays (HoA), keyed by the CSV line. Each array
  # contains the individual words from each title for that line (except
  # for those matching $exceptions).  The words are all converted to
  # lowercase to enable case-insensitive matches.
  foreach my $line (@{$csv->lines()}) {

    # The following three lines are required because the input file has
    # fields separated by ', ' rather than just ',' which makes
    # Class::CSV interpret the numeric fields as strings.
    # It's easier/quicker to do this than to rewrite using Text::CSV.
    #
    # The final output will be properly-formed CSV, with only a comma as
    # field separator and quotes around the title string.
    my $key = join(',',$line->id,'"'.$line->title.'"',$line->num1,$line->num2);
    $key =~ s/([",])\s+/$1/g;   # trim whitespace immediately following " or ,
    $key =~ s/\s+([",])/$1/g;   # trim whitespace immediately preceding " or ,

    # If it wasn't for the not-quite-right CSV format, we could just use:
    #my $key = $line->string;

    push @{ $tw->{$key} }, grep (!/$exceptions/oi, split(/\s+/,$line->title));
  };
};

# two hashes to hold the titlewords HoAs
my %tw1=();
my %tw2=();

parse_csv('csv1',\%tw1);
parse_csv('csv2',\%tw2);

# now compare the HoAs
foreach my $k2 (sort keys %tw2) {
  my @matches = ();
  foreach my $k1 (sort keys %tw1) {
    my $lc = List::Compare->new('-u', \@{ $tw2{$k2} }, \@{ $tw1{$k1} });
    push @matches, $k1 if ($lc->get_intersection ge 3);
  };
  print join("\n",sort(@matches,$k2)),"\n\n" if (@matches);
};

Saída:

11,"The Sun Still Shines in Reading",64312,464566
97,"Reading Still Shines",545464,16748967

Cada grupo de correspondências é classificado e, embora a saída da amostra não mostre (porque há apenas um grupo de correspondências), cada grupo é impresso como um parágrafo separado (isto é, separado por uma linha em branco)

BTW, se você não quiser as aspas duplas em torno dos campos de título, edite a linha my $key=join(...) que as adiciona para que não seja.

    
por 25.05.2016 / 04:38