├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main └── kotlin │ └── com │ └── github │ └── shiguruikai │ └── combinatoricskt │ ├── CartesianProductGenerator.kt │ ├── CombinationsGenerator.kt │ ├── CombinationsWithRepetitionGenerator.kt │ ├── CombinatorialSequence.kt │ ├── Combinatorics.kt │ ├── DerangementGenerator.kt │ ├── PermutationsGenerator.kt │ ├── PermutationsWithRepetitionGenerator.kt │ ├── PowerSetGenerator.kt │ ├── extensions.kt │ └── internal │ ├── Extensions.kt │ └── Math.kt └── test ├── java └── CombinatoricsTest.java └── kotlin └── com └── github └── shiguruikai └── combinatoricskt ├── CartesianProductGeneratorTest.kt ├── CombinationsGeneratorTest.kt ├── CombinationsWithRepetitionGeneratorTest.kt ├── CombinatorialSequenceTest.kt ├── CombinatoricsKtTest.kt ├── DerangementGeneratorTest.kt ├── ListComparator.kt ├── PermutationsGeneratorTest.kt ├── PermutationsWithRepetitionGeneratorTest.kt ├── PowerSetGeneratorTest.kt └── internal ├── ExtensionsKtTest.kt └── MathTest.kt /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | out/ 4 | build/ 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 1.6.0 (2020-02-24) 4 | - シーケンス生成クラスの名前を変更 5 | 6 | ## 1.5.0 (2020-02-23) 7 | - `Itertools`を`Combinatorics`に変更 8 | 9 | ## 1.4.0 (2019-03-20) 10 | - Kotlinを1.3.21にアップデート、それに伴いコードを微修正 11 | 12 | ## 1.3.0 (2018-07-18) 13 | - `CombinatorialSequence`の型パラメータを共変に変更 14 | - `CartesianProductGenerator`の変更点 15 | - `repeat`に巨大な数を指定したときなどでオーバーフローしたとき例外を発生させる 16 | - パフォーマンスが向上 17 | 18 | ## 1.2.0 (2018-07-15) 19 | - シーケンス生成のパフォーマンスが向上 20 | 21 | ## 1.1.0 (2018-07-14) 22 | 23 | - 完全順列を生成するクラス`DerangementGenerator`を追加 24 | - Iterable, Array の拡張関数`derangements`を追加 25 | - ソースファイルのコピーライトヘッダーを少し変更 26 | - 順列生成のパフォーマンスを少し向上 27 | 28 | ## 1.0.0 (2018-07-12) 29 | - Maven Central Repository にリリース 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 shiguruikai 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Maven Central](https://img.shields.io/maven-central/v/com.github.shiguruikai/combinatoricskt.svg)](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.github.shiguruikai%22%20AND%20a%3A%22combinatoricskt%22) 2 | [![MIT License](https://img.shields.io/badge/license-MIT-green.svg)](/LICENSE) 3 | 4 | # combinatoricskt 5 | 6 | A combinatorics library for Kotlin. 7 | 8 | Generate the following sequence from Iterable or Array.
9 | Iterable または Array から以下のシーケンスを生成する。 10 | 11 | - [Permutations](/README.md#permutations) (順列) 12 | - [Permutations with Repetition](/README.md#permutations-with-repetition) (重複順列) 13 | - [Derangements](/README.md#derangements) (完全順列) 14 | - [Combinations](/README.md#combinations) (組合せ) 15 | - [Combinations with Repetition](/README.md#combinations-with-repetition) (重複組合せ) 16 | - [Cartesian Product](/README.md#cartesian-product) (デカルト積) 17 | - [Power Set](/README.md#power-set) (冪集合) 18 | 19 | ## Download 20 | 21 | Gradle Groovy DSL 22 | ```groovy 23 | implementation 'com.github.shiguruikai:combinatoricskt:1.6.0' 24 | ``` 25 | 26 | Gradle Kotlin DSL 27 | ```kotlin 28 | implementation("com.github.shiguruikai:combinatoricskt:1.6.0") 29 | ``` 30 | 31 | Apache Maven 32 | ```xml 33 | 34 | com.github.shiguruikai 35 | combinatoricskt 36 | 1.6.0 37 | 38 | ``` 39 | 40 | ## Permutations 41 | 42 | ```kotlin 43 | listOf(1, 2, 3).permutations() // [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]] 44 | listOf(1, 2, 3).permutations(0) // [[]] 45 | listOf(1, 2, 3).permutations(1) // [[1], [2], [3]] 46 | listOf(1, 2, 3).permutations(2) // [[1, 2], [1, 3], [2, 1], [2, 3], [3, 1], [3, 2]] 47 | listOf(1, 2, 3).permutations(3) // [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]] 48 | listOf(1, 2, 3).permutations(4) // [] 49 | Combinatorics.permutations(listOf(1, 2, 3)) // [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]] 50 | PermutationsGenerator.indices(3, 2) // [[0, 1], [0, 2], [1, 0], [1, 2], [2, 0], [2, 1]] 51 | ``` 52 | 53 | ## Permutations with Repetition 54 | 55 | ```kotlin 56 | listOf(1, 2, 3).permutationsWithRepetition(0) // [[]] 57 | listOf(1, 2, 3).permutationsWithRepetition(1) // [[1], [2], [3]] 58 | listOf(1, 2, 3).permutationsWithRepetition(2) // [[1, 1], [1, 2], [1, 3], [2, 1], [2, 2], [2, 3], [3, 1], [3, 2], [3, 3]] 59 | listOf(1, 2, 3).permutationsWithRepetition(3) // [[1, 1, 1], [1, 1, 2], [1, 1, 3], [1, 2, 1], [1, 2, 2], [1, 2, 3], [1, 3, 1], [1, 3, 2], [1, 3, 3], [2, 1, 1], [2, 1, 2], [2, 1, 3], [2, 2, 1], [2, 2, 2], [2, 2, 3], [2, 3, 1], [2, 3, 2], [2, 3, 3], [3, 1, 1], [3, 1, 2], [3, 1, 3], [3, 2, 1], [3, 2, 2], [3, 2, 3], [3, 3, 1], [3, 3, 2], [3, 3, 3]] 60 | Combinatorics.permutationsWithRepetition(listOf(1, 2, 3), 2) // [[1, 1], [1, 2], [1, 3], [2, 1], [2, 2], [2, 3], [3, 1], [3, 2], [3, 3]] 61 | PermutationsWithRepetitionGenerator.indices(3, 2) // [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], [2, 0], [2, 1], [2, 2]] 62 | ``` 63 | 64 | ## Derangements 65 | 66 | ```kotlin 67 | listOf(1).derangements() // [] 68 | listOf(1, 2).derangements() // [[2, 1]] 69 | listOf(1, 2, 3).derangements() // [[2, 3, 1], [3, 1, 2]] 70 | listOf(1, 2, 3, 4).derangements() // [[2, 1, 4, 3], [2, 3, 4, 1], [2, 4, 1, 3], [3, 1, 4, 2], [3, 4, 1, 2], [3, 4, 2, 1], [4, 1, 2, 3], [4, 3, 1, 2], [4, 3, 2, 1]] 71 | Combinatorics.derangements(listOf(1, 2, 3)) // [[2, 3, 1], [3, 1, 2]] 72 | DerangementGenerator.indices(3) // [[1, 2, 0], [2, 0, 1]] 73 | ``` 74 | 75 | ## Combinations 76 | 77 | ```kotlin 78 | listOf(1, 2, 3).combinations(0) // [[]] 79 | listOf(1, 2, 3).combinations(1) // [[1], [2], [3]] 80 | listOf(1, 2, 3).combinations(2) // [[1, 2], [1, 3], [2, 3]] 81 | listOf(1, 2, 3).combinations(3) // [[1, 2, 3]] 82 | listOf(1, 2, 3).combinations(4) // [] 83 | Combinatorics.combinations(listOf(1, 2, 3), 2) // [[1, 2], [1, 3], [2, 3]] 84 | CombinationsGenerator.indices(3, 2) // [[0, 1], [0, 2], [1, 2]] 85 | ``` 86 | 87 | ## Combinations with Repetition 88 | 89 | ```kotlin 90 | listOf(1, 2, 3).combinationsWithRepetition(0) // [[]] 91 | listOf(1, 2, 3).combinationsWithRepetition(1) // [[1], [2], [3]] 92 | listOf(1, 2, 3).combinationsWithRepetition(2) // [[1, 1], [1, 2], [1, 3], [2, 2], [2, 3], [3, 3]] 93 | listOf(1, 2, 3).combinationsWithRepetition(3) // [[1, 1, 1], [1, 1, 2], [1, 1, 3], [1, 2, 2], [1, 2, 3], [1, 3, 3], [2, 2, 2], [2, 2, 3], [2, 3, 3], [3, 3, 3]] 94 | listOf(1, 2, 3).combinationsWithRepetition(4) // [[1, 1, 1, 1], [1, 1, 1, 2], [1, 1, 1, 3], [1, 1, 2, 2], [1, 1, 2, 3], [1, 1, 3, 3], [1, 2, 2, 2], [1, 2, 2, 3], [1, 2, 3, 3], [1, 3, 3, 3], [2, 2, 2, 2], [2, 2, 2, 3], [2, 2, 3, 3], [2, 3, 3, 3], [3, 3, 3, 3]] 95 | Combinatorics.combinationsWithRepetition(listOf(1, 2, 3), 2) // [[1, 1], [1, 2], [1, 3], [2, 2], [2, 3], [3, 3]] 96 | CombinationsWithRepetitionGenerator.indices(3, 2) // [[0, 0], [0, 1], [0, 2], [1, 1], [1, 2], [2, 2]] 97 | ``` 98 | 99 | ## Cartesian Product 100 | 101 | ```kotlin 102 | listOf(1, 2).cartesianProduct(listOf(3, 4, 5), listOf(6)) // [[1, 3, 6], [1, 4, 6], [1, 5, 6], [2, 3, 6], [2, 4, 6], [2, 5, 6]] 103 | listOf('A', 'B').cartesianProduct(listOf('X', 'Y')) // [[A, X], [A, Y], [B, X], [B, Y]] 104 | listOf('A', 'B').cartesianProduct(listOf('X', 'Y'), repeat = 2) // [[A, X, A, X], [A, X, A, Y], [A, X, B, X], [A, X, B, Y], [A, Y, A, X], [A, Y, A, Y], [A, Y, B, X], [A, Y, B, Y], [B, X, A, X], [B, X, A, Y], [B, X, B, X], [B, X, B, Y], [B, Y, A, X], [B, Y, A, Y], [B, Y, B, X], [B, Y, B, Y]] 105 | Combinatorics.cartesianProduct(listOf("Java"), listOf(8, 11)) // [[Java, 8], [Java, 11]] 106 | CartesianProductGenerator.indices(2, 2, repeat = 1) // [[0, 0], [0, 1], [1, 0], [1, 1]] 107 | ``` 108 | 109 | ## Power Set 110 | 111 | ```kotlin 112 | listOf(1, 2, 3).powerset() // [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]] 113 | Combinatorics.powerset(listOf('A', 'B')) // [[], [A], [B], [A, B]] 114 | PowerSetGenerator.indices(3) // [[], [0], [1], [0, 1], [2], [0, 2], [1, 2], [0, 1, 2]] 115 | ``` 116 | 117 | ## License 118 | 119 | [MIT](/LICENSE) 120 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java-library' 3 | id 'maven-publish' 4 | id 'signing' 5 | id 'org.jetbrains.kotlin.jvm' version '1.3.61' 6 | id 'org.jetbrains.dokka' version '0.10.1' 7 | } 8 | 9 | group 'com.github.shiguruikai' 10 | version '1.6.0' 11 | 12 | repositories { 13 | mavenCentral() 14 | jcenter() 15 | } 16 | 17 | dependencies { 18 | implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' 19 | 20 | testImplementation 'org.junit.jupiter:junit-jupiter:5.6.0' 21 | } 22 | 23 | compileKotlin { 24 | kotlinOptions.jvmTarget = '1.8' 25 | } 26 | compileTestKotlin { 27 | kotlinOptions.jvmTarget = '1.8' 28 | } 29 | 30 | test { 31 | useJUnitPlatform() 32 | } 33 | 34 | jar { 35 | manifest { 36 | attributes( 37 | 'Implementation-Title': project.name, 38 | 'Implementation-Version': project.version 39 | ) 40 | } 41 | } 42 | 43 | dokka { 44 | outputFormat = 'html' 45 | outputDirectory = "$buildDir/dokka" 46 | configuration { 47 | jdkVersion = 8 48 | reportUndocumented = true 49 | } 50 | } 51 | 52 | task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) { 53 | outputFormat = 'javadoc' 54 | outputDirectory = javadoc.destinationDir 55 | configuration { 56 | jdkVersion = 8 57 | reportUndocumented = true 58 | } 59 | } 60 | 61 | task javadocJar(type: Jar, dependsOn: dokkaJavadoc) { 62 | archiveClassifier.set('javadoc') 63 | from javadoc.destinationDir 64 | } 65 | 66 | 67 | task sourcesJar(type: Jar, dependsOn: classes) { 68 | archiveClassifier.set('sources') 69 | from sourceSets.main.allSource 70 | } 71 | 72 | java { 73 | withJavadocJar() 74 | withSourcesJar() 75 | } 76 | 77 | publishing { 78 | publications { 79 | maven(MavenPublication) { 80 | from components.java 81 | pom { 82 | name = project.name 83 | description = 'A combinatorics library for Kotlin' 84 | url = 'https://github.com/shiguruikai/combinatoricskt' 85 | licenses { 86 | license { 87 | name = 'MIT License' 88 | url = 'https://github.com/shiguruikai/combinatoricskt/blob/master/LICENSE' 89 | distribution = 'repo' 90 | } 91 | } 92 | developers { 93 | developer { 94 | id = 'shiguruikai' 95 | name = 'Shigurui Kai' 96 | email = 'kai.shigurui@gmail.com' 97 | } 98 | } 99 | scm { 100 | connection = 'scm:git:https://github.com/shiguruikai/combinatoricskt.git' 101 | developerConnection = 'scm:git:https://github.com/shiguruikai/combinatoricskt.git' 102 | url = 'https://github.com/shiguruikai/combinatoricskt' 103 | } 104 | } 105 | } 106 | } 107 | repositories { 108 | maven { 109 | def releasesRepoUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2' 110 | def snapshotsRepoUrl = 'https://oss.sonatype.org/content/repositories/snapshots' 111 | url project.version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl 112 | credentials { 113 | username = sonatypeUsername 114 | password = sonatypePassword 115 | } 116 | } 117 | } 118 | } 119 | 120 | signing { 121 | useGpgCmd() 122 | sign publishing.publications.maven 123 | } 124 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | systemProp.org.gradle.internal.publish.checksums.insecure=true 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiguruikai/combinatoricskt/39fad45833e2f1808367044c62805657a31e22ff/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | @rem Execute Gradle 88 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 89 | 90 | :end 91 | @rem End local scope for the variables with windows NT shell 92 | if "%ERRORLEVEL%"=="0" goto mainEnd 93 | 94 | :fail 95 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 96 | rem the _cmd.exe /c_ return code! 97 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 98 | exit /b 1 99 | 100 | :mainEnd 101 | if "%OS%"=="Windows_NT" endlocal 102 | 103 | :omega 104 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'combinatoricskt' 2 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/shiguruikai/combinatoricskt/CartesianProductGenerator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 shiguruikai 3 | * 4 | * Licensed under the MIT license 5 | * https://github.com/shiguruikai/combinatoricskt/blob/master/LICENSE 6 | */ 7 | 8 | package com.github.shiguruikai.combinatoricskt 9 | 10 | import com.github.shiguruikai.combinatoricskt.internal.mapToArray 11 | import com.github.shiguruikai.combinatoricskt.internal.times 12 | import java.math.BigInteger 13 | 14 | /** 15 | * The class [CartesianProductGenerator] contains methods for generating cartesian product. 16 | */ 17 | object CartesianProductGenerator { 18 | 19 | private inline fun build(sizes: IntArray, 20 | repeat: Int, 21 | crossinline transform: (IntArray) -> R): CombinatorialSequence { 22 | val totalSize = sizes.fold(BigInteger.ONE) { acc, size -> acc * size.toBigInteger() }.pow(repeat) 23 | 24 | val iterator = object : Iterator { 25 | val dimensions = sizes * repeat 26 | val indices = IntArray(dimensions.size) 27 | val lastIndex = indices.lastIndex 28 | var hasNext = true 29 | 30 | override fun hasNext(): Boolean = hasNext 31 | 32 | override fun next(): R { 33 | if (!hasNext()) throw NoSuchElementException() 34 | val nextValue = transform(indices) 35 | for (i in lastIndex downTo 0) { 36 | if (indices[i] >= dimensions[i] - 1) { 37 | indices[i] = 0 38 | } else { 39 | indices[i]++ 40 | return nextValue 41 | } 42 | } 43 | hasNext = false 44 | return nextValue 45 | } 46 | } 47 | 48 | return CombinatorialSequence(totalSize, iterator) 49 | } 50 | 51 | /** 52 | * Returns a sequence of cartesian product of [dimensions]. 53 | * 54 | * To compute the cartesian product of [dimensions] with itself, 55 | * specify the number of repetitions with the [repeat] named argument. 56 | * 57 | * @throws IllegalArgumentException if [repeat] is negative, or [dimensions] contains negative. 58 | */ 59 | @JvmStatic 60 | fun indices(vararg dimensions: Int, repeat: Int = 1): CombinatorialSequence { 61 | require(repeat >= 0) { "repeat must be non-negative, was $repeat" } 62 | 63 | if (repeat == 0) { 64 | return CombinatorialSequence(BigInteger.ONE, sequenceOf(intArrayOf())) 65 | } 66 | 67 | dimensions.forEach { 68 | require(it >= 0) { "dimensions must not contain negative, was ${dimensions.contentToString()}" } 69 | if (it == 0) { 70 | return CombinatorialSequence(BigInteger.ZERO, emptySequence()) 71 | } 72 | } 73 | 74 | return build(dimensions.copyOf(), repeat) { it.copyOf() } 75 | } 76 | 77 | /** 78 | * Returns a sequence of cartesian product of the elements of [iterables]. 79 | * 80 | * To compute the cartesian product of [iterables] with itself, 81 | * specify the number of repetitions with the [repeat] named argument. 82 | * 83 | * @throws IllegalArgumentException if [repeat] is negative. 84 | */ 85 | @JvmStatic 86 | fun generate(vararg iterables: Iterable, repeat: Int = 1): CombinatorialSequence> { 87 | require(repeat >= 0) { "repeat must be non-negative, was $repeat" } 88 | 89 | if (repeat == 0) { 90 | return CombinatorialSequence(BigInteger.ONE, sequenceOf(emptyList())) 91 | } 92 | 93 | val sizes = mutableListOf() 94 | val pools = iterables.mapToArray { iterable -> 95 | iterable.toList().also { 96 | if (it.isEmpty()) { 97 | return CombinatorialSequence(BigInteger.ZERO, emptySequence()) 98 | } 99 | sizes += it.size 100 | } 101 | } 102 | 103 | return if (repeat == 1) { 104 | build(sizes.toIntArray(), repeat) { indices -> 105 | indices.mapIndexed { index, it -> pools[index][it] } 106 | } 107 | } else { 108 | build(sizes.toIntArray(), repeat) { indices -> 109 | var index = 0 110 | indices.map { 111 | pools[index++][it].also { 112 | if (index >= pools.size) { 113 | index = 0 114 | } 115 | } 116 | } 117 | } 118 | } 119 | } 120 | 121 | /** 122 | * Returns a sequence of cartesian product of the elements of [arrays]. 123 | * 124 | * To compute the cartesian product of [arrays] with itself, 125 | * specify the number of repetitions with the [repeat] named argument. 126 | * 127 | * @throws IllegalArgumentException if [repeat] is negative. 128 | */ 129 | inline fun generate(vararg arrays: Array, repeat: Int = 1): CombinatorialSequence> { 130 | require(repeat >= 0) { "repeat must be non-negative, was $repeat" } 131 | 132 | if (repeat == 0) { 133 | return CombinatorialSequence(BigInteger.ONE, sequenceOf(emptyArray())) 134 | } 135 | 136 | var total = BigInteger.ONE 137 | val pools = arrays.map { 138 | if (it.isEmpty()) { 139 | return CombinatorialSequence(BigInteger.ZERO, emptySequence()) 140 | } 141 | total *= it.size.toBigInteger() 142 | it.copyOf().asSequence() 143 | } * repeat 144 | total = total.pow(repeat) 145 | 146 | var sequence = sequenceOf(emptyArray()) 147 | pools.forEach { pool -> 148 | sequence = sequence.flatMap { a -> pool.map { b -> a + b } } 149 | } 150 | 151 | return CombinatorialSequence(total, sequence) 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/shiguruikai/combinatoricskt/CombinationsGenerator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 shiguruikai 3 | * 4 | * Licensed under the MIT license 5 | * https://github.com/shiguruikai/combinatoricskt/blob/master/LICENSE 6 | */ 7 | 8 | package com.github.shiguruikai.combinatoricskt 9 | 10 | import com.github.shiguruikai.combinatoricskt.internal.combinations 11 | import com.github.shiguruikai.combinatoricskt.internal.mapToArray 12 | import java.math.BigInteger 13 | 14 | /** 15 | * The class [CombinationsGenerator] contains methods for generating combinations. 16 | */ 17 | object CombinationsGenerator { 18 | 19 | @PublishedApi 20 | internal inline fun build(n: Int, r: Int, 21 | crossinline transform: (IntArray) -> R): CombinatorialSequence { 22 | val totalSize = combinations(n, r) 23 | 24 | val iterator = object : Iterator { 25 | val indices = IntArray(r) { it } 26 | var hasNext = true 27 | 28 | override fun hasNext(): Boolean = hasNext 29 | 30 | override fun next(): R { 31 | if (!hasNext()) throw NoSuchElementException() 32 | val nextValue = transform(indices) 33 | for (i in r - 1 downTo 0) { 34 | if (indices[i] != i + n - r) { 35 | var v = indices[i] 36 | for (j in i until r) { 37 | indices[j] = ++v 38 | } 39 | return nextValue 40 | } 41 | } 42 | hasNext = false 43 | return nextValue 44 | } 45 | } 46 | 47 | return CombinatorialSequence(totalSize, iterator) 48 | } 49 | 50 | /** 51 | * Returns a sequence of [r] number of combinations of [n] elements. 52 | * 53 | * @throws IllegalArgumentException if [r] is negative. 54 | */ 55 | @JvmStatic 56 | fun indices(n: Int, r: Int): CombinatorialSequence { 57 | require(r >= 0) { "r must be non-negative, was $r" } 58 | 59 | if (r == 0) { 60 | return CombinatorialSequence(BigInteger.ONE, sequenceOf(intArrayOf())) 61 | } else if (r > n) { 62 | return CombinatorialSequence(BigInteger.ZERO, emptySequence()) 63 | } 64 | 65 | return build(n, r) { it.copyOf() } 66 | } 67 | 68 | /** 69 | * Returns a sequence of combinations of [length] of the elements of [iterable]. 70 | * 71 | * @throws IllegalArgumentException if [length] is negative. 72 | */ 73 | @JvmStatic 74 | fun generate(iterable: Iterable, length: Int): CombinatorialSequence> { 75 | require(length >= 0) { "length must be non-negative, was $length" } 76 | 77 | if (length == 0) { 78 | return CombinatorialSequence(BigInteger.ONE, sequenceOf(emptyList())) 79 | } 80 | 81 | val pool = iterable.toList() 82 | val n = pool.size 83 | 84 | if (length > n) { 85 | return CombinatorialSequence(BigInteger.ZERO, emptySequence()) 86 | } 87 | 88 | return build(n, length) { ints -> ints.map { pool[it] } } 89 | } 90 | 91 | /** 92 | * Returns a sequence of combinations of [length] of the elements of [array]. 93 | * 94 | * @throws IllegalArgumentException if [length] is negative. 95 | */ 96 | inline fun generate(array: Array, length: Int): CombinatorialSequence> { 97 | require(length >= 0) { "length must be non-negative, was $length" } 98 | 99 | val n = array.size 100 | 101 | if (length == 0) { 102 | return CombinatorialSequence(BigInteger.ONE, sequenceOf(emptyArray())) 103 | } else if (length > n) { 104 | return CombinatorialSequence(BigInteger.ZERO, emptySequence()) 105 | } 106 | 107 | val pool = array.copyOf() 108 | 109 | return build(n, length) { ints -> ints.mapToArray { pool[it] } } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/shiguruikai/combinatoricskt/CombinationsWithRepetitionGenerator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 shiguruikai 3 | * 4 | * Licensed under the MIT license 5 | * https://github.com/shiguruikai/combinatoricskt/blob/master/LICENSE 6 | */ 7 | 8 | package com.github.shiguruikai.combinatoricskt 9 | 10 | import com.github.shiguruikai.combinatoricskt.internal.combinationsWithRepetition 11 | import com.github.shiguruikai.combinatoricskt.internal.mapToArray 12 | import java.math.BigInteger 13 | 14 | /** 15 | * The class [CombinationsWithRepetitionGenerator] contains methods for generating combinations with repetition. 16 | */ 17 | object CombinationsWithRepetitionGenerator { 18 | 19 | @PublishedApi 20 | internal inline fun build(n: Int, r: Int, 21 | crossinline transform: (IntArray) -> R): CombinatorialSequence { 22 | val totalSize = combinationsWithRepetition(n, r) 23 | 24 | val iterator = object : Iterator { 25 | val indices = IntArray(r) 26 | var hasNext = true 27 | 28 | override fun hasNext(): Boolean = hasNext 29 | 30 | override fun next(): R { 31 | if (!hasNext()) throw NoSuchElementException() 32 | val nextValue = transform(indices) 33 | for (i in r - 1 downTo 0) { 34 | if (indices[i] != n - 1) { 35 | val v = indices[i] + 1 36 | for (j in i until r) { 37 | indices[j] = v 38 | } 39 | return nextValue 40 | } 41 | } 42 | hasNext = false 43 | return nextValue 44 | } 45 | } 46 | 47 | return CombinatorialSequence(totalSize, iterator) 48 | } 49 | 50 | /** 51 | * Returns a sequence of [r] number of combinations with repetition of [n] elements. 52 | * 53 | * @throws IllegalArgumentException if [r] is negative. 54 | */ 55 | @JvmStatic 56 | fun indices(n: Int, r: Int): CombinatorialSequence { 57 | require(r >= 0) { "r must be non-negative, was $r" } 58 | 59 | if (r == 0) { 60 | return CombinatorialSequence(BigInteger.ONE, sequenceOf(intArrayOf())) 61 | } else if (n < 1) { 62 | return CombinatorialSequence(BigInteger.ZERO, emptySequence()) 63 | } 64 | 65 | return build(n, r) { it.copyOf() } 66 | } 67 | 68 | /** 69 | * Returns a sequence of combinations with repetition of [length] of the elements of [iterable]. 70 | * 71 | * @throws IllegalArgumentException if [length] is negative. 72 | */ 73 | @JvmStatic 74 | fun generate(iterable: Iterable, length: Int): CombinatorialSequence> { 75 | require(length >= 0) { "length must be non-negative, was $length" } 76 | 77 | if (length == 0) { 78 | return CombinatorialSequence(BigInteger.ONE, sequenceOf(emptyList())) 79 | } 80 | 81 | val pool = iterable.toList() 82 | val n = pool.size 83 | 84 | if (n < 1) { 85 | return CombinatorialSequence(BigInteger.ZERO, emptySequence()) 86 | } 87 | 88 | return build(n, length) { ints -> ints.map { pool[it] } } 89 | } 90 | 91 | /** 92 | * Returns a sequence of combinations with repetition of [length] of the elements of [array]. 93 | * 94 | * @throws IllegalArgumentException if [length] is negative. 95 | */ 96 | inline fun generate(array: Array, length: Int): CombinatorialSequence> { 97 | require(length >= 0) { "length must be non-negative, was $length" } 98 | 99 | val n = array.size 100 | 101 | if (length == 0) { 102 | return CombinatorialSequence(BigInteger.ONE, sequenceOf(emptyArray())) 103 | } else if (n < 1) { 104 | return CombinatorialSequence(BigInteger.ZERO, emptySequence()) 105 | } 106 | 107 | val pool = array.copyOf() 108 | 109 | return build(n, length) { ints -> ints.mapToArray { pool[it] } } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/shiguruikai/combinatoricskt/CombinatorialSequence.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 shiguruikai 3 | * 4 | * Licensed under the MIT license 5 | * https://github.com/shiguruikai/combinatoricskt/blob/master/LICENSE 6 | */ 7 | 8 | package com.github.shiguruikai.combinatoricskt 9 | 10 | import java.math.BigInteger 11 | import java.util.LinkedList 12 | import java.util.Spliterator 13 | import java.util.Spliterators 14 | import java.util.stream.Stream 15 | import java.util.stream.StreamSupport 16 | 17 | /** 18 | * This [CombinatorialSequence] class has the [totalSize] property of the total number of sequences. 19 | * This sequence can be iterated only once. 20 | * 21 | * @property totalSize Total number of elements of this sequence. 22 | * 23 | * @constructor Construct from sequence. 24 | * 25 | * @see kotlin.sequences.Sequence 26 | * @see kotlin.sequences.constrainOnce 27 | */ 28 | class CombinatorialSequence( 29 | val totalSize: BigInteger, 30 | sequence: Sequence 31 | ) : Sequence by sequence.constrainOnce() { 32 | 33 | /** 34 | * Construct from iterator. 35 | */ 36 | constructor(totalSize: BigInteger, iterator: Iterator) : this(totalSize, iterator.asSequence()) 37 | 38 | /** 39 | * Returns a [List] containing all elements. 40 | * 41 | * The operation is _terminal_. 42 | */ 43 | fun toList(): List = toMutableList() 44 | 45 | /** 46 | * Returns a [MutableList] containing all elements. 47 | * 48 | * The operation is _terminal_. 49 | */ 50 | fun toMutableList(): MutableList = if (totalSize <= MAX_ARRAY_SIZE) { 51 | toCollection(ArrayList(totalSize.intValueExact())) 52 | } else { 53 | toCollection(LinkedList()) 54 | } 55 | 56 | /** 57 | * @see [kotlin.streams.asStream] 58 | */ 59 | fun asStream(): Stream = if (totalSize <= Long.MAX_VALUE.toBigInteger()) { 60 | StreamSupport.stream(Spliterators.spliterator( 61 | iterator(), totalSize.longValueExact(), Spliterator.ORDERED), false) 62 | } else { 63 | StreamSupport.stream(Spliterators.spliteratorUnknownSize( 64 | iterator(), Spliterator.ORDERED), false) 65 | } 66 | 67 | companion object { 68 | 69 | /** 70 | * The value is 2147483639. 71 | * 72 | * @see java.util.ArrayList.MAX_ARRAY_SIZE 73 | */ 74 | private val MAX_ARRAY_SIZE = (Int.MAX_VALUE - 8).toBigInteger() 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/shiguruikai/combinatoricskt/Combinatorics.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 shiguruikai 3 | * 4 | * Licensed under the MIT license 5 | * https://github.com/shiguruikai/combinatoricskt/blob/master/LICENSE 6 | */ 7 | 8 | @file:JvmName("Combinatorics") 9 | 10 | package com.github.shiguruikai.combinatoricskt 11 | 12 | /** 13 | * The class [Combinatorics] contains methods for generating combinatorial sequence. 14 | */ 15 | object Combinatorics { 16 | 17 | /** 18 | * Returns a sequence of permutations of [length] of the elements of [iterable]. 19 | * 20 | * If [length] is not specified or is null, the default for [length] is the length of [iterable]. 21 | * 22 | * @throws IllegalArgumentException if [length] is negative. 23 | */ 24 | @JvmStatic 25 | @JvmOverloads 26 | fun permutations(iterable: Iterable, length: Int? = null): CombinatorialSequence> = 27 | PermutationsGenerator.generate(iterable, length) 28 | 29 | /** 30 | * Returns a sequence of permutations with repetition of [length] of the elements of [iterable]. 31 | * 32 | * @throws IllegalArgumentException if [length] is negative. 33 | */ 34 | @JvmStatic 35 | fun permutationsWithRepetition(iterable: Iterable, length: Int): CombinatorialSequence> = 36 | PermutationsWithRepetitionGenerator.generate(iterable, length) 37 | 38 | /** 39 | * Returns a sequence of derangement of the elements of [iterable]. 40 | */ 41 | @JvmStatic 42 | fun derangements(iterable: Iterable): CombinatorialSequence> = 43 | DerangementGenerator.generate(iterable) 44 | 45 | /** 46 | * Returns a sequence of combinations of [length] of the elements of [iterable]. 47 | * 48 | * @throws IllegalArgumentException if [length] is negative. 49 | */ 50 | @JvmStatic 51 | fun combinations(iterable: Iterable, length: Int): CombinatorialSequence> = 52 | CombinationsGenerator.generate(iterable, length) 53 | 54 | /** 55 | * Returns a sequence of combinations with repetition of [length] of the elements of [iterable]. 56 | * 57 | * @throws IllegalArgumentException if [length] is negative. 58 | */ 59 | @JvmStatic 60 | fun combinationsWithRepetition(iterable: Iterable, length: Int): CombinatorialSequence> = 61 | CombinationsWithRepetitionGenerator.generate(iterable, length) 62 | 63 | /** 64 | * Returns a sequence of cartesian product of the elements of [iterables]. 65 | * 66 | * To compute the cartesian product of [iterables] with itself, 67 | * specify the number of repetitions with the [repeat] named argument. 68 | * 69 | * @throws IllegalArgumentException if [repeat] is negative. 70 | */ 71 | @SafeVarargs 72 | @JvmStatic 73 | @JvmOverloads 74 | fun cartesianProduct(vararg iterables: Iterable, repeat: Int = 1): CombinatorialSequence> = 75 | CartesianProductGenerator.generate(*iterables, repeat = repeat) 76 | 77 | /** 78 | * Returns a sequence of power set of the elements of [iterable]. 79 | */ 80 | @JvmStatic 81 | fun powerset(iterable: Iterable): CombinatorialSequence> = 82 | PowerSetGenerator.generate(iterable) 83 | 84 | /** 85 | * Returns a sequence of permutations of [length] of the elements of [array]. 86 | * 87 | * If [length] is not specified or is null, the default for [length] is the length of [array]. 88 | * 89 | * @throws IllegalArgumentException if [length] is negative. 90 | */ 91 | inline fun permutations(array: Array, length: Int? = null): CombinatorialSequence> = 92 | PermutationsGenerator.generate(array, length) 93 | 94 | /** 95 | * Returns a sequence of permutations with repetition of [length] of the elements of [array]. 96 | * 97 | * @throws IllegalArgumentException if [length] is negative. 98 | */ 99 | inline fun permutationsWithRepetition(array: Array, length: Int): CombinatorialSequence> = 100 | PermutationsWithRepetitionGenerator.generate(array, length = length) 101 | 102 | /** 103 | * Returns a sequence of derangement of the elements of [array]. 104 | */ 105 | inline fun derangements(array: Array): CombinatorialSequence> = 106 | DerangementGenerator.generate(array) 107 | 108 | /** 109 | * Returns a sequence of combinations of [length] of the elements of [array]. 110 | * 111 | * @throws IllegalArgumentException if [length] is negative. 112 | */ 113 | inline fun combinations(array: Array, length: Int): CombinatorialSequence> = 114 | CombinationsGenerator.generate(array, length) 115 | 116 | /** 117 | * Returns a sequence of combinations with repetition of [length] of the elements of [array]. 118 | * 119 | * @throws IllegalArgumentException if [length] is negative. 120 | */ 121 | inline fun combinationsWithRepetition(array: Array, length: Int): CombinatorialSequence> = 122 | CombinationsWithRepetitionGenerator.generate(array, length) 123 | 124 | /** 125 | * Returns a sequence of cartesian product of the elements of [arrays]. 126 | * 127 | * To compute the cartesian product of [arrays] with itself, 128 | * specify the number of repetitions with the [repeat] named argument. 129 | * 130 | * @throws IllegalArgumentException if [repeat] is negative. 131 | */ 132 | inline fun cartesianProduct(vararg arrays: Array, repeat: Int = 1): CombinatorialSequence> = 133 | CartesianProductGenerator.generate(*arrays, repeat = repeat) 134 | 135 | /** 136 | * Returns a sequence of power set of the elements of [array]. 137 | */ 138 | inline fun powerset(array: Array): CombinatorialSequence> = 139 | PowerSetGenerator.generate(array) 140 | } 141 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/shiguruikai/combinatoricskt/DerangementGenerator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 shiguruikai 3 | * 4 | * Licensed under the MIT license 5 | * https://github.com/shiguruikai/combinatoricskt/blob/master/LICENSE 6 | */ 7 | 8 | package com.github.shiguruikai.combinatoricskt 9 | 10 | import com.github.shiguruikai.combinatoricskt.internal.mapToArray 11 | import com.github.shiguruikai.combinatoricskt.internal.subFactorial 12 | import com.github.shiguruikai.combinatoricskt.internal.swap 13 | import java.math.BigInteger 14 | 15 | /** 16 | * The class [DerangementGenerator] contains methods for generating derangement. 17 | */ 18 | object DerangementGenerator { 19 | 20 | @PublishedApi 21 | internal inline fun build(n: Int, 22 | crossinline transform: (IntArray) -> R): CombinatorialSequence { 23 | val totalSize = subFactorial(n) 24 | 25 | val iterator = object : Iterator { 26 | val indices = IntArray(n) { it } 27 | val lastIndex = n - 1 28 | var hasNext = true 29 | 30 | fun nextPerm() { 31 | var i = lastIndex 32 | while (i > 0 && indices[i - 1] >= indices[i]) { 33 | i-- 34 | } 35 | if (i <= 0) { 36 | hasNext = false 37 | return 38 | } 39 | var j = lastIndex 40 | while (indices[j] <= indices[i - 1]) { 41 | j-- 42 | } 43 | indices.swap(i - 1, j) 44 | j = lastIndex 45 | while (i < j) { 46 | indices.swap(i, j) 47 | i++ 48 | j-- 49 | } 50 | } 51 | 52 | override fun hasNext(): Boolean { 53 | while (hasNext) { 54 | var i = 0 55 | if (indices.all { it != i++ }) { 56 | break 57 | } 58 | nextPerm() 59 | } 60 | return hasNext 61 | } 62 | 63 | override fun next(): R { 64 | if (!hasNext()) throw NoSuchElementException() 65 | val nextValue = transform(indices) 66 | nextPerm() 67 | return nextValue 68 | } 69 | } 70 | 71 | return CombinatorialSequence(totalSize, iterator) 72 | } 73 | 74 | /** 75 | * Returns a sequence of derangement of [n] elements. 76 | * 77 | * @throws IllegalArgumentException if [n] is negative. 78 | */ 79 | @JvmStatic 80 | fun indices(n: Int): CombinatorialSequence { 81 | require(n >= 0) { "n must be non-negative, was $n" } 82 | 83 | if (n == 0) { 84 | return CombinatorialSequence(BigInteger.ONE, sequenceOf(intArrayOf())) 85 | } else if (n == 1) { 86 | return CombinatorialSequence(BigInteger.ZERO, sequenceOf()) 87 | } 88 | 89 | return build(n) { it.copyOf() } 90 | } 91 | 92 | /** 93 | * Returns a sequence of derangement of the elements of [iterable]. 94 | */ 95 | @JvmStatic 96 | fun generate(iterable: Iterable): CombinatorialSequence> { 97 | val pool = iterable.toList() 98 | val n = pool.size 99 | 100 | if (n == 0) { 101 | return CombinatorialSequence(BigInteger.ONE, sequenceOf(emptyList())) 102 | } else if (n == 1) { 103 | return CombinatorialSequence(BigInteger.ZERO, sequenceOf()) 104 | } 105 | 106 | return build(n) { ints -> ints.map { pool[it] } } 107 | } 108 | 109 | /** 110 | * Returns a sequence of derangement of the elements of [array]. 111 | */ 112 | inline fun generate(array: Array): CombinatorialSequence> { 113 | val n = array.size 114 | 115 | if (n == 0) { 116 | return CombinatorialSequence(BigInteger.ONE, sequenceOf(emptyArray())) 117 | } else if (n == 1) { 118 | return CombinatorialSequence(BigInteger.ZERO, sequenceOf()) 119 | } 120 | 121 | val pool = array.copyOf() 122 | 123 | return build(n) { ints -> ints.mapToArray { pool[it] } } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/shiguruikai/combinatoricskt/PermutationsGenerator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 shiguruikai 3 | * 4 | * Licensed under the MIT license 5 | * https://github.com/shiguruikai/combinatoricskt/blob/master/LICENSE 6 | */ 7 | 8 | package com.github.shiguruikai.combinatoricskt 9 | 10 | import com.github.shiguruikai.combinatoricskt.internal.mapToArray 11 | import com.github.shiguruikai.combinatoricskt.internal.mapToList 12 | import com.github.shiguruikai.combinatoricskt.internal.permutations 13 | import com.github.shiguruikai.combinatoricskt.internal.swap 14 | import java.math.BigInteger 15 | 16 | /** 17 | * The class [PermutationsGenerator] contains methods for generating permutations. 18 | */ 19 | object PermutationsGenerator { 20 | 21 | @PublishedApi 22 | internal inline fun build(n: Int, r: Int, 23 | crossinline transform: (IntArray) -> R): CombinatorialSequence { 24 | val totalSize = permutations(n, r) 25 | 26 | val iterator = if (n == r) { 27 | object : Iterator { 28 | val indices = IntArray(n) { it } 29 | val lastIndex = n - 1 30 | var hasNext = true 31 | 32 | override fun hasNext(): Boolean = hasNext 33 | 34 | override fun next(): R { 35 | if (!hasNext()) throw NoSuchElementException() 36 | val nextValue = transform(indices) 37 | var i = lastIndex 38 | while (i > 0 && indices[i - 1] >= indices[i]) { 39 | i-- 40 | } 41 | if (i <= 0) { 42 | hasNext = false 43 | return nextValue 44 | } 45 | var j = lastIndex 46 | while (indices[j] <= indices[i - 1]) { 47 | j-- 48 | } 49 | indices.swap(i - 1, j) 50 | j = lastIndex 51 | while (i < j) { 52 | indices.swap(i, j) 53 | i++ 54 | j-- 55 | } 56 | return nextValue 57 | } 58 | } 59 | } else { 60 | object : Iterator { 61 | val indices = IntArray(n) { it } 62 | val cycles = IntArray(r) { n - it } 63 | var hasNext = true 64 | 65 | override fun hasNext(): Boolean = hasNext 66 | 67 | override fun next(): R { 68 | if (!hasNext()) throw NoSuchElementException() 69 | val nextValue = transform(indices) 70 | for (i in r - 1 downTo 0) { 71 | cycles[i]-- 72 | if (cycles[i] == 0) { 73 | val first = indices[i] 74 | for (j in i until n - 1) { 75 | indices[j] = indices[j + 1] 76 | } 77 | indices[n - 1] = first 78 | cycles[i] = n - i 79 | } else { 80 | indices.swap(i, n - cycles[i]) 81 | return nextValue 82 | } 83 | } 84 | hasNext = false 85 | return nextValue 86 | } 87 | } 88 | } 89 | 90 | return CombinatorialSequence(totalSize, iterator) 91 | } 92 | 93 | /** 94 | * Returns a sequence of [r] number of permutations [n] elements. 95 | * 96 | * If [r] is not specified or is null, the default for [r] is the length of [n]. 97 | * 98 | * @throws IllegalArgumentException if [r] is negative. 99 | */ 100 | @JvmStatic 101 | fun indices(n: Int, r: Int? = null): CombinatorialSequence { 102 | if (r != null) { 103 | require(r >= 0) { "r must be non-negative, was $r" } 104 | } 105 | 106 | val r1 = r ?: n 107 | 108 | if (r1 > n) { 109 | return CombinatorialSequence(BigInteger.ZERO, emptySequence()) 110 | } else if (r1 == 0) { 111 | return CombinatorialSequence(BigInteger.ONE, sequenceOf(intArrayOf())) 112 | } 113 | 114 | return build(n, r1) { it.copyOf(r1) } 115 | } 116 | 117 | /** 118 | * Returns a sequence of permutations of [length] of the elements of [iterable]. 119 | * 120 | * If [length] is not specified or is null, the default for [length] is the length of [iterable]. 121 | * 122 | * @throws IllegalArgumentException if [length] is negative. 123 | */ 124 | @JvmStatic 125 | fun generate(iterable: Iterable, length: Int? = null): CombinatorialSequence> { 126 | if (length != null) { 127 | require(length >= 0) { "length must be non-negative, was $length" } 128 | } 129 | 130 | val pool = iterable.toList() 131 | val n = pool.size 132 | val r = length ?: n 133 | 134 | if (r > n) { 135 | return CombinatorialSequence(BigInteger.ZERO, emptySequence()) 136 | } else if (r == 0) { 137 | return CombinatorialSequence(BigInteger.ONE, sequenceOf(emptyList())) 138 | } 139 | 140 | return build(n, r) { indices -> indices.mapToList(r) { pool[it] } } 141 | } 142 | 143 | /** 144 | * Returns a sequence of permutations of [length] of the elements of [array]. 145 | * 146 | * If [length] is not specified or is null, the default for [length] is the length of [array]. 147 | * 148 | * @throws IllegalArgumentException if [length] is negative. 149 | */ 150 | inline fun generate(array: Array, length: Int? = null): CombinatorialSequence> { 151 | if (length != null) { 152 | require(length >= 0) { "length must be non-negative, was $length" } 153 | } 154 | 155 | val n = array.size 156 | val r = length ?: n 157 | 158 | if (r > n) { 159 | return CombinatorialSequence(BigInteger.ZERO, emptySequence()) 160 | } else if (r == 0) { 161 | return CombinatorialSequence(BigInteger.ONE, sequenceOf(emptyArray())) 162 | } 163 | 164 | val pool = array.copyOf() 165 | 166 | return build(n, r) { indices -> indices.mapToArray(r) { pool[it] } } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/shiguruikai/combinatoricskt/PermutationsWithRepetitionGenerator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 shiguruikai 3 | * 4 | * Licensed under the MIT license 5 | * https://github.com/shiguruikai/combinatoricskt/blob/master/LICENSE 6 | */ 7 | 8 | package com.github.shiguruikai.combinatoricskt 9 | 10 | import com.github.shiguruikai.combinatoricskt.internal.mapToArray 11 | import java.math.BigInteger 12 | 13 | /** 14 | * The class [PermutationsWithRepetitionGenerator] contains methods for generating permutations with repetition. 15 | */ 16 | object PermutationsWithRepetitionGenerator { 17 | 18 | @PublishedApi 19 | internal inline fun build(n: Int, r: Int, 20 | crossinline transform: (IntArray) -> R): CombinatorialSequence { 21 | val totalSize = n.toBigInteger().pow(r) 22 | 23 | val iterator = if (totalSize <= Long.MAX_VALUE.toBigInteger()) { 24 | object : Iterator { 25 | val indices = IntArray(r) 26 | var t = totalSize.longValueExact() 27 | 28 | override fun hasNext(): Boolean = t > 0 29 | 30 | override fun next(): R { 31 | if (!hasNext()) throw NoSuchElementException() 32 | t-- 33 | val nextValue = transform(indices) 34 | for (i in r - 1 downTo 0) { 35 | if (indices[i] >= n - 1) { 36 | indices[i] = 0 37 | } else { 38 | indices[i]++ 39 | break 40 | } 41 | } 42 | return nextValue 43 | } 44 | } 45 | } else { 46 | object : Iterator { 47 | val indices = IntArray(r) 48 | var t = totalSize 49 | 50 | override fun hasNext(): Boolean = t > BigInteger.ZERO 51 | 52 | override fun next(): R { 53 | if (!hasNext()) throw NoSuchElementException() 54 | t-- 55 | val nextValue = transform(indices) 56 | for (i in r - 1 downTo 0) { 57 | if (indices[i] >= n - 1) { 58 | indices[i] = 0 59 | } else { 60 | indices[i]++ 61 | break 62 | } 63 | } 64 | return nextValue 65 | } 66 | } 67 | } 68 | 69 | return CombinatorialSequence(totalSize, iterator) 70 | } 71 | 72 | /** 73 | * Returns a sequence of [r] number of permutations with repetition of [n] elements. 74 | * 75 | * @throws IllegalArgumentException if [r] is negative. 76 | */ 77 | @JvmStatic 78 | fun indices(n: Int, r: Int): CombinatorialSequence { 79 | require(r >= 0) { "r must be non-negative, was $r" } 80 | 81 | if (r == 0) { 82 | return CombinatorialSequence(BigInteger.ONE, sequenceOf(intArrayOf())) 83 | } 84 | 85 | return build(n, r) { it.copyOf() } 86 | } 87 | 88 | /** 89 | * Returns a sequence of permutations with repetition of [length] of the elements of [iterable]. 90 | * 91 | * @throws IllegalArgumentException if [length] is negative. 92 | */ 93 | @JvmStatic 94 | fun generate(iterable: Iterable, length: Int): CombinatorialSequence> { 95 | require(length >= 0) { "length must be non-negative, was $length" } 96 | 97 | if (length == 0) { 98 | return CombinatorialSequence(BigInteger.ONE, sequenceOf(emptyList())) 99 | } 100 | 101 | val pool = iterable.toList() 102 | 103 | return build(pool.size, length) { ints -> ints.map { pool[it] } } 104 | } 105 | 106 | /** 107 | * Returns a sequence of permutations with repetition of [length] of the elements of [array]. 108 | * 109 | * @throws IllegalArgumentException if [length] is negative. 110 | */ 111 | inline fun generate(array: Array, length: Int): CombinatorialSequence> { 112 | require(length >= 0) { "length must be non-negative, was $length" } 113 | 114 | if (length == 0) { 115 | return CombinatorialSequence(BigInteger.ONE, sequenceOf(emptyArray())) 116 | } 117 | 118 | val pool = array.copyOf() 119 | 120 | return build(pool.size, length) { ints -> ints.mapToArray { pool[it] } } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/shiguruikai/combinatoricskt/PowerSetGenerator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 shiguruikai 3 | * 4 | * Licensed under the MIT license 5 | * https://github.com/shiguruikai/combinatoricskt/blob/master/LICENSE 6 | */ 7 | 8 | package com.github.shiguruikai.combinatoricskt 9 | 10 | import java.util.BitSet 11 | 12 | /** 13 | * The class [PowerSetGenerator] contains methods for generating power set. 14 | */ 15 | object PowerSetGenerator { 16 | 17 | @PublishedApi 18 | internal inline fun build( 19 | poolSize: Int, 20 | crossinline initial: (size: Int) -> T, 21 | crossinline transform: (index: Int, acc: T, poolIndex: Int) -> Unit 22 | ): CombinatorialSequence { 23 | val totalSize = 2.toBigInteger().pow(poolSize) 24 | 25 | val iterator = object : Iterator { 26 | val bitLength = totalSize.bitLength() 27 | val bitSet = BitSet(bitLength) 28 | val lastIndex = bitLength - 1 29 | var hasNext = true 30 | 31 | override fun hasNext(): Boolean = hasNext 32 | 33 | override fun next(): R { 34 | if (!hasNext()) throw NoSuchElementException() 35 | val acc = initial(bitSet.cardinality()) 36 | var i = bitSet.nextSetBit(0) 37 | var index = 0 38 | while (i != -1) { 39 | transform(index++, acc, i) 40 | i = bitSet.nextSetBit(i + 1) 41 | } 42 | 43 | i = bitSet.nextClearBit(0) 44 | if (i >= lastIndex) { 45 | hasNext = false 46 | } else { 47 | bitSet.flip(0, i + 1) 48 | } 49 | 50 | return acc 51 | } 52 | } 53 | 54 | return CombinatorialSequence(totalSize, iterator) 55 | } 56 | 57 | /** 58 | * Returns a sequence of power set of [n] elements. 59 | * 60 | * @throws IllegalArgumentException if [n] is negative. 61 | */ 62 | @JvmStatic 63 | fun indices(n: Int): CombinatorialSequence { 64 | require(n >= 0) { "n must be non-negative, was $n" } 65 | 66 | return build(n, { IntArray(it) }, { index, acc, poolIndex -> acc[index] = poolIndex }) 67 | } 68 | 69 | /** 70 | * Returns a sequence of power set of the elements of [iterable]. 71 | */ 72 | @JvmStatic 73 | fun generate(iterable: Iterable): CombinatorialSequence> { 74 | val pool = iterable.toList() 75 | 76 | return build(pool.size, { ArrayList(it) }, { _, acc, poolIndex -> acc += pool[poolIndex] }) 77 | } 78 | 79 | /** 80 | * Returns a sequence of power set of the elements of [array]. 81 | */ 82 | inline fun generate(array: Array): CombinatorialSequence> { 83 | val pool = array.copyOf() 84 | 85 | @Suppress("UNCHECKED_CAST") 86 | return build(pool.size, { arrayOfNulls(it) }, { index, acc, poolIndex -> acc[index] = pool[poolIndex] }) 87 | as CombinatorialSequence> 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/shiguruikai/combinatoricskt/extensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 shiguruikai 3 | * 4 | * Licensed under the MIT license 5 | * https://github.com/shiguruikai/combinatoricskt/blob/master/LICENSE 6 | */ 7 | 8 | @file:Suppress("NOTHING_TO_INLINE") 9 | 10 | package com.github.shiguruikai.combinatoricskt 11 | 12 | inline fun Iterable.permutations(length: Int? = null) = Combinatorics.permutations(this, length) 13 | 14 | inline fun Iterable.permutationsWithRepetition(length: Int) = Combinatorics.permutationsWithRepetition(this, length) 15 | 16 | inline fun Iterable.derangements() = Combinatorics.derangements(this) 17 | 18 | inline fun Iterable.combinations(length: Int) = Combinatorics.combinations(this, length) 19 | 20 | inline fun Iterable.combinationsWithRepetition(length: Int) = Combinatorics.combinationsWithRepetition(this, length) 21 | 22 | inline fun Iterable.cartesianProduct(vararg others: Iterable, repeat: Int = 1) = Combinatorics.cartesianProduct(this, *others, repeat = repeat) 23 | 24 | inline fun Iterable.powerset() = Combinatorics.powerset(this) 25 | 26 | inline fun Array.permutations(length: Int? = null) = Combinatorics.permutations(this, length) 27 | 28 | inline fun Array.permutationsWithRepetition(length: Int) = Combinatorics.permutationsWithRepetition(this, length) 29 | 30 | inline fun Array.derangements() = Combinatorics.derangements(this) 31 | 32 | inline fun Array.combinations(length: Int) = Combinatorics.combinations(this, length) 33 | 34 | inline fun Array.combinationsWithRepetition(length: Int) = Combinatorics.combinationsWithRepetition(this, length) 35 | 36 | inline fun Array.cartesianProduct(vararg others: Array, repeat: Int = 1) = Combinatorics.cartesianProduct(this, *others, repeat = repeat) 37 | 38 | inline fun Array.powerset() = Combinatorics.powerset(this) 39 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/shiguruikai/combinatoricskt/internal/Extensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 shiguruikai 3 | * 4 | * Licensed under the MIT license 5 | * https://github.com/shiguruikai/combinatoricskt/blob/master/LICENSE 6 | */ 7 | 8 | package com.github.shiguruikai.combinatoricskt.internal 9 | 10 | @PublishedApi 11 | internal operator fun List.times(n: Int): List { 12 | if (n <= 0) return emptyList() 13 | if (n == 1) return this 14 | 15 | val newSize = Math.multiplyExact(size, n) 16 | val result = ArrayList(newSize) 17 | repeat(n) { 18 | result.addAll(this) 19 | } 20 | return result 21 | } 22 | 23 | @PublishedApi 24 | internal operator fun IntArray.times(n: Int): IntArray { 25 | if (n <= 0) return intArrayOf() 26 | if (n == 1) return this 27 | 28 | val newSize = Math.multiplyExact(size, n) 29 | val result = IntArray(newSize) 30 | repeat(n) { 31 | System.arraycopy(this, 0, result, size * it, size) 32 | } 33 | return result 34 | } 35 | 36 | @PublishedApi 37 | internal inline fun IntArray.mapToList(newSize: Int = size, transform: (Int) -> R): List { 38 | val result = ArrayList(newSize) 39 | repeat(newSize) { 40 | result += transform(this[it]) 41 | } 42 | return result 43 | } 44 | 45 | @PublishedApi 46 | internal inline fun IntArray.mapToArray(newSize: Int = size, transform: (Int) -> R): Array { 47 | val result = arrayOfNulls(newSize) 48 | repeat(newSize) { 49 | result[it] = transform(this[it]) 50 | } 51 | @Suppress("UNCHECKED_CAST") 52 | return result as Array 53 | } 54 | 55 | @PublishedApi 56 | internal inline fun Array.mapToArray(newSize: Int = size, transform: (T) -> R): Array { 57 | val result = arrayOfNulls(newSize) 58 | repeat(newSize) { 59 | result[it] = transform(this[it]) 60 | } 61 | @Suppress("UNCHECKED_CAST") 62 | return result as Array 63 | } 64 | 65 | @Suppress("NOTHING_TO_INLINE") 66 | @PublishedApi 67 | internal inline fun IntArray.swap(i: Int, j: Int) { 68 | this[i] = this[j].also { this[j] = this[i] } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/shiguruikai/combinatoricskt/internal/Math.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 shiguruikai 3 | * 4 | * Licensed under the MIT license 5 | * https://github.com/shiguruikai/combinatoricskt/blob/master/LICENSE 6 | */ 7 | 8 | @file:JvmName("Math") 9 | 10 | package com.github.shiguruikai.combinatoricskt.internal 11 | 12 | import java.math.BigInteger 13 | import kotlin.math.max 14 | 15 | @PublishedApi 16 | internal fun factorial(n: Int): BigInteger { 17 | require(n >= 0) { "n must be non-negative, was $n" } 18 | return factorialHelper(n) 19 | } 20 | 21 | @PublishedApi 22 | internal fun permutations(n: Int, r: Int): BigInteger { 23 | require(n >= 0 && r >= 0 && n >= r) 24 | return permutationsHelper(n, r) 25 | } 26 | 27 | @PublishedApi 28 | internal fun combinations(n: Int, r: Int): BigInteger { 29 | require(n >= 0 && r >= 0 && n >= r) 30 | return permutationsHelper(n, r) / factorialHelper(r) 31 | } 32 | 33 | @PublishedApi 34 | internal fun combinationsWithRepetition(n: Int, r: Int): BigInteger { 35 | require(n >= 1 && r >= 0) 36 | return factorialHelper(n + r - 1) / factorialHelper(r) / factorialHelper(n - 1) 37 | } 38 | 39 | @PublishedApi 40 | internal fun subFactorial(n: Int): BigInteger { 41 | require(n >= 0) { "n must be non-negative, was $n" } 42 | if (n == 0) return BigInteger.ONE 43 | var prev = BigInteger.ONE 44 | var acc = BigInteger.ZERO 45 | for (i in 2..n) { 46 | val next = (i - 1).toBigInteger() * (acc + prev) 47 | prev = acc 48 | acc = next 49 | } 50 | return acc 51 | } 52 | 53 | private fun factorialHelper(n: Int): BigInteger { 54 | var acc = BigInteger.ONE 55 | for (i in 2..n) { 56 | acc *= i.toBigInteger() 57 | } 58 | return acc 59 | } 60 | 61 | private fun permutationsHelper(n: Int, r: Int): BigInteger { 62 | var acc = BigInteger.ONE 63 | for (i in max(2, n - r + 1)..n) { 64 | acc *= i.toBigInteger() 65 | } 66 | return acc 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/CombinatoricsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 shiguruikai 3 | * 4 | * Licensed under the MIT license 5 | * https://github.com/shiguruikai/combinatoricskt/blob/master/LICENSE 6 | */ 7 | 8 | import com.github.shiguruikai.combinatoricskt.Combinatorics; 9 | import org.junit.jupiter.api.Assertions; 10 | import org.junit.jupiter.api.Test; 11 | 12 | import java.util.Arrays; 13 | import java.util.List; 14 | import java.util.stream.Collectors; 15 | 16 | public class CombinatoricsTest { 17 | 18 | private List> asList(T[][] array) { 19 | return Arrays.stream(array).map(Arrays::asList).collect(Collectors.toList()); 20 | } 21 | 22 | @Test 23 | void permutations() { 24 | Assertions.assertEquals( 25 | asList(new Integer[][]{{1, 2, 3}, {1, 3, 2}, {2, 1, 3}, {2, 3, 1}, {3, 1, 2}, {3, 2, 1}}), 26 | Combinatorics.permutations(Arrays.asList(1, 2, 3)).toList() 27 | ); 28 | 29 | Assertions.assertEquals( 30 | asList(new Integer[][]{{1, 2}, {1, 3}, {2, 1}, {2, 3}, {3, 1}, {3, 2}}), 31 | Combinatorics.permutations(Arrays.asList(1, 2, 3), 2).toList() 32 | ); 33 | } 34 | 35 | @Test 36 | void permutationsWithRepetition() { 37 | Assertions.assertEquals( 38 | asList(new Integer[][]{{1}, {2}, {3}}), 39 | Combinatorics.permutationsWithRepetition(Arrays.asList(1, 2, 3), 1).toList() 40 | ); 41 | 42 | Assertions.assertEquals( 43 | asList(new Integer[][]{{1, 1}, {1, 2}, {1, 3}, {2, 1}, {2, 2}, {2, 3}, {3, 1}, {3, 2}, {3, 3}}), 44 | Combinatorics.permutationsWithRepetition(Arrays.asList(1, 2, 3), 2).toList() 45 | ); 46 | } 47 | 48 | @Test 49 | void derangements() { 50 | Assertions.assertEquals( 51 | asList(new Integer[][]{{2, 3, 1}, {3, 1, 2}}), 52 | Combinatorics.derangements(Arrays.asList(1, 2, 3)).toList() 53 | ); 54 | } 55 | 56 | @Test 57 | void combinations() { 58 | Assertions.assertEquals( 59 | asList(new Integer[][]{{1}, {2}, {3}}), 60 | Combinatorics.combinations(Arrays.asList(1, 2, 3), 1).toList() 61 | ); 62 | 63 | Assertions.assertEquals( 64 | asList(new Integer[][]{{1, 2}, {1, 3}, {2, 3}}), 65 | Combinatorics.combinations(Arrays.asList(1, 2, 3), 2).toList() 66 | ); 67 | 68 | Assertions.assertEquals( 69 | asList(new Integer[][]{{1, 2, 3}}), 70 | Combinatorics.combinations(Arrays.asList(1, 2, 3), 3).toList() 71 | ); 72 | } 73 | 74 | @Test 75 | void combinationsWithRepetition() { 76 | Assertions.assertEquals( 77 | asList(new Integer[][]{{1}, {2}, {3}}), 78 | Combinatorics.combinationsWithRepetition(Arrays.asList(1, 2, 3), 1).toList() 79 | ); 80 | 81 | Assertions.assertEquals( 82 | asList(new Integer[][]{{1, 1}, {1, 2}, {1, 3}, {2, 2}, {2, 3}, {3, 3}}), 83 | Combinatorics.combinationsWithRepetition(Arrays.asList(1, 2, 3), 2).toList() 84 | ); 85 | } 86 | 87 | @Test 88 | void cartesianProduct() { 89 | Assertions.assertEquals( 90 | asList(new Object[][]{{0, 'a'}, {0, 'b'}, {1, 'a'}, {1, 'b'}}), 91 | Combinatorics.cartesianProduct(Arrays.asList(0, 1), Arrays.asList('a', 'b')).toList() 92 | ); 93 | 94 | Assertions.assertEquals( 95 | asList(new Integer[][]{{1, 1}, {1, 2}, {1, 3}, {2, 1}, {2, 2}, {2, 3}, {3, 1}, {3, 2}, {3, 3}}), 96 | Combinatorics.cartesianProduct(new List[]{Arrays.asList(1, 2, 3)}, 2).toList() 97 | ); 98 | } 99 | 100 | @Test 101 | void powerset() { 102 | Assertions.assertEquals( 103 | asList(new Integer[][]{{}, {1}, {2}, {1, 2}, {3}, {1, 3}, {2, 3}, {1, 2, 3}}), 104 | Combinatorics.powerset(Arrays.asList(1, 2, 3)).toList() 105 | ); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/shiguruikai/combinatoricskt/CartesianProductGeneratorTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 shiguruikai 3 | * 4 | * Licensed under the MIT license 5 | * https://github.com/shiguruikai/combinatoricskt/blob/master/LICENSE 6 | */ 7 | 8 | package com.github.shiguruikai.combinatoricskt 9 | 10 | import com.github.shiguruikai.combinatoricskt.internal.times 11 | import org.junit.jupiter.api.Assertions.assertEquals 12 | import org.junit.jupiter.api.Test 13 | import java.math.BigInteger 14 | import kotlin.random.Random 15 | 16 | internal class CartesianProductGeneratorTest { 17 | 18 | private val emptyList = emptyList() 19 | private val emptyArray = emptyArray() 20 | private fun IntRange.toArray(): Array = toList().toTypedArray() 21 | 22 | @Test 23 | fun test_cartesianProduct_empty() { 24 | assertEquals("[]", emptyList.cartesianProduct().toList().toString()) 25 | assertEquals("[]", emptyArray.cartesianProduct().toList().toString()) 26 | } 27 | 28 | @Test 29 | fun test_cartesianProduct_size() { 30 | assertEquals(0, (0..3).cartesianProduct((0..4), emptyList).totalSize.intValueExact()) 31 | assertEquals(0, (0..3).cartesianProduct((0..4), emptyList).count()) 32 | assertEquals(60, (0..3).cartesianProduct((0..4), (0..2), repeat = 1).totalSize.intValueExact()) 33 | assertEquals(60, (0..3).cartesianProduct((0..4), (0..2), repeat = 1).count()) 34 | assertEquals(3600, (0..3).cartesianProduct((0..4), (0..2), repeat = 2).totalSize.intValueExact()) 35 | assertEquals(3600, (0..3).cartesianProduct((0..4), (0..2), repeat = 2).count()) 36 | 37 | assertEquals(0, (0..3).toArray().cartesianProduct((0..4).toArray(), arrayOf()).totalSize.intValueExact()) 38 | assertEquals(0, (0..3).toArray().cartesianProduct((0..4).toArray(), arrayOf()).count()) 39 | assertEquals(60, (0..3).toArray().cartesianProduct((0..4).toArray(), (0..2).toArray(), repeat = 1).totalSize.intValueExact()) 40 | assertEquals(60, (0..3).toArray().cartesianProduct((0..4).toArray(), (0..2).toArray(), repeat = 1).count()) 41 | assertEquals(3600, (0..3).toArray().cartesianProduct((0..4).toArray(), (0..2).toArray(), repeat = 2).totalSize.intValueExact()) 42 | assertEquals(3600, (0..3).toArray().cartesianProduct((0..4).toArray(), (0..2).toArray(), repeat = 2).count()) 43 | 44 | assertEquals(0, CartesianProductGenerator.indices(4, 5, 0).totalSize.intValueExact()) 45 | assertEquals(0, CartesianProductGenerator.indices(4, 5, 0).count()) 46 | assertEquals(60, CartesianProductGenerator.indices(4, 5, 3, repeat = 1).totalSize.intValueExact()) 47 | assertEquals(60, CartesianProductGenerator.indices(4, 5, 3, repeat = 1).count()) 48 | assertEquals(3600, CartesianProductGenerator.indices(4, 5, 3, repeat = 2).totalSize.intValueExact()) 49 | assertEquals(3600, CartesianProductGenerator.indices(4, 5, 3, repeat = 2).count()) 50 | } 51 | 52 | @Test 53 | fun test_cartesianProduct() { 54 | assertEquals( 55 | "[[0, a], [0, b], [1, a], [1, b], [2, a], [2, b]]", 56 | (0..2).cartesianProduct('a'..'b').toList().toString()) 57 | 58 | assertEquals( 59 | "[[0, a, x], [0, b, x], [1, a, x], [1, b, x]]", 60 | (0..1).cartesianProduct('a'..'b', listOf('x')).toList().toString()) 61 | 62 | assertEquals( 63 | "[[1, 4], [1, 5], [1, 6], [2, 4], [2, 5], [2, 6], [3, 4], [3, 5], [3, 6]]", 64 | CartesianProductGenerator.generate( 65 | arrayOf(1, 2, 3), arrayOf(4, 5, 6)).toList().map { it.contentToString() }.toString()) 66 | 67 | assertEquals( 68 | "[[1, 4], [1, 5], [1, 6], [2, 4], [2, 5], [2, 6], [3, 4], [3, 5], [3, 6]]", 69 | arrayOf(1, 2, 3).cartesianProduct(arrayOf(4, 5, 6)).toList().map { it.contentToString() }.toString()) 70 | 71 | assertEquals(480, (0..3).cartesianProduct((0..4), (0..5), (0..3)).count()) 72 | assertEquals(0, (0..3).cartesianProduct(emptyList, (0..4), (0..5), (0..3)).count()) 73 | 74 | assertEquals( 75 | (0..4).cartesianProduct((0..4)).toList(), 76 | (0..4).permutationsWithRepetition(2).toList()) 77 | assertEquals( 78 | (0..4).cartesianProduct((0..4), (0..4)).toList(), 79 | (0..4).permutationsWithRepetition(3).toList()) 80 | 81 | fun product1(vararg iterables: Iterable, repeat: Int = 1): Sequence> { 82 | val pools = iterables.map { it.toList() } * repeat 83 | val n = pools.size 84 | if (n == 0) { 85 | return sequenceOf(emptyList()) 86 | } 87 | if (pools.any { it.isEmpty() }) { 88 | return emptySequence() 89 | } 90 | val indices = IntArray(n) 91 | 92 | return sequence { 93 | loop@ while (true) { 94 | yield(indices.mapIndexed { index, it -> pools[index][it] }) 95 | 96 | for (i in n - 1 downTo 0) { 97 | indices[i]++ 98 | if (indices[i] == pools[i].size) { 99 | indices[i] = 0 100 | } else { 101 | continue@loop 102 | } 103 | } 104 | break 105 | } 106 | } 107 | } 108 | 109 | fun product2(vararg iterables: Iterable, repeat: Int = 1): Sequence> { 110 | if (repeat == 0) { 111 | return CombinatorialSequence(BigInteger.ONE, sequenceOf(emptyList())) 112 | } 113 | 114 | val pools = iterables.map { it.toList().asSequence() } * repeat 115 | 116 | var sequence = sequenceOf(emptyList()) 117 | pools.forEach { pool -> 118 | sequence = sequence.flatMap { a -> pool.map { b -> a + b } } 119 | } 120 | 121 | return sequence 122 | } 123 | 124 | val argTypes: Array> = arrayOf(emptyList, 'a'..'c', 0..0, ('a'..'d').toSet(), 0..3, listOf(8, 9)) 125 | val random = Random 126 | repeat(20) { 127 | val iterables = (0..2).map { argTypes[random.nextInt(argTypes.size)] }.toTypedArray() 128 | val arrays = iterables.map { it.toList().toTypedArray() }.toTypedArray() 129 | val repeat = random.nextInt(4) 130 | val expectedSize = iterables.fold(BigInteger.ONE) { acc, iter -> acc * iter.count().toBigInteger() }.pow(repeat) 131 | val list = CartesianProductGenerator.generate(*iterables, repeat = repeat).toList() 132 | 133 | assertEquals(expectedSize, list.size.toBigInteger()) 134 | assertEquals(list, product1(*iterables, repeat = repeat).toList()) 135 | assertEquals(list, product2(*iterables, repeat = repeat).toList()) 136 | assertEquals(list, 137 | CartesianProductGenerator.generate(*arrays, repeat = repeat).map { it.toList() }.toList()) 138 | 139 | val indices = CartesianProductGenerator.indices(*iterables.map { it.count() }.toIntArray(), repeat = repeat).toList() 140 | val repeated = iterables.map { it.toList() } * repeat 141 | assertEquals(list, indices.map { it.zip(repeated) { a, b -> b[a] } }) 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/shiguruikai/combinatoricskt/CombinationsGeneratorTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 shiguruikai 3 | * 4 | * Licensed under the MIT license 5 | * https://github.com/shiguruikai/combinatoricskt/blob/master/LICENSE 6 | */ 7 | 8 | package com.github.shiguruikai.combinatoricskt 9 | 10 | import com.github.shiguruikai.combinatoricskt.internal.combinations 11 | import org.junit.jupiter.api.Assertions.assertArrayEquals 12 | import org.junit.jupiter.api.Assertions.assertEquals 13 | import org.junit.jupiter.api.Assertions.assertIterableEquals 14 | import org.junit.jupiter.api.Assertions.assertTrue 15 | import org.junit.jupiter.api.Test 16 | import org.junit.jupiter.api.assertThrows 17 | 18 | internal class CombinationsGeneratorTest { 19 | 20 | private val emptyList = emptyList() 21 | private val emptyArray = emptyArray() 22 | 23 | @Test 24 | fun test_combinations_empty() { 25 | assertEquals("[[]]", emptyList.combinations(0).toList().toString()) 26 | assertEquals("[]", emptyList.combinations(100).toList().toString()) 27 | assertIterableEquals(listOf(emptyList), emptyList.combinations(0).toList()) 28 | assertIterableEquals(emptyList, emptyList.combinations(100).toList()) 29 | 30 | assertEquals("[[]]", emptyArray.combinations(0).toList().map { it.toList() }.toString()) 31 | assertEquals("[]", emptyArray.combinations(100).toList().map { it.toList() }.toString()) 32 | assertArrayEquals(arrayOf(emptyArray), emptyArray.combinations(0).toList().toTypedArray()) 33 | assertArrayEquals(emptyArray, emptyArray.combinations(100).toList().toTypedArray()) 34 | } 35 | 36 | @Test 37 | fun test_combinations_exception() { 38 | assertThrows { ('a'..'c').combinations(-1) } 39 | assertThrows { emptyList.combinations(-1) } 40 | } 41 | 42 | @Test 43 | fun test_combinations() { 44 | val array = arrayOf(0, 1, 2) 45 | val expected = listOf( 46 | "[[]]", 47 | "[[0], [1], [2]]", 48 | "[[0, 1], [0, 2], [1, 2]]", 49 | "[[0, 1, 2]]", 50 | "[]") 51 | 52 | expected.forEachIndexed { index, it -> 53 | assertEquals(it, array.toList().combinations(index).toList().toString()) 54 | assertEquals(it, array.combinations(index).map { it.toList() }.toList().toString()) 55 | assertEquals(it, CombinationsGenerator.indices(array.size, index).map { ints -> ints.map { array[it] } }.toList().toString()) 56 | } 57 | 58 | // combinations() は permutations() のシーケンスから、 59 | // 要素が(入力プールの位置に応じた順序で)ソート順になっていない項目をフィルタリングしたものと同じ 60 | fun Iterable.combinations2(length: Int): Sequence> = sequence { 61 | val pool = toList() 62 | pool.indices.permutations(length) 63 | .filter { it.sorted() == it } 64 | .forEach { list -> yield(list.map { pool[it] }) } 65 | } 66 | 67 | fun Iterable.combinations3(length: Int): Sequence> = sequence { 68 | val pool = toList() 69 | pool.indices.combinationsWithRepetition(length) 70 | .filter { it.toSet().size == length } 71 | .forEach { list -> yield(list.map { pool[it] }) } 72 | } 73 | 74 | val comparator = ListComparator() 75 | 76 | for (n in 0 until 7) { 77 | val values = (0 until n).map { 5 * it - 12 } 78 | for (r in 0 until n + 1) { 79 | val result = values.combinations(r).toList() 80 | 81 | assertEquals(result, values.toTypedArray().combinations(r).map { it.toList() }.toList()) 82 | assertEquals(result, CombinationsGenerator.indices(n, r).map { ints -> ints.map { values[it] } }.toList()) 83 | 84 | assertEquals(result.count(), if (r > n) 0 else combinations(n, r).intValueExact()) 85 | assertEquals(result.count(), result.toSet().count()) 86 | assertEquals(result, result.sortedWith(comparator)) 87 | 88 | for (c in result) { 89 | assertEquals(c.count(), r) 90 | assertEquals(c.toSet().count(), r) 91 | assertEquals(c, c.sorted()) 92 | assertTrue(c.all { it in values }) 93 | assertEquals(c, values.filter { it in c }) 94 | } 95 | 96 | assertEquals(result, values.combinations2(r).toList()) 97 | assertEquals(result, values.combinations3(r).toList()) 98 | } 99 | } 100 | } 101 | } 102 | 103 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/shiguruikai/combinatoricskt/CombinationsWithRepetitionGeneratorTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 shiguruikai 3 | * 4 | * Licensed under the MIT license 5 | * https://github.com/shiguruikai/combinatoricskt/blob/master/LICENSE 6 | */ 7 | 8 | package com.github.shiguruikai.combinatoricskt 9 | 10 | import com.github.shiguruikai.combinatoricskt.internal.combinationsWithRepetition 11 | import org.junit.jupiter.api.Assertions.assertArrayEquals 12 | import org.junit.jupiter.api.Assertions.assertEquals 13 | import org.junit.jupiter.api.Assertions.assertIterableEquals 14 | import org.junit.jupiter.api.Assertions.assertTrue 15 | import org.junit.jupiter.api.Test 16 | import org.junit.jupiter.api.assertThrows 17 | import java.math.BigInteger 18 | 19 | internal class CombinationsWithRepetitionGeneratorTest { 20 | 21 | private val emptyList = emptyList() 22 | private val emptyArray = emptyArray() 23 | 24 | @Test 25 | fun test_combinationsWithRepetition_empty() { 26 | assertEquals("[[]]", emptyList.combinationsWithRepetition(0).toList().toString()) 27 | assertEquals("[]", emptyList.combinationsWithRepetition(100).toList().toString()) 28 | assertIterableEquals(listOf(emptyList), emptyList.combinationsWithRepetition(0).toList()) 29 | assertIterableEquals(emptyList, emptyList.combinationsWithRepetition(100).toList()) 30 | 31 | assertEquals("[[]]", emptyArray.combinationsWithRepetition(0).toList().map { it.toList() }.toString()) 32 | assertEquals("[]", emptyArray.combinationsWithRepetition(100).toList().map { it.toList() }.toString()) 33 | assertArrayEquals(arrayOf(emptyArray), emptyArray.combinationsWithRepetition(0).toList().toTypedArray()) 34 | assertArrayEquals(emptyArray, emptyArray.combinationsWithRepetition(100).toList().toTypedArray()) 35 | } 36 | 37 | @Test 38 | fun test_combinationsWithRepetition_exception() { 39 | assertThrows { ('a'..'c').combinationsWithRepetition(-1).toList() } 40 | assertThrows { emptyList.combinationsWithRepetition(-1).toList() } 41 | } 42 | 43 | @Test 44 | fun test_combinationsWithRepetition() { 45 | assertEquals( 46 | "[[]]", 47 | (0..2).combinationsWithRepetition(0).toList().toString()) 48 | assertEquals( 49 | "[[0], [1], [2]]", 50 | (0..2).combinationsWithRepetition(1).toList().toString()) 51 | assertEquals( 52 | "[[0, 0], [0, 1], [0, 2], [1, 1], [1, 2], [2, 2]]", 53 | (0..2).combinationsWithRepetition(2).toList().toString()) 54 | assertEquals("[" + 55 | "[0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 1, 1], [0, 1, 2], " + 56 | "[0, 2, 2], [1, 1, 1], [1, 1, 2], [1, 2, 2], [2, 2, 2]]", 57 | (0..2).combinationsWithRepetition(3).toList().toString()) 58 | assertEquals("[" + 59 | "[0, 0, 0, 0], [0, 0, 0, 1], [0, 0, 0, 2], [0, 0, 1, 1], [0, 0, 1, 2], " + 60 | "[0, 0, 2, 2], [0, 1, 1, 1], [0, 1, 1, 2], [0, 1, 2, 2], [0, 2, 2, 2], " + 61 | "[1, 1, 1, 1], [1, 1, 1, 2], [1, 1, 2, 2], [1, 2, 2, 2], [2, 2, 2, 2]]", 62 | (0..2).combinationsWithRepetition(4).toList().toString()) 63 | 64 | // permutationsWithRepetition() は permutationsWithRepetition() のシーケンスから、 65 | // 要素が(入力プールの位置に応じた順序で)ソート順になっていない項目をフィルタリングしたものと同じ 66 | fun Iterable.cwr2(length: Int): List> = sequence { 67 | val pool = toList() 68 | pool.indices.permutationsWithRepetition(length) 69 | .filter { it.sorted() == it } 70 | .forEach { list -> yield(list.map { pool[it] }) } 71 | }.toList() 72 | 73 | fun numcombs(n: Int, r: Int): BigInteger = when (n) { 74 | 0 -> if (r > 0) BigInteger.ZERO else BigInteger.ONE 75 | else -> combinationsWithRepetition(n, r) 76 | } 77 | 78 | val comparator = ListComparator() 79 | 80 | for (n in 0 until 7) { 81 | val values = (0 until n).map { 5 * it - 12 } 82 | for (r in 0 until n + 1) { 83 | val result = values.combinationsWithRepetition(r).toList() 84 | 85 | assertEquals(result, values.toTypedArray().combinationsWithRepetition(r).map { it.toList() }.toList()) 86 | assertEquals(result, CombinationsWithRepetitionGenerator.indices(n, r).map { ints -> ints.map { values[it] } }.toList()) 87 | 88 | assertEquals(result.count(), numcombs(n, r).intValueExact()) 89 | assertEquals(result.count(), result.toSet().count()) 90 | assertEquals(result, result.sortedWith(comparator)) 91 | 92 | val regularCombs = values.combinations(r).toList() 93 | if (n == 0 || r <= 1) { 94 | assertEquals(result, regularCombs) 95 | } else { 96 | assertTrue(result.toSet().containsAll(regularCombs.toSet())) 97 | } 98 | 99 | for (c in result) { 100 | assertEquals(c.count(), r) 101 | val noruns = c.groupBy { it }.map { it.key } 102 | assertEquals(noruns.count(), noruns.count()) 103 | assertEquals(c, c.sorted()) 104 | assertTrue(c.all { it in values }) 105 | assertEquals(noruns, values.filter { it in c }) 106 | } 107 | 108 | assertEquals(result, values.cwr2(r)) 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/shiguruikai/combinatoricskt/CombinatorialSequenceTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 shiguruikai 3 | * 4 | * Licensed under the MIT license 5 | * https://github.com/shiguruikai/combinatoricskt/blob/master/LICENSE 6 | */ 7 | 8 | package com.github.shiguruikai.combinatoricskt 9 | 10 | import org.junit.jupiter.api.Assertions.assertEquals 11 | import org.junit.jupiter.api.Test 12 | import java.math.BigInteger.ONE 13 | import java.math.BigInteger.ZERO 14 | import kotlin.streams.toList 15 | 16 | internal class CombinatorialSequenceTest { 17 | 18 | @Test 19 | fun toList() { 20 | assertEquals(listOf("test"), CombinatorialSequence(ZERO, sequenceOf("test")).toList()) 21 | assertEquals(listOf("test"), CombinatorialSequence(ZERO, sequenceOf("test")).toList()) 22 | assertEquals(listOf('a', 'b', 'c'), CombinatorialSequence(ZERO, ('a'..'c').asSequence()).toList()) 23 | } 24 | 25 | @Test 26 | fun toMutableList() { 27 | assertEquals(listOf("test"), CombinatorialSequence(ZERO, sequenceOf("test")).toMutableList()) 28 | assertEquals(listOf("test"), CombinatorialSequence(ZERO, sequenceOf("test")).toMutableList()) 29 | assertEquals(listOf('a', 'b', 'c'), CombinatorialSequence(ZERO, ('a'..'c').asSequence()).toMutableList()) 30 | } 31 | 32 | @Test 33 | fun asStream() { 34 | assertEquals(listOf("test"), CombinatorialSequence(ZERO, sequenceOf("test")).asStream().toList()) 35 | assertEquals(listOf("test"), CombinatorialSequence(ZERO, sequenceOf("test")).asStream().toList()) 36 | assertEquals(listOf('a', 'b', 'c'), CombinatorialSequence(ZERO, ('a'..'c').asSequence()).asStream().toList()) 37 | } 38 | 39 | @Test 40 | fun getTotalSize() { 41 | assertEquals(ZERO, CombinatorialSequence(ZERO, sequenceOf("test")).totalSize) 42 | assertEquals(ONE, CombinatorialSequence(ONE, sequenceOf("test")).totalSize) 43 | assertEquals(123.toBigInteger(), CombinatorialSequence(123.toBigInteger(), sequenceOf("test")).totalSize) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/shiguruikai/combinatoricskt/CombinatoricsKtTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 shiguruikai 3 | * 4 | * Licensed under the MIT license 5 | * https://github.com/shiguruikai/combinatoricskt/blob/master/LICENSE 6 | */ 7 | 8 | package com.github.shiguruikai.combinatoricskt 9 | 10 | import com.github.shiguruikai.combinatoricskt.internal.combinations 11 | import com.github.shiguruikai.combinatoricskt.internal.combinationsWithRepetition 12 | import com.github.shiguruikai.combinatoricskt.internal.factorial 13 | import com.github.shiguruikai.combinatoricskt.internal.permutations 14 | import com.github.shiguruikai.combinatoricskt.internal.subFactorial 15 | import org.junit.jupiter.api.Assertions.assertDoesNotThrow 16 | import org.junit.jupiter.api.Assertions.assertEquals 17 | import org.junit.jupiter.api.DisplayName 18 | import org.junit.jupiter.api.Test 19 | import org.junit.jupiter.api.assertThrows 20 | import java.math.BigInteger 21 | 22 | internal class CombinatoricsKtTest { 23 | 24 | @Test 25 | @DisplayName("CombinatorialSequence の totalSize と実際のサイズが等しいかテスト") 26 | fun test_check_size() { 27 | fun CombinatorialSequence.testCheckSize(expected: BigInteger) { 28 | assertEquals(expected, totalSize) 29 | assertEquals(expected, count().toBigInteger()) 30 | } 31 | 32 | val list = ('A'..'D').toList() 33 | for (n in list.indices) { 34 | val values = list.take(n) 35 | for (r in list.indices) { 36 | values.permutations(r).testCheckSize(if (n >= 0 && r >= 0 && n >= r) permutations(n, r) else BigInteger.ZERO) 37 | values.permutationsWithRepetition(r).testCheckSize(n.toBigInteger().pow(r)) 38 | values.derangements().testCheckSize(subFactorial(n)) 39 | values.combinations(r).testCheckSize(if (n >= 0 && r >= 0 && n >= r) combinations(n, r) else BigInteger.ZERO) 40 | values.combinationsWithRepetition(r).testCheckSize(when { 41 | n >= 1 && r >= 0 -> combinationsWithRepetition(n, r) 42 | r == 0 -> BigInteger.ONE 43 | else -> BigInteger.ZERO 44 | }) 45 | values.cartesianProduct().testCheckSize(n.toBigInteger()) 46 | values.cartesianProduct(values).testCheckSize((n * n).toBigInteger()) 47 | values.cartesianProduct(values, repeat = r).testCheckSize((n * n).toBigInteger().pow(r)) 48 | values.powerset().testCheckSize(2.toBigInteger().pow(n)) 49 | } 50 | } 51 | } 52 | 53 | @Test 54 | @DisplayName("組合せの関係をテスト") 55 | fun test_combinatorics() { 56 | val sortedList: List = ('A'..'F').sorted() 57 | val comparator = ListComparator() 58 | 59 | for (n in sortedList.indices) { 60 | val s = sortedList.take(n) 61 | for (r in sortedList.indices) { 62 | val pwr = s.permutationsWithRepetition(r).toList() 63 | val cwr = s.combinationsWithRepetition(r).toList() 64 | val perm = s.permutations(r).toList() 65 | val comb = s.combinations(r).toList() 66 | 67 | // サイズをチェックする 68 | assertEquals( 69 | pwr.count().toBigInteger(), 70 | n.toBigInteger().pow(r)) 71 | assertEquals( 72 | cwr.count().toBigInteger(), 73 | when { 74 | n > 0 -> factorial(n + r - 1) / factorial(r) / factorial(n - 1) 75 | r == 0 -> BigInteger.ONE 76 | else -> BigInteger.ZERO 77 | }) 78 | assertEquals( 79 | perm.count().toBigInteger(), 80 | if (r > n) BigInteger.ZERO else factorial(n) / factorial(n - r)) 81 | assertEquals( 82 | comb.count().toBigInteger(), 83 | if (r > n) BigInteger.ZERO else factorial(n) / factorial(r) / factorial(n - r)) 84 | 85 | // 辞書順に並んでいるかチェックする 86 | assertEquals(pwr, pwr.sortedWith(comparator)) 87 | assertEquals(cwr, cwr.sortedWith(comparator)) 88 | assertEquals(perm, perm.sortedWith(comparator)) 89 | assertEquals(comb, comb.sortedWith(comparator)) 90 | 91 | // 相互関係をチェックする 92 | assertEquals(cwr, pwr.filter { it.sorted() == it }) 93 | assertEquals(perm, pwr.filter { it.toSet().count() == r }) 94 | assertEquals(comb, perm.filter { it.sorted() == it }) 95 | assertEquals(comb, cwr.filter { it.toSet().count() == r }) 96 | assertEquals(comb, cwr.toSet().filter { it in perm }) 97 | assertEquals(comb, perm.toSet().filter { it in cwr }) 98 | assertEquals(comb, cwr.intersect(perm).sortedWith(comparator)) 99 | } 100 | } 101 | } 102 | 103 | @Test 104 | @DisplayName("Sequence の再消費で IllegalStateException が発生するかテスト") 105 | fun test_sequence_can_be_consumed_only_once() { 106 | fun Sequence.testReuse() { 107 | //assertTrue(this is kotlin.sequences.ConstrainedOnceSequence) 108 | assertDoesNotThrow { count() } 109 | assertThrows { count() } 110 | } 111 | 112 | val range = 0..4 113 | val n = 2 114 | 115 | range.permutations(n).testReuse() 116 | range.permutationsWithRepetition(n).testReuse() 117 | range.derangements().testReuse() 118 | range.combinations(n).testReuse() 119 | range.combinationsWithRepetition(n).testReuse() 120 | range.cartesianProduct().testReuse() 121 | range.cartesianProduct(range).testReuse() 122 | range.cartesianProduct(range, repeat = n).testReuse() 123 | range.powerset().testReuse() 124 | } 125 | 126 | @Test 127 | @DisplayName("次の要素がない Iterator で next() を呼んだとき NoSuchElementException が発生するかテスト") 128 | fun test_iterator() { 129 | fun CombinatorialSequence.testIterator() { 130 | val iterator = iterator() 131 | while (iterator.hasNext()) { 132 | iterator.next() 133 | } 134 | assertThrows { iterator.next() } 135 | } 136 | 137 | for (n in 0..4) { 138 | for (r in 0..4) { 139 | PermutationsGenerator.indices(n, r).testIterator() 140 | PermutationsWithRepetitionGenerator.indices(n, r).testIterator() 141 | DerangementGenerator.indices(n).testIterator() 142 | CombinationsGenerator.indices(n, r).testIterator() 143 | CombinationsWithRepetitionGenerator.indices(n, r).testIterator() 144 | CartesianProductGenerator.indices(n, r).testIterator() 145 | CartesianProductGenerator.indices(n, r, repeat = 2).testIterator() 146 | PowerSetGenerator.indices(n).testIterator() 147 | } 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/shiguruikai/combinatoricskt/DerangementGeneratorTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 shiguruikai 3 | * 4 | * Licensed under the MIT license 5 | * https://github.com/shiguruikai/combinatoricskt/blob/master/LICENSE 6 | */ 7 | 8 | package com.github.shiguruikai.combinatoricskt 9 | 10 | import org.junit.jupiter.api.Assertions.assertEquals 11 | import org.junit.jupiter.api.Test 12 | 13 | internal class DerangementGeneratorTest { 14 | 15 | private val emptyList = emptyList() 16 | private val emptyArray = emptyArray() 17 | 18 | @Test 19 | fun test_derangements_empty() { 20 | assertEquals("[[]]", emptyList.derangements().toList().toString()) 21 | assertEquals("[[]]", emptyArray.derangements().map { it.toList() }.toList().toString()) 22 | 23 | } 24 | 25 | @Test 26 | fun test_derangements() { 27 | val array = arrayOf(0, 1, 2, 3) 28 | val expected = listOf( 29 | "[[]]", 30 | "[]", 31 | "[[1, 0]]", 32 | "[[1, 2, 0], [2, 0, 1]]", 33 | "[[1, 0, 3, 2], [1, 2, 3, 0], [1, 3, 0, 2], [2, 0, 3, 1], [2, 3, 0, 1], [2, 3, 1, 0], [3, 0, 1, 2], [3, 2, 0, 1], [3, 2, 1, 0]]") 34 | 35 | expected.forEachIndexed { index, it -> 36 | assertEquals(it, DerangementGenerator.indices(index).map { it.toList() }.toList().toString()) 37 | assertEquals(it, array.take(index).derangements().toList().toString()) 38 | assertEquals(it, array.copyOf(index).derangements().map { it.toList() }.toList().toString()) 39 | } 40 | 41 | val range = 0..6 42 | for (r in range) { 43 | val x = range.take(r) 44 | assertEquals( 45 | x.permutations().filter { list -> var i = 0; list.all { it != i++ } }.toList(), 46 | x.derangements().toList()) 47 | assertEquals(x.derangements().totalSize, x.derangements().toList().size.toBigInteger()) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/shiguruikai/combinatoricskt/ListComparator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 shiguruikai 3 | * 4 | * Licensed under the MIT license 5 | * https://github.com/shiguruikai/combinatoricskt/blob/master/LICENSE 6 | */ 7 | 8 | package com.github.shiguruikai.combinatoricskt 9 | 10 | class ListComparator> : Comparator> { 11 | override fun compare(a: List, b: List): Int { 12 | if (a === b) return 0 13 | 14 | a.asSequence().zip(b.asSequence()).forEach { (oa, ob) -> 15 | if (oa !== ob) { 16 | val v = oa.compareTo(ob) 17 | if (v != 0) { 18 | return v 19 | } 20 | } 21 | } 22 | 23 | return a.size - b.size 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/shiguruikai/combinatoricskt/PermutationsGeneratorTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 shiguruikai 3 | * 4 | * Licensed under the MIT license 5 | * https://github.com/shiguruikai/combinatoricskt/blob/master/LICENSE 6 | */ 7 | 8 | package com.github.shiguruikai.combinatoricskt 9 | 10 | import com.github.shiguruikai.combinatoricskt.internal.permutations 11 | import com.github.shiguruikai.combinatoricskt.internal.swap 12 | import org.junit.jupiter.api.Assertions.assertArrayEquals 13 | import org.junit.jupiter.api.Assertions.assertEquals 14 | import org.junit.jupiter.api.Assertions.assertIterableEquals 15 | import org.junit.jupiter.api.Assertions.assertTrue 16 | import org.junit.jupiter.api.Test 17 | import org.junit.jupiter.api.assertThrows 18 | 19 | internal class PermutationsGeneratorTest { 20 | 21 | private val emptyList = emptyList() 22 | private val emptyArray = emptyArray() 23 | 24 | @Test 25 | fun test_permutations_empty() { 26 | assertEquals("[[]]", emptyList.permutations().toList().toString()) 27 | assertEquals("[[]]", emptyList.permutations(0).toList().toString()) 28 | assertEquals("[]", emptyList.permutations(100).toList().toString()) 29 | assertIterableEquals(listOf(emptyList), emptyList.permutations().toList()) 30 | assertIterableEquals(listOf(emptyList), emptyList.permutations(0).toList()) 31 | assertIterableEquals(emptyList, emptyList.permutations(100).toList()) 32 | 33 | assertEquals("[[]]", emptyArray.permutations().toList().map { it.toList() }.toString()) 34 | assertEquals("[[]]", emptyArray.permutations(0).toList().map { it.toList() }.toString()) 35 | assertEquals("[]", emptyArray.permutations(100).toList().map { it.toList() }.toString()) 36 | assertArrayEquals(arrayOf(emptyArray), emptyArray.permutations().toList().toTypedArray()) 37 | assertArrayEquals(arrayOf(emptyArray), emptyArray.permutations(0).toList().toTypedArray()) 38 | assertArrayEquals(emptyArray, emptyArray.permutations(100).toList().toTypedArray()) 39 | } 40 | 41 | @Test 42 | fun test_permutations_exception() { 43 | assertThrows { ('a'..'c').permutations(-1).toList() } 44 | assertThrows { emptyList.permutations(-1).toList() } 45 | } 46 | 47 | @Test 48 | fun test_permutations() { 49 | 50 | assertEquals( 51 | (0..2).permutations().toList().toString(), 52 | (0..2).permutations((0..2).count()).toList().toString()) 53 | 54 | val array = arrayOf(0, 1, 2) 55 | val expected = listOf( 56 | "[[]]", 57 | "[[0], [1], [2]]", 58 | "[[0, 1], [0, 2], [1, 0], [1, 2], [2, 0], [2, 1]]", 59 | "[[0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, 1], [2, 1, 0]]", 60 | "[]") 61 | expected.forEachIndexed { index, it -> 62 | assertEquals(it, array.toList().permutations(index).toList().toString()) 63 | assertEquals(it, array.permutations(index).map { it.toList() }.toList().toString()) 64 | } 65 | 66 | // permutations() は permutationsWithRepetition() のシーケンスから、 67 | // 重複 (入力プールの位置に応じた順序で)を除くようフィルタリングしたものと同じ 68 | fun Iterable.permutations2(length: Int? = null): Sequence> = sequence { 69 | val pool = toList() 70 | val r = length ?: pool.size 71 | pool.indices.permutationsWithRepetition(r) 72 | .filter { it.toSet().size == r } 73 | .forEach { list -> yield(list.map { pool[it] }) } 74 | } 75 | 76 | // https://www.wikiwand.com/en/Heap%27s_algorithm 77 | // 辞書順で取得できない 78 | fun Iterable.permutations3(): Sequence> = sequence { 79 | val pool = toList() 80 | val n = pool.size 81 | val indices = IntArray(n) { it } 82 | val cycles = IntArray(n) 83 | yield(indices.map { pool[it] }) 84 | var i = 0 85 | while (i < n) { 86 | if (cycles[i] < i) { 87 | if (i and 1 == 0) { 88 | indices.swap(0, i) 89 | } else { 90 | indices.swap(cycles[i], i) 91 | } 92 | yield(indices.map { pool[it] }) 93 | cycles[i]++ 94 | i = 0 95 | } else { 96 | cycles[i] = 0 97 | i++ 98 | } 99 | } 100 | } 101 | 102 | val comparator = ListComparator() 103 | 104 | for (n in 0 until 7) { 105 | val values = (0 until n).map { 5 * it - 12 } 106 | for (r in 0 until n + 1) { 107 | val result = values.permutations(r).toList() 108 | 109 | assertEquals(result, values.toTypedArray().permutations(r).map { it.toList() }.toList()) 110 | assertEquals(result, PermutationsGenerator.indices(n, r).map { ints -> ints.map { values[it] } }.toList()) 111 | 112 | assertEquals(result.count(), if (r > n) 0 else permutations(n, r).intValueExact()) 113 | assertEquals(result.count(), result.toSet().count()) 114 | assertEquals(result, result.sortedWith(comparator)) 115 | 116 | for (p in result) { 117 | assertEquals(p.count(), r) 118 | assertEquals(p.toSet().count(), r) 119 | assertTrue(p.all { it in values }) 120 | } 121 | 122 | assertEquals(result, values.permutations2(r).toList()) 123 | 124 | if (result.size == r) { 125 | assertEquals(result, values.permutations3().sortedWith(comparator).toList()) 126 | } 127 | 128 | if (r == n) { 129 | assertEquals(result, values.permutations(null).toList()) 130 | assertEquals(result, values.permutations().toList()) 131 | } 132 | } 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/shiguruikai/combinatoricskt/PermutationsWithRepetitionGeneratorTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 shiguruikai 3 | * 4 | * Licensed under the MIT license 5 | * https://github.com/shiguruikai/combinatoricskt/blob/master/LICENSE 6 | */ 7 | 8 | package com.github.shiguruikai.combinatoricskt 9 | 10 | import org.junit.jupiter.api.Assertions.assertArrayEquals 11 | import org.junit.jupiter.api.Assertions.assertEquals 12 | import org.junit.jupiter.api.Assertions.assertIterableEquals 13 | import org.junit.jupiter.api.Test 14 | import org.junit.jupiter.api.assertThrows 15 | 16 | internal class PermutationsWithRepetitionGeneratorTest { 17 | 18 | private val emptyList = emptyList() 19 | private val emptyArray = emptyArray() 20 | 21 | @Test 22 | fun test_permutationsWithRepetition_empty() { 23 | assertEquals("[[]]", emptyList.permutationsWithRepetition(0).toList().toString()) 24 | assertEquals("[]", emptyList.permutationsWithRepetition(100).toList().toString()) 25 | assertIterableEquals(listOf(emptyList), emptyList.permutationsWithRepetition(0).toList()) 26 | assertIterableEquals(emptyList, emptyList.permutationsWithRepetition(100).toList()) 27 | 28 | assertEquals("[[]]", emptyArray.permutationsWithRepetition(0).toList().map { it.toList() }.toString()) 29 | assertEquals("[]", emptyArray.permutationsWithRepetition(100).toList().map { it.toList() }.toString()) 30 | assertArrayEquals(arrayOf(emptyArray), emptyArray.permutationsWithRepetition(0).toList().toTypedArray()) 31 | assertArrayEquals(emptyArray, emptyArray.permutationsWithRepetition(100).toList().toTypedArray()) 32 | } 33 | 34 | @Test 35 | fun test_permutationsWithRepetition_exception() { 36 | assertThrows { ('a'..'c').permutationsWithRepetition(-1) } 37 | assertThrows { emptyList.permutationsWithRepetition(-1) } 38 | } 39 | 40 | @Test 41 | fun test_permutationsWithRepetition() { 42 | val array = arrayOf(0, 1, 2) 43 | val expected = listOf( 44 | "[[]]", 45 | "[[0], [1], [2]]", 46 | "[[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], [2, 0], [2, 1], [2, 2]]", 47 | "[" + 48 | "[0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 1, 0], [0, 1, 1], [0, 1, 2], [0, 2, 0], [0, 2, 1], [0, 2, 2], " + 49 | "[1, 0, 0], [1, 0, 1], [1, 0, 2], [1, 1, 0], [1, 1, 1], [1, 1, 2], [1, 2, 0], [1, 2, 1], [1, 2, 2], " + 50 | "[2, 0, 0], [2, 0, 1], [2, 0, 2], [2, 1, 0], [2, 1, 1], [2, 1, 2], [2, 2, 0], [2, 2, 1], [2, 2, 2]]", 51 | "[" + 52 | "[0, 0, 0, 0], [0, 0, 0, 1], [0, 0, 0, 2], [0, 0, 1, 0], [0, 0, 1, 1], [0, 0, 1, 2], [0, 0, 2, 0], [0, 0, 2, 1], [0, 0, 2, 2], " + 53 | "[0, 1, 0, 0], [0, 1, 0, 1], [0, 1, 0, 2], [0, 1, 1, 0], [0, 1, 1, 1], [0, 1, 1, 2], [0, 1, 2, 0], [0, 1, 2, 1], [0, 1, 2, 2], " + 54 | "[0, 2, 0, 0], [0, 2, 0, 1], [0, 2, 0, 2], [0, 2, 1, 0], [0, 2, 1, 1], [0, 2, 1, 2], [0, 2, 2, 0], [0, 2, 2, 1], [0, 2, 2, 2], " + 55 | "[1, 0, 0, 0], [1, 0, 0, 1], [1, 0, 0, 2], [1, 0, 1, 0], [1, 0, 1, 1], [1, 0, 1, 2], [1, 0, 2, 0], [1, 0, 2, 1], [1, 0, 2, 2], " + 56 | "[1, 1, 0, 0], [1, 1, 0, 1], [1, 1, 0, 2], [1, 1, 1, 0], [1, 1, 1, 1], [1, 1, 1, 2], [1, 1, 2, 0], [1, 1, 2, 1], [1, 1, 2, 2], " + 57 | "[1, 2, 0, 0], [1, 2, 0, 1], [1, 2, 0, 2], [1, 2, 1, 0], [1, 2, 1, 1], [1, 2, 1, 2], [1, 2, 2, 0], [1, 2, 2, 1], [1, 2, 2, 2], " + 58 | "[2, 0, 0, 0], [2, 0, 0, 1], [2, 0, 0, 2], [2, 0, 1, 0], [2, 0, 1, 1], [2, 0, 1, 2], [2, 0, 2, 0], [2, 0, 2, 1], [2, 0, 2, 2], " + 59 | "[2, 1, 0, 0], [2, 1, 0, 1], [2, 1, 0, 2], [2, 1, 1, 0], [2, 1, 1, 1], [2, 1, 1, 2], [2, 1, 2, 0], [2, 1, 2, 1], [2, 1, 2, 2], " + 60 | "[2, 2, 0, 0], [2, 2, 0, 1], [2, 2, 0, 2], [2, 2, 1, 0], [2, 2, 1, 1], [2, 2, 1, 2], [2, 2, 2, 0], [2, 2, 2, 1], [2, 2, 2, 2]]") 61 | 62 | expected.forEachIndexed { index, it -> 63 | assertEquals(it, array.toList().permutationsWithRepetition(index).toList().toString()) 64 | assertEquals(it, array.permutationsWithRepetition(index).map { it.toList() }.toList().toString()) 65 | assertEquals(it, PermutationsWithRepetitionGenerator.indices(array.size, index).map { it.toList() }.toList().toString()) 66 | } 67 | 68 | assertArrayEquals( 69 | array.cartesianProduct(repeat = 4).toList().toTypedArray(), 70 | array.permutationsWithRepetition(4).toList().toTypedArray()) 71 | assertArrayEquals( 72 | array.permutationsWithRepetition(4).toList().toTypedArray(), 73 | array.cartesianProduct(array, array, array).toList().toTypedArray()) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/shiguruikai/combinatoricskt/PowerSetGeneratorTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 shiguruikai 3 | * 4 | * Licensed under the MIT license 5 | * https://github.com/shiguruikai/combinatoricskt/blob/master/LICENSE 6 | */ 7 | 8 | package com.github.shiguruikai.combinatoricskt 9 | 10 | import org.junit.jupiter.api.Assertions.assertEquals 11 | import org.junit.jupiter.api.Assertions.assertTrue 12 | import org.junit.jupiter.api.Test 13 | 14 | internal class PowerSetGeneratorTest { 15 | 16 | private val emptyList = emptyList() 17 | private val emptyArray = emptyArray() 18 | 19 | @Test 20 | fun test_powerset_empty() { 21 | assertEquals("[[]]", emptyList.powerset().toList().toString()) 22 | assertEquals("[[]]", emptyArray.powerset().toList().map { it.contentToString() }.toString()) 23 | } 24 | 25 | @Test 26 | fun test_powerset() { 27 | val array = arrayOf(0, 1, 2) 28 | val expected = listOf( 29 | "[[]]", 30 | "[[], [0]]", 31 | "[[], [0], [1], [0, 1]]", 32 | "[[], [0], [1], [0, 1], [2], [0, 2], [1, 2], [0, 1, 2]]") 33 | expected.forEachIndexed { index, it -> 34 | assertEquals(it, array.take(index).powerset().toList().toString()) 35 | assertEquals(it, array.copyOf(index).powerset().map { it.toList() }.toList().toString()) 36 | } 37 | 38 | fun Iterable.powerset1(): CombinatorialSequence> { 39 | val pool = toList() 40 | val totalSize = 2.toBigInteger().pow(pool.size) 41 | 42 | val iterator = iterator { 43 | val bitLength = totalSize.bitLength() 44 | val max = totalSize.intValueExact() - 1 45 | var i = 0 46 | while (true) { 47 | val acc = ArrayList(Integer.bitCount(i)) 48 | for (index in 0..bitLength) { 49 | if ((1 shl index) and i != 0) { 50 | acc += pool[index] 51 | } 52 | } 53 | yield(acc) 54 | 55 | if (i >= max) { 56 | break 57 | } 58 | i++ 59 | } 60 | } 61 | 62 | return CombinatorialSequence(totalSize, iterator) 63 | } 64 | 65 | // ソート順かつサイズ順で生成 66 | fun Iterable.powerset2(): Sequence> { 67 | val pool = toList() 68 | 69 | var sequence: Sequence> = emptySequence() 70 | for (r in 0..pool.size) { 71 | sequence += pool.combinations(r) 72 | } 73 | 74 | return sequence 75 | } 76 | 77 | fun Iterable.powerset3(): Sequence> { 78 | 79 | tailrec fun powerset3Tailrec(left: Collection, acc: Sequence>): Sequence> = when { 80 | left.isEmpty() -> acc 81 | else -> powerset3Tailrec(left.drop(1), acc + acc.map { it + left.first() }) 82 | } 83 | 84 | return powerset3Tailrec(toList(), sequenceOf(listOf())) 85 | } 86 | 87 | val comparator = ListComparator() 88 | 89 | val range = 'a'..'f' 90 | for (i in 0..7) { 91 | val v = range.take(i) 92 | val ps = v.powerset() 93 | val psList = ps.toList() 94 | val ps1 = v.powerset1() 95 | val ps2 = v.powerset2() 96 | val ps3 = v.powerset3() 97 | 98 | val size = 2.toBigInteger().pow(v.size) 99 | 100 | assertTrue(ps.totalSize == size && size == ps1.totalSize) 101 | assertEquals(psList, ps1.toList()) 102 | assertEquals(psList.sortedWith(comparator).sortedBy { it.size }, ps2.toList()) 103 | assertEquals(psList, ps3.toList()) 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/shiguruikai/combinatoricskt/internal/ExtensionsKtTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 shiguruikai 3 | * 4 | * Licensed under the MIT license 5 | * https://github.com/shiguruikai/combinatoricskt/blob/master/LICENSE 6 | */ 7 | 8 | package com.github.shiguruikai.combinatoricskt.internal 9 | 10 | import org.junit.jupiter.api.Assertions.assertArrayEquals 11 | import org.junit.jupiter.api.Assertions.assertIterableEquals 12 | import org.junit.jupiter.api.Assertions.assertTrue 13 | import org.junit.jupiter.api.Test 14 | 15 | internal class ExtensionsKtTest { 16 | 17 | @Test 18 | fun test_List_times() { 19 | val list = listOf(1, 2, 3) 20 | assertTrue(list === list * 1) 21 | assertIterableEquals(emptyList(), list * 0) 22 | assertIterableEquals(list, list * 1) 23 | assertIterableEquals(list + list, list * 2) 24 | assertIterableEquals(list + list + list, list * 3) 25 | } 26 | 27 | @Test 28 | fun test_IntArray_times() { 29 | val ints = intArrayOf(1, 2, 3) 30 | assertTrue(ints === ints * 1) 31 | assertArrayEquals(intArrayOf(), ints * 0) 32 | assertArrayEquals(ints, ints * 1) 33 | assertArrayEquals(ints + ints, ints * 2) 34 | assertArrayEquals(ints + ints + ints, ints * 3) 35 | } 36 | 37 | @Test 38 | fun test_IntArray_mapToList() { 39 | assertIterableEquals(listOf(1, 2, 3), intArrayOf(1, 2, 3, 4, 5).mapToList(3) { it }) 40 | assertIterableEquals(listOf("1", "2", "3"), intArrayOf(1, 2, 3).mapToList { it.toString() }) 41 | } 42 | 43 | @Test 44 | fun test_IntArray_mapToArray() { 45 | assertArrayEquals(arrayOf(1, 2, 3), intArrayOf(1, 2, 3, 4, 5).mapToArray(3) { it }) 46 | assertArrayEquals(arrayOf("1", "2", "3"), intArrayOf(1, 2, 3).mapToArray { it.toString() }) 47 | } 48 | 49 | @Test 50 | fun test_Array_mapToArray() { 51 | assertArrayEquals(arrayOf(1, 2, 3), arrayOf(1, 2, 3, 4, 5).mapToArray(3) { it }) 52 | assertArrayEquals(arrayOf("1", "2", "3"), arrayOf(1, 2, 3).mapToArray { it.toString() }) 53 | } 54 | 55 | @Test 56 | fun test_IntArray_swap() { 57 | assertArrayEquals(intArrayOf(1, 0), intArrayOf(0, 1).apply { swap(0, 1) }) 58 | assertArrayEquals(intArrayOf(1, 3, 2), intArrayOf(1, 2, 3).apply { swap(2, 1) }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/shiguruikai/combinatoricskt/internal/MathTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 shiguruikai 3 | * 4 | * Licensed under the MIT license 5 | * https://github.com/shiguruikai/combinatoricskt/blob/master/LICENSE 6 | */ 7 | 8 | package com.github.shiguruikai.combinatoricskt.internal 9 | 10 | import org.junit.jupiter.api.Assertions.assertDoesNotThrow 11 | import org.junit.jupiter.api.Assertions.assertEquals 12 | import org.junit.jupiter.api.Test 13 | import org.junit.jupiter.api.assertThrows 14 | import java.math.BigInteger 15 | 16 | internal class MathTest { 17 | 18 | @Test 19 | fun test_factorial() { 20 | for (n in -2..2) { 21 | if (n < 0) { 22 | assertThrows { factorial(n) } 23 | } else { 24 | assertDoesNotThrow { factorial(n) } 25 | } 26 | } 27 | 28 | val expectedArray = intArrayOf(1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880) 29 | expectedArray.forEachIndexed { i, expected -> 30 | assertEquals(expected, factorial(i).intValueExact()) 31 | } 32 | } 33 | 34 | @Test 35 | fun test_permutations() { 36 | for (n in -2..6) { 37 | for (r in -2..6) { 38 | if (n < 0 || r < 0 || n < r) { 39 | assertThrows { permutations(n, r) } 40 | } else { 41 | assertEquals(permutations(n, r), factorial(n) / factorial(n - r)) 42 | } 43 | } 44 | } 45 | 46 | val expectedArray = intArrayOf(1, 10, 90, 720, 5040, 30240, 151200, 604800, 1814400, 3628800) 47 | expectedArray.forEachIndexed { i: Int, expected: Int -> 48 | assertEquals(expected, permutations(expectedArray.size, i).intValueExact()) 49 | } 50 | } 51 | 52 | @Test 53 | fun test_combinations() { 54 | for (n in -2..6) { 55 | for (r in -2..6) { 56 | if (n < 0 || r < 0 || n < r) { 57 | assertThrows { combinations(n, r) } 58 | } else { 59 | assertEquals(combinations(n, r), factorial(n) / factorial(r) / factorial(n - r)) 60 | } 61 | } 62 | } 63 | 64 | val expectedArray = intArrayOf(1, 10, 45, 120, 210, 252, 210, 120, 45, 10) 65 | expectedArray.forEachIndexed { i: Int, expected: Int -> 66 | assertEquals(expected, combinations(expectedArray.size, i).intValueExact()) 67 | } 68 | } 69 | 70 | @Test 71 | fun test_combinationsWithRepetition() { 72 | for (n in -2..6) { 73 | for (r in -2..6) { 74 | if (n < 1 || r < 0) { 75 | assertThrows { combinationsWithRepetition(n, r) } 76 | } else { 77 | assertEquals(combinationsWithRepetition(n, r), 78 | factorial(n + r - 1) / factorial(r) / factorial(n - 1)) 79 | } 80 | } 81 | } 82 | 83 | val expectedArray = intArrayOf(1, 10, 55, 220, 715, 2002, 5005, 11440, 24310, 48620) 84 | expectedArray.forEachIndexed { i: Int, expected: Int -> 85 | assertEquals(expected, combinationsWithRepetition(expectedArray.size, i).intValueExact()) 86 | } 87 | } 88 | 89 | @Test 90 | fun test_subFactorial() { 91 | 92 | fun subFact(n: Int): BigInteger = when (n) { 93 | 0 -> BigInteger.ONE 94 | 1 -> BigInteger.ZERO 95 | 2 -> BigInteger.ONE 96 | else -> (n - 1).toBigInteger() * (subFact(n - 1) + subFact(n - 2)) 97 | } 98 | 99 | for (n in -2..6) { 100 | if (n < 0) { 101 | assertThrows { subFactorial(n) } 102 | } else { 103 | assertEquals(subFact(n), subFactorial(n)) 104 | } 105 | } 106 | 107 | val expectedArray = intArrayOf(1, 0, 1, 2, 9, 44, 265, 1854, 14833, 133496) 108 | expectedArray.forEachIndexed { i: Int, expected: Int -> 109 | assertEquals(expected, subFactorial(i).intValueExact()) 110 | } 111 | } 112 | } 113 | --------------------------------------------------------------------------------