요소의 길이에 따라 배쉬 정렬 배열?


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
파이썬에서 :sorted(array, key=lambda s: (len(s), s))
wjandrea

1
루비에서 :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에서는 개행 문자 대신 레코드 구분 기호로 널 문자를 사용하여 데이터에 포함 된 개행 문자를 지원할 수 있습니다.

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제 1, 대신 개행의 루프 sortcut그들의 NUL 통해 분리 된 라인을 판독 -zGNU 옵션 및 readarray최종적으로 NUL 구분 된 데이터를 판독한다 -d ''.


3
참고 -d '\0'사실상 -d ''같은 bash명령에 NUL 문자를 전달할 수 없습니다, 심지어는 내장 명령. 그러나 그것은 NUL의 경계-d '' 를 의미 하는 것으로 이해 합니다. 이를 위해서는 bash 4.4 이상이 필요합니다.
Stéphane Chazelas

StéphaneChazelas 아니 @, 그렇지 '\0', 그것은이다 $'\0'. 그리고 그렇습니다 (거의 정확히)를로 변환합니다 ''. 그러나 그것은하는 방법입니다 comunicate 수 널로 구분 기호를 사용하여 실제 의도 다른 독자는.
Isaac

4

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

이것이 특수한 솔루션이라는 증거로, 다양한 크기 배열에 대한 기존 세 가지 답변의 타이밍을 고려하십시오.

# 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

ChorobaKusalananda 는 올바른 아이디어를 가지고 있습니다. 길이를 한 번 계산하고 정렬 및 텍스트 처리를 위해 전용 유틸리티를 사용합니다.


4

해 키시? (복잡한) 배열을 길이별로 정렬하는 빠른 한 줄 방법
( 개행 및 희소 배열에 안전 ) :

#!/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의 끝에 쉽게 표시 될 수 있습니다 $(...).
mosvy

미안, 내 나쁜, 나는보고 싶었다 cut.
Stéphane Chazelas

@Isaac ${!in[@]}또는 ${#in[i]}/$i확장 확장은 glob 확장이 적용되지 않는 숫자 만 포함 하고 공백, 탭, 개행으로 unset IFS재설정 하기 때문에 따옴표 또는 변수 확장 을 인용 할 필요 가 없습니다 IFS. 실제로 이러한 인용은 유용하고 효과적이며 두 번째 예의 출력 설정 및 / 또는 필터링을 안전하게 제거 할 수 있다는 잘못된 인상을주기 때문에 인용하는 것은 해로울 것입니다. IFSsort
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}') )

zshglob 한정자를 통해 glob 확장을위한 정렬 순서를 정의 할 수 있습니다. 그래서 여기서 우리는 on 을 붙임 으로써 임의의 배열을 위해 그것을 속이고 /있지만 /배열의 요소 ( e'{reply=("$array[@]")}')로 대체 한 다음 길이 ( )를 기준으로 요소 를 n대체적 o으로 대문자로 뒤집습니다 .OOe'{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.