
org.numenta.nupic.util.NamedTuple Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of htm.java Show documentation
Show all versions of htm.java Show documentation
The Java version of Numenta's HTM technology
/* ---------------------------------------------------------------------
* Numenta Platform for Intelligent Computing (NuPIC)
* Copyright (C) 2014, Numenta, Inc. Unless you have an agreement
* with Numenta, Inc., for a separate license for this software code, the
* following terms and conditions apply:
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses.
*
* http://numenta.org/licenses/
* ---------------------------------------------------------------------
*/
package org.numenta.nupic.util;
import java.lang.reflect.Array;
import java.util.Arrays;
/**
* Immutable tuple which adds associative lookup functionality.
*
* @author David Ray
*/
public class NamedTuple extends Tuple {
Bucket[] entries;
String[] keys;
int hash;
int thisHashcode;
private static final String[] EMPTY_KEYS = {};
/**
* Constructs and new {@code NamedTuple}
*
* @param keys
* @param objects
*/
public NamedTuple(String[] keys, Object... objects) {
super(interleave(keys, objects));
if(keys.length != objects.length) {
throw new IllegalArgumentException("Keys and values must be same length.");
}
this.keys = keys;
entries = new Bucket[keys.length * 2];
for(int i = 0;i < entries.length;i++) {
entries[i] = new Bucket(i);
}
for(int i = 0;i < keys.length;i++) {
addEntry(keys[i], objects[i]);
}
this.thisHashcode = hashCode();
}
/**
* Returns a array copy of this {@code NamedTuple}'s keys.
* @return
*/
public String[] keys() {
if(keys == null || keys.length < 1) return EMPTY_KEYS;
return Arrays.copyOf(keys, keys.length);
}
/**
* Returns the Object corresponding with the specified
* key.
*
* @param key the identifier with the same corresponding index as
* its value during this {@code NamedTuple}'s construction.
* @return
*/
public Object get(String key) {
if(key == null) return null;
int hash = hashIndex(key);
Entry e = entries[hash].find(key, hash);
return e == null ? null : e.value;
}
/**
* Returns a flag indicating whether the specified key
* exists within this {@code NamedTuple}
*
* @param key
* @return
*/
public boolean hasKey(String key) {
int hash = hashIndex(key);
Entry e = entries[hash].find(key, hash);
return e != null;
}
/**
* {@inheritDoc}
*/
public String toString() {
StringBuilder sb = new StringBuilder();
for(int i = 0;i < entries.length;i++) {
sb.append(entries[i].toString());
}
return sb.toString();
}
/**
* Creates an {@link Entry} with the hashed key value, checking
* for duplicates (which aren't allowed during construction).
*
* @param key the unique String identifier
* @param value the Object corresponding to the specified key
*/
private void addEntry(String key, Object value) {
int hash = hashIndex(key);
Entry e;
if((e = entries[hash].find(key, hash)) != null && e.key.equals(key)) {
throw new IllegalStateException(
"Duplicates Not Allowed - Key: " + key + ", reinserted.");
}
Entry entry = new Entry(key, value, hash);
entries[hash].add(entry);
}
/**
* Creates and returns a hash code conforming to a number
* between 0 - n-1, where n = #Buckets
*
* @param key String to be hashed.
* @return
*/
private int hashIndex(String key) {
return Math.abs(key.hashCode()) % entries.length;
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
if(hash == 0) {
final int prime = 31;
int result = super.hashCode();
result = prime * result + Arrays.hashCode(entries);
hash = result;
}
return hash;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj) {
if(this == obj)
return true;
if(getClass() != obj.getClass())
return false;
if(!super.equals(obj))
return false;
NamedTuple other = (NamedTuple)obj;
if(this.thisHashcode != other.thisHashcode)
return false;
return true;
}
/**
* Encapsulates the hashed key/value pair in a linked node.
*/
private final class Entry {
String key;
Object value;
int hash;
Entry prev;
/**
* Constructs a new {@code Entry}
*
* @param key
* @param value
* @param hash
*/
public Entry(String key, Object value, int hash) {
this.key = key;
this.value = value;
this.hash = hashIndex(key);
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return new StringBuilder("key=").append(key)
.append(", value=").append(value)
.append(", hash=").append(hash).toString();
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + hash;
result = prime * result + ((key == null) ? 0 : key.hashCode());
return result;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj) {
if(this == obj)
return true;
if(obj == null)
return false;
if(getClass() != obj.getClass())
return false;
Entry other = (Entry)obj;
if(hash != other.hash)
return false;
if(key == null) {
if(other.key != null)
return false;
} else if(!key.equals(other.key))
return false;
if(value == null) {
if(other.value != null)
return false;
} else if(!value.equals(other.value))
return false;
return true;
}
}
/**
* Rudimentary (light-weight) Linked List implementation for storing
* hash {@link Entry} collisions.
*/
private final class Bucket {
Entry last;
int idx;
/**
* Constructs a new {@code Bucket}
* @param idx the identifier of this bucket for debug purposes.
*/
public Bucket(int idx) {
this.idx = idx;
}
/**
* Adds the specified {@link Entry} to this Bucket.
* @param e
*/
private void add(Entry e) {
if(last == null) {
last = e;
}else{
e.prev = last;
last = e;
}
}
/**
* Searches for an {@link Entry} with the specified key,
* and returns it if found and otherwise returns null.
*
* @param key the String identifier corresponding to the
* hashed value
* @param hash the hash code.
* @return
*/
private Entry find(String key, int hash) {
if(last == null) return null;
Entry found = last;
while(found.prev != null && !found.key.equals(key)) {
found = found.prev;
if(found.key.equals(key)) {
return found;
}
}
return found.key.equals(key) ? found : null;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder("Bucket: ").append(idx).append("\n");
Entry l = last;
while(l != null) {
sb.append("\t").append(l.toString()).append("\n");
l = l.prev;
}
return sb.toString();
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + idx;
result = prime * result + ((last == null) ? 0 : last.hashCode());
return result;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj) {
if(this == obj)
return true;
if(obj == null)
return false;
if(getClass() != obj.getClass())
return false;
Bucket other = (Bucket)obj;
if(idx != other.idx)
return false;
if(last == null) {
if(other.last != null)
return false;
} else if(!last.equals(other.last))
return false;
return true;
}
}
/**
* Returns an array containing the successive elements of each
* argument array as in [ first[0], second[0], first[1], second[1], ... ].
*
* Arrays may be of zero length, and may be of different sizes, but may not be null.
*
* @param first the first array
* @param second the second array
* @return
*/
static Object[] interleave(F first, S second) {
int flen, slen;
Object[] retVal = new Object[(flen = Array.getLength(first)) + (slen = Array.getLength(second))];
for(int i = 0, j = 0, k = 0;i < flen || j < slen;) {
if(i < flen) {
retVal[k++] = Array.get(first, i++);
}
if(j < slen) {
retVal[k++] = Array.get(second, j++);
}
}
return retVal;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy