Explorar el Código

#51 ArrayMap is mutable

Ranides Atterwim hace 4 años
padre
commit
70a63ac5f1

+ 9 - 9
assira.core/src/main/java/net/ranides/assira/collection/iterators/RandomAccessIterator.java

@@ -41,17 +41,17 @@ public abstract class RandomAccessIterator<T> implements ListIterator<T> {
     }
 
     @Override
-    public boolean hasNext() {
+    public final boolean hasNext() {
         return index < size();
     }
 
     @Override
-    public boolean hasPrevious() {
+    public final boolean hasPrevious() {
         return index > 0;
     }
 
     @Override
-    public T next() {
+    public final T next() {
         if (!hasNext()) {
             throw new NoSuchElementException();
         }
@@ -59,7 +59,7 @@ public abstract class RandomAccessIterator<T> implements ListIterator<T> {
     }
 
     @Override
-    public T previous() {
+    public final T previous() {
         if (!hasPrevious()) {
             throw new NoSuchElementException();
         }
@@ -67,22 +67,22 @@ public abstract class RandomAccessIterator<T> implements ListIterator<T> {
     }
 
     @Override
-    public int nextIndex() {
+    public final int nextIndex() {
         return index;
     }
 
     @Override
-    public int previousIndex() {
+    public final int previousIndex() {
         return index - 1;
     }
 
     @Override
-    public void add(T value) {
+    public final void add(T value) {
         add(index++, value);
     }
 
     @Override
-    public void set(T value) {
+    public final void set(T value) {
         if (last == -1) {
             throw new IllegalStateException();
         }
@@ -90,7 +90,7 @@ public abstract class RandomAccessIterator<T> implements ListIterator<T> {
     }
 
     @Override
-    public void remove() {
+    public final void remove() {
         if (last == -1) {
             throw new IllegalStateException();
         }

+ 3 - 2
assira.core/src/main/java/net/ranides/assira/collection/lists/NativeArrayList.java

@@ -6,6 +6,8 @@
  */
 package net.ranides.assira.collection.lists;
 
+import lombok.experimental.UtilityClass;
+
 import java.io.Serializable;
 import java.util.AbstractList;
 import java.util.List;
@@ -16,11 +18,10 @@ import net.ranides.assira.collection.arrays.NativeArray;
  *
  * @author Ranides Atterwim {@literal <ranides@gmail.com>}
  */
+@UtilityClass
 public final class NativeArrayList {
     
     private static final int[] EMPTY = new int[0];
-	
-	private NativeArrayList() { }
 
 	public static IntList wrap(int[] array) {
         if(null == array) {

+ 232 - 42
assira.core/src/main/java/net/ranides/assira/collection/maps/ArrayMap.java

@@ -7,18 +7,12 @@
 
 package net.ranides.assira.collection.maps;
 
-import net.ranides.assira.collection.arrays.ArrayUtils;
+import net.ranides.assira.collection.arrays.ArrayAllocator;
 import net.ranides.assira.collection.arrays.NativeArraySort;
 import net.ranides.assira.collection.iterators.RandomAccessIterator;
 import net.ranides.assira.generic.CompareUtils;
 
-import java.util.AbstractSet;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.Set;
-import java.util.SortedMap;
+import java.util.*;
 
 /**
  * Memory efficient, but immutable map.
@@ -31,9 +25,10 @@ public class ArrayMap<K,V> extends AMap<K, V> {
     private static final long serialVersionUID = 1L;
     
     private final Class<?> type;
-    private final K[] keys;
-    private final V[] vars;
-    
+    private K[] keys;
+    private V[] vars;
+    private int size;
+
     private final Comparator<? super K> cmp;
 
     /**
@@ -61,6 +56,7 @@ public class ArrayMap<K,V> extends AMap<K, V> {
         this.type = keys.getClass().getComponentType();
         this.keys = keys.clone();
         this.vars = vars.clone();
+        this.size = vars.length;
         this.cmp = comparator;
         sort();
     }
@@ -87,11 +83,12 @@ public class ArrayMap<K,V> extends AMap<K, V> {
         this.type = Object.class;
         this.keys = (K[])k;
         this.vars = (V[])v;
+        this.size = vars.length;
         this.cmp = map.comparator();
     }
     
     private void sort() {
-        NativeArraySort.quickSort(0, keys.length, (a,b) -> cmp.compare(keys[a],keys[b]), (a,b) -> {
+        NativeArraySort.quickSort(0, size, (a,b) -> cmp.compare(keys[a],keys[b]), (a,b) -> {
             swap(keys, a, b);
             swap(vars, a, b);
         });
@@ -108,20 +105,56 @@ public class ArrayMap<K,V> extends AMap<K, V> {
         return new AbstractSet<Entry<K, V>>() {
             @Override
             public Iterator<Entry<K, V>> iterator() {
-                return new AIterator(0);
+                return new EntryIterator(0);
             }
 
             @Override
             public int size() {
-                return keys.length;
+                return size;
+            }
+
+            @Override
+            public boolean remove(Object object) {
+                if (object instanceof Map.Entry) {
+                    Map.Entry<?, ?> entry = (Map.Entry<?,?>) object;
+                    return ArrayMap.this.remove(entry.getKey(), entry.getValue());
+                }
+                return false;
             }
 
+            @Override
+            public void clear() {
+                ArrayMap.this.clear();
+            }
         };
     }
 
     @Override
     public Collection<V> values() {
-        return Arrays.asList(vars);
+        return new AbstractList<V>() {
+            @Override
+            public Iterator<V> iterator() {
+                return new ValueIterator(0);
+            }
+
+            @Override
+            public ListIterator<V> listIterator(int index) {
+                return new ValueIterator(index);
+            }
+
+            @Override
+            public int size() {
+                return size;
+            }
+
+            @Override
+            public V get(int index) {
+                if(index <0 || index>=size) {
+                    throw new IndexOutOfBoundsException(index + " out of bound " + size);
+                }
+                return vars[index];
+            }
+        };
     }
 
     @Override
@@ -129,18 +162,30 @@ public class ArrayMap<K,V> extends AMap<K, V> {
         return new AbstractSet<K>() {
             @Override
             public Iterator<K> iterator() {
-                return ArrayUtils.iterator(keys);
+                return new KeyIterator(0);
             }
 
             @Override
             public int size() {
-                return keys.length;
+                return size;
             }
 
             @Override
             public boolean contains(Object o) {
                 return containsKey(o);
             }
+
+            @Override
+            public boolean remove(Object o) {
+                int prev = size;
+                ArrayMap.this.remove(o);
+                return prev != size;
+            }
+
+            @Override
+            public void clear() {
+                ArrayMap.this.clear();
+            }
         };
     }
 
@@ -150,7 +195,7 @@ public class ArrayMap<K,V> extends AMap<K, V> {
             return null;
         }
         @SuppressWarnings("unchecked")
-        int i = Arrays.binarySearch(keys, (K)key, cmp);
+        int i = Arrays.binarySearch(keys, 0, size, (K)key, cmp);
         return i<0 ? null : vars[i];
     }
 
@@ -161,18 +206,18 @@ public class ArrayMap<K,V> extends AMap<K, V> {
             return false;
         }
         @SuppressWarnings("unchecked")
-        int i = Arrays.binarySearch(keys, (K)key, cmp);
+        int i = Arrays.binarySearch(keys, 0, size, (K)key, cmp);
         return i >= 0;
     }
 
     @Override
     public boolean containsValue(Object value) {
         if (value==null) {
-            for (int i=0; i<vars.length; i++) {
+            for (int i=0; i<size; i++) {
                 if (vars[i]==null) { return true; }
             }
         } else {
-            for(int i=0; i<vars.length; i++) {
+            for(int i=0; i<size; i++) {
                 if( value.equals(vars[i]) ) { return true; }
             }
         }
@@ -181,45 +226,112 @@ public class ArrayMap<K,V> extends AMap<K, V> {
     
     @Override
     public int size() {
-        return keys.length;
+        return size;
     }
-    
-    private class AMEntry extends AEntry<K,V> {
-    
-        private final int index;
 
-        public AMEntry(int index) {
-            this.index = index;
+    @Override
+    public V remove(Object key) {
+        if(!type.isInstance(key)) {
+            return null;
         }
 
-        @Override
-        public K getKey() {
-            return keys[index];
+        @SuppressWarnings("unchecked")
+        int pos = Arrays.binarySearch(keys, 0, size, (K)key, cmp);
+        if(pos<0) {
+            return null;
         }
+        V prev = vars[pos];
+        $remove(pos);
+        return prev;
+    }
 
-        @Override
-        public V getValue() {
-            return vars[index];
+    @Override
+    public boolean remove(Object key, Object value) {
+        if(!type.isInstance(key)) {
+            return false;
         }
 
-        @Override
-        public V setValue(V value) {
-            V prev = vars[index];
-            vars[index] = value;
+        @SuppressWarnings("unchecked")
+        int pos = Arrays.binarySearch(keys, 0, size, (K)key, cmp);
+        if(pos<0) {
+            return false;
+        }
+        if(CompareUtils.equals(value, vars[pos])) {
+            $remove(pos);
+            return true;
+        }
+
+        for(int s = pos-1; s>0 && keys[s].equals(key); s--) {
+            if(CompareUtils.equals(value, vars[s])) {
+                $remove(s);
+                return true;
+            }
+        }
+
+        for(int s = pos+1; s<size && keys[s].equals(key); s++) {
+            if(CompareUtils.equals(value, vars[s])) {
+                $remove(s);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public V put(K key, V value) {
+        int pos = Arrays.binarySearch(keys, 0, size, key, cmp);
+        if(pos >=0) {
+            V prev = vars[pos];
+            vars[pos] = value;
             return prev;
+        } else {
+            $add(-pos - 1, key, value);
+            return null;
         }
+    }
 
+    @Override
+    public void clear() {
+        size = 0;
+        Arrays.fill(keys, null);
+        Arrays.fill(vars, null);
     }
-    
-    private class AIterator extends RandomAccessIterator<Entry<K,V>> {
 
-        public AIterator(int index) {
+    private void $remove(int index) {
+        int shift = size - index - 1;
+        if (shift > 0) {
+            System.arraycopy(keys, index + 1, keys, index, shift);
+            System.arraycopy(vars, index + 1, vars, index, shift);
+        }
+
+        size--;
+        keys[size] = null;
+        vars[size] = null;
+    }
+
+    private void $add(int index, K key, V value) {
+        keys = ArrayAllocator.grow(keys, size+1);
+        vars = ArrayAllocator.grow(vars, size+1);
+
+        int shift = size - index;
+        if (shift > 0) {
+            System.arraycopy(keys, index, keys, index + 1, shift);
+            System.arraycopy(vars, index, vars, index + 1, shift);
+        }
+        keys[index] = key;
+        vars[index] = value;
+        size++;
+    }
+
+    private class EntryIterator extends RandomAccessIterator<Entry<K,V>> {
+
+        public EntryIterator(int index) {
             super(index);
         }
 
         @Override
         protected int size() {
-            return keys.length;
+            return size;
         }
 
         @Override
@@ -227,6 +339,84 @@ public class ArrayMap<K,V> extends AMap<K, V> {
             return new AMEntry(index);
         }
 
+        @Override
+        protected void remove(int index) {
+            $remove(index);
+        }
+
+    }
+
+    private class KeyIterator extends RandomAccessIterator<K> {
+
+        public KeyIterator(int index) {
+            super(index);
+        }
+
+        @Override
+        protected int size() {
+            return size;
+        }
+
+        @Override
+        protected K get(int index) {
+            return keys[index];
+        }
+
+        @Override
+        protected void remove(int index) {
+            $remove(index);
+        }
+
+    }
+
+    private class ValueIterator extends RandomAccessIterator<V> {
+
+        public ValueIterator(int index) {
+            super(index);
+        }
+
+        @Override
+        protected int size() {
+            return size;
+        }
+
+        @Override
+        protected V get(int index) {
+            return vars[index];
+        }
+
+        @Override
+        protected void remove(int index) {
+            $remove(index);
+        }
+
+    }
+
+    private class AMEntry extends AEntry<K,V> {
+
+        private final int index;
+
+        public AMEntry(int index) {
+            this.index = index;
+        }
+
+        @Override
+        public K getKey() {
+            return keys[index];
+        }
+
+        @Override
+        public V getValue() {
+            return vars[index];
+        }
+
+        @Override
+        public V setValue(V value) {
+            V prev = vars[index];
+            vars[index] = value;
+            return prev;
+        }
+
     }
 
 }

+ 0 - 6
assira.core/src/test/java/net/ranides/assira/collection/maps/ArrayMapTest.java

@@ -55,12 +55,6 @@ public class ArrayMapTest {
         ContractTesters.runner()
             .param("map!", $map)
             .function(new int[0], array -> new ArrayMap<>($map.keys().list(array).values(), $map.values().list(array).values(), $map.comparator()))
-            .ignore("MapTester.basicEntrySet_Iterator_RemoveAll")
-            .ignore("MapTester.basicEntrySet_Iterator_SetValue")
-            .ignore(Pattern.compile("MapTester\\..*Remove"))
-            .ignore(Pattern.compile("MapTester\\.basicPut.*"))
-            .ignore(Pattern.compile("MapTester\\.basicRemove.*"))
-            .ignore(Pattern.compile("MapTester\\.basicClear.*"))
             .ignore(Pattern.compile(".*_HC"))
             .run();
     }