तत्वों की लंबाई के अनुसार बैश सॉर्ट सरणी?


9

तार की एक सरणी को देखते हुए, मैं प्रत्येक तत्व की लंबाई के अनुसार सरणी को सॉर्ट करना चाहूंगा।

उदाहरण के लिए...

    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
यहाँ पर कुछ दिलचस्प जवाब है, तो आप अच्छी तरह से स्ट्रिंग की लंबाई के लिए परीक्षण करने के लिए एक अनुकूल करने के लिए सक्षम होना चाहिए stackoverflow.com/a/30576368/2876682
frostschutz

जवाबों:


12

यदि स्ट्रिंग्स में नई लिंक नहीं हैं, तो निम्नलिखित को काम करना चाहिए। यह लंबाई के अनुसार सरणी के सूचकांकों को छाँटता है, स्ट्रिंग को स्वयं को द्वितीयक प्रकार की कसौटी के रूप में उपयोग करता है।

#!/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[@]}")

ध्यान दें कि वास्तविक प्रोग्रामिंग भाषा में जाने से समाधान को सरल बनाया जा सकता है, उदाहरण के लिए पर्ल में, आप बस कर सकते हैं

sort { length $b <=> length $a or $a cmp $b } @array

1
पायथन में:sorted(array, key=lambda s: (len(s), s))
wjandrea

1
रूबी में:array.sort { |a| a.size }
दिमित्री कुद्रियात्सेव

9
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-character को न्यूलाइन के बजाय रिकॉर्ड सेपरेटर के रूप में उपयोग करके डेटा में एम्बेडेड न्यूलाइन्स का समर्थन कर सकते हैं:

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अपने -zGNU विकल्पों के माध्यम से nul- सीमांकित रेखाओं को readarrayपढ़ता है और अंत में nul- सीमांकित डेटा को पढ़ता है -d ''


3
ध्यान दें कि -d '\0'वास्तव -d ''में bashNUL वर्णों को कमांडों, यहाँ तक कि इसके बिलिनों के लिए भी पास नहीं किया जा सकता है। लेकिन यह एनयूएल पर-d '' अर्थ परिसीमन के रूप में समझता है । ध्यान दें कि आपको इसके लिए bash 4.4+ की आवश्यकता है।
स्टीफन चेजेलस

@ स्टीफनचेज़ेलस नहीं, यह नहीं है '\0', यह है $'\0'। और हाँ, यह (लगभग बिल्कुल) को धर्मान्तरित करता है ''। लेकिन यह अन्य पाठकों के लिए एनयूएल सीमांकक का उपयोग करने के वास्तविक इरादे के अनुकूल करने का एक तरीका है ।
आइजैक

4

मैं पूरी तरह से दोहरा नहीं सकता कि मैंने पहले से ही बैश में सॉर्ट करने के बारे में क्या कहा है , बस आप बैश के भीतर सॉर्ट कर सकते हैं , लेकिन शायद आपको नहीं करना चाहिए। नीचे एक सम्मिलन प्रकार का केवल बैश कार्यान्वयन है, जो 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

एक हैकिश? (जटिल) और तेजी से लंबाई के हिसाब से सरणी को सॉर्ट करने के लिए एक लाइन रास्ता
( newlines और विरल सरणियों के लिए सुरक्षित ):

#!/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[@]}"

एक लाइन पर:

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]
?
*

4

यह उन में नए तत्वों के साथ सरणी तत्वों को भी संभालता है; यह 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स्ट्रिंग्स को भी पास करेगा (नए स्थानों के साथ रिक्त स्थान में परिवर्तित), लेकिन वे अभी भी स्रोत से गंतव्य सरणी में उनकी अनुक्रमणिका द्वारा कॉपी किए जाएंगे। दोनों उदाहरणों में, $(...)वसीयत में केवल संख्याएँ (और /पहले उदाहरण में वर्ण) वाली रेखाएँ दिखाई देंगी , इसलिए यह स्ट्रिंग में वर्णों या रिक्त स्थान को घेरे में नहीं रखा जाएगा।


पुनः उत्पन्न नहीं कर सकते। दूसरे उदाहरण में, $(...)कमांड प्रतिस्थापन केवल अनुक्रमित (न्यूलाइन्स द्वारा अलग किए गए संख्याओं की एक सूची) को देखता है, क्योंकि cut -d' ' -f1सॉर्ट के बाद। tee /dev/ttyके अंत में यह आसानी से प्रदर्शित किया जा सकता है $(...)
मॉसवी

क्षमा करें, मेरा बुरा, मैं चूक गया cut
स्टीफन चेज़लस

@Isaac को ${!in[@]}या ${#in[i]}/$iचर विस्तार को उद्धृत करने की कोई आवश्यकता नहीं है क्योंकि उनके पास केवल अंक होते हैं जो ग्लोब विस्तार के अधीन नहीं हैं और अंतरिक्ष, टैब, न्यूलाइन पर unset IFSरीसेट हो जाएगा IFS। वास्तव में, उन्हें उद्धृत करना हानिकारक होगा , क्योंकि यह गलत धारणा देगा कि इस तरह के उद्धरण उपयोगी और प्रभावी हैं, और दूसरे उदाहरण में IFSआउटपुट की / और फ़िल्टरिंग को sortसुरक्षित रूप से दूर किया जा सकता है।
मॉसवी

@Isaac यह करता नहीं तोड़ करता है, तो inशामिल "testing * here"और shopt -s nullglobपाश से पहले स्थापित किया जाएगा।
मॉसवी

3

मामले में स्विच करने के zshलिए एक विकल्प है, वहाँ एक हैक तरीका (बाइट्स के किसी भी अनुक्रम वाले सरणियों के लिए)।

array=('' blah $'x\ny\nz' $'x\0y' '1 2 3')
sorted_array=( /(e'{reply=("$array[@]")}'nOe'{REPLY=$#REPLY}') )

zshग्लोब क्वालिफायर के माध्यम से अपने ग्लोब विस्तार के लिए क्रमबद्ध क्रम को परिभाषित करने की अनुमति देता है। तो यहाँ, हम इसे ग्लोबिंग द्वारा मनमाने ढंग से सरणियों के लिए करने के लिए इसे ट्रिक कर रहे हैं /, लेकिन /एरे ( e'{reply=("$array[@]")}') के तत्वों के साथ प्रतिस्थापित करते हैं और फिर numerically order (अपरकेस के साथ रिवर्स O) तत्वों को उनकी लंबाई ( Oe'{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)।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.