要素の長さに基づいて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ループ内の改行ではなく末尾に印刷され、NULLで区切られた行はsortGNUオプションを介してcut読み取られ、最後にNULLで区切られたデータはを使用して読み取られます。-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

これが専門的なソリューションであるという証拠として、さまざまなサイズの配列に対する3つの既存の回答のタイミングを検討してください。

# 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}') )

zshglob修飾子を使用すると、glob拡張のソート順序を定義できます。したがって、ここではglobbingを介してランダムな配列でこれを行うようになります。ただし、配列の要素()/を置き換えてから、長さ()に基づいて要素を数字(大文字ではなく)に並べ替えます。/e'{reply=("$array[@]")}'noOOe'{REPLY=$#REPLY}'

文字の長さに基づいていることに注意してください。バイト数の場合は、ロケールをCLC_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(、、、、zshyash組み合わせて使用​​することもできますmksh。)

関連情報