Bash根据元素长度对数组进行排序?

Bash根据元素长度对数组进行排序?

给定一个字符串数组,我想根据每个元素的长度对数组进行排序。

例如...

    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 分隔数据。-zreadarray-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排序(与大写相反) 。OOe'{REPLY=$#REPLY}'

请注意,它基于字符数的长度。对于字节数,将区域设置设置为C( LC_ALL=C)。

另一种bash4.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)。

相关内容