次のように、カンマで区切られた数値文字列があります。
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コードで正規表現を使用することです(??{...})
。最初は埋め込みコードのみを評価し、2番目はパターンとして返す値を使用します。perlre(1)
完全な説明については、リソースを参照してください。
2つの数字のみを含む範囲(例: - > )が必要な場合は、数量子{2,}
をに置き換えます。+
1,2,7
1-2,7
答え2
awk
以下は、コンマで区切られたソートされた整数のリストを繰り返し、2つの配列を埋める短いスクリプトです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関数を使用しました。入力文字列は関数の最初のパラメータであり、結果は2番目のパラメータを介して返されます。
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[*]}")
}
あなたの考えに感謝します。