【问题标题】:How to find common parts in a paths with perl?如何使用 perl 在路径中查找公共部分?
【发布时间】:2013-06-30 08:01:01
【问题描述】:

有多个路径,例如:

1: /abc/def/some/common/part/xyz/file1.ext
2: /other/path/to/7433/qwe/some/common/part/anotherfile.ext
3: /misc/path/7433/qwe/some/common/part/filexx.ext
4: /2443/totally/different/path/file9988.ext
5: /abc/another/same/path/to/ppp/thisfile.ext
6: /deep1/deep2/another/same/path/to/diffone/filename.ext

我需要找到共同的部分 - 每个可能的部分,例如。如果可能的话,在上面找到共同的部分:

 /some/common/part/ - in the paths 1,2,3
 /another/same/path/to/ - in the 5,6
 /path/to/ - in the 2,5,6
 /path/ - 2,3,4,5,6

等等。

我根本不知道如何解决这个问题 - 什么方法好

  • 基于字符串 - 有点查找字符串的共同部分
  • 基于列表 - 将所有路径拆分为列表并在某种程度上比较常见元素的数组
  • 树形图 - 有点找到图的共同部分
  • 其他?

当我知道如何解决这个问题时,我(可能)能够自己编写代码 - 所以不想要免费的编程服务 - 但需要一些指导如何开始。

我确定这里已经有一些 CPAN 模块可以帮助我,但我真的不知道如何从 30k 模块列表中找到合适的有用模块来解决上述问题。 :(

编辑 - 我需要这个:

大约有200k 个文件,位于 10k 个目录中,其中许多“属于同一类”,例如:

/u/some/path/project1/subprojct/file1
/u/backup/of/work/date/project1/subproject/file2
/u/backup_of_backup/of/work/date/project1/subproject/file2
/u/new/addtions/to/projec1/subproject/file3

文件类型不同(pdf、图像、doc、txt 等),有几个是相同的(如上面的 file2 - 易于使用 Digest::MD5 过滤),但“将它们组合在一起”的唯一方法是基于在路径的“公共部分”上 - 例如"project1/subproject" 等等..

另一个文件具有相同的 MD5,因此可以过滤掉重复项,但它们位于不同的树中,例如

/u/path/some/file
/u/path/lastest_project/menu/file
/u/path/jquery/menu/file
/u/path/example/solution/jquery/menu/file

所以,文件是相同的,(相同的 md5)但需要稍微将一份副本移动到正确的位置(并删除其他文件)并且需要在一定程度上确定“最常用的”常用路径,并收集标签...(旧的路径元素是标签)

背后的想法是:

  • 如果相同的 md5 文件大部分存储在某个公共路径下 - 我可以决定将一份副本移动到哪里...

而且它更复杂,但上面的解释就足够了;)

只需要降低我硬盘上的熵;)

【问题讨论】:

  • 我不希望有一个 CPAN 模块来解决这个问题,这似乎是一件很不寻常的事情。需要它的应用程序是什么?
  • 您可能会调整在像diff 这样的程序中使用的算法。您处理的是路径名组件而不是行,但它们都在寻找最长的公共序列。
  • 如果你能说出你为什么需要找到这个会有所帮助,即你想通过解决这个问题来实现什么?
  • @devnull 补充说明
  • @Barmar 添加解释

标签: perl list path tree


【解决方案1】:

在这个线程中有一些关于寻找最长公共连续子串的讨论:http://www.nntp.perl.org/group/perl.fwp/2002/02/msg1662.html

“赢家”似乎是以下代码,但您可以尝试其中的一些其他内容:

#!/usr/bin/perl
use strict;
use warnings;

sub lcs {

    my $this = shift;
    my $that = shift;

    my $str = join "\0", $this, $that;
    my $len = 1;
    my $lcs;
    while ($str =~ m{ ([^\0]{$len,}) (?= [^\0]* \0 [^\0]*? \1 ) }xg) {
        $lcs = $1;
        $len = 1 + length($1);
    }

    if ($len == 1) { print("No common substring\n"); }
    else {
        print("Longest common substring of length $len: \"");
        print("$lcs");
        print("\"\n");
    }
}

