簡體   English   中英

JavaScript 算法:解壓縮壓縮字符串

[英]JavaScript algo: decompress a compressed string

我想編寫一個函數,它接受一個壓縮字符串並輸出解壓縮字符串。

a2b2c3的壓縮字符串,解壓字符串是aabbccc更多例子是

`a` -> `a`
`ab12` -> `abbbbbbbbbbbb`
`a3b2a2` -> `aaabbaa

我試圖實現它,但對於像ab12這樣的壓縮字符串來說真的很混亂和有ab12

function isNumeric(num) {
    if (num === '') return false
    if (num === null) return false
    return !isNaN(num)
  }
  

function decompress(compressedStr) {
    const array = compressedStr.split('')
    let prevChar, str = ''
    for(let i = 0; i < array.length; i++) {
        if(i === 0) {prevChar = array[i]}
        if(isNumeric(array[i])) {
            str += prevChar.repeat(Number(array[i]))
            prevChar = null
        } else {
            if(!prevChar) prevChar = array[i] 
            else {
                str += prevChar
                prevChar = array[i] 
            }
        }
    }

    return str
}

現在它適用於a3b2a2但對於像ab12這樣的情況有ab12 需要幫助重寫此函數以使其工作。

您可以在捕獲字符及其重復次數時使用String#replace

 function decompress(str) { return str.replace(/(\\D)(\\d+)/g, (_, g1, g2) => g1.repeat(g2)); } console.log(decompress('a')); console.log(decompress('ab12')); console.log(decompress('a3b2a2'));

 const is_digit = (ch) => '0' <= ch && ch <= '9' function decompress(str) { if( str.length === 0 ) return '' if( is_digit(str[0]) ) return 'invalid input' const output = [] let i = 0 while(i !== str.length) { // collect non-digits into the `output`, stop at the end or at a digit while( is_digit(str[i]) === false ) { if( i === str.length ) return output.join('') output.push(str[i]) ++i } if( i === str.length ) break // remember the `ch` with a span let ch = str[i-1] // parse the span let span = 0 while( is_digit(str[i]) ) { if( i === str.length ) break span = span * 10 + Number(str[i]) ++i } if( span === 0 ) { // don't forget about edge cases output.pop() } else { // span-1 because the first `ch` is already in the `output` for( let j = 0 ; j !== span-1 ; ++j ) { output.push(ch) } } } return output.join('') } // tests [ '', '12', // edge case 'a', 'ab', 'a0', // another edge case 'ab0', 'a0b0', 'a0b', 'a1', // yet another 'a01', // and another 'ab1', 'a1b1', 'ab01', 'ab12', // your favorite 'a12b', 'a3b2a2', ].forEach((test_case) => console.log('res:', decompress(test_case)))

這不是 JS 中的最佳解決方案(V8 中有很多魔法),因此需要對其進行測試。 但看起來 OP 並不是在編寫生產代碼,而是在練習。 所以這里的要點是

  • 代碼總是向前運行並只創建output ,因此它在速度和內存方面具有 O(N) 復雜度
  • 它不使用字符串連接(因為在幕后……你不能確定,但​​你可以假設 V8 每次連接都會創建新的字符串,它會破壞復雜性)
  • 它不使用Number.parseInt或類似的東西 - 因為,同樣,這需要創建新的字符串

在沒有正則表達式的情況下,您可以直接遍歷字符,直到遇到一個非數字字符,然后消耗掉它之后的所有數字字符。

 function decompress(str) { let prevCh = '', num = '', res = ''; function append() { if (prevCh) if (num) res += prevCh.repeat(num), num = ''; else res += prevCh; } for (const ch of str) { if (isNaN(ch)) { append(); prevCh = ch; } else num += ch; } append(); return res; } console.log(decompress('a')); console.log(decompress('ab12')); console.log(decompress('a3b2a2'));

不確定如何解決您的解決方案,但這是一種方法

 let s = 'ab12'.split('') function decompress(str) { for (let i = 1; i < s.length; i++) { if (!isNaN(s[i]) && !isNaN(s[i - 1])) { s[i - 1] = s[i - 1] + s[i] s.splice(i, 1) } } for (let i = 1; i < s.length; i++) { if (!isNaN(s[i])) { s[i - 1] = s[i - 1].repeat(Number(s[i])) s.splice(i, 1) } } return str.join('') } console.log(decompress(s))

我認為用戶 Unmitigated 的正則表達式答案是您最好的選擇。 但是如果你想要一個非正則表達式的解決方案,我們可以通過一次遍歷字符串一個字符來實現,每次更新結果字符串 ( r )、當前處理的字符 ( c ) 和十進制數字 ( d )。 reduce對此有好處,它帶有一個{c, d, r}累加器,以及一個單獨的字符s輸入到回調中。 它看起來像這樣:

 const decompress = ([...ss], {c, d, r} = ss .reduce ( ({c, d, r}, s) => '0' <= s && s <= '9' ? {c, d: d + s, r} : {c: s, d: '', r: r + c .repeat (+d || 1)}, {c: '', d: '', r: ''} ) ) => r + c .repeat (+d || 1); ['', 'a', 'ab12', 'a3b2a2', 'a3b2a2d', '1ab2c', 'ab3cd13ac'] .forEach ( s => console .log (`"${s}": "${decompress (s)}"`) )

請注意,在 reduce 結束時,我們需要根據rcd進行最終計算。 有替代方案,但我認為它們都更丑。

分步值應該說清楚,我希望:

decompress ('ab3cd13ac')
acc = {c: "",  d: "",   r: ""},                   s = "a"
acc = {c: "a", d: "",   r: ""},                   s = "b"
acc = {c: "b", d: "",   r: "a"},                  s = "3" 
acc = {c: "b", d: "3",  r: "a"},                  s = "c" 
acc = {c: "c", d: "",   r: "abbb"},               s = "d"
acc = {c: "d", d: "",   r: "abbbc"},              s = "1"
acc = {c: "d", d: "1",  r: "abbbc"},              s = "3" 
acc = {c: "d", d: "13", r: "abbbc"},              s = "a"
acc = {c: "a", d: "",   r: "abbbcddddddddddddd"}, s = "c"
acc = {c: "c", d: "",   r: "abbbcddddddddddddda"} ==> "abbbcdddddddddddddac"

c .repeat (+d || 1)的兩種用法添加了當前字符的多個副本。 如果我們有數字,我們將它們轉換為數字,然后將0轉換為1 這意味着我們不支持壓縮字符串中的純0 這看起來很合理,但如果你想這樣做,你可以簡單地用c .repeat (d.length ? +d : 1)替換這兩個出現。

更新

我注意到有沒有最終計算的解決方案說它們都更丑陋。 我想多了一點,這還不錯:

 const decompress = ([...ss]) => ss .reduce ( ({c, d, r}, s) => '0' <= s && s <= '9' ? {c, d: d + s, r: r + c .repeat (+(d + s) - (d.length ? +d : 1))} : {c: s, d: '', r: r + s}, {c: '', d: '', r: ''} ) .r; ['', 'a', 'ab12ef', 'a3b2a2', 'a3b2a2d', '1ab2c', 'ab3cd13ac'] .forEach ( s => console .log (`"${s}": "${decompress (s)}"`) )

同樣,此解決方案不支持計數中的前導零。

步驟如下所示:

decompress ('ab3cd13ac')
acc = {c: "",  d: "",   r: ""},                    s = "a"
acc = {c: "a", d: "",   r: "a"},                   s = "b"
acc = {c: "b", d: "",   r: "ab"},                  s = "3"
acc = {c: "b", d: "3",  r: "abbb"},                s = "c"
acc = {c: "c", d: "",   r: "abbbc"},               s = "d"
acc = {c: "d", d: "",   r: "abbbcd"},              s = "1"
acc = {c: "d", d: "1",  r: "abbbcd"},              s = "3"
acc = {c: "d", d: "13", r: "abbbcddddddddddddd"},  s = "a"
acc = {c: "a", d: "",   r: "abbbcddddddddddddda"}, s = "c"
acc = {c: "c", d: "",   r: "abbbcdddddddddddddac"} ==> take `r`

棘手的步驟在這里:

acc = {c: "d", d: "1",  r: "abbbcd"},              s = "3"

我們有一個數字, '3' ,所以我們查看現有的數字,現在是'1' ,將'13''1'為數字,減去它們,得到12然后再添加 12 個'd'字符。

如果之后的下一個字符是'7' ,那么我們會再做一次,從137減去13得到124 ,然后再添加 124 個'd'字符。 通過這種方式, r結果總是保存當前字符串前綴的適當結果,這是一個很好的特性。 我們可以通過將減法限制為始終至少為0來使前導零起作用,但似乎幾乎沒有必要。 如果后來決定我們想要那個,這很容易做到。

如果你真的不喜歡簡短的變量名,我們也可以寫

const decompress = ([...characters]) => characters .reduce (
  ({current, digits, result}, character) => '0' <= character && character <= '9' 
    ? {current, digits: digits + character, result: result + current .repeat (+(digits + character) - (digits.length ? +digits : 1))}
    : {current: character, digits: '', result: result + character},
  {current: '', digits: '', result: ''}
) .result;

但我覺得有點難吃。

反向迭代字符串可以簡化邏輯,因為人們只是簡單地存儲數字,直到遇到非數字為止。

在這里,將計數累積為一個數字,這在小測試中證明比使用字符串略快,但代價是代碼稍微復雜一些。

 function decompress(str) { let num = 1, place = 0, res = ''; for (let i = str.length - 1; i >= 0; i--) { const ch = str[i]; if (isNaN(ch)) { res = ch.repeat(num) + res; num = 1; place = 0; } else num = (place ? num : 0) + ch * 10 ** place++; } return res; } console.log(decompress('a')); //a console.log(decompress('a0b0x')); //x console.log(decompress('ab12')); //abbbbbbbbbbbb console.log(decompress('a3b2a2')); //aaabbaa console.log(decompress('0a3b2a2')); //aaabbaa console.log(decompress('a69546803').length);

或者在字符串中累積數字以被repeat()強制,測試速度稍慢但代碼簡潔。

 function decompress(str) { let num = '', res = ''; for (let i = str.length - 1; i >= 0; i--) { const ch = str[i]; if (isNaN(ch)) { res = ch.repeat(num || 1) + res; num = ''; } else num = ch + num; } return res; } console.log(decompress('a')); //a console.log(decompress('a0b0x')); //x console.log(decompress('ab12')); //abbbbbbbbbbbb console.log(decompress('a3b2a2')); //aaabbaa console.log(decompress('0a3b2a2')); //aaabbaa console.log(decompress('a69546803').length);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM