UnixMountainSkiFun

Unix Горы Лыжи

03-10-2008 15:52

Глава 5. Неструктурированные данные


В этой главе рассматриваются:

Простейший вид данных, который может потребоваться обработать, -- это неструктурированные данные. Это данные которые не имеют никакой внутренней структуры, установленной каким-либо особым образом. В некотором смысле, это наиболее сложный вид данных, с которым мы что-либо можем сделать (вообще с такими данными мало, что можно поделать).

Хорошим примером неструктурированных данных является обычный ASCII-файл, содержащий текст. В этой главе мы будем рассматривать некоторые вещи, которые можно сделать с такими файлами.

5.1 Текстовые файлы

Текстовые файлы содержат данные, в читаемом человеком виде. Такой файл может быть создан в текстовом редакторе, вроде vi или emacs в UNIX, Notepad в Windows, или edit в DOS. Вы можете заметить, что файлы, созданные большинством из текстовых процессоров не в формате ASCII-текста, а содержат некоторый проприетарный текстовый формат. (Большинство текстовых процессоров имеют функции сохранения документа в формате ASCII-текста, однако, при этом большая часть форматирования документа будет утеряна.) Кроме того, очень может быть, что такие файлы с ASCII-текстом, были созданы некоторой компьютерной системой или программой.

Файл с ASCII-текстом, как и все остальные файлы данных, ничто иное как последовательность байтов бинарных данных. Программное обеспечение, которым вы воспользуетесь для просмотра файла (например, редактор), будет всего лишь интерпретировать байты данных в ASCII-символы.

5.1.1 Чтение файла

Самое простое, что мы можем сделать с ASCII-файлом -- это прочитать его в некоторую структуру данных, для дальнейших манипуляций. Наиболее подходящий формат для структуры данных зависит, конечно же, от сути данных в файле, а также того, что вы в дальнейшем планируете делать с этими данными. Тем не менее, для читаемого человеком текста, вероятнее всего, наиболее приемлимой структурой будет массив строк. Если вы заинтересуетесь отдельными словами в каждой отдельной строке, то возможно, имеет смысл, расщепить каждую строку на массив слов. Заметим, что при чтении текста, порядок следования слов является важным, именно поэтому в качестве структуры для хранения данных мы используем Perl-массив (который является упорядоченной структурой), вместо, скажем, хэша (который не является упорядоченным).

Пример: Чтение текста в массив массивов

Напишем подпрограмму ввода, которая будет считывать неструктурированный текстовый файл в массив массивов. Как и ранее, мы предполагаем, что файл поступает к нам через STDIN.

 1: sub read_text {
 2:
 3: my @file;
 4:
 5: push @file, [split] while <STDIN>;
 6:
 7: return \@file;
 8: }

Давайте рассмотрим код строку за строкой.

Строка 3 определяет переменную, которая будет содержать массив строк. Каждый элемент этого массива будет ссылкой на другой массив. Каждый элемент таких массивов второго уровня будет содержать одно слово строки.

Строка 5 выполняет основную часть работы. Разобраться с ее работой будет проще, если вы прочитаете ее в обратном порядке. В действительности это сокращение кода, который, будучи развернут, выглядит как нечто следующее:

 while (<STDIN>) {
   my @line = split(/\s+/, $_);
   push @file, [@line];
 }

что может быть несколько проще понять. Для каждой строки в файле, мы выполняем расщепление везде, где бы нам не встретился пробельный символ. Затем создаем анонимный массив, который является копией массива, возвращенного функцией split, и сохраняем ссылку, возвращенную конструктором анонимного массива, в @file.

Помимо всего прочего, в этой строке кода, содержится наше неявное определение слова. В нашем случае, мы используем встроенный символьный класс Perl-а \s, чтобы определить разделитель слов, в виде пробельных символов (вспомните, что split использует разделитель \s+ по умолчанию). Ваше реальное приложение может потребовать чего-либо более сложного.

Строка 7 возвращает ссылку на массив.

Наша новая функция может быть вызвана, например, так:

 my $file = read_text;

теперь мы можем обратиться к любой строке файла так:

 my $line = $file->[$x];

здесь $x содержит номер интересующей нас строки. После этого вызова, $line будет содержать ссылку на массив строки. Другими словами, мы можем обратиться к любому желаемому слову следующим образом:

 my $word = $line->[$y];

или, если использовать исходную ссылку $file:

 my $word = $file->[$x][$y];

