Maze62: a dense and speedy alphanumeric encoding for binary data
blog.altudov.com
blog.altudov.com
Ascii85 (http://en.wikipedia.org/wiki/Base85) uses a similar concept, but using the majority of the printable ASCII characters. It gets a hard-to-beat 20% expansion on binary data (4 bytes in 5 chars) without requiring bigint math.
_
The prime scenario that caused me to think about this problem is indeed URL components, and in particular object identifies for RESTful protocols.
The other thing I was thinking about were cookies values, which I have had problems with in the past.
EDIT: The most annoying part about base64 is that different systems have different restrictions. With Maze62 I won't have to think about which system my data will end up in.
On the other hand, if it is a problem, how's this idea: Use strings of 129 base-62 digits to represent strings of 128 base-64 digits. I pick 128 because it might be convenient, and 129/128 is close to optimal:
arc> (* 129 (log 62 64))
128.01522067331783
Or if decoding 128 digits at a time when you just want a couple is excessive, you could use 17 base-62 digits for 16 base-64 digits (which has a space inefficiency of 1/16th, or 6.25%), or 9 for 8 (inefficiency 12.5%), or whatever.As to your proposed algorithm, yes, it could work. It's discussed in passing a bit earlier in this thread, here's direct link: http://news.ycombinator.com/item?id=2554726
Not sure about comparative space efficiency of this vs Maze64, but we're likely splitting hairs at this point :), it should be very close. Yes it could work. Converting 128 characters from base64 to base62 would cost N^2 time, where N is 128. Not sure if it's a big deal. Just another approach.
One could extend it upwards as far as desired (probably approaching log_64(62) worst-case and average-case space compared to base64[1]), though the integer arithmetic would become a pain eventually. 12/11 or maybe 18/17 (105.8%) or 24/23 (104.3%) is probably good enough for most purposes. The relatively hardcore might use 60/59 (101.7%), which x86_64 machines can still do with CPU arithmetic (an integer multiply of two 64-bit integers stores the results in two 64-bit registers, and you can then do an integer divide on that, which stores the quotient and remainder in those registers).
[1] Average case: I analyze it like this. If you encode n base64 integers at a time, then you'll encode 6n bits at a time, but 6n-1 bits if you're unlucky and get a number in the ranges [62^n, 64^n - 1] or [0, 64^n - 62^n - 1]. These two ranges, taken together, make 2 * (64^n - 62^n) numbers out of 64^n possible n-digit base64 numbers. Thus, on average, you encode
6n - 1 * [2 * (64^n - 62^n)]/64^n
= 6n - 2 * (1 - (62/64)^n)
bits for every group of n characters, or 6 - 2/n * (1 - (62/64)^n)
bits per character. (Note that this analysis only applies while 62^n < 64^n < 2 * 62^n, so taking n -> ∞ gives misleading results.) The original Maze62 proposal has n = 1, and this yields 5.9375 bits per character on average, which is pretty good. (Check this: 5 * 4/64 + 6 * 60/64 = 5.9375.) The next few values are: 1 5.9375
2 5.9384765625
3 5.939432779947917
4 5.940369129180908
...
10 5.945595231334425
Theoretical limit:
6 * log_64(62) = 5.954196310386876
So I guess there's really not much to be gained for the average case by increasing n. But someone pointed out that all-0 and all-1 sequences happen frequently in real situations, so it may be worth it anyway.To deal with large runs of identical numbers (e.g. all zeroes) someone suggested in my blog comments to XOR the input array with a result of a chosen pseudo-random function. This will bring any dataset into the realm of average (5.9375) except for the dataset that is specifically targeting the chosen pseudo-random function. :)
(I suspect you won't beat or even tie Base64, but I'm prepared to accept that you only got within X% of base64 but that X% is worth it in some scenario. And maybe if you get clever enough you can prove me wrong.)
See, the problem is that Base64 is linear is speed, but has large (non-alphanumeric) charset, while base62 has small (alphanumeric) charset but is quadratic in speed (at least in its straightforward implementation, the only one I was able to find).
Maze62 is the encoding that is both constrained in charset and liner in speed. Hence, the title.
Does it make sense?
http://stackoverflow.com/questions/5940416/compress-a-series...
>>> bin_string = '000111000010101010101000101001110'
>>> encode(bin_string)
'hcQOPgd'
>>> decode(encode(bin_string))
'000111000010101010101000101001110'Base64 encoding also seems to result in quite long ascii strings compared to what I threw together.
Or is there a way to use Base64 which would give results of a comparable length?
As wikipedia says, padding can be added or removed as a matter of taste: From a theoretical point of view the padding character is not needed, since the number of missing bytes can be calculated from the number of base64 digits.
There are probably cases where this is acceptable, but I'm inclined to think that Base32 is a better choice if larger size is acceptable, and that Base64 with domain appropriate characters is better where performance counts.
[edit: i thought i had a good demonstration of this. actually, two. but both were wrong. so it does seem to be non-trivial...]
[edit 2: open stack overflow question: http://stackoverflow.com/questions/3545931/efficient-algorit... ]
Now, whether we could come up with such streaming algorithm I'm not sure. I tried solving this problem, and this is what I came up with. Maybe I haven't tried hard enough to go the other route... I guess you could simply slice the incoming stream into a set of chunks, each small enough to make bigin base62 conversion fast (which is square of the size of the chunk), and yet large enough to obscure the occasional loss of space at the chunk boundaries... I guess that would be an option...
Or you could construct the output by computing the effects of the input digits one by one, and in that case, the highest input digit will affect all n(ish) digits of the output, the next highest will affect roughly n-1 digits, etc.
The algorithm could be thoroughly parallelized, but I'm pretty confident that the work done has to be O(n^2).