PowerSet generation is Java and respective unit tests are added (BAEL-3417) (#8284)

* PowerSet generation is Java and respective unit tests are added

* function name is adjusted to the actual ordering name (Reverse Lexicographical ordering)

* Guava example test function is changed

* LazyLoad powerSet (based on Guava implementation) is added

* set is used instead of map.keySet()
This commit is contained in:
mnafshin 2019-12-30 21:09:05 +03:30 committed by maibin
parent 9b35c6194f
commit 70aa0ba234
2 changed files with 545 additions and 0 deletions

View File

@ -0,0 +1,320 @@
package com.baeldung.powerset;
import com.google.common.collect.Sets;
import javax.annotation.Nullable;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
public class PowerSetUtility<T> {
private Map<T, Integer> map = new HashMap<>();
private List<T> reverseMap = new ArrayList<>();
//Lazy Load PowerSet class
private static class PowerSet<E> extends AbstractSet<Set<E>> {
private Map<E, Integer> map = new HashMap<>();
private List<E> reverseMap = new ArrayList<>();
private Set<E> set;
public PowerSet(Set<E> set) {
this.set = set;
initializeMap();
}
abstract class ListIterator<K> implements Iterator<K> {
protected int position = 0;
private int size;
public ListIterator(int size) {
this.size = size;
}
@Override
public boolean hasNext() {
return position < size;
}
}
static class Subset<E> extends AbstractSet<E> {
private Map<E, Integer> map;
private List<E> reverseMap;
private int mask;
public Subset(Map<E, Integer> map, List<E> reverseMap, int mask) {
this.map = map;
this.reverseMap = reverseMap;
this.mask = mask;
}
@Override
public Iterator<E> iterator() {
return new Iterator<E>() {
int remainingSetBits = mask;
@Override
public boolean hasNext() {
return remainingSetBits != 0;
}
@Override
public E next() {
int index = Integer.numberOfTrailingZeros(remainingSetBits);
if (index == 32) {
throw new NoSuchElementException();
}
remainingSetBits &= ~(1 << index);
return reverseMap.get(index);
}
};
}
@Override
public int size() {
return Integer.bitCount(mask);
}
@Override
public boolean contains(@Nullable Object o) {
Integer index = map.get(o);
return index != null && (mask & (1 << index)) != 0;
}
}
@Override
public Iterator<Set<E>> iterator() {
return new ListIterator<Set<E>>(this.size()) {
@Override
public Set<E> next() {
return new Subset<>(map, reverseMap, position++);
}
};
}
@Override
public int size() {
return (1 << this.set.size());
}
@Override
public boolean contains(@Nullable Object obj) {
if (obj instanceof Set) {
Set<?> set = (Set<?>) obj;
return reverseMap.containsAll(set);
}
return false;
}
@Override
public boolean equals(@Nullable Object obj) {
if (obj instanceof PowerSet) {
PowerSet<?> that = (PowerSet<?>) obj;
return set.equals(that.set);//Set equals check to have the same element regardless of the order of the items
}
return super.equals(obj);
}
private void initializeMap() {
int mapId = 0;
for (E c : this.set) {
map.put(c, mapId++);
reverseMap.add(c);
}
}
}
public Set<Set<T>> lazyLoadPowerSet(Set<T> set) {
return new PowerSet<>(set);
}
public Set<Set<T>> recursivePowerSet(Set<T> set) {
if (set.isEmpty()) {
Set<Set<T>> ret = new HashSet<>();
ret.add(set);
return ret;
}
T element = set.iterator().next();
Set<T> subSetWithoutElement = getSubSetWithoutElement(set, element);
Set<Set<T>> powerSetSubSetWithoutElement = recursivePowerSet(subSetWithoutElement);
Set<Set<T>> powerSetSubSetWithElement = addElementToAll(powerSetSubSetWithoutElement, element);
Set<Set<T>> powerSet = new HashSet<>();
powerSet.addAll(powerSetSubSetWithoutElement);
powerSet.addAll(powerSetSubSetWithElement);
return powerSet;
}
public Set<Set<T>> recursivePowerSetIndexRepresentation(Collection<T> set) {
initializeMap(set);
Set<Set<Integer>> powerSetIndices = recursivePowerSetIndexRepresentation(0, set.size());
return unMapIndex(powerSetIndices);
}
private List<List<Boolean>> iterativePowerSetByLoopOverNumbersWithReverseLexicographicalOrder(int n) {
List<List<Boolean>> powerSet = new ArrayList<>();
for (int i = 0; i < (1 << n); i++) {
List<Boolean> subset = new ArrayList<>(n);
for (int j = 0; j < n; j++)
subset.add(((1 << j) & i) > 0);
powerSet.add(subset);
}
return powerSet;
}
private List<List<Boolean>> iterativePowerSetByLoopOverNumbersWithGrayCodeOrder(int n) {
List<List<Boolean>> powerSet = new ArrayList<>();
for (int i = 0; i < (1 << n); i++) {
List<Boolean> subset = new ArrayList<>(n);
for (int j = 0; j < n; j++) {
int grayEquivalent = i ^ (i >> 1);
subset.add(((1 << j) & grayEquivalent) > 0);
}
powerSet.add(subset);
}
return powerSet;
}
public Set<Set<T>> recursivePowerSetBinaryRepresentation(Collection<T> set) {
initializeMap(set);
Set<List<Boolean>> powerSetBoolean = recursivePowerSetBinaryRepresentation(0, set.size());
return unMapBinary(powerSetBoolean);
}
public List<List<T>> iterativePowerSetByLoopOverNumbers(Set<T> set) {
initializeMap(set);
List<List<Boolean>> sets = iterativePowerSetByLoopOverNumbersWithReverseLexicographicalOrder(set.size());
return unMapListBinary(sets);
}
public List<List<T>> iterativePowerSetByLoopOverNumbersMinimalChange(Set<T> set) {
initializeMap(set);
List<List<Boolean>> sets = iterativePowerSetByLoopOverNumbersWithGrayCodeOrder(set.size());
return unMapListBinary(sets);
}
public static int getRankInLexicographicalOrder(List<Boolean> subset) {
int rank = 0;
for (int i = 0; i < subset.size(); i++)
if (subset.get(i))
rank += (1 << (subset.size() - i - 1));
return rank;
}
public static List<Boolean> getSubsetForRankInLexicographicalOrder(int rank, int sizeOfSet) {
Boolean[] subset = new Boolean[sizeOfSet];
for(int j = 0; j < sizeOfSet; j++) {
subset[sizeOfSet - j - 1] = ((rank & (1 << j)) > 0);
}
return Arrays.asList(subset);
}
private Set<Set<Integer>> recursivePowerSetIndexRepresentation(int idx, int n) {
if (idx == n) {
Set<Set<Integer>> empty = new HashSet<>();
empty.add(new HashSet<>());
return empty;
}
Set<Set<Integer>> powerSetSubset = recursivePowerSetIndexRepresentation(idx + 1, n);
Set<Set<Integer>> powerSet = new HashSet<>(powerSetSubset);
for (Set<Integer> s : powerSetSubset) {
HashSet<Integer> subSetIdxInclusive = new HashSet<>(s);
subSetIdxInclusive.add(idx);
powerSet.add(subSetIdxInclusive);
}
return powerSet;
}
private Set<List<Boolean>> recursivePowerSetBinaryRepresentation(int idx, int n) {
if (idx == n) {
Set<List<Boolean>> powerSetOfEmptySet = new HashSet<>();
powerSetOfEmptySet.add(Arrays.asList(new Boolean[n]));
return powerSetOfEmptySet;
}
Set<List<Boolean>> powerSetSubset = recursivePowerSetBinaryRepresentation(idx + 1, n);
Set<List<Boolean>> powerSet = new HashSet<>();
for (List<Boolean> s : powerSetSubset) {
List<Boolean> subSetIdxExclusive = new ArrayList<>(s);
subSetIdxExclusive.set(idx, false);
powerSet.add(subSetIdxExclusive);
List<Boolean> subSetIdxInclusive = new ArrayList<>(s);
subSetIdxInclusive.set(idx, true);
powerSet.add(subSetIdxInclusive);
}
return powerSet;
}
private void initializeMap(Collection<T> collection) {
int mapId = 0;
for (T c : collection) {
map.put(c, mapId++);
reverseMap.add(c);
}
}
private Set<Set<T>> unMapIndex(Set<Set<Integer>> sets) {
Set<Set<T>> ret = new HashSet<>();
for (Set<Integer> s : sets) {
HashSet<T> subset = new HashSet<>();
for (Integer i : s)
subset.add(reverseMap.get(i));
ret.add(subset);
}
return ret;
}
private Set<Set<T>> unMapBinary(Collection<List<Boolean>> sets) {
Set<Set<T>> ret = new HashSet<>();
for (List<Boolean> s : sets) {
HashSet<T> subset = new HashSet<>();
for (int i = 0; i < s.size(); i++)
if (s.get(i))
subset.add(reverseMap.get(i));
ret.add(subset);
}
return ret;
}
private List<List<T>> unMapListBinary(Collection<List<Boolean>> sets) {
List<List<T>> ret = new ArrayList<>();
for (List<Boolean> s : sets) {
List<T> subset = new ArrayList<>();
for (int i = 0; i < s.size(); i++)
if (s.get(i))
subset.add(reverseMap.get(i));
ret.add(subset);
}
return ret;
}
private Set<Set<T>> addElementToAll(Set<Set<T>> powerSetSubSetWithoutElement, T element) {
Set<Set<T>> powerSetSubSetWithElement = new HashSet<>();
for (Set<T> subsetWithoutElement : powerSetSubSetWithoutElement) {
Set<T> subsetWithElement = new HashSet<>(subsetWithoutElement);
subsetWithElement.add(element);
powerSetSubSetWithElement.add(subsetWithElement);
}
return powerSetSubSetWithElement;
}
private Set<T> getSubSetWithoutElement(Set<T> set, T element) {
Set<T> subsetWithoutElement = new HashSet<>();
for (T s : set) {
if (!s.equals(element))
subsetWithoutElement.add(s);
}
return subsetWithoutElement;
}
}

View File

@ -0,0 +1,225 @@
package com.baeldung.powerset;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.hamcrest.collection.IsCollectionWithSize;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
public class PowerSetUtilityUnitTest {
@Test
public void givenSet_WhenGuavaLibraryGeneratePowerSet_ThenItContainsAllSubsets() {
ImmutableSet<String> set = ImmutableSet.of("APPLE", "ORANGE", "MANGO");
Set<Set<String>> powerSet = Sets.powerSet(set);
Assertions.assertEquals((1 << set.size()), powerSet.size());
MatcherAssert.assertThat(powerSet, Matchers.containsInAnyOrder(
ImmutableSet.of(),
ImmutableSet.of("APPLE"),
ImmutableSet.of("ORANGE"),
ImmutableSet.of("APPLE", "ORANGE"),
ImmutableSet.of("MANGO"),
ImmutableSet.of("APPLE", "MANGO"),
ImmutableSet.of("ORANGE", "MANGO"),
ImmutableSet.of("APPLE", "ORANGE", "MANGO")
));
}
@Test
public void givenSet_WhenPowerSetIsLazyLoadGenerated_ThenItContainsAllSubsets() {
Set<String> set = RandomSetOfStringGenerator.generateRandomSet();
Set<Set<String>> powerSet = new PowerSetUtility<String>().lazyLoadPowerSet(set);
//To make sure that the size of power set is (2 power n)
MatcherAssert.assertThat(powerSet, IsCollectionWithSize.hasSize((1 << set.size())));
//To make sure that number of occurrence of each element is (2 power n-1)
Map<String, Integer> counter = new HashMap<>();
for (Set<String> subset : powerSet) {
for (String name : subset) {
int num = counter.getOrDefault(name, 0);
counter.put(name, num + 1);
}
}
counter.forEach((k, v) -> Assertions.assertEquals((1 << (set.size() - 1)), v.intValue()));
}
@Test
public void givenSet_WhenPowerSetIsCalculated_ThenItContainsAllSubsets() {
Set<String> set = RandomSetOfStringGenerator.generateRandomSet();
Set<Set<String>> powerSet = new PowerSetUtility<String>().recursivePowerSet(set);
//To make sure that the size of power set is (2 power n)
MatcherAssert.assertThat(powerSet, IsCollectionWithSize.hasSize((1 << set.size())));
//To make sure that number of occurrence of each element is (2 power n-1)
Map<String, Integer> counter = new HashMap<>();
for (Set<String> subset : powerSet) {
for (String name : subset) {
int num = counter.getOrDefault(name, 0);
counter.put(name, num + 1);
}
}
counter.forEach((k, v) -> Assertions.assertEquals((1 << (set.size() - 1)), v.intValue()));
}
@Test
public void givenSet_WhenPowerSetIsCalculatedRecursiveByIndexRepresentation_ThenItContainsAllSubsets() {
Set<String> set = RandomSetOfStringGenerator.generateRandomSet();
Set<Set<String>> powerSet = new PowerSetUtility<String>().recursivePowerSetIndexRepresentation(set);
//To make sure that the size of power set is (2 power n)
MatcherAssert.assertThat(powerSet, IsCollectionWithSize.hasSize((1 << set.size())));
//To make sure that number of occurrence of each element is (2 power n-1)
Map<String, Integer> counter = new HashMap<>();
for (Set<String> subset : powerSet) {
for (String name : subset) {
int num = counter.getOrDefault(name, 0);
counter.put(name, num + 1);
}
}
counter.forEach((k, v) -> Assertions.assertEquals((1 << (set.size() - 1)), v.intValue()));
}
@Test
public void givenSet_WhenPowerSetIsCalculatedRecursiveByBinaryRepresentation_ThenItContainsAllSubsets() {
Set<String> set = RandomSetOfStringGenerator.generateRandomSet();
Set<Set<String>> powerSet = new PowerSetUtility<String>().recursivePowerSetBinaryRepresentation(set);
//To make sure that the size of power set is (2 power n)
MatcherAssert.assertThat(powerSet, IsCollectionWithSize.hasSize((1 << set.size())));
//To make sure that number of occurrence of each element is (2 power n-1)
Map<String, Integer> counter = new HashMap<>();
for (Set<String> subset : powerSet) {
for (String name : subset) {
int num = counter.getOrDefault(name, 0);
counter.put(name, num + 1);
}
}
counter.forEach((k, v) -> Assertions.assertEquals((1 << (set.size() - 1)), v.intValue()));
}
@Test
public void givenSet_WhenPowerSetIsCalculatedIterativePowerSetByLoopOverNumbers_ThenItContainsAllSubsets() {
Set<String> set = RandomSetOfStringGenerator.generateRandomSet();
List<List<String>> powerSet = new PowerSetUtility<String>().iterativePowerSetByLoopOverNumbers(set);
//To make sure that the size of power set is (2 power n)
MatcherAssert.assertThat(powerSet, IsCollectionWithSize.hasSize((1 << set.size())));
//To make sure that number of occurrence of each element is (2 power n-1)
Map<String, Integer> counter = new HashMap<>();
for (List<String> subset : powerSet) {
for (String name : subset) {
int num = counter.getOrDefault(name, 0);
counter.put(name, num + 1);
}
}
counter.forEach((k, v) -> Assertions.assertEquals((1 << (set.size() - 1)), v.intValue()));
//To make sure that one subset is not generated twice
Assertions.assertEquals(powerSet.size(), new HashSet<>(powerSet).size());
//To make sure that each element in each subset is occurred once
for (List<String> subset : powerSet) {
Assertions.assertEquals(subset.size(), new HashSet<>(subset).size());
}
}
@Test
public void givenSet_WhenPowerSetIsCalculatedIterativePowerSetByLoopOverNumbersMinimalChange_ThenItContainsAllSubsetsInGrayOrder() {
Set<String> set = RandomSetOfStringGenerator.generateRandomSet();
List<List<String>> powerSet = new PowerSetUtility<String>().iterativePowerSetByLoopOverNumbersMinimalChange(set);
//To make sure that the size of power set is (2 power n)
MatcherAssert.assertThat(powerSet, IsCollectionWithSize.hasSize((1 << set.size())));
//To make sure that number of occurrence of each element is (2 power n-1)
Map<String, Integer> counter = new HashMap<>();
for (List<String> subset : powerSet) {
for (String name : subset) {
int num = counter.getOrDefault(name, 0);
counter.put(name, num + 1);
}
}
counter.forEach((k, v) -> Assertions.assertEquals((1 << (set.size() - 1)), v.intValue()));
//To make sure that one subset is not generated twice
Assertions.assertEquals(powerSet.size(), new HashSet<>(powerSet).size());
//To make sure that each element in each subset is occurred once
for (List<String> subset : powerSet) {
Assertions.assertEquals(subset.size(), new HashSet<>(subset).size());
}
//To make sure that difference of consecutive subsets is exactly 1
for(int i=1; i<powerSet.size(); i++) {
int diff = 0;
for (String s : powerSet.get(i - 1))
if (!powerSet.get(i).contains(s))
diff++;
for (String s : powerSet.get(i))
if (!powerSet.get(i - 1).contains(s))
diff++;
Assertions.assertEquals(1, diff);
}
}
@Test
public void givenSubset_WhenPowerSetIsInLexicographicalOrder_ReturnCorrectRank() {
int n = new Random().nextInt(5) + 5; //a number in [5, 10)
for(int i = 0; i < ( 1 << n); i++) {
Boolean[] subset = new Boolean[n];
for(int j=0; j < n; j++) {
subset[n - j - 1] = ((i & (1 << j)) > 0);
}
Assertions.assertEquals(i, PowerSetUtility.getRankInLexicographicalOrder(Arrays.asList(subset)));
}
}
@Test
public void givenRanking_WhenPowerSetIsInLexicographicalOrder_ReturnTheSubset() {
int n = new Random().nextInt(5) + 5; //a number in [5, 10)
List<List<Boolean>> powerSet = new ArrayList<>();
for(int i = 0; i < (1 << n); i++) {
powerSet.add(PowerSetUtility.getSubsetForRankInLexicographicalOrder(i, n));
}
//To make sure that the size of power set is (2 power n)
MatcherAssert.assertThat(powerSet, IsCollectionWithSize.hasSize((1 << n)));
//To make sure that number of occurrence of each index is (2 power n-1)
Map<Integer, Integer> counter = new HashMap<>();
for (List<Boolean> subset : powerSet) {
for (int i = 0; i < subset.size(); i++) {
if(subset.get(i)) {
int num = counter.getOrDefault(i, 0);
counter.put(i, num + 1);
}
}
}
counter.forEach((k, v) -> Assertions.assertEquals((1 << (n - 1)), v.intValue()));
//To make sure that one subset is not generated twice
Assertions.assertEquals(powerSet.size(), new HashSet<>(powerSet).size());
}
static class RandomSetOfStringGenerator {
private static List<String> fruits = Arrays.asList("Apples", "Avocados", "Banana", "Blueberry", "Cherry", "Clementine", "Cucumber", "Date", "Fig",
"Grapefruit"/*, "Grape", "Kiwi", "Lemon", "Mango", "Mulberry", "Melon", "Nectarine", "Olive", "Orange"*/);
static Set<String> generateRandomSet() {
Set<String> set = new HashSet<>();
Random random = new Random();
int size = random.nextInt(fruits.size());
while (set.size() != size) {
set.add(fruits.get(random.nextInt(fruits.size())));
}
return set;
}
}
}