Compare muchos archivos de texto que contienen "talones" duplicados del archivo anterior y siguiente y elimine automáticamente el texto duplicado
scripting nlp (7)
¿Los títulos y el autor están siempre en una sola línea? ¿Y esa línea siempre contiene la palabra "POR" en mayúsculas? Si es así, probablemente puedas hacer un buen trabajo sin awk , usando esos criterios como marcador de inicio / finalización.
Editar: Realmente no creo que usar diff vaya a funcionar, ya que es una herramienta para comparar archivos muy similares. Sus archivos son (desde el punto de vista de diff) realmente completamente diferentes, creo que se desincronizarán de inmediato. Pero entonces, no soy un guru de diff :-)
Tengo una gran cantidad de archivos de texto (más de 1000) cada uno que contienen un artículo de una revista académica. Lamentablemente, el archivo de cada artículo también contiene un "resguardo" del final del artículo anterior (al principio) y del principio del artículo siguiente (al final).
Necesito eliminar estos talones en preparación para ejecutar un análisis de frecuencia en los artículos porque los talones constituyen datos duplicados.
No hay un campo simple que marque el comienzo y el final de cada artículo en todos los casos. Sin embargo, el texto duplicado parece tener el mismo formato y en la misma línea en ambos casos.
Una secuencia de comandos que compara cada archivo con el siguiente archivo y luego elimina una copia del texto duplicado sería perfecto. Parece que sería un problema bastante común cuando se programa, así que estoy sorprendido de que no haya podido encontrar nada que lo haga.
Los nombres de los archivos se ordenan en orden, por lo que un script que compara cada archivo secuencialmente debería funcionar. P.EJ
bul_9_5_181.txt bul_9_5_186.txt
hay dos artículos, uno que comienza en la página 181 y el otro en la página 186. Ambos artículos se incluyen a continuación.
Hay dos volúmenes de datos de prueba ubicados en [ http://drop.io/fdsayre][1]
Nota: Soy un académico haciendo análisis de contenido de artículos de revistas antiguas para un proyecto en la historia de la psicología. No soy programador, pero tengo más de 10 años de experiencia con Linux y generalmente puedo resolver las cosas a medida que avanzo.
Gracias por tu ayuda
NOMBRE DE ARCHIVO: bul_9_5_181.txt
SYN & STHESIA
ISI
la mayoría de las palabras en portugués que significan objetos negros o ideas relacionadas con el negro. Esta asociación es, sin duda, sin verdadera sinestesia, pero el autor cree que es solo una cuestión de grado entre estas asociaciones lógicas y espontáneas y los casos genuinos de audición de color. REFERENCIAS
DOWNEY, JUNIO E. Un caso de color Gustation. Amer. J. of Psycho!., 1911, 22, S28-539MEDEIROS-E-ALBUQUERQUE. Sur un phenomene de synopsie presente par des millions de sujets. /. de psychol. norma, et camino., 1911, 8, 147-151. MYERS, CS Un caso de Synassthesia. Brit. J. of Psychol., 1911, 4, 228-238.
FENÓMENOS AFECTIVOS - EXPERIMENTAL POR PROFESOR JOHN F. .SHEPARD Universidad de Michigan
Han aparecido tres artículos del laboratorio de Leipzig durante el año. Drozynski (2) se opone al uso de estímulos gustativos y olfatorios en el estudio de reacciones orgánicas con sentimientos, debido a la alteración de la respiración que puede estar involucrada. Utiliza estímulos auditivos rítmicos, y encuentra que cuando se les da a diferentes ritmos y en varias agrupaciones, se acompañan de sentimientos característicos en cada sujeto. Él registra la respiración de pecho y las curvas de un esfigmógrafo y un pletismógrafo de agua. Cada experimento comenzó con un registro normal, luego se dio el estímulo, y esto fue seguido por un estímulo de contraste; finalmente, otra normal fue tomada. La longitud y la profundidad de la respiración se midieron (no se registró la línea de tiempo) y se determinó la relación entre la duración de la inspiración y la duración de la espiración. La longitud y la altura de los latidos del pulso también se midieron. Se proporcionan resúmenes tabulares del número de veces que el autor encuentra que cada cantidad se ha aumentado o disminuido durante un período de reacción con cada tipo de sentimiento. El estado de sentimiento que acompaña a un ritmo dado es siempre complejo, pero el resultado se refiere a esa dimensión que parecía ser dominante. Solo unos pocos extractos desconectados de los períodos normales y de reacción se reproducen de los registros. El autor afirma que la excitación aumenta la frecuencia y la profundidad de la respiración, en la relación inspiración-espiración, y en la frecuencia y el tamaño del pulso. Hay ondulaciones en el volumen del brazo. En la medida en que el efecto se aquiete, causa una disminución en la velocidad y la profundidad de
182
JOHN F. SHEPARD
respiración, en la relación inspiración-espiración, y en la frecuencia y tamaño del pulso. El volumen del brazo muestra una tendencia a aumentar con las ondas respiratorias. La amabilidad muestra
Aquí está el comienzo de otra posible solución en Perl (Funciona como es, pero probablemente podría hacerse más sofisticado si fuera necesario). Parece que todo lo que le preocupa es eliminar duplicados en todo el corpus y realmente no le importa si la última parte de un artículo está en el archivo para el próximo, siempre que no esté duplicado en ninguna parte. Si es así, esta solución eliminará las líneas duplicadas dejando solo una copia de una línea determinada en el conjunto de archivos como un todo.
Puede simplemente ejecutar el archivo en el directorio que contiene los archivos de texto sin argumentos o especificar alternativamente un nombre de archivo que contenga la lista de archivos que desea procesar en el orden en que desea que se procesen. Recomiendo este último como sus nombres de archivo (al menos en los archivos de muestra que proporcionó), no aparecen de forma natural en orden cuando se usan comandos simples como ls en la línea de comandos o glob en el script de Perl. Por lo tanto, no necesariamente comparará los archivos correctos entre sí, ya que simplemente se ejecuta en la lista (ingresada o generada por el comando glob). Si especifica la lista, puede garantizar que se procesarán en el orden correcto y que no tardará tanto en configurarse correctamente.
La secuencia de comandos simplemente abre dos archivos y toma nota de las tres primeras líneas del segundo archivo. A continuación, abre un nuevo archivo de salida (nombre de archivo original + ''.new'') para el primer archivo y escribe todas las líneas del primer archivo en el nuevo archivo de salida hasta que encuentra las primeras tres líneas del segundo archivo. Existe la posibilidad de que no haya tres líneas del segundo archivo en la última, sino en todos los archivos que comprobé que parecían ser el caso debido al encabezado del nombre de la revista y los números de página. Definitivamente, una línea no era suficiente, ya que el título del periódico a menudo era la primera línea y eso cortaría las cosas temprano.
También debería tener en cuenta que el último archivo en su lista de archivos ingresados no se procesará (es decir, que se cree un nuevo archivo basado en él) ya que este proceso no lo modificará.
Aquí está el guión:
#!/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 = $_;
}
EDITAR: Se agregó una marca para paréntesis en la primera línea que se encuentra en la comprobación de expresiones regulares para la duplicidad más adelante y, si se encuentra, se escapa de ellos para no estropear la verificación de duplicidad.
Parece que una solución mucho más simple realmente funcionaría.
Nadie parece estar usando la información provista por los nombres de los archivos. Si hace uso de esta información, es posible que no tenga que hacer ninguna comparación entre los archivos para identificar el área de solapamiento. Quien escribió la OCR probablemente pensó un poco en este problema.
El último número en el nombre del archivo le dice cuál es el número de página inicial para ese archivo. Este número de página también aparece en una línea en el archivo. También parece que esta línea está precedida y seguida por líneas en blanco. Por lo tanto, para un archivo determinado, debe poder ver el nombre del siguiente archivo en la secuencia y determinar el número de página en el que debe comenzar a eliminar el texto. Como este número de página aparece en su archivo, solo busque una línea que contenga solo este número (precedido y seguido de líneas en blanco) y elimine esa línea y todo lo demás después. El último archivo en la secuencia se puede dejar solo.
Aquí hay un esquema para un algoritmo
- escoge un archivo; llámalo: archivo1
- mira el nombre del archivo del siguiente archivo; llámalo: archivo2
- extraer el número de página del nombre del archivo de archivo2; llámalo: pageNumber
- escanee el contenido del archivo1 hasta que encuentre una línea que contenga solamente pageNumber
- asegúrese de que esta línea esté precedida y seguida por una línea en blanco.
- eliminar esta línea y todo después
- pasar al siguiente archivo en la secuencia
Probablemente deberías probar algo como esto (ahora lo he probado en los datos de muestra que proporcionaste):
#!/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'')
Básicamente, ejecute los archivos en orden y dentro de cada archivo ejecute las líneas en orden, omitiendo de cada archivo las líneas que estaban presentes en el archivo anterior e imprimiendo el resto en STDOUT (desde el cual se puede canalizar) a menos que un destino se especifica el director (llamado ''test_dir'' en el ejemplo, vea la última línea) en cuyo caso los archivos se crean en el directorio especificado con el mismo nombre que el archivo que contenía la mayor parte de sus contenidos.
También elimina las secciones de salto de página (título de revista, autor y número de página).
Hace dos pruebas divididas:
- una prueba en el par título / byline
- una prueba en la primera línea de título después de una sección de referencia
(debe ser obvio cómo agregar pruebas para puntos de división adicionales).
Retenido para la posteridad:
Si no especifica un directorio de destino, simplemente coloca una línea dividida aquí en la secuencia de salida en el punto de división. Esto debería facilitar la prueba (puede obtener less
resultados) y cuando los desee en archivos individuales, simplemente colóquelo en csplit
(p. Ej., Con
csplit -f abstracts - ''---SPLIT HERE---'' ''{*}''
o algo así) para cortarlo.
Son los talones idénticos al final del archivo anterior? O diferentes finales de línea / errores de OCR?
¿Hay alguna forma de discernir el comienzo de un artículo? ¿Tal vez un resumen sangrado? Luego, puede examinar cada archivo y descartar todo antes del primero y después (incluido) del segundo título.
Una puñalada rápida, suponiendo que el código es estrictamente idéntico en ambos archivos:
#!/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;
}
Usted tiene un problema no trivial. Es fácil escribir el código para encontrar el texto duplicado al final del archivo 1 y el comienzo del archivo 2. Pero no desea eliminar el texto duplicado; desea dividirlo donde comienza el segundo artículo. Conseguir la división correcta podría ser difícil --- un marcador es el todo en mayúsculas, otro es el BY
al comienzo de la siguiente línea.
Hubiera sido útil tener ejemplos de archivos consecutivos, pero el siguiente guión funciona en un caso de prueba. Antes de probar este código, realice una copia de seguridad de todos sus archivos. El código sobrescribe los archivos existentes.
La implementación está en Lua . El algoritmo es aproximadamente:
- Ignore las líneas en blanco al final del archivo 1 y el inicio del archivo 2.
- Encuentre una secuencia larga de líneas comunes al final del archivo 1 y al inicio del archivo 2.
- Esto funciona intentando una secuencia de 40 líneas, luego 39, y así sucesivamente
- Elimine la secuencia de ambos archivos y llámelo
overlap
. - Dividir solapamiento en el título
- Adjunte la primera parte de la superposición al archivo1; preceder la segunda parte al archivo2.
- Sobrescribir los contenidos de los archivos con listas de líneas.
Aquí está el código:
#!/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