Конечно, все это будет хорошо работать, если текстовый файл приемлимого размера, если же вы попытаетесь сохранить весь текст "Войны и мира" в памяти, то это может привести к тому, что компьютер начнет сбрасывать содержимое памяти на диск (прим.перев.: swapping memory to disk), что приведет к замедлению вашей программы. (Хотя, если у вас достаточно памяти, чтобы можно было сохранить весь текст "Войны и мира" в памяти без того, чтобы начался процесс замещения страниц памяти, то такой подход вполне может быть приемлимым решением).

Более тонкое управление процессом ввода

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

 my @file = <FILE>;

Если, вам требуется, чтобы весь текст был сохранен в одной скалярной переменной, то надо обратить внимание на переменную $/. Эта переменная является разделителем записей, поступающих на устройство ввода, и ее значением по-умолчанию является символ новой строки (прим.перев.: newline character). Это означает, что по-умолчанию, данные, будут продолжаться считываться оператором <>, до тех пор, пока не встретится символ новой строки. Присвоение этой переменной undef приведет к тому, что весь поток ввода будет считан целиком. (Отметим, что $/, как и большинство внутренних переменных Perl, является по-умолчанию глобальной, поэтому ее изменение в одном месте, окажет воздействие на всю программу вцелом. По этой причине, хорошей практикой будет использование use local и заключение в фигурные скобки, для того, чтобы убедиться, что все изменения имеют строго ограниченную зону действия (прим.перев.: limited scope).) Другими словами, вы можете считать целиком весь файл следующим образом:

 local $/ = undef;
 my $file = <FILE>;

Можно установить $/ в любое значение, которое найдете более удобным для своих целей. Другим, наиболее часто используемым, значением является пустая строка. Это переключает Perl в режим "абзац" (прим.перев.: paragraph mode), когда пустая строка может использоваться в качестве разделителя входных данных.

Если файл слишком велик, чтобы эффективно разместиться в памяти, то потребуется обрабатывать не более одной строки за один раз (или записи за один раз, если вы изменили $/). Строчно-ориентированные и запись-ориентированные данные будут рассмотрены в следующей главе, однако в оставшейся части этой главы мы будем предполагать, что мы считывает весь файл в память за один раз.

5.1.2 Преобразование текста

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

Пример: Простейшая замена текста

Пусть, мы имеем некий текстовый файл, в котором требуется заменить все упоминания о "Windows" на "Linux", то мы можем написать короткий скрипт вроде такого:

 my $file;

 {
   local $/ = undef;
   $file = <STDIN>;
 }

 $file =~ s/Windows/Linux/g;

 print $file;

Заметим, что секция, которая считывает данные, окружена в блок в фигурных скобках, чтобы обеспечить ограниченную область видимости переменно $/. Кроме того, в команде замены, мы использовали модификатор g, чтобы изменить все упоминания о Windows.

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

5.1.3 Статистические показатели текста

Одним, из числа полезных, действий, которое мы можем выполнить над текстовым файлом, является сбор статистической информации. Довольно просто собрать информацию о количестве строк или слов в файле. Немногим сложнее обнаружить наидлиннейшее слово или сформировать таблицу, в которой подсчитываются упоминания каждого слова. В следующих примерах мы будем предполагать, что файле считан, с использованием функции read_text, которая была определена ранее в данной главе. Эта функция возвращает ссылку на массив массивов. Мы будем разрабатывать скрипт, который подсчитывает строки и слова в файле, а затем готовит отчит о длинах слов и наиболее часто употребляемых словах в тексте.

Пример: сбор текстовой статистики

 1: # Переменные, отслеживающие то место, где мы находимся в файле
 2: my ($line, $word);
 3:
 4: # Переменные, хранящие статистику
 5: my ($num_lines, $num_words);
 6: my (lengths);
 7:
 8: my $text = read_text();
 9:
 10: $num_lines = scalar @;
 11:
 12: foreach $line (@) {
 13:   $num_words += scalar @;
 14:
 15:   foreach $word (@) {
 16:     $words++;
 17:     $lengths{length $word}++;
 18:   }
 19: }
 20:
 21: my @sorted_words = sort { $words <=> $words } keys %words;
 22: my @sorted_lengths = sort { $lengths <=> $lengths } keys %lengths;
 23:
 24: print "Ваш файл содержит $num_lines строк ";
 25: print "и $num_words слов\n\n";
 26:
 27: print "Пятерка наиболее популярных слов:\n";
 28: print map { "$_ ($words раз)\n" } @sorted_words[0 .. 4];
 29:
 30: print "\nПятерка наиболее популярных, по длине, слов:\n";
 31: print map { "$_ ($lengths words)\n" } @sorted_lengths[0 .. 4];

