Thursday | 21 NOV 2024
[ previous ]
[ next ]

Left Pad

Title:
Date: 2022-01-20
Tags:  

I needed to left pad some numbers with leading 0s and I didn't want to bring in the infamous left-pad module. I found a pretty clever way to do padding on stackoverflow which I thought was really cool.

https://stackoverflow.com/questions/3605214/javascript-add-leading-zeroes-to-date

This core idea is to use the slice function on strings and to take characters from the end. If you do something like "XYZ".slice(-2), this will take the last two characters. This way you can pad everything and then do a slice against it to get the characters you want padded with whatever you want. It's a pretty simple solution and I really like it.

It is a bit verbose and having to use negative numbers and do the padding yourself is a pain so I do wish this was a standard library function. But node doesn't have one and so we need to live without.

This did make me curious about what the left-pad module is doing so I also took a look at that.

https://github.com/left-pad/left-pad/blob/master/index.js

Some interesting stuff in there. Padding with spaces is apparently common enough that the spaces are cached and so if you need padding with spaces and its under 10, it will simply return from the cache.

If you use a different delimiter, it will add the character in a loop, doubling each time to get to the required length. There is some weirdness here where there are bitwise operations happening which is also interesting.

// add `ch` to `pad` if `len` is odd
if (len & 1) pad += ch;

This is a neat way to check if a number is odd. Very cool. The binary of an odd number will always have the 1s place set to 1. So if you do a bitwise and with 1, you can check the 1s place for 1, if there is a 1 there, then you know it is odd!

I then went to the also infamous is-odd module to see how they do it.

https://github.com/i-voted-for-trump/is-odd/blob/master/index.js

return (n % 2) === 1;

In that package, the check for odd is the traditional division method where you check the remainder if it is one.

I wonder if there is an actual performance impact, I'd imagine the first option is faster, but the fact that both versions are in two popular modules probably means that it doesn't matter.

The next line in left-pad is a bitwise shift.

// divide `len` by 2, ditch the remainder
len >>= 1;

The divide by 2 happens because the binary of length gets shifted to the right by 1 each time the loop happens. By shifting one unit over, the entire number get's cut in half because of the way binary works.

if (len) ch += ch;
else break;

Now if the length is still a number, the module doubles the delimiter.

function pad(str, len, ch) {
    var pad = '';
    while (true) {
        console.log("pad: ", pad);
        console.log("ch: ", ch);
        console.log("len: ", len);
        console.log();
        if (len & 1) pad += ch;
        len >>= 1;
        if (len) ch += ch;
        else break;
    }
    return pad + str;
}

console.log(pad("1", "5","0"));

Here is a stripped down version with some console.logs to hopefully see what's going on.

pad:
ch:  0
len:  5

pad:  0
ch:  00
len:  2

pad:  0
ch:  0000
len:  1

000001

I still don't have a full grasp on it but padding occurs when the length is odd and the length and delimiter need to be in sync when they are doubling and halving.

The module is quite optimized and I'll need to come back to it!

I also checked the oldest version that was written and it looks like it originally used a loop and padded the string 1 character at a time. 2 years after the initial commit, someone added the optimized version.

I still like the stackoverflow answer of using slice as it's a bit more obvious what's going on but left-pad is pretty interesting.