Yanıtlar:
Gerçekten bu kadar koda ihtiyacınız yok:
IFS=$'\n' sorted=($(sort <<<"${array[*]}"))
unset IFS
Elemanlardaki boşlukları destekler (yeni satır olmadığı sürece) ve Bash 3.x'te çalışır.
Örneğin:
$ array=("a c" b f "3 5")
$ IFS=$'\n' sorted=($(sort <<<"${array[*]}")); unset IFS
$ printf "[%s]\n" "${sorted[@]}"
[3 5]
[a c]
[b]
[f]
Not: @sorontar olan işaret elemanları gibi genel arama karakterleri içeriyorsa bakımı gerekli olduğu *
ya da ?
:
Sort = ($ (...)) bölümü "split and glob" operatörünü kullanıyor. Glob'u kapatmalısınız:
set -f
veyaset -o noglob
veyashopt -op noglob
veya dizinin bir öğesi*
bir dosya listesine genişletilecektir.
Sonuç, bu sırada gerçekleşen altı şeyin doruk noktasıdır:
IFS=$'\n'
"${array[*]}"
<<<
sort
sorted=($(...))
unset IFS
IFS=$'\n'
Bu, operasyonumuzun 2 ve 5'in sonuçlarını aşağıdaki şekilde etkileyen önemli bir parçasıdır:
Verilen:
"${array[*]}"
ilk karakteri ile sınırlanan her öğeye genişler IFS
sorted=()
her karakterine bölünerek elemanlar oluşturur IFS
IFS=$'\n'
öğeleri yeni bir satır kullanarak genişletilecek şekilde ayarlar , sınırlayıcı olarak ve daha sonra her satır bir öğe olacak şekilde oluşturulacak şekilde . (örneğin, yeni bir satıra bölme.)
Yeni bir satırla sınırlamak önemlidir, çünkü bu şekilde sort
çalışır (satır başına sıralama). Yalnızca yeni bir satıra bölmek o kadar önemli değildir, ancak boşluk veya sekme içeren öğeleri korumak gerekir.
Varsayılan değeri IFS
olan bir boşluk , bir sekme ardından yeni bir satır ve bizim operasyon için elverişsiz olacaktır.
sort <<<"${array[*]}"
bölüm<<<
, burada dizeler olarak adlandırılır , "${array[*]}"
yukarıda açıklandığı gibi genişlemesini alır ve standart girdiyesort
.
Örneğimizle, sort
şu dizeyle beslenir:
a c
b
f
3 5
sort
Çeşitlerinden beri üretir:
3 5
a c
b
f
sorted=($(...))
bölüm$(...)
Denilen kısmı, komut ikamesi , içeriğini (neden sort <<<"${array[*]}
sonuçlanan çekerken, normal bir komut olarak çalıştırmak için) standart çıktıyı nereye kadar gider literal olarak $(...)
oldu.
Örneğimizde, bu sadece yazmaya benzer bir şey üretir:
sorted=(3 5
a c
b
f
)
sorted
daha sonra bu değişmez değeri her yeni satıra bölerek oluşturulan bir dizi haline gelir.
unset IFS
Bu, değerini IFS
varsayılan değerine sıfırlar ve sadece iyi bir uygulamadır.
Daha IFS
sonra senaryomuzda güvenilen hiçbir şeyle ilgili sorun yaratmamamızı sağlamaktır . (Aksi takdirde, bazı şeyleri değiştirdiğimizi hatırlamamız gerekir - karmaşık komut dosyaları için pratik olmayan bir şey.)
IFS
sadece bir tür boşluk varsa, öğelerinizi küçük parçalara ayırır. İyi; mükemmel değil :-)
unset IFS
gerekli? IFS=
Bir komuta geçmenin , sadece bu komuta geçişi kapsadığını , daha sonra otomatik olarak önceki değerine döndüğünü düşündüm .
sorted=()
bir komut değil, ikinci bir değişken ataması.
Orijinal yanıt:
array=(a c b "f f" 3 5)
readarray -t sorted < <(for a in "${array[@]}"; do echo "$a"; done | sort)
çıktı:
$ for a in "${sorted[@]}"; do echo "$a"; done
3
5
a
b
c
f f
Bu sürümün özel karakterler veya boşluk içeren değerlerle ( yeni satırlar hariç ) başa çıktığını unutmayın
Not readarray, bash 4 ve üstü sürümlerde desteklenir.
Düzenle @Dimitre tarafından yapılan öneriye dayanarak şu şekilde güncellemiştim:
readarray -t sorted < <(printf '%s\0' "${array[@]}" | sort -z | xargs -0n1)
hatta doğru yerleştirilmiş yeni satır karakterleri ile sıralama öğeleri anlamak yararına sahiptir . Ne yazık ki, @ruakh tarafından doğru bir şekilde işaret edildiği gibi, bu, sonucun doğrureadarray
olacağı anlamına gelmedi , çünkü satır ayırıcılar olarak normal satır satırları yerine kullanma seçeneği yoktur .readarray
NUL
readarray -t sorted < <(printf '%s\n' "${array[@]}" | sort)
sort -z
yararlı bir gelişme, -z
seçenek bir GNU sıralama uzantısı olduğunu varsayalım .
sorted=(); while read -d $'\0' elem; do sorted[${#sorted[@]}]=$elem; done < <(printf '%s\0' "${array[@]}" | sort -z)
. Bu, bash v4 yerine bash v3 kullandığınız için de geçerlidir, çünkü bash v3'te readarray kullanılamaz.
<
ile birlikte girdi yeniden yönlendirmesi ( ) . Ya da sezgisel olarak koymak: çünkü bir dosya değil. <(...)
(printf "bla")
İşte saf bir Bash quicksort uygulaması:
#!/bin/bash
# quicksorts positional arguments
# return is in array qsort_ret
qsort() {
local pivot i smaller=() larger=()
qsort_ret=()
(($#==0)) && return 0
pivot=$1
shift
for i; do
if (( i < pivot )); then
smaller+=( "$i" )
else
larger+=( "$i" )
fi
done
qsort "${smaller[@]}"
smaller=( "${qsort_ret[@]}" )
qsort "${larger[@]}"
larger=( "${qsort_ret[@]}" )
qsort_ret=( "${smaller[@]}" "$pivot" "${larger[@]}" )
}
Örneğin,
$ array=(a c b f 3 5)
$ qsort "${array[@]}"
$ declare -p qsort_ret
declare -a qsort_ret='([0]="3" [1]="5" [2]="a" [3]="b" [4]="c" [5]="f")'
Bu uygulama özyinelemeli… yani yinelemeli bir hızlı sıralama:
#!/bin/bash
# quicksorts positional arguments
# return is in array qsort_ret
# Note: iterative, NOT recursive! :)
qsort() {
(($#==0)) && return 0
local stack=( 0 $(($#-1)) ) beg end i pivot smaller larger
qsort_ret=("$@")
while ((${#stack[@]})); do
beg=${stack[0]}
end=${stack[1]}
stack=( "${stack[@]:2}" )
smaller=() larger=()
pivot=${qsort_ret[beg]}
for ((i=beg+1;i<=end;++i)); do
if [[ "${qsort_ret[i]}" < "$pivot" ]]; then
smaller+=( "${qsort_ret[i]}" )
else
larger+=( "${qsort_ret[i]}" )
fi
done
qsort_ret=( "${qsort_ret[@]:0:beg}" "${smaller[@]}" "$pivot" "${larger[@]}" "${qsort_ret[@]:end+1}" )
if ((${#smaller[@]}>=2)); then stack+=( "$beg" "$((beg+${#smaller[@]}-1))" ); fi
if ((${#larger[@]}>=2)); then stack+=( "$((end-${#larger[@]}+1))" "$end" ); fi
done
}
Her iki durumda da, kullandığınız sırayı değiştirebilirsiniz: Dize karşılaştırmaları kullandım, ancak aritmetik karşılaştırmaları kullanabilir, wrt dosyası değiştirme zamanını karşılaştırabilir, vb. daha genel hale getirebilir ve test işlevinin kullanıldığı ilk argümanı kullanmasını sağlayabilirsiniz, ör.
#!/bin/bash
# quicksorts positional arguments
# return is in array qsort_ret
# Note: iterative, NOT recursive! :)
# First argument is a function name that takes two arguments and compares them
qsort() {
(($#<=1)) && return 0
local compare_fun=$1
shift
local stack=( 0 $(($#-1)) ) beg end i pivot smaller larger
qsort_ret=("$@")
while ((${#stack[@]})); do
beg=${stack[0]}
end=${stack[1]}
stack=( "${stack[@]:2}" )
smaller=() larger=()
pivot=${qsort_ret[beg]}
for ((i=beg+1;i<=end;++i)); do
if "$compare_fun" "${qsort_ret[i]}" "$pivot"; then
smaller+=( "${qsort_ret[i]}" )
else
larger+=( "${qsort_ret[i]}" )
fi
done
qsort_ret=( "${qsort_ret[@]:0:beg}" "${smaller[@]}" "$pivot" "${larger[@]}" "${qsort_ret[@]:end+1}" )
if ((${#smaller[@]}>=2)); then stack+=( "$beg" "$((beg+${#smaller[@]}-1))" ); fi
if ((${#larger[@]}>=2)); then stack+=( "$((end-${#larger[@]}+1))" "$end" ); fi
done
}
Sonra bu karşılaştırma fonksiyonuna sahip olabilirsiniz:
compare_mtime() { [[ $1 -nt $2 ]]; }
ve kullan:
$ qsort compare_mtime *
$ declare -p qsort_ret
geçerli klasördeki dosyaların değişiklik zamanına göre sıralanması (önce en yenisi).
NOT. Bu işlevler saf Bash! harici yardımcı programlar ve alt kabuklar yok! komik semboller (boşluklar, yeni satır karakterleri, glob karakterleri vb.)
sort
yeterliyse, bir sort
+ read -a
çözümü, örneğin 20 öğeden başlayarak daha hızlı ve daha fazla uğraştığınız unsurları giderek ve önemli ölçüde daha hızlı olacaktır. Örneğin, 2012'nin sonlarında bir Fusion Drive ile OSX 10.11.1 çalıştıran iMac'imde: 100 elemanlı dizi: ca. 0.03s saniye. ( qsort()
) ve ca. 0.005 saniye. ( sort
+ read -a
); 1000 elemanlık dizi: ca. 0.375 saniye. ( qsort()
) ve ca. 0.014 saniye ( sort
+ read -a
).
if [ "$i" -lt "$pivot" ]; then
gerekliydi, aksi takdirde çözülen "2" <"10" true değerini döndürdü. Bunun POSIX ve Sözlükbilimsel olduğuna inanıyorum; veya belki Inline Link .
Dizi öğelerinde özel kabuk karakterlerini işlemeniz gerekmiyorsa:
array=(a c b f 3 5)
sorted=($(printf '%s\n' "${array[@]}"|sort))
Bash ile zaten harici bir sıralama programına ihtiyacınız olacak.
Zsh ile harici programlara gerek yoktur ve özel kabuk karakterleri kolayca ele alınır:
% array=('a a' c b f 3 5); printf '%s\n' "${(o)array[@]}"
3
5
a a
b
c
f
Ksh vardır set -s
sıralamak için ASCIIbetically .
set -A array x 'a a' d; set -s -- "${array[@]}"; set -A sorted "$@"
Ve elbette, set komutu, varsa, geçerli konum parametrelerini sıfırlar.
tl; dr :
Diziyi sıralayın a_in
ve sonucu saklayın a_out
(öğelerin gömülü yeni satırları olmamalıdır [1]
):
Bash v4 +:
readarray -t a_out < <(printf '%s\n' "${a_in[@]}" | sort)
Bash v3:
IFS=$'\n' read -d '' -r -a a_out < <(printf '%s\n' "${a_in[@]}" | sort)
Antak'ın çözümüne göre avantajları :
Yanlışlıkla globbing (dizi öğelerinin dosya adı kalıpları olarak yanlışlıkla yorumlanması) konusunda endişelenmenize gerek yoktur, bu nedenle globbing'i devre dışı bırakmak ( set -f
ve set +f
daha sonra geri yüklemek için) için fazladan komut gerekmez .
Sen sıfırlama hakkında endişe gerekmez IFS
ile unset IFS
. [2]
Yukarıdaki, rastgele tek satırlı öğelerle ve sözcüksel veya sayısal sıralama (isteğe bağlı olarak alana göre) ile çalışansort
bir çözüm için Bash kodunu harici yardımcı programla birleştirir :
Performans : Yaklaşık 20 veya daha fazla element için , bu saf Bash çözümünden daha hızlı olacaktır - yaklaşık 100 elementin ötesine geçtiğinizde önemli ölçüde ve giderek daha fazla.
(Tam eşikler, giriş, makine ve platformunuza bağlı olacaktır.)
printf '%s\n' "${a_in[@]}" | sort
sıralama yapar (sözlük olarak varsayılan olarak - sort
POSIX spesifikasyonuna bakın ):
"${a_in[@]}"
dizinin öğelerine, içerdikleri her ne olursa olsun (boşluk dahil) bağımsız bir şekilde bağımsıza_in
olarak genişler .
printf '%s\n'
sonra her bir argümanı - yani her dizi elemanını - kendi satırına olduğu gibi yazdırır.
Not bir kullanımını işlemi ikame ( <(...)
) bir giriş olarak sıralanmış çıkışı sağlamak için read
/ ' readarray
(stdin, yeniden yönlendirme ile <
, çünkü) read
/ readarray
çalışmalıdır akım kabuk (a çalıştırmak gerekir alt kabuğa çıkış değişkeni için sırayla) a_out
görünür olmasını geçerli kabuğa (değişkenin kodun geri kalanında tanımlanmış olarak kalması için).
Okuma sort
çıktısını dizi değişkenine çevirir :
Bash v4 +: readarray -t a_out
ayrı ayrı satırları okursort
her bir öğenin ( ) a_out
sonuna iz bırakmadan , dizi değişkeninin öğelerine .\n
-t
Bash v3: readarray
mevcut değil, bu yüzden read
kullanılmalıdır: dizi ( ) değişkenine okumayı, tüm girdiyi satırlar ( ) arasında okumayı , ancak yeni satır ((LF) ile satır öğelerine ayırmayı
IFS=$'\n' read -d '' -r -a a_out
söyler . ), ANSI C alıntılı bir dizedir ).
( , neredeyse her zaman birlikte kullanılması gereken bir seçenekread
-a
a_out
-d ''
IFS=$'\n'
$'\n'
-r
read
, \
karakterlerin beklenmedik şekilde işlenmesini devre dışı bırakır .)
Açıklamalı örnek kod:
#!/usr/bin/env bash
# Define input array `a_in`:
# Note the element with embedded whitespace ('a c')and the element that looks like
# a glob ('*'), chosen to demonstrate that elements with line-internal whitespace
# and glob-like contents are correctly preserved.
a_in=( 'a c' b f 5 '*' 10 )
# Sort and store output in array `a_out`
# Saving back into `a_in` is also an option.
IFS=$'\n' read -d '' -r -a a_out < <(printf '%s\n' "${a_in[@]}" | sort)
# Bash 4.x: use the simpler `readarray -t`:
# readarray -t a_out < <(printf '%s\n' "${a_in[@]}" | sort)
# Print sorted output array, line by line:
printf '%s\n' "${a_out[@]}"
sort
Seçeneklerin kullanılmamasından dolayı , bu, sözcüksel sıralama sağlar (rakamlar harflerden önce sıralanır ve basamak dizileri sayı olarak değil, sözcük olarak ele alınır):
*
10
5
a c
b
f
Eğer isteseydik sayısal 1 alana göre sıralama, kullanmak istiyorum sort -k1,1n
yerine sadece sort
hangi verimleri (bir çeşit sayılar önce olmayan numaralar ve sayılar doğru sıralama),:
*
a c
b
f
5
10
[1] gömülü yeni satır ile etmek için sap elemanları, aşağıdaki varyantı ile (Bash V4 +, kullanımı GNU'da sort
)
readarray -d '' -t a_out < <(printf '%s\0' "${a_in[@]}" | sort -z)
.
Michał Górny'nin yardımcı cevabı Bash v3 çözümüne sahip.
, [2] IFS
bir Bash v3 varyantı yer değiştirmek olan komuta kapsama .
Buna karşılık, ne şu IFS=$'\n'
antak yanıtında bir olan atama bu durumda bir komutla, yerine IFS
değişimdir küresel .
Münih'ten Frankfurt'a giden 3 saatlik tren gezisinde (Oktoberfest yarın başlayacağı için ulaşmakta sorun yaşadım) ilk görevimi düşünüyordum. Genel bir dizi kullanmak, genel bir sıralama işlevi için çok daha iyi bir fikirdir. Aşağıdaki işlev, arbiter dizeleri (satırsonları, boşluklar vb.) İşler:
declare BSORT=()
function bubble_sort()
{ #
# @param [ARGUMENTS]...
#
# Sort all positional arguments and store them in global array BSORT.
# Without arguments sort this array. Return the number of iterations made.
#
# Bubble sorting lets the heaviest element sink to the bottom.
#
(($# > 0)) && BSORT=("$@")
local j=0 ubound=$((${#BSORT[*]} - 1))
while ((ubound > 0))
do
local i=0
while ((i < ubound))
do
if [ "${BSORT[$i]}" \> "${BSORT[$((i + 1))]}" ]
then
local t="${BSORT[$i]}"
BSORT[$i]="${BSORT[$((i + 1))]}"
BSORT[$((i + 1))]="$t"
fi
((++i))
done
((++j))
((--ubound))
done
echo $j
}
bubble_sort a c b 'z y' 3 5
echo ${BSORT[@]}
Bu yazdırır:
3 5 a b c z y
Aynı çıktı,
BSORT=(a c b 'z y' 3 5)
bubble_sort
echo ${BSORT[@]}
Muhtemelen Bash'in dahili olarak akıllı işaretçiler kullandığını unutmayın, bu yüzden takas işlemi ucuz olabilir (buna rağmen şüpheliyim). Bununla birlikte, bubble_sort
daha gelişmiş fonksiyonların merge_sort
da kabuk dilinin erişiminde olduğunu gösterir.
local -n BSORT="$1"
işlevin başlangıcında. Sonra myarraybubble_sort myarray
sıralamak için koşabilirsiniz .
Harici kullanan sort
ve herhangi bir özel karakterle başa çıkan başka bir çözüm (NUL'lar hariç :)). Bash-3.2 ve GNU veya BSD ile çalışmalıdır sort
(ne yazık ki POSIX içermez -z
).
local e new_array=()
while IFS= read -r -d '' e; do
new_array+=( "${e}" )
done < <(printf "%s\0" "${array[@]}" | LC_ALL=C sort -z)
Önce sondaki giriş yönlendirmesine bakın. printf
Sıfır sonlandırılmış dizi öğelerini yazmak için yerleşik kullanıyoruz . Alıntı, dizi öğelerinin olduğu gibi iletildiğinden emin printf
olur ve kabuk özellikleri, kalan her parametre için biçim dizesinin son bölümünü yeniden kullanmasına neden olur. Yani, şuna benzer:
for e in "${array[@]}"; do
printf "%s\0" "${e}"
done
Sonra null sonlandırılmış öğe listesi iletilir sort
. Bu -z
seçenek, boş sonlandırılmış öğeleri okumasına, sıralamasına ve boş sonlandırılmış çıkış vermesine neden olur. Sadece benzersiz öğeleri elde etmeniz gerekiyorsa -u
, daha taşınabilir olduğu için geçebilirsiniz uniq -z
. LC_ALL=C
Yereli bağımsız kararlı sıralama düzeni sağlar - komut dosyaları için bazen yararlı. sort
Yerel ayara saygı duymak istiyorsanız, bunu kaldırın.
<()
Yapı olurken boru okuma tanımlayıcıyı alır ve <
standart giriş yönlendirmeleriwhile
buna döngü. Borunun içindeki standart girişe erişmeniz gerekiyorsa, başka bir tanımlayıcı kullanabilirsiniz - okuyucu için egzersiz :).
Şimdi, başlangıca geri dönelim. read
Yerleşik Yönlendirilen stdin'den çıkışını okur. Boş ayar yapmak IFS
, burada gereksiz olan kelime bölmeyi devre dışı bırakır - sonuç olarak, read
sağlanan tek 'değişken' satırını sağlanan tek değişkene okur. -r
seçeneği, burada istenmeyen kaçış işlemini de devre dışı bırakır. Son olarak, -d ''
çizgi sınırlayıcıyı NUL olarak ayarlar - yani,read
sıfır sonlu dizeleri okumayı .
Sonuç olarak, döngü her ardışık sıfır sonlandırılmış dizi öğesi için bir kez yürütülür ve değer depolanır e
. Örnek sadece öğeleri başka bir diziye koyar, ancak doğrudan işlemeyi tercih edebilirsiniz :).
Tabii ki, bu aynı hedefe ulaşmanın birçok yolundan sadece biri. Gördüğüm gibi, bash'da tam sıralama algoritması uygulamaktan daha basit ve bazı durumlarda daha hızlı olacak. Yeni satırlar da dahil olmak üzere tüm özel karakterleri işler ve ortak sistemlerin çoğunda çalışmalıdır. En önemlisi, size bash hakkında yeni ve harika bir şey öğretebilir :).
e
ve boş IFS ayarlamak yerine REPLY değişkenini kullanın.
bunu dene:
echo ${array[@]} | awk 'BEGIN{RS=" ";} {print $1}' | sort
Çıktı:
3 5 bir b c f
Sorun çözüldü.
Dizideki her öğe için benzersiz bir tamsayı hesaplayabiliyorsanız, şöyle:
tab='0123456789abcdefghijklmnopqrstuvwxyz'
# build the reversed ordinal map
for ((i = 0; i < ${#tab}; i++)); do
declare -g ord_${tab:i:1}=$i
done
function sexy_int() {
local sum=0
local i ch ref
for ((i = 0; i < ${#1}; i++)); do
ch="${1:i:1}"
ref="ord_$ch"
(( sum += ${!ref} ))
done
return $sum
}
sexy_int hello
echo "hello -> $?"
sexy_int world
echo "world -> $?"
Bash her zaman seyrek dizi kullandığından, bu tam sayıları dizi dizinleri olarak kullanabilirsiniz, bu nedenle kullanılmayan dizinler hakkında endişelenmenize gerek yoktur:
array=(a c b f 3 5)
for el in "${array[@]}"; do
sexy_int "$el"
sorted[$?]="$el"
done
echo "${sorted[@]}"
dk. sıralama:
#!/bin/bash
array=(.....)
index_of_element1=0
while (( ${index_of_element1} < ${#array[@]} )); do
element_1="${array[${index_of_element1}]}"
index_of_element2=$((index_of_element1 + 1))
index_of_min=${index_of_element1}
min_element="${element_1}"
for element_2 in "${array[@]:$((index_of_element1 + 1))}"; do
min_element="`printf "%s\n%s" "${min_element}" "${element_2}" | sort | head -n+1`"
if [[ "${min_element}" == "${element_2}" ]]; then
index_of_min=${index_of_element2}
fi
let index_of_element2++
done
array[${index_of_element1}]="${min_element}"
array[${index_of_min}]="${element_1}"
let index_of_element1++
done
Alanların ve yeni satırların olağan sorunu için bir çözüm var:
Orijinal dizideki (gibi olmayan bir karakter kullanarak $'\1'
veya $'\4'
veya benzeri).
Bu işlev işi yapar:
# Sort an Array may have spaces or newlines with a workaround (wa=$'\4')
sortarray(){ local wa=$'\4' IFS=''
if [[ $* =~ [$wa] ]]; then
echo "$0: error: array contains the workaround char" >&2
exit 1
fi
set -f; local IFS=$'\n' x nl=$'\n'
set -- $(printf '%s\n' "${@//$nl/$wa}" | sort -n)
for x
do sorted+=("${x//$wa/$nl}")
done
}
Bu diziyi sıralar:
$ array=( a b 'c d' $'e\nf' $'g\1h')
$ sortarray "${array[@]}"
$ printf '<%s>\n' "${sorted[@]}"
<a>
<b>
<c d>
<e
f>
<gh>
Bu, kaynak dizinin geçici karakter içerdiğinden şikayet edecektir:
$ array=( a b 'c d' $'e\nf' $'g\4h')
$ sortarray "${array[@]}"
./script: error: array contains the workaround char
wa
(geçici çözüm karakter) ve boş bir IFS ayarladık$*
.[[ $* =~ [$wa] ]]
.exit 1
set -f
IFS=$'\n'
Bir döngü değişkeni x
ve bir yeni satır var ( nl=$'\n'
) yeni bir IFS ( ) değeri ayarlayın .$@
) tüm değerlerini yazdırıyoruz ."${@//$nl/$wa}"
.sort -n
.set --
.for x
sorted+=(…)
"${x//$wa/$nl}"
.Bu soru birbiriyle yakından ilgili. Ve BTW, işte Bash'te bir birleşim (harici işlemler olmadan):
mergesort() {
local -n -r input_reference="$1"
local -n output_reference="$2"
local -r -i size="${#input_reference[@]}"
local merge previous
local -a -i runs indices
local -i index previous_idx merged_idx \
run_a_idx run_a_stop \
run_b_idx run_b_stop
output_reference=("${input_reference[@]}")
if ((size == 0)); then return; fi
previous="${output_reference[0]}"
runs=(0)
for ((index = 0;;)) do
for ((++index;; ++index)); do
if ((index >= size)); then break 2; fi
if [[ "${output_reference[index]}" < "$previous" ]]; then break; fi
previous="${output_reference[index]}"
done
previous="${output_reference[index]}"
runs+=(index)
done
runs+=(size)
while (("${#runs[@]}" > 2)); do
indices=("${!runs[@]}")
merge=("${output_reference[@]}")
for ((index = 0; index < "${#indices[@]}" - 2; index += 2)); do
merged_idx=runs[indices[index]]
run_a_idx=merged_idx
previous_idx=indices[$((index + 1))]
run_a_stop=runs[previous_idx]
run_b_idx=runs[previous_idx]
run_b_stop=runs[indices[$((index + 2))]]
unset runs[previous_idx]
while ((run_a_idx < run_a_stop && run_b_idx < run_b_stop)); do
if [[ "${merge[run_a_idx]}" < "${merge[run_b_idx]}" ]]; then
output_reference[merged_idx++]="${merge[run_a_idx++]}"
else
output_reference[merged_idx++]="${merge[run_b_idx++]}"
fi
done
while ((run_a_idx < run_a_stop)); do
output_reference[merged_idx++]="${merge[run_a_idx++]}"
done
while ((run_b_idx < run_b_stop)); do
output_reference[merged_idx++]="${merge[run_b_idx++]}"
done
done
done
}
declare -ar input=({z..a}{z..a})
declare -a output
mergesort input output
echo "${input[@]}"
echo "${output[@]}"
Bash'te harici bir sıralama programına ihtiyacınız olacağından emin değilim.
İşte basit kabarcık sıralama algoritması için benim uygulama.
function bubble_sort()
{ #
# Sorts all positional arguments and echoes them back.
#
# Bubble sorting lets the heaviest (longest) element sink to the bottom.
#
local array=($@) max=$(($# - 1))
while ((max > 0))
do
local i=0
while ((i < max))
do
if [ ${array[$i]} \> ${array[$((i + 1))]} ]
then
local t=${array[$i]}
array[$i]=${array[$((i + 1))]}
array[$((i + 1))]=$t
fi
((i += 1))
done
((max -= 1))
done
echo ${array[@]}
}
array=(a c b f 3 5)
echo " input: ${array[@]}"
echo "output: $(bubble_sort ${array[@]})"
Bu aşağıdakileri basacaktır:
input: a c b f 3 5
output: 3 5 a b c f
O(n^2)
. Çoğu sıralama algoritmalarının O(n lg(n))
son düzine öğeye kadar kullandığını hatırlıyorum . Son elemanlar için seçim sıralaması kullanılır.
a=(e b 'c d')
shuf -e "${a[@]}" | sort >/tmp/f
mapfile -t g </tmp/f
sorted=($(echo ${array[@]} | tr " " "\n" | sort))
Bash / linux ruhu içinde, her adım için en iyi komut satırı aracını kullanırdım. sort
ana işi yapar ancak boşluk yerine satırsonu ile ayrılması gerekir, bu nedenle yukarıdaki basit boru hattı basitçe şunları yapar:
Yankı dizisi içeriği -> alanı satırsonu yerine değiştir -> sırala
$()
sonucu yankılamak
($())
"echoed sonucunu" bir diziye koymak
Not : @sorontar bir belirtildiği gibi comment farklı bir soruya:
Sort = ($ (...)) bölümü "split and glob" operatörünü kullanıyor. Glob'u kapatmalısınız: set -f veya set -o noglob veya shopt -op noglob veya * gibi dizinin bir öğesi bir dosya listesine genişletilir.
mapfile -t sorted < <(printf '%s\n' "${array[@]}" | sort)
aksi takdirde sorted=(); while IFS= read -r line; do sorted+=( "$line" ); done < <(printf '%s\n' | sort)
.
echo ${array[@]} | tr " " "\n"
dizi alanları beyaz boşluklar ve glob karakterleri içeriyorsa kırılır. Ayrıca, bir alt kabuk ortaya çıkarır ve işe yaramaz bir dış komut kullanır. Ve nedeniyle echo
dilsiz olmak, bu dizi ile başlıyorsa kıracak -e
, -E
ya da -n
. Bunun yerine kullanın: printf '%s\n' "${array[@]}"
. Diğer antipattern: ($())
"yankılanan sonucu" bir diziye koymaktır . Kesinlikle değil! Bu, yol adı genişlemesi (globbing) ve kelime bölünmesi nedeniyle kırılan korkunç bir antipattern. Asla bu dehşeti kullanmayın.
IFS
öğelerinizde boşluklar varsa öğelerinizi küçük parçalara böler. ÖrneğinIFS=$'\n'
atlanmış ile deneyin ve görün!