I think the regex answer from user Unmitigated is your best bet. But if you want a non-regex solution, we can do this by iterating through the string one character at a time, updating the resulting string (r
), the currently processing character (c
) and the decimal digits (d
) each time. reduce
is good for this, with a {c, d, r}
accumulator, and an individual character s
input to the callback. It would look like this:
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)}"`)
)
Note that at the end of the reduce, we need to do a final calculation based on r
, c
, and d
. There are alternatives to this, but I think they're all uglier.
The step-by-step values should make it clear, I hope:
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"
The two uses of c .repeat (+d || 1)
add a number of copies of the current character. If we have digits, we convert them to a number and then convert 0
s to 1
s. This means that we don't support plain 0
s in the compressed string. That seems reasonable, but if you want to do so, you can simply replace both occurrences with c .repeat (d.length ? +d : 1)
.
Update
My note that there are solutions without that final calculation said that they are all uglier. I've thought a bit more, and this is not bad at all:
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)}"`)
)
Again, this solution does not support leading zeros in the counts.
The steps look like this:
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`
The tricky step is here:
acc = {c: "d", d: "1", r: "abbbcd"}, s = "3"
We have a digit, '3'
, and so we look to the existing digits, now '1'
, convert both '13'
and '1'
to numbers, subtract them, giving 12
and so add twelve more 'd'
characters.
If the next character after that had been '7'
, then we would do it again, subtracting 13
from 137
to get 124
and adding 124 more 'd'
characters. In this manner, the r
result always holds the appropriate result for the current string prefix, which is a nice feature. We could get leading zeroes to work by clamping the subtraction to always be at least 0
, but there seems little need. If later decide we want that, it's easy enough to do.
If you really don't like the short variable names, we could also write
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;
but I find that a bit of a mouthful.