쉼표로 구분된 숫자 목록을 하이픈으로 연결된 목록 또는 범위로 변환

쉼표로 구분된 숫자 목록을 하이픈으로 연결된 목록 또는 범위로 변환

다음과 같이 쉼표로 구분된 숫자 문자열이 있습니다.

1,2,3,5,6,7,8,9,12,14

bash다음과 같이 인접한 숫자를 범위/하이픈 항목으로 결합하는 스크립트에서 사용할 명령을 찾고 있습니다 .

1-3,5-9,12,14

초기 문자열은 오름차순으로 정렬됩니다.

답변1

펄 사용:

perl -pe 's/\b(\d+)(?{$q=$1+1})(?:,(??{$q})\b(?{$p=$q++})){2,}/$1-$p/g'

(?{...})이는 and 표현식을 통해 임베디드 Perl 코드와 함께 정규식을 사용하는 것입니다 (??{...}). 첫 번째는 임베디드 코드만 평가하고 두 번째는 패턴으로 반환하는 값을 사용합니다. perlre(1)전체 설명은 리소스를 참조하세요 .

두 개의 숫자만 포함하는 범위(예: -> )를 원하는 경우 수량자를 {2,}로 바꾸세요.+1,2,71-2,7

답변2

awk다음은 쉼표로 구분된 정렬된 정수 목록을 반복하고 두 개의 배열을 채우는 짧은 스크립트입니다 a.b

배열 에는 해당 끝 정수 a와 함께 단조롭게 증가하는 각 정수 범위에 대한 시작 정수가 포함됩니다 . 코드의 변수는 발견된 범위의 수를 보유합니다.bn

BEGIN {
    OFS = FS = ","
}

{
    n = 0

    a[++n] = $1
    for (i = 1; i < NF; ++i)
        if ($i != $(i+1) - 1) {
            b[n] = $i
            a[++n] = $(i+1)
        }
    b[n] = $NF

    $0 = ""

    for (i = 1; i <= n; ++i)
        if (a[i] == b[i])
            $i = a[i]
        else
            $i = sprintf("%d-%d", a[i], b[i])

    print
}

출력은 발견된 다양한 범위를 반복하고 n각 필드가 단일 정수(길이 1 범위의 경우)이거나 범위의 시작과 끝을 나타내는 문자열인 레코드를 구성하여 생성됩니다.

제공한 데이터를 테스트하려면 파일에서 데이터를 읽으세요.

$ awk -f script.awk file
1-3,5-9,12,14

분명히 다음과 같이 표준 입력의 문자열을 사용하여 제공할 수 있습니다.

$ awk -f script.awk <<<"1,2,3,5,9,10,11,12,13"
1-3,5,9-13

답변3

대신 다음 zsh과 같은 함수를 정의할 수 있습니다.

reduce() {
  local i=1
  argv=(${(nus:,:)1}) # split $1 on ",", numerically sort and remove dups
  while ((i < $#)) {
    if ((${argv[i]#*-} + 1 == ${argv[i+1]%-*})) {
      argv[i]=${argv[i]%-*}-${argv[i+1]#*-}
      argv[i+1]=()
    } else {
      ((i++))
    }
  }
  print ${(j:,:)@}
}

입력 범위도 허용됩니다.

$ reduce 1,2,3,5,6,7,8,9,12,14
1-3,5-9,12,14
$ reduce 1,2,3,5-7,8,9-11,12,13-20
1-3,5-20
$ reduce 5,2,4,5,6
2,4-6

입력 범위가 겹치면 제대로 작동하지 않습니다.

$ reduce 1-3,2
1-3,2
$ reduce 1-3,2-4
1-3,2-4

그로부터 bash함수를 다음과 같이 정의할 수 있습니다.

reduce() { zsh -c '
  i=1
  argv=(${(nus:,:)1}) # split $1 on ",", numerically sort and remove dups
  while ((i < $#)) {
    if ((${argv[i]#*-} + 1 == ${argv[i+1]%-*})) {
      argv[i]=${argv[i]%-*}-${argv[i+1]#*-}
      argv[i+1]=()
    } else {
      ((i++))
    }
  }
  print ${(j:,:)@}' zsh "$@"
}

답변4

인식된 범위를 유지하기 위해 배열을 사용하여 다음 BASH 함수를 사용했습니다. 입력 문자열은 함수의 첫 번째 매개변수이고 결과는 두 번째 매개변수를 통해 다시 전달됩니다.

function compact_range {
  arr=()
  start=""
  for cpu in ${1//,/ }; do
    # Start a new range definition if necessary
    [ -z "$start" ] && start=$cpu && range=$cpu && last=$cpu && continue
    prev=$(( $cpu - 1 ))
    # If the current CPU is not adjacent to the last CPU, start a new range
    [ "$prev" -ne "$last" ] && arr+=($range) && start=$cpu && range=$cpu && last=$cpu && continue
    # Current CPU is adjacent to an existing range, expand the current range
    range="${start}-${cpu}" && last=$cpu
  done
  # Append the last range to the array of ranges
  arr+=($range)
  # Return a comma delimited list of ranges
  eval $2=$(IFS=,;printf "%s" "${arr[*]}")
}

당신의 생각에 감사드립니다.

관련 정보