Chinaunix

标题: 新手求一个算法 [打印本页]

作者: yccpp    时间: 2015-02-03 16:22
标题: 新手求一个算法
本帖最后由 yccpp 于 2015-02-03 16:24 编辑

本人接触perl时间不长,只是知道它很强大。想用它进行数据的重新排列组合,向大神求一算法。原始数据是多行两列的数据,想把有公共数据的行写成一行且公共数据只出现一次。关键是公共数据不是固定的,下面是一个小例子,第一行和第二行的第一列有相同数22875,而第一行第二列的数据30588又出现在了第五行和第六行,需要把这些数据写成一行,如果没有公共数据,就按原始数据写出。整个数据文件见附件。非常感谢您的帮助!

原始数据
22875    30588     
22875    34747         
22912    34851
22963    35901      
22969    30588
30588    40791
30609    39143      
30609    40808

重新排列后
22875    30588   34747  22969  40791  
22912    34851  
22963    35901
30609    39143   40808

number.zip

78.96 KB, 下载次数: 21

数据


作者: huang6894    时间: 2015-02-03 22:23
  1. #!/usr/bin/perl -w

  2. my (%hash,$n);
  3. while(<DATA>){
  4.         chomp;
  5.         $n ++;
  6.         my $k;
  7.         my @row = (split);
  8.         map{$k = (exists $hash{$_}) ? $hash{$_} : $k}@row;
  9.         $k ||= $n;
  10.         map{$hash{$_} = $k}@row;
  11. }
  12. $n = 1;
  13. foreach my $key(sort {$hash{$a} <=> $hash{$b}} keys %hash){
  14.                 if($hash{$key} eq $n){
  15.                         print $key."\t";
  16.                 }else{
  17.                         print $/.$key."\t";
  18.                         $n = $hash{$key}
  19.                 }
  20. }
  21. __DATA__
  22. 22875    30588     
  23. 22875    34747         
  24. 22912    34851
  25. 22963    35901      
  26. 22969    30588
  27. 30588    40791
  28. 30609    39143      
  29. 30609    40808
复制代码
友情提示,上面这个做法是有问题的

作者: yccpp    时间: 2015-02-03 23:25
谢谢huang6894, 我试试。
作者: yccpp    时间: 2015-02-04 00:21
我试了一下,好像是有点问题,如果运行给出的一小段数据,结果是正确的,如果是附件中的数据,结果有点问题,有些行只有一个数据,至少应该是两个的,请问huang6894,在您给出的程序中如何实现文件的输入输出。谢谢。
作者: xiumu2280    时间: 2015-02-04 11:36
本帖最后由 xiumu2280 于 2015-02-04 11:38 编辑
  1. my @data = map {[split]}<DATA>;

  2. my @out_data;

  3. foreach my $new (@data) {
  4.         my %hash;
  5.         foreach my $ori (@data) {
  6.                 @hash{@{$new}} = @{$new};
  7.                 if ($hash{$ori->[0]} || $hash{$ori->[1]}) {
  8.                         push @{$new},@{$ori};
  9.                 }
  10.         }
  11.         @hash{@{$new}} = @{$new};
  12.         push @out_data,join "\t",sort keys %hash;
  13. }

  14. my %hash;
  15. @hash{@out_data} = @out_data;
  16. for my $line (keys %hash) {
  17.         print "$line\n";
  18. }


  19. __DATA__
  20. 22875    30588     
  21. 22875    34747         
  22. 22912    34851
  23. 22963    35901      
  24. 22969    30588
  25. 30588    40791
  26. 30609    39143      
  27. 30609    40808
复制代码

作者: yccpp    时间: 2015-02-04 17:14
xiumu2280,谢谢你的帮助,我试试你的代码。
作者: chenyuluoyan沉鱼落    时间: 2015-02-04 17:35
回复 5# xiumu2280
大神my @data = map {[split]}<DATA>;中括号什么意思啊 为什么换成括号或去掉括号就不可以了呢 而
  1. my @data = map {[split]}<DATA>;

  2. my @out_data;

  3. foreach my $new (@data) {
  4.         print $new;
  5. }
  6.       


  7. __DATA__
  8. 22875    30588     
  9. 22875    34747         
  10. 22912    34851
  11. 22963    35901      
  12. 22969    30588
  13. 30588    40791
  14. 30609    39143      
  15. 30609    40808
