Repository: jtablesaw/tablesaw (java)

Issue:
Column.countUnique() returns a wrong value after using Column.set()
Hi,

After assigning all values to a previously empty Column, the empty value is still counted by countUnique().  Below is a simple test case demonstrating the issue. The count is correct when the column is created with no size and append() is used (2nd test case).

```
import static org.junit.Assert.*;
import org.junit.Test;
import tech.tablesaw.api.StringColumn;

public class TestTablesawUniqueValues {

	@Test
	public void testUniqueValuesAfterSet() {
		StringColumn col = StringColumn.create("col1", 2);
		col.set(0, "A");
		col.set(1, "B");
		assertEquals("Error in unique() after set()", 2, col.countUnique());
	}

	@Test
	public void testUniqueValuesAfterAppend() {
		StringColumn col = StringColumn.create("col1");
		col.append("A");
		col.append("B");
		assertEquals("Error in unique() after append()", 2, col.countUnique());
	}
}
```
Test result:
java.lang.AssertionError: Error in unique() after set() expected:<2> but was:<3>

Tested on version 0.34.1

Thanks for tablesaw !

Pull request description:
Correct issue #541 : unique values in StringColumns
Thanks for contributing.

- [X] Tick to sign-off your agreement to the [Developer Certificate of Origin (DCO) 1.1](https://developercertificate.org)

## Description

Implemented instance counting in all DictionaryMap implementations.

key/values are removed from all maps when value count reaches 0, so all operations based on these maps are always correct.
The only modified methods are set(), append() and clear(). There is no impact on other methods.
All count-based methods now uses the count map for performance.

Fixes #541 

## Testing

Added unit tests showing previous issue with StringColumn unique values counting

## Notes

* The new map member references the implementation used instead of the interface so we can use the addTo() method
* Now unused private contains() method was removed

Patch:
diff --git a/core/src/main/java/tech/tablesaw/api/Table.java b/core/src/main/java/tech/tablesaw/api/Table.java
index d3de7bd2..27aef7fd 100644
--- a/core/src/main/java/tech/tablesaw/api/Table.java
+++ b/core/src/main/java/tech/tablesaw/api/Table.java
@@ -126,7 +126,7 @@ public class Table extends Relation implements Iterable<Row> {
                 try {
                     Class.forName(clazz);
                 } catch (ClassNotFoundException e) {
-                    new IllegalStateException(e);
+                    throw new IllegalStateException(e);
                 }
             }
         }
diff --git a/core/src/main/java/tech/tablesaw/columns/strings/ByteDictionaryMap.java b/core/src/main/java/tech/tablesaw/columns/strings/ByteDictionaryMap.java
index 31137cb7..35eb09cc 100644
--- a/core/src/main/java/tech/tablesaw/columns/strings/ByteDictionaryMap.java
+++ b/core/src/main/java/tech/tablesaw/columns/strings/ByteDictionaryMap.java
@@ -1,6 +1,5 @@
 package tech.tablesaw.columns.strings;
 
-import it.unimi.dsi.fastutil.bytes.Byte2IntMap;
 import it.unimi.dsi.fastutil.bytes.Byte2IntOpenHashMap;
 import it.unimi.dsi.fastutil.bytes.Byte2ObjectMap;
 import it.unimi.dsi.fastutil.bytes.Byte2ObjectOpenHashMap;
