匹配数组中的值与容差

时间:2012-12-11 08:06:26

标签: arrays perl

我正在尝试清除数组中的重复值,我正在使用“List :: MoreUtils uniq / distinct”函数成功完成。

但是,我还想计算那些在给定容差范围内的值,比如+ -5,作为重复值(我认为公差有时也称为“delta”)。

例如,如果588是数组中的值,但589是一个值,因为差异在5的容差范围内,589会启动。

如果没有一些令人讨厌/昂贵的数组交叉检查,有没有一种优雅的方法来做到这一点?

编辑: ikegami在我的问题中引起了我一些模棱两可的注意,我在解决这个问题时遇到了一些困难。但是,我想我已经解决了。

[500,505,510,515,525,900]

如果您尝试匹配整个数组中的值,您应该得到:

[500,510,525,900]

它击中505,将其视为非唯一,将其从阵列中移除,然后由于缺少505而将510视为新的唯一,依此类推。我想象这是我概述原始问题的方式,但经过反思,它似乎是一个无用且相当随意的数据集。

我真正想要的是以下比赛:

[500,900]

它代表一组彼此相差5的数字,同时也发现900值的巨大差异。这似乎比前者更有用的信息,似乎perreal的答案让我接近。对于这种混乱感到抱歉,非常感谢ikegami和perreal强迫我做出澄清。

编辑2 更好的匹配是:

[510,900]

510,是所有连续+ -5值的中位数。

然而,我认识到现在我们正在严重偏离原来的问题,所以我对我的编辑1澄清答案非常满意。

2 个答案:

答案 0 :(得分:2)

这是一个看似复杂的问题,因为数据不仅必须组织成组,而且如果看到属于多个数据点的新数据点,则必须组合这些组。

这个程序似乎可以满足您的需求。它保留了一个数组列表@buckets,其中每个元素包含到目前为止看到的所有值,这些值在另一个TOLERANCE之内。扫描该列表以查看每个值是否落在已存在的最大值和最小值的范围内。值所属的组的索引存储在memberof中,并且此数组中始终存在零个,一个或两个条目。

@memberof指定的所有群组都会从@buckets中移除,与新数据值合并,排序并替换为列表中的新群组。

最后,@buckets数组将转换为中值列表,并进行排序和显示。我已经使用Data::Dump来显示组的内容,然后将它们聚合到它们的中值。

要从列表510, 900生成所需的输出500, 510, 525, 900,必须增加TOLERANCE的值,以便将相差15或更少的值合并。

use strict;
use warnings;

use constant TOLERANCE => 5;

my @data = qw/ 500 505 510 515 525 900 /;

my @buckets;

for my $item (@data) {

  my @memberof;
  for my $i (0 .. $#buckets) {
    if ($item >= $buckets[$i][0] - TOLERANCE and $item <= $buckets[$i][-1] + TOLERANCE) {
      push @memberof, $i;
    }
  }

  my @newbucket = ($item);
  for my $i (reverse @memberof) {
    push @newbucket, @{ splice @buckets, $i, 1 };
  }

  push @buckets, [ sort { $a <=> $b } @newbucket ];
}

use Data::Dump;
dd @buckets;

@buckets = sort { $a <=> $b } map median(@$_), @buckets;
print join(', ', @buckets), "\n";

sub median {

  my $n = @_;
  my $i = $n / 2;

  if ($n % 2) {
    return $_[$i];
  }
  else {
    return ($_[$i-1] + $_[$i]) / 2;
  }
}

<强>输出

([500, 505, 510, 515], [525], [900])
507.5, 525, 900

答案 1 :(得分:2)

隔离形成链的样本,其中每个样本都在下一个容差范围内,然后从该组中选择一个。

sub collapse {
   my $tol = shift;

   my @collapsed;
   while (@_) {
      my @group = shift(@_);
      while (@_ && $group[-1] + $tol >= $_[0]) {
         push @group, shift(@_);
      }

      push @collapsed, choose_from(@group);
   }

   return @collapsed;
}

say join ',', collapse(5 => 500,505,510,515,525,900);

那你怎么选择?好吧,你可以恢复平均值。

use List::Util qw( sum );

sub choose_from {
   return sum(@_)/@_;
}

# Outputs: 507.5,525,900

或者你可以返回中位数。

use List::Util qw( sum );

sub choose_from {
   my $median;
   if (@_ % 2 == 0) {
      my $avg = sum(@_)/@_;
      my $diff0 = abs( $_[ @_/2 - 1 ] - $avg );
      my $diff1 = abs( $_[ @_/2 - 0 ] - $avg );
      if ($diff0 <= $diff1) {
         return $_[ @_/2 - 1 ];
      } else {
         return $_[ @_/2 - 0 ];
      }
   } else {
      return $_[ @_/2 ];
   }
}

# Outputs: 505,525,900