复制代码
这样会报错
而把中括号去了或换成圆括号就可以了 这是为什么呢

   
作者: xiumu2280    时间: 2015-02-04 17:42
my @data = map {[split]}<DATA>;  这个是二维数组

涉及到引用的知识  可以百度  perl引用

下面也是相同的原因,变成()之后,@data是普通数组,所以简单的foreach又可以了

你可以用Data:umper模块 ,看一看数据结构,就明白了
回复 7# chenyuluoyan沉鱼落


   
作者: chenyuluoyan沉鱼落    时间: 2015-02-04 20:42
回复 8# xiumu2280
明白了 谢谢大神


   
作者: yccpp    时间: 2015-02-04 21:56
谢谢大神,计算结果是正确的。数据量小时,计算很快,我试了一下几百组数据,结果都是对的且挺快的,当数据量较大时,比如我附件中的数据,有1万多行,程序已经运行好几个小时了,还没有结果,等待中。再次谢谢xiumu2280 。
作者: xiumu2280    时间: 2015-02-05 09:09
把文件内容按大小排个序
再把文件分割,分别运行这个程序
最后把结果和在一起,再运行一次
回复 10# yccpp


   
作者: laohai8080    时间: 2015-02-05 10:10
#!/usr/bin/perl

use strict;
use warnings;

#if success output to outputNumber.txt;
if (@ARGV < 1)
{
        print "usage:mergeData.pl inputfile\n";
        exit(1);
}

my %datasHash;

open my $fileHandle, "<", $ARGV[0] or die "open $ARGV[0] failed$!";
while(my $lineData = <$fileHandle>)
{
        chomp $lineData;
        if($lineData =~ /^\s+$/)
        {
                next;
        }
        $lineData =~ s/^\s+//;
        $lineData =~ s/\s+$//;
        my @wordArray = split /\s+/, $lineData;
        my @exitsKey;
        my @notExitsKey;
        foreach(@wordArray)
        {
                if( exists $datasHash{$_})
                {
                        push @exitsKey,$_;
                }
                else
                {
                        push @notExitsKey,$_;
                }
        }
       
        if (@exitsKey == 0)
        {
                foreach(@wordArray)
                {
                        $datasHash{$_} = \@wordArray;
                }
        }
        else {
                my @mergeArray = @notExitsKey;
               
                for(my $index = 0; $index < @exitsKey; $index++)
                {
                        my $ref1 = $datasHash{$exitsKey[$index]};
                       
                        @mergeArray = (@$ref1, @mergeArray);
                       
                        @$ref1 = ();
                }
               
                foreach(@mergeArray)
                {
                        $datasHash{$_} = \@mergeArray;
                }
        }
}

my %refHash;
open my $outHandle, ">", "outputNumber.txt" or die "failed open outputNumber.txt $!";
foreach (keys %datasHash)
{
        my $ref1 = $datasHash{$_};
        if ( ! exists $refHash{$ref1})
        {
                my @result = join " ",@$ref1;
                #print @result, "\n";
                print $outHandle @result,"\n";
                $refHash{$ref1} = 1;
        }
}


作者: laohai8080    时间: 2015-02-05 10:12
初学Perl代码不够简练,多多指教!
作者: xiumu2280    时间: 2015-02-05 12:03
这个应该会快一些
  1. my %hash_all;
  2. while (<DATA>) {
  3.         chomp;
  4.         my @data = split /\s+/,my $line =$_;
  5.         push @{$hash_all{$data[0]}},$data[1];
  6.         push @{$hash_all{$data[1]}},$data[0];
  7. }

  8. my %out_data;

  9. while (my ($key,$value) = each %hash_all) {
  10.         my @all = @{$value};
  11.         BEGIN:
  12.         my %hash;
  13.         @hash{@all} = @all;
  14.         my $num_r = keys %hash;
  15.         for my $cell (keys %hash) {
  16.                 if ($hash_all{$cell}) {
  17.                         push @all,@{$hash_all{$cell}};
  18.                 }
  19.         }
  20.         @hash{@all} = @all;
  21.         my $num_l = keys %hash;
  22.         if ($num_l ne $num_r) {
  23.                 goto BEGIN;
  24.         }
  25.         my $new_line = join "\t",sort keys %hash;
  26.         print "$new_line\n" if !$out_data{$new_line}++;
  27. }


  28. __DATA__
  29. 22875    30588     
  30. 22875    34747         
  31. 22912    34851
  32. 22963    35901      
  33. 22969    30588
  34. 30588    40791
  35. 30609    39143      
  36. 30609    40808
