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

StackOverflow https://stackoverflow.com/questions/2264504

Вопрос

У меня есть продольный набор данных, сгенерированный с помощью компьютерного моделирования, который может быть представлен следующими таблицами ('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) для обработки этих выходных данных в плоский текстовый файл, чтобы я мог импортировать их в R, python, SQL или awk / grep для извлечения информации - пример типа информации, желаемой из одного запроса (в нотации SQL, после преобразования данных в таблицу), показан ниже:

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

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

Спасибо!

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

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

Это было полезно?

Решение

Это то, для чего нужны генераторы 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.

Другие советы

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


  grep -A 1 'subjectB'

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

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

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


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

И вы, возможно, захотите обработать переменную строку:


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

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

Для того, чтобы иметь возможность использовать данные в 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";
}

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

awk '/time/{f=0}/subjectB/{f=1;next}f' file
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top