├── codecov.yml ├── .gitignore ├── .travis.yml ├── src ├── main │ └── java │ │ └── com │ │ └── github │ │ └── dakusui │ │ └── combinatoradix │ │ ├── utils │ │ ├── Predicate.java │ │ ├── Predicates.java │ │ ├── ExceptionFactory.java │ │ └── InternalUtils.java │ │ ├── SimpleCartesianEnumerator.java │ │ ├── Shuffler.java │ │ ├── Enumerators.java │ │ ├── tuple │ │ ├── AttrValue.java │ │ └── CartesianEnumerator.java │ │ ├── Domains.java │ │ ├── Permutator.java │ │ ├── CartesianEnumeratorAdaptor.java │ │ ├── Enumerator.java │ │ ├── HomogeniousCombinator.java │ │ ├── Cartesianator.java │ │ └── Combinator.java └── test │ └── java │ └── com │ └── github │ └── dakusui │ └── combinatoradix │ ├── utils │ ├── TestBase.java │ └── TestUtils.java │ ├── InternalUtilsTest.java │ ├── EnumeratorTestBase.java │ ├── EnumeratorErrorTest.java │ ├── CartesianEnumeratorAdaptorTest.java │ ├── CombinatorTestBase.java │ ├── BigNumberTest.java │ ├── ShufflerTest.java │ ├── CartesianEnumeratorTest.java │ ├── CartesianatorTest.java │ ├── CombinatorTest.java │ ├── HomogeniousCombinatorTest.java │ └── PermutatorTest.java ├── combinatoradix.iml ├── pom.xml └── README.md /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | token: 6823352d-7453-47d0-97a8-289f3b3be133 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.settings 3 | /.classpath 4 | /.project 5 | enumerator.iml 6 | .idea/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | sudo: false # faster builds 3 | 4 | after_success: 5 | - bash <(curl -s https://codecov.io/bash) 6 | 7 | -------------------------------------------------------------------------------- /src/main/java/com/github/dakusui/combinatoradix/utils/Predicate.java: -------------------------------------------------------------------------------- 1 | package com.github.dakusui.combinatoradix.utils; 2 | 3 | public interface Predicate { 4 | Predicate NOT_NULL = new Predicate() { 5 | @Override 6 | public boolean test(Object value) { 7 | return value != null; 8 | } 9 | }; 10 | 11 | boolean test(T value); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/com/github/dakusui/combinatoradix/utils/TestBase.java: -------------------------------------------------------------------------------- 1 | package com.github.dakusui.combinatoradix.utils; 2 | 3 | import org.junit.jupiter.api.AfterEach; 4 | import org.junit.jupiter.api.BeforeEach; 5 | 6 | public class TestBase { 7 | @BeforeEach 8 | public void suppressStdOutErrIfRunUnderSurefire() { 9 | TestUtils.suppressStdOutErrIfRunUnderSurefire(); 10 | } 11 | 12 | @AfterEach 13 | public void restoreStdOutErr() { 14 | TestUtils.restoreStdOutErr(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/github/dakusui/combinatoradix/SimpleCartesianEnumerator.java: -------------------------------------------------------------------------------- 1 | package com.github.dakusui.combinatoradix; 2 | 3 | import java.util.LinkedHashMap; 4 | import java.util.Map; 5 | 6 | public class SimpleCartesianEnumerator 7 | extends CartesianEnumeratorAdaptor, K, V> { 8 | protected SimpleCartesianEnumerator( 9 | Domains domains) { 10 | super(domains); 11 | } 12 | 13 | @Override protected Map createMap() { 14 | return new LinkedHashMap(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/github/dakusui/combinatoradix/utils/Predicates.java: -------------------------------------------------------------------------------- 1 | package com.github.dakusui.combinatoradix.utils; 2 | 3 | public enum Predicates { 4 | ; 5 | 6 | @SuppressWarnings("unchecked") 7 | public static Predicate notNull() { 8 | return Predicate.NOT_NULL; 9 | } 10 | 11 | public static > Predicate inRange(final E inclusive, final E exclusive) { 12 | return new Predicate() { 13 | @Override 14 | public boolean test(E value) { 15 | return inclusive.compareTo(value) <= 0 && value.compareTo(exclusive) < 0; 16 | } 17 | }; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/com/github/dakusui/combinatoradix/InternalUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dakusui.combinatoradix; 2 | 3 | import com.github.dakusui.combinatoradix.utils.InternalUtils; 4 | import com.github.dakusui.combinatoradix.utils.TestBase; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import static org.junit.jupiter.api.Assertions.assertEquals; 8 | 9 | public class InternalUtilsTest extends TestBase { 10 | @Test 11 | public void givenInvalidArguments$when_nCk$then0() { 12 | assertEquals(0, InternalUtils.nCk(2, 3)); 13 | } 14 | 15 | @Test 16 | public void givenInvalidArguments$when_nPk$then0() { 17 | assertEquals(0, InternalUtils.nPk(2, 3)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/com/github/dakusui/combinatoradix/EnumeratorTestBase.java: -------------------------------------------------------------------------------- 1 | package com.github.dakusui.combinatoradix; 2 | 3 | import com.github.dakusui.combinatoradix.utils.TestBase; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | class EnumeratorTestBase extends TestBase { 9 | static List testset1() { 10 | List ret = new ArrayList(); 11 | ret.add("A"); 12 | ret.add("B"); 13 | ret.add("C"); 14 | ret.add("D"); 15 | ret.add("E"); 16 | return ret; 17 | } 18 | 19 | static List testset2() { 20 | List ret = new ArrayList(); 21 | ret.add("A"); 22 | ret.add("B"); 23 | ret.add("C"); 24 | return ret; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/github/dakusui/combinatoradix/Shuffler.java: -------------------------------------------------------------------------------- 1 | package com.github.dakusui.combinatoradix; 2 | 3 | import com.github.dakusui.combinatoradix.utils.InternalUtils; 4 | 5 | import java.util.List; 6 | import java.util.Random; 7 | 8 | public class Shuffler extends Permutator { 9 | 10 | private final Random random; 11 | private final long numPossibleSeqs; 12 | 13 | protected Shuffler(List items, long size, Random random) { 14 | super(items, items.size(), size); 15 | this.random = random; 16 | this.numPossibleSeqs = InternalUtils.nPk(items.size(), items.size()); 17 | } 18 | 19 | @Override 20 | protected List getElement(long index) { 21 | //// 22 | // In theory random.nextLong can return Long.MIN_VALUE. And Math.abs(Long.MIN_VALUE) 23 | // returns Long.MIN_VALUE. (This is not a typo. Refer to API reference.) 24 | long nextLong; 25 | //noinspection StatementWithEmptyBody 26 | for (nextLong = Long.MIN_VALUE; nextLong == Long.MIN_VALUE; nextLong = this.random.nextLong()) { 27 | } 28 | return super.getElement(Math.abs(nextLong) % numPossibleSeqs); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/github/dakusui/combinatoradix/Enumerators.java: -------------------------------------------------------------------------------- 1 | package com.github.dakusui.combinatoradix; 2 | 3 | import java.util.List; 4 | import java.util.Random; 5 | 6 | public enum Enumerators { 7 | ; 8 | public static Shuffler shuffler(List list, long size, long randomSeed) { 9 | return shuffler(list, size, new Random(randomSeed)); 10 | } 11 | 12 | public static Shuffler shuffler(List list, long size, Random random) { 13 | return new Shuffler(list, size, random); 14 | } 15 | 16 | public static Permutator permutator(List list, int k) { 17 | return new Permutator(list, k); 18 | } 19 | 20 | public static Combinator combinator(List list, int k) { 21 | return new Combinator(list, k); 22 | } 23 | 24 | public static HomogeniousCombinator homogeniousCombinator(List list, int k) { 25 | return new HomogeniousCombinator(list, k); 26 | } 27 | 28 | 29 | public static Cartesianator cartesianator(List> lists) { 30 | return new Cartesianator(lists); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/com/github/dakusui/combinatoradix/EnumeratorErrorTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dakusui.combinatoradix; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.Collections; 6 | import java.util.Iterator; 7 | import java.util.List; 8 | import java.util.NoSuchElementException; 9 | 10 | import static org.junit.jupiter.api.Assertions.*; 11 | 12 | public class EnumeratorErrorTest { 13 | Enumerator.Base enumerator = new Permutator<>(Collections.singletonList("Hello"), 1); 14 | 15 | @Test 16 | public void goOverLimitByNextMethod() { 17 | Iterator> i = enumerator.iterator(); 18 | assertTrue(i.hasNext()); 19 | assertEquals("Hello", i.next().get(0)); 20 | assertFalse(i.hasNext()); 21 | assertThrows(NoSuchElementException.class, i::next); 22 | } 23 | 24 | @Test 25 | public void goOverLimitByGetMethod() { 26 | assertEquals(1, enumerator.size()); 27 | assertThrows(IndexOutOfBoundsException.class, () -> enumerator.get(1)); 28 | } 29 | 30 | @Test 31 | public void tryToRemoveAndMakeSureExceptionIsThrown() { 32 | assertThrows(UnsupportedOperationException.class, () -> enumerator.iterator().remove()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/github/dakusui/combinatoradix/tuple/AttrValue.java: -------------------------------------------------------------------------------- 1 | package com.github.dakusui.combinatoradix.tuple; 2 | 3 | public class AttrValue { 4 | private final T attr; 5 | private final U value; 6 | 7 | public AttrValue(T attr, U value) { 8 | this.attr = attr; 9 | this.value = value; 10 | } 11 | 12 | public T attr() { 13 | return this.attr; 14 | } 15 | 16 | public U value() { 17 | return this.value; 18 | } 19 | 20 | @Override 21 | public int hashCode() { 22 | return ((this.attr == null) ? 0 : this.attr.hashCode()) 23 | + ((this.value == null) ? 0 : this.value.hashCode()); 24 | } 25 | 26 | @SuppressWarnings("unchecked") 27 | @Override 28 | public boolean equals(Object anotherObject) { 29 | if (!(anotherObject instanceof AttrValue)) 30 | return false; 31 | AttrValue another = (AttrValue) anotherObject; 32 | return equals(this.attr, another.attr) && equals(this.value, another.value); 33 | } 34 | 35 | public String toString() { 36 | return "(" + this.attr + "," + this.value + ")"; 37 | } 38 | 39 | static boolean equals(Object a, Object b) { 40 | if (a == null) 41 | return b == null; 42 | return a.equals(b); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/github/dakusui/combinatoradix/Domains.java: -------------------------------------------------------------------------------- 1 | package com.github.dakusui.combinatoradix; 2 | 3 | import java.util.*; 4 | 5 | public interface Domains { 6 | List getDomainNames(); 7 | 8 | List getDomain(T domainName); 9 | 10 | class DomainsImpl extends LinkedHashMap> 11 | implements Domains { 12 | @Override 13 | public List getDomainNames() { 14 | return new LinkedList<>(this.keySet()); 15 | } 16 | 17 | @Override 18 | public List getDomain(T domainName) { 19 | return Collections.unmodifiableList(this.get(domainName)); 20 | } 21 | } 22 | 23 | class Builder { 24 | private final Map> map; 25 | 26 | public Builder() { 27 | this(new LinkedHashMap<>()); 28 | } 29 | 30 | public Builder(Map> map) { 31 | this.map = map; 32 | } 33 | 34 | @SafeVarargs 35 | public final Builder addDomain(T key, U... values) { 36 | this.map.put(key, Arrays.asList(values)); 37 | return this; 38 | } 39 | 40 | public Domains build() { 41 | DomainsImpl ret = new DomainsImpl<>(); 42 | for (T k : this.map.keySet()) { 43 | ret.put(k, this.map.get(k)); 44 | } 45 | return ret; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/com/github/dakusui/combinatoradix/utils/TestUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.dakusui.combinatoradix.utils; 2 | 3 | import java.io.IOException; 4 | import java.io.OutputStream; 5 | import java.io.PrintStream; 6 | 7 | public enum TestUtils { 8 | ; 9 | 10 | static final PrintStream STDOUT = System.out; 11 | static final PrintStream STDERR = System.err; 12 | public static final PrintStream stdout = createStdOut(); 13 | 14 | private static PrintStream createStdOut() { 15 | if (System.getProperty("COMBINATORADIX_DEBUG") == null) { 16 | return new PrintStream(new OutputStream() { 17 | @Override 18 | public void write(int b) throws IOException { 19 | } 20 | }); 21 | } else { 22 | return System.out; 23 | } 24 | } 25 | 26 | public static void suppressStdOutErrIfRunUnderSurefire() { 27 | if (TestUtils.isRunUnderSurefire()) { 28 | System.setOut(new PrintStream(new OutputStream() { 29 | @Override 30 | public void write(int b) throws IOException { 31 | } 32 | })); 33 | System.setErr(new PrintStream(new OutputStream() { 34 | @Override 35 | public void write(int b) throws IOException { 36 | } 37 | })); 38 | } 39 | } 40 | 41 | public static void restoreStdOutErr() { 42 | System.setOut(STDOUT); 43 | System.setErr(STDERR); 44 | } 45 | 46 | public static boolean isRunUnderSurefire() { 47 | return System.getProperty("surefire.real.class.path") != null; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/github/dakusui/combinatoradix/utils/ExceptionFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.dakusui.combinatoradix.utils; 2 | 3 | public interface ExceptionFactory { 4 | ExceptionFactory NPE = new ExceptionFactory() { 5 | @Override 6 | public TT create(String format, Object... args) { 7 | throw new NullPointerException(String.format(format, args)); 8 | } 9 | }; 10 | 11 | ExceptionFactory ILLEGAL_ARGUMENT = new ExceptionFactory() { 12 | @Override 13 | public TT create(String format, Object... args) { 14 | throw new IllegalArgumentException(String.format(format, args)); 15 | } 16 | }; 17 | 18 | ExceptionFactory INDEX_OUT_OF_BOUNDS = new ExceptionFactory() { 19 | @Override 20 | public TT create(String format, Object... args) { 21 | throw new IndexOutOfBoundsException(String.format(format, args)); 22 | } 23 | }; 24 | 25 | TT create(String format, Object... args); 26 | 27 | enum Utils { 28 | ; 29 | 30 | public static ExceptionFactory npe() { 31 | return NPE; 32 | } 33 | 34 | public static ExceptionFactory illegalArgument() { 35 | return ILLEGAL_ARGUMENT; 36 | } 37 | public static ExceptionFactory indexOutOfBounds() { 38 | return INDEX_OUT_OF_BOUNDS; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/github/dakusui/combinatoradix/Permutator.java: -------------------------------------------------------------------------------- 1 | package com.github.dakusui.combinatoradix; 2 | 3 | import com.github.dakusui.combinatoradix.utils.InternalUtils; 4 | 5 | import java.util.ArrayList; 6 | import java.util.LinkedList; 7 | import java.util.List; 8 | 9 | /** 10 | * @param 11 | */ 12 | public class Permutator extends Enumerator.Base { 13 | public Permutator(List items, int k) { 14 | this(items, k, calculateSize(items, k)); 15 | } 16 | 17 | protected Permutator(List items, int k, long size) { 18 | super(items, k, size); 19 | } 20 | 21 | @Override 22 | protected List getElement(long index) { 23 | int[] seq = index2locator(index, this.symbols.size() - k + 1, k); 24 | List tmp = new LinkedList(this.symbols); 25 | List ret = new ArrayList(); 26 | for (int i : seq) { 27 | ret.add(tmp.remove(i)); 28 | } 29 | return ret; 30 | } 31 | 32 | /* work 33 | * 0:[a, b, c] -> [a, b] -> [b] -> []; 34 | * 1:[a, c, b] -> [a, c] -> [a] -> []; 35 | * 2:[b, a, c] -> [b, a] -> [b] -> []; 0 -> 36 | * 3:[b, c, a] 37 | * 4:[c, a, b] 38 | * 5:[c, b, a] 39 | */ 40 | @Override 41 | protected long calculateIndexOf(List element) { 42 | long ret = 0; 43 | List work = new ArrayList(symbols); 44 | long c = 1; 45 | for (T each : element) { 46 | ret = c * ret + work.indexOf(each); 47 | work.remove(each); 48 | c = work.size(); 49 | } 50 | return ret; 51 | } 52 | 53 | private static long calculateSize(List items, int k) { 54 | return InternalUtils.nPk(items.size(), k); 55 | } 56 | 57 | private static int[] index2locator(long index, int lsradix, int k) { 58 | int[] seq = new int[k]; 59 | long c = index; 60 | int radix = lsradix; 61 | for (int i = seq.length - 1; i >= 0; i--) { 62 | seq[i] = (int) (c % radix); 63 | c /= radix; 64 | radix++; 65 | } 66 | return seq; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /combinatoradix.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/main/java/com/github/dakusui/combinatoradix/utils/InternalUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.dakusui.combinatoradix.utils; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public enum InternalUtils { 7 | ; 8 | 9 | public static long nPk(long n, long k) { 10 | long ret = 1; 11 | for (long i = n; i > n - k; i--) { 12 | if (i > Long.MAX_VALUE / ret) { 13 | throw new IllegalArgumentException(String.format("Overflow. Too big numbers are used %sP%s: %d * %d", n, k, ret, i)); 14 | } 15 | ret *= i; 16 | } 17 | return ret; 18 | } 19 | 20 | public static long nHk(int n, int k) { 21 | return nCk(n + k - 1, k); 22 | } 23 | 24 | public static long nCk(long n, long k) { 25 | long ret = 1; 26 | long j = 1; 27 | for (long i = n; i > n - k; i--) { 28 | if (i > Long.MAX_VALUE / ret) { 29 | throw new IllegalArgumentException(String.format("Overflow. Too big numbers are used %sC%s: %d * %d", n, k, ret, i)); 30 | } 31 | ret *= i; 32 | ret /= j; 33 | j++; 34 | } 35 | return ret; 36 | } 37 | 38 | @SuppressWarnings("unchecked") 39 | public static T check(T value, Predicate predicate, ExceptionFactory exceptionFactory, String format, Object... args) { 40 | if (!predicate.test(value)) 41 | throw exceptionFactory.create(format, args); 42 | return value; 43 | } 44 | 45 | public static void checkCondition(boolean b, String message, Object... args) { 46 | if (!b) 47 | throw new IllegalArgumentException(String.format(message, args)); 48 | } 49 | 50 | public static int[] chop(int[] in) { 51 | int[] arr = new int[in.length - 1]; 52 | System.arraycopy(in, 0, arr, 0, arr.length); 53 | return arr; 54 | } 55 | 56 | public static int sumAll(int[] values) { 57 | int ret = 0; 58 | for (int each : values) 59 | ret += each; 60 | return ret; 61 | } 62 | 63 | public static List arrayList(List items) { 64 | if (items instanceof ArrayList) { 65 | return items; 66 | } 67 | return new ArrayList(items); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/com/github/dakusui/combinatoradix/CartesianEnumeratorAdaptorTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dakusui.combinatoradix; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.Map; 6 | 7 | import static org.junit.jupiter.api.Assertions.assertEquals; 8 | import static org.junit.jupiter.api.Assertions.assertThrows; 9 | 10 | public class CartesianEnumeratorAdaptorTest { 11 | @Test 12 | public void normal01() { 13 | Domains.Builder builder = new Domains.Builder(); 14 | builder.addDomain("A", 1, 2, 3).addDomain("B", 1, 2).addDomain("C", 1, 2); 15 | 16 | CartesianEnumeratorAdaptor, String, Integer> cea = new SimpleCartesianEnumerator(builder.build()); 17 | 18 | assertEquals(12, cea.size()); 19 | int numTuples = 0; 20 | for (Map t : cea) { 21 | assertEquals(3, t.size()); 22 | numTuples++; 23 | } 24 | 25 | assertEquals(12, numTuples); 26 | } 27 | 28 | @Test 29 | public void normal02() { 30 | Domains.Builder builder = new Domains.Builder(); 31 | builder.addDomain("A", 1, 2, 3).addDomain("B", 1, 2).addDomain("C", 1, 2); 32 | 33 | CartesianEnumeratorAdaptor, String, Integer> cea = new SimpleCartesianEnumerator(builder.build()); 34 | 35 | assertEquals(12, cea.size()); 36 | int numTuples = 0; 37 | for (int i = 0; i < cea.size(); i++) { 38 | Map t = cea.get(i); 39 | assertEquals(3, t.size()); 40 | numTuples++; 41 | } 42 | 43 | assertEquals(12, numTuples); 44 | } 45 | 46 | @Test 47 | public void error01() { 48 | Domains.Builder builder = new Domains.Builder(); 49 | builder.addDomain("A", 1, 2, 3).addDomain("B", 1, 2).addDomain("C", 1, 2); 50 | 51 | final CartesianEnumeratorAdaptor, String, Integer> cea = new SimpleCartesianEnumerator(builder.build()); 52 | //// 53 | // Should fail here with UnsupportedOperationException 54 | assertThrows(UnsupportedOperationException.class, () -> cea.iterator().remove()); 55 | } 56 | } -------------------------------------------------------------------------------- /src/test/java/com/github/dakusui/combinatoradix/CombinatorTestBase.java: -------------------------------------------------------------------------------- 1 | package com.github.dakusui.combinatoradix; 2 | 3 | import org.hamcrest.CoreMatchers; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.Arrays; 7 | import java.util.LinkedList; 8 | import java.util.List; 9 | 10 | import static java.util.Arrays.asList; 11 | import static org.hamcrest.MatcherAssert.assertThat; 12 | 13 | public abstract class CombinatorTestBase extends EnumeratorTestBase { 14 | @Test 15 | public void encode() { 16 | // 5C1 - 5 4C1 - 4 3C1 - 3 2C1 - 2 17 | // 5C2 - 10 4C2 - 6 3C2 - 3 18 | // 5C3 - 10 4C3 - 4 19 | // 5C4 - 5 20 | 21 | // [1,-] - 3 22 | // [2,-] - 3 + 2 = 5 23 | // [1,-,-] - = 6 = 4C2 24 | // [2,-,-] - = 9 = 4C2+3C2 25 | encode(createCombinator( 26 | asList("a", "b", "c", "d", "e"), 27 | 3 28 | )); 29 | } 30 | 31 | private static void encode(Combinator combinator) { 32 | for (int i = 0; i < combinator.size(); i++) { 33 | System.out.printf("%d:%s:%s%n", i, combinator.get(i), Arrays.toString(combinator.encode(combinator.getElement(i)))); 34 | } 35 | } 36 | 37 | void exerciseTest(List in, int k) { 38 | final Combinator combinator = createCombinator(in, k); 39 | System.out.println("--"); 40 | encode(createCombinator(in, k)); 41 | 42 | final List expected = new LinkedList() {{ 43 | for (int i = 0; i < combinator.size(); i++) { 44 | add(String.format("%2d:%s", i, combinator.get(i))); 45 | } 46 | }}; 47 | List actual = new LinkedList() {{ 48 | for (int i = 0; i < combinator.size(); i++) { 49 | add(String.format("%2d:%s", combinator.indexOf(combinator.get(i)), combinator.get(i))); 50 | } 51 | }}; 52 | 53 | System.out.println("--"); 54 | System.out.printf("%2s %-20s %-20s%n", "", "expected", "actual"); 55 | for (int i = 0; i < combinator.size(); i++) { 56 | System.out.printf( 57 | "%2s %-20s %-20s%n", 58 | expected.get(i).equals(actual.get(i)) ? 59 | "" : 60 | "NG", 61 | expected.get(i), 62 | actual.get(i)); 63 | } 64 | 65 | assertThat( 66 | actual.toString(), 67 | CoreMatchers.equalTo(expected.toString()) 68 | ); 69 | } 70 | 71 | abstract , T> C createCombinator(List symbols, int k); 72 | } 73 | -------------------------------------------------------------------------------- /src/test/java/com/github/dakusui/combinatoradix/BigNumberTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dakusui.combinatoradix; 2 | 3 | import com.github.dakusui.combinatoradix.utils.InternalUtils; 4 | import com.github.dakusui.combinatoradix.utils.TestBase; 5 | import com.github.dakusui.combinatoradix.utils.TestUtils; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.util.Arrays; 9 | import java.util.List; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertEquals; 12 | import static org.junit.jupiter.api.Assertions.assertThrows; 13 | 14 | 15 | public class BigNumberTest extends TestBase { 16 | private static final List dataSet = Arrays.asList( 17 | /* 18 | 1 2 3 4 5 6 7 8 9 10 19 | */ 20 | "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", 21 | "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", 22 | "U", "V", "W", "X", "Y", "Z" 23 | ); 24 | 25 | @Test 26 | public void testPermutator() { 27 | System.out.println("-:" + Long.MAX_VALUE); 28 | Throwable t = assertThrows(IllegalArgumentException.class, () -> { 29 | for (int i = 0; i < dataSet.size(); i++) { 30 | Enumerator.Base e = new Permutator<>(dataSet, i); 31 | TestUtils.stdout.println(i + ":" + e.size()); 32 | } 33 | }); 34 | assertEquals("Overflow. Too big numbers are used 26P15: 841941782922240000 * 12", t.getMessage()); 35 | } 36 | 37 | 38 | @Test 39 | public void testCombinator() { 40 | for (int i = 0; i < dataSet.size(); i++) { 41 | Enumerator.Base e = new Combinator<>(dataSet, i); 42 | TestUtils.stdout.println(i + ":" + e.size()); 43 | } 44 | } 45 | 46 | @Test 47 | public void testRepeatedCombinator() { 48 | for (int i = 0; i < dataSet.size(); i++) { 49 | Enumerator.Base e = new HomogeniousCombinator<>(dataSet, i); 50 | TestUtils.stdout.println(i + ":" + e.size()); 51 | } 52 | } 53 | 54 | @Test 55 | public void test1000000thWord_Permutation() { 56 | assertEquals("[D, I, K, H, Q]", new Permutator<>(dataSet, 5).get(1000000).toString()); 57 | } 58 | 59 | @Test 60 | public void test1000000thWord_Combination() { 61 | assertEquals("[C, G, H, K, U, X, Y, Z]", new Combinator<>(dataSet, 8).get(1000000).toString()); 62 | } 63 | 64 | @Test 65 | public void test1000000thWord_RepeatedCombination() { 66 | assertEquals("[A, B, D, G, R, T, V, Z]", new HomogeniousCombinator<>(dataSet, 8).get(1000000).toString()); 67 | } 68 | 69 | @Test 70 | public void test100C50() { 71 | assertThrows(IllegalArgumentException.class, () -> InternalUtils.nCk(100, 50)); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/com/github/dakusui/combinatoradix/ShufflerTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dakusui.combinatoradix; 2 | 3 | import org.hamcrest.BaseMatcher; 4 | import org.hamcrest.CoreMatchers; 5 | import org.hamcrest.Description; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.util.Arrays; 9 | import java.util.LinkedList; 10 | import java.util.List; 11 | 12 | import static org.hamcrest.MatcherAssert.assertThat; 13 | import static org.junit.jupiter.api.Assertions.assertEquals; 14 | 15 | public class ShufflerTest { 16 | 17 | @Test 18 | public void checkStability() { 19 | Enumerator shuffler = Enumerators.shuffler(new LinkedList<>(Arrays.asList("1", "2", "3", "4", "5")), 20, 4649); 20 | assertEquals(20, shuffler.size()); 21 | String[] expectations = new String[] { 22 | "[3, 2, 4, 1, 5]", "[4, 1, 3, 2, 5]", "[4, 3, 2, 1, 5]", "[3, 1, 2, 4, 5]", 23 | "[4, 5, 1, 3, 2]", "[3, 5, 2, 1, 4]", "[5, 3, 4, 2, 1]", "[3, 1, 4, 5, 2]", 24 | "[5, 4, 1, 2, 3]", "[1, 2, 5, 4, 3]", "[1, 5, 3, 2, 4]", "[1, 4, 5, 3, 2]", 25 | "[5, 2, 1, 3, 4]", "[5, 3, 1, 4, 2]", "[5, 1, 4, 2, 3]", "[5, 1, 2, 4, 3]", 26 | "[5, 1, 3, 4, 2]", "[3, 4, 2, 1, 5]", "[3, 5, 2, 4, 1]", "[2, 5, 1, 3, 4]" 27 | }; 28 | for (int i = 0; i < expectations.length; i++) { 29 | assertThat(String.format("Error found in %sth element", i), shuffler.get(i).toString(), CoreMatchers.equalTo(expectations[i])); 30 | } 31 | } 32 | 33 | @Test 34 | public void checkEvenness() { 35 | final int w = 2000; 36 | Enumerator shuffler = Enumerators.shuffler(new LinkedList<>(Arrays.asList("1", "2", "3", "4", "5")), w, 4649); 37 | assertEquals(w, shuffler.size()); 38 | for (int i = 0; i < 5; i++) { 39 | for (int j = 1; j <= 5; j++) { 40 | assertThat(count(shuffler, Integer.toString(j), i), inRangeMatcher(w)); 41 | } 42 | } 43 | } 44 | 45 | private BaseMatcher inRangeMatcher(@SuppressWarnings("SameParameterValue") final int w) { 46 | return new BaseMatcher() { 47 | final int m = w / 5; 48 | final int d = m / 10; 49 | final int min = m - d; 50 | final int max = m + d; 51 | 52 | @Override 53 | public boolean matches(Object item) { 54 | if (!(item instanceof Integer)) 55 | return false; 56 | int v = (Integer) item; 57 | return min <= v && v <= max; 58 | } 59 | 60 | @Override 61 | public void describeTo(Description description) { 62 | description.appendText(String.format("out of acceptable range (%s <= x <= %s). ", min, max)); 63 | } 64 | }; 65 | } 66 | 67 | private static int count(Enumerator in, String target, int index) { 68 | int ret = 0; 69 | for (List each : in) { 70 | if (target.equals(each.get(index))) { 71 | ret++; 72 | } 73 | } 74 | return ret; 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/com/github/dakusui/combinatoradix/CartesianEnumeratorAdaptor.java: -------------------------------------------------------------------------------- 1 | package com.github.dakusui.combinatoradix; 2 | 3 | import com.github.dakusui.combinatoradix.tuple.AttrValue; 4 | import com.github.dakusui.combinatoradix.tuple.CartesianEnumerator; 5 | 6 | import java.util.*; 7 | 8 | /** 9 | * User must guarantee that T is stable in order to use 'indexOf' method. 10 | * 11 | * @param Type of map whose key is {@code U} and value is {@code V}. 12 | * @param Type of keys in map. 13 | * @param Type of values in map. 14 | */ 15 | public abstract class CartesianEnumeratorAdaptor, U, V> 16 | implements Iterable { 17 | private final CartesianEnumerator cart; 18 | 19 | protected CartesianEnumeratorAdaptor(Domains domains) { 20 | this.cart = new CartesianEnumerator(domains2AttrValues(domains)); 21 | } 22 | 23 | @Override 24 | public Iterator iterator() { 25 | final Iterator>> i = this.cart.iterator(); 26 | return new Iterator() { 27 | @Override 28 | public boolean hasNext() { 29 | return i.hasNext(); 30 | } 31 | 32 | @Override 33 | public T next() { 34 | return attrValues2map(i.next()); 35 | } 36 | 37 | @Override 38 | public void remove() { 39 | throw new UnsupportedOperationException("This operation is not supported."); 40 | } 41 | }; 42 | } 43 | 44 | public T get(long index) { 45 | return attrValues2map(this.cart.get(index)); 46 | } 47 | 48 | public long indexOf(T entry) { 49 | return cart.indexOf(map2AttrValues(entry)); 50 | } 51 | 52 | public long size() { 53 | return cart.size(); 54 | } 55 | 56 | /** 57 | * A map returned by {@code get(long)} is created by this method. 58 | * A user can specify concrete {@code Map} class like {@code HashMap}, 59 | * {@code TreeMap}, etc, by overriding this method. 60 | */ 61 | abstract protected T createMap(); 62 | 63 | List> domains2AttrValues(Domains domains) { 64 | List> ret = new LinkedList>(); 65 | for (U domainName : domains.getDomainNames()) { 66 | for (V domainValue : domains.getDomain(domainName)) { 67 | ret.add(new AttrValue(domainName, domainValue)); 68 | } 69 | } 70 | return ret; 71 | } 72 | 73 | T attrValues2map(List> attrValues) { 74 | T ret = createMap(); 75 | for (AttrValue attrValue : attrValues) { 76 | ret.put(attrValue.attr(), attrValue.value()); 77 | } 78 | return ret; 79 | } 80 | 81 | List> map2AttrValues(T map) { 82 | List> ret = new ArrayList>(map.size()); 83 | for (Map.Entry each : map.entrySet()) { 84 | ret.add(new AttrValue(each.getKey(), each.getValue())); 85 | } 86 | return ret; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/test/java/com/github/dakusui/combinatoradix/CartesianEnumeratorTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dakusui.combinatoradix; 2 | 3 | import com.github.dakusui.combinatoradix.tuple.AttrValue; 4 | import com.github.dakusui.combinatoradix.tuple.CartesianEnumerator; 5 | import com.github.dakusui.combinatoradix.utils.TestUtils; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.util.LinkedList; 9 | import java.util.List; 10 | 11 | import static org.junit.jupiter.api.Assertions.*; 12 | 13 | public class CartesianEnumeratorTest { 14 | @Test 15 | public void test() { 16 | List> attrValues = new LinkedList<>(); 17 | attrValues.add(attrValue("key1", "A")); 18 | attrValues.add(attrValue("key1", "B")); 19 | attrValues.add(attrValue("key2", "a")); 20 | attrValues.add(attrValue("key2", "b")); 21 | attrValues.add(attrValue("key3", "X")); 22 | attrValues.add(attrValue("key3", "Y")); 23 | Enumerator.Base> enumerator = new CartesianEnumerator<>(attrValues); 24 | int i = 0; 25 | for (List> cur : enumerator) { 26 | TestUtils.stdout.println(String.format("%03d %s", i++, cur)); 27 | } 28 | assertEquals(8, enumerator.size()); 29 | } 30 | 31 | @Test 32 | public void testBig() { 33 | // Should be able to handle 62 attributes 34 | List> attrValues = new LinkedList<>(); 35 | for (int i = 0; i < 62; i++) { 36 | attrValues.add(attrValue("key" + i, "A")); 37 | attrValues.add(attrValue("key" + i, "B")); 38 | } 39 | new CartesianEnumerator<>(attrValues); 40 | } 41 | 42 | @Test 43 | public void testTooBig() { 44 | // 63rd attributes cannot be handled. 45 | List> attrValues = new LinkedList<>(); 46 | for (int i = 0; i < 63; i++) { 47 | attrValues.add(attrValue("key" + i, "A")); 48 | attrValues.add(attrValue("key" + i, "B")); 49 | } 50 | assertThrows(IllegalArgumentException.class, () -> new CartesianEnumerator<>(attrValues)); 51 | } 52 | 53 | @Test 54 | public void testAttrValueEquals() { 55 | assertEquals(attrValue("a", "V"), attrValue("a", "V")); 56 | } 57 | 58 | @Test 59 | public void testAttrValueNotEquals1() { 60 | assertNotEquals(attrValue("a", "V"), attrValue("a", "W")); 61 | } 62 | 63 | @Test 64 | public void testAttrValueNotEquals2() { 65 | //Intentionally comparing with an object of wrong type. 66 | assertNotEquals(attrValue("a", "V"), new Object()); 67 | } 68 | 69 | 70 | static AttrValue attrValue(String attr, String value) { 71 | return new AttrValue<>(attr, value); 72 | } 73 | 74 | public static void main(String... args) { 75 | List> attrValues = new LinkedList<>(); 76 | attrValues.add(attrValue("key1", "A")); 77 | attrValues.add(attrValue("key1", "B")); 78 | attrValues.add(attrValue("key2", "a")); 79 | attrValues.add(attrValue("key2", "b")); 80 | attrValues.add(attrValue("key3", "X")); 81 | attrValues.add(attrValue("key3", "Y")); 82 | 83 | Enumerator> enumerator = new CartesianEnumerator<>(attrValues); 84 | 85 | for (int i = 5; i < enumerator.size(); i++) { 86 | System.out.printf("%s: %s -> %s : %s%n", i, enumerator.get(i), enumerator.indexOf(enumerator.get(i)), enumerator.get(enumerator.indexOf(enumerator.get(i)))); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/com/github/dakusui/combinatoradix/Enumerator.java: -------------------------------------------------------------------------------- 1 | package com.github.dakusui.combinatoradix; 2 | 3 | import com.github.dakusui.combinatoradix.utils.InternalUtils; 4 | import com.github.dakusui.combinatoradix.utils.Predicates; 5 | 6 | import java.util.List; 7 | import java.util.NoSuchElementException; 8 | 9 | import static com.github.dakusui.combinatoradix.utils.ExceptionFactory.Utils.indexOutOfBounds; 10 | import static com.github.dakusui.combinatoradix.utils.ExceptionFactory.Utils.npe; 11 | import static com.github.dakusui.combinatoradix.utils.InternalUtils.check; 12 | import static com.github.dakusui.combinatoradix.utils.Predicates.inRange; 13 | 14 | public interface Enumerator extends Iterable> { 15 | List get(long index); 16 | 17 | long size(); 18 | 19 | long indexOf(List entry); 20 | 21 | class Iterator implements java.util.Iterator> { 22 | private final Enumerator enumerator; 23 | private long index; 24 | 25 | @SuppressWarnings("WeakerAccess") 26 | public Iterator(long offset, Enumerator enumerator) { 27 | this.enumerator = enumerator; 28 | this.index = offset; 29 | } 30 | 31 | @Override 32 | public boolean hasNext() { 33 | return this.index < enumerator.size(); 34 | } 35 | 36 | @Override 37 | public List next() { 38 | if (!hasNext()) { 39 | String message = "No more element in this enumberator."; 40 | throw new NoSuchElementException(message); 41 | } 42 | return enumerator.get(this.index++); 43 | } 44 | 45 | @Override 46 | public void remove() { 47 | throw new UnsupportedOperationException("This operation is not supported."); 48 | } 49 | } 50 | 51 | abstract class Base implements Enumerator, Iterable> { 52 | 53 | private final long enumSize; 54 | 55 | protected final int k; 56 | 57 | protected final List symbols; 58 | 59 | /** 60 | * Creates an object of this class. 61 | * 62 | * @param symbols A list of elements from which returned value of {@code get(int)} will be chosen. 63 | * @param k Number of elements chosen from {@code items} 64 | * @param size Number of lists this object can return. 65 | */ 66 | protected Base(List symbols, int k, long size) { 67 | this.symbols = check( 68 | symbols, 69 | Predicates.>notNull(), 70 | npe(), 71 | "'symbols' mustn't be null" 72 | ); 73 | this.k = k; 74 | this.enumSize = size; 75 | } 76 | 77 | @Override 78 | public List get(long index) { 79 | return getElement(check( 80 | index, 81 | inRange(0L, enumSize), 82 | indexOutOfBounds(), 83 | "Index (%d) must be less than %d", index, this.enumSize 84 | )); 85 | } 86 | 87 | protected abstract List getElement(long index); 88 | 89 | @Override 90 | public long indexOf(List element) { 91 | InternalUtils.checkCondition( 92 | element.size() == k, 93 | "Size of element:%d is not valid (expected=%d)", 94 | element.size(), 95 | k 96 | ); 97 | InternalUtils.checkCondition( 98 | symbols.containsAll(element), 99 | "Element %s contained invalid value(s): (valid values=%s)", 100 | element, 101 | symbols 102 | ); 103 | return calculateIndexOf(element); 104 | } 105 | 106 | abstract protected long calculateIndexOf(List element); 107 | 108 | 109 | final public long size() { 110 | return this.enumSize; 111 | } 112 | 113 | @Override 114 | public java.util.Iterator> iterator() { 115 | return new Iterator(0, this); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/com/github/dakusui/combinatoradix/HomogeniousCombinator.java: -------------------------------------------------------------------------------- 1 | package com.github.dakusui.combinatoradix; 2 | 3 | import com.github.dakusui.combinatoradix.utils.InternalUtils; 4 | 5 | import java.util.ArrayList; 6 | import java.util.LinkedList; 7 | import java.util.List; 8 | 9 | import static com.github.dakusui.combinatoradix.utils.InternalUtils.nHk; 10 | 11 | public class HomogeniousCombinator extends Combinator { 12 | 13 | public HomogeniousCombinator(List list, int k) { 14 | super(list, k, calculateSize(list, k)); 15 | } 16 | 17 | @Override 18 | protected List getElement(long index) { 19 | List ret = new LinkedList(); 20 | List tmp = new ArrayList(this.symbols); 21 | int[] locator = index2locator(index, symbols.size(), k); 22 | for (int i : locator) { 23 | ret.add(tmp.get(i)); 24 | tmp = tmp.subList(i, tmp.size()); 25 | } 26 | return ret; 27 | } 28 | 29 | 30 | @Override 31 | protected long calculateIndexOf(List element) { 32 | // 0:[a, b]:0 33 | // 1:[a, c]:1 34 | // 2:[a, d]:2 35 | // 3:[b, c]:3 36 | // 4:[b, d]:4 37 | // 5:[c, d]:6 38 | // long ret = 0; 39 | return numSeq( 40 | encode(element), 41 | symbols.size(), 42 | k 43 | ); 44 | } 45 | 46 | int[] encode(List element) { 47 | List work = new LinkedList(this.symbols); 48 | int[] ret = new int[this.k]; 49 | for (int i = 0; i < ret.length; i++) { 50 | ret[i] = work.indexOf(element.get(i)); 51 | work = work.subList(ret[i], work.size()); 52 | } 53 | return ret; 54 | } 55 | 56 | private static long numSeq(int[] digit, int numSymbols, int numChosen) { 57 | InternalUtils.checkCondition(numChosen >= 0, "numChosen(%s) mustn't be negative", numChosen); 58 | InternalUtils.checkCondition(numChosen <= numSymbols, "numChosen(%s) mustn't be greater than numSymbols(%s)", numChosen, numSymbols); 59 | InternalUtils.checkCondition(digit != null, "digit mustn't be null"); 60 | InternalUtils.checkCondition(digit.length > 0 && digit.length <= numChosen, "digit(%s) less than or equal to %s", digit, numChosen); 61 | 62 | if (digit.length == numChosen) { 63 | if (digit.length == 1) 64 | return digit[digit.length - 1]; 65 | return digit[digit.length - 1] + numSeq(InternalUtils.chop(digit), numSymbols, numChosen); 66 | } 67 | if (digit.length == 1) { 68 | long ret = 0; 69 | for (int i = 0; i < digit[0]; i++) { 70 | ret += nHk(numSymbols - i, numChosen - 1); 71 | } 72 | return ret; 73 | } 74 | long ret = 0; 75 | int[] chopped = InternalUtils.chop(digit); 76 | ret += numSeq(chopped, numSymbols, numChosen); 77 | 78 | int symbolsConsumed = InternalUtils.sumAll(chopped); 79 | for (int i = 0; i < digit[digit.length - 1]; i++) { 80 | int n = numSymbols - symbolsConsumed - i; 81 | ret += nHk(n, numChosen - digit.length); 82 | } 83 | return ret; 84 | } 85 | 86 | private static long calculateSize(List items, int k) { 87 | return nHk(items.size(), k); 88 | } 89 | 90 | private static void hdiv(CDivResult result, long index, int n, int k) { 91 | int q = 0; 92 | for (int nn = n; nn > 0; nn--) { 93 | long nnHk_1 = nHk(nn, k - 1); 94 | if (index < nnHk_1) { 95 | result.mod = index; 96 | result.quotient = q; 97 | break; 98 | } 99 | index -= nnHk_1; 100 | q++; 101 | } 102 | } 103 | 104 | private static int[] index2locator(long index, int n, int k) { 105 | int[] ret = new int[k]; 106 | CDivResult result = new CDivResult(); 107 | for (int i = 0; i < k; i++) { 108 | hdiv(result, index, n, k - i); 109 | index = result.mod; 110 | ret[i] = result.quotient; 111 | n -= result.quotient; 112 | } 113 | return ret; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/github/dakusui/combinatoradix/tuple/CartesianEnumerator.java: -------------------------------------------------------------------------------- 1 | package com.github.dakusui.combinatoradix.tuple; 2 | 3 | import com.github.dakusui.combinatoradix.Enumerator; 4 | import com.github.dakusui.combinatoradix.utils.InternalUtils; 5 | 6 | import java.util.*; 7 | 8 | public class CartesianEnumerator extends Enumerator.Base> { 9 | private final ArrayList attrsInReverseOrder; 10 | private final Map>> attrValues; 11 | 12 | @SuppressWarnings("unchecked") 13 | public CartesianEnumerator(List> attributeValues) { 14 | super( 15 | attributeValues, 16 | countAttributes(attributeValues.toArray(new AttrValue[attributeValues.size()])), 17 | calculateSize(attributeValues) 18 | ); 19 | this.attrsInReverseOrder = new ArrayList(this.k); 20 | for (AttrValue cur : this.symbols) { 21 | if (!this.attrsInReverseOrder.contains(cur.attr())) { 22 | this.attrsInReverseOrder.add(cur.attr()); 23 | } 24 | } 25 | this.attrValues = attrValues(attributeValues); 26 | Collections.reverse(this.attrsInReverseOrder); 27 | } 28 | 29 | @Override 30 | protected List> getElement(long index) { 31 | List> ret = new LinkedList>(); 32 | for (T key : this.attrsInReverseOrder) { 33 | List> values = this.attrValues.get(key); 34 | int sz = values.size(); 35 | int mod = (int) (index % sz); 36 | index /= sz; 37 | ret.add(values.get(mod)); 38 | } 39 | Collections.reverse(ret); 40 | return ret; 41 | } 42 | 43 | @Override 44 | public long indexOf(List> element) { 45 | InternalUtils.checkCondition(element.size() == attrValues.size(), "Invalid number of attributes; (expected=%d, actual=%d)", attrValues.size(), element.size()); 46 | return calculateIndexOf(element); 47 | } 48 | 49 | @Override 50 | protected long calculateIndexOf(List> element) { 51 | int c = 1; 52 | long ret = 0; 53 | for (int i = 0; i < element.size(); i++) { 54 | AttrValue each = find(element, i);//element.get(element.size() - i - 1); 55 | ret += c * attrValues.get(each.attr()).indexOf(each); 56 | c *= attrValues.get(each.attr()).size(); 57 | } 58 | return ret; 59 | } 60 | 61 | private AttrValue find(List> element, int index) { 62 | for (AttrValue each : element) { 63 | if (AttrValue.equals(each.attr(), this.attrsInReverseOrder.get(index))) 64 | return each; 65 | } 66 | throw new IllegalStateException(); 67 | } 68 | 69 | private static long calculateSize(List> attributeValues) { 70 | long ret = 1; 71 | for (List> values : attrValues(attributeValues).values()) { 72 | long sz = values.size(); 73 | if (sz > Long.MAX_VALUE / ret) { 74 | throw new IllegalArgumentException(String.format("Overflow. Too many attributes or attribute values: %d * %d", ret, sz)); 75 | } else { 76 | ret *= sz; 77 | } 78 | } 79 | return ret; 80 | } 81 | 82 | private static Map>> attrValues(List> attributeValues) { 83 | Map>> ret = new HashMap>>(); 84 | for (AttrValue cur : attributeValues) { 85 | List> values = ret.get(cur.attr()); 86 | if (values == null) { 87 | values = new LinkedList>(); 88 | ret.put(cur.attr(), values); 89 | } 90 | values.add(cur); 91 | } 92 | return ret; 93 | } 94 | 95 | private static int countAttributes(AttrValue[] attributeValues) { 96 | Set> attrs = new HashSet>(); 97 | Collections.addAll(attrs, attributeValues); 98 | return attrs.size(); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/github/dakusui/combinatoradix/Cartesianator.java: -------------------------------------------------------------------------------- 1 | package com.github.dakusui.combinatoradix; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.LinkedList; 6 | import java.util.List; 7 | 8 | import static com.github.dakusui.combinatoradix.utils.InternalUtils.checkCondition; 9 | import static java.util.Arrays.asList; 10 | 11 | public class Cartesianator implements Enumerator { 12 | private final List> sets; 13 | private final long size; 14 | 15 | @Override 16 | public List get(long index) { 17 | if (index < this.size) { 18 | return getElement(index); 19 | } 20 | String msg = String.format("Index (%d) must be less than %d", index, this.size); 21 | throw new IndexOutOfBoundsException(msg); 22 | } 23 | 24 | @Override 25 | public long size() { 26 | return this.size; 27 | } 28 | 29 | @Override 30 | public long indexOf(List entry) { 31 | int entrySize = entry.size(); 32 | int expectedSize = sets.size(); 33 | checkCondition( 34 | entry.size() == sets.size(), 35 | "Size of entry %d is not valid. It should have been %d", 36 | entrySize, 37 | expectedSize 38 | ); 39 | for (int i = 0; i < entrySize; i++) { 40 | E value = entry.get(i); 41 | List validValues = sets.get(entrySize - i - 1); 42 | checkCondition( 43 | validValues.contains(value), 44 | "entry.get(%d)='%s' is not a valid value. Valid values: %s", 45 | i, 46 | value, 47 | validValues 48 | ); 49 | } 50 | return calculateIndexOf(entry); 51 | } 52 | 53 | private long calculateIndexOf(List entry) { 54 | long ret = 0; 55 | int c = 1; 56 | for (int i = 0; i < entry.size(); i++) { 57 | int j = entry.size() - i - 1; 58 | List set = sets.get(i); 59 | ret += set.indexOf(entry.get(j)) * c; 60 | c *= set.size(); 61 | } 62 | return ret; 63 | } 64 | 65 | /* 66 | * sets: 67 | * 0| a b 68 | * 1| a b c 69 | * 2| a b c d 70 | * 71 | * index: 72 | * 13 73 | * 74 | * ret: 75 | * 0| 76 | * 1| 77 | * 2| 78 | */ 79 | private List getElement(long index) { 80 | List ret = new LinkedList(); 81 | for (List eachSet : this.sets) { 82 | int sz = eachSet.size(); 83 | int mod = (int) (index % sz); 84 | index /= sz; 85 | ret.add(eachSet.get(mod)); 86 | } 87 | Collections.reverse(ret); 88 | return ret; 89 | } 90 | 91 | private static long calculateSize(List> sets) { 92 | long ret = 1; 93 | for (List eachSet : sets) { 94 | long sz = eachSet.size(); 95 | if (sz > Long.MAX_VALUE / ret) { 96 | throw new IllegalArgumentException(String.format("Overflow. Too many attributes or attribute values: %d * %d", ret, sz)); 97 | } else { 98 | ret *= sz; 99 | } 100 | } 101 | return ret; 102 | } 103 | 104 | protected Cartesianator(List> sets) { 105 | this.sets = new ArrayList>(sets); 106 | Collections.reverse(this.sets); 107 | this.size = calculateSize(sets); 108 | } 109 | 110 | @Override 111 | public java.util.Iterator> iterator() { 112 | return new Enumerator.Iterator(0,this); 113 | } 114 | 115 | public static void main(String... args) { 116 | //noinspection unchecked 117 | Cartesianator cartesianator = new Cartesianator( 118 | asList( 119 | asList("a", "b"), 120 | asList("a", "b", "c"), 121 | asList("a", "b", "c", "d") 122 | ) 123 | ); 124 | for (int i = 0; i < cartesianator.size; i++) { 125 | System.out.println(i + ":" + cartesianator.get(i) + ":" + cartesianator.indexOf(cartesianator.get(i))); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/test/java/com/github/dakusui/combinatoradix/CartesianatorTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dakusui.combinatoradix; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.Iterator; 6 | import java.util.LinkedList; 7 | import java.util.List; 8 | import java.util.NoSuchElementException; 9 | 10 | import static java.util.Arrays.asList; 11 | import static java.util.Collections.singletonList; 12 | import static org.junit.jupiter.api.Assertions.*; 13 | 14 | public class CartesianatorTest { 15 | @Test 16 | public void normal1$1() { 17 | Enumerator c = Enumerators.cartesianator(singletonList( 18 | singletonList("A1") 19 | )); 20 | assertEquals(1, c.size()); 21 | Iterator> i = c.iterator(); 22 | assertEquals(singletonList("A1"), i.next()); 23 | } 24 | 25 | @Test 26 | public void normal2$1() { 27 | Enumerator c = Enumerators.cartesianator(singletonList( 28 | asList("A1", "A2") 29 | )); 30 | assertEquals(2, c.size()); 31 | Iterator> i = c.iterator(); 32 | assertEquals(singletonList("A1"), i.next()); 33 | assertEquals(singletonList("A2"), i.next()); 34 | } 35 | 36 | @Test 37 | public void normal2$3() { 38 | Enumerator c = Enumerators.cartesianator(asList( 39 | asList("A1", "A2"), 40 | asList("B1", "B2"), 41 | asList("C1", "C2") 42 | )); 43 | assertEquals(8, c.size()); 44 | Iterator> i = c.iterator(); 45 | assertEquals(asList("A1", "B1", "C1"), i.next()); 46 | assertEquals(asList("A1", "B1", "C2"), i.next()); 47 | i.next(); 48 | i.next(); 49 | i.next(); 50 | i.next(); 51 | i.next(); 52 | assertEquals(asList("A2", "B2", "C2"), i.next()); 53 | } 54 | 55 | @Test 56 | public void boundary0$1() { 57 | Enumerator c = Enumerators.cartesianator(singletonList(new LinkedList<>())); 58 | assertEquals(0, c.size()); 59 | Iterator> i = c.iterator(); 60 | assertFalse(i.hasNext()); 61 | } 62 | 63 | @Test 64 | public void boundary1$1_0$1() { 65 | Enumerator c = Enumerators.cartesianator(asList( 66 | asList("A1", "A2"), 67 | new LinkedList<>() 68 | )); 69 | assertEquals(0, c.size()); 70 | Iterator> i = c.iterator(); 71 | assertFalse(i.hasNext()); 72 | } 73 | 74 | @Test 75 | public void givenEmptyIterator$whenNextIsAttemptedAfterLast$thenNoSuchElementThrown() { 76 | //// 77 | // Given: iterator is at the last 78 | Enumerator c = Enumerators.cartesianator(singletonList( 79 | new LinkedList<>() 80 | )); 81 | assertEquals(0, c.size()); 82 | Iterator> i = c.iterator(); 83 | assertFalse(i.hasNext()); 84 | 85 | assertThrows(NoSuchElementException.class, i::next); 86 | } 87 | 88 | 89 | @Test 90 | public void givenNonEmptyIterator$whenNextIsAttemptedAfterLast$thenNoSuchElementThrown() { 91 | //// 92 | // Given: iterator is at the last 93 | Enumerator c = Enumerators.cartesianator(singletonList( 94 | singletonList("A1") 95 | )); 96 | assertEquals(1, c.size()); 97 | Iterator> i = c.iterator(); 98 | assertTrue(i.hasNext()); 99 | i.next(); 100 | assertFalse(i.hasNext()); 101 | //// 102 | // When: next() is attempted after last 103 | assertThrows(NoSuchElementException.class, i::next); 104 | } 105 | 106 | @Test 107 | public void givenEnumerator$whenGetAfterLast$thenExceptionThrown() { 108 | //// 109 | // Given: enumerator 110 | Enumerator c = Enumerators.cartesianator(singletonList( 111 | singletonList("A1") 112 | )); 113 | assertEquals(1, c.size()); 114 | //// 115 | // Then: 116 | assertThrows(IndexOutOfBoundsException.class, () -> /* whrn */c.get(1)); 117 | } 118 | 119 | @Test 120 | public void givenTooManyNonEmptyLists$whenNewCartesinator$thenExceptionThrown() { 121 | //// 122 | // Given: 123 | List> lists = new LinkedList<>(); 124 | for (int i = 0; i < 100; i++) { 125 | List each = new LinkedList<>(); 126 | each.add("I" + i + "-" + 0); 127 | each.add("I" + i + "-" + 1); 128 | lists.add(each); 129 | } 130 | 131 | //// 132 | // When: New Cartesinator 133 | assertThrows(IllegalArgumentException.class, () -> { 134 | Enumerator c = Enumerators.cartesianator(lists); 135 | assertEquals(1, c.size()); 136 | }); 137 | } 138 | 139 | } 140 | -------------------------------------------------------------------------------- /src/main/java/com/github/dakusui/combinatoradix/Combinator.java: -------------------------------------------------------------------------------- 1 | package com.github.dakusui.combinatoradix; 2 | 3 | import com.github.dakusui.combinatoradix.utils.InternalUtils; 4 | 5 | import java.util.ArrayList; 6 | import java.util.LinkedList; 7 | import java.util.List; 8 | 9 | import static com.github.dakusui.combinatoradix.utils.InternalUtils.nCk; 10 | 11 | public class Combinator extends Enumerator.Base { 12 | static class CDivResult { 13 | long mod; 14 | int quotient; 15 | } 16 | 17 | public Combinator(List items, int k) { 18 | super(InternalUtils.arrayList(items), k, calculateSize(items, k)); 19 | } 20 | 21 | protected Combinator(List items, int k, long size) { 22 | super(InternalUtils.arrayList(items), k, size); 23 | } 24 | 25 | @Override 26 | protected List getElement(long index) { 27 | List ret = new LinkedList(); 28 | List tmp = new ArrayList(this.symbols); 29 | int[] locator = index2locator(index, symbols.size(), k); 30 | for (int i : locator) { 31 | T last = null; 32 | for (int j = 0; j <= i; j++) { 33 | last = tmp.remove(0); 34 | } 35 | ret.add(last); 36 | } 37 | return ret; 38 | } 39 | 40 | /* 41 | * 0:[a, b]: [a,b,c,d];[];[a] -> [b,c,d];[a];[b] -> [c,d];[a,b]; 0 42 | * 1:[a, c]: [a,b,c,d];[];[a] -> [b,c,d];[a];[c] -> [d];[a,c]; 1 43 | * 2:[a, d]: 44 | * 3:[b, c]: 45 | * 4:[b, d]: [a,b,c,d];[];[b] -> [c,d];[b];[d] -> [c];[b,d];; 4 46 | * 5:[c, d]: 47 | */ 48 | @Override 49 | protected long calculateIndexOf(List element) { 50 | // 0:[a, b]:0 51 | // 1:[a, c]:1 52 | // 2:[a, d]:2 53 | // 3:[b, c]:3 54 | // 4:[b, d]:4 55 | // 5:[c, d]:6 56 | // long ret = 0; 57 | return numSeq( 58 | encode(element), 59 | symbols.size(), 60 | k 61 | ); 62 | } 63 | 64 | int[] encode(List element) { 65 | List work = new LinkedList(this.symbols); 66 | int[] ret = new int[this.k]; 67 | for (int i = 0; i < ret.length; i++) { 68 | ret[i] = work.indexOf(element.get(i)); 69 | work = work.subList(ret[i] + 1, work.size()); 70 | } 71 | return ret; 72 | } 73 | 74 | private static long numSeq(int[] digit, int numSymbols, int numChosen) { 75 | InternalUtils.checkCondition(numChosen >= 0, "numChosen(%s) mustn't be negative", numChosen); 76 | InternalUtils.checkCondition(numChosen <= numSymbols, "numChosen(%s) mustn't be greater than numSymbols(%s)", numChosen, numSymbols); 77 | InternalUtils.checkCondition(digit != null, "digit mustn't be null"); 78 | InternalUtils.checkCondition(digit.length > 0 && digit.length <= numChosen, "digit(%s) less than or equal to %s", digit, numChosen); 79 | 80 | if (digit.length == numChosen) { 81 | if (digit.length == 1) 82 | return digit[digit.length - 1]; 83 | return digit[digit.length - 1] + numSeq(InternalUtils.chop(digit), numSymbols, numChosen); 84 | } 85 | if (digit.length == 1) { 86 | // [c] (2,-,-), [a,b,c,d] 87 | // [a,b,c] 88 | // [a,b,d] 89 | // [a,b,e] 90 | // [a,c,d] 91 | // [a,c,e] 92 | // [a,d,e] 4C2 93 | // [b,c,d] 94 | // [b,c,e] 95 | // [b,d,e] 3C2 96 | long ret = 0; 97 | for (int i = 0; i < digit[0]; i++) { 98 | ret += nCk(numSymbols - i - 1, numChosen - 1); 99 | } 100 | return ret; 101 | } 102 | // [b,d] (1,1,-), [a,b,c,d] 103 | // [a,b,c] 104 | // [a,b,d] 105 | // [a,b,e] 106 | // [a,c,d] 107 | // [a,c,e] 108 | // [a,d,e] 4C2 109 | // [b,c,d] 110 | // [b,c,e] 111 | // 112 | long ret = 0; 113 | int[] chopped = InternalUtils.chop(digit); 114 | ret += numSeq(chopped, numSymbols, numChosen); 115 | 116 | int symbolsConsumed = InternalUtils.sumAll(chopped) + chopped.length + 1; 117 | for (int i = 0; i < digit[digit.length - 1]; i++) { 118 | int n = numSymbols - symbolsConsumed - i; 119 | ret += nCk(n, numChosen - digit.length); 120 | } 121 | return ret; 122 | } 123 | 124 | private static void cdiv(CDivResult result, long index, int n, int k) { 125 | int q = 0; 126 | for (int nn = n - 1; nn >= k - 1; nn--) { 127 | long nnCk_1 = nCk(nn, k - 1); 128 | if (index < nnCk_1) { 129 | result.mod = index; 130 | result.quotient = q; 131 | break; 132 | } 133 | index -= nnCk_1; 134 | q++; 135 | } 136 | } 137 | 138 | private static int[] index2locator(long index, int n, int k) { 139 | int[] ret = new int[k]; 140 | CDivResult result = new CDivResult(); 141 | for (int i = 0; i < k; i++) { 142 | cdiv(result, index, n, k - i); 143 | ret[i] = result.quotient; 144 | index = result.mod; 145 | n -= (result.quotient + 1); 146 | } 147 | return ret; 148 | } 149 | 150 | private static long calculateSize(List items, int k) { 151 | return nCk(items.size(), k); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/test/java/com/github/dakusui/combinatoradix/CombinatorTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dakusui.combinatoradix; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.Collections; 6 | import java.util.Iterator; 7 | import java.util.List; 8 | 9 | import static java.util.Arrays.asList; 10 | import static java.util.Collections.emptyList; 11 | import static java.util.Collections.singletonList; 12 | import static org.junit.jupiter.api.Assertions.*; 13 | 14 | public class CombinatorTest extends CombinatorTestBase { 15 | @Test 16 | public void given4SymbolsAnd2forK$whenRunCombinator$thenCorrect() { 17 | exerciseTest( 18 | asList("a", "b", "c", "d"), 19 | 2 20 | ); 21 | } 22 | 23 | @Test 24 | public void given5SymbolsAnd2forK$whenRunCombinator$thenCorrect() { 25 | exerciseTest( 26 | asList("a", "b", "c", "d", "e"), 27 | 2 28 | ); 29 | } 30 | 31 | 32 | @Test 33 | public void given4SymbolsAnd1forK$whenRunCombinator$thenCorrect() { 34 | exerciseTest( 35 | asList("a", "b", "c", "d"), 36 | 1 37 | ); 38 | } 39 | 40 | @Test 41 | public void given4SymbolsAnd3forK$whenRunCombinator$thenCorrect() { 42 | exerciseTest( 43 | asList("a", "b", "c", "d"), 44 | 3 45 | ); 46 | } 47 | 48 | @Test 49 | public void given5SymbolsAnd3forK$whenRunCombinator$thenCorrect() { 50 | exerciseTest( 51 | asList("a", "b", "c", "d", "e"), 52 | 3 53 | ); 54 | } 55 | 56 | @Test 57 | public void given6SymbolsAnd4forK$whenRunCombinator$thenCorrect() { 58 | exerciseTest( 59 | asList("a", "b", "c", "d", "e", "f"), 60 | 4 61 | ); 62 | } 63 | 64 | 65 | @Test 66 | public void given3SymbolsAnd3forK$whenRunCombinator$thenCorrect() { 67 | exerciseTest( 68 | asList("a", "b", "c"), 69 | 3 70 | ); 71 | } 72 | 73 | @Test 74 | public void test_empty() { 75 | // Empty set should result in empty iterator immediately 76 | Iterator> i = new Combinator<>(Collections.emptyList(), 0).iterator(); 77 | assertTrue(i.hasNext()); 78 | assertEquals(emptyList(), i.next()); 79 | assertFalse(i.hasNext()); 80 | } 81 | 82 | @Test 83 | public void test_nC0() { 84 | Iterator> combinator = new Combinator<>(testset1(), 0).iterator(); 85 | assertTrue(combinator.hasNext()); 86 | assertEquals(emptyList(), combinator.next()); 87 | assertFalse(combinator.hasNext()); 88 | } 89 | 90 | @Test 91 | public void test_nC1() { 92 | Iterator> combinator = new Combinator<>(testset1(), 1).iterator(); 93 | assertTrue(combinator.hasNext()); 94 | assertEquals(singletonList("A"), combinator.next()); 95 | assertTrue(combinator.hasNext()); 96 | assertEquals(singletonList("B"), combinator.next()); 97 | assertTrue(combinator.hasNext()); 98 | assertEquals(singletonList("C"), combinator.next()); 99 | assertTrue(combinator.hasNext()); 100 | assertEquals(singletonList("D"), combinator.next()); 101 | assertTrue(combinator.hasNext()); 102 | assertEquals(singletonList("E"), combinator.next()); 103 | assertFalse(combinator.hasNext()); 104 | } 105 | 106 | @Test 107 | public void test_nC2() { 108 | Iterator> combinator = new Combinator<>(testset1(), 2).iterator(); 109 | assertTrue(combinator.hasNext()); 110 | assertEquals(asList("A", "B"), combinator.next()); 111 | assertTrue(combinator.hasNext()); 112 | assertEquals(asList("A", "C"), combinator.next()); 113 | assertTrue(combinator.hasNext()); 114 | assertEquals(asList("A", "D"), combinator.next()); 115 | assertTrue(combinator.hasNext()); 116 | assertEquals(asList("A", "E"), combinator.next()); 117 | assertTrue(combinator.hasNext()); 118 | assertEquals(asList("B", "C"), combinator.next()); 119 | assertTrue(combinator.hasNext()); 120 | assertEquals(asList("B", "D"), combinator.next()); 121 | assertTrue(combinator.hasNext()); 122 | assertEquals(asList("B", "E"), combinator.next()); 123 | assertTrue(combinator.hasNext()); 124 | assertEquals(asList("C", "D"), combinator.next()); 125 | assertTrue(combinator.hasNext()); 126 | assertEquals(asList("C", "E"), combinator.next()); 127 | assertTrue(combinator.hasNext()); 128 | assertEquals(asList("D", "E"), combinator.next()); 129 | assertFalse(combinator.hasNext()); 130 | } 131 | 132 | @Test 133 | public void test_nC3() { 134 | Iterator> combinator = Enumerators.combinator(testset1(), 3).iterator(); 135 | assertTrue(combinator.hasNext()); 136 | assertEquals(asList("A", "B", "C"), combinator.next()); 137 | assertTrue(combinator.hasNext()); 138 | assertEquals(asList("A", "B", "D"), combinator.next()); 139 | assertTrue(combinator.hasNext()); 140 | assertEquals(asList("A", "B", "E"), combinator.next()); 141 | assertTrue(combinator.hasNext()); 142 | assertEquals(asList("A", "C", "D"), combinator.next()); 143 | assertTrue(combinator.hasNext()); 144 | assertEquals(asList("A", "C", "E"), combinator.next()); 145 | assertTrue(combinator.hasNext()); 146 | assertEquals(asList("A", "D", "E"), combinator.next()); 147 | assertTrue(combinator.hasNext()); 148 | assertEquals(asList("B", "C", "D"), combinator.next()); 149 | assertTrue(combinator.hasNext()); 150 | assertEquals(asList("B", "C", "E"), combinator.next()); 151 | assertTrue(combinator.hasNext()); 152 | assertEquals(asList("B", "D", "E"), combinator.next()); 153 | assertTrue(combinator.hasNext()); 154 | assertEquals(asList("C", "D", "E"), combinator.next()); 155 | assertFalse(combinator.hasNext()); 156 | } 157 | 158 | @Test 159 | public void test_1C1() { 160 | Enumerator enumerator = Enumerators.combinator(singletonList("A"), 1); 161 | assertEquals(1, enumerator.size()); 162 | assertEquals(singletonList("A"), enumerator.get(0)); 163 | } 164 | 165 | @SuppressWarnings("unchecked") 166 | @Override 167 | , T> C createCombinator(List symbols, int k) { 168 | return (C) new Combinator<>(symbols, k); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/test/java/com/github/dakusui/combinatoradix/HomogeniousCombinatorTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dakusui.combinatoradix; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.Collections; 6 | import java.util.Iterator; 7 | import java.util.LinkedList; 8 | import java.util.List; 9 | 10 | import static java.util.Arrays.asList; 11 | import static java.util.Collections.emptyList; 12 | import static java.util.Collections.singletonList; 13 | import static org.junit.jupiter.api.Assertions.*; 14 | 15 | public class HomogeniousCombinatorTest extends CombinatorTestBase { 16 | @Test 17 | public void given2SymbolsAnd2forK$whenRunCombinator$thenCorrect() { 18 | exerciseTest( 19 | asList("a", "b"), 20 | 2 21 | ); 22 | } 23 | 24 | @Test 25 | public void given3SymbolsAnd3forK$whenRunCombinator$thenCorrect() { 26 | exerciseTest( 27 | asList("a", "b", "c"), 28 | 3 29 | ); 30 | } 31 | 32 | @Test 33 | public void given3SymbolsAnd2forK$whenRunCombinator$thenCorrect() { 34 | exerciseTest( 35 | asList("a", "b", "c"), 36 | 2 37 | ); 38 | } 39 | 40 | @Test 41 | public void given4SymbolsAnd2forK$whenRunCombinator$thenCorrect() { 42 | exerciseTest( 43 | asList("a", "b", "c", "d"), 44 | 2 45 | ); 46 | } 47 | 48 | @Test 49 | public void given5SymbolsAnd2forK$whenRunCombinator$thenCorrect() { 50 | exerciseTest( 51 | asList("a", "b", "c", "d", "e"), 52 | 2 53 | ); 54 | } 55 | 56 | @Test 57 | public void given6SymbolsAnd2forK$whenRunCombinator$thenCorrect() { 58 | exerciseTest( 59 | asList("a", "b", "c", "d", "e", "f"), 60 | 2 61 | ); 62 | } 63 | 64 | @Test 65 | public void given4SymbolsAnd3forK$whenRunCombinator$thenCorrect() { 66 | exerciseTest( 67 | asList("a", "b", "c", "d"), 68 | 3 69 | ); 70 | } 71 | 72 | @Test 73 | public void given5SymbolsAnd3forK$whenRunCombinator$thenCorrect() { 74 | exerciseTest( 75 | asList("a", "b", "c", "d", "e"), 76 | 3 77 | ); 78 | } 79 | 80 | @Test 81 | public void given6SymbolsAnd3forK$whenRunCombinator$thenCorrect() { 82 | exerciseTest( 83 | asList("a", "b", "c", "d", "e", "f"), 84 | 3 85 | ); 86 | } 87 | 88 | @Test 89 | public void test_empty() { 90 | // Empty set should result in empty iterator immediately 91 | Iterator> i = new HomogeniousCombinator<>(Collections.emptyList(), 0).iterator(); 92 | assertTrue(i.hasNext()); 93 | assertEquals(emptyList(), i.next()); 94 | assertFalse(i.hasNext()); 95 | } 96 | 97 | @Test 98 | public void test_nH1() { 99 | Iterator> homogeniousCombinator = new HomogeniousCombinator<>(testset1(), 1).iterator(); 100 | assertTrue(homogeniousCombinator.hasNext()); 101 | assertEquals(singletonList("A"), homogeniousCombinator.next()); 102 | assertTrue(homogeniousCombinator.hasNext()); 103 | assertEquals(singletonList("B"), homogeniousCombinator.next()); 104 | assertTrue(homogeniousCombinator.hasNext()); 105 | assertEquals(singletonList("C"), homogeniousCombinator.next()); 106 | assertTrue(homogeniousCombinator.hasNext()); 107 | assertEquals(singletonList("D"), homogeniousCombinator.next()); 108 | assertTrue(homogeniousCombinator.hasNext()); 109 | assertEquals(singletonList("E"), homogeniousCombinator.next()); 110 | assertFalse(homogeniousCombinator.hasNext()); 111 | } 112 | 113 | @Test 114 | public void test_nH2() { 115 | Iterator> homogeniousCombinator = Enumerators.homogeniousCombinator(testset1(), 2).iterator(); 116 | assertTrue(homogeniousCombinator.hasNext()); 117 | assertEquals(asList("A", "A"), homogeniousCombinator.next()); 118 | assertTrue(homogeniousCombinator.hasNext()); 119 | assertEquals(asList("A", "B"), homogeniousCombinator.next()); 120 | assertTrue(homogeniousCombinator.hasNext()); 121 | assertEquals(asList("A", "C"), homogeniousCombinator.next()); 122 | assertTrue(homogeniousCombinator.hasNext()); 123 | assertEquals(asList("A", "D"), homogeniousCombinator.next()); 124 | assertTrue(homogeniousCombinator.hasNext()); 125 | assertEquals(asList("A", "E"), homogeniousCombinator.next()); 126 | assertTrue(homogeniousCombinator.hasNext()); 127 | assertEquals(asList("B", "B"), homogeniousCombinator.next()); 128 | assertTrue(homogeniousCombinator.hasNext()); 129 | assertEquals(asList("B", "C"), homogeniousCombinator.next()); 130 | assertTrue(homogeniousCombinator.hasNext()); 131 | assertEquals(asList("B", "D"), homogeniousCombinator.next()); 132 | assertTrue(homogeniousCombinator.hasNext()); 133 | assertEquals(asList("B", "E"), homogeniousCombinator.next()); 134 | assertTrue(homogeniousCombinator.hasNext()); 135 | assertEquals(asList("C", "C"), homogeniousCombinator.next()); 136 | assertTrue(homogeniousCombinator.hasNext()); 137 | assertEquals(asList("C", "D"), homogeniousCombinator.next()); 138 | assertTrue(homogeniousCombinator.hasNext()); 139 | assertEquals(asList("C", "E"), homogeniousCombinator.next()); 140 | assertTrue(homogeniousCombinator.hasNext()); 141 | assertEquals(asList("D", "D"), homogeniousCombinator.next()); 142 | assertTrue(homogeniousCombinator.hasNext()); 143 | assertEquals(asList("D", "E"), homogeniousCombinator.next()); 144 | assertTrue(homogeniousCombinator.hasNext()); 145 | assertEquals(asList("E", "E"), homogeniousCombinator.next()); 146 | assertFalse(homogeniousCombinator.hasNext()); 147 | } 148 | 149 | public static void main(String[] args) { 150 | List items = new LinkedList<>(); 151 | items.add("A"); 152 | items.add("B"); 153 | items.add("C"); 154 | items.add("D"); 155 | items.add("E"); 156 | 157 | Iterator> enumerator = new HomogeniousCombinator<>(items, 2).iterator(); 158 | int i = 0; 159 | while (enumerator.hasNext()) { 160 | System.out.println(i + ":" + enumerator.next()); 161 | //enumerator.next(); 162 | i++; 163 | } 164 | } 165 | 166 | @SuppressWarnings("unchecked") 167 | @Override 168 | , T> C createCombinator(List symbols, int k) { 169 | return (C) new HomogeniousCombinator(symbols, k); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | 6 | com.github.dakusui 7 | combinatoradix 8 | 1.0.0-SNAPSHOT 9 | A mathematical enumeration library for Java 10 | jar 11 | 12 | combinatoradix 13 | https://github.com/dakusui/combinatoradix 14 | 15 | 16 | 17 | Apache License, Version 2.0 18 | http://www.apache.org/licenses/LICENSE-2.0.txt 19 | repo 20 | 21 | 22 | 23 | 24 | scm:git:git://github.com/dakusui/combinatoradix.git 25 | 26 | scm:git:git@github.com:dakusui/combinatoradix.git 27 | 28 | http://github.com/dakusui/combinatoradix 29 | HEAD 30 | 31 | 32 | 33 | 34 | dakusui 35 | Hiroshi Ukai 36 | dakusui@gmail.com 37 | 38 | 39 | 40 | 41 | UTF-8 42 | 1.8 43 | ${maven.compiler.source} 44 | 5.6.2 45 | 2.2 46 | 1.5.0 47 | 0.12 48 | 49 | 50 | 51 | 52 | 53 | org.junit.jupiter 54 | junit-jupiter 55 | ${junit.jupiter.version} 56 | test 57 | 58 | 59 | org.hamcrest 60 | hamcrest 61 | ${hamcrest.version} 62 | test 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | org.apache.maven.plugins 71 | maven-release-plugin 72 | 2.5 73 | 74 | -Dgpg.passphrase=${gpg.passphrase} 75 | 76 | 77 | 78 | org.codehaus.mojo 79 | cobertura-maven-plugin 80 | 2.7 81 | 82 | 83 | html 84 | xml 85 | 86 | 87 | 88 | 89 | 90 | 91 | org.pitest 92 | pitest-maven 93 | ${pitest-maven.version} 94 | 95 | 96 | org.pitest 97 | pitest-junit5-plugin 98 | ${pitest-junit5-plugin.version} 99 | 100 | 101 | 102 | 103 | com.github.dakusui.combinatoradix.* 104 | 105 | 106 | *Test 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | sonatype-nexus-snapshots 116 | Sonatype Nexus snapshot repository 117 | https://oss.sonatype.org/content/repositories/snapshots 118 | 119 | 120 | sonatype-nexus-staging 121 | Sonatype Nexus release repository 122 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 123 | 124 | 125 | 126 | 127 | 128 | 129 | release-sign-artifacts 130 | 131 | 132 | performRelease 133 | true 134 | 135 | 136 | 137 | 138 | 139 | org.apache.maven.plugins 140 | maven-gpg-plugin 141 | 1.4 142 | 143 | ${gpg.passphrase} 144 | 145 | 146 | 147 | sign-artifacts 148 | verify 149 | 150 | sign 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /src/test/java/com/github/dakusui/combinatoradix/PermutatorTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dakusui.combinatoradix; 2 | 3 | import com.github.dakusui.combinatoradix.utils.InternalUtils; 4 | import org.hamcrest.CoreMatchers; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.util.Collections; 8 | import java.util.Iterator; 9 | import java.util.LinkedList; 10 | import java.util.List; 11 | 12 | import static java.util.Arrays.asList; 13 | import static java.util.Collections.emptyList; 14 | import static java.util.Collections.singletonList; 15 | import static org.hamcrest.MatcherAssert.assertThat; 16 | import static org.junit.jupiter.api.Assertions.*; 17 | 18 | public class PermutatorTest extends EnumeratorTestBase { 19 | @Test 20 | public void givenA_1$whenRunPermutator$thenCorrect() { 21 | exerciseTest(singletonList( 22 | "a" 23 | ), 24 | 1 25 | ); 26 | } 27 | 28 | @Test 29 | public void givenA_0$whenRunPermutator$thenCorrect() { 30 | exerciseTest(singletonList( 31 | "a" 32 | ), 33 | 0 34 | ); 35 | } 36 | 37 | @Test 38 | public void givenABCD_4$whenRunPermutator$thenCorrect() { 39 | exerciseTest(asList( 40 | "a", "b", "c", "d" 41 | ), 42 | 4 43 | ); 44 | } 45 | 46 | @Test 47 | public void givenABCD_2$whenRunPermutator$thenCorrect() { 48 | exerciseTest(asList( 49 | "a", "b", "c", "d" 50 | ), 51 | 2 52 | ); 53 | } 54 | 55 | @Test 56 | public void givenABCD_1$whenRunPermutator$thenCorrect() { 57 | exerciseTest(asList( 58 | "a", "b", "c", "d" 59 | ), 60 | 1 61 | ); 62 | } 63 | 64 | @Test 65 | public void givenABCD_0$whenRunPermutator$thenCorrect() { 66 | exerciseTest(asList( 67 | "a", "b", "c", "d" 68 | ), 69 | 0 70 | ); 71 | } 72 | 73 | @Test 74 | public void givenABCD_5$whenRunPermutator$thenError() { 75 | exerciseTest(asList( 76 | "a", "b", "c", "d" 77 | ), 78 | 5 79 | ); 80 | } 81 | 82 | private void exerciseTest(List symbols, int k) { 83 | final Permutator permutator = new Permutator<>( 84 | symbols, 85 | k 86 | ); 87 | final List expected = new LinkedList() {{ 88 | for (int i = 0; i < permutator.size(); i++) { 89 | add(String.format("%2d:%s", i, permutator.get(i))); 90 | } 91 | }}; 92 | List actual = new LinkedList() {{ 93 | for (int i = 0; i < permutator.size(); i++) { 94 | add(String.format("%2d:%s", permutator.indexOf(permutator.get(i)), permutator.get(i))); 95 | } 96 | }}; 97 | 98 | System.out.println("--"); 99 | System.out.printf("%2s %-20s %-20s%n", "", "expected", "actual"); 100 | for (int i = 0; i < permutator.size(); i++) { 101 | System.out.printf( 102 | "%2s %-20s %-20s%n", 103 | expected.get(i).equals(actual.get(i)) ? 104 | "" : 105 | "NG", 106 | expected.get(i), 107 | actual.get(i)); 108 | } 109 | 110 | assertThat( 111 | actual.toString(), 112 | CoreMatchers.equalTo(expected.toString()) 113 | ); 114 | } 115 | 116 | @Test 117 | public void test_empty() { 118 | // Empty set should result in empty iterator immediately 119 | Iterator> i = new Permutator<>(Collections.emptyList(), 0).iterator(); 120 | assertTrue(i.hasNext()); 121 | assertEquals(emptyList(), i.next()); 122 | assertFalse(i.hasNext()); 123 | } 124 | 125 | @Test 126 | public void test_nP0() { 127 | Iterator> p = new Permutator<>(testset1(), 0).iterator(); 128 | assertTrue(p.hasNext()); 129 | assertEquals(emptyList(), p.next()); 130 | assertFalse(p.hasNext()); 131 | } 132 | 133 | @Test 134 | public void test_nP1() { 135 | Iterator> p = new Permutator<>(testset1(), 1).iterator(); 136 | assertTrue(p.hasNext()); 137 | assertEquals(singletonList("A"), p.next()); 138 | assertTrue(p.hasNext()); 139 | assertEquals(singletonList("B"), p.next()); 140 | assertTrue(p.hasNext()); 141 | assertEquals(singletonList("C"), p.next()); 142 | assertTrue(p.hasNext()); 143 | assertEquals(singletonList("D"), p.next()); 144 | assertTrue(p.hasNext()); 145 | assertEquals(singletonList("E"), p.next()); 146 | assertFalse(p.hasNext()); 147 | } 148 | 149 | @Test 150 | public void test_nP2() { 151 | Iterator> p = new Permutator<>(testset1(), 2).iterator(); 152 | assertTrue(p.hasNext()); 153 | assertEquals(asList("A", "B"), p.next()); 154 | assertTrue(p.hasNext()); 155 | assertEquals(asList("A", "C"), p.next()); 156 | assertTrue(p.hasNext()); 157 | assertEquals(asList("A", "D"), p.next()); 158 | assertTrue(p.hasNext()); 159 | assertEquals(asList("A", "E"), p.next()); 160 | assertTrue(p.hasNext()); 161 | assertEquals(asList("B", "A"), p.next()); 162 | assertTrue(p.hasNext()); 163 | assertEquals(asList("B", "C"), p.next()); 164 | assertTrue(p.hasNext()); 165 | assertEquals(asList("B", "D"), p.next()); 166 | assertTrue(p.hasNext()); 167 | assertEquals(asList("B", "E"), p.next()); 168 | assertTrue(p.hasNext()); 169 | assertEquals(asList("C", "A"), p.next()); 170 | assertTrue(p.hasNext()); 171 | assertEquals(asList("C", "B"), p.next()); 172 | assertTrue(p.hasNext()); 173 | assertEquals(asList("C", "D"), p.next()); 174 | assertTrue(p.hasNext()); 175 | assertEquals(asList("C", "E"), p.next()); 176 | assertTrue(p.hasNext()); 177 | assertEquals(asList("D", "A"), p.next()); 178 | assertTrue(p.hasNext()); 179 | assertEquals(asList("D", "B"), p.next()); 180 | assertTrue(p.hasNext()); 181 | assertEquals(asList("D", "C"), p.next()); 182 | assertTrue(p.hasNext()); 183 | assertEquals(asList("D", "E"), p.next()); 184 | assertTrue(p.hasNext()); 185 | assertEquals(asList("E", "A"), p.next()); 186 | assertTrue(p.hasNext()); 187 | assertEquals(asList("E", "B"), p.next()); 188 | assertTrue(p.hasNext()); 189 | assertEquals(asList("E", "C"), p.next()); 190 | assertTrue(p.hasNext()); 191 | assertEquals(asList("E", "D"), p.next()); 192 | assertFalse(p.hasNext()); 193 | } 194 | 195 | @Test 196 | public void test_nPn() { 197 | Iterator> p = Enumerators.permutator(testset2(), 3).iterator(); 198 | assertTrue(p.hasNext()); 199 | assertEquals(asList("A", "B", "C"), p.next()); 200 | assertTrue(p.hasNext()); 201 | assertEquals(asList("A", "C", "B"), p.next()); 202 | assertTrue(p.hasNext()); 203 | assertEquals(asList("B", "A", "C"), p.next()); 204 | assertTrue(p.hasNext()); 205 | assertEquals(asList("B", "C", "A"), p.next()); 206 | assertTrue(p.hasNext()); 207 | assertEquals(asList("C", "A", "B"), p.next()); 208 | assertTrue(p.hasNext()); 209 | assertEquals(asList("C", "B", "A"), p.next()); 210 | assertFalse(p.hasNext()); 211 | } 212 | 213 | @Test 214 | public void test_forEach() { 215 | Enumerator.Base p = new Permutator<>(testset2(), 3); 216 | int i = 0; 217 | for (List entry : p) { 218 | assertEquals(3, entry.size()); 219 | i++; 220 | } 221 | assertEquals(InternalUtils.nPk(3, 3), i); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # combinatoradix 2 | ```combinatoradix``` is a Java library which enumerates partial permutations, 3 | combinations, and repeated combinations of a set that you give to it in a natural 4 | Java manner and **suitable for concurrent execution**. 5 | 6 | About what permutations, combinations, and repeated combinations are in more detail, 7 | please refer to the Wikipedia articles([4] and [5]). 8 | 9 | ```combinatoradix``` is created and maintained by Hiroshi Ukai (dakusui@gmail.com). 10 | 11 | [![Build Status](https://travis-ci.org/dakusui/combinatoradix.svg?branch=master)](https://travis-ci.org/dakusui/combinatoradix) 12 | [![codecov.io](https://codecov.io/github/dakusui/combinatoradix/coverage.svg?branch=master)](https://codecov.io/github/dakusui/combinatoradix?branch=master) 13 | 14 | 15 | # Installation 16 | This library requires [Java][7] 6 or later as its execution environment. 17 | To build this library from source, [Maven][7] 3.0.5 or later will be necessary. 18 | 19 | ## Maven coordinate 20 | 21 | ```xml 22 | 23 | 24 | com.github.dakusui 25 | combinatoradix 26 | 0.8.2 27 | 28 | ``` 29 | 30 | 31 | ## Building from source 32 | You can build ```combinatoradix``` by getting the source code from github. 33 | ``` 34 | 35 | $ git clone https://github.com/dakusui/combinatoradix.git 36 | ... 37 | $ cd combinatoradix 38 | ... 39 | $ mvn package 40 | ... 41 | $ 42 | ``` 43 | 44 | You will find a compiled jar file ```combinatoradix-{X.Y.Z}-SNAPSHOT.jar``` under 45 | ```target/``` directory. 46 | 47 | # How to use 48 | There are 3 main classes in this library, 49 | 50 | + Permutator (com.github.dakusui.combinatoradix) 51 | + Combinator (com.github.dakusui.combinatoradix) 52 | + HomogeniousCombinator (com.github.dakusui.combinatoradix) 53 | 54 | All of them can be used in the same manner. 55 | Through the constructor of ```Permutator```, ```Combinator```, or ```HomogeniousCombinator``` 56 | you can specify a list of values to be enumerated and how many elements in the set 57 | should be chosen at once. And then you can iterate all the possible 58 | partial permutations, combinations, or repeated combinations respectively. 59 | 60 | Simplest example to enumerate all the possible combinations in a set would be like following. 61 | 62 | ```java 63 | 64 | @Test 65 | public void printCombinations() { 66 | List list = Arrays.asList("A", "B", "C", "D", "E"); 67 | for (List each : new Combinator<>(list, 2)) { 68 | System.out.println(each); 69 | } 70 | } 71 | ``` 72 | 73 | And more importantly **you can pick up Nth partial permutation, combination, or repeated 74 | combination directly** without iterating upto N. 75 | 76 | 77 | ```java 78 | 79 | @Test 80 | public void printNthCombinationDirectly() { 81 | List list = Arrays.asList("A", "B", "C", "D", "E"); 82 | System.out.println(new Combinator<>(list, 2).get(3)); 83 | } 84 | ``` 85 | 86 | This feature will allow you flexible concurrent enumerations. 87 | About the benefit of this behaviour, you can refer to "Why you want to use ```combinatoradix```" 88 | sub-section. 89 | 90 | As already mentioned, by changing ```Combinator``` to ```Permutator``` or ```HomogeniousCombinator```, 91 | you can enumerate partial permutations or repeated combinations respectively. 92 | Please refer to "Examples" sections for more samples. 93 | 94 | 95 | # How it works and why you want to use ```combinatoradix``` 96 | ## How it works 97 | As its name suggests, this library relies on "factorial number system"[2] "combinatorial number system"[3]. 98 | Basic idea is to use a number system where each digit specifies an element in a given set 99 | and map integers from 0 to P(n,k) - 1 to all the k-sequences without repetition on n-set. 100 | 101 | For instance, to enumerate permutations P(5,3), ```combinatoradix``` internally uses a number system below 102 | 103 | 104 | | 2nd| 1st| 0th| 105 | |:---:|:---:|:---:| 106 | | 0| 0| 0| 107 | | 1| 1| 1| 108 | | 2| 2| 2| 109 | |(n/a)| 3| 3| 110 | |(n/a)|(n/a)| 4| 111 | 112 | All the possible numbers in this system are following. 113 | 114 | ``` 115 | 116 | 0 1 2 3 4 117 | 10 11 12 13 14 118 | 20 21 22 23 24 119 | 30 31 32 33 34 120 | 100 101 102 103 104 121 | 110 111 112 113 114 122 | 120 121 122 123 124 123 | 130 131 132 133 134 124 | 200 201 202 203 204 125 | 210 211 212 213 214 126 | 220 221 222 223 224 127 | 230 231 232 233 234 128 | ``` 129 | 130 | As shown, there are only 60 numbers and you may notice that P(5, 3) = 5! / 2! = 5 * 4 * 3 * 2 * 1 / 2 * 1 = 60. 131 | We can translate each of those sixty numbers into one permutation respectively for instance, 132 | 133 | ``` 134 | 135 | 0 -> (000) -> [Christopher, Bob, Alice] 136 | ``` 137 | 138 | To describe the algorithm, for convenience, let's consider that those are strings and 139 | suppose a function ```nth(s, i)``` extracts ith digit from a string. 140 | i.e., 141 | 142 | ``` 143 | 144 | nth(234, 0) = 4 145 | nth(234, 1) = 3 146 | nth(234, 2) = 2 147 | nth( 31, 2) = 0 148 | ``` 149 | 150 | Then we can come up with a function that translates those strings into decimals as follows. 151 | 152 | ``` 153 | 154 | dec(s) = nth(s, 2) * 4 * 3 + nth(s, 1) * 4 + nth(s, 0) 155 | ``` 156 | 157 | And suppose that ```S``` is a number represented in the system above. Now the 158 | algorithm can be described as following. 159 | 160 | ```bash 161 | 162 | # "initial" can be [Alice, Bob, Christopher, Derek, Elizabeth], for example. 163 | initial=[...] 164 | remaining=initial 165 | tmp=[] 166 | for i in [0 1 2...k-1]; do 167 | each_digit=nth(S, i) 168 | tmp.insert(remaining[each_digt]) 169 | done 170 | result=tmp 171 | 172 | ``` 173 | 174 | That is, if the number is ```234```, 175 | 176 | + pick up 4 and take the 4th item in [Alice, Bob, Christopher, Derek, Elizabeth], which is "Elizabeth". 177 | And it will be inserted in ```tmp```([Elizabeth]). The remaining elements will be [Alice, Bob, Christopher, Derek]. 178 | + pick up 3 and take the 3rd item in [Alice, Bob, Christopher, Derek], which is "Derek" 179 | And it will be inserted in ```tmp```([Derek, Elizabeth]). The remaining elements will be [Alice, Bob, Christopher]. 180 | + pick up 2 and take the 2nd item in [Alice, Bob, Christopher], which is "Christopher". 181 | And it will be inserted in ```tmp```([Christopher, Derek, Elizabeth]). 182 | + ```tmp``` will be assigned to ```result```. 183 | 184 | 185 | Of course we can generalize this number system so that we can apply it to any P(n, k). 186 | The detail is discussed in a Wikipedia [article][2]. 187 | 188 | ## Why you want to use ```combinatoradix``` 189 | 190 | This approach is clearly slower than other normal 'iterating' algorithm such as 191 | used by "[combinatorics][1]". 192 | But there are still very good reason to use it. 193 | 194 | + **Easy to resume**: As long as you have an index (```combinatoradix``` 195 | classes have ```get(long index): List``` method, which always return the same 196 | same list as long as the same index is given to the same type of object created 197 | from the same constructor parameters.), you can stop and resume your operation at 198 | any point. 199 | + **Suitable for parallel execution**: For the same reason as the previous one, 200 | you can split a big enumeration task into pieces and execute them concurrently. 201 | + **Predictable order**: As it will be shown in the example, if you give a list 202 | sorted in dictionary order, the result will be sorted in the dictionary order, 203 | too. 204 | 205 | For the first and second point, probably we need some more explanation. 206 | As mentioned, main classes of ```combinatoradix```, all of which extend ```Enumerator```, 207 | have ```get(long index): List``` method and ```size(): long``` method. 208 | 209 | And it is guaranteed that the method return the same ```List``` 210 | if the same initial data set, ```k``` (parameter to specify how many elements 211 | should be picked up from initial data set), and ```index`` are given. 212 | 213 | Therefore, for instance, ```combinatoradix``` can answer a question like "There 214 | are 26 alphabets from A to Z, if we create 5 character words from them. But each 215 | character can be used only once. What is the 1,000,000th word in dictionary order?". 216 | 217 | The answer is ```[D, I, K, H, Q]```. 218 | And it is a single line job for ```combinatoradix``` once data set is given. 219 | 220 | ```java 221 | 222 | @Test 223 | public void print1000000thWord() { 224 | List list = Arrays.asList( 225 | /* 226 | 1 2 3 4 5 6 7 8 9 10 227 | */ 228 | "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", 229 | "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", 230 | "U", "V", "W", "X", "Y", "Z" 231 | ); 232 | System.out.println(new Permutator<>(list, 5).get(1_000_000)); 233 | } 234 | 235 | ``` 236 | 237 | Probably other libraries can enumerate all the 5 character length words faster than 238 | ```combinatoradix```. But for figuring out "1,000,000th word", it can be much more 239 | efficient and do it instantly. 240 | 241 | This might look a trivial characteristic, when you want to process all the possible 242 | patterns, which can easily become huge number, and you have multiple cores (or servers 243 | with Hadoop), this becomes useful. 244 | 245 | In the above mentioned example, we will have 7,893,600 possible words in total. 246 | If you have ```N``` cores (```Core_0```, ```Core_1```, ... ```Core_i```,...```Core_N-1```) 247 | and in order to process all of them as fast as possible what you can do is to 248 | let ```Core_i``` process words returned by ```get(j)```, where ```j mod N = i```. 249 | 250 | # Examples 251 | Most of examples shown in this section is written for Java 7. But by just 252 | changing ```<>``` into `````` (or other types depending on contexts), 253 | they should work. 254 | 255 | ## Enumerating partial permutations (Java7) 256 | To enumerate all the partial permutations, you can use "Permutator" class. 257 | An example follows. 258 | 259 | ```java 260 | 261 | @Test 262 | public void printPartialPermutations() { 263 | List list = Arrays.asList("A", "B", "C", "D", "E"); 264 | 265 | for (List each : new Permutator<>(list, 2)) { 266 | System.out.println(each); 267 | } 268 | } 269 | 270 | ``` 271 | 272 | This program will print output below. 273 | 274 | ``` 275 | 276 | [A, B] 277 | [A, C] 278 | [A, D] 279 | [A, E] 280 | [B, A] 281 | [B, C] 282 | [B, D] 283 | [B, E] 284 | [C, A] 285 | [C, B] 286 | [C, D] 287 | [C, E] 288 | [D, A] 289 | [D, B] 290 | [D, C] 291 | [D, E] 292 | [E, A] 293 | [E, B] 294 | [E, C] 295 | [E, D] 296 | 297 | ``` 298 | 299 | You may notice that the elements are sorted in dictionary order. 300 | This is an important characteristic that ```combinatorix``` offers as already discussed. 301 | 302 | 303 | ## Enumerating combinations (Java7) 304 | 305 | Similarly you can enumerate all the combination just by changing the enumerator 306 | class from ```Permutator``` to ```Combinator```. 307 | 308 | ```java 309 | 310 | @Test 311 | public void printCombinations() { 312 | List list = Arrays.asList("A", "B", "C", "D", "E"); 313 | for (List each : new Combinator<>(list, 2)) { 314 | System.out.println(each); 315 | } 316 | } 317 | ``` 318 | 319 | Same as ```Permutator``` the output is sorted in dictionary order. 320 | 321 | ``` 322 | 323 | [A, B] 324 | [A, C] 325 | [A, D] 326 | [A, E] 327 | [B, C] 328 | [B, D] 329 | [B, E] 330 | [C, D] 331 | [C, E] 332 | [D, E] 333 | ``` 334 | 335 | ## Enumerating repeated combinations (Java7) 336 | 337 | The class to be used for repeated combinations is ```HomogeniousCombinator```. 338 | 339 | ```java 340 | 341 | public void printRepeatedCombinations() { 342 | List list = Arrays.asList("A", "B", "C", "D", "E"); 343 | for (List each : new HomogeniousCombinator<>(list, 2)) { 344 | System.out.println(each); 345 | } 346 | } 347 | ``` 348 | 349 | Similarly, dictionary ordered output will be printed. 350 | But an element in the original list can appear multiple times like ```[A,A]```, 351 | ```[B,B]```, etc. 352 | 353 | ```java 354 | 355 | [A, A] 356 | [A, B] 357 | [A, C] 358 | [A, D] 359 | [A, E] 360 | [B, B] 361 | [B, C] 362 | [B, D] 363 | [B, E] 364 | [C, C] 365 | [C, D] 366 | [C, E] 367 | [D, D] 368 | [D, E] 369 | [E, E] 370 | ``` 371 | 372 | ## Iterate all the partial permutations sequentially (Java7) 373 | Following example will try to find the longest 3-sequence among all possibles. 374 | 375 | In real life, please use ```Combinator```, instead of ```Permutator``` if you 376 | really want to know the length of the longest sequence. 377 | Essentially this is doing the 378 | 379 | ```java 380 | 381 | @Test 382 | public void findLongestSequenceSequentially() { 383 | int lengthOfLongestSequence = 0; 384 | for (List seq : 385 | new Permutator<>( 386 | Arrays.asList("Alice", "Bob", "Christopher", "Derek", "Elizabeth"), 387 | 3)) { 388 | int l = 0; 389 | for (String s : seq) { 390 | l += s.length(); 391 | } 392 | if (l > lengthOfLongestSequence) lengthOfLongestSequence = l; 393 | } 394 | System.out.println(lengthOfLongestSequence); 395 | } 396 | ``` 397 | 398 | This will print "25" 399 | 400 | ## Iterate all the partial permutations concurrently (with Java8) 401 | In Jave 8 single node map/reduce is an out-of-box feature. ([single-node MapReduce is available in Java 8][6]). 402 | 403 | Following example will try to find the longest 3-sequence among all possibles same as 404 | the previous one but the difference is that it will be executed concurrently. 405 | 406 | 407 | ```java 408 | 409 | @Test 410 | public void findLongestSequenceConcurrently() { 411 | int lengthOfLongestSequence = new EnumeratorAdapter<>( 412 | new Permutator<>( 413 | Arrays.asList("Alice", "Bob", "Christopher", "Derek", "Elizabeth"), 414 | 3)) 415 | .parallelStream() 416 | .map(seq -> { 417 | int ret = 0; 418 | for (String each : seq) { 419 | ret += each.length(); 420 | } 421 | return ret; 422 | }).reduce(0, Integer::max); 423 | System.out.println(lengthOfLongestSequence); 424 | } 425 | ``` 426 | 427 | ```EnumeratorAdapter``` is a trivial adapter class defined as follows. 428 | 429 | ```java 430 | 431 | public class EnumeratorAdapter extends AbstractList> { 432 | final Enumerator enumerator; 433 | 434 | public EnumeratorAdapter(Enumerator enumerator) { 435 | this.enumerator = enumerator; 436 | } 437 | 438 | @Override 439 | public List get(int index) { 440 | return this.enumerator.get(index); 441 | } 442 | 443 | @Override 444 | public int size() { 445 | long ret = this.enumerator.size(); 446 | if (ret > Integer.MAX_VALUE) { 447 | throw new IllegalStateException(); 448 | } 449 | return (int) ret; 450 | } 451 | } 452 | ``` 453 | 454 | This example will print "25", same as the previous one. 455 | Of course you can use the same technique for combinations and repeated combinations, too. 456 | 457 | 458 | # References 459 | * [1] "combinatorics" library, Christian Trimble 460 | * [2] "Factorial number system", Wikipedia.org 461 | * [3] "Combinatorial number system", Wikipedia.org 462 | * [4] "Permutation", Wikipedia.org 463 | * [5] "Combination", Wikipedia.org 464 | * [6] "mapreduce - Simple Java Map/Reduce framework" 465 | * [7] "Java SE downloads", Oracle 466 | * [8] "Maven - Download Apache Maven" 467 | 468 | [1]: https://github.com/ctrimble/combinatorics 469 | [2]: https://en.wikipedia.org/wiki/Factorial_number_system 470 | [3]: https://en.wikipedia.org/wiki/Combinatorial_number_system 471 | [4]: http://en.wikipedia.org/wiki/Permutation 472 | [5]: http://en.wikipedia.org/wiki/Combination 473 | [6]: http://stackoverflow.com/questions/5260212/simple-java-map-reduce-framework 474 | [7]: http://www.oracle.com/technetwork/java/javase/downloads/index.html 475 | [8]: https://maven.apache.org/download.cgi 476 | 477 | # Copyright and license 478 | 479 | Copyright 2013 Hiroshi Ukai. 480 | 481 | Licensed under the Apache License, Version 2.0 (the "License"); 482 | you may not use this work except in compliance with the License. 483 | You may obtain a copy of the License in the LICENSE file, or at: 484 | 485 | [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) 486 | 487 | Unless required by applicable law or agreed to in writing, software 488 | distributed under the License is distributed on an "AS IS" BASIS, 489 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 490 | See the License for the specific language governing permissions and 491 | limitations under the License. 492 | 493 | --------------------------------------------------------------------------------