我有一个逗号分隔的数字字符串,如下所示:
1,2,3,5,6,7,8,9,12,14
我正在寻找一个在bash
脚本中使用的命令,该命令可以将相邻数字组合成范围/连字符条目,如下所示:
1-3,5-9,12,14
保证初始字符串按升序排序。
答案1
使用perl:
perl -pe 's/\b(\d+)(?{$q=$1+1})(?:,(??{$q})\b(?{$p=$q++})){2,}/$1-$p/g'
(?{...})
这是通过and表达式使用嵌入 Perl 代码的正则表达式(??{...})
;第一个仅评估嵌入代码,而第二个则使用它返回的值作为模式。请参阅perlre(1)
参考资料 获取完整描述。
如果您还想要仅包含两个数字的范围(例如-> ),请将量词替换{2,}
为。+
1,2,7
1-2,7
答案2
这是一个简短的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
答案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[*]}")
}
感谢大家的想法。