给定一个字符串数组,我想根据每个元素的长度对数组进行排序。
例如...
array=(
"tiny string"
"the longest string in the list"
"middle string"
"medium string"
"also a medium string"
"short string"
)
应该排序为...
"the longest string in the list"
"also a medium string"
"medium string"
"middle string"
"short string"
"tiny string"
(作为奖励,如果列表按字母顺序对相同长度的字符串进行排序,那就太好了。在上面的示例中,即使它们具有相同的长度,也可以medium string
在之前进行排序middle string
。但这不是一个“硬”要求,如果它使解决方案)。
如果数组就地排序(即“数组”被修改)或者创建一个新的排序数组,这是可以的。
答案1
如果字符串不包含换行符,则以下内容应该有效。它使用字符串本身作为辅助排序标准,按长度对数组的索引进行排序。
#!/bin/bash
array=(
"tiny string"
"the longest string in the list"
"middle string"
"medium string"
"also a medium string"
"short string"
)
expected=(
"the longest string in the list"
"also a medium string"
"medium string"
"middle string"
"short string"
"tiny string"
)
indexes=( $(
for i in "${!array[@]}" ; do
printf '%s %s %s\n' $i "${#array[i]}" "${array[i]}"
done | sort -nrk2,2 -rk3 | cut -f1 -d' '
))
for i in "${indexes[@]}" ; do
sorted+=("${array[i]}")
done
diff <(echo "${expected[@]}") \
<(echo "${sorted[@]}")
请注意,转向真正的编程语言可以大大简化解决方案,例如在 Perl 中,您可以
sort { length $b <=> length $a or $a cmp $b } @array
答案2
readarray -t array < <(
for str in "${array[@]}"; do
printf '%d\t%s\n' "${#str}" "$str"
done | sort -k 1,1nr -k 2 | cut -f 2- )
这从进程替换中读取排序数组的值。
进程替换包含循环。循环输出数组的每个元素,前面加上元素的长度和中间的制表符。
循环的输出按数字从最大到最小排序(如果长度相同,则按字母顺序排序;使用-k 2r
代替 来-k 2
反转字母顺序),结果为那被发送到cut
删除具有字符串长度的列。
对测试脚本进行排序,然后进行测试运行:
array=(
"tiny string"
"the longest string in the list"
"middle string"
"medium string"
"also a medium string"
"short string"
)
readarray -t array < <(
for str in "${array[@]}"; do
printf '%d\t%s\n' "${#str}" "$str"
done | sort -k 1,1nr -k 2 | cut -f 2- )
printf '%s\n' "${array[@]}"
$ bash script.sh
the longest string in the list
also a medium string
medium string
middle string
short string
tiny string
这假设字符串不包含换行符。在具有最近的 GNU 系统上bash
,您可以通过使用 nul 字符而不是换行符作为记录分隔符来支持数据中嵌入换行符:
readarray -d '' -t array < <(
for str in "${array[@]}"; do
printf '%d\t%s\0' "${#str}" "$str"
done | sort -z -k 1,1nr -k 2 | cut -z -f 2- )
在这里,数据\0
在循环中以尾随而不是换行符打印,sort
和通过其GNU 选项cut
读取 nul 分隔行,最后使用 读取 nul 分隔数据。-z
readarray
-d ''
答案3
我不会完全重复我已经说过关于 bash 中的排序, 只有你能在 bash 中排序,但也许你不应该。下面是插入排序的仅 bash 实现,其时间复杂度为 O(n 2 ),因此仅适用于小型数组。它按数组元素的长度按降序对数组元素进行就地排序。它不进行二次字母排序。
array=(
"tiny string"
"the longest string in the list"
"middle string"
"medium string"
"also a medium string"
"short string"
)
function sort_inplace {
local i j tmp
for ((i=0; i <= ${#array[@]} - 2; i++))
do
for ((j=i + 1; j <= ${#array[@]} - 1; j++))
do
local ivalue jvalue
ivalue=${#array[i]}
jvalue=${#array[j]}
if [[ $ivalue < $jvalue ]]
then
tmp=${array[i]}
array[i]=${array[j]}
array[j]=$tmp
fi
done
done
}
echo Initial:
declare -p array
sort_inplace
echo Sorted:
declare -p array
作为这是一个专门解决方案的证据,请考虑现有三个答案在不同大小的数组上的时间:
# 6 elements
Choroba: 0m0.004s
Kusalananda: 0m0.004s
Jeff: 0m0.018s ## already 4 times slower!
# 1000 elements
Choroba: 0m0.004s
Kusalananda: 0m0.004s
Jeff: 0m0.021s ## up to 5 times slower, now!
5000 elements
Choroba: 0m0.004s
Kusalananda: 0m0.004s
Jeff: 0m0.019s
# 10000 elements
Choroba: 0m0.004s
Kusalananda: 0m0.006s
Jeff: 0m0.020s
# 99000 elements
Choroba: 0m0.015s
Kusalananda: 0m0.012s
Jeff: 0m0.119s
答案4
如果可以zsh
选择切换到,则有一种黑客方式(对于包含任何字节序列的数组):
array=('' blah $'x\ny\nz' $'x\0y' '1 2 3')
sorted_array=( /(e'{reply=("$array[@]")}'nOe'{REPLY=$#REPLY}') )
zsh
允许通过 glob 限定符定义其 glob 扩展的排序顺序。因此,在这里,我们通过 globbing 来欺骗它对任意数组执行此操作/
,但替换/
为数组的元素 ( e'{reply=("$array[@]")}'
),然后根据元素的长度 ( ) 对元素进行n
数字o
排序(与大写相反) 。O
Oe'{REPLY=$#REPLY}'
请注意,它基于字符数的长度。对于字节数,将区域设置设置为C
( LC_ALL=C
)。
另一种bash
4.4+ 方法(假设数组不是太大):
readarray -td '' sorted_array < <(
perl -l0 -e 'print for sort {length $b <=> length $a} @ARGV
' -- "${array[@]}")
(长度为字节)。
使用旧版本的bash
,您始终可以执行以下操作:
eval "sorted_array=($(
perl -l0 -e 'for (sort {length $b <=> length $a} @ARGV) {
'"s/'/'\\\\''/g"'; printf " '\'%s\''", $_}' -- "${array[@]}"
))"
(也可以与ksh93
, zsh
, yash
,一起使用mksh
)。