@@ -48,13 +47,16 @@ public class ByteDictionaryMap implements DictionaryMap {
 
     private final AtomicInteger nextIndex = new AtomicInteger(DEFAULT_RETURN_VALUE);
 
-    // we maintain two maps, one from strings to keys, and the second from keys to strings.
+    // we maintain 3 maps, one from strings to keys, one from keys to strings, and one from key to count of values
     private final Byte2ObjectMap<String> keyToValue = new Byte2ObjectOpenHashMap<>();
 
     private final Object2ByteOpenHashMap<String> valueToKey = new Object2ByteOpenHashMap<>();
+    
+    private final Byte2IntOpenHashMap keyToCount = new Byte2IntOpenHashMap();
 
     public ByteDictionaryMap() {
         valueToKey.defaultReturnValue(DEFAULT_RETURN_VALUE);
+        keyToCount.defaultReturnValue(0);
     }
 
     private void put(byte key, String value) {
@@ -87,16 +89,6 @@ public class ByteDictionaryMap implements DictionaryMap {
         return values.getByte(rowIndex);
     }
 
-    /**
-     * Returns true if we have seen this stringValue before, and it hasn't been removed.
-     * <p>
-     * NOTE: An answer of true does not imply that the column still contains the value, only that
-     * it is in the dictionary map
-     */
-    private boolean contains(String stringValue) {
-        return valueToKey.containsKey(stringValue);
-    }
-
     private Set<String> categories() {
         return valueToKey.keySet();
     }
@@ -129,17 +121,7 @@ public class ByteDictionaryMap implements DictionaryMap {
     }
 
     public int countOccurrences(String value) {
-        if (!contains(value)) {
-            return 0;
-        }
-        byte key = getKeyForValue(value);
-        int count = 0;
-        for (byte k : values) {
-            if (k == key) {
-                count++;
-            }
-        }
-        return count;
+        return keyToCount.get(getKeyForValue(value));
     }
 
     public Set<String> asSet() {
@@ -212,6 +194,7 @@ public class ByteDictionaryMap implements DictionaryMap {
             put(key, value);
         }
         values.add(key);
+        keyToCount.addTo(key, 1);
     }
 
     private byte getValueId() throws NoKeysAvailableException {
@@ -251,7 +234,13 @@ public class ByteDictionaryMap implements DictionaryMap {
             valueId = getValueId();
             put(valueId, str);
         }
-        values.set(rowIndex, valueId);
+        byte oldKey = values.set(rowIndex, valueId);
+        keyToCount.addTo(valueId, 1);
+        if (keyToCount.addTo(oldKey, -1) == 1) {
+            String obsoleteValue = keyToValue.remove(oldKey);
+            valueToKey.removeByte(obsoleteValue);
+            keyToCount.remove(oldKey);
+        }
     }
 
     @Override
@@ -259,6 +248,7 @@ public class ByteDictionaryMap implements DictionaryMap {
         values.clear();
         keyToValue.clear();
         valueToKey.clear();
+        keyToCount.clear();
     }
 
     @Override
@@ -273,24 +263,11 @@ public class ByteDictionaryMap implements DictionaryMap {
         Table t = Table.create("Column: " + columnName);
         StringColumn categories = StringColumn.create("Category");
         IntColumn counts = IntColumn.create("Count");
-
-        Byte2IntMap valueToCount = new Byte2IntOpenHashMap();
-
-        for (byte next : values) {
-            if (valueToCount.containsKey(next)) {
-                valueToCount.put(next, valueToCount.get(next) + 1);
-            } else {
-                valueToCount.put(next, 1);
-            }
-        }
-        for (Map.Entry<Byte, Integer> entry : valueToCount.byte2IntEntrySet()) {
+        // Now uses the keyToCount map
+        for (Map.Entry<Byte, Integer> entry : keyToCount.byte2IntEntrySet()) {
             categories.append(getValueForKey(entry.getKey()));
             counts.append(entry.getValue());
         }
-        if (countMissing() > 0) {
-            categories.append("* missing values");
-            counts.append(countMissing());
-        }
         t.addColumns(categories);
         t.addColumns(counts);
         return t;
@@ -354,13 +331,7 @@ public class ByteDictionaryMap implements DictionaryMap {
      */
     @Override
     public int countMissing() {
-        int count = 0;
-        for (int i = 0; i < size(); i++) {
-            if (MISSING_VALUE == getKeyForIndex(i)) {
-                count++;
-            }
-        }
-        return count;
+        return keyToCount.get(MISSING_VALUE);
     }
 
     @Override
diff --git a/core/src/main/java/tech/tablesaw/columns/strings/IntDictionaryMap.java b/core/src/main/java/tech/tablesaw/columns/strings/IntDictionaryMap.java
index 14ea6a1b..d4d6ecf0 100644
--- a/core/src/main/java/tech/tablesaw/columns/strings/IntDictionaryMap.java
+++ b/core/src/main/java/tech/tablesaw/columns/strings/IntDictionaryMap.java
@@ -1,6 +1,5 @@
 package tech.tablesaw.columns.strings;
 
-import it.unimi.dsi.fastutil.ints.Int2IntMap;
 import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@@ -48,16 +47,19 @@ public class IntDictionaryMap implements DictionaryMap {
 
     private final AtomicInteger nextIndex = new AtomicInteger(DEFAULT_RETURN_VALUE);
 
-    // we maintain two maps, one from strings to keys, and the second from keys to strings.
+    // we maintain 3 maps, one from strings to keys, one from keys to strings, and one from key to count of values
     private final Int2ObjectMap<String> keyToValue = new Int2ObjectOpenHashMap<>();
 
     private final Object2IntOpenHashMap<String> valueToKey = new Object2IntOpenHashMap<>();
 
+    private final Int2IntOpenHashMap keyToCount = new Int2IntOpenHashMap();
+
     /**
      * Returns a new DictionaryMap that is a deep copy of the original
      */
     IntDictionaryMap(DictionaryMap original) throws NoKeysAvailableException {
         valueToKey.defaultReturnValue(DEFAULT_RETURN_VALUE);
+        keyToCount.defaultReturnValue(0);
 
         for (int i = 0; i < original.size(); i++) {
             String value = original.getValueForIndex(i);
@@ -95,16 +97,6 @@ public class IntDictionaryMap implements DictionaryMap {
         return values.getInt(rowIndex);
     }
 
-    /**
-     * Returns true if we have seen this stringValue before, and it hasn't been removed.
-     * <p>
-     * NOTE: An answer of true does not imply that the column still contains the value, only that
-     * it is in the dictionary map
-     */
-    private boolean contains(String stringValue) {
-        return valueToKey.containsKey(stringValue);
-    }
-
     private Set<String> categories() {
         return valueToKey.keySet();
     }
@@ -133,17 +125,7 @@ public class IntDictionaryMap implements DictionaryMap {
     }
 
     public int countOccurrences(String value) {
-        if (!contains(value)) {
-            return 0;
-        }
-        int key = getKeyForValue(value);
-        int count = 0;
-        for (int k : values) {
-            if (k == key) {
-                count++;
-            }
-        }
-        return count;
+        return keyToCount.get(getKeyForValue(value));
     }
 
     public Set<String> asSet() {
@@ -220,6 +202,7 @@ public class IntDictionaryMap implements DictionaryMap {
             put(key, value);
         }
         values.add(key);
+        keyToCount.addTo(key, 1);
     }
 
     private int getValueId() throws NoKeysAvailableException {
@@ -258,7 +241,13 @@ public class IntDictionaryMap implements DictionaryMap {
             valueId = getValueId();
             put(valueId, str);
         }
-        values.set(rowIndex, valueId);
+        int oldKey = values.set(rowIndex, valueId);
+        keyToCount.addTo(valueId, 1);
+        if (keyToCount.addTo(oldKey, -1) == 1) {
+            String obsoleteValue = keyToValue.remove(oldKey);
+            valueToKey.removeInt(obsoleteValue);
+            keyToCount.remove(oldKey);
+        }
     }
 
     @Override
@@ -266,6 +255,7 @@ public class IntDictionaryMap implements DictionaryMap {
         values.clear();
         keyToValue.clear();
         valueToKey.clear();
+        keyToCount.clear();
     }
 
     /**
@@ -275,24 +265,11 @@ public class IntDictionaryMap implements DictionaryMap {
         Table t = Table.create("Column: " + columnName);
         StringColumn categories = StringColumn.create("Category");
         IntColumn counts = IntColumn.create("Count");
-
-        Int2IntMap valueToCount = new Int2IntOpenHashMap();
-
-        for (int next : values) {
-            if (valueToCount.containsKey(next)) {
-                valueToCount.put(next, valueToCount.get(next) + 1);
-            } else {
-                valueToCount.put(next, 1);
-            }
-        }
-        for (Map.Entry<Integer, Integer> entry : valueToCount.int2IntEntrySet()) {
+        // Now uses the keyToCount map
+        for (Map.Entry<Integer, Integer> entry : keyToCount.int2IntEntrySet()) {
             categories.append(getValueForKey(entry.getKey()));
             counts.append(entry.getValue());
         }
-        if (countMissing() > 0) {
-            categories.append("* missing values");
-            counts.append(countMissing());
-        }
         t.addColumns(categories);
         t.addColumns(counts);
         return t;
@@ -356,13 +333,7 @@ public class IntDictionaryMap implements DictionaryMap {
      */
     @Override
     public int countMissing() {
-        int count = 0;
-        for (int i = 0; i < size(); i++) {
-            if (MISSING_VALUE == getKeyForIndex(i)) {
-                count++;
-            }
-        }
-        return count;
+        return keyToCount.get(MISSING_VALUE);
     }
 
     @Override
diff --git a/core/src/main/java/tech/tablesaw/columns/strings/ShortDictionaryMap.java b/core/src/main/java/tech/tablesaw/columns/strings/ShortDictionaryMap.java
index 042c53ce..deabf940 100644
--- a/core/src/main/java/tech/tablesaw/columns/strings/ShortDictionaryMap.java
+++ b/core/src/main/java/tech/tablesaw/columns/strings/ShortDictionaryMap.java
@@ -1,7 +1,6 @@
 package tech.tablesaw.columns.strings;
 
 import it.unimi.dsi.fastutil.objects.Object2ShortOpenHashMap;
-import it.unimi.dsi.fastutil.shorts.Short2IntMap;
 import it.unimi.dsi.fastutil.shorts.Short2IntOpenHashMap;
 import it.unimi.dsi.fastutil.shorts.Short2ObjectMap;
 import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap;
@@ -48,16 +47,19 @@ public class ShortDictionaryMap implements DictionaryMap {
 
     private final AtomicInteger nextIndex = new AtomicInteger(DEFAULT_RETURN_VALUE);
 
-    // we maintain two maps, one from strings to keys, and the second from keys to strings.
+    // we maintain 3 maps, one from strings to keys, one from keys to strings, and one from key to count of values
     private final Short2ObjectMap<String> keyToValue = new Short2ObjectOpenHashMap<>();
 
     private final Object2ShortOpenHashMap<String> valueToKey = new Object2ShortOpenHashMap<>();
+    
+    private final Short2IntOpenHashMap keyToCount = new Short2IntOpenHashMap();
 
     /**
      * Returns a new DictionaryMap that is a deep copy of the original
      */
     ShortDictionaryMap(DictionaryMap original) throws NoKeysAvailableException {
         valueToKey.defaultReturnValue(DEFAULT_RETURN_VALUE);
+        keyToCount.defaultReturnValue(0);
 
         for (int i = 0; i < original.size(); i++) {
             String value = original.getValueForIndex(i);
@@ -95,16 +97,6 @@ public class ShortDictionaryMap implements DictionaryMap {
         return values.getShort(rowIndex);
     }
 
-    /**
-     * Returns true if we have seen this stringValue before, and it hasn't been removed.
-     * <p>
-     * NOTE: An answer of true does not imply that the column still contains the value, only that
-     * it is in the dictionary map
-     */
-    private boolean contains(String stringValue) {
-        return valueToKey.containsKey(stringValue);
-    }
-
     private Set<String> categories() {
         return valueToKey.keySet();
     }
@@ -137,17 +129,7 @@ public class ShortDictionaryMap implements DictionaryMap {
     }
 
     public int countOccurrences(String value) {
-        if (!contains(value)) {
-            return 0;
-        }
-        short key = getKeyForValue(value);
-        int count = 0;
-        for (short k : values) {
-            if (k == key) {
-                count++;
-            }
-        }
-        return count;
+        return keyToCount.get(getKeyForValue(value));
     }
 
     public Set<String> asSet() {
@@ -224,6 +206,7 @@ public class ShortDictionaryMap implements DictionaryMap {
             put(key, value);
         }
         values.add(key);
+        keyToCount.addTo(key, 1);
     }
 
     private short getValueId() throws NoKeysAvailableException {
@@ -262,7 +245,13 @@ public class ShortDictionaryMap implements DictionaryMap {
             valueId = getValueId();
             put(valueId, str);
         }
-        values.set(rowIndex, valueId);
+        short oldKey = values.set(rowIndex, valueId);
+        keyToCount.addTo(valueId, 1);
+        if (keyToCount.addTo(oldKey, -1) == 1) {
+            String obsoleteValue = keyToValue.remove(oldKey);
+            valueToKey.removeShort(obsoleteValue);
+            keyToCount.remove(oldKey);
+        }
     }
 
     @Override
@@ -270,6 +259,7 @@ public class ShortDictionaryMap implements DictionaryMap {
         values.clear();
         keyToValue.clear();
         valueToKey.clear();
+        keyToCount.clear();
     }
 
     /**
@@ -279,24 +269,11 @@ public class ShortDictionaryMap implements DictionaryMap {
         Table t = Table.create("Column: " + columnName);
         StringColumn categories = StringColumn.create("Category");
         IntColumn counts = IntColumn.create("Count");
-
-        Short2IntMap valueToCount = new Short2IntOpenHashMap();
-
-        for (short next : values) {
-            if (valueToCount.containsKey(next)) {
-                valueToCount.put(next, valueToCount.get(next) + 1);
-            } else {
-                valueToCount.put(next, 1);
-            }
-        }
-        for (Map.Entry<Short, Integer> entry : valueToCount.short2IntEntrySet()) {
+        // Now uses the keyToCount map
+        for (Map.Entry<Short, Integer> entry : keyToCount.short2IntEntrySet()) {
             categories.append(getValueForKey(entry.getKey()));
             counts.append(entry.getValue());
         }
-        if (countMissing() > 0) {
-            categories.append("* missing values");
-            counts.append(countMissing());
-        }
         t.addColumns(categories);
         t.addColumns(counts);
         return t;
@@ -360,13 +337,7 @@ public class ShortDictionaryMap implements DictionaryMap {
      */
     @Override
     public int countMissing() {
-        int count = 0;
-        for (int i = 0; i < size(); i++) {
-            if (MISSING_VALUE == getKeyForIndex(i)) {
-                count++;
-            }
-        }
-        return count;
+        return keyToCount.get(MISSING_VALUE);
     }
 
     @Override
diff --git a/core/src/main/java/tech/tablesaw/io/DataFrameWriter.java b/core/src/main/java/tech/tablesaw/io/DataFrameWriter.java
index ecb5bc05..a3a575ea 100644
--- a/core/src/main/java/tech/tablesaw/io/DataFrameWriter.java
+++ b/core/src/main/java/tech/tablesaw/io/DataFrameWriter.java
@@ -67,7 +67,7 @@ public class DataFrameWriter {
         try {
             dataWriter.write(table, new Destination(writer));
         } catch (IOException e) {
-            new IllegalStateException(e);
+            throw new IllegalStateException(e);
         }
         return writer.toString();
     }


Test patch:
diff --git a/core/src/test/java/tech/tablesaw/api/StringColumnTest.java b/core/src/test/java/tech/tablesaw/api/StringColumnTest.java
index 17ebbf26..635d6f59 100644
--- a/core/src/test/java/tech/tablesaw/api/StringColumnTest.java
+++ b/core/src/test/java/tech/tablesaw/api/StringColumnTest.java
@@ -529,4 +529,110 @@ TODO: fix
         }
         assertArrayEquals(expected, result, 0.000_000_1);
     }
+
+    @Test
+    public void countUniqueSetAfterCreateByteDict() {
+        StringColumn col = StringColumn.create("col1", 2);
+        assertEquals(1, col.countUnique(), "Wrong number of unique values after StringColumn.create");
+        assertEquals(2, col.countMissing(), "Wrong number of missing values after StringColumn.create");
+        col.set(0, "A");
+        col.set(1, "B");
+        assertEquals(2, col.countUnique(), "Wrong number of unique values after Column.set");
+        assertEquals(0, col.countMissing(), "Wrong number of missing values after Column.set");
+        col.clear();
+        assertEquals(0, col.countUnique(), "Wrong number of unique values after Column.clear");
+        assertEquals(0, col.countMissing(), "Wrong number of missing values after Column.clear");
+    }
+
+    @Test
+    public void countUniqueSetAfterAppendByteDict() {
+        StringColumn col = StringColumn.create("col1");
+        assertEquals(0, col.countUnique(), "Wrong number of unique values after StringColumn.create");
+        assertEquals(0, col.countMissing(), "Wrong number of missing values after StringColumn.create");
+        col.append("A");
+        col.append("B");
+        assertEquals(2, col.countUnique(), "Wrong number of unique values after Column.set");
+        assertEquals(0, col.countMissing(), "Wrong number of missing values after Column.set");
+        col.set(0, "C");
+        col.set(1, "D");
+        assertEquals(2, col.countUnique(), "Wrong number of unique values after Column.set");
+        assertEquals(0, col.countMissing(), "Wrong number of missing values after Column.set");
+        col.clear();
+        assertEquals(0, col.countUnique(), "Wrong number of unique values after Column.clear");
+        assertEquals(0, col.countMissing(), "Wrong number of missing values after Column.clear");
+    }
+
+    @Test
+    public void countUniqueSetAfterCreateShortDict() {
+        int size = Byte.MAX_VALUE - Byte.MIN_VALUE + 1;
+		StringColumn col = StringColumn.create("col1", size);
+        assertEquals(1, col.countUnique(), "Wrong number of unique values after StringColumn.create");
+        assertEquals(size, col.countMissing(), "Wrong number of missing values after StringColumn.create");
+        for (int i = size; --i >= 0; ) {
+            col.set(i, Integer.toString(i));
+        }
+        assertEquals(size, col.countUnique(), "Wrong number of unique values after Column.set");
+        assertEquals(0, col.countMissing(), "Wrong number of missing values after Column.set");
+        col.clear();
+        assertEquals(0, col.countUnique(), "Wrong number of unique values after Column.clear");
+        assertEquals(0, col.countMissing(), "Wrong number of missing values after Column.clear");
+    }
+
+    @Test
+    public void countUniqueSetAfterAppendShortDict() {
+        StringColumn col = StringColumn.create("col1");
+        assertEquals(0, col.countUnique(), "Wrong number of unique values after StringColumn.create");
+        assertEquals(0, col.countMissing(), "Wrong number of missing values after StringColumn.create");
+        int size = Byte.MAX_VALUE - Byte.MIN_VALUE + 1;
+        for (int i = size; --i >= 0; ) {
+            col.append(Integer.toString(i));
+        }
+        assertEquals(size, col.countUnique(), "Wrong number of unique values after Column.set");
+        assertEquals(0, col.countMissing(), "Wrong number of missing values after Column.set");
+        for (int i = size; --i >= 0; ) {
+            col.set(i, "A" + Integer.toString(i));
+        }
+        assertEquals(size, col.countUnique(), "Wrong number of unique values after Column.set");
+        assertEquals(0, col.countMissing(), "Wrong number of missing values after Column.set");
+        col.clear();
+        assertEquals(0, col.countUnique(), "Wrong number of unique values after Column.clear");
+        assertEquals(0, col.countMissing(), "Wrong number of missing values after Column.clear");
+    }
+
+    @Test
+    public void countUniqueSetAfterCreateIntDict() {
+        int size = Short.MAX_VALUE - Short.MIN_VALUE + 1;
+		StringColumn col = StringColumn.create("col1", size);
+        assertEquals(1, col.countUnique(), "Wrong number of unique values after StringColumn.create");
+        assertEquals(size, col.countMissing(), "Wrong number of missing values after StringColumn.create");
+        for (int i = size; --i >= 0; ) {
+            col.set(i, Integer.toString(i));
+        }
+        assertEquals(size, col.countUnique(), "Wrong number of unique values after Column.set");
+        assertEquals(0, col.countMissing(), "Wrong number of missing values after Column.set");
+        col.clear();
+        assertEquals(0, col.countUnique(), "Wrong number of unique values after Column.clear");
+        assertEquals(0, col.countMissing(), "Wrong number of missing values after Column.clear");
+    }
+
+    @Test
+    public void countUniqueSetAfterAppendIntDict() {
+        StringColumn col = StringColumn.create("col1");
+        assertEquals(0, col.countUnique(), "Wrong number of unique values after StringColumn.create");
+        assertEquals(0, col.countMissing(), "Wrong number of missing values after StringColumn.create");
+        int size = Short.MAX_VALUE - Short.MIN_VALUE + 1;
+        for (int i = size; --i >= 0; ) {
+            col.append(Integer.toString(i));
+        }
+        assertEquals(size, col.countUnique(), "Wrong number of unique values after Column.set");
+        assertEquals(0, col.countMissing(), "Wrong number of missing values after Column.set");
+        for (int i = size; --i >= 0; ) {
+            col.set(i, "A" + Integer.toString(i));
+        }
+        assertEquals(size, col.countUnique(), "Wrong number of unique values after Column.set");
+        assertEquals(0, col.countMissing(), "Wrong number of missing values after Column.set");
+        col.clear();
+        assertEquals(0, col.countUnique(), "Wrong number of unique values after Column.clear");
+        assertEquals(0, col.countMissing(), "Wrong number of missing values after Column.clear");
+    }
 }