从书名中生成假的ISBN? (或者:如何将字符串散列为6位数字ID)

时间:2012-10-11 07:55:12

标签: string hash barcode isbn

简短版本:如何将任意字符串转换为最小碰撞的6位数字?

长版:

我正在使用一个小型图书馆,里面有一堆没有ISBN的图书。这些通常是来自小型出版商的较旧的,绝版的标题,这些出版商从未获得过ISBN,我想为他们生成假的ISBN来帮助他们进行条形码扫描和贷款。

从技术上讲,真实的ISBN由商业实体控​​制,但可以使用该格式来指定不属于真实发布者的数字(因此不应导致任何冲突)。

格式如下:

978-0-01-######-?

给你6个数字,从000000到999999,用?最后是校验和。

是否可以在此方案中将任意书名转换为6位数字,并且碰撞的可能性极小?

2 个答案:

答案 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,在这种情况下,您可以忽略此段落。)