Проблемы со сравнением 2 строк в Perl - PullRequest
0 голосов
/ 22 февраля 2012

Я хочу посчитать количество общих строк, существующих между двумя файлами, используя Perl.

У меня есть 1 базовый файл, используемый для сравнения, если в файле A есть все строки (разделенные новой строкой \ n). То, что я сделал, это поместил все строки из базового файла в хэш base_config, а строки из файла A в хеш конфигурации. Я хочу сравнить это для всех ключей в% config, его также можно найти в ключах% base_config. Чтобы сделать сравнение ключей более эффективным, я отсортировал ключи в% base_config и поместил их в @ sorted_base_config.

Однако для некоторых файлов, которые имеют точно такие же строки, но в другом порядке, я не могу получить правильный счет. Например, Базовый файл содержит:

hello
hi
tired
sleepy

, тогда как файл A содержит:

hi
tired
sleepy
hello

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

$count=0;
while(($key,$value)=each(%config))
{
    foreach (@sorted_base_config) 
    {
        print "config: $config{$key}\n";
                print "\$_: $_\n";
        if($config{$key} eq $_)
        {
            $count++;
        }
    }
}

Может кто-нибудь сказать мне, если я сделал какую-либо ошибку? Предполагается, что счет равен 4, но он постоянно печатает 2.

EDIT: Вот мой оригинальный код, который не работал. Это выглядит совсем по-другому, потому что я пытался использовать разные методы для решения проблемы. Тем не менее, я все еще застрял в той же проблеме.

#open base config file and load them into the base_config hash
open BASE_CONFIG_FILE, "< script/base.txt" or die;
my %base_config;
while (my $line=<BASE_CONFIG_FILE>) {
   (my $word1,my $word2) = split /\n/, $line;
   $base_config{$word1} = $word1;
}
#sort BASE_CONFIG_FILE
@sorted_base_config = sort keys %base_config;

#open config file and load them into the config hash
open CONFIG_FILE, "< script/hello.txt" or die;
my %config;
while (my $line=<CONFIG_FILE>) {
   (my $word1,my $word2) = split /\n/, $line;
   $config{$word1} = $word1;
}
#sort CONFIG_FILE
@sorted_config = sort keys %config;

%common={};
$count=0;
while(($key,$value)=each(%config))
{
    $num=keys(%base_config);
    $num--;#to get the correct index
    #print "$num\n";
    while($num>=0)
    {
        #check if all the strings in BASE_CONFIG_FILE can be found in CONFIG_FILE
        $common{$value}=$value if exists $base_config{$key};
        #print "yes!\n" if exists $base_config{$key};
        $num--;
    }
}
print "count: $count\n";

while(($key,$value)=each(%common))
{
    print "key: ".$key."\n";
    print "value: ".$value."\n";
}
$num=keys(%common)-1;
print "common lines: ".$num;

Ранее я помещал общие ключи, которые существуют и в файле base_config, и в файле A, в% common. Я хотел напечатать общие ключи в текстовый файл в будущем, и все, что найдено в файле A, но не найдено в файле base_config, будет выведено в другой текстовый файл. Однако я уже застрял на начальном этапе поиска общих ключей.

Я использую "\ n" для разделения на ключи для хранения, поэтому я не могу использовать функцию chomp, которая удалит "\ n".

РЕДАКТИРОВАТЬ 2: Я только что понял, что не так с моим кодом. В конце моих текстовых файлов мне нужно добавить «\ n», чтобы это работало. Спасибо за вашу помощь! : D

Ответы [ 4 ]

3 голосов
/ 22 февраля 2012

Я думаю, что ваша попытка повысить эффективность на самом деле замедляет процесс.

my %listA;

# Read first file (name in $NameA)
{
    open my $fileA, '<', "$NameA" or die $!;
    while (<$fileA>)
    {
        chomp;
        $listA{$_}++;
    }
}

# Read second file (name in $NameB)
{
    open my $fileB, '<', "$NameB" or die $!;
    while (<$fileB>)
    {
        chomp;
        if ($listA{$_})
        {
            print "Line appears in $NameB once and $listA{$_} times in $NameA: $_\n";
        }
    }
}

