From b832c40e544f721157c63dbe93766398851f3184 Mon Sep 17 00:00:00 2001 From: Aleksey Vasenev Date: Fri, 15 Dec 2023 15:37:49 +0300 Subject: [PATCH] Improved matchmaking performance The order of combinations has changed slightly. Now the result is sorted according to the sorting of the input data. --- go.mod | 2 + go.sum | 4 + server/matchmaker_process.go | 59 +- vendor/github.com/mowshon/iterium/.gitignore | 15 + vendor/github.com/mowshon/iterium/LICENSE | 21 + vendor/github.com/mowshon/iterium/README.md | 709 ++++++++++++++++++ .../github.com/mowshon/iterium/accumulate.go | 34 + .../mowshon/iterium/combinations.go | 71 ++ .../iterium/combinations_with_replacement.go | 54 ++ vendor/github.com/mowshon/iterium/count.go | 22 + vendor/github.com/mowshon/iterium/cycle.go | 35 + .../github.com/mowshon/iterium/dropwhile.go | 39 + vendor/github.com/mowshon/iterium/filter.go | 27 + .../github.com/mowshon/iterium/filterfalse.go | 27 + .../github.com/mowshon/iterium/firstfalse.go | 26 + .../github.com/mowshon/iterium/firsttrue.go | 26 + vendor/github.com/mowshon/iterium/iterator.go | 92 +++ vendor/github.com/mowshon/iterium/iternium.go | 74 ++ vendor/github.com/mowshon/iterium/map.go | 21 + .../mowshon/iterium/permutations.go | 69 ++ vendor/github.com/mowshon/iterium/product.go | 52 ++ vendor/github.com/mowshon/iterium/range.go | 51 ++ vendor/github.com/mowshon/iterium/repeat.go | 30 + vendor/github.com/mowshon/iterium/starmap.go | 24 + vendor/github.com/mowshon/iterium/string.go | 65 ++ .../github.com/mowshon/iterium/takewhile.go | 33 + vendor/golang.org/x/exp/LICENSE | 27 + vendor/golang.org/x/exp/PATENTS | 22 + .../x/exp/constraints/constraints.go | 50 ++ vendor/modules.txt | 6 + 30 files changed, 1768 insertions(+), 19 deletions(-) create mode 100644 vendor/github.com/mowshon/iterium/.gitignore create mode 100644 vendor/github.com/mowshon/iterium/LICENSE create mode 100644 vendor/github.com/mowshon/iterium/README.md create mode 100644 vendor/github.com/mowshon/iterium/accumulate.go create mode 100644 vendor/github.com/mowshon/iterium/combinations.go create mode 100644 vendor/github.com/mowshon/iterium/combinations_with_replacement.go create mode 100644 vendor/github.com/mowshon/iterium/count.go create mode 100644 vendor/github.com/mowshon/iterium/cycle.go create mode 100644 vendor/github.com/mowshon/iterium/dropwhile.go create mode 100644 vendor/github.com/mowshon/iterium/filter.go create mode 100644 vendor/github.com/mowshon/iterium/filterfalse.go create mode 100644 vendor/github.com/mowshon/iterium/firstfalse.go create mode 100644 vendor/github.com/mowshon/iterium/firsttrue.go create mode 100644 vendor/github.com/mowshon/iterium/iterator.go create mode 100644 vendor/github.com/mowshon/iterium/iternium.go create mode 100644 vendor/github.com/mowshon/iterium/map.go create mode 100644 vendor/github.com/mowshon/iterium/permutations.go create mode 100644 vendor/github.com/mowshon/iterium/product.go create mode 100644 vendor/github.com/mowshon/iterium/range.go create mode 100644 vendor/github.com/mowshon/iterium/repeat.go create mode 100644 vendor/github.com/mowshon/iterium/starmap.go create mode 100644 vendor/github.com/mowshon/iterium/string.go create mode 100644 vendor/github.com/mowshon/iterium/takewhile.go create mode 100644 vendor/golang.org/x/exp/LICENSE create mode 100644 vendor/golang.org/x/exp/PATENTS create mode 100644 vendor/golang.org/x/exp/constraints/constraints.go diff --git a/go.mod b/go.mod index 174c9a645c..7ad05149b6 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa github.com/jackc/pgtype v1.14.0 github.com/jackc/pgx/v4 v4.18.1 + github.com/mowshon/iterium v1.0.0 github.com/prometheus/client_golang v1.17.0 github.com/rubenv/sql-migrate v1.5.2 github.com/stretchr/testify v1.8.4 @@ -74,6 +75,7 @@ require ( github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.11.1 // indirect go.uber.org/multierr v1.10.0 // indirect + golang.org/x/exp v0.0.0-20230307190834-24139beb5833 // indirect golang.org/x/net v0.19.0 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect diff --git a/go.sum b/go.sum index 82e21f1fb5..8a99a6d864 100644 --- a/go.sum +++ b/go.sum @@ -269,6 +269,8 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mowshon/iterium v1.0.0 h1:04pku9dTNnfVvshf+DQIV3E92T/EwmnXKtXMkmZ3+5Q= +github.com/mowshon/iterium v1.0.0/go.mod h1:Bnchn9HAYNQ/7MLUwKDyvjduSuVJQQwrNxmXJDqpXg4= github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -394,6 +396,8 @@ golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20230307190834-24139beb5833 h1:SChBja7BCQewoTAU7IgvucQKMIXrEpFxNMs0spT3/5s= +golang.org/x/exp v0.0.0-20230307190834-24139beb5833/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= diff --git a/server/matchmaker_process.go b/server/matchmaker_process.go index 4138fa6e7f..a947606903 100644 --- a/server/matchmaker_process.go +++ b/server/matchmaker_process.go @@ -16,11 +16,11 @@ package server import ( "math" - "math/bits" "sort" "time" "github.com/blugelabs/bluge" + "github.com/mowshon/iterium" "go.uber.org/zap" ) @@ -595,32 +595,53 @@ func (m *LocalMatchmaker) processCustom(activeIndexesCopy map[string]*Matchmaker return matchedEntries, expiredActiveIndexes } +func rangeCombinations[T comparable](symbols []T, max int) <-chan []T { + type combinations[T any] struct { + iter iterium.Iter[[]T] + value []T + } + + c := make(chan []T) + go func() { + defer close(c) + if len(symbols) > 0 && max > 0 { + allCombinations := make([]combinations[T], max-1) + for i := 0; i < max-1; i++ { + iter := iterium.Combinations(symbols, i+2) + value, _ := iter.Next() + allCombinations[i] = combinations[T]{ + iter: iter, + value: value, + } + } + for _, symbol := range symbols { + c <- []T{symbol} + for i := range allCombinations { + combinations := &allCombinations[i] + for combinations.value != nil && combinations.value[0] == symbol { + c <- combinations.value + combinations.value, _ = combinations.iter.Next() + } + } + } + } + }() + return c +} + func combineIndexes(from []*MatchmakerIndex, min, max int) <-chan []*MatchmakerIndex { c := make(chan []*MatchmakerIndex) go func() { defer close(c) - length := uint(len(from)) - // Go through all possible combinations of from 1 (only first element in subset) to 2^length (all objects in subset) - // and return those that contain between min and max elements. combination: - for combinationBits := 1; combinationBits < (1 << length); combinationBits++ { - count := bits.OnesCount(uint(combinationBits)) - if count > max { - continue - } - - combination := make([]*MatchmakerIndex, 0, count) + for combination := range rangeCombinations(from, max) { entryCount := 0 - for element := uint(0); element < length; element++ { - // Check if element should be contained in combination by checking if bit 'element' is set in combinationBits. - if (combinationBits>>element)&1 == 1 { - entryCount = entryCount + from[element].Count - if entryCount > max { - continue combination - } - combination = append(combination, from[element]) + for _, element := range combination { + entryCount = entryCount + element.Count + if entryCount > max { + continue combination } } if entryCount >= min { diff --git a/vendor/github.com/mowshon/iterium/.gitignore b/vendor/github.com/mowshon/iterium/.gitignore new file mode 100644 index 0000000000..66fd13c903 --- /dev/null +++ b/vendor/github.com/mowshon/iterium/.gitignore @@ -0,0 +1,15 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ diff --git a/vendor/github.com/mowshon/iterium/LICENSE b/vendor/github.com/mowshon/iterium/LICENSE new file mode 100644 index 0000000000..a39abc9708 --- /dev/null +++ b/vendor/github.com/mowshon/iterium/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Student B. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/mowshon/iterium/README.md b/vendor/github.com/mowshon/iterium/README.md new file mode 100644 index 0000000000..e17769624a --- /dev/null +++ b/vendor/github.com/mowshon/iterium/README.md @@ -0,0 +1,709 @@ +

🚀 Iterium - Generic Channel-based Iterators

+ +The **Iterium** package is a powerful toolkit for creating and manipulating generic iterators in Golang. Inspired by the popular Python **itertools** library, Iterium provides a variety of functions for working with iterators in different ways. + +Iterium is designed to be easy to use and flexible, with a clear and concise API that enables you to create iterators that meet your specific needs. Whether you're working with strings, arrays, slices, or any other data type. **Iterium** makes it easy to traverse, filter, and manipulate your data with ease. + +--------- +### Contents +- [Decrypting the MD5 hash in Golang](https://github.com/mowshon/iterium#user-content-md5) + - [Benchmark](https://github.com/mowshon/iterium#user-content-benchmark) +- [Iterator architecture](https://github.com/mowshon/iterium#user-content-structure) +- [Creating an Iterator](https://github.com/mowshon/iterium#user-content-new) +- [Getting data from an iterator](https://github.com/mowshon/iterium#user-content-get) +- [Combinatoric iterators](https://github.com/mowshon/iterium#user-content-combinatorics) + - 🟢 [Product() - Cartesian Product](https://github.com/mowshon/iterium#user-content-product) + - 🟢 [Permutations()](https://github.com/mowshon/iterium#user-content-permutations) + - 🟢 [Combinations()](https://github.com/mowshon/iterium#user-content-combinations) + - 🟢 [CombinationsWithReplacement()](https://github.com/mowshon/iterium#user-content-combinations-with-replacement) +- [Infinite iterators](https://github.com/mowshon/iterium#user-content-infinite) + - 🔴 [Count()](https://github.com/mowshon/iterium#user-content-count) + - 🔴 [Cycle()](https://github.com/mowshon/iterium#user-content-cycle) + - 🔴 [Repeat()](https://github.com/mowshon/iterium#user-content-repeat) +- [Finite iterators](https://github.com/mowshon/iterium#user-content-finite) + - 🔵 [Range()](https://github.com/mowshon/iterium#user-content-range) + - 🔵 [Map()](https://github.com/mowshon/iterium#user-content-map) + - 🔵 [StarMap()](https://github.com/mowshon/iterium#user-content-starmap) + - 🔵 [Filter()](https://github.com/mowshon/iterium#user-content-filter) + - 🔵 [FilterFalse()](https://github.com/mowshon/iterium#user-content-filter-false) + - 🔵 [Accumulate()](https://github.com/mowshon/iterium#user-content-accumulate) + - 🔵 [TakeWhile()](https://github.com/mowshon/iterium#user-content-take-while) + - 🔵 [DropWhile()](https://github.com/mowshon/iterium#user-content-drop-while) +- [Create your own iterator](https://github.com/mowshon/iterium#user-content-custom) +--------- + +**Iterium** provides a powerful set of tools for fast and easy data processing and transformations. + +

Decrypting the MD5 hash in Golang

+ +Before we move on to explore each iterator in particular, let me give you a small example of **decrypting an md5 hash in a few lines of code** using **Iterium**. Assume that our password consists only of **lower-case Latin letters** and we don't know exactly its length, but assume no more than 6 characters. + +```golang +// result of md5("qwerty") = d8578edf8458ce06fbc5bb76a58c5ca4 +passHash := "d8578edf8458ce06fbc5bb76a58c5ca4" + +for passLength := range Range(1, 7).Chan() { + fmt.Println("Password Length:", passLength) + + // Merge a slide into a string. + // []string{"a", "b", "c"} => "abc" + join := func(product []string) string { + return strings.Join(product, "") + } + + // Check the hash of a raw password with an unknown hash. + sameHash := func(rawPassword string) bool { + hash := md5.Sum([]byte(rawPassword)) + return hex.EncodeToString(hash[:]) == passHash + } + + // Combine iterators to achieve the goal... + decrypt := FirstTrue(Map(Product(AsciiLowercase, passLength), join), sameHash) + + if result, err := decrypt.Next(); err == nil { + fmt.Println("Raw password:", result) + break + } +} +``` + +Output: + +``` +Raw password: qwerty +``` + +Let's look at what's going on here. The main thing we are interested in is the line: + +```golang +decrypt := FirstTrue(Map(Product(ascii, passLength), join), sameHash) +``` + +- Initially the `Product` iterator **generates all possible combinations** of Latin letters from a certain length, and returns a slice like `[]string{"p", "a", "s", "s"}`. [AsciiLowercase](https://github.com/mowshon/iterium/blob/main/string.go) is a slice of all lowercase Latin letters.; +- Sending `Product` iterator to `Map` iterator which will use a closure-function to merge the slice into a string, like `[]string{"a", "b"} => "ab"`; +- Sending the obtained iterator from `Map` to the `FirstTrue` iterator, which returns the first value from `Map` that returned **true** after applying the `sameHash()` function to it; +- The `sameHash()` function turns the received string from the `Map` iterator into an md5 hash and checks if it matches with the unknown hash. + +

Benchmark ⏰

+ +One of the special features of this package (compared to the python module) is the ability to **know the exact number of combinations** before running the process. + +```golang +Product([]string{"a", "b", "c", "d"}, 10).Count() # 1048576 possible combinations +``` + +#### 🔑 How many total combinations of possible passwords did it take to crack a 6-character md5 hash? + +``` +Password Length: 1, total combinations: 26 +Password Length: 2, total combinations: 676 +Password Length: 3, total combinations: 17576 +Password Length: 4, total combinations: 456976 +Password Length: 5, total combinations: 11881376 +Password Length: 6, total combinations: 308915776 +``` + +``` +goos: linux +goarch: amd64 +pkg: github.com/mowshon/iterium +cpu: AMD Ryzen 5 3600 6-Core Processor +BenchmarkDecryptMD5Hash + +Raw password: qwerty +BenchmarkDecryptMD5Hash-12 1 254100234180 ns/op +``` + +The hash was cracked in `4.23` minutes. This is just using the capabilities of the iterium package. + +

Iterator architecture

+ +Each iterator corresponds to the following interface: + +```golang +// Iter is the iterator interface with all the necessary methods. +type Iter[T any] interface { + IsInfinite() bool + SetInfinite(bool) + Next() (T, error) + Chan() chan T + Close() + Slice() ([]T, error) + Count() int64 +} +``` +Description of the methods: +- `IsInfinite()` returns the iterator infinite state; +- `SetInfinite()` update the infinity state of the iterator; +- `Chan()` returns the iterator channel; +- `Next()` returns the next value or error from the iterator channel; +- `Close()` closes the iterator channel; +- `Count()` returns the number of possible values the iterator can return; +- `Slice()` turns the iterator into a slice of values; + +

Creating an Iterator

+ +You can use the function `iterium.New(1, 2, 3)` or `iterium.New("a", "b", "c")` to create a new iterator. + +```golang +package main + +import ( + "github.com/mowshon/iterium" +) + +type Store struct { + price float64 +} + +func main() { + iterOfInt := iterium.New(1, 2, 3) + iterOfString := iterium.New("A", "B", "C") + iterOfStruct := iterium.New(Store{10.5}, Store{5.1}, Store{0.15}) + iterOfFunc := iterium.New( + func(x int) int {return x + 1}, + func(y int) int {return y * 2}, + func(z int) int {return z / 3}, + ) +} +``` + +

Getting data from an iterator

+ +There are two ways to retrieve data from an iterator. The first way is to use the `Next()` method or read values from the iterator channel `range iter.Chan()`. + +Using the `Next()` method: +```golang +func main() { + iterOfInt := iterium.New(1, 2, 3) + + for { + value, err := iterOfInt.Next() + if err != nil { + break + } + + fmt.Println(value) + } +} +``` + +Reading from the channel: + +```golang +func main() { + iterOfInt := iterium.New(1, 2, 3) + + for value := range iterOfInt.Chan() { + fmt.Println(value) + } +} +``` + +

Combinatoric iterators

+ +Combinatoric iterators are a powerful tool for solving problems that involve generating all possible combinations or permutations of a slice of elements, and are widely used in a range of fields and applications. + +

🟢 iterium.Product([]T, length) - Cartesian Product

+ +The iterator generates a **Cartesian product** depending on the submitted slice of values and the required length. The **Cartesian product** is a mathematical concept that refers to the set of all possible ordered pairs formed by taking one element from each of two sets. + +In the case of `iterium.Product()`, the Cartesian product is formed by taking one element from each of the input slice. + +```golang +product := iterium.Product([]string{"A", "B", "C", "D"}, 2) +toSlice, _ := product.Slice() + +fmt.Println("Total:", product.Count()) +fmt.Println(toSlice) +``` + +Output: + +``` +Total: 16 + +[ + [A, A] [A, B] [A, C] [A, D] [B, A] [B, B] [B, C] [B, D] + [C, A] [C, B] [C, C] [C, D] [D, A] [D, B] [D, C] [D, D] +] +``` + +

🟢 iterium.Permutations([]T, length)

+ +`Permutations()` returns an iterator that generates all possible permutations of a given slice. A permutation is an arrangement of elements in a specific order, where each arrangement is different from all others. + +```golang +permutations := iterium.Permutations([]string{"A", "B", "C", "D"}, 2) +toSlice, _ := permutations.Slice() + +fmt.Println("Total:", permutations.Count()) +fmt.Println(toSlice) +``` + +Result: + +``` +Total: 12 + +[ + [A, B] [A, C] [A, D] [B, A] [B, C] [B, D] + [C, B] [C, A] [C, D] [D, B] [D, C] [D, A] +] +``` + +

🟢 iterium.Combinations([]T, length)

+ +`Combinations()` returns an iterator that generates all possible combinations of a given length from a given slice. A combination is a selection of items from a slice, such that the order in which the items are selected does not matter. + +```golang +combinations := iterium.Combinations([]string{"A", "B", "C", "D"}, 2) +toSlice, _ := combinations.Slice() + +fmt.Println("Total:", combinations.Count()) +fmt.Println(toSlice) +``` + +Output: + +``` +Total: 6 + +[ + [A, B] [A, C] [A, D] [B, C] [B, D] [C, D] +] +``` + +

🟢 iterium.CombinationsWithReplacement([]T, length)

+ +`CombinationsWithReplacement()` generates all possible combinations of a given slice, **including the repeated elements**. + +```golang +result := iterium.CombinationsWithReplacement([]string{"A", "B", "C", "D"}, 2) +toSlice, _ := result.Slice() + +fmt.Println("Total:", result.Count()) +fmt.Println(toSlice) +``` + +Output: + +``` +Total: 10 + +[ + [A, A] [A, B] [A, C] [A, D] [B, B] + [B, C] [B, D] [C, C] [C, D] [D, D] +] +``` + +

Infinite iterators

+ +Infinite iterators are a type of iterator that generate an **endless sequence of values**, without ever reaching an endpoint. Unlike finite iterators, which generate a fixed number of values based on the size of a given iterable data structure, infinite iterators continue to generate values indefinitely, until they are stopped or interrupted. + +

🔴 iterium.Count(start, step)

+ +`Count()` returns an iterator that generates an infinite stream of values, starting from a specified number and incrementing by a specified step. + +```golang +stream := iterium.Count(0, 3) + +// Retrieve the first 5 values from the iterator. +for i := 0; i <= 5; i++ { + value, err := stream.Next() + if err != nil { + break + } + + fmt.Println(value) +} + +stream.Close() +``` + +Output: + +``` +0, 3, 6, 9, 12, 15 +``` + +

🔴 iterium.Cycle(Iterator)

+ +`Cycle()` returns an iterator that cycles endlessly through an iterator. Note that since `iterium.Cycle()` generates an infinite stream of values, you should be careful not to use it in situations where you do not want to generate an infinite loop. Also, if the iterator passed to `Cycle()` is empty, the iterator will not generate any values **and will immediately close the channel**. + +```golang +cycle := iterium.Cycle(iterium.Range(3)) + +for i := 0; i <= 11; i++ { + value, err := cycle.Next() + if err != nil { + break + } + + fmt.Print(value, ", ") +} +``` + +Output: + +``` +0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2 +``` + +

🔴 iterium.Repeat(value, n)

+ +`Repeat()` returns an iterator that repeats a specified value **infinitely** `n = -1` or a **specified number of times** `n = 50`. + +Here's an example code snippet that demonstrates how to use `iterium.Repeat()`: + +```golang +type User struct { + Username string +} + +func main() { + // To receive an infinite iterator, you + // need to specify a length of -1 + users := iterium.Repeat(User{"mowshon"}, 3) + slice, _ := users.Slice() + + fmt.Println(slice) + fmt.Println(slice[1].Username) +} +``` + +Output: + +``` +[ User{mowshon}, User{mowshon}, User{mowshon} ] +mowshon +``` + +

Finite iterators

+ +Finite iterators return iterators that terminate as soon as any of the input sequences they iterate over are exhausted. + +

🔵 iterium.Range(start, stop, step)

+ +`Range()` generates a sequence of numbers. It takes up to three arguments: + +```golang +iterium.Range(end) # starts from 0 to the end with step = +1 +iterium.Range(start, end) # step is +1 +iterium.Range(start, end, step) +``` + +- `start`: (optional) Starting number of the sequence. Defaults to 0 if not provided. +- `stop`: (required) Ending number of the sequence. +- `step`: (optional) Step size of the sequence. Defaults to 1 if not provided. + +Here's an example code snippet that demonstrates how to use `iterium.Range()`: + +```golang +first, _ := iterium.Range(5).Slice() +second, _ := iterium.Range(-5).Slice() +third, _ := iterium.Range(0, 10, 2).Slice() +float, _ := iterium.Range(0.0, 10.0, 1.5).Slice() + +fmt.Println(first) +fmt.Println(second) +fmt.Println(third) +fmt.Println(float) +``` + +Output: + +``` +first: [0, 1, 2, 3, 4] +second: [0, -1, -2, -3, -4] +third: [0, 2, 4, 6, 8] + +float: [0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0] +``` +#### Features 🔥 +- **Note** that compared to `range()` from Python, `Range()` from **Iterium** if it receives the first parameter below zero, the `step` automatically becomes `-1` and starts with `0`. In Python such parameters will return an empty array. +- **Also**, this iterator is more like `numpy.arange()` as it can handle the float type. + +

🔵 iterium.Map(iter, func)

+ +`Map()` is a function that takes two arguments, a function and another iterator, and returns a new iterator that applies the function to each element of the source iterator, producing the resulting values one at a time. + +### Calculating the Fibonacci Number with Iterium +Here is an example code snippet that demonstrates how to use `iterium.Map()` to apply a function to each element from another iterator: + +```golang +numbers := iterium.Range(30) +fibonacci := iterium.Map(numbers, func(n int) int { + f := make([]int, n+1, n+2) + if n < 2 { + f = f[0:2] + } + + f[0] = 0 + f[1] = 1 + + for i := 2; i <= n; i++ { + f[i] = f[i-1] + f[i-2] + } + + return f[n] +}) + +slice, _ := fibonacci.Slice() +fmt.Println(slice) +``` + +Output: + +``` +[ + 0 1 1 2 3 5 8 13 21 34 55 89 + 144 233 377 610 987 1597 2584 + 4181 6765 10946 17711 28657 46368 + 75025 121393 196418 317811 514229 +] +``` + +

🔵 iterium.StarMap(iter, func)

+ +`StarMap()` takes an iterator of slices and a function as input, and returns an iterator that applies the function to each slice in the iterator, unpacking the slices as function arguments. + +Here's an example code snippet that demonstrates how to use `iterium.StarMap()`: + +```golang +func pow(a, b float64) float64 { + return math.Pow(a, b) +} + +func main() { + values := iterium.New([]float64{2, 5}, []float64{3, 2}, []float64{10, 3}) + starmap := iterium.StarMap(values, pow) + + slice, _ := starmap.Slice() + fmt.Println(slice) +} +``` + +Output: + +``` +[32, 9, 1000] +``` + +**Note** that `iterium.StarMap()` is similar to `iterium.Map()`, but is used when the function to be applied expects two arguments, unlike `Map()` where the function only takes in a single argument. + +

🔵 iterium.Filter(iter, func)

+ +`Filter()` is used to filter out elements from an iterator based on a given condition. It returns a new iterator with only the elements that satisfy the condition. + +Here is an example of using the `iterium.Filter()` function to filter out even numbers from a list: + +```golang +func even(x int) bool { + return x % 2 == 0 +} + +func main() { + numbers := iterium.New(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + filter := iterium.Filter(numbers, even) + + slice, _ := filter.Slice() + fmt.Println(slice) +} +``` + +Output: + +``` +[2, 4, 6, 8, 10] +``` + +

🔵 iterium.FilterFalse(iter, func)

+ +`FilterFalse()` returns an iterator that contains only the elements from the input iterator for which the given function returns `False`. + +Here is an example of using the `iterium.FilterFalse()` function to filter out even numbers from a list: + +```golang +func even(x int) bool { + return x % 2 == 0 +} + +func main() { + numbers := iterium.New(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + filter := iterium.FilterFalse(numbers, even) + + slice, _ := filter.Slice() + fmt.Println(slice) +} +``` + +Output: + +``` +[1, 3, 5, 7, 9] +``` + +

🔵 iterium.Accumulate(iter, func)

+ +`Accumulate()` generates a sequence of accumulated values from an iterator. The function takes two arguments: the iterator and the function that defines how to combine the iterator elements. + +Here's an example: + +```golang +func sum(x, y int) int { + return x + y +} + +func main() { + numbers := iterium.New(1, 2, 3, 4, 5) + filter := iterium.Accumulate(numbers, sum) + + slice, _ := filter.Slice() + fmt.Println(slice) +} +``` + +In this example, `Accumulate()` generates an iterator that outputs the accumulated `sum` of elements from the iterator `numbers`. + +Output: + +``` +[1 3 6 10 15] +``` + +It also works fine with **strings**: + +```golang +func merge(first, second string) string { + return fmt.Sprintf("%s-%s", first, second) +} + +func main() { + letters := iterium.New("A", "B", "C", "D") + filter := iterium.Accumulate(letters, merge) + + slice, _ := filter.Slice() + fmt.Println(slice) +} +``` + +Output: + +``` +["A", "A-B", "A-B-C", "A-B-C-D"] +``` + +

🔵 iterium.TakeWhile(iter, func)

+ +`TakeWhile()` returns an iterator that generates elements from an iterator while a given predicate function holds `true`. Once the predicate function returns `false` for an element, `TakeWhile()` stops generating elements. + +The function takes two arguments: an iterator and a predicate function. The predicate function should take one argument and return a boolean value. + +Here's an example: + +```golang +func lessThenSix(x int) bool { + return x < 6 +} + +func main() { + numbers := iterium.New(1, 3, 5, 7, 9, 2, 4, 6, 8) + filter := iterium.TakeWhile(numbers, lessThenSix) + + slice, _ := filter.Slice() + fmt.Println(slice) +} +``` + +Output: + +``` +[1, 3, 5] +``` + +In this example, `TakeWhile()` generates an iterator that yields elements from the `numbers` iterator while they are less than 6. Once `TakeWhile()` encounters an element that does not satisfy the predicate (in this case, the number 7), it stops generating elements. + +Note that `TakeWhile()` does not apply the predicate function to all elements from the iterator, but only until the first element that fails the condition. In other words, `TakeWhile()` returns an iterator with values satisfying the condition up to a certain point. + +

🔵 iterium.DropWhile(iter, func)

+ +`DropWhile` returns an iterator that generates elements from an iterator after a given predicate function no longer holds `true`. Once the predicate function returns `false` for an element, `DropWhile` starts generating all the remaining elements. + +The function takes two arguments: an iterator and predicate function. The predicate function should take one argument and return a boolean value. + +Here's an example: + +```golang +func lessThenSix(x int) bool { + return x < 6 +} + +func main() { + numbers := iterium.New(1, 3, 5, 7, 9, 2, 4, 6, 8) + filter := iterium.DropWhile(numbers, lessThenSix) + + slice, _ := filter.Slice() + fmt.Println(slice) +} +``` + +Output: + +``` +[7, 9, 2, 4, 6, 8] +``` + +In this example, `DropWhile()` generates an iterator that yields elements from the `numbers` iterator after the first element that is greater than or equal to 6. + +**Note** that `DropWhile()` applies the predicate function to all elements from the iterator **until it finds the first element that fails the condition**. Once that happens, it starts generating all the remaining elements from the iterator, regardless of whether they satisfy the predicate function. + +`DropWhile()` is often used to skip over elements in an iterator that do not satisfy a certain condition, and start processing or generating elements once the condition is met. + +

Create your own iterator 🛠️

+ +You can create your own iterators for your unique tasks. Below is an example of how to do this: + +```golang +// CustomStuttering is a custom iterator that repeats +// elements from the iterator 3 times. +func CustomStuttering[T any](iterable iterium.Iter[T]) iterium.Iter[T] { + total := iterable.Count() * 3 + iter := iterium.Instance[T](total, false) + + go func() { + defer iter.Close() + + for { + // Here will be the logic of your iterator... + next, err := iterable.Next() + if err != nil { + return + } + + // Send each value from the iterator + // three times to a new channel. + iter.Chan() <- next + iter.Chan() <- next + iter.Chan() <- next + } + }() + + return iter +} + +func main() { + numbers := iterium.New(1, 2, 3) + custom := CustomStuttering(numbers) + + slice, _ := custom.Slice() + fmt.Println(slice) + fmt.Println("Total:", custom.Count()) +} +``` + +Output: + +``` +[1, 1, 1, 2, 2, 2, 3, 3, 3] +``` diff --git a/vendor/github.com/mowshon/iterium/accumulate.go b/vendor/github.com/mowshon/iterium/accumulate.go new file mode 100644 index 0000000000..ecdd824b54 --- /dev/null +++ b/vendor/github.com/mowshon/iterium/accumulate.go @@ -0,0 +1,34 @@ +package iterium + +// Accumulate returns an iterator that sends the accumulated +// result from the binary function to the channel. +func Accumulate[T any](iterable Iter[T], operator func(T, T) T) Iter[T] { + iter := Instance[T](iterable.Count(), iterable.IsInfinite()) + + go func() { + defer IterRecover() + defer iter.Close() + + var last T + var start bool + for true { + next, err := iterable.Next() + if err != nil { + return + } + + if !start { + iter.Chan() <- next + last = next + start = true + continue + } + + result := operator(last, next) + iter.Chan() <- result + last = result + } + }() + + return iter +} diff --git a/vendor/github.com/mowshon/iterium/combinations.go b/vendor/github.com/mowshon/iterium/combinations.go new file mode 100644 index 0000000000..3d47e78c3b --- /dev/null +++ b/vendor/github.com/mowshon/iterium/combinations.go @@ -0,0 +1,71 @@ +package iterium + +import "math" + +// CombinationsCount is a function that takes a positive integer `n` and a limit `k` as input +// and returns the total number of possible combinations of `k` elements from a set of `n` distinct elements. +// +// Formula: n! / (k! * (n - k)!) +func CombinationsCount(n, k int) int64 { + a, _ := math.Lgamma(float64(n) + 1) + b, _ := math.Lgamma(float64(k) + 1) + c, _ := math.Lgamma(float64(n-k) + 1) + return int64(math.Round(math.Exp(a - b - c))) +} + +// Combinations is a function that takes a slice of T and a +// limit as input and returns a slice of all possible combinations of +// the T in the input slice of the given limit. +func Combinations[T any](symbols []T, limit int) Iter[[]T] { + // If the length of the input slice is less than the desired limit, + // there are no valid combinations, so return an empty slice. + if len(symbols) < limit { + return Empty[[]T]() + } + + total := CombinationsCount(len(symbols), limit) + iter := Instance[[]T](total, false) + nums := placeHolders(len(symbols)) + + // Initialize a stack to hold the indices of the elements to be included + // in each combination. The stack is initialized with the index of each + // element in the input slice. + stack := make([][]int, 0, len(nums)) + for i := 0; i < len(nums); i++ { + stack = append(stack, []int{i}) + } + + go func() { + defer IterRecover() + defer iter.Close() + + // Loop over the stack until it is empty. + for len(stack) > 0 { + // Pop the first set of indices from the stack. + combIdxs := stack[0] + stack = stack[1:] + + // If the combination has the desired length, construct the combination + // from the corresponding elements of the input slice and insert it to + // the channel. + if len(combIdxs) == limit { + result := make([]T, limit) + replacePlaceholders[T](symbols, combIdxs, &result) + iter.Chan() <- result + continue + } + + // If the combination has fewer elements than the desired length, add + // all possible extensions to the stack. + lastIdx := combIdxs[len(combIdxs)-1] + for i := lastIdx + 1; i <= len(nums)-(limit-len(combIdxs)); i++ { + newCombIdxs := make([]int, len(combIdxs)) + copy(newCombIdxs, combIdxs) + newCombIdxs = append(newCombIdxs, i) + stack = append(stack, newCombIdxs) + } + } + }() + + return iter +} diff --git a/vendor/github.com/mowshon/iterium/combinations_with_replacement.go b/vendor/github.com/mowshon/iterium/combinations_with_replacement.go new file mode 100644 index 0000000000..782b079cf8 --- /dev/null +++ b/vendor/github.com/mowshon/iterium/combinations_with_replacement.go @@ -0,0 +1,54 @@ +package iterium + +import ( + "math/big" +) + +// CombinationsWithReplacementCount calculates the total number of combinations with replacement +// for a given set of n elements and a combination length of k. +func CombinationsWithReplacementCount(n, k int) int64 { + // The function uses the binomial coefficient formula to + // calculate the total number of combinations with replacement. + numerator := big.NewInt(1).Binomial(int64(n+k-1), int64(k)) + return numerator.Int64() +} + +// CombinationsWithReplacement generates all possible combinations with replacement +// of a given set of elements. +func CombinationsWithReplacement[T any](symbols []T, k int) Iter[[]T] { + arr := placeHolders(len(symbols)) + total := CombinationsWithReplacementCount(len(symbols), k) + iter := Instance[[]T](total, false) + + go func() { + defer IterRecover() + defer iter.Close() + + comb := make([]int, k) + for i := range comb { + comb[i] = -1 + } + + // Define a recursive function to generate combinations. + var generateCombination func(start, combIndex int) + generateCombination = func(start, combIndex int) { + if combIndex == k { + // When a combination is complete, send it to the channel. + result := make([]T, k) + replacePlaceholders[T](symbols, comb, &result) + iter.Chan() <- result + return + } + + for i := start; i < len(arr); i++ { + comb[combIndex] = arr[i] + // Recursively generate the rest of the combination. + generateCombination(i, combIndex+1) + } + } + + generateCombination(0, 0) + }() + + return iter +} diff --git a/vendor/github.com/mowshon/iterium/count.go b/vendor/github.com/mowshon/iterium/count.go new file mode 100644 index 0000000000..d4f79bb2cd --- /dev/null +++ b/vendor/github.com/mowshon/iterium/count.go @@ -0,0 +1,22 @@ +package iterium + +// Count returns an iterator in which each successive value +// will be added to the value from step. +func Count[N Number](args ...N) Iter[N] { + start, step, _ := argsTrio[N](args, 0, 1, 0) + + // Initialisation of a new channel. + iter := Instance[N](0, true) + + go func() { + defer IterRecover() + defer iter.Close() + + for { + iter.Chan() <- start + start = start + step + } + }() + + return iter +} diff --git a/vendor/github.com/mowshon/iterium/cycle.go b/vendor/github.com/mowshon/iterium/cycle.go new file mode 100644 index 0000000000..ee47419259 --- /dev/null +++ b/vendor/github.com/mowshon/iterium/cycle.go @@ -0,0 +1,35 @@ +package iterium + +// Cycle returns an infinite iterator that writes data from +// the provided iterator to the infinite iterator. +// +// e.g. Cycle(New(1, 2, 3)) => 1, 2, 3, 1, 2, 3 ... +func Cycle[T any](iterable Iter[T]) Iter[T] { + if iterable.IsInfinite() { + return iterable + } + + // Creation of a new iterator. + iter := Instance[T](0, true) + + // Conversion of iterator to slice + slice, _ := iterable.Slice() + if len(slice) == 0 { + return Empty[T]() + } + + // Run infinite loop into the goroutine and + // send values from the slice to the channel. + go func() { + defer IterRecover() + defer iter.Close() + + for { + for _, value := range slice { + iter.Chan() <- value + } + } + }() + + return iter +} diff --git a/vendor/github.com/mowshon/iterium/dropwhile.go b/vendor/github.com/mowshon/iterium/dropwhile.go new file mode 100644 index 0000000000..d25a73de24 --- /dev/null +++ b/vendor/github.com/mowshon/iterium/dropwhile.go @@ -0,0 +1,39 @@ +package iterium + +// DropWhile returns all other values from the provided iterator +// after receiving the first `false` from the provided function. +// +// e.g. DropWhile(New(1, 4, 6, 4, 1), x < 5) => [6, 4, 1] +func DropWhile[T any](iterable Iter[T], pred func(T) bool) Iter[T] { + iter := Instance[T](0, false) + + go func() { + defer IterRecover() + defer iter.Close() + + // Wait until the value from the channel returns false. + for { + if value, ok := <-iterable.Chan(); ok { + if !pred(value) { + // This value is also written + // to the new iterator. + iter.Chan() <- value + break + } + } + } + + // Once false has been received, write all + // the following values to the channel. + for true { + next, err := iterable.Next() + if err != nil { + return + } + + iter.Chan() <- next + } + }() + + return iter +} diff --git a/vendor/github.com/mowshon/iterium/filter.go b/vendor/github.com/mowshon/iterium/filter.go new file mode 100644 index 0000000000..c8f59ffcda --- /dev/null +++ b/vendor/github.com/mowshon/iterium/filter.go @@ -0,0 +1,27 @@ +package iterium + +// Filter creates a new iterator and writes to the channel only +// those values that returned `true` after executing the predicate function. +func Filter[T any](iterable Iter[T], predicate func(T) bool) Iter[T] { + iter := Instance[T](iterable.Count(), iterable.IsInfinite()) + + go func() { + defer IterRecover() + defer iter.Close() + + for { + next, err := iterable.Next() + if err != nil { + return + } + + // Send a value to the channel only + // if the result is `true`. + if predicate(next) { + iter.Chan() <- next + } + } + }() + + return iter +} diff --git a/vendor/github.com/mowshon/iterium/filterfalse.go b/vendor/github.com/mowshon/iterium/filterfalse.go new file mode 100644 index 0000000000..585e9f9f76 --- /dev/null +++ b/vendor/github.com/mowshon/iterium/filterfalse.go @@ -0,0 +1,27 @@ +package iterium + +// FilterFalse creates a new iterator and writes to the channel only +// those values that returned FALSE after executing the predicate function. +func FilterFalse[T any](iterable Iter[T], predicate func(T) bool) Iter[T] { + iter := Instance[T](iterable.Count(), iterable.IsInfinite()) + + go func() { + defer IterRecover() + defer iter.Close() + + for { + next, err := iterable.Next() + if err != nil { + return + } + + // Send a value to the channel only + // if the result is `false`. + if !predicate(next) { + iter.Chan() <- next + } + } + }() + + return iter +} diff --git a/vendor/github.com/mowshon/iterium/firstfalse.go b/vendor/github.com/mowshon/iterium/firstfalse.go new file mode 100644 index 0000000000..83f7209d68 --- /dev/null +++ b/vendor/github.com/mowshon/iterium/firstfalse.go @@ -0,0 +1,26 @@ +package iterium + +// FirstFalse returns the iterator with the first value from +// the provided iterator that returned `false` after the function was applied. +func FirstFalse[T any](iterable Iter[T], apply func(T) bool) Iter[T] { + iter := Instance[T](0, false) + + go func() { + defer IterRecover() + defer iter.Close() + + for true { + next, err := iterable.Next() + if err != nil { + return + } + + if !apply(next) { + iter.Chan() <- next + return + } + } + }() + + return iter +} diff --git a/vendor/github.com/mowshon/iterium/firsttrue.go b/vendor/github.com/mowshon/iterium/firsttrue.go new file mode 100644 index 0000000000..d16aaa716a --- /dev/null +++ b/vendor/github.com/mowshon/iterium/firsttrue.go @@ -0,0 +1,26 @@ +package iterium + +// FirstTrue returns the iterator with the first value from +// the provided iterator that returned `true` after the function was applied. +func FirstTrue[T any](iterable Iter[T], apply func(T) bool) Iter[T] { + iter := Instance[T](0, false) + + go func() { + defer IterRecover() + defer iter.Close() + + for true { + next, err := iterable.Next() + if err != nil { + return + } + + if apply(next) { + iter.Chan() <- next + return + } + } + }() + + return iter +} diff --git a/vendor/github.com/mowshon/iterium/iterator.go b/vendor/github.com/mowshon/iterium/iterator.go new file mode 100644 index 0000000000..6c8148cb0b --- /dev/null +++ b/vendor/github.com/mowshon/iterium/iterator.go @@ -0,0 +1,92 @@ +package iterium + +// iterator is the initial iterator structure. +type iterator[T any] struct { + channel chan T + infinite bool + length int64 +} + +// IsInfinite returns the iterator infinite state. +func (i *iterator[T]) IsInfinite() bool { + return i.infinite +} + +// SetInfinite update the infinity state of the iterator. +func (i *iterator[T]) SetInfinite(endless bool) { + i.infinite = endless +} + +// Chan returns the iterator channel. +func (i *iterator[T]) Chan() chan T { + return i.channel +} + +// Next returns the next value or error from the iterator channel. +func (i *iterator[T]) Next() (result T, err error) { + if value, ok := <-i.Chan(); ok { + return value, nil + } + + return result, stopIterationErr +} + +// Close closes the iterator channel. +func (i *iterator[T]) Close() { + close(i.channel) +} + +// Count returns the number of possible values the iterator can return. +func (i *iterator[T]) Count() int64 { + return i.length +} + +// Slice turns the iterator into a slice of values. +func (i *iterator[T]) Slice() ([]T, error) { + if i.IsInfinite() { + return nil, infiniteIteratorErr + } + + result := make([]T, 0) + for { + next, err := i.Next() + if err != nil { + return result, nil + } + + result = append(result, next) + } +} + +// New creates a new iterator with a generic data type. +func New[T any](values ...T) Iter[T] { + iter := Instance[T](int64(len(values)), false) + + go func() { + defer IterRecover() + defer iter.Close() + + for _, val := range values { + iter.Chan() <- val + } + }() + + return iter +} + +// Instance initialises and returns the basic iterator structure. +func Instance[T any](length int64, infinite bool) Iter[T] { + return &iterator[T]{ + channel: make(chan T), + infinite: infinite, + length: length, + } +} + +// Empty creates an empty-closed iterator. +func Empty[T any]() Iter[T] { + empty := Instance[T](0, false) + empty.Close() + + return empty +} diff --git a/vendor/github.com/mowshon/iterium/iternium.go b/vendor/github.com/mowshon/iterium/iternium.go new file mode 100644 index 0000000000..0df6f2c0bc --- /dev/null +++ b/vendor/github.com/mowshon/iterium/iternium.go @@ -0,0 +1,74 @@ +package iterium + +import ( + "errors" + "golang.org/x/exp/constraints" +) + +var ( + // stopIterationErr is an error that occurs when the iterator is closed. + stopIterationErr = errors.New("stop iteration") + // infiniteIteratorErr is an error that occurs when converting an infinite iterator to a slice. + infiniteIteratorErr = errors.New("an infinite iterator cannot be a slice") +) + +// Number is the type constraint which includes all numbers. +type Number interface { + constraints.Integer | constraints.Float +} + +// Signed is a type restriction on all numbers especially +// including negative numbers and floating point numbers. +type Signed interface { + constraints.Signed | constraints.Float +} + +// Iter is the iterator interface with all the necessary methods. +type Iter[T any] interface { + IsInfinite() bool + SetInfinite(bool) + Next() (T, error) + Chan() chan T + Close() + Slice() ([]T, error) + Count() int64 +} + +// IterRecover intercepts the resulting error from the goroutine. +func IterRecover() { + recover() +} + +// placeHolders creates a slice of successive indexes. +func placeHolders(length int) []int { + result := make([]int, length) + + for i := 0; i < length; i++ { + result[i] = i + } + + return result +} + +// replacePlaceholders replaces the slice of the indexes with a slice +// of the provided values depending on their index in the first slide. +func replacePlaceholders[T any](from []T, to []int, result *[]T) { + replace := *result + for i := 0; i < len(to); i++ { + replace[i] = from[to[i]] + } +} + +// argsTrio takes the first three values from the slice and returns them as arguments. +func argsTrio[T any](args []T, first, second, third T) (T, T, T) { + switch len(args) { + case 0: + return first, second, third + case 1: + return args[0], second, third + case 2: + return args[0], args[1], third + default: + return args[0], args[1], args[2] + } +} diff --git a/vendor/github.com/mowshon/iterium/map.go b/vendor/github.com/mowshon/iterium/map.go new file mode 100644 index 0000000000..df0da82377 --- /dev/null +++ b/vendor/github.com/mowshon/iterium/map.go @@ -0,0 +1,21 @@ +package iterium + +func Map[T, W any](iterable Iter[T], apply func(T) W) Iter[W] { + iter := Instance[W](iterable.Count(), iterable.IsInfinite()) + + go func() { + defer IterRecover() + defer iter.Close() + + for true { + next, err := iterable.Next() + if err != nil { + break + } + + iter.Chan() <- apply(next) + } + }() + + return iter +} diff --git a/vendor/github.com/mowshon/iterium/permutations.go b/vendor/github.com/mowshon/iterium/permutations.go new file mode 100644 index 0000000000..8cbf0f8ea0 --- /dev/null +++ b/vendor/github.com/mowshon/iterium/permutations.go @@ -0,0 +1,69 @@ +package iterium + +import ( + "math/big" +) + +// PermutationCount returns the total number of possible permutations +// of k elements from a sequence of n elements. +// +// Formula: n! / (n-k)! +func PermutationCount(countOfSymbols, limit int) int64 { + // Create a big.Int value to store the total number of permutations. + total := big.NewInt(1) + + // Calculate n! / (n-k)! and store the result in the 'total' variable. + for i := countOfSymbols - limit + 1; i <= countOfSymbols; i++ { + total.Mul(total, big.NewInt(int64(i))) + } + + // Return the total number of permutations. + return total.Int64() +} + +// Permutations generates all possible permutations of the input slice of symbols using recursion. +func Permutations[T any](symbols []T, limit int) Iter[[]T] { + if limit > len(symbols) { + // The length of the permutation cannot be + // longer than the characters provided. + return Empty[[]T]() + } + + total := PermutationCount(len(symbols), limit) + // Create a new channel receiving slices. + iter := Instance[[]T](total, false) + arr := placeHolders(len(symbols)) + + // Define a recursive backtrack function to generate permutations. + var backtrack func(first int) + + go func() { + defer IterRecover() + defer iter.Close() + + backtrack = func(first int) { + // if we have used up all the elements in the iterable, + // add the current permutation to the channel. + if first == limit { + result := make([]T, limit) + replacePlaceholders[T](symbols, arr[:limit], &result) + iter.Chan() <- result + return + } + + // for each index i in the range [first, len(arr)), + // swap the elements at index i and first, + // and recursively generate permutations starting from index first+1 + for i := first; i < len(arr); i++ { + arr[first], arr[i] = arr[i], arr[first] + backtrack(first + 1) + arr[first], arr[i] = arr[i], arr[first] + } + } + + // Call the backtrack function to generate permutations starting from index 0. + backtrack(0) + }() + + return iter +} diff --git a/vendor/github.com/mowshon/iterium/product.go b/vendor/github.com/mowshon/iterium/product.go new file mode 100644 index 0000000000..37f2146362 --- /dev/null +++ b/vendor/github.com/mowshon/iterium/product.go @@ -0,0 +1,52 @@ +package iterium + +import ( + "math" +) + +// ProductCount calculates the number of Cartesian products with repeat. +func ProductCount(countOfSymbols, repeat int) int64 { + return int64(math.Pow(float64(countOfSymbols), float64(repeat))) +} + +// Product generates a Cartesian product for a given slice of elements with a repeat. +func Product[T any](symbols []T, repeat int) Iter[[]T] { + total := ProductCount(len(symbols), repeat) + // Create a new channel receiving slices. + iter := Instance[[]T](total, false) + + start, end := 0, len(symbols)-1 + slice := make([]int, repeat) + + // Create a slice of length `repeat` and initialize it with the value `start` + for i := 0; i < repeat; i++ { + slice[i] = start + } + + go func() { + defer IterRecover() + defer iter.Close() + + // Generate all possible combinations of `repeat` elements + // from the integer slice `[start, end]`. + for step := uint(0); step < uint(total); step++ { + result := make([]T, repeat) + replacePlaceholders[T](symbols, slice, &result) + iter.Chan() <- result + + // Increment the rightmost element of the combination by 1 + // and propagate the carry to the left if necessary. + for i := repeat - 1; i >= 0; i-- { + slice[i]++ + + if slice[i] <= end { + break + } + + slice[i] = start + } + } + }() + + return iter +} diff --git a/vendor/github.com/mowshon/iterium/range.go b/vendor/github.com/mowshon/iterium/range.go new file mode 100644 index 0000000000..6ef9365333 --- /dev/null +++ b/vendor/github.com/mowshon/iterium/range.go @@ -0,0 +1,51 @@ +package iterium + +import ( + "math" +) + +func RangeCount[N Number](start, stop, step N) int64 { + a, b, c := float64(start), float64(stop), float64(step) + return int64(math.Ceil((b - a) / c)) +} + +// Range function returns a sequence of numbers, starting from 0 by default, and +// increments by 1 (by default), and stops before a specified number. +func Range[S Signed](args ...S) Iter[S] { + var start, stop, step S + var total int64 + + switch len(args) { + case 0: + return Empty[S]() + case 1: + // If there is only one value, assign a value to the variable `stop`. + stop, start, step = argsTrio(args, 0, 0, 1) + if args[0] < 0 { + step = -1 + } + default: + start, stop, step = argsTrio(args, 0, 0, 1) + } + + // Check if the parameters are logically correct. + total = RangeCount(start, stop, step) + if total <= 0 { + return Empty[S]() + } + + // Initialisation of a new channel. + iter := Instance[S](total, false) + + go func() { + defer IterRecover() + defer iter.Close() + + for i := uint(0); i < uint(total); i++ { + iter.Chan() <- start + start = start + step + } + }() + + return iter +} diff --git a/vendor/github.com/mowshon/iterium/repeat.go b/vendor/github.com/mowshon/iterium/repeat.go new file mode 100644 index 0000000000..8baca113d3 --- /dev/null +++ b/vendor/github.com/mowshon/iterium/repeat.go @@ -0,0 +1,30 @@ +package iterium + +// Repeat returns a channel from which a value can be retrieved n-number of times. +func Repeat[T any](value T, n int) Iter[T] { + // Initialisation of a new channel. + iter := Instance[T](int64(n), false) + + // If the length is below zero, then + // the iterator will run forever. + if n < 0 { + iter.SetInfinite(true) + } + + go func() { + defer IterRecover() + defer iter.Close() + + if iter.IsInfinite() { + for { + iter.Chan() <- value + } + } + + for step := 0; step < n; step++ { + iter.Chan() <- value + } + }() + + return iter +} diff --git a/vendor/github.com/mowshon/iterium/starmap.go b/vendor/github.com/mowshon/iterium/starmap.go new file mode 100644 index 0000000000..312233ea6d --- /dev/null +++ b/vendor/github.com/mowshon/iterium/starmap.go @@ -0,0 +1,24 @@ +package iterium + +// StarMap takes a iterator with slices of two values and applies +// a binary function to them, returning a new iterator with the result of that function. +func StarMap[T any](iterable Iter[[]T], apply func(T, T) T) Iter[T] { + iter := Instance[T](iterable.Count(), iterable.IsInfinite()) + + go func() { + defer IterRecover() + defer iter.Close() + + for true { + next, err := iterable.Next() + if err != nil { + return + } + + // Apply the function to the values from the slide. + iter.Chan() <- apply(next[0], next[1]) + } + }() + + return iter +} diff --git a/vendor/github.com/mowshon/iterium/string.go b/vendor/github.com/mowshon/iterium/string.go new file mode 100644 index 0000000000..c97a1349ac --- /dev/null +++ b/vendor/github.com/mowshon/iterium/string.go @@ -0,0 +1,65 @@ +package iterium + +// concatMultipleSlices merge more than two slices at once. +func concatMultipleSlices[T any](slices ...[]T) (result []T) { + for _, s := range slices { + result = append(result, s...) + } + + return result +} + +// AsciiLowercase represents lower case letters. +var AsciiLowercase = []string{ + "a", "b", "c", "d", "e", "f", "g", + "h", "i", "j", "k", "l", "m", "n", + "o", "p", "q", "r", "s", "t", "u", + "v", "w", "x", "y", "z", +} + +// AsciiUppercase represents upper case letters. +var AsciiUppercase = []string{ + "A", "B", "C", "D", "E", "F", "G", + "H", "I", "J", "K", "L", "M", "N", + "O", "P", "Q", "R", "S", "T", "U", + "V", "W", "X", "Y", "Z", +} + +// AsciiLetters is a concatenation of AsciiLowercase and AsciiUppercase. +var AsciiLetters = append(AsciiLowercase, AsciiUppercase...) + +// Digits is a slice of the digits in the string type. +var Digits = []string{ + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", +} + +// HexDigits represents hexadecimal letters. +var HexDigits = []string{ + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", + "a", "b", "c", "d", "e", "f", "A", "B", "C", "D", + "E", "F", +} + +// OctDigits represents octadecimal letters. +var OctDigits = []string{ + "0", "1", "2", "3", "4", "5", "6", "7", +} + +// Punctuation is a slice of ASCII characters that +// are considered punctuation marks in the C locale +var Punctuation = []string{ + "!", "\"", "#", "$", "%", "&", "'", "(", + ")", "*", "+", ",", "-", ".", "/", ":", + ";", "<", "=", ">", "?", "@", "[", "\\", + "]", "^", "_", "`", "{", "|", "}", "~", +} + +// Whitespace contains all ASCII characters that are considered whitespace +var Whitespace = []string{ + " ", "\t", "\n", "\r", "\x0b", "\x0c", +} + +// Printable is a slice of ASCII characters which are considered printable. +var Printable = concatMultipleSlices( + AsciiLetters, Digits, Punctuation, Whitespace, +) diff --git a/vendor/github.com/mowshon/iterium/takewhile.go b/vendor/github.com/mowshon/iterium/takewhile.go new file mode 100644 index 0000000000..d70fa4339f --- /dev/null +++ b/vendor/github.com/mowshon/iterium/takewhile.go @@ -0,0 +1,33 @@ +package iterium + +// TakeWhile returns only the first values from the provided iterator that +// returned `true` after sending them to the provided function. +// +// e.g. TakeWhile(New(1, 4, 6, 4, 1), x < 5) => [1, 4] +func TakeWhile[T any](iterable Iter[T], pred func(T) bool) Iter[T] { + iter := Instance[T](0, false) + + go func() { + defer IterRecover() + defer iter.Close() + + for true { + next, err := iterable.Next() + if err != nil { + return + } + + // Send values to the channel if the result + // of the function returns true. + if pred(next) { + iter.Chan() <- next + } else { + // The first error forces the channel to be + // closed and the result returned. + return + } + } + }() + + return iter +} diff --git a/vendor/golang.org/x/exp/LICENSE b/vendor/golang.org/x/exp/LICENSE new file mode 100644 index 0000000000..6a66aea5ea --- /dev/null +++ b/vendor/golang.org/x/exp/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/exp/PATENTS b/vendor/golang.org/x/exp/PATENTS new file mode 100644 index 0000000000..733099041f --- /dev/null +++ b/vendor/golang.org/x/exp/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/exp/constraints/constraints.go b/vendor/golang.org/x/exp/constraints/constraints.go new file mode 100644 index 0000000000..2c033dff47 --- /dev/null +++ b/vendor/golang.org/x/exp/constraints/constraints.go @@ -0,0 +1,50 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package constraints defines a set of useful constraints to be used +// with type parameters. +package constraints + +// Signed is a constraint that permits any signed integer type. +// If future releases of Go add new predeclared signed integer types, +// this constraint will be modified to include them. +type Signed interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 +} + +// Unsigned is a constraint that permits any unsigned integer type. +// If future releases of Go add new predeclared unsigned integer types, +// this constraint will be modified to include them. +type Unsigned interface { + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr +} + +// Integer is a constraint that permits any integer type. +// If future releases of Go add new predeclared integer types, +// this constraint will be modified to include them. +type Integer interface { + Signed | Unsigned +} + +// Float is a constraint that permits any floating-point type. +// If future releases of Go add new predeclared floating-point types, +// this constraint will be modified to include them. +type Float interface { + ~float32 | ~float64 +} + +// Complex is a constraint that permits any complex numeric type. +// If future releases of Go add new predeclared complex numeric types, +// this constraint will be modified to include them. +type Complex interface { + ~complex64 | ~complex128 +} + +// Ordered is a constraint that permits any ordered type: any type +// that supports the operators < <= >= >. +// If future releases of Go add new ordered types, +// this constraint will be modified to include them. +type Ordered interface { + Integer | Float | ~string +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 57829204b9..3e135b037a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -195,6 +195,9 @@ github.com/klauspost/compress/zstd/internal/xxhash # github.com/matttproud/golang_protobuf_extensions v1.0.4 ## explicit; go 1.9 github.com/matttproud/golang_protobuf_extensions/pbutil +# github.com/mowshon/iterium v1.0.0 +## explicit; go 1.20 +github.com/mowshon/iterium # github.com/mschoch/smat v0.2.0 ## explicit; go 1.13 github.com/mschoch/smat @@ -261,6 +264,9 @@ go.uber.org/zap/zaptest/observer golang.org/x/crypto/bcrypt golang.org/x/crypto/blowfish golang.org/x/crypto/pbkdf2 +# golang.org/x/exp v0.0.0-20230307190834-24139beb5833 +## explicit; go 1.18 +golang.org/x/exp/constraints # golang.org/x/net v0.19.0 ## explicit; go 1.18 golang.org/x/net/http/httpguts