shell 如何收集范围内的随机数列表?

jogvjijk  于 2023-11-21  发布在  Shell
关注(0)|答案(3)|浏览(145)

我有一个不按顺序排列的数字列表8,9,10,25,47,48,49,50,51,52,53,54,55,102,107,111,201,202,203,204,205,对我来说,把这个数字列表放在范围内是非常重要的,就像这样:8-10,25,47-55,102,107,111,201-205,我熟悉shell脚本,但我找不到任何解决方案。

dohp0rv5

dohp0rv51#

由于只有shellbash被标记,因此这里有一个基于Parameter Expansion的bash解决方案:
这将遍历数字列表,并对每个新数字相应地调整目标字符串的最后一部分。

list=
for n in 8 9 10 25 47 48 49 50 51 52 53 54 55 102 107 111 201 202 203 204 205; do
  a=${list##*,} b=${a#*-} c=${a%-*} list=${list%$a}
  if ((n > b+1)); then list+=$a,$n
  elif ((n > c)); then list+=$c-$n
  else list+=$n; fi
done
echo ${list:1}

个字符

  • 如果你的数字存储在数组中,那么循环遍历数组,就像for n in ${arrayvar[@]}; do一样。
  • 如果输入是一串逗号分隔的数字(类似于输出),则使用arrayvar=(${strvar//,/ })将其转换为数组,或者直接在转换for n in ${strvar//,/ }; do时进行转换。
  • 如果你想在一个文件中,每个数字都在自己的一行上,把for循环改为while read -r n; do
  • 如果您需要先对文件的内容进行排序,sort -n是最简单的方法。
  • 如果你想把它作为一个数组进行排序,可以使用mapfile -t arrayvar < <(printf '%s\n' "${arrayvar[@]}" | sort -n),或者按照here的解决方案:IFS=$'\n' arrayvar=($(sort -n <<< "${arrayvar[*]}")); unset IFS
trnvg8h3

trnvg8h32#

找到列表中的最小值和最大值。将所有数字作为键存储在一个关联数组中。然后,从最小值到最大值重新排列所有数字,如果数字-1不在关联数组中,则开始一个新的范围。如果它在那里,则范围继续,如果数字+1不在那里,则范围到此结束。

#! /bin/bash

list2ranges () {
    local -a arr
    IFS=, arr=($1)
    local min=${arr[0]}
    local max=${arr[0]}

    local -A seen
    local e
    for e in "${arr[@]}" ; do
        (( e < min )) && min=e
        (( e > max )) && max=e
        seen[$e]=1
    done

    local -a out
    local i
    for ((i=min; i<=max; ++i)) ; do
        if [[ ${seen[$i]} ]] ; then
            if [[ ${seen[$((i-1))]} ]] ; then
                if [[ ${out[${#out[@]}-1]} != *- ]] ; then
                    out[${#out[@]}-1]+=-
                fi
                if [[ ! ${seen[$((i+1))]} ]] ; then
                    out[${#out[@]}-1]+=$i
                fi
            else
                out+=($i)
            fi
        fi
    done
    IFS=, echo "${out[*]}"
}

list=8,9,10,25,47,48,49,50,51,52,53,54,55,102,107,111,201,202,203,204,205

diff <(list2ranges "$list") <(echo 8-10,25,47-55,102,107,111,201-205)

字符串
如果最小值和最大值之间的差异很大,这将花费很长时间。考虑使用Perl与Set::IntSpan代替。

list=8,9,10,25,47,48,49,50,51,52,53,54,55,102,107,111,201,202,203,204,205
perl -MSet::IntSpan -wE 'say Set::IntSpan->new(shift)' -- "$list"

mcdcgff0

mcdcgff03#

好吧,这是一个有趣的小问题,值得添加另一种方法。
你可以通过保持一对夫妇的旗帜使这项任务更容易(或状态)变量。10的简单变量(true或false),它会跟踪您是否已经写入了第一个值,以及您是在一个数字序列中还是在连续数字的范围之间。当您循环值时,如果您保留上一次迭代中的最后一个值,任务变得相当容易。
例如,您可以:

#!/bin/bash

list=8,9,10,25,47,48,49,50,51,52,53,54,55,102,107,111,201,202,203,204,205

first=0     ## first value written flag
inseq=0     ## in sequence flag

while read -r n; do   ## read each of the sorted values
  ## if first not written, write, set last to current and set first flag
  [ "$first" -eq 0 ] && { printf "%s" "$n"; last="$n"; first=1; continue; }
  ## if current minus last is 1 -- in sequence, set inseq flag
  if ((n - last == 1)); then
    inseq=1
  elif [ "$inseq" -eq 1 ]; then         ## if not sequential
    printf -- "-%s,%s" "$last" "$n"     ## finish with last, output next
    inseq=0                             ## unset inseq flag
  else
    printf ",%s" "$n"                   ## individual number, print
  fi
  last="$n"                             ## set last to current
        ## process substitution sorts list with printf trick
done < <(IFS=$','; printf "%s\n" $list | sort -n)

## output final term either ending sequence or as individual number
if [ "$inseq" -gt 0 ]; then
  printf -- "-%s" "$last"
else
  printf ",%s" "$last"
fi

echo ""   ## tidy up with a newline

字符串
您声明您的数字没有按顺序排列,因此while()循环由一个进程替换提供,该进程替换在读取值之前使用sort -n对值进行排序。(进程替换中未加引号的$list是故意的)

示例使用/输出

$ bash ranges.sh
8-10,25,47-55,102,107,111,201-205

相关问题