Если вы тоже хотите прочитать второй файл в хеш, то это также работает:

Теперь, если определенная строка появляется в обоих файлах, она будет указана в списке. Обратите внимание, что, хотя я представляю ключи в отсортированном порядке, я использую поиск по хешу, потому что это будет быстрее, чем перетасовка через два отсортированных массива. Конечно, вам будет сложно измерить разницу в 4-строчных файлах. А для больших файлов существует вероятность того, что время ввода-вывода при чтении файлов и печати результатов будет доминировать во времени поиска.

my %listB;

# Read second file (name in $NameB)
{
    open my $fileB, '<', "$NameB" or die $!;
    while (<$fileB>)
    {
        chomp;
        $listB{$_}++;
    }
}

foreach my $key (sort keys %listA)
{
    if ($listB{$key})
    {
        print "$NameA: $listA{$key}; $NameB: $listB{$key}; $key\n";
    }
}

Реорганизовать вывод по вашему желанию.

Непроверенный код! Код проверен - см. Ниже.


Преобразовано в тестовый код

Данные: Файл A

hello
hi
tired
sleepy

Данные: ФайлB

hi
tired
sleepy
hello

Программа: ppp.pl

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

my $NameA = "fileA";
my $NameB = "fileB";

my %listA;

# Read first file (name in $NameA)
{
    open my $fileA, '<', "$NameA" or die "Failed to open $NameA: $!\n";
    while (<$fileA>)
    {
        chomp;
        $listA{$_}++;
    }
}

# Read second file (name in $NameB)
{
    open my $fileB, '<', "$NameB" or die "Failed to open $NameB: $!\n";
    while (<$fileB>)
    {
        chomp;
        if ($listA{$_})
        {
            print "Line appears in $NameB once and $listA{$_} times in $NameA: $_\n";
        }
    }
}

выход

$ perl ppp.pl
Line appears in fileB once and 1 times in fileA: hi
Line appears in fileB once and 1 times in fileA: tired
Line appears in fileB once and 1 times in fileA: sleepy
Line appears in fileB once and 1 times in fileA: hello
$

Обратите внимание, что это перечисляет вещи в порядке fileB, как и следует, учитывая, что цикл читает fileB и проверяет каждую строку по очереди.

Код: qqq.pl

Это второй фрагмент, превращенный в законченную рабочую программу.

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

my $NameA = "fileA";
my $NameB = "fileB";

my %listA;

# Read first file (name in $NameA)
{
    open my $fileA, '<', "$NameA" or die "Failed to open $NameA: $!\n";
    while (<$fileA>)
    {
        chomp;
        $listA{$_}++;
    }
}

my %listB;

# Read second file (name in $NameB)
{
    open my $fileB, '<', "$NameB" or die "Failed to open $NameB: $!\n";
    while (<$fileB>)
    {
        chomp;
        $listB{$_}++;
    }
}

foreach my $key (sort keys %listA)
{
    if ($listB{$key})
    {
        print "$NameA: $listA{$key}; $NameB: $listB{$key}; $key\n";
    }
}

Выход:

$ perl qqq.pl
fileA: 1; fileB: 1; hello
fileA: 1; fileB: 1; hi
fileA: 1; fileB: 1; sleepy
fileA: 1; fileB: 1; tired
$

Обратите внимание, что ключи перечислены в отсортированном порядке, который не является порядком ни в файле A, ни в файле B.

Незначительные чудеса иногда случаются! Помимо добавления 5 строк преамбулы (shebang, 2 x using, 2 x my), код для обоих фрагментов программы работал правильно, в соответствии с моим первым расчетом для обеих программ. (О, и я улучшил сообщения об ошибках при невозможности открыть файл, по крайней мере, указав, какой файл мне не удалось открыть. И ikegami отредактировал мой код (спасибо!), Чтобы последовательно добавлять вызовы chomp, и символы новой строки для операций print, которые теперь нуждаются в явном переводе строки.)