Строка 2 объявляет две переменные, которые мы будем использовать для отслеживания местоположения в файле.

Строки 5 и 6 объявляют четыре переменные, которые мы будем использовать для накопления статистики. $num_lines и $num_words это количество строк и слов в файле. %words это хэш, который будет хранить количество упоминаний каждого слова, упоминающегося в файле. Его ключем будет слово, а значением количество упоминаний слова. %lengths это хэш, который хранит частоту появления слов, с одинаковой длиной, и организован сходным образом.

Строка 8 вызывает нашу функцию read_text, чтобы получить содержимое файла.

Строка 10 вычисляет число строк в файле. Это всего лишь количество элементов в массиве $text.

Строкой 12 начинается цикл перебирающий строку за строкой в массиве.

Строка 13 увеличивает переменную $num_words, на значение, равное количеству элементов в массиве $line. Это значение равно количеству слов в строке.

Строка 15 начинает цикл перебирающий слова в строке.

Строки 16 и 17 наращивают соответствующие элементы в двух хэшах.

Строки 21 и 22 создают два массива, которые содержат ключевые значения хэшей %words и %lengths, отсортированные в порядке убывания соответствующих им хэш-значений.

Строки 24 и 25 печатают общее количество слов и строк в файле.

Строки 27 и 28 печатают пять наиболее популярных слов в файле, используя первые пять элементов в массиве @sorted_words, печатая, кроме того, значения ассоциированные с таким ключем, в хэше %words. Строки 30 и 31 выполняют ту же самую задачу для массива @sorted_lengths.

Пример: вычисление средней длины слова

В качестве последнего примера, сбора текстовой статистики, давайте вычислим среднюю длину слова в файлах. И снова, мы будем использовать, уже существующую функцию read_text, для того, чтобы считать наш текст.

 my ($total_length, $num_words);
 my $text = read_text();

 my ($word, $line);
 foreach $line (@) {
   $num_words += scalar @;

   foreach $word (@) {
     $total_length += length $word;
   }
 }

 printf "Средняя длина слова составляет %.2f\n", $total_length / $num_words;

5.2 Преобразование данных

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

5.2.1 Преобразование кодировки

Большинство текстовых данных, с которыми вы будете иметь дело, будут в виде ASCII, однако вполне возможно, что вы столкнетесь и с другими кодировками (прим.перев.: character set). Если вы обменивались данными с системами, которые работают на, так называемых, IBM mainframe, то вам частенько приходилось конвертировать данные в/из EBCDIC. Кроме того, вы может быть столкнетесь с многобайтовыми символами, если вам придется иметь дело с данными, поступающими из стран, где такие символы являются обычным делом (такие как Китай или Япония).

Unicode

Чтобы поддерживать многобайтовые символы, Perl версии 5.6, включает в себя некоторую поддержку Unicode, посредством модуля utf8. Такое новшество в Perl имело своей целью упростить работу с XML (XML, для определения всех своих символьных данных, использует Unicode формата UTF-8). Если вы имеете более старую версию Perl, то вас могут заинтересовать модули Unicode::Map8 и Unicode::String.

Преобразование между ASCII и EBCDIC

Для преобразования между ASCII и EBCDIC вы можете воспользоваться модулем Convert::EBCDIC, который можно найти на CPAN. Этот модуль может быть использован как в объектном, так и в традиционном варианте. Как традиционный модуль, он экспортирует две функции -- ascii2ebcdic и ebcdic2ascii. Отметим, что эти функции должны быть явно импортированы в ваше пространство имен. В объектном варианте, этот модуль имеет два метода, с названиями toascii и toebcdic. Следующий пример использует традиционный метод, для конвертирования ASCII-данных, поступающих на STDIN, в формат EBCDIC.

 use strict;
 use Convert::EBCDIC qw/ascii2ebcdic/;

 my $data;

 {
   local $/ = undef;
   $data = <STDIN>;
 }

 print ascii2ebcdic($data);

