将字符串转换为独特的整数哈希

28

我正在尝试开发一个系统,可以将我的字符串转换成唯一的整数值,例如单词“account”的加密数字值为0891,没有其他单词可以使用相同的转换过程转换为0891。这个系统不需要能够将生成的整数转换回字符串。

同时,它将依赖于单词结构规则,例如“accuracy”和“announcement”这样的单词将具有大于0891的生成数字,而“a”、“abacus”和“abbreviation”这样的单词将具有小于0891的生成数字。

此应用的目的类似于索引或主键。我之所以不使用递增索引是出于安全目的,因为索引依赖于数据集中的数据数量。

(例如)

[0] A, [1] B, [2] C, [3] D, [4] E, [5] F

上述字母各有对应的索引,E的索引为4

然而,如果数据突然增加或减少,那么将进行排序

[0] A, [1] AA, [2] AAB, [3] C, [4] D, [5] DA, [6] DZ, [7] E, [8] F

E现在的索引为7

每个单词必须有一个独特的整数等价值,并对应相应的权重。

我需要知道是否存在一种可以实现上述要求的算法。

任何帮助都将不胜感激。


3
除非你设定一个最大单词长度,否则这是不可能的。(即使你设定了单词长度限制,我仍然不能确定)。 - Roger Rowland
2
我想说的是,如果你想要安全性,就应该放弃对“单词结构规则”的依赖。这样的要求已经让攻击者的工作变得更加容易了。 - UmNyobe
1
UmNyobe所说的加上,您应该接受冲突。索引通常会有冲突,只要它们是例外而不是规则,这没有任何问题。 - Damon
1
请注意,如果您想将某些内容转换为其他内容,而没有返回原始值的选项,则应使用哈希而不是加密。您的问题在于大多数哈希算法存在将不同的输入返回相同哈希的可能性。特别是如果输出必须是整数值,则这种情况很可能发生。 - pyrocumulus
2
@marcolopes:只有2^32个可能的整数哈希码。有比2^32个字符串更多的可能性,因此String.hashCode()保证为多个字符串生成相同的哈希码。 - Jim Mischel
显示剩余3条评论
9个回答

14

在您给出的限制条件下,除非您施加最大长度,否则不可能实现此目标。

假设k("a")k("b")是这两个字符串的代码。

根据您的限制条件,您要寻找一个唯一的整数,它在这两个值之间,但是k("a") < k("a....a") < k("b")。由于有无限多个样式为"a....a"(以及"akjhdsfkjhs")的字符串需要适合这两个代码之间,因此对于任意长度的字符串而言,不存在既有顺序又是通用、唯一且固定长度的编码。因为您需要与字符串数量一样多的整数,而字符串长度没有上限,所以这种方式行不通。

放弃通用(因此不允许插入新字符串)、唯一(允许碰撞-例如使用前四个字母作为代码!)、无限长度(例如3个字符)或有序保持属性中的任何一个。


13

为了简单起见,我假设单词中只允许包含从az的字符。

我们将为长度为2的字符串分配数字:

String Value
a      0
aa     1
ab     2
...
az     26
b      27
ba     28
bb     29
...
bz     53
c      54
...

现在,仅从这个角度来看,您应该能够欣赏到,要确定任何给定短字符串的偏移量,您需要使用允许的最大长度。假设我们知道这个数字。

为了算法简单起见,我们希望从27开始:(如果您想尝试从0开始计算,请注意您将需要一些特殊情况)

String Value
a      27
aa     28
ab     29
...

因此,基本上最左侧的字符会贡献一个值27*(1-26) (对于a-z),右侧紧随其后的字符(如果存在)会为字符串的值贡献1-26 (对于a-z)。

现在可以将其概括为以下规律,即最左侧的数字会贡献(1-26)*27^(len-1),接下来的数字会贡献(1-26)*27^(len-2),依此类推,直到(1-26)*27^0

这导致我写了一些Java代码:

long result = 0;
for (int i = 0; i < s.length(); i++)
   result += pow(27, MAX_LENGTH - i - 1)*(1 + s.charAt(i) - 'a');

测试输出:

a                    =   150094635296999121
aa                   =   155653695863554644
aaa                  =   155859586995649293
aaaa                 =   155867212593134280
aaaaa                =   155867495022670761
abacus               =   161447654121636735
abbreviation         =   161763445236432690
account              =   167509959568845165
accuracy             =   167554723653128367
announcement         =   230924421746611173
z                    =  3902460517721977146

在线演示

是的,这些对于长度为13的字符串来说是相当大的数字,但是,如果没有给一个实际字典中的单词顺序逐个分配号码,你就无法做得更好(除非你从0开始,那相对而言只有一点点差别),因为有那么多的字母序列的可能性。


4
为了保证唯一性,可以从为字母分配质数开始: A -> 2, B -> 3, C -> 5, D -> 7等。
要计算单词中给定字母的“密钥”,需要将该质数提高到单词中的位置索引的幂。要获取整个单词的“密钥”,请将所有字母密钥相乘。
例如,单词CAB:
C -> 5 ^ 1 = 5
A -> 2 ^ 2 = 4
B -> 3 ^ 3 = 81
CAB -> 5 * 4 * 81 =  1620.

没有其他单词可以将1620作为密钥。

注意:您不必以A -> 2开始,也不必按顺序为字母字符分配质数,只要跟踪映射即可。还要记住,这种方法的结果会非常快地变大。

但请记住有关安全性的其他评论-这并不是特别安全的算法。


