我有一个不符合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脚本,但我找不到任何解决方案。
发布于 2022-02-09 22:19:13
由于只有shell
和bash
被标记,下面是一个基于参数展开的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}
8-10,25,47-55,102,107,111,201-205
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)
,或者按照这里:IFS=$'\n' arrayvar=($(sort -n <<< "${arrayvar[*]}")); unset IFS
中的解决方案进行排序。发布于 2022-02-08 17:52:36
查找列表中的最小值和最大值。将所有数字作为键存储在关联数组中。然后,将所有数字从最小值迭代到最大值,如果数字-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与套装: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"
发布于 2022-02-10 04:17:35
好的,这是一个有趣的小问题,即使你没有显示你的最小、完全和可验证的示例(MCVE),它是值得增加另一种方法。
您可以通过保留两个标志(或状态)变量来简化此任务。1
或0
(真或假)的简单变量,这些变量跟踪您是否已经写入了第一个值,以及您是否处于一个数字序列中,还是在序列号的范围之间。当您循环这些值时,如果您保留上次迭代中的最后一个值,则任务变得相当容易。
例如,您可以:
#!/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
https://stackoverflow.com/questions/71038194
复制相似问题