Второй пример использует объектный интерфейс для конвертирования EBCDIC-данных в ASCII.

 use strict;
 use Convert::EBCDIC;

 my $data;
 my $conv = Convert::EBCDIC->new;

 my $data;

 {
   local $/ = undef;
   $data = <STDIN>;
 }

 print $conv->toascii($data);

Конструктор Convert::EBCDIC получает один опциональный параметр, который является 256-символьной строкой, которая определяет таблицу преобразования.

5.2.2 Преобразование окончаний строк

Как я уже говорил выше, ASCII-файл не более чем поток бинарных данных. И только программное обеспечение, которое мы используем для его обработки, может интерпретировать данные таким образом, чтобы получить строки текста. Одним из важнейших символов (или последовательности символов), в текстовом файле, является символ, который отделяет друг от друга различные строки текста. Когда, к примеру, текстовый редактор встречает такой символ в файле, он знает, что следующией символы должны быть отображены начиная с первой колонки, следующей строки, пользовательского дисплея.

Различные варианты признаков окончания строк

С течением времени, два символа становятся наиболее часто используемым признаком окончания строки. Это символы с ASCII-кодами 10 (line feed -- перевод строки) и 13 (carriage return -- возврат каретки). Перевод строки используется в UNIX (и Linux) системах. В Apple Macintoshes используется возврат картеки. DOS и Windows используют комбинацию обоих этих символов, причем перевод строки следует за возвратом каретки.

Такое различие в признаках окончания строк не приводит к проблемам, когда файлы данных используются на той же системе, где они создавались, однако когда вы начинаете передавать файлы данных между различными системами, это может привести к недоразумениям. Вы можете редактировать файл, который был создан под Windows в UNIX-овском текстовом редакторе. И если такое случится, то вы увидите дополнительные символы ^M в конце каждой строки текста. (На данный момент, такое поведение редакторов становится довольно редким, так как большинство редакторов теперь отображают строки без ^M, вместо этого отображая стиль окончания строки в своих статусных строках). Это печатный эквивалент символа возврата каретки, который Windows вставляет перед переводом строки. Сходным образом, UNIX-овский текстовый файл, открытый в Windows Notepad не будет иметь возврата картеки перед переводом строки, и таким образом, Notepad не распознает признак конца строки. Все строки будут следовать одна за одной, сцепленные друг с другом черным прямоугольником, который является способом, которым Windows предоставляет непечатный символ перевода строки.

Есть несколько способов избежать этой проблемы. Например, пересылка файлов меж системами, с использованием FTP в ASCII-режиме, при этом признаки окончания строк будут автоматически конвертироваться в соответствующую форму. И, в большинстве случаев, это гарантирует корректный результат, однако, бывают случаи, когда вам все равно встретятся файлы данных с некорректным, с точки зрения вашей системы, признаком окончания строк. И Perl, конечно же, превосходно подходит для решения этой проблемы.

Пример: простейший фильтр-конвертор признака конца строки

Следующая программа может использоваться как фильтр, чтобы исправлять проблемные файлы. Она использует два параметра, которыми являются признаки конца строки исходной и целевой систем. Это строки CR, LF или CRLF.

В этой программе, вместо использования \n и \r мы используем управляющие последовательности ASCII \cM и \cJ (Ctrl-M и Ctrl-J). Это сделано по той причине, что Perl в данном случае куда умнее, чем мы можем предположить. Когда Perl встречает последовательность \n в прогамме, он конвертирует ее в корректный, с точки зрения текущей системы, признак окончания строки. В большинстве случаев, это весьма полезно (например, это значит, что вам не требуется писать print "some text\r\n";, когда вы используете Perl под управлением Windows). Однако в данной ситуации, такое поведение Perl, будет мешать решению нашей проблемы, поэтому мы используем низкоуровневое представление символов.

 #!/usr/local/bin/perl -w

 use strict;

 (@ARGV == 2) or die "Error: не указаны исходный и/или целевой форматы.";

 my ($src, $tgt) = @ARGV;

 my %conv = (CR =>   "\cM",
             LF =>   "\cJ",
             CRLF => "\cM\cJ");

 $src = $conv;
 $tgt = $conv;

 $/ = $src;

 while (<STDIN>) {
   s/$src/$tgt/go;
   print;
 }

Заметим, что в операторе замены мы используем модификатор o, так как мы знаем, что исходное значение $src не изменится в течение исполнения while-цикла.

5.2.3 Преобразование числовых форматов

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

Распознавание чисел

