обработка текста из неплоского файла (для извлечения информации, как если бы это *был* плоский файл)

У меня есть набор продольных данных, сгенерированный компьютерным моделированием, который может быть представлен следующими таблицами («var» — это переменные):

time subject var1 var2 var3
t1   subjectA  ...
t2   subjectB  ...

а также

subject   name
subjectA  nameA
subjectB  nameB

Однако сгенерированный файл записывает файл данных в формате, подобном следующему:

time t1 
  description
subjectA nameA
  var1 var2 var3
subjectB nameB
  var1 var2 var3
time t2
  description
subjectA nameA
  var1 var2 var3
subjectB nameB
  var1 var2 var3
...(and so on)

Я использовал (python) script для обработки этих выходных данных в плоский текстовый файл, чтобы я мог импортировать его в R, python, SQL или awk/grep для извлечения информации - пример типа информации, необходимой из один запрос (в нотации SQL, после преобразования данных в таблицу) показан ниже:

SELECT var1, var2, var3 FROM datatable WHERE subject='subjectB'

Интересно, есть ли более эффективное решение, поскольку каждый из этих файлов данных может иметь размер около 100 МБ каждый (а у меня их сотни), а создание плоского текстового файла занимает много времени и занимает дополнительное место на жестком диске с избыточной информацией. В идеале я бы взаимодействовал с исходным набором данных напрямую, чтобы извлечь нужную мне информацию, не создавая дополнительный плоский текстовый файл... Есть ли более простое решение awk/perl для таких задач? Я довольно хорошо разбираюсь в обработке текста на python, но мои навыки в awk находятся в зачаточном состоянии, и у меня нет практических знаний о perl; Интересно, могут ли эти или другие инструменты для предметной области предоставить лучшее решение.

Спасибо!

Постскриптум: Ого, спасибо всем! Мне жаль, что я не могу выбрать ответы всех @FM: спасибо. Мой скрипт Python похож на ваш код без этапа фильтрации. Но ваша организация чиста. @PP: Я думал, что уже разбираюсь в grep, но, видимо, нет! Это очень полезно... но я думаю, что grepping становится трудным при смешивании "времени" с выводом (которое я не включил в свой пример в качестве возможного сценария извлечения! Это моя ошибка). @ghostdog74: Это просто фантастика... но изменить строку, чтобы получить 'subjectA', было непросто... (хотя тем временем я буду больше читать об awk и, надеюсь, попозже покопаюсь). @weismat: Хорошо сказано. @S.Lott: Это чрезвычайно элегантно и гибко - я не просил решения на python (ic), но оно полностью соответствует структуре синтаксического анализа, фильтрации и вывода, предложенной PP, и достаточно гибко, чтобы приспособить ряд различные запросы для извлечения различных типов информации из этого иерархического файла.

Еще раз всем благодарна - большое спасибо.


person hatmatrix    schedule 15.02.2010    source источник


Ответы (4)


Вот что такое генераторы Python.

def read_as_flat( someFile ):
    line_iter= iter(someFile)
    time_header= None
    for line in line_iter:
        words = line.split()
        if words[0] == 'time':
            time_header = [ words[1:] ] # the "time" line
            description= line_iter.next()
            time_header.append( description )
        elif words[0] in subjectNameSet:
            data = line_iter.next()
            yield time_header + data

Вы можете использовать это как стандартный итератор Python

for time, description, var1, var2, var3 in read_as_flat( someFile ):
    etc.
person S.Lott    schedule 15.02.2010

Если все, что вам нужно, это var1, var2, var3 при сопоставлении с определенной темой, вы можете попробовать следующую команду:


  grep -A 1 'subjectB'

Аргумент командной строки -A 1 предписывает grep распечатать совпавшую строку и одну строку после совпадающей строки (и в этом случае переменные идут в строке после темы).

Возможно, вы захотите использовать параметр -E, чтобы выполнить поиск регулярного выражения в grep и привязать предметный поиск к началу строки (например, grep -A 1 -E '^subjectB').

Наконец, вывод теперь будет состоять из строки темы и строки переменной, которую вы хотите. Вы можете скрыть строку темы:


  grep -A 1 'subjectB' |grep -v 'subjectB'

И вы можете захотеть обработать переменную line:


  grep -A 1 'subjectB' |grep -v 'subjectB' |perl -pe 's/ /,/g'

person PP.    schedule 15.02.2010

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

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

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

use strict;
use warnings;

read_file($ARGV[0], \&check_record);

sub read_file {
    my ($file_name, $check_record) = @_;
    open(my $file_handle, '<', $file_name) or die $!;
    # A data structure to hold an entire record.
    my $rec = {
        time => '',
        desc => '',
        subj => '',
        name => '',
        vars => [],
    };
    # A code reference to get the next line and do some cleanup.
    my $get_line = sub {
        my $line = <$file_handle>;
        return unless defined $line;
        chomp $line;
        $line =~ s/^\s+//;
        return $line;
    };
    # Start parsing the data file.
    while ( my $line = $get_line->() ){
        if ($line =~ /^time (\w+)/){
            $rec->{time} = $1;
            $rec->{desc} = $get_line->();
        }
        else {
            ($rec->{subj}, $rec->{name}) = $line =~ /(\w+) +(\w+)/;
            $rec->{vars} = [ split / +/, $get_line->() ];

            # OK, we have a complete record. Now invoke our filtering
            # code to decide whether to export record to rectangular format.
            $check_record->($rec);
        }
    }
}

sub check_record {
    my $rec = shift;
    # Just an illustration. You'll want to parameterize this, most likely.
    write_output($rec)
        if  $rec->{subj} eq 'subjectB'
        and $rec->{time} eq 't1'
    ;
}

sub write_output {
    my $rec = shift;
    print join("\t", 
        $rec->{time}, $rec->{subj}, $rec->{name},
        @{$rec->{vars}},
    ), "\n";
}
person FMc    schedule 15.02.2010
comment
+1: Из прошлого опыта я знаю, что разбор больших файлов в хэши может потреблять много памяти. Я должен сказать, что это решение, вероятно, будет трудно улучшить из-за скупости памяти... - person Zaid; 15.02.2010

Если вы ленивы и у вас достаточно оперативной памяти, то я бы работал на RAM-диске вместо файловой системы, если они вам нужны немедленно.
Я не думаю, что Perl или awk будут быстрее, чем Python, если вы просто перекодирование вашего текущего алгоритма на другой язык.

person weismat    schedule 15.02.2010