0

「List::MoreUtils uniq/distinct」関数を使用して、配列内の重複する値を除外しようとしています。

ただし、特定の許容範囲内にある値、たとえば +-5 も重複としてカウントしたいと思います (許容範囲は「デルタ」とも呼ばれることがあると思います)。

たとえば、配列内の値が 588 で 589 の場合、差が 5 の許容範囲内にあるため、589 が起動します。

厄介な/コストのかかる配列のクロスチェックなしで、これを行うエレガントな方法はありますか?

編集:池上は私の質問にいくつかのあいまいさをもたらしました。私は問題に頭を悩ませています。しかし、私はそれがうまくいったと思います。

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

配列全体で値を一致させようとすると、次のようになります。

[500,510,525,900]

505 にヒットし、それを一意でないと見なし、配列から削除してから、505 がないために 510 を新たに一意であると見なします。これは、元の質問の概要を説明した方法だと思いますが、振り返ってみると、役に立たず、かなり恣意的なデータセットのようです。

私が本当に欲しいのは、次の試合です:

[500,900]

これは、互いに 5 以内の数値のグループを表し、900 の値の大きな分散も示しています。これは前者よりも有用な情報であると思われ、perreal の回答が私を近づけているようです。混乱を招いて申し訳ありません。池上氏と perreal 氏に、説明を強要していただいたことに感謝します。

EDIT 2 さらに良い一致は次のとおりです。

[510,900]

510、すべての連続した +-5 値の中央値です。

ただし、元の質問から大幅に逸脱していることを認識しているため、EDIT 1の説明に対する回答に満足しています。

4

2 に答える 2

2

これは一見複雑な問題です。データをグループに編成するだけでなく、複数のグループに属する新しいデータポイントが見つかった場合は、それらのグループを組み合わせる必要があるためです。

このプログラムはあなたが必要なことをしているようです。配列のリストを保持します@buckets。各要素には、これまでに見られた相互の範囲内TOLERANCEのすべての値が含まれます。このリストをスキャンして、各値がすでに存在する最大値と最小値の範囲内にあるかどうかを確認します。値が属するグループのインデックスはに格納されmemberof、この配列には常に0、1、または2つのエントリがあります。

で指定されたすべてのグループ@memberofがから削除され@buckets、新しいデータ値と結合され、並べ替えられ、リスト内の新しいグループとして置き換えられます。

最後に、@buckets配列は中央値のリストに変換され、ソートされて表示されます。私はData::Dump、グループが中央値に集約される前に、グループの内容を表示するために使用しました。

510, 900リストから目的の出力を生成するには、15以下の差がある値が結合されるように、500, 510, 525, 900の値を増やす必要があります。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

それぞれが次の許容範囲内にあるチェーンを形成するサンプルを分離し、そのグループから 1 つを選択します。

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 に答える