Java tutorial
/* * Copyright 2009 David Jurgens * * This file is part of the S-Space package and is covered under the terms and * conditions therein. * * The S-Space package is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as published * by the Free Software Foundation and distributed hereunder to you. * * THIS SOFTWARE IS PROVIDED "AS IS" AND NO REPRESENTATIONS OR WARRANTIES, * EXPRESS OR IMPLIED ARE MADE. BY WAY OF EXAMPLE, BUT NOT LIMITATION, WE MAKE * NO REPRESENTATIONS OR WARRANTIES OF MERCHANT- ABILITY OR FITNESS FOR ANY * PARTICULAR PURPOSE OR THAT THE USE OF THE LICENSED SOFTWARE OR DOCUMENTATION * WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER * RIGHTS. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ //package edu.ucla.sspace.util; import java.util.AbstractCollection; import java.util.AbstractSet; import java.util.AbstractMap; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.Set; /** * A space-optimized map for associating {@code char} keys with values.<p> * * This class makes a trade off for reduced space usage at the cost of decreased * performace. The {@code put}, {@code get}, {@code containsKey} and {@code * get} operations are all logarithmic when the map is unmodified. If a new * mapping is added, or one is removed, the operation is linear in the number of * mappings. Both {@code size} and {@code isEmpty} are still constant time. <p> * * The {@code get} operation runs in logarithmic time. The {@code set} * operation runs in consant time if setting an existing non-zero value to a * non-zero value. However, if the {@code set} invocation sets a zero value to * non-zero, the operation is linear with the size of the array.<p> * * This map allows {@code null values}.<p> * * Iteration ordering follows the natural ordering of {@code char} values, with * the lowest {@code char} key value being returned first. * * <i>Implementation Note:</i> the {@code Iterator.remove()} method is currently * unsupported and will throw an exception when called. However, a future * implementation will fix this.<p> * * @see TrieMap * @see IntegerMap * @see Map * * @author David Jurgens */ public class CharMap<V> extends AbstractMap<Character, V> implements java.io.Serializable { private static final long serialVersionUID = 1L; /** * The keys stored in this map, in sorted order. The index at which a key * is found corresponds to the index at which its value is found. */ char[] keyIndices; /** * The values stored in this map. */ Object[] values; /** * Creates a new map. */ public CharMap() { keyIndices = new char[0]; values = new Object[0]; } /** * Creates a new map with the mappings contained in {@code m}. */ public CharMap(Map<Character, ? extends V> m) { // Pre-allocate the arrays for all the mappings needed instead of just // one at time keyIndices = new char[m.size()]; values = new Object[m.size()]; // Get all of the integer keys and sort them to their correct position // in the key array Iterator<Character> it = m.keySet().iterator(); for (int i = 0; i < m.size(); ++i) { keyIndices[i] = it.next(); } Arrays.sort(keyIndices); // Then map the values to their respective positions for (int i = 0; i < keyIndices.length; ++i) { values[i] = m.get(keyIndices[i]); } } /** * Checks that the key is non-{@code null} and is an {@code Integer} object, * and then returns its {@code int} value. */ private char checkKey(Object key) { if (key == null) { throw new NullPointerException("key cannot be null"); } else if (!(key instanceof Character)) { throw new IllegalArgumentException("key must be an Character"); } else { return ((Character) key).charValue(); } } /** * Removes all of the mappings from this map. */ public void clear() { keyIndices = new char[0]; values = new Object[0]; } /** * Returns {@code true} if this map contains a mapping for the specified key. */ public boolean containsKey(Object key) { char k = checkKey(key); int index = Arrays.binarySearch(keyIndices, k); return index >= 0; } public boolean containsValue(Object value) { for (Object o : values) { if (o == value || (o != null && o.equals(value))) { return true; } } return false; } /** * Returns a {@link Set} view of the mappings contained in this map. */ public Set<Entry<Character, V>> entrySet() { return new EntrySet(); } /** * Returns the value to which the specified key is mapped, or {@code null} * if this map contains no mapping for the key. */ public V get(Object key) { char k = checkKey(key); return get(k); } /** * Returns the value to which the specified key is mapped, or {@code null} * if this map contains no mapping for the key. */ @SuppressWarnings("unchecked") public V get(char key) { int pos = Arrays.binarySearch(keyIndices, key); return (pos >= 0) ? (V) (values[pos]) : null; } /** * Returns a {@link Set} view of the keys contained in this map. */ public Set<Character> keySet() { return new KeySet(); } /** * Adds the mapping from the provided key to the value. * * @param key * @param value * * @throws NullPointerException if the key is {@code null} * @throws IllegalArgumentException if the key is not an instance of {@link * Integer} */ public V put(Character key, V value) { char k = checkKey(key); return put(k, value); } /** * Adds the mapping from the provided key to the value. * * @param key * @param value * * @throws NullPointerException if the key is {@code null} * @throws IllegalArgumentException if the key is not an instance of {@link * Integer} */ @SuppressWarnings("unchecked") public V put(char key, V value) { char k = key; int index = Arrays.binarySearch(keyIndices, k); if (index >= 0) { V old = (V) (values[index]); values[index] = value; return old; } else { int newIndex = 0 - (index + 1); Object[] newValues = Arrays.copyOf(values, values.length + 1); char[] newIndices = Arrays.copyOf(keyIndices, values.length + 1); // shift the elements down to make room for the new value for (int i = newIndex; i < values.length; ++i) { newValues[i + 1] = values[i]; newIndices[i + 1] = keyIndices[i]; } // insert the new value newValues[newIndex] = value; newIndices[newIndex] = k; // switch the arrays with the lengthed versions values = newValues; keyIndices = newIndices; return null; } } /** * Removes the mapping for a key from this map if it is present and returns * the value to which this map previously associated the key, or {@code * null} if the map contained no mapping for the key. * * @param key key whose mapping is to be removed from the map * * @return the previous value associated with key, or {@code null} if there * was no mapping for key. */ @SuppressWarnings("unchecked") public V remove(Object key) { char k = checkKey(key); return remove(k); } /** * Removes the mapping for a key from this map if it is present and returns * the value to which this map previously associated the key, or {@code * null} if the map contained no mapping for the key. * * @param key key whose mapping is to be removed from the map * * @return the previous value associated with key, or {@code null} if there * was no mapping for key. */ @SuppressWarnings("unchecked") public V remove(char key) { char k = key; int index = Arrays.binarySearch(keyIndices, k); if (index >= 0) { V old = (V) (values[index]); Object[] newValues = Arrays.copyOf(values, values.length - 1); char[] newIndices = Arrays.copyOf(keyIndices, keyIndices.length - 1); // shift the elements up to remove the values for (int i = index; i < values.length - 1; ++i) { newValues[i] = values[i + 1]; newIndices[i] = keyIndices[i + 1]; } // update the arrays with the shorted versions values = newValues; keyIndices = newIndices; return old; } return null; } /** * Returns the number of key-value mappings in this trie. */ public int size() { return keyIndices.length; } /** * Returns a {@link Collection} view of the values contained in this map. */ public Collection<V> values() { return new Values(); } private class EntryIterator extends CharMapIterator<Map.Entry<Character, V>> { public Map.Entry<Character, V> next() { return nextEntry(); } } private class KeyIterator extends CharMapIterator<Character> { public Character next() { return nextEntry().getKey(); } } private class ValueIterator extends CharMapIterator<V> { public V next() { return nextEntry().getValue(); } } abstract class CharMapIterator<E> implements Iterator<E> { private int next; public CharMapIterator() { next = 0; } public boolean hasNext() { return next < size(); } @SuppressWarnings("unchecked") public Entry<Character, V> nextEntry() { if (next >= size()) { throw new NoSuchElementException("no further elements"); } char key = keyIndices[next]; V value = (V) (values[next]); next++; return new CharEntry(key, value); } // REMINDER: this class needs to work with the actual indices for the // key and value to avoid the logrithmic lookup public void remove() { throw new UnsupportedOperationException(); } } // REMINDER: this class needs to work with the actual indices for the key // and value to avoid the logrithmic lookup class CharEntry extends SimpleEntry<Character, V> { private static final long serialVersionUID = 1L; public CharEntry(char key, V value) { super(key, value); } public V setValue(V newValue) { return CharMap.this.put(getKey(), newValue); } } class EntrySet extends AbstractSet<Entry<Character, V>> { private static final long serialVersionUID = 1L; public void clear() { CharMap.this.clear(); } public boolean contains(Object o) { if (o instanceof Map.Entry) { Map.Entry e = (Map.Entry) o; Object key = e.getKey(); Object val = e.getValue(); Object mapVal = CharMap.this.get(key); return mapVal == val || (val != null && val.equals(mapVal)); } return false; } public Iterator<Map.Entry<Character, V>> iterator() { return new EntryIterator(); } public int size() { return CharMap.this.size(); } } class KeySet extends AbstractSet<Character> { private static final long serialVersionUID = 1L; public KeySet() { } public void clear() { CharMap.this.clear(); } public boolean contains(Object o) { return containsKey(o); } public Iterator<Character> iterator() { return new KeyIterator(); } public boolean remove(Object o) { return CharMap.this.remove(o) != null; } public int size() { return CharMap.this.size(); } } /** * A {@link Collection} view of the values contained in this trie. */ private class Values extends AbstractCollection<V> { private static final long serialVersionUID = 1L; public void clear() { CharMap.this.clear(); } public boolean contains(Object o) { return containsValue(o); } public Iterator<V> iterator() { return new ValueIterator(); } public int size() { return CharMap.this.size(); } } } /* * Copyright 2009 David Jurgens * * This file is part of the S-Space package and is covered under the terms and * conditions therein. * * The S-Space package is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as published * by the Free Software Foundation and distributed hereunder to you. * * THIS SOFTWARE IS PROVIDED "AS IS" AND NO REPRESENTATIONS OR WARRANTIES, * EXPRESS OR IMPLIED ARE MADE. BY WAY OF EXAMPLE, BUT NOT LIMITATION, WE MAKE * NO REPRESENTATIONS OR WARRANTIES OF MERCHANT- ABILITY OR FITNESS FOR ANY * PARTICULAR PURPOSE OR THAT THE USE OF THE LICENSED SOFTWARE OR DOCUMENTATION * WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER * RIGHTS. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* package edu.ucla.sspace.util; import java.util.Arrays; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.*; // * A collection of unit tests for {@link CharMap} public class CharMapTests { @Test public void testConstructor() { CharMap<String> m = new CharMap<String>(); } @Test public void testPut() { CharMap<String> m = new CharMap<String>(); m.put('1', "1"); String s = m.get('1'); assertEquals("1", s); } @Test(expected=NullPointerException.class) public void testPutNullKey() { CharMap<String> m = new CharMap<String>(); m.put(null, "value"); } public void testPutNullValue() { CharMap<String> m = new CharMap<String>(); m.put('0', null); String s = m.get('0'); assertEquals(null, s); } @Test public void testPutConflict() { CharMap<String> m = new CharMap<String>(); m.put('1', "1"); m.put('1', "2"); String s = m.get('1'); assertEquals("2", s); } @Test public void testProgressive() { CharMap<Integer> m = new CharMap<Integer>(); assertEquals(0, m.size()); int size = 1024; for (int i = 0; i < size; ++i) { m.put((char)i, Integer.valueOf(i)); // check mapping for (Map.Entry<Character,Integer> e : m.entrySet()) { char p = e.getKey(); Integer q = e.getValue(); assertEquals(Integer.valueOf(p), q); } } } @Test public void testContainsKey() { CharMap<String> m = new CharMap<String>(); m.put('1', "1"); m.put('2', "2"); assertTrue(m.containsKey('1')); assertTrue(m.containsKey('2')); } @Test public void testContainsKeyFalse() { CharMap<String> m = new CharMap<String>(); m.put('1', "1"); m.put('2', "2"); assertFalse(m.containsKey('3')); assertFalse(m.containsKey('4')); assertFalse(m.containsKey('0')); } @Test public void testContainsValue() { CharMap<String> m = new CharMap<String>(); m.put('1', "1"); m.put('2', "2"); assertTrue(m.containsValue("1")); assertTrue(m.containsValue("2")); } @Test public void testContainsValueFalse() { CharMap<String> m = new CharMap<String>(); m.put('1', "1"); m.put('2', "2"); assertFalse(m.containsValue("3")); assertFalse(m.containsValue("4")); assertFalse(m.containsValue("5")); } @Test public void testKeySet() { CharMap<String> m = new CharMap<String>(); m.put('1', "0"); m.put('2', "1"); m.put('3', "2"); m.put('4', "3"); m.put('5', "4"); Set<Character> test = m.keySet(); assertTrue(test.contains('1')); assertTrue(test.contains('2')); assertTrue(test.contains('3')); assertTrue(test.contains('4')); assertTrue(test.contains('5')); assertFalse(test.contains('0')); assertFalse(test.contains('6')); } @Test public void testKeyIterator() { CharMap<String> m = new CharMap<String>(); m.put('0', "0"); m.put('1', "1"); m.put('2', "2"); Set<Character> control = new HashSet<Character>(); control.add('0'); control.add('1'); control.add('2'); Set<Character> test = m.keySet(); assertTrue(test.containsAll(control)); assertTrue(control.containsAll(test)); } @Test public void testValueIterator() { CharMap<String> m = new CharMap<String>(); m.put('0', "0"); m.put('1', "1"); m.put('2', "2"); Set<String> control = new HashSet<String>(); control.add("0"); control.add("1"); control.add("2"); Collection<String> test = m.values(); assertEquals(control.size(), test.size()); for (String s : test) { assertTrue(control.contains(s)); } } @Test public void testIteratorHasNext() { CharMap<String> m = new CharMap<String>(); Iterator<Character> it = m.keySet().iterator(); assertFalse(it.hasNext()); m.put('0', "0"); m.put('1', "1"); m.put('2', "2"); it = m.keySet().iterator(); Set<Character> control = new HashSet<Character>(); while (it.hasNext()) { control.add(it.next()); } Set<Character> test = m.keySet(); assertTrue(test.containsAll(control)); assertTrue(control.containsAll(test)); } @Test(expected=NoSuchElementException.class) public void testIteratorNextError() { CharMap<String> m = new CharMap<String>(); m.put('0', "0"); Iterator<Character> it = m.keySet().iterator(); it.next(); it.next(); // error } @Test(expected=NoSuchElementException.class) public void testEmptyIntIteratorNextError() { CharMap<String> m = new CharMap<String>(); Iterator<Character> it = m.keySet().iterator(); it.next(); // error } @Test public void testRemove() { CharMap<String> m = new CharMap<String>(); m.put('0', "0"); assertTrue(m.containsKey('0')); m.remove('0'); assertFalse(m.containsKey('0')); assertEquals(0, m.size()); } @Test public void testMultipleRemoves() { CharMap<String> m = new CharMap<String>(); Set<Character> control = new HashSet<Character>(); LinkedList<Character> list = new LinkedList<Character>(); for (int i = 0; i < 512; ++i) { list.add((char)i); } for (Character c : list) { m.put(c, String.valueOf(c)); control.add(c); } assertEquals(control.size(), m.size()); assertTrue(control.containsAll(m.keySet())); // remove half int half = control.size(); for (int i = 0; i < half; ++i) { Character j = list.poll(); control.remove(j); m.remove(j); } assertEquals(control.size(), m.size()); assertTrue(control.containsAll(m.keySet())); } @Test public void testSize() { CharMap<String> m = new CharMap<String>(); assertEquals(0, m.size()); m.put('0', "0"); assertEquals(1, m.size()); m.put('1', "1"); assertEquals(2, m.size()); m.put('2', "2"); assertEquals(3, m.size()); } @Test public void testIsEmpty() { CharMap<String> m = new CharMap<String>(); assertTrue(m.isEmpty()); m.put('0', "0"); assertFalse(m.isEmpty()); m.clear(); assertTrue(m.isEmpty()); } @Test public void testClear() { CharMap<String> m = new CharMap<String>(); m.put('0', "0"); m.put('1', "1"); m.put('2', "2"); m.clear(); assertEquals(0, m.size()); assertFalse(m.containsKey('0')); assertFalse(m.containsKey('1')); assertFalse(m.containsKey('2')); } public static void main(String args[]) { org.junit.runner.JUnitCore.main(CharMapTests.class.getName()); } } */