3

假设我在列中有一些数值数据,例如

11.100000 36.829657 6.101642
11.400000 36.402069 5.731998
11.700000 35.953025 5.372652
12.000000 35.482082 5.023737
12.300000 34.988528 4.685519
12.600000 34.471490 4.358360
12.900000 33.930061 4.042693
13.200000 33.363428 3.738985
13.500000 32.770990 3.447709
13.800000 32.152473 3.169312

我还有一个目标值和一个列索引。给定这组数据,我想在具有指定索引的列中找到最接近目标值的值。

例如,如果我的目标值11.6在 column 中1,那么脚本应该输出11.7。如果有两个数字与目标值等距,则应输出较高的值。

我觉得 awk 具有执行此操作的必要功能,但是欢迎任何在 bash 脚本中工作的解决方案。

4

3 回答 3

8

尝试这个:

awk -v c=2 -v t=35 'NR==1{d=$c-t;d=d<0?-d:d;v=$c;next}{m=$c-t;m=m<0?-m:m}m<d{d=m;v=$c}END{print v}' file

和可以是动态值-v c=2-v t=35它们是列 idx ( c) 和您的目标值 ( t)。在上面的行中,参数是第 2 列和目标 25。它们可以是 shell 变量。

基于给定输入数据的上述行的输出是:

kent$  awk -v c=2 -v t=35 'NR==1{d=$c-t;d=d<0?-d:d;v=$c;next}{m=$c-t;m=m<0?-m:m}m<d{d=m;v=$c}END{print v}' f
34.988528

kent$  awk -v c=1 -v t=11.6 'NR==1{d=$c-t;d=d<0?-d:d;v=$c;next}{m=$c-t;m=m<0?-m:m}m<d{d=m;v=$c}END{print v}' f
11.700000

编辑

如果有两个数字与目标值等距,则应输出较大的值

上面的代码没有检查这个要求....下面的代码应该可以工作:

awk -v c=1 -v t=11.6 '{a[NR]=$c}END{
        asort(a);d=a[NR]-t;d=d<0?-d:d;v = a[NR]
        for(i=NR-1;i>=1;i--){
                m=a[i]-t;m=m<0?-m:m
                if(m<d){
                    d=m;v=a[i]
                }
        }
        print v
}' file

测试:

kent$  awk -v c=1 -v t=11.6 '{a[NR]=$c}END{
        asort(a);d=a[NR]-t;d=d<0?-d:d;v = a[NR]
        for(i=NR-1;i>=1;i--){
                m=a[i]-t;m=m<0?-m:m
                if(m<d){
                    d=m;v=a[i]
                }
        }
        print v
}' f
11.700000

简短的解释。

我不会解释每一行代码,它的作用。只是告诉一点做这项工作的想法。

  • 首先读取给定列中的所有元素,保存在数组中
  • 对数组进行排序。
  • 从数组中取出最后一个元素(最大的数字)。将其分配给 var v,并计算它与给定目标之间的差异,将其(绝对值)保存在d
  • 从数组循环的第二个最后一个元素到第一个。如果元素和目标之间的差异(绝对值)小于d,用差异覆盖d,也将当前元素保存到v
  • 打印v,循环之后,v就是答案。

一些注意事项

  • 有优化逻辑的空间。例如,我们不必遍历整个数组。只需比较d(abs),如果 new diff > d,我们可以停止循环。
  • 由于排序,这个算法是O(nlogn). 其实这个问题可以通过O(n). 如果您的输入数据很大,并且在最坏的情况下(例如,您的列的值在 range 500-99999999999,但您的目标是 1。)您可能希望避免排序。但我认为性能不是你的问题。
于 2013-07-25T08:50:48.863 回答
2

让我们尝试另一种方式,虽然肯特的回答必须更短更尖锐:)

awk -vc=1 -vv=13.6 '
    BEGIN{l=$c; ld=99}
    {d=($c-v>=0) ? ($c-v) : v-$c; if (d <= ld) {ld=d; l=$c}}
    END{print l}' file

我们在开始时提供c(=column) 和v(=value) 参数。

然后我们跟踪较低的值l和最低的距离ld。对于每个值,我们计算与该值的距离d,如果它低于前一个值ld,我们交换并保存新的最小值l。最后我们打印l.

d=($c-v>=0) ? ($c-v) : v-$c是一种将距离保存为绝对值的方法:如果c-v是负数,则将其保存为正数。它基于value=(condition) ? if yes : else结构。

测试

$ awk -vc=2 -vv=13.6 'BEGIN{l=$c; ld=99} {d=($c-v>=0) ? ($c-v) : v-$c; if (d <= ld) {ld=d; l=$c}} END{print l}' file
32.152473
$ awk -vc=3 -vv=10.6 'BEGIN{l=$c; ld=99} {d=($c-v>=0) ? ($c-v) : v-$c; if (d <= ld) {ld=d; l=$c}} END{print l}' file
3.169312
于 2013-07-25T08:56:31.733 回答
2

Perl 解决方案:

#!/usr/bin/perl
use warnings;
use strict;

@ARGV == 2 or die "Usage: closest column value < input\n";
my ($column, $target) = (shift, shift);
my $closest;
while (<>) {
    my $value = (split)[$column - 1];
    if ($. == 1
        or abs($closest - $target) >  abs($target - $value)
        or abs($closest - $target) == abs($target - $value)
           && $value > $closest) {
        $closest = $value;
    }
}
print $closest, "\n";

请注意,使用float == float可能不起作用(每个计算机科学家都应该知道的关于浮点运算的知识)。您可能需要类似abs(abs($closest - $target) - abs($target - $value)) < 1e-14.

于 2013-07-25T09:15:51.957 回答