Каким образом распознать число? Ответ зависит от того, с какого рода числами вы имеете дело. Это целые числа или с плавающей точкой? Могут ли они быть отрицательными? Могут ли они быть в экспоненциальной нотации (такие как, например, 1E6 для 1 * 10^6)? Когда вы ответите на эти вопросы, то сможете построить регулярное выражение, которое будет совпадать с конкретным типом числа, подлежащему обработке.

Чтобы найти совпадение с натуральными числами (положительными целыми), вы можете воспользоваться простым регулярным выражением вроде такого:

 /\d+/

Чтобы найти совпадение с целыми числами (с опциональными знаками +/-), используйте

 /[-+]?\d+/

Чтобы найти свопадение с числами с плавающей точкой, используйте

 /[-+]?(\d+(\.\d*)?|\.\d+)/

Чтобы найти совпадение с числами, которые опционально могут быть представлены в экспоненциальной нотации, используйте

 /[-+]?(?=\d|\.\d)\d*(\.\d*)?([eE]([-+]?\d+))?/

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

 my $num_re = qr/[-+]?(?=\d|\.\d)\d*(\.\d*)?([eE]([-+]?\d+))?/;

 my @nums;
 while ($data =~ /$num_re/g) {
   push @nums, $1;
 }

чтобы вывести список всех чисел в $data.

Если у вас есть функция reformat, которая изменяет число в желаемый вами формат, то можно воспользоваться кодом, вроде этого:

 $data =~ s/$num_re/reformat($1)/ge;

который, помимо всего прочего, использует модификатор e, чтобы исполнить строку замещения, перед тем как воспользоваться ей.

Преобразование чисел с использованием sprintf

Простейший способ преобразовать формат числа состоит в использовании функции sprintf. Такой подход позволит вам зафиксировать количество десятичных позиций; заполнить начальную часть числа пробелами или нулями; выровнять число к правой или левой части, внутри некоторого поля. Вот пример, некоторых действий, которые вы можете выполнить:

 my $number = 123.456789;

 my @fmts = ('0.2f', '.2f', '10.4f', '-10.4f');

 foreach (@fmts) {
   my $fmt = sprintf "%$_", $number;
   print "$_: [$fmt]\n";
 }

который выдаст следующий результат:

 0.2f: [123.46]
 .2f: [123.46]
 10.4f: [ 123.4568]
 -10.4f: [123.4568 ]

(Здесь используются квадратные скобки, чтобы в точности показать начало и конец каждого поля.)

Форматирование чисел, с использованием модулей CPAN

Кроме того, имеется пара модулей, доступных на CPAN, которые позволят вам выполнить куда более изощренные преобразования чисел. Это Convert::SciEng и Number::Format.

Convert::SciEng

Convert::SciEng -- это модуль, для преобразования чисел из/в формат, в котором они имеют постфиксную букву, указывающую амплитуду (прим.перев.: magnitde) числа. Такое преобразование называется закреплением и откреплением числа (прим.перев.: fixing and unfixing the number). Модуль распознает две различные схемы закреплений (прим.перев.: schemes of fixes), -- SI и SPICE. Модуль обладает объектно-ориентированным интерфейсом. Новый объект создается вызовом метода класса new, и передачей ему строки, указывающей желаемую вами схему закрепления (SI или SPICE).

 my $conv = Convert::SciEng->new('SI');

После этого можно начинать закреплять или откреплять числа. Например, такой код:

 print $conv->unfix('2.34u');

напечатает значение 2.34e-06. Здесь "u" означает SI-символ микроединиц. Кроме того, вы можете передать в unfix массив, например так:

 print map { "$_\n" } $conv->unfix(qw/1P 1T 1G 1M 1K 1 1m 1u 1p 1f 1a/);

в результате получим вот что:

 1e+015
 1000000000000
 1000000000
 1000000
 1000
 1
 0.001
 1e-006
 1e-012
 1e-015
 1e-018

(помимо всего прочего этот пример демонстрирует полный диапазон постфиксов, определенных в схеме SI). Помимо этого, вы можете настроить формат, возвращаемых результатов, с использованием метода format, которому передается строка, специфицирующая формат. Форматная строка -- это просто строка, которая будет передана в sprintf, когда потребуется выдать значение. Значение форматной строки по-умолчанию %5.5g.

Конечно имеется и метод fix, который, в качестве аргумента, принимает число, и возвращает значение, с добавленной, корректной буквой постфикса:

 print $conv->fix(100_000)

