文字列の配列がある場合、各要素の長さに従って配列をソートしたいと思います。
例えば...
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
の前に並べ替えられました。しかし、それは「難しい」要件(ソリューションが複雑すぎる場合)。
配列がインプレースで並べ替えられている(つまり、「配列」が変更されている)場合、または新しい並べ替えられた配列が作成される場合は、問題ありません。
文字列に改行が含まれていない場合は、次のように動作します。文字列自体を2次ソート基準として使用して、配列のインデックスを長さでソートします。
#!/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
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
を使用して、アルファベット順を逆にします)およびthatが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
これは、文字列に改行が含まれていないことを前提としています。最近のbash
を使用するGNUシステムでは、改行の代わりにヌル文字をレコード区切りとして使用することにより、データに埋め込まれた改行をサポートできます。
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
およびcut
は-z
GNUオプションとreadarray
は、-d ''
を使用してヌル区切りデータを最後に読み取ります。
私は完全には繰り返さない bashでのソートについてすでに述べた 、あなただけがcanbash内でソートするが、多分あなたはすべきではない。以下は、挿入ソートのbashのみの実装で、O(n2)、したがって、これは小さなアレイでのみ許容されます。配列要素を長さの大きい順に並べ替えます。二次的なアルファベット順の並べ替えは行いません。
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
Choroba と Kusalananda は正しい考えを持っています:長さを一度計算し、ソートとテキスト処理に専用のユーティリティを使用します。
これは、改行を含む配列要素も処理します。各要素の長さとインデックスのみをsort
を通過させることで機能します。 bash
およびksh
で動作するはずです。
_in=(
"tiny string"
"the longest
string also containing
newlines"
"middle string"
"medium string"
"also a medium string"
"short string"
)
out=()
unset IFS
for a in $(for i in ${!in[@]}; do echo ${#in[i]}/$i; done | sort -rn); do
out+=("${in[${a#*/}]}")
done
printf '"%s"\n' "${out[@]}"
_
同じ長さの要素も辞書順にソートする必要がある場合は、ループを次のように変更できます。
_IFS='
'
for a in $(for i in ${!in[@]}; do printf '%s\n' "$i ${#in[i]} ${in[i]//$IFS/ }"; done | sort -k 2,2nr -k 3 | cut -d' ' -f1); do
out+=("${in[$a]}")
done
_
これはsort
にも文字列を渡します(改行はスペースに変更されます)が、それらのインデックスによって、コピー元からコピー先の配列にコピーされます。どちらの例でも、$(...)
は数値(および最初の例の_/
_文字)を含む行のみを表示するため、文字列内の文字またはスペースのグロビングによってトリップされることはありません。
ハッカっぽい? (複雑)配列を長さで並べ替える高速な1行の方法
(改行に対して安全およびスパース配列):
#!/bin/bash
in=(
"tiny string"
"the longest
string also containing
newlines"
"middle string"
"medium string"
"also a medium string"
"short string"
"test * string"
"*"
"?"
"[abc]"
)
readarray -td $'\0' sorted < <(
for i in "${in[@]}"
do printf '%s %s\0' "${#i}" "$i";
done |
sort -bz -k1,1rn -k2 |
cut -zd " " -f2-
)
printf '%s\n' "${sorted[@]}"
1行で:
readarray -td $'\0' sorted < <(for i in "${in[@]}";do printf '%s %s\0' "${#i}" "$i"; done | sort -bz -k1,1rn -k2 | cut -zd " " -f2-)
実行時
$ ./script
the longest
string also containing
newlines
also a medium string
medium string
middle string
test * string
short string
tiny string
[abc]
?
*
zsh
への切り替えがオプションの場合、そこにはハックな方法があります(バイトのシーケンスを含む配列の場合):
_array=('' blah $'x\ny\nz' $'x\0y' '1 2 3')
sorted_array=( /(e'{reply=("$array[@]")}'nOe'{REPLY=$#REPLY}') )
_
zsh
を使用すると、グロブ修飾子を使用して、グロブ拡張のソート順を定義できます。したがって、ここでは、_/
_をグロブすることにより、任意の配列に対してそれを行うようにだましていますが、_/
_を配列の要素(e'{reply=("$array[@]")}'
)に置き換えてから、n
umerically o
rder(大文字の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[@]}")
_
(それはbytesの長さです)。
古いバージョンのbash
では、いつでも次のことができます。
_eval "sorted_array=($(
Perl -l0 -e 'for (sort {length $b <=> length $a} @ARGV) {
'"s/'/'\\\\''/g"'; printf " '\'%s\''", $_}' -- "${array[@]}"
))"
_
(これは_ksh93
_、zsh
、yash
、mksh
でも機能します)。