Vergleichen Sie viele Textdateien, die enthalten Duplikat „Stubs“ aus dem vorherigen und nächsten Datei und entfernen Sie doppelte Text automatisch

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

Frage

Ich habe eine große Anzahl von Textdateien (1000), die jeweils einen Artikel aus einer Fachzeitschrift enthält. Leider Datei jeden Artikel enthält auch einen „Stub“ aus dem Ende des vorhergehenden Artikels (am Anfang) und von Anfang des nächsten Artikels (am Ende).

Ich brauche diese Stubs in der Vorbereitung für die Ausführung einer Frequenzanalyse auf den Gegenstände zu entfernen, weil der Stubs doppelte Daten darstellen.

Es gibt kein einfaches Feld, das den Anfang und das Ende jeden Artikel in allen Fällen markiert. Allerdings ist die doppelte Text scheint die gleiche und auf der gleichen Linie in beiden Fällen formatiert.

Ein Skript, das jede Datei in die nächste Datei verglichen und entfernt dann 1 Kopie des Duplikats Text wäre perfekt. Dies scheint, wie es ein ziemlich häufiges Problem wäre bei der Programmierung so bin ich überrascht, dass ich nicht in der Lage, etwas zu finden, das dies tut.

Die Dateinamen sortieren, um so ein Skript, das jede Datei auf die nächste sequentiell vergleicht sollte funktionieren. Z.

bul_9_5_181.txt
bul_9_5_186.txt

sind zwei Artikel, eine ab Seite 181 und die andere auf Seite 186. Diese beiden Artikel unten enthalten sind.

Es gibt zwei Bände von Testdaten im historischen Zentrum [ http://drop.io/fdsayre] [ 1]

Hinweis: Ich bin ein Akademiker tun Inhaltsanalyse der alten Zeitschriftenartikel für ein Projekt in der Geschichte der Psychologie. Ich bin kein Programmierer, aber ich habe 10 Jahre Erfahrung mit Linux haben und kann in der Regel Dinge herausfinden, wie ich gehen.

Danke für Ihre Hilfe

Dateiname: bul_9_5_181.txt

SYN & STHESIA

ISI

die Mehrheit der portugiesischen Wörter bedeuten schwarze Objekte oder Ideen schwarz beziehen. Dieser Verein ist zugegebenermaßen keine wahre synsesthesia, aber der Autor glaubt, dass es nur eine Frage des Grades zwischen diesen logischen und spontanen Assoziationen und echten Fällen von farbigem Probespiel ist. Ihr Feedback

DOWNEY, Juni E. Ein Fall von Farbige Gustation. Amer. J. of Psycho!., 1911, 22, S28-539MEDEIROS-E-ALBUQUERQUE. Sur un phenomene de synopsie presente par des millions de sujets. /. de psychol. norm, et Pfad. 1911, 8, 147-151. MYERS, C. S. Ein Fall von Synassthesia. Brit. J. of Psychol., 1911, 4, 228-238.

AFFEKTIVE PHENOMENA - Experimenteller Von Professor John F. .SHEPARD University of Michigan

Drei Artikel wurden aus dem Leipziger Labor im Laufe des Jahres erschienen. Drozynski (2) Objekte auf die Verwendung von Geschmacks- und Geruchsreizen bei der Untersuchung von organischen Reaktionen mit Gefühlen, wegen der Störung der Atmung, die beteiligt sein kann. Er verwendet rhythmische akustische Reize, und stellt fest, dass, wenn mit unterschiedlichen Geschwindigkeiten und in verschiedenen Gruppierungen gegeben, sie durch charakteristische Gefühle in jedem Fach begleitet werden. Er zeichnet die Brustatmung und Kurven von einem Sphygmographen und einem Wasser Plethysmographen. Jedes Experiment begann mit einer normalen Aufzeichnung, dann wurde der Reiz gegeben, und dies wurde durch einen Kontrast Reiz gefolgt; Schließlich wurde ein weiterer normaler genommen. Die Länge und die Tiefe der Atmung wurden gemessen (keine Zeitlinie aufgezeichnet wurde), und das Verhältnis der Länge von Inspiration zu Exspiration Länge bestimmt. Die Länge und die Höhe des pulsebeats wurden ebenfalls gemessen. Tabellarische Zusammenfassungen sind von der Anzahl der angegebenen Zeiten der Autor jede Menge erhöht findet worden oder verringert während einer Reaktionszeit mit jeder Art von Gefühl. Der Gefühlszustand einen bestimmten Rhythmus begleitet, ist immer komplex, aber das Ergebnis wird auf diese Dimension bezeichnet, die dominant zu sein schien. Nur wenige getrennte Auszüge aus normalen und Reaktionszeiten werden aus den Aufzeichnungen wiedergegeben. Der Autor stellt fest, dass die Aufregung Erhöhung der Geschwindigkeit und Tiefe der Atmung gibt, in der Inspiration-Exspirationsverhältnis, und in der Geschwindigkeit und Größe des Impulses. Es gibt Wellen im Arm Volumen. Soweit die Wirkung quieting wird, verursacht es Abnahme der Rateund Tiefe

182

JOHN F. SHEPARD

Atmung, in dem Inspiration-Exspirationsverhältnis und in der Pulsrate und Größe. Der Arm Volumen zeigt eine Tendenz, mit Atem Wellen steigen. Agreeableness zeigt

War es hilfreich?

Lösung

Hier ist der Anfang einer weiteren möglichen Lösung in Perl (Es funktioniert wie es ist aber wahrscheinlich anspruchsvoller gemacht werden könnte, wenn erforderlich). Es klingt, als ob alles, was Sie sind besorgt über ist Duplikaten in den Korpus und nicht wirklich egal, ob der letzte Teil eines Artikels in der Datei für die nächste, so lange zu entfernen, da es nicht überall dupliziert. Wenn ja, wird diese Lösung Streifen aus den doppelten Linien nur eine Kopie einer bestimmten Zeile in dem Satz von Dateien als Ganze zu verlassen.

Sie können entweder nur die Datei im Verzeichnis ausführen, um die Textdateien ohne Argument enthält, oder alternativ einen Dateinamen mit der Liste der Dateien, die Sie angeben, in der Reihenfolge, die Sie bearbeiten möchten, verarbeitet. Ich empfehle die letztere als Dateinamen (zumindest in den Beispieldateien, die Sie zur Verfügung gestellt) nicht natürlich, um aufzulisten, wenn einfache Befehle wie mit ls auf der Kommandozeile oder glob im Perl-Skript. So wird es nicht unbedingt die richtigen Dateien miteinander vergleichen, da es nur die Liste rinnt (eingegeben oder durch den glob Befehl generiert). Wenn Sie die Liste angeben, können Sie garantieren, dass sie in der richtigen Reihenfolge verarbeitet werden, und es dauert nicht so lange es einzurichten richtig.

Das Skript einfach öffnet zwei Dateien und macht Kenntnis von den ersten drei Zeilen der zweiten Datei. Es öffnet sich dann eine neue Ausgabedatei (Originaldateiname + ‚.new‘) für die erste Datei und schreibt alle Zeilen aus der ersten Datei in die neue Ausgabedatei, bis sie die ersten drei Zeilen der zweiten Datei findet. Es gibt einen vagen Hoffnung, dass es nicht mehr als drei Zeilen aus der zweiten Datei in den letzten ein, aber in allen Dateien, die ich überprüft Fleck, schien der Fall zu sein, weil der Name der Zeitschrift Kopf- und Seitenzahlen. Eine Linie war definitiv nicht genug, wie der Titel der Zeitschrift war oft die erste Zeile, und das würde die Dinge abgeschnitten früh.

Ich sollte auch beachten, dass die letzte Datei in der Liste der Dateien eingegeben werden nicht bearbeitet werden (d haben eine neue Datei aus der IT basierend erstellt wurde), da sie nicht durch dieses Verfahren geändert werden.

Hier ist das Skript:

#!/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 = $_;
}

