All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.numenta.nupic.util.NamedTuple Maven / Gradle / Ivy

/* ---------------------------------------------------------------------
 * 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