From 5e0375756b0bfeafda47599814bc45906f80fddf Mon Sep 17 00:00:00 2001 From: Nils Bandener Date: Sat, 15 Jun 2024 11:46:13 +0200 Subject: [PATCH] Added HashMap based implementation of CheckList --- .../com/selectivem/check/CheckListImpl.java | 351 ++++++++++++++++-- .../com/selectivem/check/CheckTableImpl.java | 4 - .../check/CheckListRandomizedTest.java | 38 +- .../check/CheckTableRandomizedTest.java | 2 +- 4 files changed, 364 insertions(+), 31 deletions(-) diff --git a/src/main/java/com/selectivem/check/CheckListImpl.java b/src/main/java/com/selectivem/check/CheckListImpl.java index 7daea9c..e4031c9 100644 --- a/src/main/java/com/selectivem/check/CheckListImpl.java +++ b/src/main/java/com/selectivem/check/CheckListImpl.java @@ -34,9 +34,7 @@ package com.selectivem.check; -import java.util.Iterator; -import java.util.NoSuchElementException; -import java.util.Set; +import java.util.*; import java.util.function.Predicate; class CheckListImpl { @@ -46,16 +44,19 @@ static CheckList create(Set elements) { } static CheckList create(Set elements, String elementName) { - if (elements.size() == 2) { - Iterator iter = elements.iterator(); + int size = elements.size(); + if (size == 2) { + Iterator iter = elements.iterator(); return new CheckListImpl.TwoElementCheckList(iter.next(), iter.next(), elementName); + } else if (size >= 800) { + return new CheckListImpl.HashMapCheckList<>(elements, elementName); } else { - return new CheckListImpl.BasicCheckList(elements, elementName); + return new CheckListImpl.ArrayCheckList(elements, elementName); } } - static class TwoElementCheckList implements CheckList { + final static class TwoElementCheckList implements CheckList { private final E e1; private final E e2; @@ -216,7 +217,7 @@ public Iterable iterateUncheckedElements() { } - static class BasicCheckList implements CheckList { + final static class ArrayCheckList implements CheckList { private final BackingCollections.IndexedUnmodifiableSet elements; private final boolean[] checked; @@ -224,7 +225,7 @@ static class BasicCheckList implements CheckList { private int uncheckedCount; private final int size; - BasicCheckList(Set elements, String elementName) { + ArrayCheckList(Set elements, String elementName) { this.elements = BackingCollections.IndexedUnmodifiableSet.of(elements); this.size = this.elements.size(); this.checked = new boolean[this.size]; @@ -366,18 +367,18 @@ public Set getCheckedElements() { @Override public boolean contains(Object o) { - int tablePos = BasicCheckList.this.elements.elementToIndex(o); + int tablePos = ArrayCheckList.this.elements.elementToIndex(o); if (tablePos == -1) { return false; } else { - return BasicCheckList.this.checked[tablePos]; + return ArrayCheckList.this.checked[tablePos]; } } @Override public Iterator iterator() { - int tableSize = BasicCheckList.this.elements.size(); + int tableSize = ArrayCheckList.this.elements.size(); return new Iterator() { @@ -404,14 +405,14 @@ public E next() { throw new NoSuchElementException(); } - E element = (E) BasicCheckList.this.elements.indexToElement(pos); + E element = (E) ArrayCheckList.this.elements.indexToElement(pos); ready = false; return element; } int findNext(int start) { for (int i = start; i < tableSize; i++) { - if (BasicCheckList.this.checked[i]) { + if (ArrayCheckList.this.checked[i]) { return i; } } @@ -441,18 +442,18 @@ public Set getUncheckedElements() { @Override public boolean contains(Object o) { - int tablePos = BasicCheckList.this.elements.elementToIndex(o); + int tablePos = ArrayCheckList.this.elements.elementToIndex(o); if (tablePos == -1) { return false; } else { - return !BasicCheckList.this.checked[tablePos]; + return !ArrayCheckList.this.checked[tablePos]; } } @Override public Iterator iterator() { - int tableSize = BasicCheckList.this.elements.size(); + int tableSize = ArrayCheckList.this.elements.size(); return new Iterator() { @@ -469,14 +470,14 @@ public E next() { throw new NoSuchElementException(); } - E element = (E) BasicCheckList.this.elements.indexToElement(pos); + E element = (E) ArrayCheckList.this.elements.indexToElement(pos); this.pos = findNext(this.pos + 1); return element; } int findNext(int start) { for (int i = start; i < tableSize; i++) { - if (!BasicCheckList.this.checked[i]) { + if (!ArrayCheckList.this.checked[i]) { return i; } } @@ -505,7 +506,7 @@ public Iterable iterateCheckedElements() { @Override public Iterator iterator() { - int tableSize = BasicCheckList.this.elements.size(); + int tableSize = ArrayCheckList.this.elements.size(); return new Iterator() { @@ -522,14 +523,14 @@ public E next() { throw new NoSuchElementException(); } - E element = (E) BasicCheckList.this.elements.indexToElement(pos); + E element = (E) ArrayCheckList.this.elements.indexToElement(pos); this.pos = findNext(this.pos + 1); return element; } int findNext(int start) { for (int i = start; i < tableSize; i++) { - if (BasicCheckList.this.checked[i]) { + if (ArrayCheckList.this.checked[i]) { return i; } } @@ -555,7 +556,7 @@ public Iterable iterateUncheckedElements() { @Override public Iterator iterator() { - int tableSize = BasicCheckList.this.elements.size(); + int tableSize = ArrayCheckList.this.elements.size(); return new Iterator() { @@ -572,14 +573,14 @@ public E next() { throw new NoSuchElementException(); } - E element = (E) BasicCheckList.this.elements.indexToElement(pos); + E element = (E) ArrayCheckList.this.elements.indexToElement(pos); this.pos = findNext(this.pos + 1); return element; } int findNext(int start) { for (int i = start; i < tableSize; i++) { - if (!BasicCheckList.this.checked[i]) { + if (!ArrayCheckList.this.checked[i]) { return i; } } @@ -592,4 +593,304 @@ int findNext(int start) { } } } + + final static class HashMapCheckList implements CheckList { + private final Set elements; + private final Map checked; + private final String elementName; + private int uncheckedCount; + private final int size; + + HashMapCheckList(Set elements, String elementName) { + this.checked = createCheckedMap(elements); + this.elements = Collections.unmodifiableSet(this.checked.keySet()); + this.size = this.elements.size(); + this.uncheckedCount = this.size; + this.elementName = elementName; + } + + @Override + public boolean check(E element) { + doCheck(element); + + return this.uncheckedCount == 0; + } + + private void doCheck(E element) { + Boolean current = this.checked.get(element); + + if (current == null) { + throw new IllegalArgumentException("Invalid " + elementName + ": " + element); + } + + if (!current) { + this.checked.put(element, Boolean.TRUE); + this.uncheckedCount--; + } + } + + @Override + public void uncheck(E element) { + Boolean current = this.checked.get(element); + + if (current == null) { + throw new IllegalArgumentException("Invalid " + elementName + ": " + element); + } + + if (current) { + this.checked.put(element, Boolean.FALSE); + this.uncheckedCount++; + } + } + + @Override + public void uncheckIfPresent(E element) { + Boolean current = this.checked.get(element); + + if (current == null) { + return; + } + + if (current) { + this.checked.put(element, Boolean.FALSE); + this.uncheckedCount++; + } + } + + @Override + public boolean checkIf(Predicate checkPredicate) { + if (isComplete()) { + return true; + } + + this.checked.forEach((e, v) -> { + if (!v && checkPredicate.test(e)) { + this.checked.put(e, Boolean.TRUE); + this.uncheckedCount--; + } + }); + + return this.uncheckedCount == 0; + } + + @Override + public void uncheckIf(Predicate checkPredicate) { + if (isBlank()) { + return; + } + + this.checked.forEach((e, v) -> { + if (v && checkPredicate.test(e)) { + this.checked.put(e, Boolean.FALSE); + this.uncheckedCount++; + } + }); + } + + @Override + public void checkAll() { + if (isComplete()) { + return; + } + + for (E element : elements) { + this.checked.put(element, Boolean.TRUE); + } + + this.uncheckedCount = 0; + } + + @Override + public void uncheckAll() { + if (isBlank()) { + return; + } + + for (E element : elements) { + this.checked.put(element, Boolean.FALSE); + } + + this.uncheckedCount = this.size; + } + + @Override + public boolean isChecked(E element) { + Boolean current = this.checked.get(element); + + if (current == null) { + throw new IllegalArgumentException("Invalid " + elementName + ": " + element); + } + + return current; + } + + @Override + public boolean isComplete() { + return this.uncheckedCount == 0; + } + + @Override + public boolean isBlank() { + return this.uncheckedCount == this.size; + } + + @Override + public int size() { + return this.size; + } + + @Override + public Set getElements() { + return elements; + } + + @Override + public Set getCheckedElements() { + if (isComplete()) { + return elements; + } else if (isBlank()) { + return BackingCollections.IndexedUnmodifiableSet.empty(); + } else { + return new BackingCollections.UnmodifiableSet() { + @Override + public boolean contains(Object o) { + Boolean checked = HashMapCheckList.this.checked.get(o); + + if (checked == null) { + return false; + } else { + return checked; + } + } + + @Override + public Iterator iterator() { + Iterator delegate = HashMapCheckList.this.elements.iterator(); + + return new Iterator() { + private E next = findNext(); + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public E next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + E result = this.next; + this.next = findNext(); + return result; + } + + E findNext() { + while (delegate.hasNext()) { + E e = delegate.next(); + + if (HashMapCheckList.this.checked.get(e)) { + return e; + } + } + + return null; + } + }; + } + + @Override + public int size() { + return size - uncheckedCount; + } + }; + } + } + + @Override + public Set getUncheckedElements() { + if (isComplete()) { + return BackingCollections.IndexedUnmodifiableSet.empty(); + } else if (isBlank()) { + return elements; + } else { + return new BackingCollections.UnmodifiableSet() { + @Override + public boolean contains(Object o) { + Boolean checked = HashMapCheckList.this.checked.get(o); + + if (checked == null) { + return false; + } else { + return !checked; + } + } + + @Override + public Iterator iterator() { + Iterator delegate = HashMapCheckList.this.elements.iterator(); + + return new Iterator() { + private E next = findNext(); + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public E next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + E result = this.next; + this.next = findNext(); + return result; + } + + E findNext() { + while (delegate.hasNext()) { + E e = delegate.next(); + + if (!HashMapCheckList.this.checked.get(e)) { + return e; + } + } + + return null; + } + }; + } + + @Override + public int size() { + return uncheckedCount; + } + }; + } + } + + @Override + public Iterable iterateCheckedElements() { + return getCheckedElements(); + } + + @Override + public Iterable iterateUncheckedElements() { + return getUncheckedElements(); + } + + static Map createCheckedMap(Set elements) { + HashMap result = new HashMap<>(elements.size()); + + for (E e : elements) { + result.put(e, Boolean.FALSE); + } + + return result; + } + } + } diff --git a/src/main/java/com/selectivem/check/CheckTableImpl.java b/src/main/java/com/selectivem/check/CheckTableImpl.java index 4d0b511..2df22c9 100644 --- a/src/main/java/com/selectivem/check/CheckTableImpl.java +++ b/src/main/java/com/selectivem/check/CheckTableImpl.java @@ -860,7 +860,6 @@ public Set getCheckedRows(C column) { } else { throw new IllegalArgumentException("Invalid column: " + column); } - } @Override @@ -879,7 +878,6 @@ public Iterable iterateCheckedRows(C column) { } else { throw new IllegalArgumentException("Invalid column: " + column); } - } @Override @@ -894,12 +892,10 @@ public Iterable iterateUncheckedRows(C column) { } else { throw new IllegalArgumentException("Invalid column: " + column); } - } @Override public Iterable iterateUncheckedColumns(R row) { - if (rows.isChecked(row)) { return BackingCollections.IndexedUnmodifiableSet.empty(); } else { diff --git a/src/test/java/com/selectivem/check/CheckListRandomizedTest.java b/src/test/java/com/selectivem/check/CheckListRandomizedTest.java index ba1ae9f..95f7e50 100644 --- a/src/test/java/com/selectivem/check/CheckListRandomizedTest.java +++ b/src/test/java/com/selectivem/check/CheckListRandomizedTest.java @@ -41,6 +41,7 @@ import java.util.List; import java.util.Random; import java.util.Set; +import java.util.stream.Collectors; import org.junit.Assert; import org.junit.Test; @@ -58,7 +59,7 @@ public class CheckListRandomizedTest { public static Collection seeds() { ArrayList result = new ArrayList<>(10000); - for (int size : Arrays.asList(1, 2, 3, 4, 6, 10, 20, 60, 100, 500, 1000)) { + for (int size : Arrays.asList(1, 2, 3, 4, 6, 10, 20, 60, 100, 200, 500, 700, 1000)) { for (int seed = 100; seed < 1000; seed++) { result.add(new Params(size, seed)); } @@ -188,6 +189,41 @@ public void basicTest() { } } + @Test + public void checkIf_uncheckIf() { + Random random = new Random(params.seed + 1000); + Set elements = createElements(params, random); + List elementsList = new ArrayList<>(elements); + Collections.shuffle(elementsList, random); + + CheckList subject = CheckList.create(elements); + Set reference = new HashSet<>(); + + subject.checkIf(e -> e.contains("7")); + Set expected = elements.stream().filter(e -> e.contains("7")).collect(Collectors.toSet()); + + Assert.assertEquals(expected, subject.getCheckedElements()); + + subject.uncheckIf(e -> e.contains("9")); + expected.removeAll(elements.stream().filter(e -> e.contains("9")).collect(Collectors.toSet())); + + Assert.assertEquals(expected, subject.getCheckedElements()); + + for (int i = 0; i <= 9; i++) { + String s = i + ""; + subject.checkIf(e -> e.contains(s)); + expected.addAll(elements.stream().filter(e -> e.contains(s)).collect(Collectors.toSet())); + + Assert.assertEquals(expected, subject.getCheckedElements()); + Assert.assertEquals(expected.size() == elements.size(), subject.isComplete()); + } + + subject.uncheckAll(); + Assert.assertEquals(Collections.emptySet(), subject.getCheckedElements()); + subject.checkAll(); + Assert.assertEquals(elements, subject.getCheckedElements()); + } + static Set createElements(Params params, Random random) { int size = params.size; diff --git a/src/test/java/com/selectivem/check/CheckTableRandomizedTest.java b/src/test/java/com/selectivem/check/CheckTableRandomizedTest.java index ab34cb5..3981858 100644 --- a/src/test/java/com/selectivem/check/CheckTableRandomizedTest.java +++ b/src/test/java/com/selectivem/check/CheckTableRandomizedTest.java @@ -79,7 +79,7 @@ public static Collection seeds() { result.add(new Object[] { i, 2, 2 }); } - for (int i = 111; i < 150; i++) { + for (int i = 111; i <= 222; i++) { result.add(new Object[] { i, randomSize(random), randomSize(random) }); }