2

如何使用 C 或 Perl 将文件的所有元素与另一个文件的所有元素进行比较以获得更大的数据?文件 1 包含 100,000 个这样的数字,文件 2 包含 500,000 个元素。

我在 foreach 中使用了 foreach 来拆分数组中的每个元素。它在 perl 中完美运行,但检查和打印文件 1 中 File2 中单个列的每次出现的元素所花费的时间为 40 分钟。有 28 个这样的列。

有什么方法可以减少时间或使用 C 等其他语言?

文件 1:

0.1
0.11
0.12
0.13
0.14
0.15
0.16
0.17
0.18
0.19
0.2

文件 2:

0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 0.1 0.11    0.12    0.13    0.14    0.15    0.16    0.17    0.18    0.19    0.2 0.21    0.22    0.23    0.24    0.25    0.26    0.27    0.28
1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 1.1 1.11    1.12    1.13    1.14    1.15    1.16    1.17    1.18    1.19    1.2 1.21    1.22    1.23    1.24    1.25    1.26    1.27    1.28

编辑:

预期输出:

如果文件 2 中的元素,匹配打印“列号”,如果不打印“0”。

1  2  0  0  0  0  0  0  0  10  11  12  13  14  15  16  17  18  19  20  0   0  0  0  0  0  0  0   
0  0  0  0  0  0  0  0  0   0   0  0   0   0   0   0   0   0   0   0  0   0  0  0  0  0  0  0  

这是我正在使用的代码。注意:它检查文件 1 中的 File2 列,如果为 true 则打印列,如果为false则打印'0' 。它将打印 28 个不同文件中每一列的输出。

#!/usr/bin/perl-w
chomp($file = "File1.txt");
open(FH, $file);
@k_org = <FH>;
chomp($hspfile = 'file2.txt');
open(FH1, $hspfile);
@hsporg = <FH1>;
for $z (1 .. 28) {
  open(OUT, ">$z.txt");
  foreach (@hsporg) {
    $i = 0;
    @h_org = split('\t', $_);
    chomp ($h_org[0]);
    foreach(@k_org) {
      @orginfo = split('\t', $_);
      chomp($orginfo[0]);
      if($h_org[0] eq $orginfo[0]) {
        print OUT "$z\n";
        $i = 1;
        goto LABEL;
      } elsif ($h_org[0] ne $orginfo[0]) {
        if($h_org[0]=~/(\w+\s\w+)\s/) {
          if($orginfo[0] eq $1) {
            print  OUT "0";
            $i = 1;
            goto LABEL;
          }
        }
      }
    }
    if ($i == 0) {
      print OUT "0";
    }
    LABEL: 
  }
}
close FH;
close FH1;
close OUT;
4

3 回答 3

5

如果你sort(1)有文件,你可以一次检查它。不应超过几秒钟(包括排序)。

另一种方法是将 file1 中的所有值加载到哈希中。这有点消耗内存,特别是如果 file1 很大,但应该很快(同样,不超过几秒钟)。

对于这样的工作,我会选择 perl 而不是 C,而且我在 C 方面比在 perl 方面更精通。对于这种工作,用 perl 编码要快得多,不易出错并且运行速度足够快。

于 2013-04-16T12:28:50.677 回答
3

该脚本运行一个测试用例。请注意,您的预期输出在客观上是错误的:在文件 2,第 1 行,第 20 列中,该值0.2存在。

#!perl

use 5.010; # just for `say`
use strict; use warnings;
use Test::More;

# define input files + expected outcome
my $file_1_contents = <<'_FILE1_';
0.1
0.11
0.12
0.13
0.14
0.15
0.16
0.17
0.18
0.19
0.2
_FILE1_

my $file_2_contents = <<'_FILE2_';
0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 0.1 0.11 0.12 0.13 0.14 0.15 0.16 0.17 0.18 0.19 0.2 0.21 0.22 0.23 0.24 0.25 0.26 0.27 0.28
1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 1.1 1.11 1.12 1.13 1.14 1.15 1.16 1.17 1.18 1.19 1.2 1.21 1.22 1.23 1.24 1.25 1.26 1.27 1.28
_FILE2_

my $expected_output = <<'_OUTPUT_';
1 2 0 0 0 0 0 0 0 10 11 12 13 14 15 16 17 18 19 20 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
_OUTPUT_

# open the filehandles
open my $file1, "<", \$file_1_contents or die "$!";
open my $file2, "<", \$file_2_contents or die "$!";
open my $expected, "<", \$expected_output or die "$!";

my %file1 = map { chomp; 0+$_ => undef } <$file1>;

while (<$file2>) {
    chomp;
    my @vals = split;
    # If value exists in file1, print the col number.
    my $line = join " " => map { exists $file1{0+$vals[$_]} ? $_+1 : 0 } 0 .. $#vals;
    chomp(my $expected_line = <$expected>);
    is $line, $expected_line;
}
done_testing;

要将完全相同的输出打印到 28 个文件,您将删除测试代码,而不是

say {$_} $line for @filehandles;

反而。

旧答案

您现有的代码效率低下且不习惯。让我帮你解决这个问题。

首先,所有的 Perl 脚本use strict; use warnings;都使用use 5.010;.

该调用接受一个变量并从字符串末尾chomp删除(通常是换行符)的当前值。$/这很重要,因为 readline 运算符不会为我们这样做。声明一个常量变量是不好的。而是做

my $file   = "File1.txt"; 
my $hspfle = "File2.txt";

use strict强制您正确声明变量,您可以使用my.

要打开文件,您应该使用以下成语:

open my $fh, "<", $filename or die "Can't open $filename: $!";

而不是or die ...你可以use autodie在你的脚本顶部。

