But I wouldn't say "no wasted bits" unless 120 bits is also exactly the right length for a particular use case. For example, if my use case would be served better by 64 bits, I'd prefer losing some bits to padding than extending it to 120 bits.
This gives base 28, of course, which is admittedly slightly less neat, but still perfectly usable. For example, in TypeScript I use this:
const targetChars = '23456789bcdfghjkmnpqrstvwxyz'; // must be <=36 chars
const sourceChars = '0123456789abcdefghijklmnopqr'; // what we get from toString(radix)
const radix = targetChars.length;
export function tokenFromId(id: number) {
return [...id.toString(radix)]
.map(origChar => targetChars.charAt(sourceChars.indexOf(origChar)))
.join('');
}
export function idFromToken(token: string) {
return parseInt([...token.toLowerCase()]
.map(pathChar => sourceChars.charAt(targetChars.indexOf(pathChar)))
.join('')
, radix);
}So, you don't need to remove ambiguities if you can collapse the ambiguities into equivalence classes.
If you're going to use some flavor of base32, crockford32 is hard to argue against. I could bikeshed some alternatives (I would use the letter U and include Q in the 0 equivalence class, and not worry too much about obscenities), but of the somewhat common base32 standards, crockford32 is the best, IMNSHO.
Base32H has some advantages and disadvantages compared to Crockford's (see: https://base32h.github.io/comparisons#crockfords-base32 ); long story short: L/l is its own digit, 5/S/s are merged, and U/u/V/v are merged. In my totally-not-biased-at-all opinion, the advantages outweigh the disadvantages enough to have warranted creating Base32H instead of just using Crockford's.
If I was willing to break from duotrigesimal, I'd probably merge 0/O/o/Q/q, 1/I/i/L/l, 2/Z/z, 3/E/e, 6/G/g, 7/T/t, 8/B/b, and 9/P/p. The resulting Base24H would hinder readability of encoded words, and would break the alignment to whole bits, but it's probably close to the optimal intersection of information density, unambiguity, and convenience.
It still leaves users who don’t know or can’t assume that normalization is applied worrying about which character exactly is being displayed.
In the present case, instead of having to inform the user about the normalization, it would be simpler to refrain from using potentially ambiguous characters in the first place.
But, needing users to read / type a base32 ID is always bad UX anyway.
let slug = base32Encode( window.performance.now() )
Maybe 15 LoC if you don't want to import a b32 encoder. Hardly seems worth a dependency.How? I mean they're of course no cryptographic measure but with the salt you have some secrecy.
I wrote a library that generates short IDs with the goal of making the similarity between two codes have nothing to do with sequence order.
> Hashids is a small open-source library that generates short, unique, non-sequential ids from numbers.
vs the summary in the article:
> So now when I list atoms from my filesystem or in S3, they come back in the same order that I wrote them.
To me, "math/big" feels like a big hammer for working on an int64; a nice alternative is "encoding/binary":
func atomSlug(publishedAt time.Time) string {
b := make([]byte, 0, 8)
b = binary.BigEndian.AppendUint64(b, uint64(publishedAt.Unix()))
b = bytes.TrimLeft(b, "\x00")
return lexicographicBase32Encoding.EncodeToString(b)
}Right now the time is converted to 4 bytes which then uses 7 base32 characters to encode. When the time rolls over to 33 bits in 2106, it will increase to 5 bytes encoded as 8 base32 chars, even though 7 base32 chars have enough bits (35) to represent the number.
So perhaps a better way would be something like:
func atomSlug(publishedAt time.Time) string {
b := make([]byte, 0, 8)
b = binary.BigEndian.AppendUint64(b, uint64(publishedAt.Unix()))
s = lexicographicBase32Encoding.EncodeToString(b)
return strings.TrimLeft(s, '2'); // '2' is all zero bits
}
This will stick with 7 base32 chars until you exceed 35 bits in 3058.Linking to a github repo without any context does not prove your point.
The reason they encode a timestamp (which I learned from the link upthread) is interesting, though. They knew that people use UUIDs as keys in data structures like BTrees, and using a timestamp improves memory/disk locality.
> Non-time-ordered UUID versions such as UUIDv4 have poor database index locality. Meaning new values created in succession are not close to each other in the index and thus require inserts to be performed at random locations. The negative performance effects of which on common structures used for this (B-tree and its variants) can be dramatic.
The GitHub link in question is that of an IETF working group.
https://www.ietf.org/about/introduction/
Granted, linking to the draft of the standard may have been more clear. The draft is in the repo they linked. Direct link to the draft here:
https://ietf-wg-uuidrev.github.io/rfc4122bis/draft-00/draft-...
Alternatively, the currently published draft:
https://www.ietf.org/archive/id/draft-ietf-uuidrev-rfc4122bi...
> squeezing a timestamp in there seems to be a hack with unexpected consequences at best
It’s not a hack. It’s a future standard, and it is going to be widely used.
IETF is an organisation that publishes many of the standards that the internet and computer software is built with. Including publishing a standard for the existing UUID variants in common use today:
> UUIDs are standardized by the Open Software Foundation (OSF) as part of the Distributed Computing Environment (DCE).
> UUIDs are documented as part of ISO/IEC 11578:1996 "Information technology – Open Systems Interconnection – Remote Procedure Call (RPC)" and more recently in ITU-T Rec. X.667 | ISO/IEC 9834-8:2005.
> The Internet Engineering Task Force (IETF) published the Standards-Track RFC 4122, technically equivalent to ITU-T Rec. X.667 | ISO/IEC 9834-8.
https://en.wikipedia.org/wiki/Universally_unique_identifier
UUID was specifically created so that it could support different versions. Some of the bits in UUID identify the version.
Hence, a standard that says how to encode timestamp into UUID, and assigns a version for this format, UUID v7, is in fact the exact opposite of a hack.
And the reason they made UUID v7 is to have chronologically sortable UUIDs. They are inspired/based on ULID format, but made to conform to the UUID format.