简短版本:如何将任意字符串转换为最小碰撞的6位数字?
长版:
我正在使用一个小型图书馆,里面有一堆没有ISBN的图书。这些通常是来自小型出版商的较旧的,绝版的标题,这些出版商从未获得过ISBN,我想为他们生成假的ISBN来帮助他们进行条形码扫描和贷款。
从技术上讲,真实的ISBN由商业实体控制,但可以使用该格式来指定不属于真实发布者的数字(因此不应导致任何冲突)。
格式如下:
978-0-01-######-?
给你6个数字,从000000到999999,用?最后是校验和。
是否可以在此方案中将任意书名转换为6位数字,并且碰撞的可能性极小?
答案 0 :(得分:1)
在使用making a fixed-length hash的代码段并计算ISBN-13 checksum之后,我设法创建了非常难看的C#代码,似乎可行。它将采用任意字符串并将其转换为有效(但假的)ISBN-13:
public int GetStableHash(string s)
{
uint hash = 0;
// if you care this can be done much faster with unsafe
// using fixed char* reinterpreted as a byte*
foreach (byte b in System.Text.Encoding.Unicode.GetBytes(s))
{
hash += b;
hash += (hash << 10);
hash ^= (hash >> 6);
}
// final avalanche
hash += (hash << 3);
hash ^= (hash >> 11);
hash += (hash << 15);
// helpfully we only want positive integer < MUST_BE_LESS_THAN
// so simple truncate cast is ok if not perfect
return (int)(hash % MUST_BE_LESS_THAN);
}
public int CalculateChecksumDigit(ulong n)
{
string sTemp = n.ToString();
int iSum = 0;
int iDigit = 0;
// Calculate the checksum digit here.
for (int i = sTemp.Length; i >= 1; i--)
{
iDigit = Convert.ToInt32(sTemp.Substring(i - 1, 1));
// This appears to be backwards but the
// EAN-13 checksum must be calculated
// this way to be compatible with UPC-A.
if (i % 2 == 0)
{ // odd
iSum += iDigit * 3;
}
else
{ // even
iSum += iDigit * 1;
}
}
return (10 - (iSum % 10)) % 10;
}
private void generateISBN()
{
string titlehash = GetStableHash(BookTitle.Text).ToString("D6");
string fakeisbn = "978001" + titlehash;
string check = CalculateChecksumDigit(Convert.ToUInt64(fakeisbn)).ToString();
SixDigitID.Text = fakeisbn + check;
}
答案 1 :(得分:0)
6位数允许大约10M的可能值,这对于大多数内部使用来说应该足够了。 在这种情况下,我会使用序列,因为6位校验和具有相对较高的碰撞机会。
因此,您可以将所有字符串插入到散列中,并在索引后使用索引编号作为ISBN,或者在排序后使用它。
这应该使碰撞几乎不可能,但它需要保留一些“已分配”的ISBN以避免将来发生冲突,并保留已经存储的标题列表,但这是您最有可能想要保留的信息。
另一种选择是打破ISBN标准并使用十六进制/ uuencoded条形码,这可能会将可能的范围增加到可能使用截断到适合的加密哈希的点。
我建议,既然你正在处理可能有多个版本大写和标点不同的旧书籍,我会删除标点符号,重复的空格并在比较之前将所有内容转换为小写,以尽量减少技术复制的可能性字符串是不同的(除非您希望不同的版本具有不同的ISBN,在这种情况下,您可以忽略此段落。)