我想将任何unicode字符串转换为唯一的数字(在Clojure或Java中)。我希望生成的数字具有以下属性: - 它对于该字符串是唯一的 - 当一组这样的数字被排序并映射回原始字符串时,字符串将按排序顺序出现。这些字符串并非事先都知道。
可以这样做的一种方法是:
(defn strval [^String s]
(bigdec (reduce #(str %1 (format "%05d" (int %2))) "0." s)))
我们可以通过以下方式验证排序顺序是否正确:
(assert (< (strval "a") (strval "b")))
(assert (< (strval "a") (strval "aa")))
(assert (< (strval "aa") (strval "ab")))
(忽略,如果你喜欢“int”不一定是获得单个角色的排序顺序的最佳方式。)
对于那些不熟悉Clojure的人,这个算法:
但是,以这种方式创建BigDecimal的过程是次优的:
除了上述独特性和排序属性外,还有哪些替代功能可以加快速度并使生成的数字更小,同时保留上述的唯一性和排序属性?
注意:解决方案没有生成BigDecimal,它只需要生成一个数字,但我不知道如何使用BigInteger使其工作。此外,我意识到该函数可以被记忆以加速后续执行,但我在初始执行时性能提升。
答案 0 :(得分:8)
一般情况下不可能,但如果事先知道整个字符串的范围,则可能。您要求的是一个保留字典排序顺序的哈希函数。为了做到这一点,散列函数必须为每个可能的字符串产生唯一值 - 即在所有可能的输入上没有冲突的散列函数。在这种情况下,散列值的长度的下限等于输入中信息的位数。
要了解一般情况下这是不可能的原因,请考虑一组长度为1000的随机字符串,仅由[A-Za-z0-9]
组成。每个字母有62个可能的值,称之为6位数据(稍微向上舍入)。因此,可能的不同值的数量约为62 1000 ,或约10 1792 。如何计划在哈希函数中编码这些值?保留顺序以便正确排序"[999 random characters]A"
和"[same 999 random characters]B"
将需要至少6000位的哈希码。
如果事先知道所有可能的字符串,您可以对列表进行排序并按递增顺序分配哈希值,但这可能不是您想要的。
此外,如果字符串的最大长度有界(即所有字符串都小于某个合理的值),则可能能够提供有效的编码。您需要确定编码所有可能值所需的总位数,即
小区(日志<子> 2 子>(
A
L
))
其中L
是字符串的最大长度,A
是字母表的大小,即在最大长度字符串的每个位置可能出现的不同字符的数量。因此,例如,对于最大长度为10和由[A-Z]
组成的字母表,所需的位数将是26 10 的基数2对数,其中向上舍入为48。
设计一个适合最佳48位的保持顺序的哈希可能会非常困难。稍微不太理想的方法是计算每个符号所需的位数,即
小区(日志<子> 2 子>(A))
在你的情况下是5位。将每个8位字节编码为5位,将这些位打包成二进制字符串并将其作为字节流写出。
答案 1 :(得分:0)
此类应用程序由JDK类java.text.CollationKey
涵盖。 CollationKey
表示某些字符串(Unicode)归类顺序。
因此,如果您使用的是Java平台,则可以轻松获取归类键并直接对其进行比较 - 这就是它们的用途:
(def root-collator (java.text.Collator/getInstance java.util.Locale/ROOT))
(defn collation-key [s]
(.getCollationKey root-collator s))
(compare (collation-key "a") (collation-key "b")) ; => -1
CollationKey
有一个toByteArray
方法,它返回表示键的字节数组。由于这些字节数组可以直接相互比较,因此如果必须,可以将它们的内容倒入大整数:
(defn bigint-key [s]
(-> s collation-key .toByteArray bigint))
;; these all pass:
(assert (< (bigint-key "a") (bigint-key "b")))
(assert (< (bigint-key "a") (bigint-key "aa")))
(assert (< (bigint-key "aa") (bigint-key "ab")))
(我不是100%肯定bigint-key
是正确的。整理关键字节数组是无符号的,但java.math.BigInteger
字节数组是二进制补码表示;处理符号的一些工作可能是必要的。)
你强调你对空间/性能有一些限制,所以我不确定这个解决方案是否有用。不过,最好注意JDK中存在诸如CollationKey
之类的东西,并且可以用最少量的代码应用于此问题。
答案 2 :(得分:0)
我不知道Clojure或Java如何处理与C的接口,但这听起来像C标准库的strxfrm函数。也就是说,只有两个字符串都使用相同的LC_COLLATE设置转换后,strxfrm的结果才有效。换句话说,将德语单词与法语单词进行比较是没有意义的,因为这些语言对于如何对单词进行排序有不同的规则。
如果您可以将排序规则与字节字符串比较一起使用(涵盖了我所需要的所有时间),那么strxfrm就是您所需要的。但是,如果您真的需要数值比较,则必须做更多的事情。
如果您确实需要数字比较,则必须使用任意精度的整数(例如Java的BigInteger;不需要BigDecimal)。毕竟,您不能将两个七个七个字符的字符串作为64位整数进行比较(根据信鸽原理)。
在这种情况下,最好的选择是将结果字节字符串解释为任意精度的大端数字。换句话说,如果字节字符串的长度为7个字节,则需要将生成的数字构建为(byte_string[0]<<64) + (byte_string[1]<<56) + … + (byte_string[6]<<0)
(其中每个字节向左移动总长度减去其位置* 8位)。
我实际上没有遇到过这样一种情况,那就是像这样在您所要做的那样,可以将字符串转换为保留排序规则的任意精度数字是有用的。通常,我发现我需要将Unicode字符串转换为字节字符串,以在类似memcmp的比较中保留排序规则。但是,当然可能有些数据库层需要您的要求(大概是在底层使用Elias伽玛编码等任意精度数字)。如果这是您所需要的,那么可能需要使用strxfrm,然后再使用任意精度的big-endian解释(如我在此处所述)。