请记住,您必须稍微调整一下,以考虑到您只想要匹配的整个子目录...即将if ($len == 1) 更改为if ($len == 1 or $lcs !~ /^\// or $lcs !~ /\/$/) 之类的东西

您还必须添加一些簿记以跟踪哪些匹配。当我在上面的示例中运行此代码时,它还在第 1 行和第 5 行中找到了 /abc/ 匹配项。

可能有问题也可能没有问题的是以下两行:

/abc/another/same/path/to/ppp/thisfile.ext
/abc/another/different/path/to/ppp/otherfile.ext

匹配于:

/abc/another/

但不在:

/path/to/ppp/

但是 -- 这是个坏消息 -- 您必须对 n=200,000 个文件进行 O(n^2) 比较。这可能会花费大量时间。

另一种解决方案是遍历列表中的每个路径,将其所有可能的目录路径作为键添加到哈希中,然后将文件本身推送到哈希中(这样,值就是具有此路径的文件数组在里面)。像这样的:

use strict;
use warnings;
my %links;

open my $fh, "<", 'filename' or die "Can't open $!";
while (my $line = <$fh>) {
    chomp($line);
    my @dirs = split /\//, $line;
    for my $i (0..$#dirs) {
        if ($i == $#dirs) {
            push(@{ $links{$dirs[$i]} }, $line);
        }
        for my $j ($i+1..$#dirs) {
            push(@{ $links{join("/",@dirs[$i..$j])} }, $line);
            #PROCESS THIS if length of array is > 1
        }
    }
}

当然,这会占用大量内存。要处理 200,000 个文件,无论您尝试什么,您都可能会遇到困难,但也许您可以将其分解成更易于管理的块。希望这将为您提供一个起点。

【讨论】:

  • 我可以缩小匹配范围 - 例如不需要匹配文件,只需要目录。 10kx10k 这是可以接受的 - 也许找到一些方法来缩小范围 - (我希望 - 会尝试):) 谢谢。
【解决方案2】:

要解决这个问题,您需要正确的数据结构。计算部分路径的哈希效果很好:

use File::Spec;

my %Count_of = ();

while( <DATA> ){
  my @names = File::Spec->splitdir( $_ );

  # remove file
  pop @names;

  # if absolute path, remove empty names at start
  shift @names while length( $names[0] ) == 0;

  # don't count blank lines
  next unless @names;

  # move two cursor thru the names,
  # and count the partial parts
  # created from one to the other
  for my $i ( 0 .. $#names ){
    for my $j ( $i .. $#names ){
      my $partial_path = File::Spec->catdir( @names[ $i .. $j ] );
      $Count_of{ $partial_path } ++;
    }
  }
}

# now display the results
for my $path ( sort { $Count_of{$b} <=> $Count_of{$a} || $a cmp $b } keys %Count_of ){

  # skip if singleton.
  next if $Count_of{ $path } <= 1;

  printf "%3d : %s\n", $Count_of{ $path }, $path;
}


__DATA__
/abc/def/some/common/part/xyz/file1.ext
/other/path/to/7433/qwe/some/common/part/anotherfile.ext
/misc/path/7433/qwe/some/common/part/filexx.ext
/2443/totally/different/path/file9988.ext
/abc/another/same/path/to/ppp/thisfile.ext
/deep1/deep2/another/same/path/to/diffone/filename.ext

【讨论】:

  • 这当然是最好的办法,但是需要存储每个部分路径有哪些路径,所以my $paths; while ( my $path = &lt;DATA&gt; ) { ... split_dir($path); ... push @{ $paths{$partial_path} }, $path;
猜你喜欢
  • 1970-01-01
  • 2021-12-31
  • 1970-01-01
  • 1970-01-01
  • 2022-01-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-15
相关资源
最近更新 更多