有效地将任何字符串映射到表示字符串的字典顺序的唯一数字

时间:2016-03-24 03:32:40

标签: java string sorting hash clojure

我想将任何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的人,这个算法:

  1. 将字符串转换为字符序列
  2. 获取一个字符的整数值
  3. 将此整数转换为字符串并用零填充它,以便它生成一个包含五个字符的字符串。
  4. 将此字符串追加到以“0”开头的结果字符串。
  5. 如果有更多字符,请返回步骤2,否则
  6. 将结果字符串转换为Java BigDecimal
  7. 但是,以这种方式创建BigDecimal的过程是次优的:

    1. 它依赖于数字和字符串之间的转换,然后再回到最终的数字。
    2. 用零填充每个值不会产生最紧凑的表示。
    3. 除了上述独特性和排序属性外,还有哪些替代功能可以加快速度并使生成的数字更小,同时保留上述的唯一性和排序属性?

      注意:解决方案没有生成BigDecimal,它只需要生成一个数字,但我不知道如何使用BigInteger使其工作。此外,我意识到该函数可以被记忆以加速后续执行,但我在初始执行时性能提升。

3 个答案:

答案 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解释(如我在此处所述)。