代码之家  ›  专栏  ›  技术社区  ›  Mike

这个Perl代码如何从一个数组中选择两个不同的元素?

  •  8
  • Mike  · 技术社区  · 15 年前

    我从一个人那里继承了一些代码,他过去最喜欢把每一行都缩短到绝对最小(有时只是为了让它看起来更酷)。他的代码很难理解,但我设法理解(并重写)了大部分代码。

    现在我偶然发现了一段代码,不管我多么努力,我都无法理解。

    my @heads = grep {s/\.txt$//} OSA::Fast::IO::Ls->ls($SysKey,'fo','osr/tiparlo',qr{^\d+\.txt$}) || ();
    my @selected_heads = ();
    for my $i (0..1) {
       $selected_heads[$i] = int rand scalar @heads;
       for my $j (0..@heads-1) {
          last if (!grep $j eq $_, @selected_heads[0..$i-1]);
          $selected_heads[$i] = ($selected_heads[$i] + 1) % @heads; #WTF?
       }
       my $head_nr = sprintf "%04d", $i;
       OSA::Fast::IO::Cp->cp($SysKey,'',"osr/tiparlo/$heads[$selected_heads[$i]].txt","$recdir/heads/$head_nr.txt");
       OSA::Fast::IO::Cp->cp($SysKey,'',"osr/tiparlo/$heads[$selected_heads[$i]].cache","$recdir/heads/$head_nr.cache");
    }
    

    据我所知,这应该是某种随机化器,但我从未见过更复杂的方法来实现随机性。或者我的假设是错误的?至少,这就是代码应该做的。选择2个随机文件并复制它们。

    ===Notes====

    OSA框架是我们自己的框架。它们是以它们的Unix对应项命名的,并进行一些基本的测试,这样应用程序就不需要为此而烦恼了。

    5 回复  |  直到 15 年前
        1
  •  12
  •   brian d foy    15 年前

    这看起来像一些带有Perl语法的C代码。有时,了解对方所想的语言有助于你弄清发生了什么。在这种情况下,人的大脑会受到内存管理、指针算术和其他低级问题的内部工作的影响,因此他想对所有事情进行细微的控制:

    my @selected_heads = ();
    
    # a tricky way to make a two element array
    for my $i (0..1) {
    
       # choose a random file
       $selected_heads[$i] = int rand @heads;
    
       # for all the files (could use $#heads instead)
       for my $j (0..@heads-1) {
          # stop if the chosen file is not already in @selected_heads
          # it's that damned ! in front of the grep that's mind-warping
          last if (!grep $j eq $_, @selected_heads[0..$i-1]);
    
          # if we are this far, the two files we selected are the same
          # choose a different file if we're this far
          $selected_heads[$i] = ($selected_heads[$i] + 1) % @heads; #WTF?
       }
    
    ...
    }
    

    这是很多工作,因为原始程序员要么不理解哈希值,要么不喜欢哈希值。

    my %selected_heads;
    until( keys %selected_heads == 2 )
        {
        my $try = int rand @heads;
        redo if exists $selected_heads{$try};
        $selected_heads{$try}++;
        }
    
    my @selected_heads = keys %selected_heads;
    

    如果您仍然不喜欢散列,并且拥有Perl5.10或更高版本,则可以使用智能匹配来检查值是否在数组中:

    my @selected_heads;
    until( @selected_heads == 2 )
        {
        my $try = int rand @heads;
        redo if $try ~~ @selected_heads;
        push @selected_heads, $try;
        }
    

    但是,您对这个问题有一个特殊的约束。因为您知道只有两个元素,所以您只需检查要添加的元素是否是前一个元素。在第一种情况下,它不会是不死的,所以第一个加法总是有效的。在第二种情况下,它不能是数组中的最后一个元素:

    my @selected_heads;
    until( @selected_heads == 2 )
        {
        my $try = int rand @heads;
        redo if $try eq $selected_heads[-1];
        push @selected_heads, $try;
        }
    

    呵呵。我不记得我上次用过 until 当它真正适合这个问题时。:)

    请注意,所有这些解决方案都存在这样一个问题:如果原始文件的数量小于2,它们可能导致无限循环。我会在更高的位置添加一个保护条件,这样就可以通过一个错误来处理no和single文件案例,也许这两个文件案例不会麻烦您订购它们。

    另一种方法是洗牌 List::Util )完整的原始文件列表,只需去掉前两个文件:

    use List::Util qw(shuffle);
    
    my @input = 'a' .. 'z';
    
    my @two = ( shuffle( @input ) )[0,1];
    
    print "selected: @two\n";
    
        2
  •  2
  •   DVK    15 年前

    它从@heads中选择一个随机元素。

    然后再加上另一个随机数 但不同 来自@heads的元素(如果它是以前选择的元素,则会滚动到@heads,直到找到以前未选择的元素)。

    总之,它在@heads数组中选择n个(在您的情况下n=2)不同的随机索引,然后复制与这些索引对应的文件。

    就我个人而言,我会写得有点不同:

    # ...
    %selected_previously = ();
    foreach my $i (0..$N) { # Generalize for N random files instead of 2
        my $random_head_index = int rand scalar @heads;
        while ($selected_previously[$random_head_index]++) {
            $random_head_index = $random_head_index + 1) % @heads; # Cache me!!!
        }
        # NOTE: "++" in the while() might be considered a bit of a hack
        # More readable version: $selected_previously[$random_head_index]=1; here.
    
        3
  •  1
  •   Axeman maxelost    15 年前

    你贴上“wtf”标签的那部分并不是那么麻烦,只是要确保 $selected_heads[$i] 保留为的有效下标 @head . 真正令人不安的是,这是一种非常低效的方法,以确保他没有选择相同的文件。

    再重申一次,如果 @heads 很小,从 0..$#heads 可能比生成更有效 int rand( 2 ) 测试它们是否相同。

    但基本上它随机复制两个文件(为什么?)作为“.txt”文件和“.cache”文件。

        4
  •  1
  •   brian d foy    15 年前

    只是怎么样

    for my $i (0..1) {
        my $selected = splice( @heads, rand @heads, 1 );
        my $head_nr = sprintf "%04d", $i;
        OSA::Fast::IO::Cp->cp($SysKey,'',"osr/tiparlo/$selected.txt","$recdir/heads/$head_nr.txt");
        OSA::Fast::IO::Cp->cp($SysKey,'',"osr/tiparlo/$selected.cache","$recdir/heads/$head_nr.cache");
    }
    

    除非 @heads @selected_heads 以后使用。

        5
  •  0
  •   jfs    15 年前

    这里还有另一种选择2个唯一随机索引的方法:

    my @selected_heads = ();
    my @indices = 0..$#heads;
    for my $i (0..1) {
      my $j = int rand (@heads - $i);
      push @selected_heads, $indices[$j];
      $indices[$j] = $indices[@heads - $i - 1];
    }