Я бы не стал утверждать, что это отличный Perl-код; это конечно не выиграет (код) соревнование по гольфу. Это, кажется, работает, хотя.


Анализ кода в вопросе

open BASE_CONFIG_FILE, "< script/base.txt" or die;
my %base_config;
while (my $line=<BASE_CONFIG_FILE>) {
   (my $word1,my $word2) = split /\n/, $line;
   $base_config{$word1} = $word1;
}

Разделение странное ... у вас есть строка, которая заканчивается новой строкой, и вы разделяете ее на новой строке, поэтому $word2 пусто, а $word1 содержит остаток строки. Затем вы сохраняете значение $word1 (а не $word2, как я предполагал на первый взгляд) в базовой конфигурации. Таким образом, ключ и значение одинаковы для каждой записи. Необычный. Не совсем неправильно, но ... необычно. Второй цикл по сути один и тот же (мы оба должны быть застрелены за то, что не использовали ни одного сабвуфера для чтения).

Вы не можете использовать use strict; и use warnings; - обратите внимание, что практически первое, что я сделал с моим кодом, это добавил их. Я программирую на Perl всего около 20 лет, и я знаю, что не знаю достаточно, чтобы рисковать запускать код без них. Ваши отсортированные массивы, %common, $count, $num, $key, $value не являются my d. Возможно, в этот раз это не навредит, но ... это плохой знак. Всегда, но всегда используйте use strict; use warnings;, пока вы не узнаете достаточно о Perl, чтобы вам не нужно было задавать вопросы по этому поводу (и не ожидайте, что это произойдет в ближайшее время).

Когда я запускаю его, в точке, где есть:

my %common={};  # line 32 - I added diagnostic printing
my $count=0;

Perl говорит мне:

Reference found where even-sized list expected at rrr.pl line 32, <CONFIG_FILE> line 4.

Упс - эти {} должны быть пустым списком (). Узнайте, почему вы запускаете с включенными предупреждениями!

А потом, на

 50 while(my($key,$value)=each(%common))
 51 {
 52     print "key: ".$key."\n";
 53     print "value: ".$value."\n";
 54 }

Perl говорит мне:

key: HASH(0x100827720)
Use of uninitialized value $value in concatenation (.) or string at rrr.pl line 53, <CONFIG_FILE> line 4.

Это первая запись в %common броске вещей для цикла.


Фиксированный код: rrr.pl

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

#open base config file and load them into the base_config hash
open BASE_CONFIG_FILE, "< fileA" or die;
my %base_config;
while (my $line=<BASE_CONFIG_FILE>) {
   (my $word1,my $word2) = split /\n/, $line;
   $base_config{$word1} = $word1;
   print "w1 = <<$word1>>; w2 = <<$word2>>\n";
}

{ print "First file:\n"; foreach my $key (sort keys %base_config) { print "$key => $base_config{$key}\n"; } }

#sort BASE_CONFIG_FILE
my @sorted_base_config = sort keys %base_config;

#open config file and load them into the config hash
open CONFIG_FILE, "< fileB" or die;
my %config;
while (my $line=<CONFIG_FILE>) {
   (my $word1,my $word2) = split /\n/, $line;
   $config{$word1} = $word1;
   print "w1 = <<$word1>>; w2 = <<$word2>>\n";
}
#sort CONFIG_FILE
my @sorted_config = sort keys %config;

{ print "Second file:\n"; foreach my $key (sort keys %base_config) { print "$key => $base_config{$key}\n"; } }

my %common=();
my $count=0;
while(my($key,$value)=each(%config))
{
    print "Loop: $key = $value\n";
    my $num=keys(%base_config);
    $num--;#to get the correct index
    #print "$num\n";
    while($num>=0)
    {
        #check if all the strings in BASE_CONFIG_FILE can be found in CONFIG_FILE
        $common{$value}=$value if exists $base_config{$key};
        #print "yes!\n" if exists $base_config{$key};
        $num--;
    }
}
print "count: $count\n";

