เรียงลำดับทุบตีตามความยาวขององค์ประกอบ?


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

โปรดทราบว่าการย้ายไปใช้ภาษาการเขียนโปรแกรมจริงสามารถลดความซับซ้อนของโซลูชันได้อย่างมากเช่นใน Perl คุณสามารถทำได้

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

1
ใน Python:sorted(array, key=lambda s: (len(s), s))
wjandrea

1
ใน Ruby:array.sort { |a| a.size }
Dmitry Kudriavtsev

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 เป็นตัวคั่นเรคคอร์ดแทนการขึ้นบรรทัดใหม่:

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อ่านบรรทัด NUL คั่นของพวกเขาผ่าน-zตัวเลือก GNU และreadarrayในที่สุดก็อ่านข้อมูล NUL -d ''คั่นด้วย


3
โปรดทราบว่า-d '\0'ในความ-d ''เป็นจริงแล้วbashไม่สามารถส่งผ่านอักขระ NUL ไปยังคำสั่งแม้ในตัว แต่ก็ไม่เข้าใจ-d ''ความหมายคั่นใน NUL โปรดทราบว่าคุณต้องการ bash 4.4+ สำหรับสิ่งนั้น
Stéphane Chazelas

@ StéphaneChazelasไม่มีก็ไม่ได้ก็คือ'\0' $'\0'และใช่มันแปลง (เกือบ) ''ไป แต่นั่นเป็นวิธีที่จะสื่อสารกับผู้อ่านคนอื่น ๆ ถึงเจตนาที่แท้จริงของการใช้ตัวคั่น NUL
Isaac

4

ฉันจะไม่ทำซ้ำอย่างสมบูรณ์ในสิ่งที่ฉันได้พูดไปแล้วเกี่ยวกับการเรียงลำดับใน bashเพียงคุณสามารถจัดเรียงใน bash แต่บางทีคุณไม่ควร ด้านล่างเป็นการนำ bash-only มาใช้ในการเรียงลำดับการแทรกซึ่งก็คือ 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

ChorobaและKusalanandaมีความคิดที่ถูกต้อง: คำนวณความยาวหนึ่งครั้งและใช้โปรแกรมอรรถประโยชน์เฉพาะสำหรับการเรียงลำดับและการประมวลผลข้อความ


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เพียงความยาวและดัชนีของแต่ละองค์ประกอบ มันควรจะทำงานกับและbashksh

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ยังสตริง (ด้วยการขึ้นบรรทัดใหม่เปลี่ยนเป็นช่องว่าง) แต่จะยังคงถูกคัดลอกจากแหล่งข้อมูลไปยังอาร์เรย์ปลายทางด้วยดัชนี ในตัวอย่างทั้งสอง$(...)จะเห็นเฉพาะบรรทัดที่มีตัวเลข (และ/ตัวอักษรในตัวอย่างแรก) ดังนั้นมันจะไม่ถูก tripping ด้วยตัวอักษรกลมหรือช่องว่างในสตริง


ไม่สามารถทำซ้ำได้ ในตัวอย่างที่สองการ$(...)ทดแทนคำสั่งจะเห็นเฉพาะดัชนี (รายการของตัวเลขที่คั่นด้วยบรรทัดใหม่) เนื่องจากcut -d' ' -f1ลำดับหลัง นี้สามารถแสดงให้เห็นได้อย่างง่ายดายโดยในตอนท้ายของtee /dev/tty $(...)
mosvy

cutขออภัยที่ไม่ดีของฉันฉันพลาด
Stéphane Chazelas

@Isaac ไม่จำเป็นต้องอ้าง${!in[@]}หรือ${#in[i]}/$iการขยายตัวแปรเพราะมันมีเพียงตัวเลขที่ไม่อยู่ภายใต้การขยายตัวของ glob และunset IFSจะรีเซ็ตเป็นIFSช่องว่างแท็บขึ้นบรรทัดใหม่ ในความเป็นจริงการอ้างอิงพวกมันจะเป็นอันตรายเพราะมันจะให้ความรู้สึกที่ผิดว่าการอ้างนั้นมีประโยชน์และมีประสิทธิภาพและการตั้งค่าIFSและ / หรือการกรองเอาท์พุทของsortตัวอย่างที่สองนั้นสามารถทำได้อย่างปลอดภัย
mosvy

@Isaac มันจะไม่แตกหากinมี"testing * here"และshopt -s nullglobตั้งไว้ก่อนหน้าลูป
mosvy

3

ในกรณีที่เปลี่ยนzshเป็นตัวเลือกวิธีแฮ็ก (สำหรับอาร์เรย์ที่มีลำดับไบต์ใด ๆ ):

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

zshอนุญาตให้กำหนดคำสั่งการเรียงลำดับสำหรับการขยายตัวของ glob ผ่านตัวระบุแบบกลม ดังนั้นที่นี่เรากำลังหลอกให้ทำเพื่ออาร์เรย์โดยพลการ/แต่แทนที่/ด้วยองค์ประกอบของอาร์เรย์ ( e'{reply=("$array[@]")}') และจากนั้นnแรนด์ umerically o(ตรงกันข้ามกับตัวพิมพ์ใหญ่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)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.