Почему я не могу использовать функцию map для создания хорошего хэша из простого файла данных в Perl? - PullRequest
3 голосов
/ 19 ноября 2009

Запись обновлена. Пожалуйста, перейдите к части «Решение», если вы уже прочитали опубликованный вопрос. Спасибо!

Вот свернутый код, демонстрирующий мою проблему:

Входной файл данных для теста был сохранен встроенным блокнотом Window в кодировке UTF-8. Он имеет следующие три строки:

abacus  æbәkәs
abalone æbәlәuni
abandon әbændәn

Файл сценария Perl также был сохранен встроенным блокнотом Window в кодировке UTF-8. Он содержит следующий код:

#!perl -w

use Data::Dumper;
use strict;
use autodie;
open my $in,'<',"./hash_test.txt";
open my $out,'>',"./hash_result.txt";

my %hash = map {split/\t/,$_,2} <$in>;
print $out Dumper(\%hash),"\n";
print $out "$hash{abacus}";
print $out "$hash{abalone}";
print $out "$hash{abandon}";

В выводе хеш-таблица выглядит нормально:

$VAR1 = {
          'abalone' => 'æbәlәuni
',
          'abandon' => 'әbændәn',
          'abacus' => 'æbәkәs
'
        };

Но на самом деле это не так, потому что я получаю только два значения вместо трех:

æbәlәuni
әbændәn

Perl выдает следующее предупреждение:

Use of uninitialized value $hash{"abacus"} in string at C:\test2.pl line 11, <$i n> line 3.

в чем проблема? Может кто-нибудь любезно объяснить? Спасибо.

Решение

Миллионы благодарностей всем вам, ребята :) Теперь, наконец, виновник найден, и проблема становится решаемой :) Как проницательно указал @Sinan, я теперь на 100% уверен, что виновником возникновения описанной выше проблемы являются два байта спецификации, которые Блокнот добавил в мой файл данных, когда он был сохранен как UTF-8, и который каким-то образом Perl не лечит должным образом. Хотя многие предлагали мне использовать «<: utf8» и «>: utf8» для чтения и записи файлов, дело в том, что эти конфигурации utf-8 не решают проблему. Вместо этого они могут вызвать некоторые другие проблемы.

Чтобы действительно решить проблему, все, что мне действительно нужно, это добавить одну строку кода, чтобы Perl игнорировал спецификацию:

#!perl -w

use Data::Dumper;
use strict;
use autodie;

open my $in,'<',"./hash_test.txt";
open my $out,'>',"./hash_result.txt";

seek $in,3,0; # force Perl to ignore the BOM!
my %hash = map {split/\t/,$_,2} <$in>;
print $out Dumper(\%hash);
print $out $hash{abacus};
print $out $hash{abalone};
print $out $hash{abandon};

Теперь вывод в точности соответствует тому, что я ожидал:

$VAR1 = {
          'abalone' => 'æbәlәuni
',
          'abandon' => 'әbændәn',
          'abacus' => 'æbәkәs
'
        };
æbәkәs
æbәlәuni
әbændәn

Обратите внимание, что сценарий сохраняется в кодировке UTF-8, и в коде нет необходимости включать какие-либо метки utf-8, поскольку входной файл и выходной файл предварительно сохраняются в кодировке UTF-8.

Наконец, еще раз спасибо всем вам. И спасибо, @Sinan, за проницательное руководство. Без вашей помощи я бы остался в неведении, потому что Бог знает, как долго.

Примечание Чтобы уточнить немного больше, если я использую:

open my $in,'<:utf8',"./hash_test.txt";
open my $out,'>:utf8',"./hash_result.txt";

my %hash = map {split/\t/,$_,2} <$in>;
print $out Dumper(\%hash);
print $out $hash{abacus};
print $out $hash{abalone};
print $out $hash{abandon};

Вывод такой:

$VAR1 = {
          'abalone' => "\x{e6}b\x{4d9}l\x{4d9}uni
",
          'abandon' => "\x{4d9}b\x{e6}nd\x{4d9}n",
          "\x{feff}abacus" => "\x{e6}b\x{4d9}k\x{4d9}s
"
        };
æbәlәuni
әbændәn

И предупреждающее сообщение:

Use of uninitialized value in print at C:\hash_test.pl line 13,  line 3.

Ответы [ 5 ]

7 голосов
/ 19 ноября 2009

Я нахожу предупреждение немного подозрительным. Он говорит вам, что файловый дескриптор $in находится в строке 3, когда он должен быть в строке 4 после прочтения последней строки.

Когда я попробовал ваш код, я сохранил входной файл, используя GVim, который сконфигурирован в моей системе для сохранения как UTF-8, я не увидел проблемы. Теперь, когда я попробовал это с Блокнотом, глядя на выходной файл, я вижу:

"\x{feff}abacus" => "\x{e6}b\x{4d9}k\x{4d9}s
"

, где \x{feff} - это спецификация .

В вашем выводе Dumper есть ложный пробел перед abacus (где вы не указали :utf8 для дескриптора вывода).

Как я упоминал ранее (потерян из-за многочисленных изменений в этом посте - спасибо за напоминания), укажите '<:utf8' при открытии входного файла.

2 голосов
/ 19 ноября 2009

Если вы хотите читать / записывать файлы UTF8, вы должны убедиться, что вы действительно читаете их как UTF8 .

#! /usr/bin/env perl
use Data::Dumper;
open my $in,  '<:utf8', "hash_test.txt";
open my $out, '>:utf8', "hash_result.txt";

my %hash = map { chomp; split ' ', $_, 2 } <$in>;
print $out Dumper(\%hash),"\n";
print $out "$hash{abacus}\n";
print $out "$hash{abalone}\n";
print $out "$hash{abandon}\n";

Если вы хотите, чтобы он был более устойчивым, рекомендуется использовать :encoding(utf8) вместо :utf8 для чтения файла.

open my $in, '<:encoding(utf8)', "hash_test.txt";

Прочитайте PerlIO для получения дополнительной информации.

1 голос
/ 19 ноября 2009

Я думаю, что ваш ответ может быть прямо перед вами. Вывод из Data::Dumper, который вы разместили:

$VAR1 = {
          'abalone' => 'æbәlәuni
',
          'abandon' => 'әbændәn',
          'abacus' => 'æbәkәs
'
        };

Обратите внимание на символ между ' и abacus? Вы пытались получить доступ к третьему значению через $hash{abacus}. Это неверно из-за этого символа до abacus в хеше Dumper(). Вы можете попробовать включить его в цикл, который должен позаботиться об этом:

foreach my $k (keys %hash) {
  print $out $hash{$k};
}
0 голосов
/ 19 ноября 2009

У меня работает. Вы уверены, что ваш пример соответствует вашему фактическому коду и данным?

0 голосов
/ 19 ноября 2009

split / \ s / вместо split / \ t /

...