EDIT:. Es wurde ein Scheck für Klammer in der ersten Zeile, die später in die regex-Check für Doppelzüngigkeit geht und wenn sie gefunden entweicht, so dass sie die Duplizität Prüfung nicht vermasseln tun

Andere Tipps

Es sieht aus wie eine viel einfachere Lösung wäre tatsächlich funktionieren.

scheint niemand die Informationen der Dateinamen zur Verfügung gestellt werden. Wenn Sie den Gebrauch dieser Information machen tun, können Sie keine Vergleiche zwischen den Dateien zu tun haben, um den Überlappungsbereich zu identifizieren. Wer schrieb wahrscheinlich die OCR einige Gedanken in dieses Problem behoben werden.

Die letzte Zahl im Dateinamen erfahren Sie, was die Startseitennummer für diese Datei ist. Diese Seitennummer erscheint auf einer Linie von selbst, als auch in der Datei. Es sieht auch wie diese Zeile vorangestellt ist und durch Leerzeilen gefolgt. Daher wird für eine bestimmte Datei sollten Sie auf den Namen der nächsten Datei in der Sequenz suchen können, und bestimmen Sie die Seitenzahl an, in dem Sie zu entfernen Text beginnen. Da diese Seitennummer in der Datei erscheint aussehen nur für eine Zeile, die nur diese Nummer (vorangestellt und durch Leerzeilen) und löschen, nachdem diese Zeile und alles enthält. Die letzte Datei in der Sequenz allein gelassen werden kann.

