JavaScript: 数列を圧縮する

2026年2月14日
2 分

単調増加する整数列を圧縮した文字列表現に変換したい。

つまり、[1, 2, 3, 5, 7, 8, 9]"1-3,5,7-9" に変換したい。

JavaScript で書いてみたけど存外に長くなってしまった。

const { parseArgs } = require('node:util')


const numCompress = (ary) => {
    diff = diffSeq(ary)
    const result = []
    let buf = [ary.shift(), null]
    for (let i=0; i<diff.length; i++) {
        if (diff[i] == 1) {
            buf[1] = ary[i]
        } else {
            result.push(compress(buf))
            buf = [ary[i], null]
        }
    }
    result.push(compress(buf))
    return result.join(',')
}

const slices = (ary, n) => {
    const l = ary.length - n
    const result = []
    for (let i=0; i<=l; i++) {
        result.push(ary.slice(i, i+n))
    }
    return result
}

const diffSeq = (ary) => slices(ary, 2).map(([a, b]) => b - a)

const compress = (buf) => {
    if (buf[1] == null) {
        return String(buf[0])
    } else {
        return buf.join('-')
    }
}


// main routine
const { positionals } = parseArgs({
    allowPositionals: true,
})

ary = positionals.map(Number)
result = numCompress(ary)
console.log(result)

考え方としては、先に階差数列を作っておき、それを参照しながらバッファを操作する。 バッファは2つの値(初めと終わりに相当)を保持でき、階差が 1 なら終わりの値を更新、1 より大きいなら圧縮した文字列を result 変数に追加してバッファを初期化する。最後に result',' でつないで完了だ。

ちゃんと期待通りに動作する。

$ node numCompress.js 1 2 3 5 7 8 9
1-3,5,7-9

だけど、階差数列を作るのってまどろっこしくないか? そんなことしなくても for ループの繰り返しの中でやればいいんじゃないか? と思い直して書いたのがこれ。

const { parseArgs } = require('node:util')


const numCompress = (ary) => {
    const result = []
    let buf = [ary[0], null]
    for (let i=1; i<ary.length; i++) {
        if (buf[1] === null) {
            if (ary[i] - buf[0] == 1) {
                buf[1] = ary[i]
            } else {
                result.push(compress(buf))
                buf = [ary[i], null]
            }
        } else if (ary[i] - buf[1] == 1) {
            buf[1] = ary[i]
        } else {
            result.push(compress(buf))
            buf = [ary[i], null]
        }
    }
    result.push(compress(buf))
    return result.join(',')
}

const compress = (buf) => {
    if (buf[1] == null) {
        return String(buf[0])
    } else {
        return buf.join('-')
    }
}


// main routine
const { positionals } = parseArgs({
    allowPositionals: true,
})

ary = positionals.map(Number)
result = numCompress(ary)
console.log(result)

これも期待通りに動作する。

$ node numCompress2.js 1 2 3 5 7 8 9
1-3,5,7-9

が、短くはなったけど、条件分岐が複雑になってわかりにくくなった。一つ目のほうがよさそう。