[Question]
  [
In this [code-challenge](/questions/tagged/code-challenge "show questions tagged 'code-challenge'") you will write a hash function in 140 bytes1 or less of source code. The hash function must take an ASCII string as input, and return a 24-bit unsigned integer ([0, 224-1]) as output.
Your hash function will be evaluated for every word [in this large British English dictionary](https://gist.githubusercontent.com/orlp/3ff520a34ad74d547557e761d92be470/raw/british-english-huge.txt)2. Your score is the amount of words that share a hash value with another word (a collision).
### The lowest score wins, ties broken by first poster.
## Test case
Before submitting, please test your scoring script on the following input:
```
duplicate
duplicate
duplicate
duplicate
```
If it gives any score other than 4, it is buggy.
---
Clarifying rules:
1. Your hash function must run on a single string, not a whole array. Also, your hash function may not do any other I/O than the input string and output integer.
2. Built-in hash functions or similar functionality (e.g. encryption to scramble bytes) is disallowed.
3. Your hash function must be deterministic.
4. Contrary to most other contests optimizing specifically for the scoring input is allowed.
1 I am aware Twitter limits characters instead of bytes, but for simplicity we will use bytes as a limit for this challenge.  
2 Modified from Debian's [wbritish-huge](https://packages.debian.org/sid/wbritish-huge), removing any non-ASCII words.
  
[Answer]
# Python, 5333 4991
I believe this is the first contender to score significantly better than a random oracle.
```
def H(s):n=int(s.encode('hex'),16);return n%(8**8-ord('+%:5O![/5;QwrXsIf]\'k#!__u5O}nQ~{;/~{CutM;ItulA{uOk_7"ud-o?y<Cn~-`bl_Yb'[n%70]))
```
[Answer]
# Python 2, 140 bytes, 4266 colliding words
I didn’t really want to start with the non-printable bytes thing given their unclear tweetability, but well, I didn’t start it. :-P
```
00000000: efbb bf64 6566 2066 2873 293a 6e3d 696e  ...def f(s):n=in
00000010: 7428 732e 656e 636f 6465 2827 6865 7827  t(s.encode('hex'
00000020: 292c 3336 293b 7265 7475 726e 206e 2528  ),36);return n%(
00000030: 382a 2a38 2b31 2d32 3130 2a6f 7264 2827  8**8+1-210*ord('
00000040: 6f8e 474c 9f5a b49a 01ad c47f cf84 7b53  o.GL.Z........{S
00000050: 49ea c71b 29cb 929a a53b fc62 3afb e38e  I...)....;.b:...
00000060: e533 7360 982a 50a0 2a82 1f7d 768c 7877  .3s`.*P.*..}v.xw
00000070: d78a cb4f c5ef 9bdb 57b4 7745 3a07 8cb0  ...O....W.wE:...
00000080: 868f a927 5b6e 2536 375d 2929            ...'[n%67]))
```
# Python 2, 140 printable bytes, ~~4662~~ ~~4471~~ 4362 colliding words
```
def f(s):n=int(s.encode('hex'),16);return n%(8**8+3-60*ord('4BZp%(jTvy"WTf.[Lbjk6,-[LVbSvF[Vtw2e,NsR?:VxC0h5%m}F5,%d7Kt5@SxSYX-=$N>'[n%71]))
```
Inspired by the form of kasperd’s solution, obviously—but with the important addition of an affine transformation on the modulus space, and entirely different parameters.
[Answer]
# CJam, ~~4125~~ ~~3937~~ ~~3791~~ 3677
```
0000000: 7b 5f 39 62 31 31 30 25 5f 22 7d 13 25 77  {_9b110%_"}.%w
000000e: 77 5c 22 0c e1 f5 7b 83 45 85 c0 ed 08 10  w\"...{.E.....
000001c: d3 46 0c 5c 22 59 f8 da 7b f8 18 14 8e 4b  .F.\"Y..{....K
000002a: 3a c1 9e 97 f8 f2 5c 18 21 63 13 c8 d3 86  :.....\.!c....
0000038: 45 8e 64 33 61 50 96 c4 48 ea 54 3b b3 ab  E.d3aP..H.T;..
0000046: bc 90 bc 24 21 20 50 30 85 5f 7d 7d 59 2c  ...$! P0._}}Y,
0000054: 4a 67 88 c8 94 29 1a 1a 1a 0f 38 c5 8a 49  Jg...)....8..I
0000062: 9b 54 90 b3 bd 23 c6 ed 26 ad b6 79 89 6f  .T...#..&..y.o
0000070: bd 2f 44 6c f5 3f ae af 62 9b 22 3d 69 40  ./Dl.?..b."=i@
000007e: 62 31 35 32 35 31 39 25 31 31 30 2a 2b 7d  b152519%110*+}
```
This approach divides domain *and codomain* into 110 disjoint sets, and defines a slightly different hash function for each pair.
### Scoring / Verification
```
$ echo $LANG
en_US
$ cat gen.cjam
"qN%{_9b110%_"
[125 19 37 119 119 34 12 225 245 123 131 69 133 192 237 8 16 211 70 12 34 89 248 218 123 248 24 20 142 75 58 193 158 151 248 242 92 24 33 99 19 200 211 134 69 142 100 51 97 80 150 196 72 234 84 59 179 171 188 144 188 36 33 32 80 48 133 95 125 125 89 44 74 103 136 200 148 41 26 26 26 15 56 197 138 73 155 84 144 179 189 35 198 237 38 173 182 121 137 111 189 47 68 108 245 63 174 175 98 155]
:c`"=i@b152519%110*+}%N*N"
$ cjam gen.cjam > test.cjam
$ cjam test.cjam < british-english-huge.txt | sort -n > temp
$ head -1 temp
8
$ tail -1 temp
16776899
$ all=$(wc -l < british-english-huge.txt)
$ unique=$(uniq -u < temp | wc -l)
$ echo $[all - unique]
3677
```
The following port to Python can be used with the official scoring snippet:
```
h=lambda s,b:len(s)and ord(s[-1])+b*h(s[:-1],b)
def H(s):
 p=h(s,9)%110
 return h(s,ord(
  '}\x13%ww"\x0c\xe1\xf5{\x83E\x85\xc0\xed\x08\x10\xd3F\x0c"Y\xf8\xda{\xf8\x18\x14\x8eK:\xc1\x9e\x97\xf8\xf2\\\x18!c\x13\xc8\xd3\x86E\x8ed3aP\x96\xc4H\xeaT;\xb3\xab\xbc\x90\xbc$! P0\x85_}}Y,Jg\x88\xc8\x94)\x1a\x1a\x1a\x0f8\xc5\x8aI\x9bT\x90\xb3\xbd#\xc6\xed&\xad\xb6y\x89o\xbd/Dl\xf5?\xae\xafb\x9b'
  [p]))%152519*110+p
```
[Answer]
Alright fine I’ll go learn a golfing language.
# CJam, 140 bytes, 3314 colliding words
```
00000000: 7b5f 3162 225e d466 4a55 a05e 9f47 fc51  {_1b"^.fJU.^.G.Q
00000010: c45b 4965 3073 72dd e1b4 d887 a4ac bcbd  .[Ie0sr.........
00000020: 9c8f 70ca 2981 b2df 745a 10d0 dfca 6cff  ..p.)...tZ....l.
00000030: 7a3b 64df e730 54b4 b068 8584 5f6c 9f6b  z;d..0T..h.._l.k
00000040: b7f8 7a1f a2d3 b2b8 bcf5 cfa6 1ef7 a55c  ..z............\
00000050: dca8 795c 2492 dc32 1fb6 f449 f9ca f6b7  ..y\$..2...I....
00000060: a2cf 4772 266e ad4f d90c d236 b51d c5d5  ..Gr&n.O...6....
00000070: 5c46 3f9b 7cb4 f195 4efc fe4a ce8d 9aee  \F?.|...N..J....
00000080: 9dbc 223d 6962 3443 2329 257d            .."=ib4C#)%}
```
Defines a block (anonymous function). To test, you could add `qN%%N*N` to take the newline-separated list of words on stdin and write a newline-separated list of hashes on stdout. Equivalent Python code:
```
b=lambda s,a:reduce(lambda n,c:n*a+ord(c),s,0)
f=lambda s:b(s,ord('^\xd4fJU\xa0^\x9fG\xfcQ\xc4[Ie0sr\xdd\xe1\xb4\xd8\x87\xa4\xac\xbc\xbd\x9c\x8fp\xca)\x81\xb2\xdftZ\x10\xd0\xdf\xcal\xffz;d\xdf\xe70T\xb4\xb0h\x85\x84_l\x9fk\xb7\xf8z\x1f\xa2\xd3\xb2\xb8\xbc\xf5\xcf\xa6\x1e\xf7\xa5\\\xdc\xa8y\\$\x92\xdc2\x1f\xb6\xf4I\xf9\xca\xf6\xb7\xa2\xcfGr&n\xadO\xd9\x0c\xd26\xb5\x1d\xc5\xd5\\F?\x9b|\xb4\xf1\x95N\xfc\xfeJ\xce\x8d\x9a\xee\x9d\xbc'[b(s,1)%125]))%(8**8+1)
```
# Pyth, 140 bytes, ~~3535~~ 3396 colliding words
```
00000000: 4c25 4362 2d68 5e38 2038 2a36 3643 4022  L%Cb-h^8 8*66C@"
00000010: aa07 f29a 27a7 133a 3901 484d 3f9b 1982  ....'..:9.HM?...
00000020: d261 79ab adab 9d92 888c 3012 a280 76cf  .ay.......0...v.
00000030: a2e5 8f81 7039 acee c42e bc18 28d8 efbf  ....p9......(...
00000040: 0ebe 2910 9c90 158e 3742 71b4 bdf5 59c2  ..).....7Bq...Y.
00000050: f90b e291 8673 ea59 6975 10be e750 84c8  .....s.Yiu...P..
00000060: 0b0f e7e8 f591 f628 cefa 1ab3 2e3c 72a3  .......(.....<r.
00000070: 7f09 6190 dbd2 d54e d6d0 d391 a780 ebb6  ..a....N........
00000080: ae86 2d1e 49b0 552e 7522 4362            ..-.I.U.u"Cb
```
Defines a function named `y`. To test, you could add `jmyd.z` to take the newline-separated list of words on stdin and write a newline-separated list of hashes on stdout. Equivalent Python code:
```
b=lambda s,a:reduce(lambda n,c:n*a+ord(c),s,0)
f=lambda s:b(s,256)%(8**8+1-66*ord("\xaa\x07\xf2\x9a'\xa7\x13:9\x01HM?\x9b\x19\x82\xd2ay\xab\xad\xab\x9d\x92\x88\x8c0\x12\xa2\x80v\xcf\xa2\xe5\x8f\x81p9\xac\xee\xc4.\xbc\x18(\xd8\xef\xbf\x0e\xbe)\x10\x9c\x90\x15\x8e7Bq\xb4\xbd\xf5Y\xc2\xf9\x0b\xe2\x91\x86s\xeaYiu\x10\xbe\xe7P\x84\xc8\x0b\x0f\xe7\xe8\xf5\x91\xf6(\xce\xfa\x1a\xb3.<r\xa3\x7f\ta\x90\xdb\xd2\xd5N\xd6\xd0\xd3\x91\xa7\x80\xeb\xb6\xae\x86-\x1eI\xb0U.u"[b(s,256)%121]))
```
# Theoretical limits
How well can we expect to do? Here is a plot of x, the number of colliding words, vs. y, the entropy in bytes required to get at most x colliding words. For example, the point (2835, 140) tells us that a random function gets at most 2835 colliding words with probability 1/256\*\*140, so it’s exceedingly unlikely that we’ll ever be able to do much better than that with 140 bytes of code.
[![graph](https://i.stack.imgur.com/S642a.png)](https://i.stack.imgur.com/S642a.png)
[Answer]
## Python, 6446 6372
---
This solution achieves lower collision count than all previous entries, and it needs only 44 of the 140 bytes allowed for code:
```
H=lambda s:int(s.encode('hex'),16)%16727401
```
[Answer]
# CJam, 6273
```
{49f^245b16777213%}
```
XOR each character with **49**, reduce the resulting string via **x, y ↦ 245x + y**, and take the residue modulo **16,777,213** (the largest 24-bit prime).
### Scoring
```
$ cat hash.cjam
qN% {49f^245b16777213%} %N*N
$ all=$(wc -l < british-english-huge.txt)
$ unique=$(cjam hash.cjam < british-english-huge.txt | sort | uniq -u | wc -l)
$ echo $[all - unique]
6273
```
[Answer]
# JavaScript (ES6), 6389
The hash function (105 bytes):
```
s=>[...s.replace(/[A-Z]/g,a=>(b=a.toLowerCase())+b+b)].reduce((a,b)=>(a<<3)*28-a^b.charCodeAt(),0)<<8>>>8
```
The scoring function (NodeJS) (170 bytes):
```
h={},c=0,l=require('fs').readFileSync(process.argv[2],'utf8').split('\n').map(a=>h[b=F(a)]=-~h[b])
for(w of Object.getOwnPropertyNames(h)){c+=h[w]>1&&h[w]}
console.log(c)
```
Call as `node hash.js dictionary.txt`, where `hash.js` is the script, `dictionary.txt` is the dictionary text file (without the final newline), and `F` is defined as the hashing function.
*Thanks Neil for shaving 9 bytes off the hashing function!*
[Answer]
## Mathematica, 6473
The next step up... instead of summing the character codes we treat them as the digits of a base-151 number, before taking them modulo 224.
```
hash[word_] := Mod[FromDigits[ToCharacterCode @ word, 151], 2^24]
```
Here is a short script to determine the number of collisions:
```
Total[Last /@ DeleteCases[Tally[hash /@ words], {_, 1}]]
```
I've just tried all bases systematically from `1` onwards, and so far base 151 yielded the fewest collisions. I'll try a few more to bring down the score a bit further, but the testing is a bit slow.
[Answer]
# Javascript (ES5), 6765
This is CRC24 shaved down to 140 Bytes. Could golf more but wanted to get my answer in :)
```
function(s){c=0xb704ce;i=0;while(s[i]){c^=(s.charCodeAt(i++)&255)<<16;for(j=0;j++<8;){c<<=1;if(c&0x1000000)c^=0x1864cfb}}return c&0xffffff}
```
Validator in node.js:
```
var col = new Array(16777215);
var n = 0;
var crc24_140 = 
function(s){c=0xb704ce;i=0;while(s[i]){c^=(s.charCodeAt(i++)&255)<<16;for(j=0;j++<8;){c<<=1;if(c&0x1000000)c^=0x1864cfb}}return c&0xffffff}
require('fs').readFileSync('./dict.txt','utf8').split('\n').map(function(s){ 
    var h = crc24_140(s);
    if (col[h]===1) {
        col[h]=2;
        n+=2;
    } else if (col[h]===2) {
        n++;
    } else {
        col[h]=1;
    }
});
console.log(n);
```
[Answer]
# Python, 340053
A terrible score from a terrible algorithm, this answer exists more to give a small Python script that displays scoring.
```
H=lambda s:sum(map(ord, s))%(2**24)
```
To score:
```
hashes = []
with open("british-english-huge.txt") as f:
    for line in f:
        word = line.rstrip("\n")
        hashes.append(H(word))
from collections import Counter
print(sum(v for k, v in Counter(hashes).items() if v > 1))
```
[Answer]
# Python, ~~6390~~ ~~6376~~ 6359
```
H=lambda s:reduce(lambda a,x:a*178+ord(x),s,0)%(2**24-48)
```
May be considered a trivial modification to [Martin Büttner's answer](https://codegolf.stackexchange.com/a/76784/39244).
[Answer]
## Python, 9310
---
Yeah, not the best, but at least it is something. As we say in crypto, [never write your own hash function](http://locklessinc.com/articles/crypto_hash/).
This is exactly 140 bytes long, as well.
```
F=lambda x,o=ord,m=map:int((int(''.join(m(lambda z:str(o(z)^o(x[-x.find(z)])^o(x[o(z)%len(x)])),x)))^(sum(m(int,m(o,x))))^o(x[-1]))%(2**24))
```
[Answer]
## Matlab, 30828 8620 6848
It builds the hash by assigning a prime number to each ascii character/position combo and calculating their product for each word modulo the largest prime smaller than 2^24. Note that for testing I moved the call to primes outside into the tester directly before the while loop and passed it into the hash function, because it sped it up by about a factor of 1000, but this version works, and is self-contained. It may crash with words longer than about 40 characters.
```
function h = H(s)
p = primes(1e6);
h = 1;
for i=1:length(s)
    h = mod(h*p(double(s(i))*i),16777213);
end
end
```
Tester:
```
clc
clear variables
close all
file = fopen('british-english-huge.txt');
hashes = containers.Map('KeyType','uint64','ValueType','uint64');
words = 0;
p = primes(1e6);
while ~feof(file)
    words = words + 1;
    word = fgetl(file);
    hash = H(word,p);
    if hashes.isKey(hash)
        hashes(hash) = hashes(hash) + 1;
    else
        hashes(hash) = 1;
    end
end
collisions = 0;
for key=keys(hashes)
    if hashes(key{1})>1
        collisions = collisions + hashes(key{1});
    end
end
```
[Answer]
## Ruby, 9309 collisions, 107 bytes
```
def hash(s);require'prime';p=Prime.first(70);(0...s.size).reduce(0){|a,i|a+=p[i]**(s[i].ord)}%(2**24-1);end 
```
Not a good contender, but I wanted to explore a different idea from other entries. 
Assign the first n primes to the first n positions of the string, then sum all prime[i] \*\* (ascii code of string[i]), then mod 2\*\*24-1.
[Answer]
# Java 8, 7054 6467
This is inspired by (but not copied from) the builtin java.lang.String.hashCode function, so feel free to disallow according to rule #2.
```
w -> { return w.chars().reduce(53, (acc, c) -> Math.abs(acc * 79 + c)) % 16777216; };
```
To score:
```
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
public class TweetableHash {
    public static void main(String[] args) throws Exception {
        List<String> words = Files.readAllLines(Paths.get("british-english-huge.txt"));
        Function<String, Integer> hashFunc = w -> { return w.chars().reduce(53, (acc, c) -> Math.abs(acc * 79 + c)) % 16777216; };
        Map<Integer, Integer> hashes = new HashMap<>();
        for (String word : words) {
            int hash = hashFunc.apply(word);
            if (hash < 0 || hash >= 16777216) {
                throw new Exception("hash too long for word: " + word + " hash: " + hash);
            }
            Integer numOccurences = hashes.get(hash);
            if (numOccurences == null) {
                numOccurences = 0;
            }
            numOccurences++;
            hashes.put(hash, numOccurences);
        }
        int numCollisions = hashes.values().stream().filter(i -> i > 1).reduce(Integer::sum).get();
        System.out.println("num collisions: " + numCollisions);
    }
}
```
[Answer]
# Python, ~~6995~~ ~~6862~~ 6732
Just a simple RSA function. Pretty lame, but beats some answers.
```
M=0x5437b3a3b1
P=0x65204c34d
def H(s):
    n=0
    for i in range(len(s)):
        n+=pow(ord(s[i]),P,M)<<i
    return n%(8**8)
```
[Answer]
# C++: 7112 6694 6483 6479 6412 6339 collisions, 90 bytes
I implemented a naïve genetic algorithm for my coefficient array. I'll update this code as it finds better ones. :)
```
int h(const char*s){uint32_t t=0,p=0;while(*s)t="cJ~Z]q"[p++%6]*t+*s++;return t%16777213;}
```
Test function:
```
int main(void)
{
    std::map<int, int> shared;
    std::string s;
    while (std::cin >> s) {
        shared[h(s.c_str())]++;
    }
    int count = 0;
    for (auto c : shared) {
        if ((c.first & 0xFFFFFF) != c.first) { std::cerr << "invalid hash: " << c.first << std::endl; }
        if (c.second > 1) { count += c.second; }
    }
    std::cout << count << std::endl;
    return 0;
}
```
[Answer]
# C#, 6251 6335
```
int H(String s){int h = 733;foreach (char c in s){h = (h * 533 + c);}return h & 0xFFFFFF;}
```
The constants 533 and 733 889 and 155 give the best score out of all of the ones I've searched so far.
[Answer]
# tcl
## 88 bytes, 6448 / 3233 collisions
I see people have been counting either the number of colliding words,
or else the number of words placed in nonempty buckets. I give both counts - the first is according to the problem specification, and the
second is what more posters have been reporting.
```
# 88 bytes, 6448 collisions, 3233 words in nonempty buckets
puts "[string length {proc H w {incr h;lmap c [split $w {}] {set h [expr (2551*$h+[scan $c %c])%2**24]};set h}}] bytes"
proc H w {incr h;lmap c [split $w {}] {set h [expr (2551*$h+[scan $c %c])%2**24]};set h}
# change 2551 above to:
#   7: 85 bytes, 25839 colliding words, 13876 words in nonempty buckets
#   97: 86 bytes, 6541 colliding words, 3283 words in nonempty buckets
#   829: 87 bytes, 6471 colliding words, 3251 words in nonempty buckets
# validation program
set f [open ~/Downloads/british-english-huge.txt r]
set words [split [read $f] \n]
close $f
set have {};                        # dictionary whose keys are hash codes seen
foreach w $words {
    if {$w eq {}} continue
    set h [H $w]
    dict incr have $h
}
set coll 0
dict for {- count} $have {
    if {$count > 1} {
        incr coll $count
    }
}
puts "found $coll collisions"
```
[Answer]
## Python 3, 89 bytes, 6534 hash collisions
```
def H(x):
 v=846811
 for y in x:
  v=(972023*v+330032^ord(y))%2**24
 return v%2**24
```
All the large magic numbers you see here are fudge constants.
[Answer]
## JavaScript, 121 bytes, ~~3268~~ ~~3250~~ ~~3244~~ 6354(3185) collisions
```
s=>{v=i=0;[...s].map(z=>{v=((((v*13)+(s.length-i)*7809064+i*380886)/2)^(z.charCodeAt(0)*266324))&16777215;i++});return v}
```
Parameters (13, 7809064, 380886, 2, 266324) are by trial and error.
Still optimizable I think, and there is still room for adding extra parameters,
working for further optimization...
## Verification
```
hashlist = [];
conflictlist = [];
for (x = 0; x < britain.length; x++) {
    hash = h(britain[x]);                      //britain is the 340725-entry array
    hashlist.push(hash);
}
conflict = 0; now_result = -1;
(sortedlist = sort(hashlist)).map(v => {
    if (v == now_result) {
        conflict++;
        conflictlist.push(v);
    }
    else
        now_result = v;
});
console.log(conflictlist);
var k = 0;
while (k < conflictlist.length) {
    if (k < conflictlist.length - 1 && conflictlist[k] == conflictlist[k+1])
        conflictlist.splice(k,1);
    else
        k++;
}
console.log(conflict + " " + (conflict+conflictlist.length));
```
---
3268 > 3250 - Changed the 3rd parameter from 380713 to 380560.
3250 > 3244 - Changed the 3rd parameter from 380560 to 380886.
3244 > 6354 - Changed the 2nd parameter from 7809143 to 7809064, and found I've used the wrong calculation method ;P
[Answer]
Here are a few similar constructs, which are quite "seedable" and makes incremental parameter optimization possible. Damn it is difficult to get lower than 6k! Assuming the score has the mean of 6829 and std of 118 I also calculated the likelihood of getting such low scores by random.
**Clojure A, 6019, Pr = 1:299.5e9**
```
 #(reduce(fn[r i](mod(+(* r 811)i)16777213))(map *(cycle(map int"~:XrBaXYOt3'tH-x^W?-5r:c+l*#*-dtR7WYxr(CZ,R6J7=~vk"))(map int %)))
```
**Clojure B, 6021, Pr = 1:266.0e9**
```
#(reduce(fn[r i](mod(+(* r 263)i)16777213))(map *(cycle(map int"i@%(J|IXt3&R5K'XOoa+Qk})w<!w[|3MJyZ!=HGzowQlN"))(map int %)(rest(range))))
```
**Clojure C, 6148, Pr = 1:254.0e6**
```
#(reduce(fn[r i](mod(+(* r 23)i)16777213))(map *(cycle(map int"ZtabAR%H|-KrykQn{]u9f:F}v#OI^so3$x54z2&gwX<S~"))(for[c %](bit-xor(int c)3))))
```
**Clojure, 6431, Pr = 1:2.69e3 (something different)**
```
#(mod(reduce bit-xor(map(fn[i[a b c]](bit-shift-left(* a b)(mod(+ i b c)19)))(range)(partition 3 1(map int(str"w"%"m")))))16776869)
```
This was my original ad-hoc hash function, it has four tunable parameters.
[Answer]
# tcl
~~#91 bytes, 6508 collisions~~
# 91 bytes, 6502 collisions
```
proc H s {lmap c [split $s ""] {incr h [expr [scan $c %c]*875**[incr i]]};expr $h&0xFFFFFF}
```
Computer is still performing a search to evaluate if there is a value that causes less collisions than the ~~147~~ 875 base, which is still the recordist.
[Answer]
## Ruby, 6473 collisions, 129 bytes
```
h=->(w){@p=@p||(2..999).select{|i|(2..i**0.5).select{|j|i%j==0}==[]};c=w.chars.reduce(1){|a,s|(a*@p[s.ord%92]+179)%((1<<24)-3)}}
```
The @p variable is filled with all the primes below 999.
This converts ascii values into primes numbers and takes their product modulo a large prime. The fudge factor of 179 deals with the fact that the original algorithm was for use in finding anagrams, where all words that are rearrangements of the same letters get the same hash. By adding the factor in the loop, it makes anagrams have distinct codes.
I could remove the \*\*0.5 (sqrt test for prime) at the expense of poorer performance to shorten the code. I could even make the prime number finder execute in the loop to remove nine more characters, leaving 115 bytes. 
To test, the following tries to find the best value for the fudge factor in the range 1 to 300. It assume that the word file in in the /tmp directory:
```
h=->(w,y){
  @p=@p||(2..999).
    select{|i|(2..i**0.5). 
    select{|j|i%j==0}==[]};
  c=w.chars.reduce(1){|a,s|(a*@p[s.ord%92]+y)%((1<<24)-3)}
}
american_dictionary = "/usr/share/dict/words"
british_dictionary = "/tmp/british-english-huge.txt"
words = (IO.readlines british_dictionary).map{|word| word.chomp}.uniq
wordcount = words.size
fewest_collisions = 9999
(1..300).each do |y|
  whash = Hash.new(0)
  words.each do |w|
    code=h.call(w,y)
    whash[code] += 1
  end
  hashcount = whash.size
  collisions = whash.values.select{|count| count > 1}.inject(:+)
  if (collisions < fewest_collisions)
    puts "y = #{y}. #{collisions} Collisions. #{wordcount} Unique words. #{hashcount} Unique hash values"
    fewest_collisions = collisions
  end
end
```
]