在javascript中散列字符串数组

时间:2014-08-03 12:02:16

标签: javascript arrays lookup string-hashing

只是想知道是否还有其他方法。

var hashStringArray = function(array) {
    array.sort();
    return array.join('|');
};

我不喜欢排序太多,如果它包含在其中一个字符串中,那么使用该分隔符也不安全。总的来说,无论字符串的顺序如何,我都需要产生相同的哈希值。它将是相当短的数组(最多10个项目),但它将经常被要求所以它不应该太慢。

我打算将它与ES6 Map对象一起使用,我需要轻松找到相同的数组集合。

更新了使用示例

var theMap = new Map();
var lookup = function(arr) {
    var item = null;
    var hashed = hashStringArray(arr);
    if (item = theMap.get( hashed )) {
        return item;
    }
    theMap.set( hashed, itemBasedOnInput );
    return itemBasedOnInput;
}

var arr1 = ['alpha','beta','gama'];
var arr2 = ['beta','alpha','gama'];

lookup(arr1) === lookup(arr2)

性能测试

http://jsperf.com/hashing-array-of-strings/5

4 个答案:

答案 0 :(得分:1)

作为解决方案的基础,我发现了两件事:

  1. 求和并不取决于顺序,这实际上是简单校验和中的一个缺陷(它们不能捕捉到单词中块顺序的变化),

  2. 我们可以使用他们的字符代码将字符串转换为可汇总数字

  3. 这是一个可以做(2)的功能:

    charsum = function(s) {
      var i, sum = 0;
      for (i = 0; i < s.length; i++) {
        sum += (s.charCodeAt(i) * (i+1));
      }
      return sum
    }
    

    这是(1)的一个版本,它通过对charsum值求和来计算数组哈希:

    array_hash = function(a) {
      var i, sum = 0
      for (i = 0; i < a.length; i++) {
        var cs = charsum(a[i])
        sum = sum + (65027 / cs)
      }
      return ("" + sum).slice(0,16)
    }
    

    在这里小提琴:http://jsfiddle.net/WS9dC/11/

    如果我们对charsum值进行了直接求和,那么数组[&#34; a&#34;,&#34; d&#34;]将具有与数组相同的哈希值[&#34; b& #34;,&#34; c&#34;] - 导致不希望的碰撞。所以基于使用非UTF字符串,其中charcodes最多为255,并且在每个字符串中允许255个字符,那么charsum的最大返回值是255 * 255 = 65025.所以我选择了下一个素数,65027,并使用(65027 / cs)来计算哈希值。我并不是100%确信这可以消除碰撞......或许需要更多的思考......但它肯定会修复[a,d]与[b,c]的情况。 测试:

    var arr1 = ['alpha','beta','gama'];
    var arr2 = ['beta','alpha','gama'];
    
    console.log(array_hash(arr1))
    console.log(array_hash(arr2))
    console.log(array_hash(arr1) == array_hash(arr2))
    

    输出:

    443.5322979371356 
    443.5322979371356
    true 
    

    测试一个显示不同哈希值的案例:

    var arr3 = ['a', 'd'];
    var arr4 = ['b', 'c'];
    
    console.log(array_hash(arr3))
    console.log(array_hash(arr4))
    console.log(array_hash(arr3) == array_hash(arr4))
    

    输出:

    1320.651443298969
    1320.3792001649144
    false 
    

    修改

    这是一个修订版本,它会忽略数组中的重复项,并仅根据唯一项返回哈希:

    http://jsfiddle.net/WS9dC/7/

    array_hash = function(a) {
      var i, sum = 0, product = 1
      for (i = 0; i < a.length; i++) {
        var cs = charsum(a[i])
        if (product % cs > 0) {
          product = product * cs
          sum = sum + (65027 / cs)  
        }
      }
      return ("" + sum).slice(0, 16)
    }
    

    测试:

    var arr1 = ['alpha', 'beta', 'gama', 'delta', 'theta', 'alpha', 'gama'];
    var arr2 = ["beta", "gama", "alpha", "theta", "delta", "beta"];
    
    console.log(array_hash(arr1))
    console.log(array_hash(arr2))
    console.log(array_hash(arr1) === array_hash(arr2))
    

    返回:

    689.878503111701
    689.878503111701
    true 
    

    修改

    我修改了上面的答案,以解释具有相同字母的单词数组。我们需要这些来返回不同的哈希值,他们现在这样做:

    var arr1 = ['alpha', 'beta']
    var arr2 = ['alhpa', 'ateb'] 
    

    修复是基于char索引为charsum func添加一个乘数:

    sum += (s.charCodeAt(i) * (i+1));
    

答案 1 :(得分:0)

你可以这样做:

var hashStringArray = function(array) {
    return array.sort().join('\u200b');
};

\u200b字符是一个unicode字符,也意味着null,但与使用最广泛的\0字符不同。

'\u200b' == '\0'

> false

答案 2 :(得分:0)

如果您为每个字符串计算数字哈希码,那么您可以将它们与顺序无关紧要的运算符组合,例如^ XOR运算符,那么您不需要对数组进行排序:

function hashStringArray(array) {
  var code = 0;
  for (var i = 0; i < array.length; i++) {
    var n = 0;
    for (var j = 0; j < array[i].length; j++) {
      n = n * 251 ^ array[i].charCodeAt(j);
    }
    code ^= n;
  }
  return code
};

答案 3 :(得分:0)

如果你的可能字符串长度少于32个项目,那么拥有非常快速哈希的想法:使用内置哈希函数对字符串进行哈希处理,该函数将返回2的幂作为哈希​​:

function getStringHash(aString) {
   var currentPO2 = 0;
   var hashSet = [];
   getStringHash = function ( aString) {
       var aHash = hashSet[aString];
       if (aHash) return aHash;
       aHash = 1 << currentPO2++;
       hashSet[aString] = aHash; 
       return aHash;
   }
   return getStringHash(aString);
}

然后在字符串数组上使用此哈希,或者哈希(|):

function getStringArrayHash( aStringArray) {
    var aHash = 0;
    for (var i=0; i<aStringArray.length; i++) {
        aHash |= getStringHash(aStringArray[i]);
    }
    return aHash;
}

所以要测试一下:

console.log(getStringHash('alpha'));  // 1
console.log(getStringHash('beta'));   // 2
console.log(getStringHash('gamma'));  // 4
console.log(getStringHash('alpha'));  // 1 again

var arr1 = ['alpha','beta','gama'];
var arr2 = ['beta','alpha','gama'];
var arr3 = ['alpha', 'teta'];

console.log(getStringArrayHash(arr1)); // 11
console.log(getStringArrayHash(arr2)); // 11 also, like for arr1

var arr3 = ['alpha', 'teta'];
console.log(getStringArrayHash(arr3)); // 17 : a different array has != hashset

jsbin在这里:http://jsbin.com/rozanufa/1/edit?js,console

RQ !!!使用此方法,数组被视为set,这意味着重复的项不会更改数组的哈希值!!!

这个HAS要快,因为它只使用1)函数调用2)查找3)整数运算。 所以没有排序,没有(长)字符串,没有连续。