печатает "100K", а

 print $conv->fix(1_000_000)

печатает "1M".

Number::Format

Модуль Number::Format -- это модуль более общего назначения, для форматирования чисел различными интересными способами. Как и Convert::SciEng, данный модуль имеет объектно-ориентированный интерфейс. Вызов метода new создает новый форматирующий объект. Этот метод получает, в качестве аргумента, хэш, который содержит различные опции форматирования. Эти опции детализированы в приложении А, вместе с другими объектными методами, содержащимися внутри Number::Format.

Вот некоторые примеры использования этого модуля:

 my $fmt = Number::Format->new; # используем все по-умолчанию

 my $number = 1234567.890;

 print $fmt->round($number), "\n";
 print $fmt->format_number($number), "\n";
 print $fmt->format_negative($number), "\n";
 print $fmt->format_picture($number, '###########'), "\n";
 print $fmt->format_price($number), "\n";
 print $fmt->format_bytes($number), "\n";
 print $fmt->unformat_number('1,000,000.00'), "\n";

Результат будет таким:

 1234567.89
 1,234,567.89
 -1234567.89
 1234568
 USD 1,234,567.89
 1.18M
 1000000

Слегка изменим опции форматирования:

 my $fmt = Number::Format->new(INTL_CURRENCY_SYMBOL => 'GBP',
                               DECIMAL_DIGITS => 1);

 my $number = 1234567.890;

 print $fmt->round($number), "\n";
 print $fmt->format_number($number), "\n";
 print $fmt->format_negative($number), "\n";
 print $fmt->format_picture($number, '###########'), "\n";
 print $fmt->format_bytes($number), "\n";
 print $fmt->unformat_number('1,000,000.00'), "\n";

и вот результат:

 1234567.9
 1,234,567.9
 -1234567.89
 1234568
 GBP 1,234,567.89
 1.18M
 1000000

Если мы желаем форматировать числа в немецкой системе, то можем получить нечто похожее на это:

 my $de = Number::Format->new(INT_CURR_SYMBOL => 'DEM ',
                              THOUSANDS_SEP => '.',
                              DECIMAL_POINT => ',');

 my $number = 1234567.890;

 print $de->format_number($number), "\n";
 print $de->format_negative($number), "\n";
 print $de->format_price($number), "\n";

что приведет к такому результату:

 1.234.567,89
 -1234567.89
 DEM 1.234.567,89

И наконец, если бы мы были бухгалтерами, то мы могли бы пожелать что-нибудь такое:

 my $fmt = Number::Format->new(NEG_FORMAT=> '(x)');

 my $debt = -12345678.90;

 print $fmt->format_negative($debt);

и в результате получили следующее:

 (12345678.90)

Конечно же, возможно комбинировать Number::Format с остальными способами, которые мы использовали ранее. Если бы мы имели текстовый документ, содержащий числа в различных форматах, и желали бы убедиться, что они все в одном, некотором, нашем стандарте, то могли бы сделать нечто следующее:

 use Number::Format;

 my $data;

 {
  local $/ = undef;
  $data = <STDIN>;
 }

 my $fmt = Number::Format->new;

 my $num_re = qr/[-+]?(?=\d|\.\d)\d*(\.\d*)?([eE]([-+]?\d+))?/;

 $data =~ s/$num_re/$fmt->format_number($1)/ge;

 print $data;

5.3 Дополнительная информация

Для тополнительной информации о переменных, управляющих вводом, таких как $/, смотри страницы руководства perldoc perlvar.

Для дополнительной информации о поддержке Unicode в Perl, смотри страницы руководства perldoc perlunicode и perldoc utf8.

Для дополнительной информации о sprintf, смотри страницы руководства perldoc -f sprintf.

Модули Convert::SciEng и Number::Format могут быть получены в CPAN. Когда они будут установлены, то их документация будет доступна через использование команды perldoc.

5.4 Резюме

  • Большинство неструктурированных данных содержится в текстовых ASCII-файлах.
  • Perl очень хорошо приспособлен для извлечения статистической информации из тектовых файлов.
  • Большинство полезных преобразований форматов данных, может быть выполнено с использованием стандартных дистрибутивов Perl, либо с добавлением модулей со CPAN.

<< '''Часть вторая. Обработка данных''' | Data Munging With Perl | Глава 6. Запись-ориентированные данные >>


edit RightSideBar