Hier ist ein Entwurf für einen Algorithmus

  1. eine Datei auswählen; nennen es: file1
  2. Blick auf den Dateinamen der nächsten Datei; nennen es: file2
  3. extrahieren Sie die Seitenzahl aus dem Dateinamen von file2; nennen es: Seitennummer
  4. scannen den Inhalt von Datei1, bis Sie eine Zeile finden, die nur Seitennummer
  5. enthält
  6. muss diese Zeile vorangestellt ist, gefolgt von einer Leerzeile.
  7. entfernen Sie diese Zeile und alles nach
  8. bewegen auf die nächste Datei in der Sequenz

Sie sollten wahrscheinlich so etwas wie dies versuchen (ich jetzt es auf der Beispieldaten getestet haben Sie zur Verfügung gestellt):

#!/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')

Grundsätzlich durch die Dateien in Reihenfolge ausgeführt, und innerhalb jeder Datei durch die Leitungen laufen, um von jeder Weglassen der Linien-Datei, die in der vorangehenden Datei vorhanden waren und den Druck, den Rest zu STDOUT (aus dem sie geleitet werden kann) es sei denn, ein Ziel Direktor angegeben wird, in dem Fall Dateien erstellt werden im angegebenen Verzeichnis mit dem gleichen Namen wie die Datei ( ‚test_dir‘ im Beispiel die letzte Zeile sehen genannt), die den größten Teil ihres Inhalts enthalten sind.

Es beseitigt auch die page-break Abschnitte (Zeitschriftentitel, Autor und Seitenzahl).

Es hat zwei Split-Tests:

  • ein Test auf den Titel / rechten Seite durch Paar
  • ein Test auf der ersten Titel-Zeile nach einem Referenzabschnitt

(sollte es offensichtlich sein, wie Tests hinzufügen für weitere Split-Punkte).

Retained für die Nachwelt:

Wenn Sie nicht über ein Zielverzeichnis angeben, es setzt einfach eine geteiltes hier Linie in dem Ausgangsstrom an dem Split-Punkt. Dadurch sollte es einfacher für den Test machen (man kann nur den Ausgang less) und, wenn Sie wollen, dass sie in einzelnen Dateien nur eine Pipe an csplit (zB mit

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

oder etwas) schneiden es auf.

Sie haben ein nicht-triviales Problem. Es ist leicht, Code zu schreiben, den doppelten Text am Ende der Datei 1 und den Beginn der Datei zu finden 2. Aber Sie wollen nicht den doppelten Text löschen --- Sie wollen Split es wo der zweite Artikel beginnt. die Spaltung immer heikel könnte richtig sein --- ein Marker ist die alle Kappen, ein anderer die BY am Anfang der nächsten Zeile ist.

Es habe geholfen Beispiele von aufeinander folgenden Dateien zu haben, aber das Skript unten arbeitet an einem Testfall. Vor diesem Code versucht, sichern Sie alle Ihre Dateien. Der Code überschreibt vorhandenen Dateien.

Die Implementierung ist in Lua . Der Algorithmus ist in etwa:

  1. Ignorieren Leerzeilen am Ende der Datei 1 und der Beginn der Datei 2.
  2. Finden Sie eine lange Folge von Linien gemeinsam das Ende der Datei 1 und dem Beginn der Datei 2.
    • Dies funktioniert, indem eine Folge von 40 Zeilen versuchen, dann 39, und so weiter
  3. Entfernen Sequenz aus beiden Dateien und nennt es overlap.
  4. Split Überlappung bei Titel
  5. anhängen ersten Teil der Überlappung file1; prepend zweiten Teil zu file2.
  6. Überschreiben Inhalt von Dateien mit Listen der Linien.

Hier ist der Code:

#!/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

Sind die Stubs identisch bis zum Ende der vorherigen Datei? Oder verschiedene Zeilenende / OCR Fehler?

Gibt es eine Möglichkeit einen Artikel Anfang zu erkennen? Vielleicht ein gegliedertes abstrakt? Dann könnten Sie jede Datei durchlaufen und entsorgen Sie alles vor dem ersten und nach (einschließlich) dem zweiten Titel.

Sind die Titel & Autor immer auf einer einzelnen Zeile? Und hat diese Zeile enthält immer das Wort „BY“ in Großbuchstaben? Wenn ja, können Sie wahrscheinlich einen fairen Job withn awk , indem diese Kriterien als Beginn / Ende-Marker.

Edit: Ich glaube nicht wirklich, dass diff funktionieren wird, wie es ein Werkzeug für den Vergleich im Großen und Ganzen ähnliche Dateien ist. Ihre Dateien sind (von diff-Sicht) eigentlich ganz anders - ich denke, es sofort nicht mehr synchronisiert wird. Aber dann, ich bin kein diff Guru: -)

Ein kurzer Stich an mich, unter der Annahme, dass der Stummel in beiden Dateien genau identisch ist:

#!/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;
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top