Moved
I originally posted this on SoftwareEngineering because that's where this related question was; but having looked into the Help in detail, I think my question is more on-topic for stackoverflow, and I'm moving it there. Will probably delete this question once the move is complete. Sorry I can't move the comments, which have been helpful, along with the question.
Original question
I'm trying to modify a SQLite query (in Android) to return its results in pseudorandom order. As in this question, the order needs to be stable over time (e.g. paging, screen rotation, etc.), so I can't just use ORDER BY RANDOM()
. Instead I want to use a hash function that depends on a couple of input values that provide stability and sufficient uniqueness. One of these values is ROWID of the table, which is a set of integers fairly close together; the other value is more like a session ID, that remains invariant within this query.
According to this well-researched answer, FNV-1 and FNV-1a are simple hash functions with few collisions and good distribution. But as simple as they are, FNV-1 and FNV-1a both involve XOR operations, as well as looping over the bytes of input.
Looping within each row of a query is pretty awkward. One could fake it by unrolling the loop, especially if only a few bytes are involved. I could make do with two bytes, combining LSBs from the two input values (val1 & 255
and val2 & 255
).
XOR isn't supported directly in SQLite. I understand A ^ B
can be implemented as (A | B) - (A & B)
. But the repetition of values, combined with the unrolling of the loop, starts to get unwieldy. Could I just use +
(ignoring overflow) instead of XOR? I don't need very high quality randomness. The order just needs to look random to a casual observer over small-integer scales.
So I'm wondering if anyone has already implemented such a thing. Given how widely used this hash function is, it seems like there would likely already be an implementation for this situation.
Here's my attempt at implementing FNV-1a:
SELECT ..... ORDER BY (((fnvbasis + val1 & 255) * fnvprime) + val2 & 255) * fnvprime;
I'm ignoring the fact that in FNV, the XOR operation (which I've replaced with +
) is only supposed to affect the lowest 8 bits of the hash value. I'm also ignoring any overflow (which I hope just means the upper bits that I don't care about are lost).
For fnvbasis
I'll use 16777619, and for fnvprime
I'll use 2166136261. These are the specified values for 32 bit input, since I don't see a specified value for 16 bit input.
So is this a reasonable way to approximate FNV-1a in a SQLite query? Is there a better, existing implementation? I.e. will it actually produce an ordering that looks pretty random to a casual user, despite my mutilating the operations of the real FNV-1a?
ROWID
can change (for various operations other than CRUD). It is better to use some other columns as input to your hash function. (2) It is easier for your application to compute the FNV1a using the input data and include it as part of the insert/update query. (3) The "extensible" way for SQLite to compute a function that it doesn't natively support would require calling into C. See sqlite3_create_function