(从https://softwareengineering.stackexchange.com/questions/406813/how-to-implement-fnv-1a-in-sqlite移动]
我正在尝试修改SQLite查询(在Android中)以伪随机顺序返回其结果。与this question中一样,顺序必须在重复查询时保持稳定(例如,由于页面调度,屏幕旋转等),因此我不能只使用ORDER BY RANDOM()
。相反,我想使用一个哈希函数,该函数依赖于提供稳定性和足够唯一性的几个输入值。 (这些值之一是表的唯一ID列,它是一组非常接近的整数;另一个值更像是会话ID,也是一个整数,在此查询中保持不变。)
根据this well-researched answer,FNV-1和FNV-1a是简单的哈希函数,冲突少且分布良好。但是,尽管非常简单,但FNV-1和FNV-1a都涉及XOR操作,以及循环输入字节。
在查询的每一行中循环非常尴尬。可以通过展开循环来伪造它,特别是如果只涉及几个字节的话。我可以处理两个字节,将来自两个输入值(val1 & 255
和val2 & 255
)的LSB组合在一起。
XOR在SQLite中不直接支持。我了解A ^ B
可以实现为(A | B) - (A & B)
。但是值的重复,再加上循环的展开,开始变得笨拙。我可以只使用+
(忽略溢出)而不是XOR吗?我不需要非常高质量的随机性。对于偶然的观察者,该顺序只需要在小整数范围内随机观察即可。
所以我想知道是否有人已经实现了这样的事情。考虑到widely used this hash function is的方式,似乎很可能已经有这种情况的实现。
这是我实施FNV-1a的尝试:
SELECT ..... ORDER BY (((fnvbasis + val1 & 255) * fnvprime) + val2 & 255) * fnvprime % range;
我忽略了这样一个事实,在FNV中,XOR操作(我已经用+
代替)只应该影响哈希值的最低8位。我也忽略了任何溢出(我希望这只是意味着我不在乎的高位丢失了)。
对于fnvbasis
,我将使用16777619,对于fnvprime
,我将使用2166136261。这些是32位输入的指定值,因为我看不到16位输入的指定值。对于range
,我将使用一个质数,该质数大于此查询返回的预期行数。
那么,这是在SQLite查询中近似FNV-1a的合理方法吗?是否有更好的现有实施方案?即尽管我破坏了真正的FNV-1a的操作,但它实际上是否会产生对休闲用户来说看起来非常随机的排序?
[受到rwong和GrandmasterB对the previous attempt at this question before I moved it的评论的启发,我决定可以预先计算FNV-1a循环的第一个迭代,即基于表的唯一ID列的哈希。预先计算的列fnv1a_step1
可以设置为
(fnvbasis + ID & 255) * fnvprime
甚至
(fnvbasis + RANDOM() & 255) * fnvprime
此值永远不会改变。可以使用当前会话ID在查询的ORDER BY子句中非常简单地计算FNV-1a循环的第二次迭代,因此它会为每个会话产生不同但稳定的排序:
ORDER BY (fnv1a_step11 + sessionId & 255) * fnvprime % range;