diff --git a/src/be/tarsos/lsh/HashTable.java b/src/be/tarsos/lsh/HashTable.java index 4212f6e..72d0441 100644 --- a/src/be/tarsos/lsh/HashTable.java +++ b/src/be/tarsos/lsh/HashTable.java @@ -25,8 +25,9 @@ import java.io.Serializable; import java.util.ArrayList; -import java.util.HashMap; +import java.util.Hashtable; import java.util.List; +import java.util.concurrent.*; import be.tarsos.lsh.families.HashFamily; import be.tarsos.lsh.families.HashFunction; @@ -35,90 +36,147 @@ * An index contains one or more locality sensitive hash tables. These hash * tables contain the mapping between a combination of a number of hashes * (encoded using an integer) and a list of possible nearest neighbours. - * + * * @author Joren Six */ -class HashTable implements Serializable { +public class HashTable implements Serializable +{ - private static final long serialVersionUID = -5410017645908038641L; + private static final long serialVersionUID = -5410017645908038641L; - /** - * Contains the mapping between a combination of a number of hashes (encoded - * using an integer) and a list of possible nearest neighbours - */ - private HashMap> hashTable; - private HashFunction[] hashFunctions; - private HashFamily family; - - /** - * Initialize a new hash table, it needs a hash family and a number of hash - * functions that should be used. - * - * @param numberOfHashes - * The number of hash functions that should be used. - * @param family - * The hash function family knows how to create new hash - * functions, and is used therefore. - */ - public HashTable(int numberOfHashes,HashFamily family){ - hashTable = new HashMap>(); - this.hashFunctions = new HashFunction[numberOfHashes]; - for(int i=0;i> hashTable; + private HashFunction[] hashFunctions; + private HashFamily family; + //private ExecutorService executor; - /** - * Query the hash table for a vector. It calculates the hash for the vector, - * and does a lookup in the hash table. If no candidates are found, an empty - * list is returned, otherwise, the list of candidates is returned. - * - * @param query - * The query vector. - * @return Does a lookup in the table for a query using its hash. If no - * candidates are found, an empty list is returned, otherwise, the - * list of candidates is returned. - */ - public List query(Vector query) { - Integer combinedHash = hash(query); - if(hashTable.containsKey(combinedHash)) - return hashTable.get(combinedHash); - else - return new ArrayList(); - } + /** + * Initialize a new hash table, it needs a hash family and a number of hash + * functions that should be used. + * + * @param numberOfHashes The number of hash functions that should be used. + * @param family The hash function family knows how to create new hash + * functions, and is used therefore. + */ + public HashTable(int numberOfHashes, HashFamily family) + { + hashTable = new Hashtable<>(); + this.hashFunctions = new HashFunction[numberOfHashes]; + for (int i = 0; i < numberOfHashes; i++) + { + hashFunctions[i] = family.createHashFunction(); + } + this.family = family; + //executor = Executors.newFixedThreadPool(16); + } - /** - * Add a vector to the index. - * @param vector - */ - public void add(Vector vector) { - Integer combinedHash = hash(vector); - if(! hashTable.containsKey(combinedHash)){ - hashTable.put(combinedHash, new ArrayList()); - } - hashTable.get(combinedHash).add(vector); - } - - /** - * Calculate the combined hash for a vector. - * @param vector The vector to calculate the combined hash for. - * @return An integer representing a combined hash. - */ - private Integer hash(Vector vector){ - int hashes[] = new int[hashFunctions.length]; - for(int i = 0 ; i < hashFunctions.length ; i++){ - hashes[i] = hashFunctions[i].hash(vector); - } - Integer combinedHash = family.combine(hashes); - return combinedHash; - } + /** + * Query the hash table for a vector. It calculates the hash for the vector, + * and does a lookup in the hash table. If no candidates are found, an empty + * list is returned, otherwise, the list of candidates is returned. + * + * @param query The query vector. + * @return Does a lookup in the table for a query using its hash. If no + * candidates are found, an empty list is returned, otherwise, the + * list of candidates is returned. + */ + public List query(Vector query) + { + Long combinedHash = hash(query); + if (hashTable.containsKey(combinedHash)) + return hashTable.get(combinedHash); + else + return new ArrayList(); + } - /** - * Return the number of hash functions used in the hash table. - * @return The number of hash functions used in the hash table. - */ - public int getNumberOfHashes() { - return hashFunctions.length; - } + /** + * Add a vector to the index. + * + * @param vector + */ + public void add(Vector vector) + { + Long combinedHash = hash(vector); + if (!hashTable.containsKey(combinedHash)) + { + hashTable.put(combinedHash, new ArrayList()); + } + hashTable.get(combinedHash).add(vector); + } + + /** + * Calculate the combined hash for a vector. + * + * @param vector The vector to calculate the combined hash for. + * @return An integer representing a combined hash. + */ + public Long hash(final Vector vector) + { + int hashes[] = new int[hashFunctions.length]; + //Try hashing in Parallel. If fails, do it serially. +// try +// { +// List> futures = new ArrayList>(); +// for(int i = 0 ; i < hashFunctions.length ; i++) +// { +// futures.add(executor.submit(new ParallelHash(hashFunctions[i], vector))); +// } +// for(int i = 0 ; i < hashFunctions.length ; i++) +// { +// hashes[i] = futures.get(i).get(); +// } +// } catch (InterruptedException | ExecutionException e) +// { + for(int i = 0 ; i < hashFunctions.length ; i++) + { + hashes[i] = hashFunctions[i].hash(vector); + } +// } + + return family.combine(hashes); + } + + /** + * Return the number of hash functions used in the hash table. + * + * @return The number of hash functions used in the hash table. + */ + public int getNumberOfHashes() + { + return hashFunctions.length; + } + + /** + * Class to parallelize hashing + * @author utsav + * + */ + private class ParallelHash implements Callable + { + private HashFunction mHashFunction; + private Vector mVector; + /** + * @param hashFunction The hashfunction to use + * @param vector The vector to hash + */ + public ParallelHash(HashFunction hashFunction, Vector vector) + { + mHashFunction = hashFunction; + mVector = vector; + } + + /** + * Returns the Hash for the given Vector + * @see java.util.concurrent.Callable#call() + */ + @Override + public Integer call() throws Exception + { + return mHashFunction.hash(mVector); + } + + } } diff --git a/src/be/tarsos/lsh/Index.java b/src/be/tarsos/lsh/Index.java index 7a77e00..4ae4c7c 100644 --- a/src/be/tarsos/lsh/Index.java +++ b/src/be/tarsos/lsh/Index.java @@ -40,6 +40,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.*; import java.util.logging.Logger; import be.tarsos.lsh.families.DistanceComparator; @@ -52,177 +53,287 @@ * the moment the index is stored in memory. It holds a number of hash tables, * each with a couple of hashes. Together they can be used for efficient lookup * of nearest neighbours. - * + * * @author Joren Six */ -public class Index implements Serializable{ - - private static final long serialVersionUID = 3757702142917691272L; - - private final static Logger LOG = Logger.getLogger(Index.class.getName()); - - private HashFamily family; - private List hashTable; - private int evaluated; - - /** - * Create a new index. - * - * @param family - * The family of hash functions to use. - * @param numberOfHashes - * The number of hashes that are concatenated in each hash table. - * More concatenated hashes means that less candidates are - * selected for evaluation. - * @param numberOfHashTables - * The number of hash tables in use, recall increases with the - * number of hash tables. Memory use also increases. Time needed - * to compute a hash also increases marginally. - */ - public Index(HashFamily family,int numberOfHashes, int numberOfHashTables){ - this.family = family; - hashTable = new ArrayList(); - for(int i = 0 ; i < numberOfHashTables ; i++ ){ - hashTable.add(new HashTable(numberOfHashes, family)); - } - evaluated = 0; - } - - /** - * Add a vector to the current index. The hashes are calculated with the - * current hash family and added in the right place. - * - * @param vector - * The vector to add. - */ - public void index(Vector vector) { - for (HashTable table : hashTable) { - table.add(vector); - } - } - - /** - * The number of hash tables used in the current index. - * @return The number of hash tables used in the current index. - */ - public int getNumberOfHashTables(){ - return hashTable.size(); - } - - /** - * The number of hashes used in each hash table in the current index. - * @return The number of hashes used in each hash table in the current index. - */ - public int getNumberOfHashes(){ - return hashTable.get(0).getNumberOfHashes(); - } - - /** - * Query for the k nearest neighbours in using the current index. The - * performance (in computing time and recall/precision) depends mainly on - * how the current index is constructed and how the underlying data looks. - * - * @param query - * The query vector. The center of the neighbourhood. - * @param maxSize - * The maximum number of neighbours to return. Beware, the number - * of neighbours returned lays between zero and the chosen - * maximum. - * @return A list of nearest neighbours, the number of neighbours returned - * lays between zero and a chosen maximum. - */ - public List query(final Vector query,int maxSize){ - Set candidateSet = new HashSet(); - for(HashTable table : hashTable){ - List v = table.query(query); - candidateSet.addAll(v); - } - Listcandidates = new ArrayList(candidateSet); - evaluated += candidates.size(); - DistanceMeasure measure = family.createDistanceMeasure(); - DistanceComparator dc = new DistanceComparator(query, measure); - Collections.sort(candidates,dc); - if(candidates.size() > maxSize){ - candidates = candidates.subList(0, maxSize); - } - return candidates; - } - - /** - * The number of near neighbour candidates that are evaluated during the queries on this index. - * Can be used to calculate the average evaluations per query. - * @return The number of near neighbour candidates that are evaluated during the queries on this index. - */ - public int getTouched(){ - return evaluated; - } - - - /** - * Serializes the index to disk. - * @param index the storage object. - */ - public static void serialize(Index index){ - try { - String serializationFile = serializationName(index);; - OutputStream file = new FileOutputStream(serializationFile); - OutputStream buffer = new BufferedOutputStream(file); - ObjectOutput output = new ObjectOutputStream(buffer); - try { - output.writeObject(index); - } finally { - output.close(); - } - } catch (IOException ex) { - - } - } - - /** - * Return a unique name for a hash table wit a family and number of hashes. - * @param hashtable the hash table. - * @return e.g. "be.hogent.tarsos.lsh.CosineHashfamily_16.bin" - */ - private static String serializationName(Index index){ - String name = index.family.getClass().getName(); - int numberOfHashes = index.getNumberOfHashes(); - int numberOfHashTables = index.getNumberOfHashTables(); - return name + "_" + numberOfHashes + "_" + numberOfHashTables + ".bin"; - } - - - /** - * Deserializes the hash table from disk. If deserialization fails, - * a new hash table object is created. - * - * @param family The family. - * @param numberOfHashes the number of hashes. - * @param numberOfHashTables The number of hash tables - * @return a new, or deserialized object. - */ - public static Index deserialize(HashFamily family,int numberOfHashes,int numberOfHashTables){ - Index index = new Index(family,numberOfHashes,numberOfHashTables); - String serializationFile = serializationName(index); - if(FileUtils.exists(serializationFile)){ - try { - - InputStream file = new FileInputStream(serializationFile); - InputStream buffer = new BufferedInputStream(file); - ObjectInput input = new ObjectInputStream(buffer); - try { - index = (Index) input.readObject(); - } finally { - input.close(); - } - } catch (ClassNotFoundException ex) { - LOG.severe("Could not find class during deserialization: " + ex.getMessage()); - } catch (IOException ex) { - LOG.severe("IO exeption during during deserialization: " + ex.getMessage()); - ex.printStackTrace(); - } - } - return index; - } +public class Index implements Serializable +{ + + private static final long serialVersionUID = 3757702142917691272L; + + private final static Logger LOG = Logger.getLogger(Index.class.getName()); + + private HashFamily family; + + public List getHashTable() + { + return hashTable; + } + + public HashFamily getFamily() + { + return family; + } + + private List hashTable; + private int evaluated; + private ExecutorService executor; + + /** + * Create a new index. + * + * @param family The family of hash functions to use. + * @param numberOfHashes The number of hashes that are concatenated in each hash table. + * More concatenated hashes means that less candidates are + * selected for evaluation. + * @param numberOfHashTables The number of hash tables in use, recall increases with the + * number of hash tables. Memory use also increases. Time needed + * to compute a hash also increases marginally. + */ + public Index(HashFamily family, int numberOfHashes, int numberOfHashTables) + { + this.family = family; + hashTable = new ArrayList(); + for (int i = 0; i < numberOfHashTables; i++) + { + hashTable.add(new HashTable(numberOfHashes, family)); + } + evaluated = 0; + executor = Executors.newFixedThreadPool(numberOfHashTables); + } + + /** + * Add a vector to the current index. The hashes are calculated with the + * current hash family and added in the right place. + * + * @param vector The vector to add. + */ + public void index(final Vector vector) + { + //Try Adding in Parallel. If fails, do it serially. + try + { + for(HashTable table : hashTable) + { + executor.submit(new ParallelAdd(table, vector)); + } + executor.awaitTermination(2, TimeUnit.SECONDS); + } + catch (Exception e) + { + for (HashTable table : hashTable) + { + table.add(vector); + } + } + } + + /** + * The number of hash tables used in the current index. + * + * @return The number of hash tables used in the current index. + */ + public int getNumberOfHashTables() + { + return hashTable.size(); + } + + /** + * The number of hashes used in each hash table in the current index. + * + * @return The number of hashes used in each hash table in the current index. + */ + public int getNumberOfHashes() + { + return hashTable.get(0).getNumberOfHashes(); + } + + /** + * Query for the k nearest neighbours in using the current index. The + * performance (in computing time and recall/precision) depends mainly on + * how the current index is constructed and how the underlying data looks. + * + * @param query The query vector. The center of the neighbourhood. + * @param maxSize The maximum number of neighbours to return. Beware, the number + * of neighbours returned lays between zero and the chosen + * maximum. + * @return A list of nearest neighbours, the number of neighbours returned + * lays between zero and a chosen maximum. + */ + public List query(final Vector query, int maxSize) + { + Set candidateSet = new HashSet(); + //Try hashing in Parallel. If fails, do it serially. + try + { + List>> futures = new ArrayList > >(); + for(HashTable table : hashTable) + { + futures.add(executor.submit(new ParallelQuery(table, query))); + } + for(int i = 0 ; i < hashTable.size() ; i++) + { + candidateSet.addAll(futures.get(i).get()); + } + } catch (InterruptedException | ExecutionException e) + { + for(HashTable table : hashTable){ + List v = table.query(query); + candidateSet.addAll(v); + } + } + List candidates = new ArrayList(candidateSet); + evaluated += candidates.size(); + DistanceMeasure measure = family.createDistanceMeasure(); + DistanceComparator dc = new DistanceComparator(query, measure); + Collections.sort(candidates, dc); + if (candidates.size() > maxSize) + { + candidates = candidates.subList(0, maxSize); + } + return candidates; + } + + /** + * The number of near neighbour candidates that are evaluated during the queries on this index. + * Can be used to calculate the average evaluations per query. + * + * @return The number of near neighbour candidates that are evaluated during the queries on this index. + */ + public int getTouched() + { + return evaluated; + } + + /** + * Class to parallelize Adding vectors to table + * @author utsav + * + */ + private class ParallelAdd implements Runnable + { + private HashTable mHashTable; + private Vector mVector; + + public ParallelAdd(HashTable table, Vector vector) + { + mHashTable = table; + mVector = vector; + } + + @Override + public void run() + { + mHashTable.add(mVector); + } + + } + + /** + * Class to parallelize Querying vectors from tables + * @author utsav + * + */ + private class ParallelQuery implements Callable> + { + private HashTable mHashTable; + private Vector mVector; + + public ParallelQuery(HashTable table, Vector vector) + { + mHashTable = table; + mVector = vector; + } + + @Override + public List call() throws Exception + { + return mHashTable.query(mVector); + } + + } + + /** + * Serializes the index to disk. + * + * @param index the storage object. + */ + public static void serialize(Index index) + { + try + { + String serializationFile = serializationName(index); + ; + OutputStream file = new FileOutputStream(serializationFile); + OutputStream buffer = new BufferedOutputStream(file); + ObjectOutput output = new ObjectOutputStream(buffer); + try + { + output.writeObject(index); + } finally + { + output.close(); + } + } catch (IOException ex) + { + + } + } + + /** + * Return a unique name for a hash table wit a family and number of hashes. + * + * @param hashtable the hash table. + * @return e.g. "be.hogent.tarsos.lsh.CosineHashfamily_16.bin" + */ + private static String serializationName(Index index) + { + String name = index.family.getClass().getName(); + int numberOfHashes = index.getNumberOfHashes(); + int numberOfHashTables = index.getNumberOfHashTables(); + return name + "_" + numberOfHashes + "_" + numberOfHashTables + ".bin"; + } + + + /** + * Deserializes the hash table from disk. If deserialization fails, + * a new hash table object is created. + * + * @param family The family. + * @param numberOfHashes the number of hashes. + * @param numberOfHashTables The number of hash tables + * @return a new, or deserialized object. + */ + public static Index deserialize(HashFamily family, int numberOfHashes, int numberOfHashTables) + { + Index index = new Index(family, numberOfHashes, numberOfHashTables); + String serializationFile = serializationName(index); + if (FileUtils.exists(serializationFile)) + { + try + { + + InputStream file = new FileInputStream(serializationFile); + InputStream buffer = new BufferedInputStream(file); + ObjectInput input = new ObjectInputStream(buffer); + try + { + index = (Index) input.readObject(); + } finally + { + input.close(); + } + } catch (ClassNotFoundException ex) + { + LOG.severe("Could not find class during deserialization: " + ex.getMessage()); + } catch (IOException ex) + { + LOG.severe("IO exeption during during deserialization: " + ex.getMessage()); + ex.printStackTrace(); + } + } + return index; + } } diff --git a/src/be/tarsos/lsh/experimental/Clustering.java b/src/be/tarsos/lsh/experimental/Clustering.java index 0170c6e..497c35d 100644 --- a/src/be/tarsos/lsh/experimental/Clustering.java +++ b/src/be/tarsos/lsh/experimental/Clustering.java @@ -118,7 +118,7 @@ public static void kMeansClustering(int k,List dataset){ } } - public static void nearestNeighbourClustering(double threshold,List dataset){ + public static void nearestNeighbourClustering(double threshold, List dataset){ //Nearest neighbour clustering List clusters = new ArrayList(); DistanceMeasure dm = new EuclideanDistance(); diff --git a/src/be/tarsos/lsh/families/BinHash.java b/src/be/tarsos/lsh/families/BinHash.java new file mode 100644 index 0000000..69975f3 --- /dev/null +++ b/src/be/tarsos/lsh/families/BinHash.java @@ -0,0 +1,62 @@ +/* +* _______ _ ____ _ _ +* |__ __| | | / ____| | | | +* | | __ _ _ __ ___ ___ ___| | | (___ | |___| | +* | |/ _` | '__/ __|/ _ \/ __| | \___ \| ___ | +* | | (_| | | \__ \ (_) \__ \ |____ ____) | | | | +* |_|\__,_|_| |___/\___/|___/_____/|_____/|_| |_| +* +* ----------------------------------------------------------- +* +* TarsosLSH is developed by Joren Six at +* The School of Arts, +* University College Ghent, +* Hoogpoort 64, 9000 Ghent - Belgium +* +* ----------------------------------------------------------- +* +* Info : http://tarsos.0110.be/tag/TarsosLSH +* Github : https://github.com/JorenSix/TarsosLSH +* Releases: http://tarsos.0110.be/releases/TarsosLSH/ +* +*/ + +package be.tarsos.lsh.families; + +import be.tarsos.lsh.Vector; + +import java.util.Random; + +public class BinHash implements HashFunction +{ + /** + * Implements a random Binary Projection hash. + * Hash returns 0 if dot product is negative/0, 1 if positive + */ + private static final long serialVersionUID = -3784656820380622717L; + private Vector randomProjection; + + public BinHash(int dimensions, int seed) + { + Random rand = new Random(seed); + randomProjection = new Vector(dimensions); + for (int d = 0; d < dimensions; d++) + { + //mean 0 + //standard deviation 1.0 + double val = rand.nextGaussian(); + randomProjection.set(d, val); + } + } + + /** + * + * @param vector + * The vector to hash. Can have any number of dimensions. + * @return 0 if dot product is negative/0, 1 if positive + */ + public int hash(Vector vector) + { + return ((vector.dot(randomProjection)<=0)?0:1); + } +} diff --git a/src/be/tarsos/lsh/families/BinHashFamily.java b/src/be/tarsos/lsh/families/BinHashFamily.java new file mode 100644 index 0000000..425c40d --- /dev/null +++ b/src/be/tarsos/lsh/families/BinHashFamily.java @@ -0,0 +1,70 @@ +/* +* _______ _ ____ _ _ +* |__ __| | | / ____| | | | +* | | __ _ _ __ ___ ___ ___| | | (___ | |___| | +* | |/ _` | '__/ __|/ _ \/ __| | \___ \| ___ | +* | | (_| | | \__ \ (_) \__ \ |____ ____) | | | | +* |_|\__,_|_| |___/\___/|___/_____/|_____/|_| |_| +* +* ----------------------------------------------------------- +* +* TarsosLSH is developed by Joren Six at +* The School of Arts, +* University College Ghent, +* Hoogpoort 64, 9000 Ghent - Belgium +* +* ----------------------------------------------------------- +* +* Info : http://tarsos.0110.be/tag/TarsosLSH +* Github : https://github.com/JorenSix/TarsosLSH +* Releases: http://tarsos.0110.be/releases/TarsosLSH/ +* +*/ + +package be.tarsos.lsh.families; + +import java.util.Random; + +public class BinHashFamily implements HashFamily +{ + /** + * + */ + private static final long serialVersionUID = 3406464542795652263L; + private final int dimensions; + private int seed; + private Random rand; + + public BinHashFamily(int seed, int dimensions) + { + this.dimensions = dimensions; + this.rand = new Random(seed); + } + + @Override + public HashFunction createHashFunction() + { + return new BinHash(dimensions, this.rand.nextInt()); + } + + /** + * @param hashes The raw binary hashes that need to be combined. + * @return An Integer representing the binary array of hashes + */ + @Override + public Long combine(int[] hashes) + { + Long ret = (long) 0; + for (int hash : hashes) + { + ret = (ret << 1) + ((hash == 0) ? 0 : 1); + } + return ret; + } + + @Override + public DistanceMeasure createDistanceMeasure() + { + return new CityBlockDistance(); + } +} diff --git a/src/be/tarsos/lsh/families/CityBlockDistance.java b/src/be/tarsos/lsh/families/CityBlockDistance.java index 2a02b4e..87c6df7 100644 --- a/src/be/tarsos/lsh/families/CityBlockDistance.java +++ b/src/be/tarsos/lsh/families/CityBlockDistance.java @@ -60,4 +60,8 @@ public double distance(Vector one, Vector other) { return distance; } + public int distance(Integer one, Integer two) + { + return Integer.bitCount(one ^ two); + } } diff --git a/src/be/tarsos/lsh/families/CityBlockHashFamily.java b/src/be/tarsos/lsh/families/CityBlockHashFamily.java index 7be5bf1..29b9c8a 100644 --- a/src/be/tarsos/lsh/families/CityBlockHashFamily.java +++ b/src/be/tarsos/lsh/families/CityBlockHashFamily.java @@ -45,8 +45,8 @@ public HashFunction createHashFunction(){ } @Override - public Integer combine(int[] hashes) { - return Arrays.hashCode(hashes); + public Long combine(int[] hashes) { + return (long) Arrays.hashCode(hashes); } @Override diff --git a/src/be/tarsos/lsh/families/CosineHashFamily.java b/src/be/tarsos/lsh/families/CosineHashFamily.java index 1b6570c..2ab0a9a 100644 --- a/src/be/tarsos/lsh/families/CosineHashFamily.java +++ b/src/be/tarsos/lsh/families/CosineHashFamily.java @@ -42,11 +42,11 @@ public HashFunction createHashFunction() { } @Override - public Integer combine(int[] hashes) { + public Long combine(int[] hashes) { //Treat the hashes as a series of bits. //They are either zero or one, the index //represents the value. - int result = 0; + Long result = (long) 0; //factor holds the power of two. int factor = 1; for(int i = 0 ; i < hashes.length ; i++){ diff --git a/src/be/tarsos/lsh/families/EuclidianHashFamily.java b/src/be/tarsos/lsh/families/EuclidianHashFamily.java index 1189df9..14d132d 100644 --- a/src/be/tarsos/lsh/families/EuclidianHashFamily.java +++ b/src/be/tarsos/lsh/families/EuclidianHashFamily.java @@ -44,8 +44,8 @@ public HashFunction createHashFunction(){ } @Override - public Integer combine(int[] hashes){ - return Arrays.hashCode(hashes); + public Long combine(int[] hashes){ + return (long) Arrays.hashCode(hashes); } @Override diff --git a/src/be/tarsos/lsh/families/HashFamily.java b/src/be/tarsos/lsh/families/HashFamily.java index 6e06aa8..f0efb27 100644 --- a/src/be/tarsos/lsh/families/HashFamily.java +++ b/src/be/tarsos/lsh/families/HashFamily.java @@ -52,7 +52,7 @@ public interface HashFamily extends Serializable { * unique hash values result in a unique, deterministic combined * hash value. */ - Integer combine(int[] hashes); + Long combine(int[] hashes); /** * Create a new distance measure.