从值字符串中获取中位数

Get median from string of values

我需要找到保存在字符串中的值的中位数。我必须在 bash 中实现此功能而无需任何其他临时文件,并且我 不能 使用 awk。

我把这个字符串保存在 $string:

85 13 4 45 1111 89 87 66 1 5 2 51 13 66 98 50 20 14 18 16 31 21 5175 12

首先,我需要像这样对这些值进行排序:

1 2 4 5 12 13 13 14 16 18 20 21 31 45 50 51 66 66 85 87 89 98 1111 5175

然后我需要找到这些值的中位数

(21+31) / 2 = 26

我怎样才能做到这一点? bash有没有有效的方法或命令?

我的想法:

要对值进行排序,我可以使用 sort,但我不确定如何强制它对字符串中的值进行排序,因为它使用的是 FILE。

虽然我不知道如何实现中位数,所以我希望至少能得到一些小提示。

您可以使用如下命令:

str="85 13 4 45 1111 89 87 66 1 5 2 51 13 66 98 50 20 14 18 16 31 21 5175 12"
count=$(echo $str | wc -w)
arr=($(echo $str | tr " " "\n" | sort -n ))
#echo ${arr[*]}

if [[ $(( $count % 2 )) == 0 ]]; then 
    # even element count, get the elements around the middle
    f1=${arr[ $(( (count - 1 ) /2 )) ]}
    f2=${arr[ $(( (count + 1 ) /2 )) ]}
    #echo "f1=$f1, f2=$f2"
    echo $(( ($f1 + f2) / 2 )) 
else
    # odd element count
    echo ${arr[ $(( $count / 2 ))]}
fi

要将字符串中的数字放入排序数组中,您可以将它们分别打印在单独的一行上,通过管道传输到 sort -n,然后使用 mapfile:[=22 读入数组=]

string='85 13 4 45 1111 89 87 66 1 5 2 51 13 66 98 50 20 14 18 16 31 21 5175 12'
mapfile -t arr < <(for num in $string; do echo "$num"; done | sort -n)

-t 选项从每个值中删除换行符。请注意,您 不能 管道到 mapfile 因为那将在子外壳中并且 arr 之后将是空的。

引用你的变量通常是个好主意,但在这种情况下,我们依赖分词,不能引用 $string

现在,对于中位数,有两种选择:

  • 数组元素个数为奇数,我们只需要中间元素的值。
  • 数组元素个数为偶数,求中间两个元素的平均值

数组元素的数量是${#arr[@]},所以我们可以检查一下,然后决定做什么:

nel=${#arr[@]}
if (( nel % 2 == 1 )); then     # Odd number of elements
    val="${arr[ $((nel/2)) ]}"
else                            # Even number of elements
    val="$(( ( arr[$((nel/2))] + arr[$((nel/2-1))] ) / 2 ))"
fi
printf "%d\n" "$val"

这依赖于整数运算:如果我们有奇数个元素,比如三个,则中位数的索引为 1 – 我们可以从三除以二得到整数。对于偶数个元素,比如四个,我们需要索引 1 和 2 处的元素,我们通过将四除以二得到较高索引并从中减去一以获得较低索引。

如果两个元素相加不是偶数,结果将向下舍入。如果这还不够好,我们可以检查数字是否为奇数并手动将 .5 添加到结果中,或者我们可以使用 bc 进行计算。考虑:

$ echo $(( 11/2 ))
5
$ bc <<< 'scale=1; 11/2'
5.5