4

我有大量文本文件(1000 多个),每个文件都包含来自学术期刊的文章。不幸的是,每篇文章的文件还包含上一篇文章结尾(开头)和下一篇文章开头(结尾)的“存根”。

我需要删除这些存根以准备对文章进行频率分析,因为存根构成重复数据。

在所有情况下,没有一个简单的字段来标记每篇文章的开头和结尾。但是,在这两种情况下,重复文本的格式似乎相同且位于同一行。

将每个文件与下一个文件进行比较然后删除重复文本的 1 个副本的脚本将是完美的。这似乎是编程时非常常见的问题,所以我很惊讶我找不到任何可以做到这一点的东西。

文件名按顺序排序,因此将每个文件依次与下一个文件进行比较的脚本应该可以工作。例如

bul_9_5_181.txt
bul_9_5_186.txt

是两篇文章,一篇从第 181 页开始,另一篇从第 186 页开始。这两篇文章都包含在下面。

有两卷测试数据位于 [ http://drop.io/fdsayre][1]

注意:我是一名学者,正在为心理学史上的一个项目对旧期刊文章进行内容分析。我不是程序员,但我确实有 10 年以上的 linux 经验,并且通常可以在我去的时候解决问题。

谢谢你的帮助

文件名:bul_9_5_181.txt

通感

ISI

大多数葡萄牙语单词表示黑色物体或与黑色有关的想法。诚然,这种关联并不是真正的联觉,但作者认为,这些合乎逻辑的自发关联与真实的有色试镜案例之间只是程度问题。参考

DOWNEY, JUNE E. 一个有色味觉的案例。阿米尔。J. of Psycho!., 1911, 22, S28-539MEDEIROS-E-ALBUQUERQUE。Sur un phenomene de synopsie presente par des Millions de sujets。/ 。德心理。规范等路径,1911, 8, 147-151。MYERS, CS 一例通感。英国人。J. of Psychol., 1911, 4, 228-238。

情感现象——密歇根大学约翰·F·谢泼德教授的实验

今年莱比锡实验室发表了三篇文章。Drozynski (2) 反对使用味觉和嗅觉刺激来研究有感觉的器质性反应,因为可能涉及呼吸障碍。他使用有节奏的听觉刺激,发现当以不同的速率和不同的分组给予时,每个受试者都伴随着独特的感觉。他用脉搏计和水体积描记器记录胸部呼吸和曲线。每个实验都以正常记录开始,然后给予刺激,然后是对比刺激;最后,取了另一个正常值。测量呼吸的长度和深度(没有记录时间线),并确定吸气长度与呼气长度的关系。还测量了脉搏的长度和高度。表格总结了作者在每种感觉的反应期间发现每个数量增加或减少的次数。伴随给定节奏的感觉状态总是复杂的,但结果是指那个似乎占主导地位的维度。仅从记录中复制了一些与正常和反应期无关的摘录。作者指出,兴奋会增加呼吸的频率和深度、吸气-呼气比以及脉搏的频率和大小。手臂体积有起伏。只要效果是安静的,它会导致速度和深度的降低 表格总结了作者在每种感觉的反应期间发现每个数量增加或减少的次数。伴随给定节奏的感觉状态总是复杂的,但结果是指那个似乎占主导地位的维度。仅从记录中复制了一些与正常和反应期无关的摘录。作者指出,兴奋会增加呼吸的频率和深度、吸气-呼气比以及脉搏的频率和大小。手臂体积有起伏。只要效果是安静的,它会导致速度和深度的降低 表格总结了作者在每种感觉的反应期间发现每个数量增加或减少的次数。伴随给定节奏的感觉状态总是复杂的,但结果是指那个似乎占主导地位的维度。仅从记录中复制了一些与正常和反应期无关的摘录。作者指出,兴奋会增加呼吸的频率和深度、吸气-呼气比以及脉搏的频率和大小。手臂体积有起伏。只要效果是安静的,它会导致速度和深度的降低 但结果是指似乎占主导地位的那个维度。仅从记录中复制了一些与正常和反应期无关的摘录。作者指出,兴奋会增加呼吸的频率和深度、吸气-呼气比以及脉搏的频率和大小。手臂体积有起伏。只要效果是安静的,它会导致速度和深度的降低 但结果是指似乎占主导地位的那个维度。仅从记录中复制了一些与正常和反应期无关的摘录。作者指出,兴奋会增加呼吸的频率和深度、吸气-呼气比以及脉搏的频率和大小。手臂体积有起伏。只要效果是安静的,它会导致速度和深度的降低

182

约翰·F·谢泼德

呼吸、吸气-呼气比、脉搏频率和大小。手臂体积显示出随着呼吸波增加的趋势。宜人性显示

4

7 回答 7

4

看起来一个更简单的解决方案实际上会起作用。

似乎没有人使用文件名提供的信息。如果您确实使用了此信息,则可能无需在文件之间进行任何比较来识别重叠区域。编写 OCR 的人可能对这个问题进行了一些思考。

文件名中的最后一个数字告诉您该文件的起始页码是什么。此页码也单独出现在文件中的一行上。看起来这一行之前和之后都是空行。因此,对于给定的文件,您应该能够查看序列中下一个文件的名称,并确定您应该开始删除文本的页码。由于此页码出现在您的文件中,因此只需查找仅包含此页码的行(前后为空行)并删除该行和之后的所有内容。序列中的最后一个文件可以单独放置。

这是算法的大纲

  1. 选择一个文件;调用它:file1
  2. 查看下一个文件的文件名;调用它:file2
  3. 从file2的文件名中提取页码;称它为:pageNumber
  4. 扫描 file1 的内容,直到找到仅包含 pageNumber 的行
  5. 确保此行前后各有一个空行。
  6. 删除此行以及之后的所有内容
  7. 移动到序列中的下一个文件
于 2009-04-15T20:44:04.513 回答
3

您可能应该尝试这样的事情(我现在已经在您提供的示例数据上对其进行了测试):

#!/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(可以从中传输),除非目标指定了director(在示例中称为“test_dir”,请参见最后一行),在这种情况下,将在指定目录中创建与包含大部分内容的文件同名的文件。

它还删除了分页部分(期刊标题、作者和页码)。

它做了两个拆分测试:

  • 对标题/署名对的测试
  • 在参考部分之后的第一个标题行上进行测试

(应该很明显如何为额外的分割点添加测试)。

为后代保留:

如果您没有指定目标目录,它只会在输出流中的分割点处放置一个 split-here 行。这应该使测试更容易(您可以只less输出),并且当您希望它们在单个文件中时,只需将其通过管道传输到csplit(例如

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

或其他东西)把它剪掉。

于 2009-04-09T17:34:31.290 回答
2

你有一个不平凡的问题。编写代码很容易在文件 1 的末尾和文件 2 的开头找到重复的文本。但是你不想删除重复的文本——你想把它拆分到第二篇文章的开头。正确分割可能很棘手——一个标记是全部大写,另一个是BY下一行开头的。

从连续文件中获取示例会有所帮助,但下面的脚本适用于一个测试用例。在尝试此代码之前,请备份所有文件。 该代码会覆盖现有文件。

实现是在Lua中。算法大致是:

  1. 忽略文件 1 末尾和文件 2 开头的空白行。
  2. 查找文件 1 的结尾和文件 2 的开头共有的一长串行。
    • 这通过尝试一系列 40 行,然后是 39 行,依此类推
  3. 从两个文件中删除序列并调用它overlap
  4. 在标题处拆分重叠
  5. 将重叠的第一部分附加到 file1;将第二部分添加到 file2。
  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
于 2009-04-07T01:59:35.090 回答
2

这是 Perl 中另一个可能的解决方案的开始(它按原样工作,但如果需要,可能会变得更复杂)。听起来好像您所关心的只是删除整个语料库中的重复项,并且并不真正关心一篇文章的最后一部分是否在下一篇的文件中,只要它没有在任何地方重复。如果是这样,此解决方案将删除重复的行,在整个文件集中只留下任何给定行的一个副本。

您可以只在包含不带参数的文本文件的目录中运行该文件,或者指定一个文件名,该文件名包含您想要按照处理顺序处理的文件列表。我推荐后者,因为当在命令行上使用ls或在 Perl 脚本中使用glob等简单命令时,您的文件名(至少在您提供的示例文件中)不会自然地按顺序列出。因此,它不一定会相互比较正确的文件,因为它只是顺着列表运行(由 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 = $_;
}

编辑:在第一行中添加了括号检查,稍后将进入正则表达式检查重复性,如果发现则将它们转义,以免它们弄乱重复性检查。

于 2009-04-15T01:28:26.257 回答
0

存根是否与前一个文件的结尾相同?还是不同的行尾/OCR 错误?

有没有办法辨别文章的开头?也许是缩进的摘要?然后,您可以浏览每个文件并丢弃第一个标题之前和(包括)第二个标题之后的所有内容。

于 2009-04-06T22:05:14.640 回答
0

标题和作者总是在一行吗?那一行是否总是包含大写的“BY”这个词?如果是这样,您可能可以使用awk做一个公平的工作,使用这些标准作为开始/结束标记。

编辑:我真的不认为使用 diff 会起作用,因为它是一种用于比较大致相似文件的工具。您的文件(从差异的角度来看)实际上完全不同 - 我认为它会立即不同步。但是,我不是差异大师 :-)

于 2009-04-06T22:35:08.640 回答
0

假设存根在两个文件中完全相同:

#!/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;
}
于 2009-04-15T17:21:52.083 回答