Search code examples
javascriptclojureclojurescript

Replicating Java's String.hashCode() method for a cljc project


I have a project which is supposed to work both as a clj and cljs project, it currently uses Java String's hashCode for hashing and I can't change this. I need to find a way to implement the same solution for the cljs scenarios.

I have the required code for the javascript implemnetation of the same hashing process and I think ideally I'd like to use this function for both clj and cljs scenarios as it is the fastest way of hashing this specific way that I know of.

hashCode = function(stringToHash){
    let hash = 0;
    if (stringToHash.length === 0) return hash;
    let char;
    for (i = 0; i < p1.length; i++) {
        char = stringToHash.charCodeAt(i);
        hash = ((hash << 5) - hash) + char;
        hash = hash & hash; // Convert to 32bit integer
    }
    return hash;
};

The other option is to recreate the hashing algorithm in pure clojure but I think this would result in worse performance. So my question is either how to either properly require this one js method into my cljc project or how to recreate the same algorithm in clojure.

Here's my `hashCode.js

goog.provide('hashCode');

/**
 * @param {string} stringToHash
 * @return {number}
 */
hashCode.hashCode = function(stringToHash){
    let hash = 0;
    if (stringToHash.length === 0) return hash;
    let char;
    for (i = 0; i < p1.length; i++) {
        char = stringToHash.charCodeAt(i);
        hash = ((hash << 5) - hash) + char;
        hash = hash & hash; // Convert to 32bit integer
    }
    return hash;
};

which I've tried requiring in my core.cljc file like so [hashCode :as hs] to no avail.


Solution

  • Your hashCode.js is a Closure lib, so you can use the :libs compiler option to access it. This is described in more detail on the Dependencies page.

    (Note that there is a typo in your source; p1 should be replaced with stringToHash.)

    Here is an example using :libs to access your implementation:

    $ clj -m cljs.main -co '{:libs ["hashCode.js"]}' -r
    ClojureScript 1.10.520
    cljs.user=> (require '[hashCode :as hs])
    nil
    cljs.user=> (hs/hashCode "hello")
    99162322
    cljs.user=> (hs/hashCode "abcdef")
    -1424385949
    

    (Note that if you use :advanced :optimizations, since your code makes use of let, you would also need to specify :language-in as :es6.)

    A fairly direct conversion of that implementation to ClojureScript is:

    (defn string-hash [s]
       (loop [i 0 h 0]
         (if (< i (.-length s))
           (let [c (.charCodeAt s i)
                 h (+ (- (bit-shift-left h 5) h) c)]
              (recur (inc i) (bit-and h h)))
           h)))
    

    While Closure Library ships with a string hashCode implementation that is documented as being similar to Java's, it may not be suitable for your use, as it doesn't generate signed hash values:

    cljs.user=> (goog.string/hashCode "hello")
    99162322
    cljs.user=> (goog.string/hashCode "abcdef")
    2870581347