Сравните множество текстовых файлов, которые содержат дубликаты «заглушек» из предыдущего и следующего файлов, и автоматически удалите дублирующийся текст - PullRequest
4 голосов
/ 07 апреля 2009

У меня есть большое количество текстовых файлов (1000+), каждый из которых содержит статью из академического журнала. К сожалению, файл каждой статьи также содержит «заглушку» в конце предыдущей статьи (в начале) и в начале следующей статьи (в конце).

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

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

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

Имена файлов сортируются по порядку, поэтому должен работать скрипт, который последовательно сравнивает каждый файл с следующим. Е.Г.

bul_9_5_181.txt
bul_9_5_186.txt

- две статьи, одна из которых начинается на стр. 181, а другая - на стр. 186. Обе эти статьи включены ниже.

В [http://drop.io/fdsayre][1]

] находятся два тома тестовых данных.

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

Спасибо за вашу помощь

FILENAME: bul_9_5_181.txt

SYN & STHESIA

ISI

большинство португальских слов, обозначающих черные объекты или идеи, относящиеся к черным. Эта связь, по общему признанию, не является истинной синестезией, но автор считает, что это только вопрос степени между этими логическими и спонтанными ассоциациями и подлинными случаями цветного прослушивания. ЛИТЕРАТУРА

Дауни, Э. ИЮНЯ Amer. J. of Psycho!., 1911, 22, S28-539MEDEIROS-E-ALBUQUERQUE. Сюжет феномена синопсии предстает перед миллионами людей. /. де психол. norm et al., 1911, 8, 147-151. Майерс, С. С. Случай синастезии. Британия J. of Psychol., 1911, 4, 228-238.

АФФЕКТИВНЫЕ ЯВЛЕНИЯ - ЭКСПЕРИМЕНТАЛЬНАЯ ЧАСТЬ ПРОФЕССОР ДЖОН Ф. ШЕПАРД Мичиганский университет

В течение года из Лейпцигской лаборатории появилось три статьи. Drozynski (2) возражает против использования вкусовых и обонятельных раздражителей при изучении органических реакций с чувствами из-за нарушения дыхания, которое может быть вовлечено. Он использует ритмические слуховые стимулы и обнаруживает, что, когда они даны с разной скоростью и в разных группах, они сопровождаются характерными чувствами у каждого субъекта. Он записывает дыхание грудной клетки и изгибы от сфигмографа и водного плетизмографа. Каждый эксперимент начинался с нормальной записи, затем давался стимул, после чего следовал контрастный стимул; наконец, был взят другой нормальный. Измеряли длину и глубину дыхания (временная шкала не регистрировалась) и определяли отношение длины вдоха к продолжительности выдоха. Длина и высота импульсов также были измерены. В табличных сводках приводятся данные о том, сколько раз автор обнаружил, что каждая величина увеличивалась или уменьшалась в течение периода реакции с каждым типом ощущений. Состояние чувств, сопровождающее данный ритм, всегда сложное, но результат относится к тому измерению, которое казалось доминирующим. Только несколько отключенных отрывков из нормальных периодов и периодов реакции воспроизводятся из записей. Автор утверждает, что возбуждение дает увеличение частоты и глубины дыхания, соотношения вдоха и выдоха, а также частоты и величины пульса. В объёме руки есть неровности. Поскольку эффект успокаивает, он вызывает уменьшение скорости и глубины

182

ДЖОН Ф. ШЕПАРД

дыхание, соотношение вдоха и выдоха, частота и частота пульса. Объем руки показывает тенденцию расти с дыхательными волнами. Показывает приятность

Ответы [ 7 ]

4 голосов
/ 16 апреля 2009

Похоже, что гораздо более простое решение действительно сработает.

Никто, кажется, не использует информацию, предоставленную именами файлов. Если вы действительно используете эту информацию, вам, возможно, не придется делать какие-либо сравнения между файлами, чтобы определить область перекрытия. Кто бы ни написал OCR, вероятно, задумался над этой проблемой.

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

Вот схема алгоритма

  1. выберите файл; назовите это: file1
  2. посмотрите имя файла следующего файла; назовите это: file2
  3. извлечь номер страницы из имени файла file2; назовите это: pageNumber
  4. проверять содержимое файла1, пока не найдете строку, содержащую только номер страницы
  5. убедитесь, что перед этой строкой стоит пустая строка.
  6. удалить эту строку и все после
  7. перейти к следующему файлу в последовательности
3 голосов
/ 09 апреля 2009

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

#!/usr/bin/ruby