如果您无法打开文件,这将中止脚本,告诉您原因($!),并指定显式打开模式(此处:<= 读取)。这避免了文件名中带有特殊字符的错误。

词法文件句柄(在my变量中,与裸字文件句柄相比)具有适当的范围,并且会自行关闭。您应该使用它们还有其他各种原因。

split函数采用正则表达式,而不是字符串作为第一个参数。如果你仔细检查你的程序,你会发现split每个元素@hsporg28 次,每个元素@k_org28 × @hsporg 次。这是非常缓慢且不必要的,因为我们可以事先这样做。

如果条件为假,则无需在

if ($h_org[0] eq $orginfo[0]) {
  ...;
} elsif ($h_org[0] ne $orginfo[0]) {
  ...;
}

as$a ne $b完全等同于not $a eq $b.

在 Perl 中使用 a 是非常不习惯的goto,并且跳转到某个地方的标签也不是特别快。标签主要用于循环控制:

# random example
LOOP: for my $i (1 .. 10) {
  for my $j (1 .. 5) {
    next      if     $i == $j; # start next iteration of current loop
    next LOOP if 2 * $i == $j; # start next iteration of labeled loop
    last LOOP if $i + $j == 13;# like `break` in C
  }

redo循环控制动词类似于,next但不重新检查循环条件,如果有的话。

由于这些循环控制设施,以及打破任何封闭循环的能力,维护标志或精心设计的 goto 通常是不必要的。


这是脚本的清理版本,没有修复太多实际算法:

#!/usr/bin/perl

use strict; use warnings;
use autodie; # automatic error messages

my ($file, $hspfile) = ("File1.txt", "file2.txt");
open my $fh1, "<", $file;
open my $fh2, "<", $hspfile;

my @k_org  = <$fh1>;
my @hsporg = <$fh2>;

# Presplit the contents of the arrays:
for my $arr (\@k_org, \@hsporg) {
  for (@$arr) {
    chomp;
    $_ = [ split /\t/ ]; # put an *anonymous arrayref* into each slot
  }
}

my $output_files = 28;

for my $z (1 .. $output_files) {
  open my $out, ">", "$z.txt";

  H_ORG:
  for my $h_org (@hsporg) {
    my $i = 0;

    ORGINFO:
    for my $orginfo (@k_org) {
      # elements in array references are accessed like $arrayref->[$i]
      if($h_org->[0] eq $orginfo->[0]) {
        print $out "$z\n";
        $i = 1;
        last ORGINFO; # break out of this loop
      } elsif($h_org->[0] =~ /(\w+\s\w+)\s/ and $orginfo->[0] eq $1) {
        print $out "0";
        $i = 1;
        last ORGINFO;
      }
    }
    print $out "0" if not $i;
  }
}

# filehandles are closed automatically.

现在我们可以进一步优化:在每一行中,您只使用第一个元素。这意味着我们不必存储其余部分:

...;
  for (@$arr) {
    chomp;
    $_ = (split /\t/, $_, 2)[0]; # save just the first element
  }
...;
    ORGINFO:
    for my $orginfo (@k_org) {
      # elements in array references are accessed like $arrayref->[$i]
      if($h_org eq $orginfo) {
        ...;
      } elsif($h_org =~ /(\w+\s\w+)\s/ and $orginfo eq $1) {
        ...;
      }
    }

此外,访问标量比访问数组元素要快一些。

第三个参数split限制结果片段的数量。因为我们只对第一个字段感兴趣,所以我们也不必拆分其他字段。

接下来,我们last退出ORGINFO循环,然后检查一个标志。这是不必要的:我们可以直接跳到H_ORG循环的下一个迭代,而不是设置标志。如果我们自然退出ORGINFO循环,则保证不会设置标志,因此我们可以执行以下操作print

  H_ORG:
  for my $h_org (@hsporg) {
    for my $orginfo (@k_org) {
      if($h_org eq $orginfo) {
        print $out "$z\n";
        next H_ORG;
      } elsif($h_org =~ /(\w+\s\w+)\s/ and $orginfo eq $1) {
        print $out "0";
        next H_ORG;
      }
    }
    print $out "0";
  }

然后,您将相同的数据比较 28 次以将其打印到不同的文件中。更好:定义两个 subprint_indexprint_zero. 在这些内部,您循环输出文件句柄:

# make this initialization *before* you use the subs!
my @filehandles = map {open my $fh, ">", "$_.txt"; $fh} 1 .. $output_files;

...; # the H_ORG loop

sub print_index {
  for my $i (0 .. $#filehandles) {
    print {$filehandles[$i]} $i+1, "\n";
  }
}
sub print_zero {
  print {$_} 0 for @filehandles;
}

然后:

  # no enclosing $z loop!
  H_ORG:
  for my $h_org (@hsporg) {
    for my $orginfo (@k_org) {
      if($h_org eq $orginfo) {
        print_index()
        next H_ORG;
      } elsif($h_org =~ /(\w+\s\w+)\s/ and $orginfo eq $1) {
        print_zero();
        next H_ORG;
      }
    }
    print_zero();
  }

这样可以避免检查您已经知道不匹配的数据。

于 2013-04-17T07:06:25.200 回答
1

在 C 中,您可以尝试使用“qsort”和“bsearch”函数

首先,您需要将文件加载到数组中。

比你应该执行一个 qsort() (除非你确定元素有顺序)。并使用 bsearch() 对数组执行二进制搜索。

http://linux.die.net/man/3/bsearch

这将比逐个检查所有元素要快得多。

如果它不存在,您可以尝试在 perl 中实现二进制搜索,这是一个简单的算法。

于 2013-04-16T12:35:53.693 回答