0

我正在尝试清除数组中的重复值,我通过“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 值的中位数。

但是,我认识到现在我们严重偏离了我原来的问题,所以我很高兴能回答我的 EDIT 1 澄清。

4

2 回答 2

2

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

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

由 指定的所有组@memberof都从 中删除@buckets,与新数据值组合在一起,排序并替换为列表中的新组。

最后,@buckets数组被转换为中值列表,排序和显示。我曾经Data::Dump在将它们聚合到它们的中值之前显示组的内容。

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

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
于 2012-12-11T12:03:25.250 回答
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
于 2012-12-11T15:13:56.573 回答