while(my($key,$value)=each(%common))
{
    print "key: $key -- value: $value\n";
}
my $num=keys(%common);
print "common lines: $num\n";

Выход:

$ perl rrr.pl
w1 = <<hello>>; w2 = <<>>
w1 = <<hi>>; w2 = <<>>
w1 = <<tired>>; w2 = <<>>
w1 = <<sleepy>>; w2 = <<>>
First file:
hello => hello
hi => hi
sleepy => sleepy
tired => tired
w1 = <<hi>>; w2 = <<>>
w1 = <<tired>>; w2 = <<>>
w1 = <<sleepy>>; w2 = <<>>
w1 = <<hello>>; w2 = <<>>
Second file:
hello => hello
hi => hi
sleepy => sleepy
tired => tired
Loop: hi = hi
Loop: hello = hello
Loop: tired = tired
Loop: sleepy = sleepy
count: 0
key: hi -- value: hi
key: tired -- value: tired
key: hello -- value: hello
key: sleepy -- value: sleepy
common lines: 4
$
0 голосов
/ 22 февраля 2012

использовать Список :: Сравнить

0 голосов
/ 22 февраля 2012

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

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

Этот код:

#!C:\Perl\bin\perl
use strict;
use warnings;

my $f1 = $ARGV[0];
my $f2 = $ARGV[1];
my %config_base;
my %config;
my $line;
print "F1 = $f1\nF2 = $f2\n";

open F1, '<', $f1 || die;
while ($line = <F1>) {
chomp $line;
print "adding $line\n";
$config_base{$line}=$line;
}
close F1;
open F2, '<', $f2 || die;
while ($line = <F2>) {
chomp $line;
print "adding $line\n";
$config{$line}=$line;
}
close F2;
my $count=0;
my $key; my $value;
my @sorted_base_config = sort keys %config_base;
while(($key,$value)=each(%config))
{
    foreach (@sorted_base_config) 
    {
        print "config: $config{$key}\n";
                print "\$_: $_\n";
        if($config{$key} eq $_)
        {
            $count++;
        }
    }
}
print "Count = $count\n";

Результат:

F1 = config_base.txt
F2 = config.txt
adding hello
adding hi
adding tired
adding sleepy
adding hi
adding tired
adding sleepy
adding hello
config: hi
$_: hello
config: hi
$_: hi
config: hi
$_: sleepy
config: hi
$_: tired
config: hello
$_: hello
config: hello
$_: hi
config: hello
$_: sleepy
config: hello
$_: tired
config: tired
$_: hello
config: tired
$_: hi
config: tired
$_: sleepy
config: tired
$_: tired
config: sleepy
$_: hello
config: sleepy
$_: hi
config: sleepy
$_: sleepy
config: sleepy
$_: tired
Count = 4

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

В этом случае у вас будет что-то вроде:

foreach my $key (keys %config_base) 
    {
        print "config: $config{$key}\n";
                print "\$_: $key\n";
        if(exists $config{$key})
        {
            $count++;
        }
    }
print "Count = $count\n";
0 голосов
/ 22 февраля 2012

Может быть, это не тот подход, который вы ищете, но что, если вы пошли на это больше так:

#!/usr/bin/perl
use Data::Dumper;
use warnings;
use strict;

my @sorted_base_config = qw(hello hi tired sleepy);
my @file_a = qw(hi tired sleepy hello);
my @found_in_both = ();

foreach (@sorted_base_config) {
  if (grep /$_/, @file_a) {
    push(@found_in_both, $_);
  }
}

print "These items were found in file_a:\n";
print Dumper(@found_in_both);

По сути, вместо хеширования ключ / значение ... почему бы не попробовать использовать два массива и использовать foreach для массива базовых файлов. Проходя каждую строку @sorted_base_config, вы проверяете, можно ли найти строку в @ file_a.

Вам решать, как вы хотите получить файлы в массивы @sorted_base_config и @file_a (и как обращаться с символами новой строки или переносами строк). Но при таком способе, по крайней мере, кажется, что более точная проверка того, какие слова соответствуют.

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