├── .github ├── FUNDING.yml └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── README.md ├── _config.yml ├── combinations.go ├── combinations_test.go └── go.mod /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: mxschmitt 2 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: 3 | push: 4 | branches: [ "main" ] 5 | pull_request: 6 | branches: [ "main" ] 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - name: Set up Go 13 | uses: actions/setup-go@v3 14 | with: 15 | go-version: 1.18 16 | - name: Build 17 | run: go build -v ./... 18 | - name: Test 19 | run: go test -v -race -covermode atomic -coverprofile=covprofile ./... 20 | - name: Install goveralls 21 | run: go install github.com/mattn/goveralls@latest 22 | - name: Send coverage 23 | env: 24 | COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | run: goveralls -coverprofile=covprofile -service=github 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Max Schmitt 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 | # Golang combinations 2 | 3 | [![GoDoc](https://godoc.org/github.com/mxschmitt/golang-combinations?status.svg)](https://godoc.org/github.com/mxschmitt/golang-combinations) 4 | [![Go](https://github.com/mxschmitt/golang-combinations/actions/workflows/go.yml/badge.svg)](https://github.com/mxschmitt/golang-combinations/actions/workflows/go.yml) 5 | [![Coverage Status](https://coveralls.io/repos/github/mxschmitt/golang-combinations/badge.svg?branch=master)](https://coveralls.io/github/mxschmitt/golang-combinations?branch=master) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/mxschmitt/golang-combinations)](https://goreportcard.com/report/github.com/mxschmitt/golang-combinations) 7 | [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) 8 | 9 | This package provides a method to generate all ordered combinations out of a given generic array. 10 | This essentially creates the powerset of the given array except that the empty set is disregarded. 11 | 12 | ## Examples 13 | 14 | Take a look at the [godoc](https://godoc.org/github.com/mxschmitt/golang-combinations/#pkg-examples) for examples. 15 | 16 | In general when you have e.g. `[]string{"A", "B", "C"}` you will get: 17 | 18 | ```json 19 | [ 20 | ["A"], 21 | ["B"], 22 | ["A", "B"], 23 | ["C"], 24 | ["A", "C"], 25 | ["B", "C"], 26 | ["A", "B", "C"] 27 | ] 28 | ``` 29 | 30 | ## Background 31 | 32 | The algorithm iterates over each number from `1` to `2^length(input)`, separating it by binary components and utilizes the true/false interpretation of binary 1's and 0's to extract all unique ordered combinations of the input slice. 33 | 34 | E.g. a binary number `0011` means selecting the first and second index from the slice and ignoring the third and fourth. For input `{"A", "B", "C", "D"}` this signifies the combination `{"A", "B"}`. 35 | 36 | For input slice `{"A", "B", "C", "D"}` there are `2^4 - 1 = 15` binary combinations, so mapping each bit position to a slice index and selecting the entry for binary `1` and discarding for binary `0` gives the full subset as: 37 | 38 | ```txt 39 | 1 = 0001 => ---A => {"A"} 40 | 2 = 0010 => --B- => {"B"} 41 | 3 = 0011 => --BA => {"A", "B"} 42 | 4 = 0100 => -C-- => {"C"} 43 | 5 = 0101 => -C-A => {"A", "C"} 44 | 6 = 0110 => -CB- => {"B", "C"} 45 | 7 = 0111 => -CBA => {"A", "B", "C"} 46 | 8 = 1000 => D--- => {"D"} 47 | 9 = 1001 => D--A => {"A", "D"} 48 | 10 = 1010 => D-B- => {"B", "D"} 49 | 11 = 1011 => D-BA => {"A", "B", "D"} 50 | 12 = 1100 => DC-- => {"C", "D"} 51 | 13 = 1101 => DC-A => {"A", "C", "D"} 52 | 14 = 1110 => DCB- => {"B", "C", "D"} 53 | 15 = 1111 => DCBA => {"A", "B", "C", "D"} 54 | ``` 55 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /combinations.go: -------------------------------------------------------------------------------- 1 | // Package combinations provides a method to generate all combinations out of a given generic array. 2 | package combinations 3 | 4 | import "math/bits" 5 | 6 | // All returns all combinations for a given generic array. 7 | // This is essentially a powerset of the given set except that the empty set is disregarded. 8 | func All[T any](set []T) (subsets [][]T) { 9 | length := uint(len(set)) 10 | 11 | // Go through all possible combinations of objects 12 | // from 1 (only first object in subset) to 2^length (all objects in subset) 13 | for subsetBits := 1; subsetBits < (1 << length); subsetBits++ { 14 | var subset []T 15 | 16 | for object := uint(0); object < length; object++ { 17 | // checks if object is contained in subset 18 | // by checking if bit 'object' is set in subsetBits 19 | if (subsetBits>>object)&1 == 1 { 20 | // add object to subset 21 | subset = append(subset, set[object]) 22 | } 23 | } 24 | // add subset to subsets 25 | subsets = append(subsets, subset) 26 | } 27 | return subsets 28 | } 29 | 30 | // AllRepeat returns all combinations with repetitions for a given slice, 31 | // from 1 up to a maximum combination length of m. 32 | func AllRepeat[T any](set []T, m int) (subsets [][]T) { 33 | if m < 1 { 34 | return nil 35 | } 36 | 37 | var generateCombos func([]T, int) 38 | generateCombos = func(current []T, depth int) { 39 | if depth == 0 { 40 | subset := make([]T, len(current)) 41 | copy(subset, current) 42 | subsets = append(subsets, subset) 43 | return 44 | } 45 | 46 | for _, item := range set { 47 | generateCombos(append(current, item), depth-1) 48 | } 49 | } 50 | 51 | for length := 1; length <= m; length++ { 52 | generateCombos([]T{}, length) 53 | } 54 | 55 | return subsets 56 | } 57 | 58 | // Combinations returns combinations of n elements for a given generic array. 59 | // For n < 1, it equals to All and returns all combinations. 60 | func Combinations[T any](set []T, n int) (subsets [][]T) { 61 | length := uint(len(set)) 62 | 63 | if n > len(set) { 64 | n = len(set) 65 | } 66 | 67 | // Go through all possible combinations of objects 68 | // from 1 (only first object in subset) to 2^length (all objects in subset) 69 | for subsetBits := 1; subsetBits < (1 << length); subsetBits++ { 70 | if n > 0 && bits.OnesCount(uint(subsetBits)) != n { 71 | continue 72 | } 73 | 74 | var subset []T 75 | 76 | for object := uint(0); object < length; object++ { 77 | // checks if object is contained in subset 78 | // by checking if bit 'object' is set in subsetBits 79 | if (subsetBits>>object)&1 == 1 { 80 | // add object to subset 81 | subset = append(subset, set[object]) 82 | } 83 | } 84 | // add subset to subsets 85 | subsets = append(subsets, subset) 86 | } 87 | return subsets 88 | } 89 | -------------------------------------------------------------------------------- /combinations_test.go: -------------------------------------------------------------------------------- 1 | package combinations 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestStringCombinations(t *testing.T) { 10 | tt := []struct { 11 | name string 12 | in []string 13 | out [][]string 14 | }{ 15 | { 16 | name: "Empty slice", 17 | in: []string{}, 18 | out: nil, 19 | }, 20 | { 21 | name: "Single item", 22 | in: []string{"A"}, 23 | out: [][]string{ 24 | {"A"}, 25 | }, 26 | }, 27 | { 28 | name: "Two items", 29 | in: []string{"A", "B"}, 30 | out: [][]string{ 31 | {"A"}, 32 | {"B"}, 33 | {"A", "B"}, 34 | }, 35 | }, 36 | { 37 | name: "Three items", 38 | in: []string{"A", "B", "C"}, 39 | out: [][]string{ 40 | {"A"}, 41 | {"B"}, 42 | {"A", "B"}, 43 | {"C"}, 44 | {"A", "C"}, 45 | {"B", "C"}, 46 | {"A", "B", "C"}, 47 | }, 48 | }, 49 | { 50 | name: "Four items", 51 | in: []string{"A", "B", "C", "D"}, 52 | out: [][]string{ 53 | {"A"}, 54 | {"B"}, 55 | {"A", "B"}, 56 | {"C"}, 57 | {"A", "C"}, 58 | {"B", "C"}, 59 | {"A", "B", "C"}, 60 | {"D"}, 61 | {"A", "D"}, 62 | {"B", "D"}, 63 | {"A", "B", "D"}, 64 | {"C", "D"}, 65 | {"A", "C", "D"}, 66 | {"B", "C", "D"}, 67 | {"A", "B", "C", "D"}, 68 | }, 69 | }, 70 | } 71 | for _, tc := range tt { 72 | t.Run(tc.name, func(t *testing.T) { 73 | out := All(tc.in) 74 | if !reflect.DeepEqual(out, tc.out) { 75 | t.Errorf("error: \nreturn:\t%v\nwant:\t%v", out, tc.out) 76 | } 77 | }) 78 | } 79 | } 80 | 81 | func TestIntegerCombinations(t *testing.T) { 82 | tt := []struct { 83 | name string 84 | in []int 85 | out [][]int 86 | }{ 87 | { 88 | name: "Empty slice", 89 | in: []int{}, 90 | out: nil, 91 | }, 92 | { 93 | name: "Single item", 94 | in: []int{1}, 95 | out: [][]int{ 96 | {1}, 97 | }, 98 | }, 99 | { 100 | name: "Two items", 101 | in: []int{1, 2}, 102 | out: [][]int{ 103 | {1}, 104 | {2}, 105 | {1, 2}, 106 | }, 107 | }, 108 | { 109 | name: "Three items", 110 | in: []int{1, 2, 3}, 111 | out: [][]int{ 112 | {1}, 113 | {2}, 114 | {1, 2}, 115 | {3}, 116 | {1, 3}, 117 | {2, 3}, 118 | {1, 2, 3}, 119 | }, 120 | }, 121 | { 122 | name: "Four items", 123 | in: []int{1, 2, 3, 4}, 124 | out: [][]int{ 125 | {1}, 126 | {2}, 127 | {1, 2}, 128 | {3}, 129 | {1, 3}, 130 | {2, 3}, 131 | {1, 2, 3}, 132 | {4}, 133 | {1, 4}, 134 | {2, 4}, 135 | {1, 2, 4}, 136 | {3, 4}, 137 | {1, 3, 4}, 138 | {2, 3, 4}, 139 | {1, 2, 3, 4}, 140 | }, 141 | }, 142 | } 143 | for _, tc := range tt { 144 | t.Run(tc.name, func(t *testing.T) { 145 | out := All(tc.in) 146 | if !reflect.DeepEqual(out, tc.out) { 147 | t.Errorf("error: \nreturn:\t%v\nwant:\t%v", out, tc.out) 148 | } 149 | }) 150 | } 151 | } 152 | 153 | func ExampleAll() { 154 | combinations := All([]string{"A", "B", "C"}) 155 | fmt.Println(combinations) 156 | // Output: 157 | // [[A] [B] [A B] [C] [A C] [B C] [A B C]] 158 | } 159 | 160 | func TestStringCombinationsN(t *testing.T) { 161 | tt := []struct { 162 | name string 163 | in []string 164 | n int 165 | out [][]string 166 | }{ 167 | { 168 | name: "Empty slice", 169 | in: []string{}, 170 | n: 1, 171 | out: nil, 172 | }, 173 | { 174 | name: "Single item", 175 | in: []string{"A"}, 176 | n: 1, 177 | out: [][]string{ 178 | {"A"}, 179 | }, 180 | }, 181 | { 182 | name: "Two items, n = 0", 183 | in: []string{"A", "B"}, 184 | n: 0, 185 | out: [][]string{ 186 | {"A"}, 187 | {"B"}, 188 | {"A", "B"}, 189 | }, 190 | }, 191 | { 192 | name: "Two items, n = 1", 193 | in: []string{"A", "B"}, 194 | n: 1, 195 | out: [][]string{ 196 | {"A"}, 197 | {"B"}, 198 | }, 199 | }, { 200 | name: "Two items, n = 2", 201 | in: []string{"A", "B"}, 202 | n: 2, 203 | out: [][]string{ 204 | {"A", "B"}, 205 | }, 206 | }, 207 | { 208 | name: "Three items, n = 0", 209 | in: []string{"A", "B", "C"}, 210 | n: 0, 211 | out: [][]string{ 212 | {"A"}, 213 | {"B"}, 214 | {"A", "B"}, 215 | {"C"}, 216 | {"A", "C"}, 217 | {"B", "C"}, 218 | {"A", "B", "C"}, 219 | }, 220 | }, 221 | { 222 | name: "Three items, n = 1", 223 | in: []string{"A", "B", "C"}, 224 | n: 1, 225 | out: [][]string{ 226 | {"A"}, 227 | {"B"}, 228 | {"C"}, 229 | }, 230 | }, 231 | { 232 | name: "Three items, n = 2", 233 | in: []string{"A", "B", "C"}, 234 | n: 2, 235 | out: [][]string{ 236 | {"A", "B"}, 237 | {"A", "C"}, 238 | {"B", "C"}, 239 | }, 240 | }, 241 | { 242 | name: "Three items, n = 3", 243 | in: []string{"A", "B", "C"}, 244 | n: 3, 245 | out: [][]string{ 246 | {"A", "B", "C"}, 247 | }, 248 | }, 249 | { 250 | name: "Three items, n = 4", 251 | in: []string{"A", "B", "C"}, 252 | n: 4, 253 | out: [][]string{ 254 | {"A", "B", "C"}, 255 | }, 256 | }, 257 | } 258 | for _, tc := range tt { 259 | t.Run(tc.name, func(t *testing.T) { 260 | out := Combinations(tc.in, tc.n) 261 | if !reflect.DeepEqual(out, tc.out) { 262 | t.Errorf("error: \nreturn:\t%v\nwant:\t%v", out, tc.out) 263 | } 264 | }) 265 | } 266 | } 267 | 268 | func TestAllWithRepetitions(t *testing.T) { 269 | tt := []struct { 270 | name string 271 | in []string 272 | m int 273 | out [][]string 274 | }{ 275 | { 276 | name: "Empty slice", 277 | in: []string{}, 278 | m: 1, 279 | out: nil, 280 | }, 281 | { 282 | name: "Single item, m = 1", 283 | in: []string{"A"}, 284 | m: 1, 285 | out: [][]string{ 286 | {"A"}, 287 | }, 288 | }, 289 | { 290 | name: "Single item, m = 2", 291 | in: []string{"A"}, 292 | m: 2, 293 | out: [][]string{ 294 | {"A"}, 295 | {"A", "A"}, 296 | }, 297 | }, 298 | { 299 | name: "Two items, m = 1", 300 | in: []string{"A", "B"}, 301 | m: 1, 302 | out: [][]string{ 303 | {"A"}, 304 | {"B"}, 305 | }, 306 | }, 307 | { 308 | name: "Two items, m = 2", 309 | in: []string{"A", "B"}, 310 | m: 2, 311 | out: [][]string{ 312 | {"A"}, 313 | {"B"}, 314 | {"A", "A"}, 315 | {"A", "B"}, 316 | {"B", "A"}, 317 | {"B", "B"}, 318 | }, 319 | }, 320 | { 321 | name: "Three items, m = 2", 322 | in: []string{"A", "B", "C"}, 323 | m: 2, 324 | out: [][]string{ 325 | {"A"}, 326 | {"B"}, 327 | {"C"}, 328 | {"A", "A"}, 329 | {"A", "B"}, 330 | {"A", "C"}, 331 | {"B", "A"}, 332 | {"B", "B"}, 333 | {"B", "C"}, 334 | {"C", "A"}, 335 | {"C", "B"}, 336 | {"C", "C"}, 337 | }, 338 | }, 339 | { 340 | name: "Two items, m = 3", 341 | in: []string{"A", "B"}, 342 | m: 3, 343 | out: [][]string{ 344 | {"A"}, 345 | {"B"}, 346 | {"A", "A"}, 347 | {"A", "B"}, 348 | {"B", "A"}, 349 | {"B", "B"}, 350 | {"A", "A", "A"}, 351 | {"A", "A", "B"}, 352 | {"A", "B", "A"}, 353 | {"A", "B", "B"}, 354 | {"B", "A", "A"}, 355 | {"B", "A", "B"}, 356 | {"B", "B", "A"}, 357 | {"B", "B", "B"}, 358 | }, 359 | }, 360 | } 361 | 362 | for _, tc := range tt { 363 | t.Run(tc.name, func(t *testing.T) { 364 | out := AllRepeat(tc.in, tc.m) 365 | if !reflect.DeepEqual(out, tc.out) { 366 | t.Errorf("error: \nreturn:\t%v\nwant:\t%v", out, tc.out) 367 | } 368 | }) 369 | } 370 | } 371 | 372 | func TestAllWithRepetitionsInt(t *testing.T) { 373 | tt := []struct { 374 | name string 375 | in []int 376 | m int 377 | out [][]int 378 | }{ 379 | { 380 | name: "Two items, m = 0", 381 | in: []int{1, 2}, 382 | m: 0, 383 | out: nil, 384 | }, 385 | { 386 | name: "Empty slice", 387 | in: []int{}, 388 | m: 1, 389 | out: nil, 390 | }, 391 | { 392 | name: "Single item, m = 1", 393 | in: []int{1}, 394 | m: 1, 395 | out: [][]int{ 396 | {1}, 397 | }, 398 | }, 399 | { 400 | name: "Single item, m = 2", 401 | in: []int{1}, 402 | m: 2, 403 | out: [][]int{ 404 | {1}, 405 | {1, 1}, 406 | }, 407 | }, 408 | { 409 | name: "Two items, m = 2", 410 | in: []int{1, 2}, 411 | m: 2, 412 | out: [][]int{ 413 | {1}, 414 | {2}, 415 | {1, 1}, 416 | {1, 2}, 417 | {2, 1}, 418 | {2, 2}, 419 | }, 420 | }, 421 | { 422 | name: "Three items, m = 1", 423 | in: []int{1, 2, 3}, 424 | m: 1, 425 | out: [][]int{ 426 | {1}, 427 | {2}, 428 | {3}, 429 | }, 430 | }, 431 | { 432 | name: "Three items, m = 2", 433 | in: []int{1, 2, 3}, 434 | m: 2, 435 | out: [][]int{ 436 | {1}, 437 | {2}, 438 | {3}, 439 | {1, 1}, 440 | {1, 2}, 441 | {1, 3}, 442 | {2, 1}, 443 | {2, 2}, 444 | {2, 3}, 445 | {3, 1}, 446 | {3, 2}, 447 | {3, 3}, 448 | }, 449 | }, 450 | { 451 | name: "Two items, m = 3", 452 | in: []int{1, 2}, 453 | m: 3, 454 | out: [][]int{ 455 | {1}, 456 | {2}, 457 | {1, 1}, 458 | {1, 2}, 459 | {2, 1}, 460 | {2, 2}, 461 | {1, 1, 1}, 462 | {1, 1, 2}, 463 | {1, 2, 1}, 464 | {1, 2, 2}, 465 | {2, 1, 1}, 466 | {2, 1, 2}, 467 | {2, 2, 1}, 468 | {2, 2, 2}, 469 | }, 470 | }, 471 | } 472 | 473 | for _, tc := range tt { 474 | t.Run(tc.name, func(t *testing.T) { 475 | out := AllRepeat(tc.in, tc.m) 476 | if !reflect.DeepEqual(out, tc.out) { 477 | t.Errorf("error: \nreturn:\t%v\nwant:\t%v", out, tc.out) 478 | } 479 | }) 480 | } 481 | } 482 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mxschmitt/golang-combinations 2 | 3 | go 1.18 4 | --------------------------------------------------------------------------------