class A_splitter
    Title   = /^[A-Z]+[^a-z]*$/
    Byline  = /^BY /
    Number = /^\d*$/
    Blank_line = /^ *$/
    attr_accessor :recent_lines,:in_references,:source_glob,:destination_path,:seen_in_last_file
    def initialize(src_glob,dst_path=nil)
        @recent_lines = []
        @seen_in_last_file = {}
        @in_references = false
        @source_glob = src_glob
        @destination_path = dst_path
        @destination = STDOUT
        @buffer = []
        split_em
        end
    def split_here
        if destination_path
            @destination.close if @destination
            @destination = nil
          else
            print "------------SPLIT HERE------------\n" 
          end
        print recent_lines.shift
        @in_references = false
        end
    def at_page_break
        ((recent_lines[0] =~ Title  and recent_lines[1] =~ Blank_line and recent_lines[2] =~ Number) or
         (recent_lines[0] =~ Number and recent_lines[1] =~ Blank_line and recent_lines[2] =~ Title))
        end
    def print(*args)
        (@destination || @buffer) << args
        end
    def split_em
        Dir.glob(source_glob).sort.each { |filename|
            if destination_path
                @destination.close if @destination
                @destination = File.open(File.join(@destination_path,filename),'w')
                print @buffer
                @buffer.clear
              end
            in_header = true
            File.foreach(filename) { |line|
                line.gsub!(/\f/,'')
                if in_header and seen_in_last_file[line]
                    #skip it
                  else 
                    seen_in_last_file.clear if in_header
                    in_header = false
                    recent_lines << line
                    seen_in_last_file[line] = true
                  end
                3.times {recent_lines.shift} if at_page_break
                if recent_lines[0] =~ Title and recent_lines[1] =~ Byline
                    split_here
                  elsif in_references and recent_lines[0] =~ Title and recent_lines[0] !~ /\d/
                    split_here
                  elsif recent_lines.length > 4
                    @in_references ||= recent_lines[0] =~ /^REFERENCES *$/
                    print recent_lines.shift
                  end
                }
            } 
        print recent_lines
        @destination.close if @destination
        end
    end

A_splitter.new('bul_*_*_*.txt','test_dir')

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

Также удаляет разделы на разрыв страницы (заголовок журнала, автор и номер страницы).

Он выполняет два сплит теста:

  • тест на пару заголовок / подпись
  • тест в первой строке заголовка после справочного раздела

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

Сохранено для потомков:

Если вы не укажете каталог назначения, он просто помещает строку разделения в выходной поток в точке разделения. Это должно облегчить тестирование (вы можете просто less вывод), а когда вы хотите, чтобы они были в отдельных файлах, просто перенаправьте его на csplit (например, с

csplit -f abstracts - '---SPLIT HERE---' '{*}'

или что-то), чтобы разрезать его.

2 голосов
/ 15 апреля 2009

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

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

Сценарий просто открывает два файла и записывает первые три строки второго файла. Затем он открывает новый выходной файл (исходное имя файла + '.new') для первого файла и записывает все строки из первого файла в новый выходной файл, пока не найдет первые три строки второго файла. Не исключено, что в последнем файле есть не три строки из второго файла, а во всех отмеченных мной файлах, что, как мне показалось, имело место из-за заголовка названия журнала и номеров страниц. Одной строки определенно было недостаточно, так как заголовок журнала часто был первой строкой, и это оборвало бы все рано.

Следует также отметить, что последний файл в вашем списке введенных файлов не будет обработан (т. Е. Будет создан новый файл на его основе), так как он не будет изменен этим процессом.

Вот сценарий:

#!/usr/bin/perl
use strict;

my @files;
my $count = @ARGV;
if ($count>0){
    open (IN, "$ARGV[0]");
    @files = <IN>;
    close (IN);
} else {
    @files = glob "bul_*.txt";
}
$count = @files;
print "Processing $count files.\n";

my $lastFile="";
foreach(@files){
    if ($lastFile ne ""){
        print "Processing $_\n";
        open (FILEB,"$_");
        my @fileBLines = <FILEB>;
        close (FILEB);
        my $line0 = $fileBLines[0];
            if ($line0 =~ /\(/ || $line0 =~ /\)/){
                    $line0 =~ s/\(/\\\(/;
                    $line0 =~ s/\)/\\\)/;
            }
        my $line1 = $fileBLines[1];
        my $line2 = $fileBLines[2];
        open (FILEA,"$lastFile");
        my @fileALines = <FILEA>;
        close (FILEA);
        my $newName = "$lastFile.new";
        open (OUT, ">$newName");
        my $i=0;
        my $done = 0;
        while ($done != 1 and $i < @fileALines){
            if ($fileALines[$i] =~ /$line0/ 
                && $fileALines[$i+1] == $line1
                && $fileALines[$i+2] == $line2) {
                $done=1;
            } else {
                print OUT $fileALines[$i];
                $i++;
            }
        }
        close (OUT);
    }
    $lastFile = $_;
}

РЕДАКТИРОВАТЬ: В первую строку добавлена ​​проверка на наличие круглых скобок, которая позже включается в проверку регулярных выражений на двойственность и, если она обнаруживается, избегает их, чтобы они не испортили проверку на двойственность.

2 голосов
/ 07 апреля 2009

У вас нетривиальная проблема. Легко написать код, чтобы найти дублирующий текст в конце файла 1 и в начале файла 2. Но вы не хотите удалять дублирующий текст - вы хотите split это где вторая статья начинается. Получить правильное разделение может быть сложно - один маркер - все заглавные буквы, другой - BY в начале следующей строки.

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