复制代码

作者: yccpp    时间: 2015-02-05 16:50
谢谢laohai8080 和xiumu2280 的热心帮助。 xiumu2280的新代码快了不止一些,附件中的数据几秒就处理完了,非常感谢。我有两个问题想请教您,第一个问题是能否让处理的结果按每行数据的多少从大到小排序,比如输出的结果如下
22875    30588   34747 22969  40791
30609    39143   40808
22912    34851
22963    35901
第二个问题是自己对您的代码理解不深,从输出结果看,不是从第一行的数据开始排列的,好像比较随机,能不能请xiumu2280帮忙解读一下。谢谢。

作者: chenyuluoyan沉鱼落    时间: 2015-02-05 16:50
回复 8# xiumu2280
  1. @hash{@{$new}} = @{$new};
  2.                 if ($hash{$ori->[0]} || $hash{$ori->[1]}) {
  3.                         push @{$new},@{$ori};
复制代码
这段代码可以解释一下吗 @hash{@$new}}表示什么呢
   
作者: xiumu2280    时间: 2015-02-06 09:02
这是哈希切片  用来给数组去重回复 16# chenyuluoyan沉鱼落


   
作者: xiumu2280    时间: 2015-02-06 09:07
因为这次是存的哈希,而遍历哈希的时候,是不像数组一样有顺序的。
如果你想这样从大到小的排列
可以对得到的数据进行二次处理,把这次得到的数据按大小排序就好了。
回复 15# yccpp


   
作者: yccpp    时间: 2015-02-06 20:52
谢谢xiumu2280,涨姿势了。
作者: fly_in_thesky    时间: 2015-02-08 15:36
回复 18# xiumu2280


    那有没有办法让数据输出就是按照读入的顺序呢? 我的意思是比如输出时的键值按照按照他给的数据键对应后面依次为第一次出现该键的值,第二次出现该键的值。 因为如果后面的数据不是数字,而是一些无规律的字母,比如地名,是没有办法再处理的。 特别是当数据非常大的时候。 求指教!
作者: b98103032b    时间: 2015-02-08 18:17
提示: 作者被禁止或删除 内容自动屏蔽
作者: yccpp    时间: 2015-02-19 21:23
b98103032b,我对PERL 不熟,还真不懂。
我还想请教xiumu2280,如何让输出结果如下面所示:
22875,30588 ,34747,22969,40791
30609,39143,40808
22912,34851
22963,35901
也就是数据用逗号或者其他字符隔开,谢谢,祝春节快乐!
作者: yccpp    时间: 2015-03-12 23:42
还是这个问题想请教 xiumu2280,由于我需要处理的数据增多,现在处理一个9万行左右的文件需要2到3天的时间,想请教xiumu2280,您的程序是否可以增加并行计算功能从而提高处理速度。谢谢。
作者: rubyish    时间: 2015-03-16 17:26
本帖最后由 rubyish 于 2015-03-16 13:46 编辑

shishi:

perl abc.pl > save
  1. #!/usr/bin/perl

  2. my ( %A, @B, %C );
  3. open my $DATA, '/tmp/number.txt';

  4. while (<$DATA>) {
  5.     my ( $A, $B ) = split;
  6.     push @{ $A{$A} }, $B;
  7.     push @{ $A{$B} }, $A;
  8.     push @B, $A;
  9. }

  10. for my $k (@B) {
  11.     next if exists $C{$k};
  12.     my @K = keys %{ pick($k) };
  13.     @C{@K} = ();
  14.     print join( ', ', sort @K ), "\n";
  15. }

  16. sub pick {
  17.     my ( $K, $H ) = @_;
  18.     return if $H->{$K}++;
  19.     pick( $_, $H ) for @{ $A{$K} };
  20.     $H;
  21. }

  22. __DATA__
  23. 22875    30588
  24. 22875    34747
  25. 22912    34851
  26. 22963    35901
  27. 22969    30588
  28. 30588    40791
  29. 30609    39143
  30. 30609    40808
复制代码

作者: yccpp    时间: 2015-03-17 18:05
本帖最后由 yccpp 于 2015-03-18 15:27 编辑

谢谢rubyish,我试了一下,速度超快。非常感谢。




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2