如何找到两个数组元素之间的距离?

时间:2010-12-08 19:37:18

标签: perl algorithm debugging

我正在使用perl编写,但它似乎更像是一个算法问题。欢迎使用其他语言的回复。

我有两个排序的整数数组,shortlong。对于short中的每个元素,我想在long中找到最接近的元素,在我的特定情况下,我想要制作距离的直方图。

这是我正在使用的算法:

sub makeDistHist {
    my ($hist, $short, $long, $max) = @_; # first 3 are array references

    my $lIndex = 0;
    foreach my $s (@$short) {
        my $distance = abs( $s - $long->[$lIndex] );
        while (abs( $s - $long->[$lIndex+1] ) < $distance) {
            $distance = abs( $s - $long->[$lIndex] );
            $lIndex++;
        }
        $distance = $max if $distance>$max; # make overflow bin
        $hist->[$distance]++;
    }  
}

这取决于shortlong的排序。

这是我编写的用于测试算法的子程序。第一次测试成功,但第二次测试失败:

sub test { # test makeDistHist 

    my @long = qw(100 200 210 300 350 400 401 402 403 404 405 406);
    my @short = qw(3 6 120 190 208 210 300 350);
    my @tarHist;
    $tarHist[97]++;
    $tarHist[94]++;
    $tarHist[20]++;
    $tarHist[10]++;
    $tarHist[2]++;
    $tarHist[0]+=3;

    my $max = 3030;
    my @gotHist;
    makeDistHist(\@gotHist, \@short, \@long, $max);

    use Test::More tests => 2;
    is_deeply(\@gotHist, \@tarHist, "did i get the correct distances for two different arrays?");

    @gotHist = ();
    @tarHist = ( @long+0 );
    makeDistHist(\@gotHist, \@long, \@long, $max);
    is_deeply(\@gotHist, \@tarHist, "did i get the correct distances between an array and itself?");  # nope!
    print Dumper(\@gotHist);
}

这是转储:

$VAR1 = [
          7,
          5
        ];

(如果我将long与其副本减去一个元素进行比较,则问题仍然存在,因此算法要求short严格要比long严格要短。我将401,402 ...更改为402,404 ... gotHist变为(7, undef, 5)。)

以下是我们所喜欢的内容:首先,这是一个有效的算法。要么修复我所拥有的,要么从整个布料中设计另一个。其次,我可以在调试技巧方面使用帮助。您将如何使用现有算法识别问题?如果我能这样做,我就不需要问这个问题:)

谢谢!

2 个答案:

答案 0 :(得分:3)

你应该打破子程序:计算距离和构建直方图是两回事,试图将两者结合起来会失去很多清晰度。

首先从最简单的解决方案开始。我通过使用排序的@long了解潜在的优化,但只有在List::Util::min速度缓慢的情况下才能使用它。

您可以使用Statistics::Descriptive生成频次分配。

#!/usr/bin/perl

use strict; use warnings;
use List::Util qw( min );
use Statistics::Descriptive;

my $stat = Statistics::Descriptive::Full->new;

my @long = (100, 200, 210, 300, 350, 400, 401, 402, 403, 404, 405, 406);
my @short = (3, 6, 120, 190, 208, 210, 300, 350);

for my $x ( @short ) {
    $stat->add_data(find_dist($x, \@long));
}

my $freq = $stat->frequency_distribution_ref([0, 2, 10, 20, 94, 97]);
for my $bin ( sort { $a <=> $b } keys %$freq ) {
    print "$bin:\t$freq->{$bin}\n";
}

sub find_dist {
    my ($x, $v) = @_;
    return min map abs($x - $_), @$v;
}

输出:

[sinan@archardy so]$ ./t.pl
0:      3
2:      1
10:     1
20:     1
94:     1
97:     1

当然,可以不使用任何模块并使用您对已排序@long的假设来执行此操作:

#!/usr/bin/perl

use strict; use warnings;

my @long = (100, 200, 210, 300, 350, 400, 401, 402, 403, 404, 405, 406);
my @short = (3, 6, 120, 190, 208, 210, 300, 350);

my @bins = reverse (0, 2, 10, 20, 94, 97);
my %hist;

for my $x ( @short ) {
    add_hist(\%hist, \@bins, find_dist($x, \@long));
}

for my $bucket ( sort { $a <=> $b } keys %hist ) {
    print "$bucket:\t$hist{$bucket}\n";
}

sub find_dist {
    my ($x, $v) = @_;
    my $min = abs($x - $v->[0]);
    for my $i ( 1 .. $#$v ) {
        my $dist = abs($x - $v->[$i]);
        last if $dist >= $min;
        $min = $dist;
    }
    return $min;
}

sub add_hist {
    my ($hist, $bins, $x) = @_;
    for my $u ( @$bins ) {
        if ( $x >= $u ) {
            $hist{ $u } += 1;
            last;
        }
    }
    return;
}

答案 1 :(得分:0)

关于有关调试的部分,请使用允许断点的IDE。我没有perl的例子,但是对于PHP和ASP.NET,分别有Eclipse和Visual Studio(或免费版本,Visual Web Developer)。