jsperf证实: http://jsperf.com/hashing-array-of-strings/4

enter image description here

编辑:

带有素数的

版本,在这里:http://jsbin.com/rozanufa/3/edit?js,console

        // return the unique prime associated with the string.
    function getPrimeStringHash(aString) {
       var hashSet = [];
       var currentPrimeIndex = 0;
       var primes = [ 2, 3, 5, 7, 11, 13, 17 ];
       getPrimeStringHash = function ( aString) {
           var aPrime = hashSet[aString];
           if (aPrime) return aPrime;
           if (currentPrimeIndex == primes.length) aPrime = getNextPrime();
           else aPrime = primes[currentPrimeIndex]; 
           currentPrimeIndex++
           hashSet[aString] = aPrime; 
           return aPrime;
       };
       return getPrimeStringHash(aString);
       // compute next prime number, store it and returns it.
       function getNextPrime() {
         var pr = primes[primes.length-1];
         do {
             pr+=2;
             var divides = false;
             // discard the number if it divides by one earlier prime.
             for (var i=0; i<primes.length; i++) {
                 if ( ( pr % primes[i] ) == 0 ) {
                     divides = true;
                     break;
                 }
             }
          } while (divides == true)
          primes.push(pr);
         return pr;
        }
    }

    function getStringPrimeArrayHash( aStringArray) {
        var primeMul = 1;
        for (var i=0; i<aStringArray.length; i++) {
            primeMul *= getPrimeStringHash(aStringArray[i]);
        }
        return primeMul;
    }

    function compareByPrimeHash( aStringArray, anotherStringArray)  {
        var mul1 = getStringPrimeArrayHash ( aStringArray ) ;
        var mul2 = getStringPrimeArrayHash ( anotherStringArray ) ;
        return  ( mul1 > mul2 ) ? 
                                   ! ( mul1 % mul2 ) 
                                 : ! ( mul2 % mul1 );
      // Rq : just test for mul1 == mul2 if you are sure there's no duplicates
    }

测试:

console.log(getPrimeStringHash('alpha'));  // 2
console.log(getPrimeStringHash('beta'));   // 3
console.log(getPrimeStringHash('gamma'));  // 5
console.log(getPrimeStringHash('alpha'));  // 2 again
console.log(getPrimeStringHash('a1'));  // 7 
console.log(getPrimeStringHash('a2'));  // 11


var arr1 = ['alpha','beta','gamma'];
var arr2 = ['beta','alpha','gamma'];
var arr3 = ['alpha', 'teta'];
var arr4 = ['alpha','beta','gamma', 'alpha']; // == arr1 + duplicate 'alpha'

console.log(getStringPrimeArrayHash(arr1)); // 30
console.log(getStringPrimeArrayHash(arr2)); // 30 also, like for arr1

var arr3 = ['alpha', 'teta'];
console.log(getStringPrimeArrayHash(arr3)); // 26 : a different array has != hashset

console.log(compareByPrimeHash(arr1, arr2) ); // true
console.log(compareByPrimeHash(arr1, arr3) ); // false
console.log(compareByPrimeHash(arr1, arr4) ); // true despite duplicate