Реализация в Lua . Алгоритм примерно:

  1. Игнорировать пустые строки в конце файла 1 и начале файла 2.
  2. Найти длинную последовательность строк, общих для конца файла 1 и начала файла 2.
    • Это работает, пробуя последовательность из 40 строк, затем 39 и т. Д.
  3. Удалить последовательность из обоих файлов и назвать ее overlap.
  4. Разделенное наложение в заголовке
  5. Добавить первую часть перекрытия к файлу1; добавьте вторую часть к файлу 2.
  6. Перезаписать содержимое файлов списками строк.

Вот код:

#!/usr/bin/env lua

local ext = arg[1] == '-xxx' and '.xxx' or ''
if #ext > 0 then table.remove(arg, 1) end  

local function lines(filename)
  local l = { }
  for line in io.lines(filename) do table.insert(l, (line:gsub('', ''))) end
  assert(#l > 0, "No lines in file " .. filename)
  return l
end

local function write_lines(filename, lines)
  local f = assert(io.open(filename .. ext, 'w'))
  for i = 1, #lines do
    f:write(lines[i], '\n')
  end
  f:close()
end

local function lines_match(line1, line2)
  io.stderr:write(string.format("%q ==? %q\n", line1, line2))
  return line1 == line2 -- could do an approximate match here
end

local function lines_overlap(l1, l2, k)
  if k > #l2 or k > #l1 then return false end
  io.stderr:write('*** k = ', k, '\n')
  for i = 1, k do
    if not lines_match(l2[i], l1[#l1 - k + i]) then
      if i > 1 then
        io.stderr:write('After ', i-1, ' matches: FAILED <====\n')
      end
      return false
    end
  end
  return true
end

function find_overlaps(fname1, fname2)
  local l1, l2 = lines(fname1), lines(fname2)
  -- strip trailing and leading blank lines
  while l1[#l1]:find '^[%s]*$' do table.remove(l1)    end
  while l2[1]  :find '^[%s]*$' do table.remove(l2, 1) end
  local matchsize  -- # of lines at end of file 1 that are equal to the same 
                   -- # at the start of file 2
  for k = math.min(40, #l1, #l2), 1, -1 do
    if lines_overlap(l1, l2, k) then
      matchsize = k
      io.stderr:write('Found match of ', k, ' lines\n')
      break
    end
  end

  if matchsize == nil then
    return false -- failed to find an overlap
  else
    local overlap = { }
    for j = 1, matchsize do
      table.remove(l1) -- remove line from first set
      table.insert(overlap, table.remove(l2, 1))
    end
    return l1, overlap, l2
  end
end

local function split_overlap(l)
  for i = 1, #l-1 do
    if l[i]:match '%u' and not l[i]:match '%l' then -- has caps but no lowers
      -- io.stderr:write('Looking for byline following ', l[i], '\n')
      if l[i+1]:match '^%s*BY%s' then
        local first = {}
        for j = 1, i-1 do
          table.insert(first, table.remove(l, 1))
        end
        -- io.stderr:write('Split with first line at ', l[1], '\n')
        return first, l
      end
    end
  end
end

local function strip_overlaps(filename1, filename2)
  local l1, overlap, l2 = find_overlaps(filename1, filename2)
  if not l1 then
    io.stderr:write('No overlap in ', filename1, ' an
0 голосов
/ 15 апреля 2009

Быстрый удар, предполагая, что заглушка строго идентична в обоих файлах:

#!/usr/bin/perl

use strict;

use List::MoreUtils qw/ indexes all pairwise /;

my @files = @ARGV;

my @previous_text;

for my $filename ( @files ) {
    open my $in_fh,  '<', $filename          or die;
    open my $out_fh, '>', $filename.'.clean' or die;

    my @lines = <$in_fh>;
    print $out_fh destub( \@previous_text, @lines );
    @previous_text = @lines;
}


sub destub {
    my @previous = @{ shift() };
    my @lines = @_;

    my @potential_stubs = indexes { $_ eq $lines[0] } @previous;

    for my $i ( @potential_stubs ) {
        # check if the two documents overlap for that index
        my @p = @previous[ $i.. $#previous ];
        my @l = @lines[ 0..$#previous-$i ];

        return @lines[ $#previous-$i + 1 .. $#lines ]
                if all { $_ } pairwise { $a eq $b } @p, @l;

    }

    # no stub detected
    return @lines;
}
0 голосов
/ 07 апреля 2009

Заголовки и автор всегда в одной строке? И всегда ли эта строка содержит слово «BY» в верхнем регистре? Если это так, вы, вероятно, можете сделать хорошую работу с n awk , используя эти критерии в качестве маркера начала / конца.

Редактировать: Я действительно не думаю, что использование diff сработает, поскольку это инструмент для сравнения широко похожих файлов. Ваши файлы (с точки зрения различий) на самом деле совершенно разные - я думаю, что они сразу выйдут из синхронизации. Но тогда я не гуру различий: -)

0 голосов
/ 07 апреля 2009

Останки идентичны концу предыдущего файла? Или разные окончания строк / ошибки OCR?

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

...