package squidpony.squidmath; import regexodus.Category; import java.io.Serializable; /** * Additional implementations of the {@link CrossHash.IHasher} interface for more specialized uses, like for use in an * OrderedSet or OrderedMap with String keys that should use case-insensitive equality/hashing. * Created by Tommy Ettinger on 4/15/2017. */ public class Hashers { private static class CaseInsensitiveStringHasher implements CrossHash.IHasher, Serializable { private static final long serialVersionUID = 1L; CaseInsensitiveStringHasher() { } @Override public int hash(final Object data) { if(data == null) return 0; if(!(data instanceof CharSequence)) return data.hashCode(); CharSequence data2 = (CharSequence)data; int result = 0x9E3779B9, a = 0x632BE5AB; final int len = data2.length(); for (int i = 0; i < len; i++) { result += (a ^= 0x85157AF5 * Category.caseFold(data2.charAt(i))); } return result * (a | 1) ^ (result >>> 11 | result << 21); } @Override public boolean areEqual(Object left, Object right) { if(left == right) return true; if(!(left instanceof CharSequence && right instanceof CharSequence)) return false; CharSequence l = (CharSequence)left, r = (CharSequence)right; int llen = l.length(), rlen = r.length(); if(llen != rlen) return false; for (int i = 0; i < llen; i++) { if(Category.caseFold(l.charAt(i)) != Category.caseFold(r.charAt(i))) return false; } return true; } } /** * Hashes and equality-checks CharSequences, such as Strings and StringBuilders, using case-insensitive comparison * in a cross-platform way. */ public static final CrossHash.IHasher caseInsensitiveStringHasher = new CaseInsensitiveStringHasher(); private static class CategoryOnlyStringHasher implements CrossHash.IHasher, Serializable { private static final long serialVersionUID = 1L; public Category category; CategoryOnlyStringHasher(Category category) { this.category = category; } @Override public int hash(final Object data) { if(data == null) return 0; if(!(data instanceof CharSequence)) return data.hashCode(); CharSequence data2 = (CharSequence)data; int result = 0x9E3779B9, a = 0x632BE5AB; final int len = data2.length(); char c; for (int i = 0; i < len; i++) { if(category.contains(c = data2.charAt(i))) result += (a ^= 0x85157AF5 * c); } return result * (a | 1) ^ (result >>> 11 | result << 21); } @Override public boolean areEqual(Object left, Object right) { if(left == right) return true; if(!(left instanceof CharSequence && right instanceof CharSequence)) return false; CharSequence l = (CharSequence)left, r = (CharSequence)right; int llen = l.length(), rlen = r.length(); char c1, c2; for (int i = 0, j = 0; i < llen && j < rlen;) { while (!category.contains(c1 = l.charAt(i++))) {} while (!category.contains(c2 = r.charAt(j++))) {} if(c1 != c2) return false; } return true; } } private static class NoCategoryStringHasher implements CrossHash.IHasher, Serializable { private static final long serialVersionUID = 1L; public Category category; NoCategoryStringHasher(Category category) { this.category = category; } @Override public int hash(final Object data) { if(data == null) return 0; if(!(data instanceof CharSequence)) return data.hashCode(); CharSequence data2 = (CharSequence)data; int result = 0x9E3779B9, a = 0x632BE5AB; final int len = data2.length(); char c; for (int i = 0; i < len; i++) { if(!category.contains(c = data2.charAt(i))) result += (a ^= 0x85157AF5 * c); } return result * (a | 1) ^ (result >>> 11 | result << 21); } @Override public boolean areEqual(Object left, Object right) { if(left == right) return true; if(!(left instanceof CharSequence && right instanceof CharSequence)) return false; CharSequence l = (CharSequence)left, r = (CharSequence)right; int llen = l.length(), rlen = r.length(); char c1, c2; for (int i = 0, j = 0; i < llen && j < rlen;) { while (category.contains(c1 = l.charAt(i++))) {} while (category.contains(c2 = r.charAt(j++))) {} if(c1 != c2) return false; } return true; } } /** * Hashes and equality-checks CharSequences, such as Strings and StringBuilders, but only considers letters (that * is, characters that are in the Unicode category "L", including A-Z, a-z, most characters used in most non-English * languages (katakana glyphs from Japanese count as letters, for instance)), and works in a cross-platform way. */ public static final CrossHash.IHasher letterOnlyStringHasher = new CategoryOnlyStringHasher(Category.L); /** * Hashes and equality-checks CharSequences, such as Strings and StringBuilders, but only considers valid chars that * are valid components of Java identifiers (it does not check that the Strings are valid identifiers, but considers * only letters, digits, currency symbols, underscores (and related underscore-like characters), and a few other * types of glyph, ignoring whitespace and most punctuation marks), and works in a cross-platform way. */ public static final CrossHash.IHasher identifierOnlyStringHasher = new CategoryOnlyStringHasher(Category.Identifier); /** * Hashes and equality-checks CharSequences, such as Strings and StringBuilders, but does not consider whitespace * (including space, newline, carriage return, tab, and so on), and works in a cross-platform way. */ public static final CrossHash.IHasher noSpaceStringHasher = new NoCategoryStringHasher(Category.Space); /** * Hashes and equality-checks CharSequences, such as Strings and StringBuilders, but does not consider any number * glyphs (Unicode category "N", including 0-9, but also various numbers in other languages, such as the dedicated * Roman numeral characters), and works in a cross-platform way. */ public static final CrossHash.IHasher noNumberStringHasher = new NoCategoryStringHasher(Category.N); /** * Hashes and equality-checks CharSequences, such as Strings and StringBuilders, but does not consider letters (that * is, characters that are in the Unicode category "L", including A-Z, a-z, most characters used in most non-English * languages (katakana glyphs from Japanese count as letters, for instance)), and works in a cross-platform way. */ public static final CrossHash.IHasher noLetterStringHasher = new NoCategoryStringHasher(Category.L); }