Преобразовать список чисел, разделенных запятыми, в список или диапазон, разделенный дефисами

Угловые скобки ( )обозначают «32 -битный массив целых чисел без знака», и я полагаю, что их часто называют «ячейками». В случае со свойством regпервое значение — это (адрес смещения ), а второе — длина регистра (с ). Обратите внимание, что также возможно иметь несколько диапазонов, т.е.

reg = 

Источники:

0
16.01.2020, 01:00
6 ответов

В итоге я получил следующую функцию 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[*]}")
}

Спасибо всем за идеи.

0
28.01.2020, 02:54

Вот короткий awkскрипт, который проходит через -разделенный запятыми список отсортированных целых чисел и заполняет два массива, aи b, при этом.

Массив aбудет содержать начальное целое число для каждого диапазона монотонно возрастающих целых чисел, а массив bбудет содержать соответствующее конечное целое число. Переменная nв коде содержит количество найденных диапазонов.

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
0
28.01.2020, 02:54

Вместо этого с помощью 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 "$@"
}
0
28.01.2020, 02:54

Я был бы очень удивлен, если бы это можно было сделать в sed. Вы можете написать чистый bash-скрипт, но в awkэто довольно просто. Эта программа чем-то похожа на Кусалананды . но не использует массив.

awk -F, '
    {
        begin=$1
        prev=$1
        for (i=2; i<=NF; i++) {
                if ($i == prev+1) {
                        prev=$i
                        continue
                }
                if (begin==prev) {
                        printf "%s,",    begin
                } else {
                        printf "%s-%s,", begin, prev
                }
                begin=$i
                prev=$i
        }
        if (begin==prev) {
                printf "%s",    begin
        } else {
                printf "%s-%s", begin, prev
        }
        print ""
    }'
  • -F,устанавливает разделитель полей, поэтому мы можем рассматривать каждое число как поле.
  • begin— первое число в текущем диапазоне последовательных номеров.
  • prevвсегда является самым последним числом, которое мы просматривали. Мы могли бы просто сказать $(i-1); Я подумал, что будет понятнее дать ему имя.
  • Если текущий номер на единицу больше предыдущего, мы просто добавляем еще одно число к ряду последовательных чисел, так что запишите это и двигайтесь дальше.
  • В противном случае мы начинаем новый диапазон. Распечатайте диапазон, который мы только что закончили. Если диапазон начинается и заканчивается одним и тем же числом, просто напечатайте это число и не печатайте его дважды через дефис. Печатать запятую и без новой строки.
  • Повторите описанную выше логику в конце строки. Поскольку это последний диапазон (или число )в строке, не ставьте после него запятую; вместо этого используйте print "", чтобы напечатать новую строку.
0
28.01.2020, 02:54

Использование Perl:

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

Здесь используется регулярное выражение со встроенным кодом Perl через выражения (?{...})и (??{...}); первый просто оценивает встроенный код, а второй использует возвращаемое им значение в качестве шаблона. Полное описание см. в perlre(1).

Замените квантификатор {2,}на +, если вам также нужны диапазоны только из двух чисел (, например.1,2,7->1-2,7).

1
28.01.2020, 02:54

Очень короткий awk ответ для однострочного ввода, обрабатывающий все случаи ниже(сканирование чисел выполняется слева -в -справа во всех случаях )в отличие от других существующих в настоящее время ответов, которые терпят неудачу во всех этих случаях, и в основном они обрабатывают только случай #2:

$ awk -v RS=, 'function prnt(){ printf sep start (end==start?"":"—"end) ; sep=RS }
    end!="" && ( end==$0-1 || end==$0+1) { end=$0; next }
    end!=""                              { prnt() }
                                         { start=end=$0 }
END{ prnt() }'

1. числа в порядке убывания:

$ awk -v RS=, 'function prnt(){ printf sep start (end==start?"":"—"end) ; sep=RS }
    end!="" && ( end==$0-1 || end==$0+1) { end=$0; next }
    end!=""                              { prnt() }
                                         { start=end=$0 }
END{ prnt() }' <<<'14,13,12,11,9,8,7,3,2,1,0,-1'
14—11,9—7,3—-1

2. цифры в порядке возрастания:

$ awk -v RS=, 'function prnt(){ printf sep start (end==start?"":"—"end) ; sep=RS }
    end!="" && ( end==$0-1 || end==$0+1) { end=$0; next }
    end!=""                              { prnt() }
                                         { start=end=$0 }
END{ prnt() }' <<<'1,2,3,5,6,7,8,9,12,14'
1—3,5—9,12,14

3. числа в несортированном порядке:

$ awk -v RS=, 'function prnt(){ printf sep start (end==start?"":"—"end) ; sep=RS }
    end!="" && ( end==$0-1 || end==$0+1) { end=$0; next }
    end!=""                              { prnt() }
                                         { start=end=$0 }
END{ prnt() }' <<<'10,3,4,5,6,2,1,5,6,7,9,7,2,3'
10,3—6,2—1,5—7,9,7,2—3

Для применения к файлу, содержащему несколько таких строк, например:

$ cat infile
14,13,12,11,9,8,7,3,2,1,0,-1
1,2,3,5,6,7,8,9,12,14
-1,0,1,2,4,3,2,1,0,-1,-2,-2,-2,-2,-4
10,3,4,5,6,2,1,5,6,7,9,7,2,3

и немного подкорректировать скрипт:

$ awk -v RS=, 'function prnt(){ printf sep start (end==start?"":"—"end) ; sep=RS }
   /\n/{
         printf "%s\n", sep start ((end==$1-1 || end==$1+1)?"—":sep) $1;
         sep=""; start=end=$2; next
       }
   end!="" && ( end==$0-1 || end==$0+1) { end=$0; next }
   end!=""                              { prnt() }
{ start=end=$0 }' infile

Выход:

14—11,9—7,3—-1
1—3,5—9,12,14
-1—2,4—-2,-2,-2,-2,-4
10,3—6,2—1,5—7,9,7,2—3
0
28.04.2021, 20:09

Теги

Похожие вопросы