假设我在列中有一些数字数据,比如
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
列中为1
,则脚本应输出11.7
。如果有两个与目标值等距的数字,则应输出较高的值。
我觉得awk具有执行此操作的必要功能,但欢迎任何在bash脚本中运行的解决方案。
答案 0 :(得分:7)
试试这个:
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
简短说明。
我不会解释每行代码,它的作用。告诉我们做这项工作的想法。
v
,并计算它与给定目标之间的差异,将其保存(绝对值)d
d
,则用diff覆盖d
,同时将当前元素保存到v
注意事项:
d(abs)
,如果新差异&gt; d,我们可以停止循环。O(nlogn)
。事实上,这个问题可以通过 O(n)
来解决。如果您的输入数据很大,并且情况最糟糕(例如,您的列的值在500-99999999999
范围内,但您的目标是1.),您可能希望避免排序。但我认为你的表现不是问题。答案 1 :(得分: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
(=列)和v
(=值)参数。
然后我们会跟踪较低的值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
答案 2 :(得分: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 可能不起作用(What Every Computer Scientist Should Know About Floatin-Point Arithmetic)。您可能需要abs(abs($closest - $target) - abs($target - $value)) < 1e-14
。