1
同样的答案和反例:hash('abba') == hash("baab") - Thomash
它是否按照要求保留顺序?据我所知,您的顺序是不正确的:B < AB。 - Erich Schubert
就Python 2.7而言,“abba”和“baab”的哈希值不相等。我想知道它们在做什么。 - netskink

2

如果这些整数可以占用任意数量的字节,则每个字符的底层(例如Ascii)字节代码将为您提供一个整数表示。同样,将0 = A,1 = B分配到Z = 25,然后单词本身是26进制中的整数。


这个程序如何处理字符串 "10020" 和 "100C0"? - ruipacheco

1

为每个字母分配一个唯一的质数值,按照递增顺序(顺序不必要)。

请注意:由于质数相乘的结果是唯一的,只能被这些数字相乘,它将为每个单词提供唯一的值。

算法:

int hash = 0;
forEach (int i = 0 ; i < word.length ; i++)
{ 
   hash *= (prime[c[i]] ** (length - i)); 
}

质数 - 一个数组,用于存储与每个幂(长度-1)对应的质数值,以给出该字符出现的位置的值,以保持字典顺序。

这个算法将给出足够大的值,将会超出您的数组范围。

此外:长度较小的单词可能会比一些长度较大的单词具有更低的值,这可能会影响您的字典顺序,但我不确定为什么您想要字典顺序,因为这里的唯一性将得到保持。


1
反例:hash('abba') == hash("baab") - Thomash
此外,它并不可扩展。你很快会用完整数,而且它也不能保持顺序:hash('b') < hash('ab'),是吧? - Erich Schubert
@ErichSchubert 顺便说一句,尽管编程语言中的整数类型通常是有限的,但整数是无限多的。这会跳过多少整数可能是一个好问题。 - Bernhard Barker
任意两个整数之间并不会有无限多的“中间值”。但是在 ab 之间却有无限多的字符串。所以,除非你要将无穷大赋给 b,否则你就失败了。此外,我敢打赌问题的作者想要的是有限的整数哈希码。 - Erich Schubert
1
我在我的答案中已经提到,对于非常大的字符串,它很可能会产生非常大的数字。此外,长度较小的单词可能比长度较大的单词具有较小的哈希码。但是,hash('abba') != hash('baab')。假设a=2,b=3。然后,hash("abba")=23 * 32 * 3*1 *2 = 432,而hash('baab')=3**2 * 22 * 21 * 3 = 648。这些是不同的数字。质数的乘积将给出唯一值,这些唯一值只能由这些质数获得。 - Rahul
1
首先,使用初始哈希值为0的“hash *=”将始终为零。也许你的意思是1。其次,在for循环中,“(length-i)”部分从长度到1,例如从4到1。因此,hash(“abba”)将是2^4 * 3^3 * 3^2 * 2^1 = 7776,而bash(“baba”)将是3^4 * 2^3 * 2^2 * 3^1 = 7776。 - Hejazzman

1

是的,但大多数情况下不行。

当设置一个26进制(或ASCII码的128进制)时,理论上可以唯一地哈希每个字符串,这就是Stochastically的回答所说的“是的”。

另一方面,这是不切实际的。不仅数字会变得过大,超出大多数语言的处理能力,而且这也可能是一个非常耗费资源的过程。此外,如果允许字符串无限长,则可以应用Cantor's diagonal argument的形式,也会“破坏”此算法。无法创建一个基数为aleph-null(整数)的集合与基数为aleph-one(字符串)的集合之间的一对一映射。


1
你可以这样做:
SEPARETOR = '000'
string_to_hash = "some_string"
hashed_result = int(SEPARETOR.join(list(str(ord(character)) for character in string_to_hash)))

享受吧!


1

字符串 s 的一般形式函数,长度为 n

hashCode(s) = s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

其中^表示指数。由于Java使用32位整数来保存哈希值,所有的值都应该保持为32位。

如果你想要将字符串哈希成小整数,可以使用以下的C#代码:

int StringToIntegerHash(string str)
{
  int hash = 0;
  str = GetTicketHash(str);
  for(int i=0; i<str.Length;i++)
  {
     hash +=(int) ((int)str[i]) * Math.Pow(2, str.Length - i);
  }
  return hash;
}





string GetTicketHash(string str)
{
   const string chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
   byte[] bytes = Encoding.UTF8.GetBytes(str);

   SHA256Managed hashstring = new SHA256Managed();
   byte[] hash = hashstring.ComputeHash(bytes);

   char[] hash2 = new char[16];

   // Note that here we are wasting bits of hash! 
   // But it isn't really important, because hash.Length == 32
   for (int i = 0; i < hash2.Length; i++)
   {
     hash2[i] = chars[hash[i] % chars.Length];
   }

   return new string(hash2);
 }

0
我会将字符串转换为字节数组,然后再将其转换为数字。这里是一个 PowerShell 示例代码:
$string = "test"

# convert string into byte-array:
$enc = [System.Text.Encoding]::UTF8
$arr = $enc.GetBytes($string)

# convert byte-array into number:
$hexbin = [System.Runtime.Remoting.Metadata.W3cXsd2001.SoapHexBinary]::new()
$hexbin.Value = $arr
$result = $hexbin.ToString()
write-host $result

当然,你可以选择任何其他/更短的转换方式,比如基于26进制等,但这会使编码变得更加复杂和缓慢。
顺便提一下,如果你想将字符串转换为数字以便在数据库中进行更快的比较,请记住大多数数据库已经在内部对字符串进行了哈希处理。不需要进行任何其他的微调。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接