mirror of
https://github.com/duke-git/lancet.git
synced 2026-02-05 05:12:26 +08:00
Compare commits
85 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d6ab72059 | ||
|
|
30eb2c72b0 | ||
|
|
adf18a2e47 | ||
|
|
f99a8ef3cf | ||
|
|
fcdf1d5839 | ||
|
|
ee0afed963 | ||
|
|
1d94896c9b | ||
|
|
69cf9bbcf0 | ||
|
|
2bbcb85286 | ||
|
|
e58c9b797b | ||
|
|
3e1ac5e0b5 | ||
|
|
8869e0440d | ||
|
|
69f9c74bcb | ||
|
|
84ebc7ce71 | ||
|
|
c745097749 | ||
|
|
ba75e58e5f | ||
|
|
7e85a0ed7d | ||
|
|
2268a0312f | ||
|
|
da84d95aa3 | ||
|
|
48244d6711 | ||
|
|
5e3337a52e | ||
|
|
c3372e18b1 | ||
|
|
90e5a0bfb2 | ||
|
|
93be25920f | ||
|
|
f9e5ec9096 | ||
|
|
601df5dc12 | ||
|
|
63216d9b1c | ||
|
|
c32a19868d | ||
|
|
71e914019b | ||
|
|
9824db0056 | ||
|
|
ba9188a29a | ||
|
|
8625fbd8d3 | ||
|
|
81b29baf30 | ||
|
|
5a38e34063 | ||
|
|
159168dd7b | ||
|
|
ec092a009a | ||
|
|
ca40b5d6c6 | ||
|
|
a6d39a3bba | ||
|
|
38148978cf | ||
|
|
3e8c3bd396 | ||
|
|
30971c1aab | ||
|
|
bc260277bc | ||
|
|
c0b200f846 | ||
|
|
305847993c | ||
|
|
f5d70728c3 | ||
|
|
c2a5335bc6 | ||
|
|
7b4e060f85 | ||
|
|
a360372aa9 | ||
|
|
7f78a6b11e | ||
|
|
5c53cb5867 | ||
|
|
f7e9d5dc47 | ||
|
|
5c580ed013 | ||
|
|
0f9764f41e | ||
|
|
0bc5f82554 | ||
|
|
e91965b013 | ||
|
|
483a286d8e | ||
|
|
3f8e306ced | ||
|
|
0b29f0520d | ||
|
|
8611ec0c10 | ||
|
|
286e10d189 | ||
|
|
3e7f94b03e | ||
|
|
356351896d | ||
|
|
9be124211e | ||
|
|
f467658481 | ||
|
|
5c9d0e396e | ||
|
|
8f74460c1b | ||
|
|
d5752499bf | ||
|
|
eb7cf76eae | ||
|
|
4af074d181 | ||
|
|
73fb8fefd2 | ||
|
|
9cf535055d | ||
|
|
8be7b3e396 | ||
|
|
dac706d700 | ||
|
|
2097277a7d | ||
|
|
95b516e278 | ||
|
|
ca373b00a7 | ||
|
|
a220220f09 | ||
|
|
aeef0418a4 | ||
|
|
9b7d8d7abf | ||
|
|
4d21e81263 | ||
|
|
ce2397422e | ||
|
|
4b3a62b36a | ||
|
|
e054680d20 | ||
|
|
5381842eec | ||
|
|
6e0498514c |
@@ -1,4 +1,4 @@
|
||||
# Lancet Contributing Guide
|
||||
# Lancet Contribution Guide
|
||||
|
||||
Hi! Thank you for choosing Lancet.
|
||||
|
||||
201
README.md
201
README.md
@@ -4,7 +4,7 @@
|
||||
<br/>
|
||||
|
||||

|
||||
[](https://github.com/duke-git/lancet/releases)
|
||||
[](https://github.com/duke-git/lancet/releases)
|
||||
[](https://pkg.go.dev/github.com/duke-git/lancet/v2)
|
||||
[](https://goreportcard.com/report/github.com/duke-git/lancet/v2)
|
||||
[](https://github.com/duke-git/lancet/actions/workflows/codecov.yml)
|
||||
@@ -24,7 +24,7 @@
|
||||
## Features
|
||||
|
||||
- 👏 Comprehensive, efficient and reusable.
|
||||
- 💪 600+ go util functions, support string, slice, datetime, net, crypt...
|
||||
- 💪 700+ go util functions, support string, slice, datetime, net, crypt...
|
||||
- 💅 Only depends on two kinds of libraries: go standard library and golang.org/x.
|
||||
- 🌍 Unit test for every exported function.
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
go get github.com/duke-git/lancet/v2 // will install latest version of v2.x.x
|
||||
```
|
||||
|
||||
2. <b>For users who use version below go1.18, you should install v1.x.x. The latest of v1.x.x is v1.4.3. </b>
|
||||
2. <b>For users who use version below go1.18, you should install v1.x.x. The latest of v1.x.x is v1.4.4. </b>
|
||||
|
||||
```go
|
||||
go get github.com/duke-git/lancet // below go1.18, install latest version of v1.x.x
|
||||
@@ -362,6 +362,12 @@ import "github.com/duke-git/lancet/v2/cryptor"
|
||||
- **<big>AesOfbDecrypt</big>** : decrypt byte slice data with key use AES OFB algorithm.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/cryptor.md#AesOfbDecrypt)]
|
||||
[[play](https://go.dev/play/p/VtHxtkUj-3F)]
|
||||
- **<big>AesGcmEncrypt</big>** : encrypt byte slice data with key use AES GCM algorithm.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/cryptor.md#AesGcmEncrypt)]
|
||||
[[play](todo)]
|
||||
- **<big>AesGcmDecrypt</big>** : decrypt byte slice data with key use AES GCM algorithm.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/cryptor.md#AesGcmDecrypt)]
|
||||
[[play](todo)]
|
||||
- **<big>Base64StdEncode</big>** : encode string with base64 encoding.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/cryptor.md#Base64StdEncode)]
|
||||
[[play](https://go.dev/play/p/VOaUyQUreoK)]
|
||||
@@ -603,6 +609,16 @@ import "github.com/duke-git/lancet/v2/datetime"
|
||||
- **<big>TimestampNano</big>** : returns current nano second timestamp.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#TimestampNano)]
|
||||
[[play](https://go.dev/play/p/A9Oq_COrcCF)]
|
||||
- **<big>TrackFuncTime</big>** : tracks function execution time.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#TrackFuncTime)]
|
||||
[[play](todo)]
|
||||
- **<big>DaysBetween</big>** : returns the number of days between two times.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#DaysBetween)]
|
||||
[[play](todo)]
|
||||
- **<big>GenerateDatetimesBetween</big>** : returns a slice of strings between two times.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#GenerateDatetimesBetween)]
|
||||
[[play](todo)]
|
||||
|
||||
|
||||
<h3 id="datastructure"> 8. Datastructure package contains some common data structure. eg. list, linklist, stack, queue, set, tree, graph. <a href="#index">index</a></h3>
|
||||
|
||||
@@ -747,7 +763,7 @@ import "github.com/duke-git/lancet/v2/formatter"
|
||||
|
||||
#### Function list:
|
||||
|
||||
- **<big>Comma</big>** : add comma to a number value by every 3 numbers from right, ahead by symbol char.
|
||||
- **<big>Comma</big>** : add comma to a number value by every 3 numbers from right, ahead by a prefix symbol char.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/formatter.md#Comma)]
|
||||
[[play](https://go.dev/play/p/eRD5k2vzUVX)]
|
||||
- **<big>Pretty</big>** : pretty print data to JSON string.
|
||||
@@ -792,9 +808,15 @@ import "github.com/duke-git/lancet/v2/function"
|
||||
- **<big>Delay</big>** : call the function after delayed time.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/function.md#Delay)]
|
||||
[[play](https://go.dev/play/p/Ivtc2ZE-Tye)]
|
||||
- **<big>Debounced</big>** : creates a debounced function that delays invoking fn until after wait duration have elapsed since the last time the debounced function was invoked.
|
||||
- **<big>Debounced<sup>deprecated</sup></big>** : creates a debounced function that delays invoking fn until after wait duration have elapsed since the last time the debounced function was invoked.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/function.md#Debounced)]
|
||||
[[play](https://go.dev/play/p/absuEGB_GN7)]
|
||||
- **<big>Debounce</big>** : creates a debounced version of the provided function.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/function.md#Debounce)]
|
||||
[[play](todo)]
|
||||
- **<big>Throttle</big>** : creates a throttled version of the provided function.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/function.md#Throttle)]
|
||||
[[play](todo)]
|
||||
- **<big>Schedule</big>** : invoke function every duration time, util close the returned bool channel.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/function.md#Schedule)]
|
||||
[[play](https://go.dev/play/p/hbON-Xeyn5N)]
|
||||
@@ -901,10 +923,72 @@ import "github.com/duke-git/lancet/v2/maputil"
|
||||
- **<big>HasKey</big>** : checks if map has key or not.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#HasKey)]
|
||||
[[play](https://go.dev/play/p/isZZHOsDhFc)]
|
||||
- **<big>GetOrSet</big>** : returns value of the given key or set the given value value if not present.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#GetOrSet)]
|
||||
[[play](https://go.dev/play/p/IVQwO1OkEJC)]
|
||||
- **<big>MapToStruct</big>** : converts map to struct.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#MapToStruct)]
|
||||
[[play](https://go.dev/play/p/7wYyVfX38Dp)]
|
||||
- **<big>ToSortedSlicesDefault</big>** : converts a map to two slices sorted by key: one for the keys and another for the values.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#ToSortedSlicesDefault)]
|
||||
[[play](https://go.dev/play/p/43gEM2po-qy)]
|
||||
- **<big>ToSortedSlicesWithComparator</big>** : converts a map to two slices sorted by key and using a custom comparison function: one for the keys and another for the values.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#ToSortedSlicesWithComparator)]
|
||||
[[play](https://go.dev/play/p/0nlPo6YLdt3)]
|
||||
- **<big>NewOrderedMap</big>** : creates a new OrderedMap.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#NewOrderedMap)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_Set</big>** : sets the given key-value pair for ordered map.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#OrderedMap_Set)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_Get</big>** : returns the value for the given key.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#OrderedMap_Get)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_Delete</big>** : deletes the key-value pair for the given key.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#OrderedMap_Delete)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_Clear</big>** : clears the ordered map.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#OrderedMap_Clear)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_Front</big>** : returns the first key-value pair.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#OrderedMap_Front)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_Back</big>** : returns the last key-value pair.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#OrderedMap_Back)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_Range</big>** : calls the given function for each key-value pair.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#OrderedMap_Range)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_Keys</big>** : returns the keys in order.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#OrderedMap_Keys)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_Values</big>** : returns the values in order.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#OrderedMap_Values)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_Elements</big>** : returns the key-value pairs in order.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#OrderedMap_Elements)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_Len</big>** : returns the number of key-value pairs.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#OrderedMap_Len)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_Contains</big>** : returns true if the given key exists.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#OrderedMap_Contains)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_Iter</big>** : returns a channel that yields key-value pairs in order.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#OrderedMap_Iter)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_ReverseIter</big>** : returns a channel that yields key-value pairs in reverse order.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#OrderedMap_ReverseIter)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_SortByKey</big>** : sorts the map by key given less function.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#OrderedMap_SortByKey)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_MarshalJSON</big>** : implements the json.Marshaler interface.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#OrderedMap_MarshalJSON)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_UnmarshalJSON</big>** : implements the json.Unmarshaler interface.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#OrderedMap_UnmarshalJSON)]
|
||||
[[play](todo)]
|
||||
- **<big>NewConcurrentMap</big>** : creates a ConcurrentMap with specific shard count.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#NewConcurrentMap)]
|
||||
[[play](https://go.dev/play/p/3PenTPETJT0)]
|
||||
@@ -929,6 +1013,13 @@ import "github.com/duke-git/lancet/v2/maputil"
|
||||
- **<big>ConcurrentMap_Range</big>** : calls iterator sequentially for each key and value present in each of the shards in the map.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#ConcurrentMap_Range)]
|
||||
[[play](https://go.dev/play/p/iqcy7P8P0Pr)]
|
||||
- **<big>SortByKey</big>** : sorts the map by its keys and returns a new map with sorted keys.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#SortByKey)]
|
||||
[[play](todo)]
|
||||
- **<big>GetOrDefault</big>** : returns the value of the given key or a default value if the key is not present.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#GetOrDefault)]
|
||||
[[play](todo)]
|
||||
|
||||
|
||||
<h3 id="mathutil"> 13. Mathutil package implements some functions for math calculation. <a href="#index">index</a></h3>
|
||||
|
||||
@@ -1120,7 +1211,7 @@ import "github.com/duke-git/lancet/v2/pointer"
|
||||
- **<big>Unwrap</big>** : return the value from the pointer.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/pointer.md#Unwrap)]
|
||||
[[play](https://go.dev/play/p/cgeu3g7cjWb)]
|
||||
- **<big>UnwarpOr</big>** : UnwarpOr returns the value from the pointer or fallback if the pointer is nil.
|
||||
- **<big>UnwrapOr</big>** : UnwrapOr returns the value from the pointer or fallback if the pointer is nil.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/pointer.md#UnwrapOr)]
|
||||
[[play](https://go.dev/play/p/mmNaLC38W8C)]
|
||||
- **<big>UnwarpOrDefault</big>** : UnwarpOrDefault returns the value from the pointer or the default value if the pointer is nil.
|
||||
@@ -1159,20 +1250,36 @@ import "github.com/duke-git/lancet/v2/random"
|
||||
- **<big>UUIdV4</big>** : generate a random UUID of version 4 according to RFC 4122.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/random.md#UUIdV4)]
|
||||
[[play](https://go.dev/play/p/_Z9SFmr28ft)]
|
||||
- **<big>RandUniqueIntSlice</big>** : generate a slice of random int of length n that do not repeat.
|
||||
- **<big>RandUniqueIntSlice</big>** : generate a slice of random int that do not repeat.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/random.md#RandUniqueIntSlice)]
|
||||
[[play](https://go.dev/play/p/uBkRSOz73Ec)]
|
||||
- **<big>RandSymbolChar</big>** : Generate a random symbol char of specified length.
|
||||
- **<big>RandSymbolChar</big>** : generate a random symbol char of specified length.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/random.md#RandSymbolChar)]
|
||||
[[play](https://go.dev/play/p/Im6ZJxAykOm)]
|
||||
|
||||
- **<big>RandFloat</big>** : Generate a random float64 number between [min, max) with specific precision.
|
||||
- **<big>RandFloat</big>** : generate a random float64 number between [min, max) with specific precision.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/random.md#RandFloat)]
|
||||
[[play](https://go.dev/play/p/zbD_tuobJtr)]
|
||||
|
||||
- **<big>RandFloats</big>** : Generate a slice of random float64 numbers of length n that do not repeat.
|
||||
- **<big>RandFloats</big>** : generate a slice of random float64 numbers that do not repeat.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/random.md#RandFloats)]
|
||||
[[play](https://go.dev/play/p/I3yndUQ-rhh)]
|
||||
- **<big>RandStringSlice</big>** : generate a slice of random string of length strLen based on charset.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/random.md#RandStringSlice)]
|
||||
[[play](todo)]
|
||||
- **<big>RandBool</big>** : generate a random boolean value (true or false).
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/random.md#RandBool)]
|
||||
[[play](todo)]
|
||||
- **<big>RandBoolSlice</big>** : generate a random boolean slice of specified length.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/random.md#RandBoolSlice)]
|
||||
[[play](todo)]
|
||||
- **<big>RandIntSlice</big>** : generate a slice of random int. Number range in [min, max)
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/random.md#RandIntSlice)]
|
||||
[[play](todo)]
|
||||
- **<big>RandFromGivenSlice</big>** : generate a random element from given slice.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/random.md#RandFromGivenSlice)]
|
||||
[[play](todo)]
|
||||
- **<big>RandSliceFromGivenSlice</big>** : generate a random slice of length num from given slice.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/random.md#RandSliceFromGivenSlice)]
|
||||
[[play](todo)]
|
||||
|
||||
|
||||
<h3 id="retry"> 17. Retry package is for executing a function repeatedly until it was successful or canceled by the context. <a href="#index">index</a></h3>
|
||||
@@ -1202,11 +1309,13 @@ import "github.com/duke-git/lancet/v2/retry"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/retry.md#BackoffStrategy)]
|
||||
- **<big>RetryWithCustomBackoff</big>** : set abitary custom backoff strategy.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/retry.md#RetryWithCustomBackoff)]
|
||||
[[play](https://go.dev/play/p/jIm_o2vb5Y4)]
|
||||
- **<big>RetryWithLinearBackoff</big>** : set linear strategy backoff.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/retry.md#RetryWithLinearBackoff)]
|
||||
[[play](https://go.dev/play/p/PDet2ZQZwcB)]
|
||||
- **<big>RetryWithExponentialWithJitterBackoff</big>** : set exponential strategy backoff.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/retry.md#RetryWithExponentialWithJitterBackoff)]
|
||||
|
||||
[[play](https://go.dev/play/p/xp1avQmn16X)]
|
||||
|
||||
|
||||
<h3 id="slice"> 18. Slice contains some functions to manipulate slice. <a href="#index">index</a></h3>
|
||||
@@ -1313,6 +1422,9 @@ import "github.com/duke-git/lancet/v2/slice"
|
||||
- **<big>ForEachWithBreak</big>** : iterates over elements of slice and invokes function for each element, when iteratee return false, will break the for each loop.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#ForEachWithBreak)]
|
||||
[[play](https://go.dev/play/p/qScs39f3D9W)]
|
||||
- **<big>ForEachConcurrent</big>** : applies the iteratee function to each item in the slice concurrently.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#ForEachConcurrent)]
|
||||
[[play](todo)]
|
||||
- **<big>GroupBy</big>** : iterate over elements of the slice, each element will be group by criteria, returns two slices.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#GroupBy)]
|
||||
[[play](https://go.dev/play/p/QVkPxzPR0iA)]
|
||||
@@ -1340,6 +1452,9 @@ import "github.com/duke-git/lancet/v2/slice"
|
||||
- **<big>Map</big>** : creates an slice of values by running each element of slice thru iteratee function.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#Map)]
|
||||
[[play](https://go.dev/play/p/biaTefqPquw)]
|
||||
- **<big>MapConcurrent</big>** : applies the iteratee function to each item in the slice by concrrent.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#MapConcurrent)]
|
||||
[[play](todo)]
|
||||
- **<big>Merge</big>** : merge all given slices into one slice.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#Merge)]
|
||||
[[play](https://go.dev/play/p/lbjFp784r9N)]
|
||||
@@ -1355,6 +1470,9 @@ import "github.com/duke-git/lancet/v2/slice"
|
||||
- **<big>ReduceRight</big>** : ReduceRight is like ReduceBy, but it iterates over elements of slice from right to left.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#ReduceRight)]
|
||||
[[play](https://go.dev/play/p/qT9dZC03A1K)]
|
||||
- **<big>ReduceConcurrent</big>** : reduces the slice to a single value by applying the reducer function to each item in the slice concurrently.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#ReduceConcurrent)]
|
||||
[[play](todo)]
|
||||
- **<big>Replace</big>** : returns a copy of the slice with the first n non-overlapping instances of old replaced by new.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#Replace)]
|
||||
[[play](https://go.dev/play/p/P5mZp7IhOFo)]
|
||||
@@ -1406,9 +1524,18 @@ import "github.com/duke-git/lancet/v2/slice"
|
||||
- **<big>Unique</big>** : remove duplicate elements in slice.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#Unique)]
|
||||
[[play](https://go.dev/play/p/AXw0R3ZTE6a)]
|
||||
- **<big>UniqueBy</big>** : call iteratee func with every item of slice, then remove duplicated.
|
||||
- **<big>UniqueBy</big>** : remove duplicate elements from the input slice based on the values returned by the iteratee function.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#UniqueBy)]
|
||||
[[play](https://go.dev/play/p/UR323iZLDpv)]
|
||||
- **<big>UniqueByComparator</big>** : remove duplicate elements from the input slice using the provided comparator function.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#UniqueByComparator)]
|
||||
[[play](todo)]
|
||||
- **<big>UniqueByField</big>** : remove duplicate elements in struct slice by struct field.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#UniqueByField)]
|
||||
[[play](https://go.dev/play/p/6cifcZSPIGu)]
|
||||
- **<big>UniqueByConcurrent</big>** : remove duplicate elements from the slice by concurrent.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#UniqueByConcurrent)]
|
||||
[[play](todo)]
|
||||
- **<big>Union</big>** : creates a slice of unique elements, in order, from all given slices.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#Union)]
|
||||
[[play](https://go.dev/play/p/hfXV1iRIZOf)]
|
||||
@@ -1440,9 +1567,13 @@ import "github.com/duke-git/lancet/v2/slice"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#Break)]
|
||||
- **<big>RightPadding</big>** : adds padding to the right end of a slice.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#RightPadding)]
|
||||
[[play](https://go.dev/play/p/0_2rlLEMBXL)]
|
||||
- **<big>LeftPadding</big>** : adds padding to the left begin of a slice.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#LeftPadding)]
|
||||
|
||||
[[play](https://go.dev/play/p/jlQVoelLl2k)]
|
||||
- **<big>Frequency</big>** : counts the frequency of each element in the slice.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#Frequency)]
|
||||
[[play](todo)]
|
||||
|
||||
|
||||
<h3 id="stream"> 19. Stream package implements a sequence of elements supporting sequential and operations. this package is an experiment to explore if stream in go can work as the way java does. its function is very limited. <a href="#index">index</a></h3>
|
||||
@@ -1477,6 +1608,9 @@ import "github.com/duke-git/lancet/v2/stream"
|
||||
- **<big>Filter</big>** : returns a stream consisting of the elements of this stream that match the given predicate.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/stream.md#Filter)]
|
||||
[[play](https://go.dev/play/p/MFlSANo-buc)]
|
||||
- **<big>FilterConcurrent</big>** : Applies the provided filter function `predicate` to each element of the input slice concurrently.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/stream.md#FilterConcurrent)]
|
||||
[[play](todo)]
|
||||
- **<big>Map</big>** : returns a stream consisting of the elements of this stream that apply the given function to elements of stream.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/stream.md#Map)]
|
||||
[[play](https://go.dev/play/p/OtNQUImdYko)]
|
||||
@@ -1694,7 +1828,21 @@ import "github.com/duke-git/lancet/v2/strutil"
|
||||
- **<big>HammingDistance</big>** : calculates the Hamming distance between two strings.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/strutil.md#HammingDistance)]
|
||||
[[play](https://go.dev/play/p/glNdQEA9HUi)]
|
||||
|
||||
- **<big>Concat</big>** : concatenates strings.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/strutil.md#Concat)]
|
||||
[[play](todo)]
|
||||
- **<big>Ellipsis</big>** : truncates a string to a specified length and appends an ellipsis.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/strutil.md#Ellipsis)]
|
||||
[[play](todo)]
|
||||
- **<big>Shuffle</big>** : shuffle the order of characters of given string.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/strutil.md#Shuffle)]
|
||||
[[play](todo)]
|
||||
- **<big>Rotate</big>** : rotates the string by the specified number of characters.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/strutil.md#Rotate)]
|
||||
[[play](todo)]
|
||||
- **<big>TemplateReplace</big>** : replaces the placeholders in the template string with the corresponding values in the data map.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/strutil.md#TemplateReplace)]
|
||||
[[play](todo)]
|
||||
|
||||
<h3 id="system"> 22. System package contain some functions about os, runtime, shell command. <a href="#index">index</a></h3>
|
||||
|
||||
@@ -1731,6 +1879,19 @@ import "github.com/duke-git/lancet/v2/system"
|
||||
- **<big>GetOsBits</big>** : return current os bits (32 or 64).
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/system.md#GetOsBits)]
|
||||
[[play](https://go.dev/play/p/ml-_XH3gJbW)]
|
||||
- **<big>StartProcess</big>** : start a new process with the specified name and arguments.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/system.md#StartProcess)]
|
||||
[[play](todo)]
|
||||
- **<big>StopProcess</big>** : stop a process by pid.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/system.md#StopProcess)]
|
||||
[[play](todo)]
|
||||
- **<big>KillProcess</big>** : kill a new process with the specified name and arguments.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/system.md#KillProcess)]
|
||||
[[play](todo)]
|
||||
- **<big>GetProcessInfo</big>** : retrieves detailed process information by pid.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/system.md#GetProcessInfo)]
|
||||
[[play](todo)]
|
||||
|
||||
|
||||
<h3 id="tuple"> 23. Tuple package implements tuple data type and some operations on it. <a href="#index">index</a></h3>
|
||||
|
||||
@@ -2034,11 +2195,15 @@ import "github.com/duke-git/lancet/v2/xerror"
|
||||
|
||||
## How to Contribute
|
||||
|
||||
#### [Contributing Guide](./CONTRIBUTING.md)
|
||||
#### [Contribution Guide](./CONTRIBUTION.md)
|
||||
|
||||
## Contributors
|
||||
Thank you to all the people who contributed to lancet!
|
||||
|
||||
<a href="https://github.com/duke-git/lancet/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=duke-git/lancet" />
|
||||
</a>
|
||||
</a>
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#duke-git/lancet&Date)
|
||||
|
||||
198
README_zh-CN.md
198
README_zh-CN.md
@@ -4,7 +4,7 @@
|
||||
<br/>
|
||||
|
||||

|
||||
[](https://github.com/duke-git/lancet/releases)
|
||||
[](https://github.com/duke-git/lancet/releases)
|
||||
[](https://pkg.go.dev/github.com/duke-git/lancet/v2)
|
||||
[](https://goreportcard.com/report/github.com/duke-git/lancet/v2)
|
||||
[](https://github.com/duke-git/lancet/actions/workflows/codecov.yml)
|
||||
@@ -23,7 +23,7 @@
|
||||
## 特性
|
||||
|
||||
- 👏 全面、高效、可复用。
|
||||
- 💪 600+常用 go 工具函数,支持 string、slice、datetime、net、crypt...
|
||||
- 💪 700+常用 go 工具函数,支持 string、slice、datetime、net、crypt...
|
||||
- 💅 只依赖 go 标准库和 golang.org/x。
|
||||
- 🌍 所有导出函数单元测试覆盖率 100%。
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
go get github.com/duke-git/lancet/v2 //安装v2最新版本v2.x.x
|
||||
```
|
||||
|
||||
2. <b>使用 go1.18 以下版本的用户,必须安装 v1.x.x。目前最新的 v1 版本是 v1.4.3。</b>
|
||||
2. <b>使用 go1.18 以下版本的用户,必须安装 v1.x.x。目前最新的 v1 版本是 v1.4.4。</b>
|
||||
|
||||
```go
|
||||
go get github.com/duke-git/lancet// 使用go1.18以下版本, 必须安装v1.x.x版本
|
||||
@@ -363,6 +363,12 @@ import "github.com/duke-git/lancet/v2/cryptor"
|
||||
- **<big>AesOfbDecrypt</big>** : 使用 AES OFB 算法模式解密数据。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#AesOfbDecrypt)]
|
||||
[[play](https://go.dev/play/p/VtHxtkUj-3F)]
|
||||
- **<big>AesGcmEncrypt</big>** : 使用 AES GCM 算法模式加密数据。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#AesGcmEncrypt)]
|
||||
[[play](todo)]
|
||||
- **<big>AesGcmDecrypt</big>** : 使用 AES GCM 算法模式解密数据。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#AesGcmDecrypt)]
|
||||
[[play](todo)]
|
||||
- **<big>Base64StdEncode</big>** : 将字符串 base64 编码。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#Base64StdEncode)]
|
||||
[[play](https://go.dev/play/p/VOaUyQUreoK)]
|
||||
@@ -472,7 +478,7 @@ import "github.com/duke-git/lancet/v2/cryptor"
|
||||
[[play](https://go.dev/play/p/sSVmkfENKMz)]
|
||||
|
||||
|
||||
<h3 id="datetime"> 7. datetime 日期时间处理包,格式化日期,比较日期。 <a href="#index">回到目录</a></h3>
|
||||
<h3 id="datetime"> 7. datetime日期时间处理包,格式化日期,比较日期。 <a href="#index">回到目录</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/datetime"
|
||||
@@ -606,6 +612,16 @@ import "github.com/duke-git/lancet/v2/datetime"
|
||||
- **<big>TimestampNano</big>** : 返回当前纳秒级时间戳。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#TimestampNano)]
|
||||
[[play](https://go.dev/play/p/A9Oq_COrcCF)]
|
||||
- **<big>TrackFuncTime</big>** : 测试函数执行时间。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#TrackFuncTime)]
|
||||
[[play](todo)]
|
||||
- **<big>DaysBetween</big>** : 返回两个日期之间的天数差。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#DaysBetween)]
|
||||
[[play](todo)]
|
||||
- **<big>GenerateDatetimesBetween</big>** : 生成从start到end的所有日期时间的字符串列表。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#GenerateDatetimesBetween)]
|
||||
[[play](todo)]
|
||||
|
||||
|
||||
<h3 id="datastructure"> 8. datastructure 包含一些普通的数据结构实现。例如:list, linklist, stack, queue, set, tree, graph。 <a href="#index">回到目录</a></h3>
|
||||
|
||||
@@ -748,7 +764,7 @@ import "github.com/duke-git/lancet/v2/formatter"
|
||||
|
||||
#### 函数列表:
|
||||
|
||||
- **<big>Comma</big>** : 用逗号每隔 3 位分割数字/字符串,支持前缀添加符号。
|
||||
- **<big>Comma</big>** : 用逗号每隔 3 位分割数字/字符串,支持添加前缀符号。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/formatter.md#Comma)]
|
||||
[[play](https://go.dev/play/p/eRD5k2vzUVX)]
|
||||
- **<big>Pretty</big>** : 返回 pretty JSON 字符串。
|
||||
@@ -793,9 +809,15 @@ import "github.com/duke-git/lancet/v2/function"
|
||||
- **<big>Delay</big>** : 延迟 delay 时间后调用函数。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/function.md#Delay)]
|
||||
[[play](https://go.dev/play/p/Ivtc2ZE-Tye)]
|
||||
- **<big>Debounced</big>** : 创建一个 debounced 函数,该函数延迟调用 fn 直到自上次调用 debounced 函数后等待持续时间过去。
|
||||
- **<big>Debounced<sup>deprecated</sup></big>** : 创建一个 debounced 函数,该函数延迟调用 fn 直到自上次调用 debounced 函数后等待持续时间过去。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/function.md#Debounced)]
|
||||
[[play](https://go.dev/play/p/absuEGB_GN7)]
|
||||
- **<big>Debounce</big>** : 创建一个函数的去抖动版本。该去抖动函数仅在上次调用后的指定延迟时间过去之后才会调用原始函数。支持取消去抖动。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/function.md#Debounce)]
|
||||
[[play](todo)]
|
||||
- **<big>Throttle</big>** : 创建一个函数的节流版本。返回的函数保证在每个时间间隔内最多只会被调用一次。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/function.md#Throttle)]
|
||||
[[play](todo)]
|
||||
- **<big>Schedule</big>** : 每次持续时间调用函数,直到关闭返回的 channel。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/function.md#Schedule)]
|
||||
[[play](https://go.dev/play/p/hbON-Xeyn5N)]
|
||||
@@ -903,10 +925,72 @@ import "github.com/duke-git/lancet/v2/maputil"
|
||||
- **<big>HasKey</big>** : 检查 map 是否包含某个 key。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#HasKey)]
|
||||
[[play](https://go.dev/play/p/isZZHOsDhFc)]
|
||||
- **<big>GetOrSet</big>** : 返回给定键的值,如果不存在则设置该值。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#GetOrSet)]
|
||||
[[play](https://go.dev/play/p/IVQwO1OkEJC)]
|
||||
- **<big>MapToStruct</big>** : 将map转成struct。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#MapToStruct)]
|
||||
[[play](https://go.dev/play/p/7wYyVfX38Dp)]
|
||||
- **<big>ToSortedSlicesDefault</big>** : 将map的key和value转化成两个根据key的值从小到大排序的切片,value切片中元素的位置与key对应。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#ToSortedSlicesDefault)]
|
||||
[[play](https://go.dev/play/p/43gEM2po-qy)]
|
||||
- **<big>ToSortedSlicesWithComparator</big>** : 将map的key和value转化成两个使用比较器函数根据key的值自定义排序规则的切片,value切片中元素的位置与key对应。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#ToSortedSlicesWithComparator)]
|
||||
[[play](https://go.dev/play/p/0nlPo6YLdt3)]
|
||||
- **<big>NewOrderedMap</big>** : 创建有序映射。有序映射是键值对的集合,其中键是唯一的,并且保留键插入的顺序。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#NewOrderedMap)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_Set</big>** : 设置给定的键值对。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#OrderedMap_Set)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_Get</big>** : 返回给定键的值。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#OrderedMap_Get)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_Delete</big>** : 删除给定键的键值对。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#OrderedMap_Delete)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_Clear</big>** : 清空map数据。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#OrderedMap_Clear)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_Front</big>** : 返回第一个键值对。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#OrderedMap_Front)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_Back</big>** : 返回最后一个键值对。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#OrderedMap_Back)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_Range</big>** : 为每个键值对调用给定的函数。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#OrderedMap_Range)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_Keys</big>** : 按顺序返回键的切片。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#OrderedMap_Keys)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_Values</big>** : 按顺序返回值的切片。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#OrderedMap_Values)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_Elements</big>** : 按顺序返回键值对。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#OrderedMap_Elements)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_Len</big>** : 返回键值对的数量。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#OrderedMap_Len)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_Contains</big>** : 如果给定的键存在则返回true。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#OrderedMap_Contains)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_Iter</big>** : 返回按顺序产生键值对的通道。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#OrderedMap_Iter)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_ReverseIter</big>** : 返回以相反顺序产生键值对的通道。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#OrderedMap_ReverseIter)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_SortByKey</big>** : 使用传入的比较函数排序map key。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#OrderedMap_SortByKey)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_MarshalJSON</big>** : 实现json.Marshaler接口。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#OrderedMap_MarshalJSON)]
|
||||
[[play](todo)]
|
||||
- **<big>OrderedMap_UnmarshalJSON</big>** : 实现json.Unmarshaler接口。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#OrderedMap_UnmarshalJSON)]
|
||||
[[play](todo)]
|
||||
- **<big>NewConcurrentMap</big>** : ConcurrentMap 协程安全的 map 结构。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#NewConcurrentMap)]
|
||||
[[play](https://go.dev/play/p/3PenTPETJT0)]
|
||||
@@ -931,6 +1015,12 @@ import "github.com/duke-git/lancet/v2/maputil"
|
||||
- **<big>ConcurrentMap_Range</big>** : 为 map 中每个键和值顺序调用迭代器。 如果 iterator 返回 false,则停止迭代。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#ConcurrentMap_Range)]
|
||||
[[play](https://go.dev/play/p/iqcy7P8P0Pr)]
|
||||
- **<big>SortByKey</big>** : 对传入的map根据key进行排序。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#SortByKey)]
|
||||
[[play](todo)]
|
||||
- **<big>GetOrDefault</big>** : 返回给定键的值,如果键不存在,则返回默认值。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#GetOrDefault)]
|
||||
[[play](todo)]
|
||||
|
||||
<h3 id="mathutil"> 13. mathutil 包实现了一些数学计算的函数。 <a href="#index">回到目录</a></h3>
|
||||
|
||||
@@ -1161,7 +1251,7 @@ import "github.com/duke-git/lancet/v2/random"
|
||||
- **<big>UUIdV4</big>** : 生成 UUID v4 字符串。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/random.md#UUIdV4)]
|
||||
[[play](https://go.dev/play/p/_Z9SFmr28ft)]
|
||||
- **<big>RandUniqueIntSlice</big>** : 生成一个不重复的长度为 n 的随机 int 切片。
|
||||
- **<big>RandUniqueIntSlice</big>** : 生成一个不重复的随机int切片。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/random.md#RandUniqueIntSlice)]
|
||||
[[play](https://go.dev/play/p/uBkRSOz73Ec)]
|
||||
- **<big>RandSymbolChar</big>** : 生成给定长度的随机符号字符串。
|
||||
@@ -1173,6 +1263,24 @@ import "github.com/duke-git/lancet/v2/random"
|
||||
- **<big>RandFloats</big>** : 生成随机float64数字切片,可以指定长度,范围和精度.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/random.md#RandFloats)]
|
||||
[[play](https://go.dev/play/p/uBkRSOz73Ec)]
|
||||
- **<big>RandStringSlice</big>** : 生成随机字符串slice。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/random.md#RandStringSlice)]
|
||||
[[play](todo)]
|
||||
- **<big>RandBool</big>** : 生成随机bool值(true or false)。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/random.md#RandBool)]
|
||||
[[play](todo)]
|
||||
- **<big>RandBoolSlice</big>** : 生成特定长度的随机bool slice。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/random.md#RandBoolSlice)]
|
||||
[[play](todo)]
|
||||
- **<big>RandIntSlice</big>** : 生成一个特定长度的随机int切片,数值范围[min, max)。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/random.md#RandIntSlice)]
|
||||
[[play](todo)]
|
||||
- **<big>RandFromGivenSlice</big>** : 从给定切片中随机生成元素。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/random.md#RandFromGivenSlice)]
|
||||
[[play](todo)]
|
||||
- **<big>RandSliceFromGivenSlice</big>** : 从给定切片中生成长度为 num 的随机切片。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/random.md#RandSliceFromGivenSlice)]
|
||||
[[play](todo)]
|
||||
|
||||
|
||||
<h3 id="retry"> 17. retry 重试执行函数直到函数运行成功或被 context cancel。 <a href="#index">回到目录</a></h3>
|
||||
@@ -1192,9 +1300,6 @@ import "github.com/duke-git/lancet/v2/retry"
|
||||
- **<big>RetryFunc</big>** : 重试执行的函数。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/retry.md#RetryFunc)]
|
||||
[[play](https://go.dev/play/p/nk2XRmagfVF)]
|
||||
- **<big>RetryDuration</big>** : 设置重试间隔时间,默认 3 秒。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/retry.md#RetryDuration)]
|
||||
[[play](https://go.dev/play/p/nk2XRmagfVF)]
|
||||
- **<big>RetryTimes</big>** : 设置重试次数,默认 5。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/retry.md#RetryTimes)]
|
||||
[[play](https://go.dev/play/p/ssfVeU2SwLO)]
|
||||
@@ -1202,10 +1307,13 @@ import "github.com/duke-git/lancet/v2/retry"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/retry.md#BackoffStrategy)]
|
||||
- **<big>RetryWithCustomBackoff</big>** : 设置自定义退避策略。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/retry.md#RetryWithCustomBackoff)]
|
||||
[[play](https://go.dev/play/p/jIm_o2vb5Y4)]
|
||||
- **<big>RetryWithLinearBackoff</big>** : 设置线性策略退避。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/retry.md#RetryWithLinearBackoff)]
|
||||
[[play](https://go.dev/play/p/PDet2ZQZwcB)]
|
||||
- **<big>RetryWithExponentialWithJitterBackoff</big>** : 设置指数策略退避。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/retry.md#RetryWithExponentialWithJitterBackoff)]
|
||||
[[play](https://go.dev/play/p/xp1avQmn16X)]
|
||||
|
||||
|
||||
|
||||
@@ -1310,6 +1418,9 @@ import "github.com/duke-git/lancet/v2/slice"
|
||||
- **<big>ForEach</big>** : 遍历切片的元素并为每个元素调用 iteratee 函数。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#ForEach)]
|
||||
[[play](https://go.dev/play/p/DrPaa4YsHRF)]
|
||||
- **<big>ForEachConcurrent</big>** : 对slice并发执行foreach操作。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#ForEachConcurrent)]
|
||||
[[play](todo)]
|
||||
- **<big>ForEachWithBreak</big>** : 遍历切片的元素并为每个元素调用 iteratee 函数,当 iteratee 函数返回 false 时,终止遍历。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#ForEachWithBreak)]
|
||||
[[play](https://go.dev/play/p/qScs39f3D9W)]
|
||||
@@ -1340,6 +1451,9 @@ import "github.com/duke-git/lancet/v2/slice"
|
||||
- **<big>Map</big>** : 对 slice 中的每个元素执行 map 函数以创建一个新切片。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#Map)]
|
||||
[[play](https://go.dev/play/p/biaTefqPquw)]
|
||||
- **<big>MapConcurrent</big>** : 对slice并发执行map操作。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#MapConcurrent)]
|
||||
[[play](todo)]
|
||||
- **<big>Merge</big>** : 合并多个切片(不会消除重复元素)。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#Merge)]
|
||||
[[play](https://go.dev/play/p/lbjFp784r9N)]
|
||||
@@ -1355,6 +1469,9 @@ import "github.com/duke-git/lancet/v2/slice"
|
||||
- **<big>ReduceRight</big>** : 类似 ReduceBy 操作,迭代切片元素顺序从右至左。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#ReduceRight)]
|
||||
[[play](https://go.dev/play/p/qT9dZC03A1K)]
|
||||
- **<big>ReduceConcurrent</big>** : 对切片元素执行并发reduce操作。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#ReduceConcurrent)]
|
||||
[[play](todo)]
|
||||
- **<big>Replace</big>** : 返回切片的副本,其中前 n 个不重叠的 old 替换为 new。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#Replace)]
|
||||
[[play](https://go.dev/play/p/P5mZp7IhOFo)]
|
||||
@@ -1406,9 +1523,18 @@ import "github.com/duke-git/lancet/v2/slice"
|
||||
- **<big>Unique</big>** : 删除切片中的重复元素。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#Unique)]
|
||||
[[play](https://go.dev/play/p/AXw0R3ZTE6a)]
|
||||
- **<big>UniqueBy</big>** : 对切片的每个元素调用 iteratee 函数,然后删除重复元素。
|
||||
- **<big>UniqueBy</big>** : 根据迭代函数返回的值,从输入切片中移除重复元素。此函数保持元素的顺序。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#UniqueBy)]
|
||||
[[play](https://go.dev/play/p/UR323iZLDpv)]
|
||||
- **<big>UniqueByComparator</big>** : 使用提供的比较器函数从输入切片中移除重复元素。此函数保持元素的顺序。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#UniqueByComparator)]
|
||||
[[play](todo)]
|
||||
- **<big>UniqueByField</big>** : 根据struct字段对struct切片去重复。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#UniqueByField)]
|
||||
[[play](https://go.dev/play/p/6cifcZSPIGu)]
|
||||
- **<big>UniqueByConcurrent</big>** : 并发的从输入切片中移除重复元素,结果保持元素的顺序。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#UniqueByConcurrent)]
|
||||
[[play](todo)]
|
||||
- **<big>Union</big>** : 合并多个切片。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#Union)]
|
||||
[[play](https://go.dev/play/p/hfXV1iRIZOf)]
|
||||
@@ -1439,8 +1565,13 @@ import "github.com/duke-git/lancet/v2/slice"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#Break)]
|
||||
- **<big>RightPadding</big>** : 在切片的右部添加元素。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#RightPadding)]
|
||||
[[play](https://go.dev/play/p/0_2rlLEMBXL)]
|
||||
- **<big>LeftPadding</big>** : 在切片的左部添加元素。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#LeftPadding)]
|
||||
[[play](https://go.dev/play/p/jlQVoelLl2k)]
|
||||
- **<big>Frequency</big>** : 计算切片中每个元素出现的频率。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#Frequency)]
|
||||
[[play](todo)]
|
||||
|
||||
|
||||
<h3 id="stream"> 19. stream 流,该包仅验证简单的 stream 实现,功能有限。 <a href="#index">回到目录</a></h3>
|
||||
@@ -1469,12 +1600,15 @@ import "github.com/duke-git/lancet/v2/stream"
|
||||
- **<big>Concat</big>** : 创建一个延迟连接 stream,其元素是第一个 stream 的所有元素,后跟第二个 stream 的全部元素。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/stream.md#Concat)]
|
||||
[[play](https://go.dev/play/p/HM4OlYk_OUC)]
|
||||
- **<big>Distinct</big>** : 创建并返回一个 stream,用于删除重复的项。
|
||||
- **<big>Distinct</big>** : 创建并返回一个stream,用于删除重复的项。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/stream.md#Distinct)]
|
||||
[[play](https://go.dev/play/p/eGkOSrm64cB)]
|
||||
- **<big>Filter</big>** : 返回一个通过判定函数的 stream。
|
||||
- **<big>Filter</big>** : 返回一个通过判定函数的stream。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/stream.md#Filter)]
|
||||
[[play](https://go.dev/play/p/MFlSANo-buc)]
|
||||
- **<big>FilterConcurrent</big>** : 对slice并发执行filter操作。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/stream.md#FilterConcurrent)]
|
||||
[[play](todo)]
|
||||
- **<big>Map</big>** : 返回一个 stream,该 stream 由将给定函数应用于源 stream 元素的元素组成。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/stream.md#Map)]
|
||||
[[play](https://go.dev/play/p/OtNQUImdYko)]
|
||||
@@ -1696,7 +1830,21 @@ import "github.com/duke-git/lancet/v2/strutil"
|
||||
- **<big>HammingDistance</big>** : 计算两个字符串之间的汉明距离。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/strutil.md#HammingDistance)]
|
||||
[[play](https://go.dev/play/p/glNdQEA9HUi)]
|
||||
|
||||
- **<big>Concat</big>** : 拼接字符串。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/strutil.md#Concat)]
|
||||
[[play](todo)]
|
||||
- **<big>Ellipsis</big>** : 将字符串截断到指定长度,并在末尾添加省略号。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/strutil.md#Ellipsis)]
|
||||
[[play](todo)]
|
||||
- **<big>Shuffle</big>** : 打乱给定字符串中的字符顺序。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/strutil.md#Shuffle)]
|
||||
[[play](todo)]
|
||||
- **<big>Rotate</big>** : 按指定的字符数旋转字符串。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/strutil.md#Rotate)]
|
||||
[[play](todo)]
|
||||
- **<big>TemplateReplace</big>** : 将模板字符串中的占位符替换为map中的相应值。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/strutil.md#TemplateReplace)]
|
||||
[[play](todo)]
|
||||
|
||||
<h3 id="system"> 22. system 包含 os, runtime, shell command 的相关函数。 <a href="#index">回到目录</a></h3>
|
||||
|
||||
@@ -1733,6 +1881,20 @@ import "github.com/duke-git/lancet/v2/system"
|
||||
- **<big>GetOsBits</big>** : 获取当前操作系统位数(32/64)。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/system#GetOsBits)]
|
||||
[[play](https://go.dev/play/p/ml-_XH3gJbW)]
|
||||
- **<big>StartProcess</big>** :创建进程。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/system.md#StartProcess)]
|
||||
[[play](todo)]
|
||||
- **<big>StopProcess</big>** : 停止进程。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/system.md#StopProcess)]
|
||||
[[play](todo)]
|
||||
- **<big>KillProcess</big>** : 杀掉进程。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/system.md#KillProcess)]
|
||||
[[play](todo)]
|
||||
- **<big>GetProcessInfo</big>** : 根据进程id获取进程信息。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/system.md#GetProcessInfo)]
|
||||
[[play](todo)]
|
||||
|
||||
|
||||
|
||||
<h3 id="tuple"> 23. Tuple 包实现一个元组数据类型。 <a href="#index">回到目录</a></h3>
|
||||
|
||||
@@ -1968,7 +2130,7 @@ import "github.com/duke-git/lancet/v2/validator"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsBase64URL)]
|
||||
[[play](https://go.dev/play/p/vhl4mr8GZ6S)]
|
||||
- **<big>IsJWT</big>** : 检查字符串是否是有效的 JSON Web Token (JWT)。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsJWT)]
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsJWT)]
|
||||
[[play](https://go.dev/play/p/R6Op7heJbKI)]
|
||||
- **<big>IsVisa</big>** : 检查字符串是否是有效的 visa 卡号。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsVisa)]
|
||||
@@ -2036,7 +2198,7 @@ import "github.com/duke-git/lancet/v2/xerror"
|
||||
|
||||
## 如何贡献代码
|
||||
|
||||
#### [贡献代码指南](./CONTRIBUTING.zh-CN.md)
|
||||
#### [代码贡献指南](./CONTRIBUTION.zh-CN.md)
|
||||
|
||||
## 贡献者
|
||||
|
||||
@@ -2045,3 +2207,7 @@ import "github.com/duke-git/lancet/v2/xerror"
|
||||
<a href="https://github.com/duke-git/lancet/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=duke-git/lancet" />
|
||||
</a>
|
||||
|
||||
## GitHub Stars
|
||||
|
||||
[](https://star-history.com/#duke-git/lancet&Date)
|
||||
@@ -49,9 +49,7 @@ func Or[T, U any](a T, b U) bool {
|
||||
// Xor returns true if a or b but not both is truthy.
|
||||
// Play: https://go.dev/play/p/gObZrW7ZbG8
|
||||
func Xor[T, U any](a T, b U) bool {
|
||||
valA := Bool(a)
|
||||
valB := Bool(b)
|
||||
return (valA || valB) && valA != valB
|
||||
return Bool(a) != Bool(b)
|
||||
}
|
||||
|
||||
// Nor returns true if neither a nor b is truthy.
|
||||
@@ -63,9 +61,7 @@ func Nor[T, U any](a T, b U) bool {
|
||||
// Xnor returns true if both a and b or neither a nor b are truthy.
|
||||
// Play: https://go.dev/play/p/OuDB9g51643
|
||||
func Xnor[T, U any](a T, b U) bool {
|
||||
valA := Bool(a)
|
||||
valB := Bool(b)
|
||||
return (valA && valB) || (!valA && !valB)
|
||||
return Bool(a) == Bool(b)
|
||||
}
|
||||
|
||||
// Nand returns false if both a and b are truthy.
|
||||
|
||||
@@ -244,6 +244,56 @@ func AesOfbDecrypt(data, key []byte) []byte {
|
||||
return decrypted
|
||||
}
|
||||
|
||||
// AesGcmEncrypt encrypt data with key use AES GCM algorithm
|
||||
// Play: todo
|
||||
func AesGcmEncrypt(data, key []byte) []byte {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ciphertext := gcm.Seal(nonce, nonce, data, nil)
|
||||
|
||||
return ciphertext
|
||||
}
|
||||
|
||||
// AesGcmDecrypt decrypt data with key use AES GCM algorithm
|
||||
// Play: todo
|
||||
func AesGcmDecrypt(data, key []byte) []byte {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
nonceSize := gcm.NonceSize()
|
||||
if len(data) < nonceSize {
|
||||
panic("ciphertext too short")
|
||||
}
|
||||
|
||||
nonce, ciphertext := data[:nonceSize], data[nonceSize:]
|
||||
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return plaintext
|
||||
}
|
||||
|
||||
// DesEcbEncrypt encrypt data with key use DES ECB algorithm
|
||||
// len(key) should be 8.
|
||||
// Play: https://go.dev/play/p/8qivmPeZy4P
|
||||
|
||||
@@ -129,6 +129,34 @@ func ExampleAesOfbDecrypt() {
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleAesGcmEncrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
encrypted := AesGcmEncrypt([]byte(data), []byte(key))
|
||||
|
||||
decrypted := AesGcmDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleAesGcmDecrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
encrypted := AesGcmEncrypt([]byte(data), []byte(key))
|
||||
|
||||
decrypted := AesGcmDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleDesEcbEncrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefgh"
|
||||
|
||||
@@ -168,3 +168,17 @@ func TestRsaEncryptOAEP(t *testing.T) {
|
||||
assert.IsNil(err)
|
||||
assert.Equal("hello world", string(decrypted))
|
||||
}
|
||||
|
||||
func TestAesGcmEncrypt(t *testing.T) {
|
||||
|
||||
t.Parallel()
|
||||
|
||||
data := "hello world"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
encrypted := AesGcmEncrypt([]byte(data), []byte(key))
|
||||
decrypted := AesGcmDecrypt(encrypted, []byte(key))
|
||||
|
||||
assert := internal.NewAssert(t, "TestAesGcmEncrypt")
|
||||
assert.Equal(data, string(decrypted))
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ package datetime
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -382,11 +383,63 @@ func TimestampNano(timezone ...string) int64 {
|
||||
return t.UnixNano()
|
||||
}
|
||||
|
||||
// TraceFuncTime: trace the func costed time,just call it at top of the func like `defer TraceFuncTime()()`
|
||||
func TraceFuncTime() func() {
|
||||
pre := time.Now()
|
||||
// TrackFuncTime track the time of function execution.
|
||||
// call it at top of the func like `defer TrackFuncTime(time.Now())()`
|
||||
// Play: todo
|
||||
func TrackFuncTime(pre time.Time) func() {
|
||||
callerName := getCallerName()
|
||||
return func() {
|
||||
elapsed := time.Since(pre)
|
||||
fmt.Println("Costs Time:\t", elapsed)
|
||||
fmt.Printf("Function %s execution time:\t %v", callerName, elapsed)
|
||||
}
|
||||
}
|
||||
|
||||
func getCallerName() string {
|
||||
pc, _, _, ok := runtime.Caller(2)
|
||||
if !ok {
|
||||
return "Unknown"
|
||||
}
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn == nil {
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
fullName := fn.Name()
|
||||
if lastDot := strings.LastIndex(fullName, "."); lastDot != -1 {
|
||||
return fullName[lastDot+1:]
|
||||
}
|
||||
|
||||
return fullName
|
||||
}
|
||||
|
||||
// DaysBetween returns the number of days between two times.
|
||||
// Play: todo
|
||||
func DaysBetween(start, end time.Time) int {
|
||||
duration := end.Sub(start)
|
||||
days := int(duration.Hours() / 24)
|
||||
|
||||
return days
|
||||
}
|
||||
|
||||
// GenerateDatetimesBetween returns a slice of strings between two times.
|
||||
// layout: the format of the datetime string
|
||||
// interval: the interval between two datetimes
|
||||
// Play: todo
|
||||
func GenerateDatetimesBetween(start, end time.Time, layout string, interval string) ([]string, error) {
|
||||
var result []string
|
||||
|
||||
if start.After(end) {
|
||||
start, end = end, start
|
||||
}
|
||||
|
||||
duration, err := time.ParseDuration(interval)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for current := start; !current.After(end); current = current.Add(duration) {
|
||||
result = append(result, current.Format(layout))
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -408,3 +408,32 @@ func ExampleIsWeekend() {
|
||||
// true
|
||||
// false
|
||||
}
|
||||
|
||||
func ExampleDaysBetween() {
|
||||
start := time.Date(2024, time.September, 1, 0, 0, 0, 0, time.UTC)
|
||||
end := time.Date(2024, time.September, 10, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
result := DaysBetween(start, end)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// 9
|
||||
}
|
||||
|
||||
func ExampleGenerateDatetimesBetween() {
|
||||
start := time.Date(2024, time.September, 1, 0, 0, 0, 0, time.UTC)
|
||||
end := time.Date(2024, time.September, 1, 2, 0, 0, 0, time.UTC)
|
||||
|
||||
layout := "2006-01-02 15:04:05"
|
||||
interval := "1h"
|
||||
|
||||
result, err := GenerateDatetimesBetween(start, end, layout, interval)
|
||||
|
||||
fmt.Println(result)
|
||||
fmt.Println(err)
|
||||
|
||||
// Output:
|
||||
// [2024-09-01 00:00:00 2024-09-01 01:00:00 2024-09-01 02:00:00]
|
||||
// <nil>
|
||||
}
|
||||
|
||||
@@ -410,3 +410,114 @@ func TestTimestamp(t *testing.T) {
|
||||
ts4 := TimestampNano()
|
||||
t.Log(ts4)
|
||||
}
|
||||
|
||||
func TestTrackFuncTime(t *testing.T) {
|
||||
defer TrackFuncTime(time.Now())()
|
||||
|
||||
var n int
|
||||
for i := 0; i < 5000000; i++ {
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaysBetween(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestDaysBetween")
|
||||
|
||||
tests := []struct {
|
||||
start time.Time
|
||||
end time.Time
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
start: time.Date(2024, time.September, 1, 0, 0, 0, 0, time.UTC),
|
||||
end: time.Date(2024, time.September, 10, 0, 0, 0, 0, time.UTC),
|
||||
expected: 9,
|
||||
},
|
||||
{
|
||||
start: time.Date(2024, time.September, 10, 0, 0, 0, 0, time.UTC),
|
||||
end: time.Date(2024, time.September, 1, 0, 0, 0, 0, time.UTC),
|
||||
expected: -9,
|
||||
},
|
||||
{
|
||||
start: time.Date(2024, time.September, 1, 0, 0, 0, 0, time.UTC),
|
||||
end: time.Date(2024, time.September, 1, 0, 0, 0, 0, time.UTC),
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
start: time.Date(2024, time.January, 1, 0, 0, 0, 0, time.UTC),
|
||||
end: time.Date(2024, time.December, 31, 0, 0, 0, 0, time.UTC),
|
||||
expected: 365,
|
||||
},
|
||||
{
|
||||
start: time.Date(2024, time.March, 1, 0, 0, 0, 0, time.UTC),
|
||||
end: time.Date(2024, time.March, 31, 0, 0, 0, 0, time.UTC),
|
||||
expected: 30,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
result := DaysBetween(tt.start, tt.end)
|
||||
assert.Equal(tt.expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateDatetimesBetween(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestGenerateDatetimesBetween")
|
||||
|
||||
tests := []struct {
|
||||
start time.Time
|
||||
end time.Time
|
||||
layout string
|
||||
interval string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
start: time.Date(2024, time.September, 1, 0, 0, 0, 0, time.UTC),
|
||||
end: time.Date(2024, time.September, 1, 2, 0, 0, 0, time.UTC),
|
||||
layout: "2006-01-02 15:04:05",
|
||||
interval: "30m",
|
||||
expected: []string{
|
||||
"2024-09-01 00:00:00",
|
||||
"2024-09-01 00:30:00",
|
||||
"2024-09-01 01:00:00",
|
||||
"2024-09-01 01:30:00",
|
||||
"2024-09-01 02:00:00",
|
||||
},
|
||||
},
|
||||
{
|
||||
start: time.Date(2024, time.September, 1, 0, 0, 0, 0, time.UTC),
|
||||
end: time.Date(2024, time.September, 1, 0, 0, 0, 0, time.UTC),
|
||||
layout: "2006-01-02 15:04:05",
|
||||
interval: "1h",
|
||||
expected: []string{"2024-09-01 00:00:00"},
|
||||
},
|
||||
{
|
||||
start: time.Date(2024, time.September, 1, 0, 0, 0, 0, time.UTC),
|
||||
end: time.Date(2024, time.September, 1, 3, 0, 0, 0, time.UTC),
|
||||
layout: "2006-01-02 15:04:05",
|
||||
interval: "2h",
|
||||
expected: []string{
|
||||
"2024-09-01 00:00:00",
|
||||
"2024-09-01 02:00:00",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
result, err := GenerateDatetimesBetween(tt.start, tt.end, tt.layout, tt.interval)
|
||||
|
||||
assert.Equal(tt.expected, result)
|
||||
assert.IsNil(err)
|
||||
}
|
||||
|
||||
t.Run("Invalid interval", func(t *testing.T) {
|
||||
_, err := GenerateDatetimesBetween(time.Now(), time.Now(), "2006-01-02 15:04:05", "invalid")
|
||||
if err == nil {
|
||||
t.Fatal("Expected error, got nil")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ export const slugify = (str: string): string =>
|
||||
export const commonConfig = defineConfig({
|
||||
title: 'Lancet',
|
||||
appearance: true,
|
||||
ignoreDeadLinks: true,
|
||||
|
||||
markdown: {
|
||||
theme: {
|
||||
@@ -83,7 +84,7 @@ export const commonConfig = defineConfig({
|
||||
|
||||
footer: {
|
||||
copyright: 'Copyright © 2023-present Duke Du',
|
||||
message: '备案号: 京ICP备2023022770号',
|
||||
message: '<a href="https://beian.miit.gov.cn/" target="_blank">京ICP备2023022770号-1</a>',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -42,7 +42,7 @@ export const enConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
||||
},
|
||||
{
|
||||
text: 'Contribution',
|
||||
link: 'https://github.com/duke-git/lancet/blob/main/CONTRIBUTING.en-US.md',
|
||||
link: 'https://github.com/duke-git/lancet/blob/main/CONTRIBUTION.md',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -51,7 +51,7 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
||||
},
|
||||
{
|
||||
text: '参与贡献',
|
||||
link: 'https://github.com/duke-git/lancet/blob/main/CONTRIBUTING.zh-CN.md',
|
||||
link: 'https://github.com/duke-git/lancet/blob/main/CONTRIBUTION.zh-CN.md',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -32,6 +32,8 @@ import (
|
||||
- [AesCfbDecrypt](#AesCfbDecrypt)
|
||||
- [AesOfbEncrypt](#AesOfbEncrypt)
|
||||
- [AesOfbDecrypt](#AesOfbDecrypt)
|
||||
- [AesGcmEncrypt](#AesGcmEncrypt)
|
||||
- [AesGcmDecrypt](#AesGcmDecrypt)
|
||||
- [Base64StdEncode](#Base64StdEncode)
|
||||
- [Base64StdDecode](#Base64StdDecode)
|
||||
- [DesEcbEncrypt](#DesEcbEncrypt)
|
||||
@@ -379,6 +381,74 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="AesGcmEncrypt">AesGcmEncrypt</span>
|
||||
|
||||
<p>使用AES GCM算法模式加密数据。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func AesGcmEncrypt(data, key []byte) []byte
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行]()</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
encrypted := cryptor.AesGcmEncrypt([]byte(data), []byte(key))
|
||||
decrypted := cryptor.AesGcmDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="AesGcmDecrypt">AesGcmDecrypt</span>
|
||||
|
||||
<p>使用AES GCM算法解密数据。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func AesGcmDecrypt(data, key []byte) []byte
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行]()</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
encrypted := cryptor.AesGcmEncrypt([]byte(data), []byte(key))
|
||||
decrypted := cryptor.AesGcmDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Base64StdEncode">Base64StdEncode</span>
|
||||
|
||||
<p>将字符串base64编码。</p>
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
|
||||
- [New](#New)
|
||||
- [FromSlice](#FromSlice)
|
||||
- [Values](#Values)
|
||||
- [Values<sup>deprecated</sup>](#Values)
|
||||
- [Add](#Add)
|
||||
- [AddIfNotExist](#AddIfNotExist)
|
||||
- [AddIfNotExistBy](#AddIfNotExistBy)
|
||||
@@ -101,10 +101,11 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Values">Values<sup>deprecated</sup></span>
|
||||
### <span id="Values">Values</span>
|
||||
|
||||
<p>获取集合中所有元素的切片<br>
|
||||
<a href='#ToSlice'>ToSlice()</a> 方法提供与 Values 方法相同的功能</p>
|
||||
<p>获取集合中所有元素的切片。</p>
|
||||
|
||||
> ⚠️ 本函数已弃用,使用`ToSlice`代替。
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
|
||||
@@ -64,6 +64,9 @@ import (
|
||||
- [TimestampMilli](#TimestampMilli)
|
||||
- [TimestampMicro](#TimestampMicro)
|
||||
- [TimestampNano](#TimestampNano)
|
||||
- [TrackFuncTime](#TrackFuncTime)
|
||||
- [DaysBetween](#DaysBetween)
|
||||
- [GenerateDatetimesBetween](#GenerateDatetimesBetween)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -1334,7 +1337,7 @@ import (
|
||||
func main() {
|
||||
result1 := datetime.NowDateOrTime("yyyy-mm-dd hh:mm:ss")
|
||||
|
||||
result2 := datetime.NowDateOrTime("yyyy-mm-dd hh:mm:ss", "EST")
|
||||
result2 := datetime.NowDateOrTime("yyyy-mm-dd hh:mm:ss", "EST")
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
@@ -1464,4 +1467,107 @@ func main() {
|
||||
// Output:
|
||||
// 1690363051331788000
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="TrackFuncTime">TrackFuncTime</span>
|
||||
|
||||
<p>测试函数执行时间。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func TrackFuncTime(pre time.Time) func()
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/datetime"
|
||||
)
|
||||
|
||||
func main() {
|
||||
defer datetime.TrackFuncTime(time.Now())()
|
||||
|
||||
var n int
|
||||
for i := 0; i < 5000000; i++ {
|
||||
n++
|
||||
}
|
||||
|
||||
fmt.Println(1) // Function main execution time: 1.460287ms
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="DaysBetween">DaysBetween</span>
|
||||
|
||||
<p>返回两个日期之间的天数差。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func DaysBetween(start, end time.Time) int
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/datetime"
|
||||
)
|
||||
|
||||
func main() {
|
||||
start := time.Date(2024, time.September, 1, 0, 0, 0, 0, time.UTC)
|
||||
end := time.Date(2024, time.September, 10, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
result := datetime.DaysBetween(start, end)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// 9
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="GenerateDatetimesBetween">GenerateDatetimesBetween</span>
|
||||
|
||||
<p>生成从start到end的所有日期时间的字符串列表。layout参数表示时间格式,例如"2006-01-02 15:04:05",interval参数表示时间间隔,例如"1h"表示1小时,"30m"表示30分钟。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func GenerateDatetimesBetween(start, end time.Time, layout string, interval string) ([]string, error)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/datetime"
|
||||
)
|
||||
|
||||
func main() {
|
||||
start := time.Date(2024, time.September, 1, 0, 0, 0, 0, time.UTC)
|
||||
end := time.Date(2024, time.September, 1, 2, 0, 0, 0, time.UTC)
|
||||
|
||||
layout := "2006-01-02 15:04:05"
|
||||
interval := "1h"
|
||||
|
||||
result, err := datetime.GenerateDatetimesBetween(start, end, layout, interval)
|
||||
|
||||
fmt.Println(result)
|
||||
fmt.Println(err)
|
||||
|
||||
// Output:
|
||||
// [2024-09-01 00:00:00 2024-09-01 01:00:00 2024-09-01 02:00:00]
|
||||
// <nil>
|
||||
}
|
||||
```
|
||||
@@ -37,12 +37,12 @@ import (
|
||||
|
||||
### <span id="Comma">Comma</span>
|
||||
|
||||
<p>用逗号每隔3位分割数字/字符串,支持前缀添加符号。参数value必须是数字或者可以转为数字的字符串, 否则返回空字符串</p>
|
||||
<p>用逗号每隔3位分割数字/字符串,支持添加前缀符号。参数value必须是数字或者可以转为数字的字符串, 否则返回空字符串</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func Comma[T constraints.Float | constraints.Integer | string](value T, symbol string) string
|
||||
func Comma[T constraints.Float | constraints.Integer | string](value T, prefixSymbol string) string
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/eRD5k2vzUVX)</span></b>
|
||||
|
||||
@@ -28,7 +28,8 @@ import (
|
||||
- [Before](#Before)
|
||||
- [CurryFn](#CurryFn)
|
||||
- [Compose](#Compose)
|
||||
- [Debounced](#Debounced)
|
||||
- [Debounce](#Debounce)
|
||||
- [Debounced<sup>deprecated</sup>](#Debounced)
|
||||
- [Delay](#Delay)
|
||||
- [Schedule](#Schedule)
|
||||
- [Pipeline](#Pipeline)
|
||||
@@ -40,6 +41,7 @@ import (
|
||||
- [Xnor](#Xnor)
|
||||
- [Nand](#Nand)
|
||||
- [AcceptIf](#AcceptIf)
|
||||
- [Throttle](#Throttle)
|
||||
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
@@ -193,9 +195,58 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Debounce">Debounce</span>
|
||||
|
||||
<p>创建一个函数的去抖动版本。该去抖动函数仅在上次调用后的指定延迟时间过去之后才会调用原始函数。支持取消去抖动。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func Debounce(fn func(), delay time.Duration) (debouncedFn func(), cancelFn func())
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/function"
|
||||
)
|
||||
|
||||
func main() {
|
||||
callCount := 0
|
||||
fn := func() {
|
||||
callCount++
|
||||
}
|
||||
|
||||
debouncedFn, _ := function.Debounce(fn, 500*time.Millisecond)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
debouncedFn()
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
fmt.Println(callCount)
|
||||
|
||||
debouncedFn()
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
fmt.Println(callCount)
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
// 2
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Debounced">Debounced</span>
|
||||
|
||||
<p>创建一个debounced函数,该函数延迟调用fn直到自上次调用debounced函数后等待持续时间过去。</p>
|
||||
<p>创建一个函数的去抖动版本。</p>
|
||||
|
||||
> ⚠️ 本函数已弃用. 使用 `Debounce` 代替.
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
@@ -690,4 +741,46 @@ func main() {
|
||||
// false
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### <span id="Throttle">Throttle</span>
|
||||
|
||||
<p>创建一个函数的节流版本。返回的函数保证在每个时间间隔内最多只会被调用一次。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func Throttle(fn func(), interval time.Duration) func()
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/function"
|
||||
)
|
||||
|
||||
func main() {
|
||||
callCount := 0
|
||||
|
||||
fn := func() {
|
||||
callCount++
|
||||
}
|
||||
|
||||
throttledFn := function.Throttle(fn, 1*time.Second)
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
throttledFn()
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
fmt.Println(callCount)
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
}
|
||||
```
|
||||
File diff suppressed because it is too large
Load Diff
@@ -624,7 +624,9 @@ func main() {
|
||||
|
||||
### <span id="HttpGet">HttpGet</span>
|
||||
|
||||
<p>发送http get请求。(已废弃:使用SendRequest)</p>
|
||||
<p>发送http get请求。</p>
|
||||
|
||||
> ⚠️ 本函数已弃用,使用`SendRequest`代替。
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
@@ -666,7 +668,9 @@ func main() {
|
||||
|
||||
### <span id="HttpPost">HttpPost</span>
|
||||
|
||||
<p>发送http post请求。(已废弃:使用SendRequest)</p>
|
||||
<p>发送http post请求。</p>
|
||||
|
||||
> ⚠️ 本函数已弃用,使用`SendRequest`代替。
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
@@ -713,7 +717,9 @@ func main() {
|
||||
|
||||
### <span id="HttpPut">HttpPut</span>
|
||||
|
||||
<p>发送http put请求。(已废弃:使用SendRequest)</p>
|
||||
<p>发送http put请求。</p>
|
||||
|
||||
> ⚠️ 本函数已弃用,使用`SendRequest`代替。
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
@@ -763,7 +769,9 @@ func main() {
|
||||
|
||||
### <span id="HttpDelete">HttpDelete</span>
|
||||
|
||||
<p>发送http delete请求。(已废弃:使用SendRequest)</p>
|
||||
<p>发送http delete请求。</p>
|
||||
|
||||
> ⚠️ 本函数已弃用,使用`SendRequest`代替。
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
@@ -802,7 +810,9 @@ func main() {
|
||||
|
||||
### <span id="HttpPatch">HttpPatch</span>
|
||||
|
||||
<p>发送http patch请求。(已废弃:使用SendRequest)</p>
|
||||
<p>发送http patch请求。</p>
|
||||
|
||||
> ⚠️ 本函数已弃用,使用`SendRequest`代替。
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ import (
|
||||
- [Of](#Of)
|
||||
- [Unwrap](#Unwrap)
|
||||
- [ExtractPointer](#ExtractPointer)
|
||||
- [UnwarpOr](#UnwarpOr)
|
||||
- [UnwarpOrDefault](#UnwarpOrDefault)
|
||||
- [UnwrapOr](#UnwrapOr)
|
||||
- [UnwrapOrDefault](#UnwrapOrDefault)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -136,14 +136,14 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="UnwarpOr">UnwarpOr</span>
|
||||
### <span id="UnwrapOr">UnwrapOr</span>
|
||||
|
||||
<p>返回指针的值,如果指针为零值,则返回fallback。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
UnwarpOr[T any](p *T, fallback T) T
|
||||
UnwrapOr[T any](p *T, fallback T) T
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/mmNaLC38W8C)</span></b>
|
||||
@@ -163,10 +163,10 @@ func main() {
|
||||
var c *int
|
||||
var d *string
|
||||
|
||||
result1 := pointer.UnwarpOr(&a, 456)
|
||||
result2 := pointer.UnwarpOr(&b, "abc")
|
||||
result3 := pointer.UnwarpOr(c, 456)
|
||||
result4 := pointer.UnwarpOr(d, "def")
|
||||
result1 := pointer.UnwrapOr(&a, 456)
|
||||
result2 := pointer.UnwrapOr(&b, "abc")
|
||||
result3 := pointer.UnwrapOr(c, 456)
|
||||
result4 := pointer.UnwrapOr(d, "def")
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
@@ -181,14 +181,14 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="UnwarpOrDefault">UnwarpOrDefault</span>
|
||||
### <span id="UnwrapOrDefault">UnwrapOrDefault</span>
|
||||
|
||||
<p>返回指针的值,如果指针为零值,则返回相应零值。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
UnwarpOrDefault[T any](p *T) T
|
||||
UnwrapOrDefault[T any](p *T) T
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/ZnGIHf8_o4E)</span></b>
|
||||
@@ -208,10 +208,10 @@ func main() {
|
||||
var c *int
|
||||
var d *string
|
||||
|
||||
result1 := pointer.UnwarpOrDefault(&a)
|
||||
result2 := pointer.UnwarpOrDefault(&b)
|
||||
result3 := pointer.UnwarpOrDefault(c)
|
||||
result4 := pointer.UnwarpOrDefault(d)
|
||||
result1 := pointer.UnwrapOrDefault(&a)
|
||||
result2 := pointer.UnwrapOrDefault(&b)
|
||||
result3 := pointer.UnwrapOrDefault(c)
|
||||
result4 := pointer.UnwrapOrDefault(d)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
|
||||
@@ -25,15 +25,22 @@ import (
|
||||
- [RandBytes](#RandBytes)
|
||||
- [RandInt](#RandInt)
|
||||
- [RandString](#RandString)
|
||||
- [RandFromGivenSlice](#RandFromGivenSlice)
|
||||
- [RandSliceFromGivenSlice](#RandSliceFromGivenSlice)
|
||||
- [RandUpper](#RandUpper)
|
||||
- [RandLower](#RandLower)
|
||||
- [RandNumeral](#RandNumeral)
|
||||
- [RandNumeralOrLetter](#RandNumeralOrLetter)
|
||||
- [RandSymbolChar](#RandSymbolChar)
|
||||
- [UUIdV4](#UUIdV4)
|
||||
- [RandIntSlice](#RandIntSlice)
|
||||
- [RandUniqueIntSlice](#RandUniqueIntSlice)
|
||||
- [RandFloat](#RandFloat)
|
||||
- [RandFloats](#RandFloats)
|
||||
- [RandStringSlice](#RandStringSlice)
|
||||
- [RandBool](#RandBool)
|
||||
- [RandBoolSlice](#RandBoolSlice)
|
||||
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -117,6 +124,60 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="RandFromGivenSlice">RandFromGivenSlice</span>
|
||||
|
||||
<p>从给定切片中随机生成元素。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func RandFromGivenSlice[T any](slice []T) T
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/random"
|
||||
)
|
||||
|
||||
func main() {
|
||||
nicknames := []string{"张三", "李四", "王五", "赵六", "钱七"}
|
||||
randElm := random.RandFromGivenSlice(nicknames)
|
||||
fmt.Println(randElm)
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="RandSliceFromGivenSlice">RandSliceFromGivenSlice</span>
|
||||
|
||||
<p>从给定切片中生成长度为 num 的随机切片。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func RandSliceFromGivenSlice[T any](slice []T, num int, repeatable bool) []T
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/random"
|
||||
)
|
||||
|
||||
func main() {
|
||||
goods := []string{"apple", "banana", "cherry", "elderberry", "fig", "grape", "honeydew", "kiwi", "lemon","mango", "nectarine", "orange"}
|
||||
chosen3goods := random.RandSliceFromGivenSlice(goods, 3, false)
|
||||
fmt.Println(chosen3goods)
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="RandUpper">RandUpper</span>
|
||||
|
||||
<p>生成给定长度的随机大写字母字符串</p>
|
||||
@@ -276,14 +337,40 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="RandUniqueIntSlice">RandUniqueIntSlice</span>
|
||||
### <span id="RandIntSlice">RandIntSlice</span>
|
||||
|
||||
<p>生成一个不重复的长度为n的随机int切片。</p>
|
||||
<p>生成一个特定长度的随机int切片,数值范围[min, max)。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func RandUniqueIntSlice(n, min, max int) []int
|
||||
func RandIntSlice(length, min, max int) []int
|
||||
```
|
||||
|
||||
<b>示例:</b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/random"
|
||||
)
|
||||
|
||||
func main() {
|
||||
result := random.RandIntSlice(5, 0, 10)
|
||||
fmt.Println(result) //[1 2 7 1 5] (random)
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="RandUniqueIntSlice">RandUniqueIntSlice</span>
|
||||
|
||||
<p>生成一个特定长度的,数值不重复的随机int切片,数值范围[min, max)。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func RandUniqueIntSlice(length, min, max int) []int
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/uBkRSOz73Ec)</span></b>
|
||||
@@ -304,7 +391,7 @@ func main() {
|
||||
|
||||
### <span id="RandFloat">RandFloat</span>
|
||||
|
||||
<p>生成随机float64数字,可以指定范围和精度。</p>
|
||||
<p>生成一个随机float64数值,可以指定精度。数值范围[min, max)。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
@@ -330,7 +417,7 @@ func main() {
|
||||
|
||||
### <span id="RandFloats">RandFloats</span>
|
||||
|
||||
<p>生成随机float64数字切片,指定长度,范围和精度.</p>
|
||||
<p>生成一个特定长度的随机float64切片,可以指定数值精度。数值范围[min, max)。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
@@ -352,4 +439,85 @@ func main() {
|
||||
floatNumbers := random.RandFloats(5, 1.0, 5.0, 2)
|
||||
fmt.Println(floatNumber) //[3.42 3.99 1.3 2.38 4.23] (random)
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="RandStringSlice">RandStringSlice</span>
|
||||
|
||||
<p>生成随机字符串slice. 字符串类型需要是以下几种或者它们的组合: random.Numeral, random.LowwerLetters, random.UpperLetters random.Letters, random.SymbolChars, random.AllChars。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func RandStringSlice(charset string, sliceLen, strLen int) []string
|
||||
```
|
||||
|
||||
<b>实例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/random"
|
||||
)
|
||||
|
||||
func main() {
|
||||
strs := random.RandStringSlice(random.Letters, 4, 6)
|
||||
fmt.Println(strs)
|
||||
|
||||
// output random string slice like below:
|
||||
//[CooSMq RUFjDz FAeMPf heRyGv]
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="RandBool">RandBool</span>
|
||||
|
||||
<p>生成随机bool值(true or false)。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func RandBool() bool
|
||||
```
|
||||
|
||||
<b>实例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/random"
|
||||
)
|
||||
|
||||
func main() {
|
||||
result := random.RandBool()
|
||||
fmt.Println(result) // true or false (random)
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="RandBoolSlice">RandBoolSlice</span>
|
||||
|
||||
<p>生成特定长度的随机bool slice。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func RandBoolSlice(length int) []bool
|
||||
```
|
||||
|
||||
<b>实例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/random"
|
||||
)
|
||||
|
||||
func main() {
|
||||
result := random.RandBoolSlice(2)
|
||||
fmt.Println(result) // [true false] (random)
|
||||
}
|
||||
```
|
||||
@@ -70,7 +70,7 @@ func main() {
|
||||
return errors.New("error occurs")
|
||||
}
|
||||
|
||||
duration := retry.RetryDuration(time.Microsecond*50)
|
||||
duration := retry.RetryWithLinearBackoff(time.Microsecond*50)
|
||||
|
||||
retry.Retry(increaseNumber,
|
||||
duration,
|
||||
@@ -116,7 +116,7 @@ func main() {
|
||||
return errors.New("error occurs")
|
||||
}
|
||||
|
||||
duration := retry.RetryDuration(time.Microsecond*50)
|
||||
duration := retry.RetryWithLinearBackoff(time.Microsecond*50)
|
||||
|
||||
err := retry.Retry(increaseNumber, duration)
|
||||
if err != nil {
|
||||
@@ -173,52 +173,6 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="RetryDuration">RetryDuration</span>
|
||||
|
||||
<p>设置重试间隔时间,默认3秒</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func RetryDuration(d time.Duration)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/nk2XRmagfVF)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"log"
|
||||
"github.com/duke-git/lancet/v2/retry"
|
||||
)
|
||||
|
||||
func main() {
|
||||
number := 0
|
||||
increaseNumber := func() error {
|
||||
number++
|
||||
if number == 3 {
|
||||
return nil
|
||||
}
|
||||
return errors.New("error occurs")
|
||||
}
|
||||
|
||||
duration := retry.RetryDuration(time.Microsecond*50)
|
||||
|
||||
err := retry.Retry(increaseNumber, duration)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(number)
|
||||
|
||||
// Output:
|
||||
// 3
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Retry">Retry</span>
|
||||
|
||||
<p>重试执行函数retryFunc,直到函数运行成功,或被context停止</p>
|
||||
@@ -251,7 +205,7 @@ func main() {
|
||||
return errors.New("error occurs")
|
||||
}
|
||||
|
||||
duration := retry.RetryDuration(time.Microsecond*50)
|
||||
duration := retry.RetryWithLinearBackoff(time.Microsecond*50)
|
||||
|
||||
err := retry.Retry(increaseNumber, duration)
|
||||
if err != nil {
|
||||
@@ -331,7 +285,7 @@ func main() {
|
||||
func RetryWithCustomBackoff(backoffStrategy BackoffStrategy) Option
|
||||
```
|
||||
|
||||
<b>示例:</b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/jIm_o2vb5Y4)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -384,7 +338,7 @@ func main() {
|
||||
func RetryWithLinearBackoff(interval time.Duration) Option
|
||||
```
|
||||
|
||||
<b>示例:</b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/PDet2ZQZwcB)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -429,7 +383,7 @@ func main() {
|
||||
func RetryWithExponentialWithJitterBackoff(interval time.Duration, base uint64, maxJitter time.Duration) Option
|
||||
```
|
||||
|
||||
<b>示例:</b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/xp1avQmn16X)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
@@ -6,7 +6,8 @@ slice 包包含操作切片的方法集合。
|
||||
|
||||
## 源码:
|
||||
|
||||
- [https://github.com/duke-git/lancet/blob/main/slice/slice.go](https://github.com/duke-git/lancet/blob/main/slice/slice.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/slice/slice.go](https://github.com/duke-git/lancet/blob/main/slice/slice.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/slice/slice_concurrent.go](https://github.com/duke-git/lancet/blob/main/slice/slice_concurrent.go)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -44,6 +45,7 @@ import (
|
||||
- [Equal](#Equal)
|
||||
- [EqualWith](#EqualWith)
|
||||
- [Filter](#Filter)
|
||||
- [FilterConcurrent](#FilterConcurrent)
|
||||
- [Find<sup>deprecated</sup>](#Find)
|
||||
- [FindBy](#FindBy)
|
||||
- [FindLast<sup>deprecated</sup>](#FindLast)
|
||||
@@ -51,6 +53,7 @@ import (
|
||||
- [Flatten](#Flatten)
|
||||
- [FlattenDeep](#FlattenDeep)
|
||||
- [ForEach](#ForEach)
|
||||
- [ForEachConcurrent](#ForEachConcurrent)
|
||||
- [ForEachWithBreak](#ForEachWithBreak)
|
||||
- [GroupBy](#GroupBy)
|
||||
- [GroupWith](#GroupWith)
|
||||
@@ -61,11 +64,13 @@ import (
|
||||
- [IndexOf](#IndexOf)
|
||||
- [LastIndexOf](#LastIndexOf)
|
||||
- [Map](#Map)
|
||||
- [MapConcurrent](#MapConcurrent)
|
||||
- [FilterMap](#FilterMap)
|
||||
- [FlatMap](#FlatMap)
|
||||
- [Merge](#Merge)
|
||||
- [Reverse](#Reverse)
|
||||
- [Reduce<sup>deprecated</sup>](#Reduce)
|
||||
- [ReduceConcurrent](#ReduceConcurrent)
|
||||
- [ReduceBy](#ReduceBy)
|
||||
- [ReduceRight](#ReduceRight)
|
||||
- [Replace](#Replace)
|
||||
@@ -86,6 +91,9 @@ import (
|
||||
- [ToSlicePointer](#ToSlicePointer)
|
||||
- [Unique](#Unique)
|
||||
- [UniqueBy](#UniqueBy)
|
||||
- [UniqueByComparator](#UniqueByComparator)
|
||||
- [UniqueByField](#UniqueByField)
|
||||
- [UniqueByConcurrent](#UniqueByConcurrent)
|
||||
- [Union](#Union)
|
||||
- [UnionBy](#UnionBy)
|
||||
- [UpdateAt](#UpdateAt)
|
||||
@@ -97,6 +105,7 @@ import (
|
||||
- [Break](#Break)
|
||||
- [RightPadding](#RightPadding)
|
||||
- [LeftPadding](#LeftPadding)
|
||||
- [Frequency](#Frequency)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -322,12 +331,12 @@ func main() {
|
||||
|
||||
### <span id="Concat">Concat</span>
|
||||
|
||||
<p>合并多个slices到slice中</p>
|
||||
<p>创建一个新的切片,将传入的切片拼接起来返回。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func Concat[T any](slice []T, slices ...[]T) []T
|
||||
func Concat[T any](slices ...[]T) []T
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/gPt-q7zr5mk)</span></b>
|
||||
@@ -896,10 +905,46 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Find">Find (废弃:使用 FindBy)</span>
|
||||
### <span id="FilterConcurrent">FilterConcurrent</span>
|
||||
|
||||
<p>对slice并发执行filter操作。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func FilterConcurrent[T any](slice []T, predicate func(index int, item T) bool, numThreads int) []T
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
nums := []int{1, 2, 3, 4, 5}
|
||||
|
||||
isEven := func(i, num int) bool {
|
||||
return num%2 == 0
|
||||
}
|
||||
|
||||
result := slice.FilterConcurrent(nums, isEven, 2)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// [2 4]
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Find">Find</span>
|
||||
|
||||
<p>遍历slice的元素,返回第一个通过predicate函数真值测试的元素</p>
|
||||
|
||||
> ⚠️ 本函数已弃用,使用`FindBy`代替。
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
@@ -968,10 +1013,12 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="FindLast">FindLast(废弃:使用 FindLastBy)</span>
|
||||
### <span id="FindLast">FindLast</span>
|
||||
|
||||
<p>遍历slice的元素,返回最后一个通过predicate函数真值测试的元素。</p>
|
||||
|
||||
> ⚠️ 本函数已弃用,使用`FindLastBy`代替。
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
@@ -1135,6 +1182,43 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="ForEachConcurrent">ForEachConcurrent</span>
|
||||
|
||||
<p>对slice并发执行foreach操作。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func ForEachConcurrent[T any](slice []T, iteratee func(index int, item T), numThreads int)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行]()</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
nums := []int{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
|
||||
result := make([]int, len(nums))
|
||||
|
||||
addOne := func(index int, value int) {
|
||||
result[index] = value + 1
|
||||
}
|
||||
|
||||
slice.ForEachConcurrent(nums, addOne, 4)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// [2 3 4 5 6 7 8 9]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### <span id="ForEachWithBreak">ForEachWithBreak</span>
|
||||
|
||||
<p>遍历切片的元素并为每个元素调用iteratee函数,当iteratee函数返回false时,终止遍历。</p>
|
||||
@@ -1243,10 +1327,12 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="IntSlice">IntSlice (已弃用: 使用 go1.18+泛型代替)</span>
|
||||
### <span id="IntSlice">IntSlice</span>
|
||||
|
||||
<p>将接口切片转换为int切片</p>
|
||||
|
||||
> ⚠️ 本函数已弃用,使用go1.18+泛型代替。
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
@@ -1272,10 +1358,12 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="InterfaceSlice">InterfaceSlice(已弃用: 使用 go1.18+泛型代替)</span>
|
||||
### <span id="InterfaceSlice">InterfaceSlice</span>
|
||||
|
||||
<p>将值转换为接口切片</p>
|
||||
|
||||
> ⚠️ 本函数已弃用,使用go1.18+泛型代替。
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
@@ -1440,7 +1528,7 @@ func main() {
|
||||
|
||||
### <span id="Map">Map</span>
|
||||
|
||||
<p>对slice中的每个元素执行map函数以创建一个新切片</p>
|
||||
<p>对slice中的每个元素执行map函数以创建一个新切片。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
@@ -1472,6 +1560,38 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="MapConcurrent">MapConcurrent</span>
|
||||
|
||||
<p>对slice并发执行map操作。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func MapConcurrent[T any, U any](slice []T, iteratee func(index int, item T) U, numThreads int) []U
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
nums := []int{1, 2, 3, 4, 5, 6}
|
||||
|
||||
result := slice.MapConcurrent(nums, func(_, n int) int {
|
||||
return n * n
|
||||
}, 4)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// [1 4 9 16 25 36]
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="FilterMap">FilterMap</span>
|
||||
|
||||
<p>返回一个将filter和map操作应用于给定切片的切片。 iteratee回调函数应该返回两个值:1,结果值。2,结果值是否应该被包含在返回的切片中。</p>
|
||||
@@ -1546,6 +1666,8 @@ func main() {
|
||||
|
||||
<p>合并多个切片(不会消除重复元素).</p>
|
||||
|
||||
> ⚠️ 本函数已弃用,使用`Concat`代替。
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
@@ -1605,7 +1727,9 @@ func main() {
|
||||
|
||||
### <span id="Reduce">Reduce</span>
|
||||
|
||||
<p>将切片中的元素依次运行iteratee函数,返回运行结果(废弃:建议使用ReduceBy)</p>
|
||||
<p>将切片中的元素依次运行iteratee函数,返回运行结果。</p>
|
||||
|
||||
> ⚠️ 本函数已弃用,使用`ReduceBy`代替。
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
@@ -1637,6 +1761,38 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="ReduceConcurrent">ReduceConcurrent</span>
|
||||
|
||||
<p>对切片元素执行并发reduce操作。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func ReduceConcurrent[T any](slice []T, initial T, reducer func(index int, item T, agg T) T, numThreads int) T
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
|
||||
|
||||
result := slice.ReduceConcurrent(nums, 0, func(_ int, item, agg int) int {
|
||||
return agg + item
|
||||
}, 1)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// 55
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="ReduceBy">ReduceBy</span>
|
||||
|
||||
<p>对切片元素执行reduce操作。</p>
|
||||
@@ -2131,10 +2287,12 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="StringSlice">StringSlice(已弃用: 使用 go1.18+泛型代替)</span>
|
||||
### <span id="StringSlice">StringSlice</span>
|
||||
|
||||
<p>将接口切片转换为字符串切片</p>
|
||||
|
||||
> ⚠️ 本函数已弃用,使用go1.18+泛型代替。
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
@@ -2283,12 +2441,12 @@ func main() {
|
||||
|
||||
### <span id="UniqueBy">UniqueBy</span>
|
||||
|
||||
<p>对切片的每个元素调用iteratee函数,然后删除重复元素</p>
|
||||
<p>根据迭代函数返回的值,从输入切片中移除重复元素。此函数保持元素的顺序。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func UniqueBy[T comparable](slice []T, iteratee func(item T) T) []T
|
||||
func UniqueBy[T any, U comparable](slice []T, iteratee func(item T) U) []T
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/UR323iZLDpv)</span></b>
|
||||
@@ -2308,7 +2466,114 @@ func main() {
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// [1 2 0]
|
||||
// [1 2 3]
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="UniqueByComparator">UniqueByComparator</span>
|
||||
|
||||
<p>使用提供的比较器函数从输入切片中移除重复元素。此函数保持元素的顺序。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func UniqueByComparator[T comparable](slice []T, comparator func(item T, other T) bool) []T
|
||||
```
|
||||
|
||||
<b>示例:</b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/slice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
uniqueNums := slice.UniqueByComparator([]int{1, 2, 3, 1, 2, 4, 5, 6, 4}, func(item int, other int) bool {
|
||||
return item == other
|
||||
})
|
||||
|
||||
caseInsensitiveStrings := slice.UniqueByComparator([]string{"apple", "banana", "Apple", "cherry", "Banana", "date"}, func(item string, other string) bool {
|
||||
return strings.ToLower(item) == strings.ToLower(other)
|
||||
})
|
||||
|
||||
fmt.Println(uniqueNums)
|
||||
fmt.Println(caseInsensitiveStrings)
|
||||
|
||||
// Output:
|
||||
// [1 2 3 4 5 6]
|
||||
// [apple banana cherry date]
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="UniqueByConcurrent">UniqueByConcurrent</span>
|
||||
|
||||
<p>并发的从输入切片中移除重复元素,结果保持元素的顺序。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func UniqueByConcurrent[T comparable](slice []T, comparator func(item T, other T) bool, numThreads int) []T
|
||||
```
|
||||
|
||||
<b>示例:</b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/slice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
nums := []int{1, 2, 3, 1, 2, 4, 5, 6, 4, 7}
|
||||
comparator := func(item int, other int) bool { return item == other }
|
||||
|
||||
result := slice.UniqueByConcurrent(nums, comparator, 4)
|
||||
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// [1 2 3 4 5 6 7]
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="UniqueByField">UniqueByField</span>
|
||||
|
||||
<p>根据struct字段对struct切片去重复。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func UniqueByField[T any](slice []T, field string) ([]T, error)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/6cifcZSPIGu)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/slice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
users := []User{
|
||||
{ID: 1, Name: "a"},
|
||||
{ID: 2, Name: "b"},
|
||||
{ID: 1, Name: "c"},
|
||||
}
|
||||
|
||||
result, err := slice.UniqueByField(users, "ID")
|
||||
if err != nil {
|
||||
}
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// [{1 a} {2 b}]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -2594,14 +2859,14 @@ import (
|
||||
|
||||
func main() {
|
||||
strs := []string{"a", "b", "a", "c", "d", "a"}
|
||||
modifiedStrs, count := slice.SetToDefaultIf(strs, func(s string) bool { return "a" == s })
|
||||
|
||||
modifiedStrs, count := slice.SetToDefaultIf(strs, func(s string) bool { return "a" == s })
|
||||
|
||||
fmt.Println(modifiedStrs)
|
||||
fmt.Println(count)
|
||||
|
||||
fmt.Println(count)
|
||||
|
||||
// Output:
|
||||
// [ b c d ]
|
||||
// 3
|
||||
// [ b c d ]
|
||||
// 3
|
||||
}
|
||||
```
|
||||
|
||||
@@ -2615,7 +2880,7 @@ func main() {
|
||||
func Break[T any](values []T, predicate func(T) bool) ([]T, []T)
|
||||
```
|
||||
|
||||
<b>示例:</b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/yLYcBTyeQIz)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -2638,7 +2903,7 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
<span id="RightPadding">RightPadding</span>
|
||||
### <span id="RightPadding">RightPadding</span>
|
||||
|
||||
<p>在切片的右部添加元素。</p>
|
||||
|
||||
@@ -2648,7 +2913,7 @@ func main() {
|
||||
func RightPadding[T any](slice []T, paddingValue T, paddingLength int) []T
|
||||
```
|
||||
|
||||
<b>示例:</b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/0_2rlLEMBXL)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -2657,15 +2922,15 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
nums := []int{1, 2, 3, 4, 5}
|
||||
padded := slice.RightPadding(nums, 0, 3)
|
||||
fmt.Println(padded)
|
||||
// Output:
|
||||
// [1 2 3 4 5 0 0 0]
|
||||
nums := []int{1, 2, 3, 4, 5}
|
||||
padded := slice.RightPadding(nums, 0, 3)
|
||||
fmt.Println(padded)
|
||||
// Output:
|
||||
// [1 2 3 4 5 0 0 0]
|
||||
}
|
||||
```
|
||||
|
||||
<span id="LeftPadding">LeftPadding</span>
|
||||
### <span id="LeftPadding">LeftPadding</span>
|
||||
|
||||
<p>在切片的左部添加元素。</p>
|
||||
|
||||
@@ -2675,7 +2940,7 @@ func main() {
|
||||
func LeftPadding[T any](slice []T, paddingValue T, paddingLength int) []T
|
||||
```
|
||||
|
||||
<b>示例:</b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/jlQVoelLl2k)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -2684,10 +2949,39 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
nums := []int{1, 2, 3, 4, 5}
|
||||
padded := slice.LeftPadding(nums, 0, 3)
|
||||
fmt.Println(padded)
|
||||
// Output:
|
||||
// [0 0 0 1 2 3 4 5]
|
||||
nums := []int{1, 2, 3, 4, 5}
|
||||
padded := slice.LeftPadding(nums, 0, 3)
|
||||
fmt.Println(padded)
|
||||
// Output:
|
||||
// [0 0 0 1 2 3 4 5]
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Frequency">Frequency</span>
|
||||
|
||||
<p>计算切片中每个元素出现的频率。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func Frequency[T comparable](slice []T) map[T]int
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行]()</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
strs := []string{"a", "b", "b", "c", "c", "c"}
|
||||
result := slice.Frequency(strs)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// map[a:1 b:2 c:3]
|
||||
}
|
||||
```
|
||||
@@ -63,6 +63,12 @@ import (
|
||||
- [SubInBetween](#SubInBetween)
|
||||
- [HammingDistance](#HammingDistance)
|
||||
- [Concat](#Concat)
|
||||
- [Ellipsis](#Ellipsis)
|
||||
- [Shuffle](#Shuffle)
|
||||
- [Rotate](#Rotate)
|
||||
- [TemplateReplace](#TemplateReplace)
|
||||
- [RegexMatchAllGroups](#RegexMatchAllGroups)
|
||||
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -1541,7 +1547,7 @@ func main() {
|
||||
func Concat(length int, str ...string) string
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行]()</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -1551,16 +1557,175 @@ import (
|
||||
|
||||
func main() {
|
||||
|
||||
result1 := strutil.Concat(12, "Hello", " ", "World", "!")
|
||||
result2 := strutil.Concat(11, "Go", " ", "Language")
|
||||
result3 := strutil.Concat(0, "An apple a ", "day,", "keeps the", " doctor away")
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
result1 := strutil.Concat(12, "Hello", " ", "World", "!")
|
||||
result2 := strutil.Concat(11, "Go", " ", "Language")
|
||||
result3 := strutil.Concat(0, "An apple a ", "day,", "keeps the", " doctor away")
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
|
||||
// Output:
|
||||
// Hello World!
|
||||
// Go Language
|
||||
// An apple a day,keeps the doctor away
|
||||
// Output:
|
||||
// Hello World!
|
||||
// Go Language
|
||||
// An apple a day,keeps the doctor away
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Ellipsis">Ellipsis</span>
|
||||
|
||||
<p>将字符串截断到指定长度,并在末尾添加省略号。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func Ellipsis(str string, length int) string
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/strutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
result1 := strutil.Ellipsis("hello world", 5)
|
||||
result2 := strutil.Ellipsis("你好,世界!", 2)
|
||||
result3 := strutil.Ellipsis("😀😃😄😁😆", 3)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
|
||||
// Output:
|
||||
// hello...
|
||||
// 你好...
|
||||
// 😀😃😄...
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Shuffle">Shuffle</span>
|
||||
|
||||
<p>打乱给定字符串中的字符顺序。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func Shuffle(str string) string
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/strutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
result := strutil.Shuffle("hello")
|
||||
fmt.Println(result) //olelh (random order)
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Rotate">Rotate</span>
|
||||
|
||||
<p>按指定的字符数旋转字符串。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func Rotate(str string, shift int) string
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/strutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
result1 := Rotate("Hello", 0)
|
||||
result2 := Rotate("Hello", 1)
|
||||
result3 := Rotate("Hello", 2)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
|
||||
// Output:
|
||||
// Hello
|
||||
// oHell
|
||||
// loHel
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="TemplateReplace">TemplateReplace</span>
|
||||
|
||||
<p>将模板字符串中的占位符替换为map中的相应值。占位符括在花括号中,例如 {key}。例如,模板字符串为“Hello, {name}!”,map为{"name": "world"},结果将为“Hello, world!”。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func TemplateReplace(template string, data map[string]string) string
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行]()</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/strutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
template := `Hello, my name is {name}, I'm {age} years old.`
|
||||
data := map[string]string{
|
||||
"name": "Bob",
|
||||
"age": "20",
|
||||
}
|
||||
|
||||
result := strutil.TemplateReplace(template, data)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// Hello, my name is Bob, I'm 20 years old.
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="RegexMatchAllGroups">RegexMatchAllGroups</span>
|
||||
|
||||
<p>使用正则表达式匹配字符串中的所有子组并返回结果。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func RegexMatchAllGroups(pattern, str string) [][]string
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/strutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
pattern := `(\w+\.+\w+)@(\w+)\.(\w+)`
|
||||
str := "Emails: john.doe@example.com and jane.doe@example.com"
|
||||
|
||||
result := strutil.RegexMatchAllGroups(pattern, str)
|
||||
|
||||
fmt.Println(result[0])
|
||||
fmt.Println(result[1])
|
||||
|
||||
// Output:
|
||||
// [john.doe@example.com john.doe example com]
|
||||
// [jane.doe@example.com jane.doe example com]
|
||||
}
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
# System
|
||||
|
||||
system 包含 os, runtime, shell command 相关函数。
|
||||
system 包含 os, 运行time, shell command 相关函数。
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -31,6 +31,11 @@ import (
|
||||
- [CompareOsEnv](#CompareOsEnv)
|
||||
- [ExecCommand](#ExecCommand)
|
||||
- [GetOsBits](#GetOsBits)
|
||||
- [StartProcess](#StartProcess)
|
||||
- [StopProcess](#StopProcess)
|
||||
- [KillProcess](#KillProcess)
|
||||
- [GetProcessInfo](#GetProcessInfo)
|
||||
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -241,13 +246,14 @@ func main() {
|
||||
|
||||
### <span id="ExecCommand">ExecCommand</span>
|
||||
|
||||
<p>执行shell命令,返回命令的stdout和stderr字符串,如果出现错误,则返回错误。参数`command`是一个完整的命令字符串,如ls-a(linux),dir(windows),ping 127.0.0.1。在linux中,使用/bin/bash-c执行命令,在windows中,使用powershell.exe执行命令。</p>
|
||||
<p>执行shell命令,返回命令的stdout和stderr字符串,如果出现错误,则返回错误。参数`command`是一个完整的命令字符串,如ls-a(linux),dir(windows),ping 127.0.0.1。在linux中,使用/bin/bash-c执行命令,在windows中,使用powershell.exe执行命令。
|
||||
函数的第二个参数是cmd选项控制参数,类型是func(*exec.Cmd),可以通过这个参数设置cmd属性。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
type (
|
||||
Option func(*exec.Cmd)
|
||||
Option func(*exec.Cmd)
|
||||
)
|
||||
func ExecCommand(command string, opts ...Option) (stdout, stderr string, err error)
|
||||
```
|
||||
@@ -262,7 +268,9 @@ import (
|
||||
|
||||
func main() {
|
||||
// linux or mac
|
||||
stdout, stderr, err := system.ExecCommand("ls")
|
||||
stdout, stderr, err := system.ExecCommand("ls", func(cmd *exec.Cmd) {
|
||||
cmd.Dir = "/tmp"
|
||||
})
|
||||
fmt.Println("std out: ", stdout)
|
||||
fmt.Println("std err: ", stderr)
|
||||
assert.Equal("", stderr)
|
||||
@@ -305,3 +313,132 @@ func main() {
|
||||
fmt.Println(osBit) // 32 or 64
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="StartProcess">StartProcess</span>
|
||||
|
||||
<p>创建进程。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func StartProcess(command string, args ...string) (int, error)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block">[运行](todo)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/system"
|
||||
)
|
||||
|
||||
func main() {
|
||||
pid, err := system.StartProcess("sleep", "2")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(pid)
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="StopProcess">StopProcess</span>
|
||||
|
||||
<p>停止进程。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func StopProcess(pid int) error
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block">[运行](todo)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/system"
|
||||
)
|
||||
|
||||
func main() {
|
||||
pid, err := system.StartProcess("sleep", "10")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
err = system.StopProcess(pid)
|
||||
|
||||
fmt.Println(err)
|
||||
|
||||
// Output:
|
||||
// <nil>
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="KillProcess">KillProcess</span>
|
||||
|
||||
<p>杀掉进程。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func KillProcess(pid int) error
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block">[运行](todo)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/system"
|
||||
)
|
||||
|
||||
func main() {
|
||||
pid, err := system.StartProcess("sleep", "10")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
err = system.KillProcess(pid)
|
||||
|
||||
fmt.Println(err)
|
||||
|
||||
// Output:
|
||||
// <nil>
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="GetProcessInfo">GetProcessInfo</span>
|
||||
|
||||
<p>根据进程id获取进程信息。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func GetProcessInfo(pid int) (*ProcessInfo, error)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block">[运行](todo)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/system"
|
||||
)
|
||||
|
||||
func main() {
|
||||
pid, err := system.StartProcess("ls", "-a")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
processInfo, err := system.GetProcessInfo(pid)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(processInfo)
|
||||
}
|
||||
```
|
||||
@@ -32,6 +32,8 @@ import (
|
||||
- [AesCfbDecrypt](#AesCfbDecrypt)
|
||||
- [AesOfbEncrypt](#AesOfbEncrypt)
|
||||
- [AesOfbDecrypt](#AesOfbDecrypt)
|
||||
- [AesGcmEncrypt](#AesGcmEncrypt)
|
||||
- [AesGcmDecrypt](#AesGcmDecrypt)
|
||||
- [Base64StdEncode](#Base64StdEncode)
|
||||
- [Base64StdDecode](#Base64StdDecode)
|
||||
- [DesEcbEncrypt](#DesEcbEncrypt)
|
||||
@@ -379,6 +381,74 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="AesGcmEncrypt">AesGcmEncrypt</span>
|
||||
|
||||
<p>Encrypt data with key use AES GCM algorithm.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func AesGcmEncrypt(data, key []byte) []byte
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run]()</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
encrypted := cryptor.AesGcmEncrypt([]byte(data), []byte(key))
|
||||
decrypted := cryptor.AesGcmDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="AesGcmDecrypt">AesGcmDecrypt</span>
|
||||
|
||||
<p>Decrypt data with key use AES GCM algorithm.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func AesGcmDecrypt(data, key []byte) []byte
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run]()</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
encrypted := cryptor.AesGcmEncrypt([]byte(data), []byte(key))
|
||||
decrypted := cryptor.AesGcmDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Base64StdEncode">Base64StdEncode</span>
|
||||
|
||||
<p>Encode string with base64 encoding.</p>
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
|
||||
- [New](#New)
|
||||
- [FromSlice](#FromSlice)
|
||||
- [Values](#Values)
|
||||
- [Values<sup>deprecated</sup>](#Values)
|
||||
- [Add](#Add)
|
||||
- [AddIfNotExist](#AddIfNotExist)
|
||||
- [AddIfNotExistBy](#AddIfNotExistBy)
|
||||
@@ -102,10 +102,11 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Values">Values<sup>deprecated</sup></span>
|
||||
### <span id="Values">Values</span>
|
||||
|
||||
<p>Return slice of all set data.<br>
|
||||
The <a href='#ToSlice'>ToSlice()</a> function provides the same functionality as Values and returns a slice containing all values of the set.</p>
|
||||
<p>Return slice of all set data.</p>
|
||||
|
||||
> ⚠️ This function is deprecated. use `ToSlice` instead.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
|
||||
@@ -65,6 +65,10 @@ import (
|
||||
- [TimestampMilli](#TimestampMilli)
|
||||
- [TimestampMicro](#TimestampMicro)
|
||||
- [TimestampNano](#TimestampNano)
|
||||
- [TrackFuncTime](#TrackFuncTime)
|
||||
- [DaysBetween](#DaysBetween)
|
||||
- [GenerateDatetimesBetween](#GenerateDatetimesBetween)
|
||||
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -1465,3 +1469,106 @@ func main() {
|
||||
// 1690363051331788000
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="TrackFuncTime">TrackFuncTime</span>
|
||||
|
||||
<p>Tracks function execution time.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func TrackFuncTime(pre time.Time) func()
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/datetime"
|
||||
)
|
||||
|
||||
func main() {
|
||||
defer datetime.TrackFuncTime(time.Now())()
|
||||
|
||||
var n int
|
||||
for i := 0; i < 5000000; i++ {
|
||||
n++
|
||||
}
|
||||
|
||||
fmt.Println(1) // Function main execution time: 1.460287ms
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="DaysBetween">DaysBetween</span>
|
||||
|
||||
<p>Returns the number of days between two times.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func DaysBetween(start, end time.Time) int
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/datetime"
|
||||
)
|
||||
|
||||
func main() {
|
||||
start := time.Date(2024, time.September, 1, 0, 0, 0, 0, time.UTC)
|
||||
end := time.Date(2024, time.September, 10, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
result := datetime.DaysBetween(start, end)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// 9
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="GenerateDatetimesBetween">GenerateDatetimesBetween</span>
|
||||
|
||||
<p>Returns a slice of strings between two times. `layout`: the format of the datetime string.`interval`: the interval between two datetimes.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func GenerateDatetimesBetween(start, end time.Time, layout string, interval string) ([]string, error)
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/datetime"
|
||||
)
|
||||
|
||||
func main() {
|
||||
start := time.Date(2024, time.September, 1, 0, 0, 0, 0, time.UTC)
|
||||
end := time.Date(2024, time.September, 1, 2, 0, 0, 0, time.UTC)
|
||||
|
||||
layout := "2006-01-02 15:04:05"
|
||||
interval := "1h"
|
||||
|
||||
result, err := datetime.GenerateDatetimesBetween(start, end, layout, interval)
|
||||
|
||||
fmt.Println(result)
|
||||
fmt.Println(err)
|
||||
|
||||
// Output:
|
||||
// [2024-09-01 00:00:00 2024-09-01 01:00:00 2024-09-01 02:00:00]
|
||||
// <nil>
|
||||
}
|
||||
```
|
||||
@@ -37,12 +37,12 @@ import (
|
||||
|
||||
### <span id="Comma">Comma</span>
|
||||
|
||||
<p>Add comma to a number value by every 3 numbers from right to left. ahead by symbol char. if value is a invalid number string like "aa", return empty string.</p>
|
||||
<p>Add comma to a number value by every 3 numbers from right to left. ahead by a prefix symbol char. if value is a invalid number string like "aa", return empty string.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func Comma[T constraints.Float | constraints.Integer | string](value T, symbol string) string
|
||||
func Comma[T constraints.Float | constraints.Integer | string](value T, prefixSymbol string) string
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/eRD5k2vzUVX)</span></b>
|
||||
|
||||
@@ -28,7 +28,8 @@ import (
|
||||
- [Before](#Before)
|
||||
- [CurryFn](#CurryFn)
|
||||
- [Compose](#Compose)
|
||||
- [Debounced](#Debounced)
|
||||
- [Debounce](#Debounce)
|
||||
- [Debounced<sup>deprecated</sup>](#Debounced)
|
||||
- [Delay](#Delay)
|
||||
- [Schedule](#Schedule)
|
||||
- [Pipeline](#Pipeline)
|
||||
@@ -40,6 +41,8 @@ import (
|
||||
- [Xnor](#Xnor)
|
||||
- [Nand](#Nand)
|
||||
- [AcceptIf](#AcceptIf)
|
||||
- [Throttle](#Throttle)
|
||||
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -191,11 +194,59 @@ func main() {
|
||||
// ABCDE
|
||||
}
|
||||
```
|
||||
### <span id="Debounce">Debounce</span>
|
||||
|
||||
<p>Creates a debounced version of the provided function. The debounced function will only invoke the original function after the specified delay has passed since the last time it was invoked. It also supports canceling the debounce.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func Debounce(fn func(), delay time.Duration) (debouncedFn func(), cancelFn func())
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/function"
|
||||
)
|
||||
|
||||
func main() {
|
||||
callCount := 0
|
||||
fn := func() {
|
||||
callCount++
|
||||
}
|
||||
|
||||
debouncedFn, _ := function.Debounce(fn, 500*time.Millisecond)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
debouncedFn()
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
fmt.Println(callCount)
|
||||
|
||||
debouncedFn()
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
fmt.Println(callCount)
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
// 2
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Debounced">Debounced</span>
|
||||
|
||||
<p>Creates a debounced function that delays invoking fn until after wait duration have elapsed since the last time the debounced function was invoked.</p>
|
||||
|
||||
> ⚠️ This function is deprecated. use `Debounce` instead.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
@@ -689,5 +740,46 @@ func main() {
|
||||
// 0
|
||||
// false
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Throttle">Throttle</span>
|
||||
|
||||
<p>Throttle creates a throttled version of the provided function. The returned function guarantees that it will only be invoked at most once per interval.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func Throttle(fn func(), interval time.Duration) func()
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/function"
|
||||
)
|
||||
|
||||
func main() {
|
||||
callCount := 0
|
||||
|
||||
fn := func() {
|
||||
callCount++
|
||||
}
|
||||
|
||||
throttledFn := function.Throttle(fn, 1*time.Second)
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
throttledFn()
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
fmt.Println(callCount)
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
}
|
||||
```
|
||||
File diff suppressed because it is too large
Load Diff
@@ -624,7 +624,9 @@ func main() {
|
||||
|
||||
### <span id="HttpGet">HttpGet</span>
|
||||
|
||||
<p>Send http get request. (Deprecated: use SendRequest for replacement)</p>
|
||||
<p>Send http get request.</p>
|
||||
|
||||
> ⚠️ This function is deprecated. use `SendRequest` instead.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
@@ -666,7 +668,9 @@ func main() {
|
||||
|
||||
### <span id="HttpPost">HttpPost</span>
|
||||
|
||||
<p>Send http post request.(Deprecated: use SendRequest for replacement)</p>
|
||||
<p>Send http post request.</p>
|
||||
|
||||
> ⚠️ This function is deprecated. use `SendRequest` instead.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
@@ -713,7 +717,9 @@ func main() {
|
||||
|
||||
### <span id="HttpPut">HttpPut</span>
|
||||
|
||||
<p>Send http put request. (Deprecated: use SendRequest for replacement)</p>
|
||||
<p>Send http put request.</p>
|
||||
|
||||
> ⚠️ This function is deprecated. use `SendRequest` instead.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
@@ -763,7 +769,9 @@ func main() {
|
||||
|
||||
### <span id="HttpDelete">HttpDelete</span>
|
||||
|
||||
<p>Send http delete request. (Deprecated: use SendRequest for replacement)</p>
|
||||
<p>Send http delete request.</p>
|
||||
|
||||
> ⚠️ This function is deprecated. use `SendRequest` instead.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
@@ -802,7 +810,9 @@ func main() {
|
||||
|
||||
### <span id="HttpPatch">HttpPatch</span>
|
||||
|
||||
<p>Send http patch request. (Deprecated: use SendRequest for replacement)</p>
|
||||
<p>Send http patch request.</p>
|
||||
|
||||
> ⚠️ This function is deprecated. use `SendRequest` instead.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ import (
|
||||
|
||||
- [Of](#Of)
|
||||
- [Unwrap](#Unwrap)
|
||||
- [UnwarpOr](#UnwarpOr)
|
||||
- [UnwarpOrDefault](#UnwarpOrDefault)
|
||||
- [UnwrapOr](#UnwrapOr)
|
||||
- [UnwrapOrDefault](#UnwrapOrDefault)
|
||||
- [ExtractPointer](#ExtractPointer)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
@@ -103,13 +103,13 @@ func main() {
|
||||
```
|
||||
|
||||
|
||||
### <span id="UnwarpOr">UnwarpOr</span>
|
||||
### <span id="UnwrapOr">UnwrapOr</span>
|
||||
|
||||
<p>Returns the value from the pointer or fallback if the pointer is nil.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
```go
|
||||
UnwarpOr[T any](p *T, fallback T) T
|
||||
UnwrapOr[T any](p *T, fallback T) T
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/mmNaLC38W8C)</span></b>
|
||||
@@ -129,10 +129,10 @@ func main() {
|
||||
var c *int
|
||||
var d *string
|
||||
|
||||
result1 := pointer.UnwarpOr(&a, 456)
|
||||
result2 := pointer.UnwarpOr(&b, "abc")
|
||||
result3 := pointer.UnwarpOr(c, 456)
|
||||
result4 := pointer.UnwarpOr(d, "def")
|
||||
result1 := pointer.UnwrapOr(&a, 456)
|
||||
result2 := pointer.UnwrapOr(&b, "abc")
|
||||
result3 := pointer.UnwrapOr(c, 456)
|
||||
result4 := pointer.UnwrapOr(d, "def")
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
@@ -148,13 +148,13 @@ func main() {
|
||||
```
|
||||
|
||||
|
||||
### <span id="UnwarpOrDefault">UnwarpOrDefault</span>
|
||||
### <span id="UnwrapOrDefault">UnwrapOrDefault</span>
|
||||
|
||||
<p>Returns the value from the pointer or the default value if the pointer is nil.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
```go
|
||||
UnwarpOrDefault[T any](p *T) T
|
||||
UnwrapOrDefault[T any](p *T) T
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/ZnGIHf8_o4E)</span></b>
|
||||
@@ -174,10 +174,10 @@ func main() {
|
||||
var c *int
|
||||
var d *string
|
||||
|
||||
result1 := pointer.UnwarpOrDefault(&a)
|
||||
result2 := pointer.UnwarpOrDefault(&b)
|
||||
result3 := pointer.UnwarpOrDefault(c)
|
||||
result4 := pointer.UnwarpOrDefault(d)
|
||||
result1 := pointer.UnwrapOrDefault(&a)
|
||||
result2 := pointer.UnwrapOrDefault(&b)
|
||||
result3 := pointer.UnwrapOrDefault(c)
|
||||
result4 := pointer.UnwrapOrDefault(d)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
|
||||
@@ -25,15 +25,21 @@ import (
|
||||
- [RandBytes](#RandBytes)
|
||||
- [RandInt](#RandInt)
|
||||
- [RandString](#RandString)
|
||||
- [RandFromGivenSlice](#RandFromGivenSlice)
|
||||
- [RandSliceFromGivenSlice](#RandSliceFromGivenSlice)
|
||||
- [RandUpper](#RandUpper)
|
||||
- [RandLower](#RandLower)
|
||||
- [RandNumeral](#RandNumeral)
|
||||
- [RandNumeralOrLetter](#RandNumeralOrLetter)
|
||||
- [RandSymbolChar](#RandSymbolChar)
|
||||
- [UUIdV4](#UUIdV4)
|
||||
- [RandIntSlice](#RandIntSlice)
|
||||
- [RandUniqueIntSlice](#RandUniqueIntSlice)
|
||||
- [RandFloat](#RandFloat)
|
||||
- [RandFloats](#RandFloats)
|
||||
- [RandStringSlice](#RandStringSlice)
|
||||
- [RandBool](#RandBool)
|
||||
- [RandBoolSlice](#RandBoolSlice)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -117,6 +123,60 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="RandFromGivenSlice">RandFromGivenSlice</span>
|
||||
|
||||
<p>Generate a random element from given slice.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func RandFromGivenSlice[T any](slice []T) T
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/random"
|
||||
)
|
||||
|
||||
func main() {
|
||||
randomSet := []any{"a", 8, "hello", true, 1.1}
|
||||
randElm := random.RandFromGivenSlice(randomSet)
|
||||
fmt.Println(randElm)
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="RandSliceFromGivenSlice">RandSliceFromGivenSlice</span>
|
||||
|
||||
<p>Generate a random slice of length num from given slice.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func RandSliceFromGivenSlice[T any](slice []T, num int, repeatable bool) []T
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/random"
|
||||
)
|
||||
|
||||
func main() {
|
||||
goods := []string{"apple", "banana", "cherry", "elderberry", "fig", "grape", "honeydew", "kiwi", "lemon", "mango", "nectarine", "orange"}
|
||||
chosen3goods := random.RandSliceFromGivenSlice(goods, 3, false)
|
||||
fmt.Println(chosen3goods)
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="RandUpper">RandUpper</span>
|
||||
|
||||
<p>Generate a random upper case string</p>
|
||||
@@ -276,15 +336,41 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="RandIntSlice">RandIntSlice</span>
|
||||
|
||||
### <span id="RandUniqueIntSlice">RandUniqueIntSlice</span>
|
||||
|
||||
<p>Generate a slice of random int of length n that do not repeat.</p>
|
||||
<p>Generate a slice of random int. Number range in [min, max)</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func RandUniqueIntSlice(n, min, max int) []int
|
||||
func RandIntSlice(length, min, max int) []int
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/random"
|
||||
)
|
||||
|
||||
func main() {
|
||||
result := random.RandIntSlice(5, 0, 10)
|
||||
fmt.Println(result) //[1 4 7 1 5] (random)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### <span id="RandUniqueIntSlice">RandUniqueIntSlice</span>
|
||||
|
||||
<p>Generate a slice of random int of length that do not repeat. Number range in [min, max)</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func RandUniqueIntSlice(length, min, max int) []int
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/uBkRSOz73Ec)</span></b>
|
||||
@@ -331,12 +417,12 @@ func main() {
|
||||
|
||||
### <span id="RandFloats">RandFloats</span>
|
||||
|
||||
<p>Generate a slice of random float64 numbers of length n that do not repeat.</p>
|
||||
<p>Generate a slice of random float64 numbers of length n that do not repeat. Number range in [min, max)</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func RandFloats(n int, min, max float64, precision int) []float64
|
||||
func RandFloats(length int, min, max float64, precision int) []float64
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/I3yndUQ-rhh)</span></b>
|
||||
@@ -353,4 +439,86 @@ func main() {
|
||||
floatNumbers := random.RandFloats(5, 1.0, 5.0, 2)
|
||||
fmt.Println(floatNumbers) //[3.42 3.99 1.3 2.38 4.23] (random)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### <span id="RandStringSlice">RandStringSlice</span>
|
||||
|
||||
<p>Generate a slice of random string of length strLen based on charset. chartset should be one of the following: random.Numeral, random.LowwerLetters, random.UpperLetters random.Letters, random.SymbolChars, random.AllChars. or a combination of them.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func RandStringSlice(charset string, sliceLen, strLen int) []string
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/random"
|
||||
)
|
||||
|
||||
func main() {
|
||||
strs := random.RandStringSlice(random.Letters, 4, 6)
|
||||
fmt.Println(strs)
|
||||
|
||||
// output random string slice like below:
|
||||
//[CooSMq RUFjDz FAeMPf heRyGv]
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="RandBool">RandBool</span>
|
||||
|
||||
<p>Generate a random boolean value (true or false).</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func RandBool() bool
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/random"
|
||||
)
|
||||
|
||||
func main() {
|
||||
result := random.RandBool()
|
||||
fmt.Println(result) // true or false (random)
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="RandBoolSlice">RandBoolSlice</span>
|
||||
|
||||
<p>Generates a random boolean slice of specified length.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func RandBoolSlice(length int) []bool
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/random"
|
||||
)
|
||||
|
||||
func main() {
|
||||
result := random.RandBoolSlice(2)
|
||||
fmt.Println(result) // [true false] (random)
|
||||
}
|
||||
```
|
||||
@@ -331,7 +331,7 @@ func main() {
|
||||
func RetryWithCustomBackoff(backoffStrategy BackoffStrategy) Option
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/jIm_o2vb5Y4)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -361,7 +361,7 @@ func main() {
|
||||
return errors.New("error occurs")
|
||||
}
|
||||
|
||||
err := retry,Retry(increaseNumber, retry.RetryWithCustomBackoff(&ExampleCustomBackoffStrategy{interval: time.Microsecond * 50}))
|
||||
err := retry.Retry(increaseNumber, retry.RetryWithCustomBackoff(&ExampleCustomBackoffStrategy{interval: time.Microsecond * 50}))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -384,7 +384,7 @@ func main() {
|
||||
func RetryWithLinearBackoff(interval time.Duration) Option
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/nk2XRmagfVF)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -429,7 +429,7 @@ func main() {
|
||||
func RetryWithExponentialWithJitterBackoff(interval time.Duration, base uint64, maxJitter time.Duration) Option
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/xp1avQmn16X)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
@@ -6,7 +6,8 @@ Package slice implements some functions to manipulate slice.
|
||||
|
||||
## Source:
|
||||
|
||||
- [https://github.com/duke-git/lancet/blob/main/slice/slice.go](https://github.com/duke-git/lancet/blob/main/slice/slice.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/slice/slice.go](https://github.com/duke-git/lancet/blob/main/slice/slice.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/slice/slice_concurrent.go](https://github.com/duke-git/lancet/blob/main/slice/slice_concurrent.go)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -44,6 +45,7 @@ import (
|
||||
- [EqualWith](#EqualWith)
|
||||
- [Every](#Every)
|
||||
- [Filter](#Filter)
|
||||
- [FilterConcurrent](#FilterConcurrent)
|
||||
- [Find<sup>deprecated</sup>](#Find)
|
||||
- [FindBy](#FindBy)
|
||||
- [FindLast<sup>deprecated</sup>](#FindLast)
|
||||
@@ -51,6 +53,7 @@ import (
|
||||
- [Flatten](#Flatten)
|
||||
- [FlattenDeep](#FlattenDeep)
|
||||
- [ForEach](#ForEach)
|
||||
- [ForEachConcurrent](#ForEachConcurrent)
|
||||
- [ForEachWithBreak](#ForEachWithBreak)
|
||||
- [GroupBy](#GroupBy)
|
||||
- [GroupWith](#GroupWith)
|
||||
@@ -61,11 +64,13 @@ import (
|
||||
- [IndexOf](#IndexOf)
|
||||
- [LastIndexOf](#LastIndexOf)
|
||||
- [Map](#Map)
|
||||
- [MapConcurrent](#MapConcurrent)
|
||||
- [FilterMap](#FilterMap)
|
||||
- [FlatMap](#FlatMap)
|
||||
- [Merge](#Merge)
|
||||
- [Reverse](#Reverse)
|
||||
- [Reduce<sup>deprecated</sup>](#Reduce)
|
||||
- [ReduceConcurrent](#ReduceConcurrent)
|
||||
- [ReduceBy](#ReduceBy)
|
||||
- [ReduceRight](#ReduceRight)
|
||||
- [Replace](#Replace)
|
||||
@@ -86,6 +91,9 @@ import (
|
||||
- [ToSlicePointer](#ToSlicePointer)
|
||||
- [Unique](#Unique)
|
||||
- [UniqueBy](#UniqueBy)
|
||||
- [UniqueByComparator](#UniqueByComparator)
|
||||
- [UniqueByField](#UniqueByField)
|
||||
- [UniqueByConcurrent](#UniqueByConcurrent)
|
||||
- [Union](#Union)
|
||||
- [UnionBy](#UnionBy)
|
||||
- [UpdateAt](#UpdateAt)
|
||||
@@ -97,6 +105,7 @@ import (
|
||||
- [Break](#Break)
|
||||
- [RightPadding](#RightPadding)
|
||||
- [LeftPadding](#LeftPadding)
|
||||
- [Frequency](#Frequency)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -321,12 +330,12 @@ func main() {
|
||||
|
||||
### <span id="Concat">Concat</span>
|
||||
|
||||
<p>Creates a new slice concatenating slice with any additional slices.</p>
|
||||
<p>Concat creates a new slice concatenating slice with any additional slices.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func Concat[T any](slice []T, slices ...[]T) []T
|
||||
func Concat[T any](slices ...[]T) []T
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/gPt-q7zr5mk)</span></b>
|
||||
@@ -894,10 +903,46 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Find">Find(deprecated: use FindBy)</span>
|
||||
### <span id="FilterConcurrent">FilterConcurrent</span>
|
||||
|
||||
<p>Applies the provided filter function `predicate` to each element of the input slice concurrently.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func FilterConcurrent[T any](slice []T, predicate func(index int, item T) bool, numThreads int) []T
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
nums := []int{1, 2, 3, 4, 5}
|
||||
|
||||
isEven := func(i, num int) bool {
|
||||
return num%2 == 0
|
||||
}
|
||||
|
||||
result := slice.FilterConcurrent(nums, isEven, 2)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// [2 4]
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Find">Find</span>
|
||||
|
||||
<p>Iterates over elements of slice, returning the first one that passes a truth test on function.</p>
|
||||
|
||||
> ⚠️ This function is deprecated. use `FindBy` instead.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
@@ -966,10 +1011,12 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="FindLast">FindLast(deprecated: use FindLastBy)</span>
|
||||
### <span id="FindLast">FindLast</span>
|
||||
|
||||
<p>iterates over elements of slice from end to begin, returning the last one that passes a truth test on function.</p>
|
||||
|
||||
> ⚠️ This function is deprecated. use `FindLastBy` instead.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
@@ -1133,6 +1180,42 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="ForEachConcurrent">ForEachConcurrent</span>
|
||||
|
||||
<p>Applies the iteratee function to each item in the slice concurrently.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func ForEachConcurrent[T any](slice []T, iteratee func(index int, item T), numThreads int)
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
nums := []int{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
|
||||
result := make([]int, len(nums))
|
||||
|
||||
addOne := func(index int, value int) {
|
||||
result[index] = value + 1
|
||||
}
|
||||
|
||||
slice.ForEachConcurrent(nums, addOne, 4)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// [2 3 4 5 6 7 8 9]
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="ForEachWithBreak">ForEachWithBreak</span>
|
||||
|
||||
<p>Iterates over elements of slice and invokes function for each element, when iteratee return false, will break the for each loop.</p>
|
||||
@@ -1241,10 +1324,12 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="IntSlice">IntSlice (Deprecated: use generic feature of go1.18+ for replacement)</span>
|
||||
### <span id="IntSlice">IntSlice</span>
|
||||
|
||||
<p>Convert interface slice to int slice.</p>
|
||||
|
||||
> ⚠️ This function is deprecated. Use generic feature of go1.18+ for replacement.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
@@ -1270,10 +1355,12 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="InterfaceSlice">InterfaceSlice (Deprecated: use generic feature of go1.18+ for replacement)</span>
|
||||
### <span id="InterfaceSlice">InterfaceSlice</span>
|
||||
|
||||
<p>Convert value to interface slice.</p>
|
||||
|
||||
> ⚠️ This function is deprecated. Use generic feature of go1.18+ for replacement.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
@@ -1470,6 +1557,36 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="MapConcurrent">MapConcurrent</span>
|
||||
|
||||
<p>Applies the iteratee function to each item in the slice by concrrent.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func MapConcurrent[T any, U any](slice []T, iteratee func(index int, item T) U, numThreads int) []U
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
nums := []int{1, 2, 3, 4, 5, 6}
|
||||
|
||||
result := slice.MapConcurrent(nums, func(_, n int) int { return n * n }, 4)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// [1 4 9 16 25 36]
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="FilterMap">FilterMap</span>
|
||||
|
||||
<p>Returns a slice which apply both filtering and mapping to the given slice. iteratee callback function should returntwo values: 1, mapping result. 2, whether the result element should be included or not.</p>
|
||||
@@ -1544,6 +1661,8 @@ func main() {
|
||||
|
||||
<p>Merge all given slices into one slice.</p>
|
||||
|
||||
> ⚠️ This function is deprecated. use `Concat` instead.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
@@ -1603,7 +1722,9 @@ func main() {
|
||||
|
||||
### <span id="Reduce">Reduce</span>
|
||||
|
||||
<p>Reduce slice.(Deprecated: use ReduceBy)</p>
|
||||
<p>Reduce slice.</p>
|
||||
|
||||
> ⚠️ This function is deprecated. use `ReduceBy` instead.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
@@ -1635,6 +1756,39 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="ReduceConcurrent">ReduceConcurrent</span>
|
||||
|
||||
<p>Reduces the slice to a single value by applying the reducer function to each item in the slice concurrently.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func ReduceConcurrent[T any](slice []T, initial T, reducer func(index int, item T, agg T) T, numThreads int) T
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
|
||||
|
||||
result := slice.ReduceConcurrent(nums, 0, func(_ int, item, agg int) int {
|
||||
return agg + item
|
||||
}, 1)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// 55
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### <span id="ReduceBy">ReduceBy</span>
|
||||
|
||||
<p>Produces a value from slice by accumulating the result of each element as passed through the reducer function.</p>
|
||||
@@ -2129,10 +2283,12 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="StringSlice">StringSlice (Deprecated: use generic feature of go1.18+ for replacement)</span>
|
||||
### <span id="StringSlice">StringSlice</span>
|
||||
|
||||
<p>Convert interface slice to string slice.</p>
|
||||
|
||||
> ⚠️ This function is deprecated. use generic feature of go1.18+ for replacement
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
@@ -2281,12 +2437,12 @@ func main() {
|
||||
|
||||
### <span id="UniqueBy">UniqueBy</span>
|
||||
|
||||
<p>Call iteratee func with every item of slice, then remove duplicated.</p>
|
||||
<p>Removes duplicate elements from the input slice based on the values returned by the iteratee function. this function maintains the order of the elements.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func UniqueBy[T comparable](slice []T, iteratee func(item T) T) []T
|
||||
func UniqueBy[T any, U comparable](slice []T, iteratee func(item T) U) []T
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/UR323iZLDpv)</span></b>
|
||||
@@ -2306,7 +2462,114 @@ func main() {
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// [1 2 0]
|
||||
// [1 2 3]
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="UniqueByComparator">UniqueByComparator</span>
|
||||
|
||||
<p>Removes duplicate elements from the input slice using the provided comparator function. The function maintains the order of the elements.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func UniqueByComparator[T comparable](slice []T, comparator func(item T, other T) bool) []T
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/slice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
uniqueNums := slice.UniqueByComparator([]int{1, 2, 3, 1, 2, 4, 5, 6, 4}, func(item int, other int) bool {
|
||||
return item == other
|
||||
})
|
||||
|
||||
caseInsensitiveStrings := slice.UniqueByComparator([]string{"apple", "banana", "Apple", "cherry", "Banana", "date"}, func(item string, other string) bool {
|
||||
return strings.ToLower(item) == strings.ToLower(other)
|
||||
})
|
||||
|
||||
fmt.Println(uniqueNums)
|
||||
fmt.Println(caseInsensitiveStrings)
|
||||
|
||||
// Output:
|
||||
// [1 2 3 4 5 6]
|
||||
// [apple banana cherry date]
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="UniqueByConcurrent">UniqueByConcurrent</span>
|
||||
|
||||
<p>Removes duplicate elements from the slice by parallel.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func UniqueByConcurrent[T comparable](slice []T, comparator func(item T, other T) bool, numThreads int) []T
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/slice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
nums := []int{1, 2, 3, 1, 2, 4, 5, 6, 4, 7}
|
||||
comparator := func(item int, other int) bool { return item == other }
|
||||
|
||||
result := slice.UniqueByConcurrent(nums,comparator, 4)
|
||||
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// [1 2 3 4 5 6 7]
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="UniqueByField">UniqueByField</span>
|
||||
|
||||
<p>Remove duplicate elements in struct slice by struct field.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func UniqueByField[T any](slice []T, field string) ([]T, error)
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Runs](https://go.dev/play/p/6cifcZSPIGu)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/slice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
users := []User{
|
||||
{ID: 1, Name: "a"},
|
||||
{ID: 2, Name: "b"},
|
||||
{ID: 1, Name: "c"},
|
||||
}
|
||||
|
||||
result, err := slice.UniqueByField(users, "ID")
|
||||
if err != nil {
|
||||
}
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// [{1 a} {2 b}]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -2591,14 +2854,14 @@ import (
|
||||
|
||||
func main() {
|
||||
strs := []string{"a", "b", "a", "c", "d", "a"}
|
||||
modifiedStrs, count := slice.SetToDefaultIf(strs, func(s string) bool { return "a" == s })
|
||||
|
||||
modifiedStrs, count := slice.SetToDefaultIf(strs, func(s string) bool { return "a" == s })
|
||||
|
||||
fmt.Println(modifiedStrs)
|
||||
fmt.Println(count)
|
||||
|
||||
fmt.Println(count)
|
||||
|
||||
// Output:
|
||||
// [ b c d ]
|
||||
// 3
|
||||
// [ b c d ]
|
||||
// 3
|
||||
}
|
||||
```
|
||||
|
||||
@@ -2612,7 +2875,7 @@ func main() {
|
||||
func Break[T any](values []T, predicate func(T) bool) ([]T, []T)
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/yLYcBTyeQIz)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -2635,7 +2898,7 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
<span id="c">RightPadding</span>
|
||||
### <span id="RightPadding">RightPadding</span>
|
||||
|
||||
<p>RightPadding adds padding to the right end of a slice.</p>
|
||||
|
||||
@@ -2645,7 +2908,7 @@ func main() {
|
||||
func RightPadding[T any](slice []T, paddingValue T, paddingLength int) []T
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/0_2rlLEMBXL)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -2654,15 +2917,15 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
nums := []int{1, 2, 3, 4, 5}
|
||||
padded := RightPadding(nums, 0, 3)
|
||||
fmt.Println(padded)
|
||||
// Output:
|
||||
// [1 2 3 4 5 0 0 0]
|
||||
nums := []int{1, 2, 3, 4, 5}
|
||||
padded := slice.RightPadding(nums, 0, 3)
|
||||
fmt.Println(padded)
|
||||
// Output:
|
||||
// [1 2 3 4 5 0 0 0]
|
||||
}
|
||||
```
|
||||
|
||||
<span id="LeftPadding">LeftPadding</span>
|
||||
### <span id="LeftPadding">LeftPadding</span>
|
||||
|
||||
<p>LeftPadding adds padding to the left begin of a slice.</p>
|
||||
|
||||
@@ -2672,7 +2935,7 @@ func main() {
|
||||
func LeftPadding[T any](slice []T, paddingValue T, paddingLength int) []T
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/jlQVoelLl2k)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -2681,10 +2944,39 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
nums := []int{1, 2, 3, 4, 5}
|
||||
padded := LeftPadding(nums, 0, 3)
|
||||
fmt.Println(padded)
|
||||
// Output:
|
||||
// [0 0 0 1 2 3 4 5]
|
||||
nums := []int{1, 2, 3, 4, 5}
|
||||
padded := slice.LeftPadding(nums, 0, 3)
|
||||
fmt.Println(padded)
|
||||
// Output:
|
||||
// [0 0 0 1 2 3 4 5]
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Frequency">Frequency</span>
|
||||
|
||||
<p>Counts the frequency of each element in the slice.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func Frequency[T comparable](slice []T) map[T]int
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
strs := []string{"a", "b", "b", "c", "c", "c"}
|
||||
result := slice.Frequency(strs)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// map[a:1 b:2 c:3]
|
||||
}
|
||||
```
|
||||
@@ -63,6 +63,11 @@ import (
|
||||
- [SubInBetween](#SubInBetween)
|
||||
- [HammingDistance](#HammingDistance)
|
||||
- [Concat](#Concat)
|
||||
- [Ellipsis](#Ellipsis)
|
||||
- [Shuffle](#Shuffle)
|
||||
- [Rotate](#Rotate)
|
||||
- [TemplateReplace](#TemplateReplace)
|
||||
- [RegexMatchAllGroups](#RegexMatchAllGroups)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -1100,10 +1105,10 @@ import (
|
||||
|
||||
func main() {
|
||||
result1 := strutil.IsNotBlank("")
|
||||
result2 := strutil.IsNotBlank(" ")
|
||||
result2 := strutil.IsNotBlank(" ")
|
||||
result3 := strutil.IsNotBlank("\t\v\f\n")
|
||||
result4 := strutil.IsNotBlank(" 中文")
|
||||
result5 := strutil.IsNotBlank(" world ")
|
||||
result5 := strutil.IsNotBlank(" world ")
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
@@ -1544,7 +1549,7 @@ func main() {
|
||||
func Concat(length int, str ...string) string
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run]()</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -1553,17 +1558,174 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
result1 := strutil.Concat(12, "Hello", " ", "World", "!")
|
||||
result2 := strutil.Concat(11, "Go", " ", "Language")
|
||||
result3 := strutil.Concat(0, "An apple a ", "day,", "keeps the", " doctor away")
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
|
||||
result1 := strutil.Concat(12, "Hello", " ", "World", "!")
|
||||
result2 := strutil.Concat(11, "Go", " ", "Language")
|
||||
result3 := strutil.Concat(0, "An apple a ", "day,", "keeps the", " doctor away")
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
// Output:
|
||||
// Hello World!
|
||||
// Go Language
|
||||
// An apple a day,keeps the doctor away
|
||||
}
|
||||
```
|
||||
|
||||
// Output:
|
||||
// Hello World!
|
||||
// Go Language
|
||||
// An apple a day,keeps the doctor away
|
||||
### <span id="Ellipsis">Ellipsis</span>
|
||||
|
||||
<p>Truncates a string to a specified length and appends an ellipsis.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func Ellipsis(str string, length int) string
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/strutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
result1 := strutil.Ellipsis("hello world", 5)
|
||||
result2 := strutil.Ellipsis("你好,世界!", 2)
|
||||
result3 := strutil.Ellipsis("😀😃😄😁😆", 3)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
|
||||
// Output:
|
||||
// hello...
|
||||
// 你好...
|
||||
// 😀😃😄...
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Shuffle">Shuffle</span>
|
||||
|
||||
<p>Shuffle the order of characters of given string.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func Shuffle(str string) string
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/strutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
result := strutil.Shuffle("hello")
|
||||
fmt.Println(result) //olelh (random order)
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Rotate">Rotate</span>
|
||||
|
||||
<p>Rotates the string by the specified number of characters.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func Rotate(str string, shift int) string
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/strutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
result1 := Rotate("Hello", 0)
|
||||
result2 := Rotate("Hello", 1)
|
||||
result3 := Rotate("Hello", 2)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
|
||||
// Output:
|
||||
// Hello
|
||||
// oHell
|
||||
// loHel
|
||||
}
|
||||
```
|
||||
### <span id="TemplateReplace">TemplateReplace</span>
|
||||
|
||||
<p>Replaces the placeholders in the template string with the corresponding values in the data map.The placeholders are enclosed in curly braces, e.g. {key}. for example, the template string is "Hello, {name}!", and the data map is {"name": "world"}, the result will be "Hello, world!".</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func TemplateReplace(template string, data map[string]string string
|
||||
```
|
||||
|
||||
<b>example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/strutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
template := `Hello, my name is {name}, I'm {age} years old.`
|
||||
data := map[string]string{
|
||||
"name": "Bob",
|
||||
"age": "20",
|
||||
}
|
||||
|
||||
result := strutil.TemplateReplace(template, data)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// Hello, my name is Bob, I'm 20 years old.
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="RegexMatchAllGroups">RegexMatchAllGroups</span>
|
||||
|
||||
<p>Matches all subgroups in a string using a regular expression and returns the result.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func RegexMatchAllGroups(pattern, str string) [][]string
|
||||
```
|
||||
|
||||
<b>example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/strutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
pattern := `(\w+\.+\w+)@(\w+)\.(\w+)`
|
||||
str := "Emails: john.doe@example.com and jane.doe@example.com"
|
||||
|
||||
result := strutil.RegexMatchAllGroups(pattern, str)
|
||||
|
||||
fmt.Println(result[0])
|
||||
fmt.Println(result[1])
|
||||
|
||||
// Output:
|
||||
// [john.doe@example.com john.doe example com]
|
||||
// [jane.doe@example.com jane.doe example com]
|
||||
}
|
||||
```
|
||||
@@ -31,6 +31,11 @@ import (
|
||||
- [CompareOsEnv](#CompareOsEnv)
|
||||
- [ExecCommand](#ExecCommand)
|
||||
- [GetOsBits](#GetOsBits)
|
||||
- [StartProcess](#StartProcess)
|
||||
- [StopProcess](#StopProcess)
|
||||
- [KillProcess](#KillProcess)
|
||||
- [GetProcessInfo](#GetProcessInfo)
|
||||
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -242,13 +247,14 @@ func main() {
|
||||
|
||||
### <span id="ExecCommand">ExecCommand</span>
|
||||
|
||||
<p>Execute shell command, return the stdout and stderr string of command, and error if error occur. param `command` is a complete command string, like, ls -a (linux), dir(windows), ping 127.0.0.1. In linux, use /bin/bash -c to execute command, In windows, use powershell.exe to execute command.</p>
|
||||
<p>Execute shell command, return the stdout and stderr string of command, and error if error occur. param `command` is a complete command string, like, ls -a (linux), dir(windows), ping 127.0.0.1. In linux, use /bin/bash -c to execute command, In windows, use powershell.exe to execute command.
|
||||
The second parameter of the function is the cmd option control parameter. The type is func(*exec.Cmd). You can set the cmd attribute through this parameter.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
type (
|
||||
Option func(*exec.Cmd)
|
||||
Option func(*exec.Cmd)
|
||||
)
|
||||
func ExecCommand(command string, opts ...Option) (stdout, stderr string, err error)
|
||||
```
|
||||
@@ -263,7 +269,9 @@ import (
|
||||
|
||||
func main() {
|
||||
// linux or mac
|
||||
stdout, stderr, err := system.ExecCommand("ls")
|
||||
stdout, stderr, err := system.ExecCommand("ls", func(cmd *exec.Cmd) {
|
||||
cmd.Dir = "/tmp"
|
||||
})
|
||||
fmt.Println("std out: ", stdout)
|
||||
fmt.Println("std err: ", stderr)
|
||||
assert.Equal("", stderr)
|
||||
@@ -285,7 +293,7 @@ func main() {
|
||||
|
||||
### <span id="GetOsBits">GetOsBits</span>
|
||||
|
||||
<p>Get current os bits, 32bit or 64bit. return 32 or 64</p>
|
||||
<p>Get current os bits, 32bit or 64bit. return 32 or 64.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
@@ -306,3 +314,132 @@ func main() {
|
||||
fmt.Println(osBit) // 32 or 64
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="StartProcess">StartProcess</span>
|
||||
|
||||
<p>Start a new process with the specified name and arguments.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func StartProcess(command string, args ...string) (int, error)
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block">[Run](todo)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/system"
|
||||
)
|
||||
|
||||
func main() {
|
||||
pid, err := system.StartProcess("sleep", "2")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(pid)
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="StopProcess">StopProcess</span>
|
||||
|
||||
<p>Stop a process by pid.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func StopProcess(pid int) error
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block">[Run](todo)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/system"
|
||||
)
|
||||
|
||||
func main() {
|
||||
pid, err := system.StartProcess("sleep", "10")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
err = system.StopProcess(pid)
|
||||
|
||||
fmt.Println(err)
|
||||
|
||||
// Output:
|
||||
// <nil>
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="KillProcess">KillProcess</span>
|
||||
|
||||
<p>Kill a process by pid.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func KillProcess(pid int) error
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block">[Run](todo)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/system"
|
||||
)
|
||||
|
||||
func main() {
|
||||
pid, err := system.StartProcess("sleep", "10")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
err = system.KillProcess(pid)
|
||||
|
||||
fmt.Println(err)
|
||||
|
||||
// Output:
|
||||
// <nil>
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="GetProcessInfo">GetProcessInfo</span>
|
||||
|
||||
<p>Retrieves detailed process information by pid.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func GetProcessInfo(pid int) (*ProcessInfo, error)
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block">[Run](todo)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/system"
|
||||
)
|
||||
|
||||
func main() {
|
||||
pid, err := system.StartProcess("ls", "-a")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
processInfo, err := system.GetProcessInfo(pid)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(processInfo)
|
||||
}
|
||||
```
|
||||
@@ -33,7 +33,7 @@ features:
|
||||
details: Well structure, test for every exported function.
|
||||
---
|
||||
|
||||
<p style="position:relative; top: -316px;left: 560px;">
|
||||
<p style="position:relative; inline-block;top: -330px;left: 30%">
|
||||
<img style="display: inline-block;margin-right:10px;" src="https://img.shields.io/github/stars/duke-git/lancet?style=social" alt="">
|
||||
<img style="display: inline-block" src="https://img.shields.io/github/forks/duke-git/lancet?style=social" alt="">
|
||||
</p>
|
||||
|
||||
@@ -33,7 +33,7 @@ features:
|
||||
details: 结构良好,测试每个导出的函数。
|
||||
---
|
||||
|
||||
<p style="position:relative;display: inline-block;top: -316px;left: 32%">
|
||||
<p style="position:relative;display: inline-block;top: -330px;left: 30%">
|
||||
<img style="display: inline-block;margin-right:10px;" src="https://img.shields.io/github/stars/duke-git/lancet?style=social" alt="">
|
||||
<img style="display: inline-block" src="https://img.shields.io/github/forks/duke-git/lancet?style=social" alt="">
|
||||
</p>
|
||||
|
||||
1171
docs/package-lock.json
generated
1171
docs/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,6 @@
|
||||
"docs:preview": "vitepress preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitepress": "^1.0.0-rc.4"
|
||||
"vitepress": "^1.2.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,58 +119,43 @@ func CreateDir(absPath string) error {
|
||||
// if dstPath exists, it will return an error.
|
||||
// Play: https://go.dev/play/p/YAyFTA_UuPb
|
||||
func CopyDir(srcPath string, dstPath string) error {
|
||||
if !IsDir(srcPath) {
|
||||
return errors.New("source path is not a directory")
|
||||
}
|
||||
var err error
|
||||
srcPath, err = filepath.Abs(srcPath)
|
||||
srcInfo, err := os.Stat(srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if IsExist(dstPath) {
|
||||
return errors.New("destination path already exists")
|
||||
}
|
||||
dstPath, err = filepath.Abs(dstPath)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to get source directory info: %w", err)
|
||||
}
|
||||
|
||||
// get srcPath's file info
|
||||
srcFileInfo, err := os.Stat(srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
if !srcInfo.IsDir() {
|
||||
return fmt.Errorf("source path is not a directory: %s", srcPath)
|
||||
}
|
||||
|
||||
// create dstPath with srcPath's mode
|
||||
err = os.MkdirAll(dstPath, srcFileInfo.Mode())
|
||||
err = os.MkdirAll(dstPath, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to create destination directory: %w", err)
|
||||
}
|
||||
|
||||
err = filepath.Walk(srcPath, func(path string, info os.FileInfo, err error) error {
|
||||
if srcPath == path {
|
||||
return nil
|
||||
}
|
||||
curDstPath := filepath.Join(dstPath, filepath.Base(path))
|
||||
if info.IsDir() {
|
||||
err = CopyDir(path, curDstPath)
|
||||
entries, err := os.ReadDir(srcPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read source directory: %w", err)
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
srcDir := filepath.Join(srcPath, entry.Name())
|
||||
dstDir := filepath.Join(dstPath, entry.Name())
|
||||
|
||||
if entry.IsDir() {
|
||||
err := CopyDir(srcDir, dstDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err = CopyFile(path, curDstPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Chmod(curDstPath, info.Mode())
|
||||
err := CopyFile(srcDir, dstDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsDir checks if the path is directory or not.
|
||||
|
||||
@@ -6,6 +6,9 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/duke-git/lancet/v2/mathutil"
|
||||
"github.com/duke-git/lancet/v2/strutil"
|
||||
)
|
||||
|
||||
//
|
||||
@@ -15,63 +18,63 @@ import (
|
||||
// http://en.wikipedia.org/wiki/Binary_prefix
|
||||
const (
|
||||
// Decimal
|
||||
UnitB = 1
|
||||
UnitKB = 1000
|
||||
UnitMB = 1000 * UnitKB
|
||||
UnitGB = 1000 * UnitMB
|
||||
UnitTB = 1000 * UnitGB
|
||||
UnitPB = 1000 * UnitTB
|
||||
UnitEB = 1000 * UnitPB
|
||||
unitB = 1
|
||||
unitKB = 1000
|
||||
unitMB = 1000 * unitKB
|
||||
unitGB = 1000 * unitMB
|
||||
unitTB = 1000 * unitGB
|
||||
unitPB = 1000 * unitTB
|
||||
unitEB = 1000 * unitPB
|
||||
|
||||
// Binary
|
||||
UnitBiB = 1
|
||||
UnitKiB = 1024
|
||||
UnitMiB = 1024 * UnitKiB
|
||||
UnitGiB = 1024 * UnitMiB
|
||||
UnitTiB = 1024 * UnitGiB
|
||||
UnitPiB = 1024 * UnitTiB
|
||||
UnitEiB = 1024 * UnitPiB
|
||||
unitBiB = 1
|
||||
unitKiB = 1024
|
||||
unitMiB = 1024 * unitKiB
|
||||
unitGiB = 1024 * unitMiB
|
||||
unitTiB = 1024 * unitGiB
|
||||
unitPiB = 1024 * unitTiB
|
||||
unitEiB = 1024 * unitPiB
|
||||
)
|
||||
|
||||
// type byteUnitMap map[byte]int64
|
||||
|
||||
var (
|
||||
decimalByteMap = map[string]uint64{
|
||||
"b": UnitB,
|
||||
"kb": UnitKB,
|
||||
"mb": UnitMB,
|
||||
"gb": UnitGB,
|
||||
"tb": UnitTB,
|
||||
"pb": UnitPB,
|
||||
"eb": UnitEB,
|
||||
"b": unitB,
|
||||
"kb": unitKB,
|
||||
"mb": unitMB,
|
||||
"gb": unitGB,
|
||||
"tb": unitTB,
|
||||
"pb": unitPB,
|
||||
"eb": unitEB,
|
||||
|
||||
// Without suffix
|
||||
"": UnitB,
|
||||
"k": UnitKB,
|
||||
"m": UnitMB,
|
||||
"g": UnitGB,
|
||||
"t": UnitTB,
|
||||
"p": UnitPB,
|
||||
"e": UnitEB,
|
||||
"": unitB,
|
||||
"k": unitKB,
|
||||
"m": unitMB,
|
||||
"g": unitGB,
|
||||
"t": unitTB,
|
||||
"p": unitPB,
|
||||
"e": unitEB,
|
||||
}
|
||||
|
||||
binaryByteMap = map[string]uint64{
|
||||
"bi": UnitBiB,
|
||||
"kib": UnitKiB,
|
||||
"mib": UnitMiB,
|
||||
"gib": UnitGiB,
|
||||
"tib": UnitTiB,
|
||||
"pib": UnitPiB,
|
||||
"eib": UnitEiB,
|
||||
"bi": unitBiB,
|
||||
"kib": unitKiB,
|
||||
"mib": unitMiB,
|
||||
"gib": unitGiB,
|
||||
"tib": unitTiB,
|
||||
"pib": unitPiB,
|
||||
"eib": unitEiB,
|
||||
|
||||
// Without suffix
|
||||
"": UnitBiB,
|
||||
"ki": UnitKiB,
|
||||
"mi": UnitMiB,
|
||||
"gi": UnitGiB,
|
||||
"ti": UnitTiB,
|
||||
"pi": UnitPiB,
|
||||
"ei": UnitEiB,
|
||||
"": unitBiB,
|
||||
"ki": unitKiB,
|
||||
"mi": unitMiB,
|
||||
"gi": unitGiB,
|
||||
"ti": unitTiB,
|
||||
"pi": unitPiB,
|
||||
"ei": unitEiB,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -84,26 +87,28 @@ var (
|
||||
// The precision parameter specifies the number of digits after the decimal point, which defaults to 4.
|
||||
// Play: https://go.dev/play/p/FPXs1suwRcs
|
||||
func DecimalBytes(size float64, precision ...int) string {
|
||||
p := 5
|
||||
pointPosition := 4
|
||||
if len(precision) > 0 {
|
||||
p = precision[0] + 1
|
||||
pointPosition = precision[0]
|
||||
}
|
||||
|
||||
size, unit := calculateByteSize(size, 1000.0, decimalByteUnits)
|
||||
|
||||
return fmt.Sprintf("%.*g%s", p, size, unit)
|
||||
return roundToToString(size, pointPosition) + unit
|
||||
}
|
||||
|
||||
// BinaryBytes returns a human-readable byte size under binary standard (base 1024)
|
||||
// The precision parameter specifies the number of digits after the decimal point, which defaults to 4.
|
||||
// Play: https://go.dev/play/p/G9oHHMCAZxP
|
||||
func BinaryBytes(size float64, precision ...int) string {
|
||||
p := 5
|
||||
pointPosition := 4
|
||||
if len(precision) > 0 {
|
||||
p = precision[0] + 1
|
||||
pointPosition = precision[0]
|
||||
}
|
||||
|
||||
size, unit := calculateByteSize(size, 1024.0, binaryByteUnits)
|
||||
|
||||
return fmt.Sprintf("%.*g%s", p, size, unit)
|
||||
return roundToToString(size, pointPosition) + unit
|
||||
}
|
||||
|
||||
func calculateByteSize(size float64, base float64, byteUnits []string) (float64, string) {
|
||||
@@ -116,6 +121,29 @@ func calculateByteSize(size float64, base float64, byteUnits []string) (float64,
|
||||
return size, byteUnits[i]
|
||||
}
|
||||
|
||||
func roundToToString(x float64, max ...int) string {
|
||||
pointPosition := 4
|
||||
if len(max) > 0 {
|
||||
pointPosition = max[0]
|
||||
}
|
||||
result := mathutil.RoundToString(x, pointPosition)
|
||||
|
||||
// 删除小数位结尾的0
|
||||
decimal := strings.TrimRight(strutil.After(result, "."), "0")
|
||||
if decimal == "" || pointPosition == 0 {
|
||||
// 没有小数位直接返回整数
|
||||
return strutil.Before(result, ".")
|
||||
}
|
||||
|
||||
// 小数位大于想要设置的位数,按需要截断
|
||||
if len(decimal) > pointPosition {
|
||||
return strutil.Before(result, ".") + "." + decimal[:pointPosition]
|
||||
}
|
||||
|
||||
// 小数位小于等于想要的位数,直接拼接返回
|
||||
return strutil.Before(result, ".") + "." + decimal
|
||||
}
|
||||
|
||||
// ParseDecimalBytes return the human readable bytes size string into the amount it represents(base 1000).
|
||||
// ParseDecimalBytes("42 MB") -> 42000000, nil
|
||||
// Play: https://go.dev/play/p/Am98ybWjvjj
|
||||
|
||||
@@ -15,11 +15,19 @@ func TestDecimalBytes(t *testing.T) {
|
||||
assert.Equal("1.024KB", DecimalBytes(1024))
|
||||
assert.Equal("1.2346MB", DecimalBytes(1234567))
|
||||
assert.Equal("1.235MB", DecimalBytes(1234567, 3))
|
||||
assert.Equal("1.123GB", DecimalBytes(float64(1.123*UnitGB)))
|
||||
assert.Equal("2.123TB", DecimalBytes(float64(2.123*UnitTB)))
|
||||
assert.Equal("3.123PB", DecimalBytes(float64(3.123*UnitPB)))
|
||||
assert.Equal("4.123EB", DecimalBytes(float64(4.123*UnitEB)))
|
||||
assert.Equal("1EB", DecimalBytes(float64(1000*UnitPB)))
|
||||
assert.Equal("1.123GB", DecimalBytes(float64(1.123*unitGB)))
|
||||
assert.Equal("2.123TB", DecimalBytes(float64(2.123*unitTB)))
|
||||
assert.Equal("3.123PB", DecimalBytes(float64(3.123*unitPB)))
|
||||
assert.Equal("4.123EB", DecimalBytes(float64(4.123*unitEB)))
|
||||
assert.Equal("1EB", DecimalBytes(float64(1000*unitPB)))
|
||||
assert.Equal("62MB", DecimalBytes(61812496, 0))
|
||||
assert.Equal("61.8MB", DecimalBytes(61812496, 1))
|
||||
assert.Equal("401MB", DecimalBytes(401000000))
|
||||
assert.Equal("401MB", DecimalBytes(401000000, 0))
|
||||
assert.Equal("4MB", DecimalBytes(4010000, 0))
|
||||
|
||||
assert.Equal("4MB", DecimalBytes(4000000, 0))
|
||||
assert.Equal("4MB", DecimalBytes(4000000, 1))
|
||||
}
|
||||
|
||||
func TestBinaryBytes(t *testing.T) {
|
||||
@@ -31,6 +39,10 @@ func TestBinaryBytes(t *testing.T) {
|
||||
assert.Equal("1MiB", BinaryBytes(1024*1024))
|
||||
assert.Equal("1.1774MiB", BinaryBytes(1234567))
|
||||
assert.Equal("1.18MiB", BinaryBytes(1234567, 2))
|
||||
|
||||
assert.Equal("10KiB", BinaryBytes(10240, 0))
|
||||
assert.Equal("10KiB", BinaryBytes(10240, 1))
|
||||
assert.Equal("10KiB", BinaryBytes(10240, 2))
|
||||
}
|
||||
|
||||
func TestParseDecimalBytes(t *testing.T) {
|
||||
|
||||
@@ -14,11 +14,11 @@ import (
|
||||
"golang.org/x/exp/constraints"
|
||||
)
|
||||
|
||||
// Comma add comma to a number value by every 3 numbers from right. ahead by symbol char.
|
||||
// Comma add comma to a number value by every 3 numbers from right. ahead by prefix symbol char.
|
||||
// if value is invalid number string eg "aa", return empty string
|
||||
// Comma("12345", "$") => "$12,345", Comma(12345, "$") => "$12,345"
|
||||
// Play: https://go.dev/play/p/eRD5k2vzUVX
|
||||
func Comma[T constraints.Float | constraints.Integer | string](value T, symbol string) string {
|
||||
func Comma[T constraints.Float | constraints.Integer | string](value T, prefixSymbol string) string {
|
||||
numString := convertor.ToString(value)
|
||||
|
||||
_, err := strconv.ParseFloat(numString, 64)
|
||||
@@ -26,17 +26,26 @@ func Comma[T constraints.Float | constraints.Integer | string](value T, symbol s
|
||||
return ""
|
||||
}
|
||||
|
||||
isNegative := strings.HasPrefix(numString, "-")
|
||||
if isNegative {
|
||||
numString = numString[1:]
|
||||
}
|
||||
|
||||
index := strings.Index(numString, ".")
|
||||
if index == -1 {
|
||||
index = len(numString)
|
||||
}
|
||||
|
||||
for index > 3 {
|
||||
index = index - 3
|
||||
index -= 3
|
||||
numString = numString[:index] + "," + numString[index:]
|
||||
}
|
||||
|
||||
return symbol + numString
|
||||
if isNegative {
|
||||
numString = "-" + numString
|
||||
}
|
||||
|
||||
return prefixSymbol + numString
|
||||
}
|
||||
|
||||
// Pretty data to JSON string.
|
||||
|
||||
@@ -28,6 +28,10 @@ func TestComma(t *testing.T) {
|
||||
assert.Equal("12,345.6789", Comma(+12345.6789, ""))
|
||||
assert.Equal("12,345,678.9", Comma(12345678.9, ""))
|
||||
assert.Equal("123,456,789,000", Comma(123456789000, ""))
|
||||
|
||||
assert.Equal("-999", Comma(-999, ""))
|
||||
assert.Equal("-1,000", Comma(-1000, ""))
|
||||
assert.Equal("-1,234,567", Comma(-1234567, ""))
|
||||
}
|
||||
|
||||
func TestPretty(t *testing.T) {
|
||||
|
||||
@@ -7,6 +7,7 @@ package function
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -84,36 +85,97 @@ func Delay(delay time.Duration, fn any, args ...any) {
|
||||
}
|
||||
|
||||
// Debounced creates a debounced function that delays invoking fn until after wait duration have elapsed since the last time the debounced function was invoked.
|
||||
// Deprecated: Use Debounce function instead.
|
||||
// Play: https://go.dev/play/p/absuEGB_GN7
|
||||
func Debounced(fn func(), duration time.Duration) func() {
|
||||
// Catch programming error while constructing the closure
|
||||
mustBeFunction(fn)
|
||||
func Debounced(fn func(), delay time.Duration) func() {
|
||||
debouncedFn, _ := Debounce(fn, delay)
|
||||
return debouncedFn
|
||||
}
|
||||
|
||||
timer := time.NewTimer(duration)
|
||||
timer.Stop()
|
||||
// Debounce creates a debounced version of the provided function.
|
||||
// Play: todo
|
||||
func Debounce(fn func(), delay time.Duration) (debouncedFn func(), cancelFn func()) {
|
||||
var (
|
||||
timer *time.Timer
|
||||
mu sync.Mutex
|
||||
)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
<-timer.C
|
||||
go fn()
|
||||
debouncedFn = func() {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
if timer != nil {
|
||||
timer.Stop()
|
||||
}
|
||||
}()
|
||||
|
||||
return func() { timer.Reset(duration) }
|
||||
timer = time.AfterFunc(delay, func() {
|
||||
fn()
|
||||
})
|
||||
}
|
||||
|
||||
cancelFn = func() {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
if timer != nil {
|
||||
timer.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
return debouncedFn, cancelFn
|
||||
}
|
||||
|
||||
// Throttle creates a throttled version of the provided function.
|
||||
// The returned function guarantees that it will only be invoked at most once per interval.
|
||||
// Play: todo
|
||||
func Throttle(fn func(), interval time.Duration) func() {
|
||||
var (
|
||||
timer *time.Timer
|
||||
lastRun time.Time
|
||||
mu sync.Mutex
|
||||
)
|
||||
|
||||
return func() {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
if now.Sub(lastRun) >= interval {
|
||||
fn()
|
||||
lastRun = now
|
||||
if timer != nil {
|
||||
timer.Stop()
|
||||
timer = nil
|
||||
}
|
||||
} else if timer == nil {
|
||||
delay := interval - now.Sub(lastRun)
|
||||
|
||||
timer = time.AfterFunc(delay, func() {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
fn()
|
||||
lastRun = time.Now()
|
||||
timer = nil
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Schedule invoke function every duration time, util close the returned bool channel.
|
||||
// Play: https://go.dev/play/p/hbON-Xeyn5N
|
||||
func Schedule(d time.Duration, fn any, args ...any) chan bool {
|
||||
func Schedule(duration time.Duration, fn any, args ...any) chan bool {
|
||||
// Catch programming error while constructing the closure
|
||||
mustBeFunction(fn)
|
||||
|
||||
quit := make(chan bool)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
unsafeInvokeFunc(fn, args...)
|
||||
|
||||
select {
|
||||
case <-time.After(d):
|
||||
case <-time.After(duration):
|
||||
case <-quit:
|
||||
return
|
||||
}
|
||||
|
||||
@@ -79,6 +79,32 @@ func ExampleDelay() {
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleDebounce() {
|
||||
callCount := 0
|
||||
fn := func() {
|
||||
callCount++
|
||||
}
|
||||
|
||||
debouncedFn, _ := Debounce(fn, 500*time.Millisecond)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
debouncedFn()
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
fmt.Println(callCount)
|
||||
|
||||
debouncedFn()
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
fmt.Println(callCount)
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
// 2
|
||||
}
|
||||
|
||||
func ExampleDebounced() {
|
||||
count := 0
|
||||
add := func() {
|
||||
@@ -174,3 +200,24 @@ func ExampleAcceptIf() {
|
||||
// 0
|
||||
// false
|
||||
}
|
||||
|
||||
func ExampleThrottle() {
|
||||
callCount := 0
|
||||
|
||||
fn := func() {
|
||||
callCount++
|
||||
}
|
||||
|
||||
throttledFn := Throttle(fn, 1*time.Second)
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
throttledFn()
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
fmt.Println(callCount)
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
}
|
||||
|
||||
@@ -125,24 +125,178 @@ func TestDebounced(t *testing.T) {
|
||||
assert.Equal(2, count)
|
||||
}
|
||||
|
||||
func TestDebounce(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestDebounce")
|
||||
|
||||
t.Run("Single call", func(t *testing.T) {
|
||||
callCount := 0
|
||||
|
||||
debouncedFn, _ := Debounce(func() {
|
||||
callCount++
|
||||
}, 500*time.Millisecond)
|
||||
|
||||
debouncedFn()
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
assert.Equal(1, callCount)
|
||||
})
|
||||
|
||||
t.Run("Multiple calls within debounce interval", func(t *testing.T) {
|
||||
callCount := 0
|
||||
|
||||
debouncedFn, _ := Debounce(func() {
|
||||
callCount++
|
||||
}, 1*time.Second)
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
go func(index int) {
|
||||
time.Sleep(time.Duration(index) * 200 * time.Millisecond)
|
||||
debouncedFn()
|
||||
}(i)
|
||||
}
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
assert.Equal(1, callCount)
|
||||
})
|
||||
|
||||
t.Run("Immediate consecutive calls", func(t *testing.T) {
|
||||
callCount := 0
|
||||
|
||||
debouncedFn, _ := Debounce(func() {
|
||||
callCount++
|
||||
}, 500*time.Millisecond)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
debouncedFn()
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
assert.Equal(1, callCount)
|
||||
})
|
||||
|
||||
t.Run("Cancel calls", func(t *testing.T) {
|
||||
callCount := 0
|
||||
|
||||
debouncedFn, cancelFn := Debounce(func() {
|
||||
callCount++
|
||||
}, 500*time.Millisecond)
|
||||
|
||||
debouncedFn()
|
||||
|
||||
cancelFn()
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
assert.Equal(0, callCount)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestThrottle(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestThrottle")
|
||||
|
||||
t.Run("Single call", func(t *testing.T) {
|
||||
callCount := 0
|
||||
|
||||
throttledFn := Throttle(func() {
|
||||
callCount++
|
||||
}, 1*time.Second)
|
||||
|
||||
throttledFn()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
assert.Equal(1, callCount)
|
||||
})
|
||||
|
||||
t.Run("Multiple calls within throttle interval", func(t *testing.T) {
|
||||
callCount := 0
|
||||
|
||||
throttledFn := Throttle(func() {
|
||||
callCount++
|
||||
}, 1*time.Second)
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
throttledFn()
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
assert.Equal(1, callCount)
|
||||
})
|
||||
|
||||
t.Run("Mutiple calls space out throttle interval", func(t *testing.T) {
|
||||
callCount := 0
|
||||
|
||||
throttledFn := Throttle(func() {
|
||||
callCount++
|
||||
}, 500*time.Millisecond)
|
||||
|
||||
throttledFn()
|
||||
time.Sleep(600 * time.Millisecond)
|
||||
|
||||
throttledFn()
|
||||
time.Sleep(600 * time.Millisecond)
|
||||
|
||||
throttledFn()
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
assert.Equal(3, callCount)
|
||||
})
|
||||
|
||||
t.Run("Call function near the end of the interval", func(t *testing.T) {
|
||||
callCount := 0
|
||||
|
||||
throttledFn := Throttle(func() {
|
||||
callCount++
|
||||
}, 1*time.Second)
|
||||
|
||||
throttledFn()
|
||||
time.Sleep(900 * time.Millisecond)
|
||||
|
||||
throttledFn()
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
assert.Equal(2, callCount)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestSchedule(t *testing.T) {
|
||||
// assert := internal.NewAssert(t, "TestSchedule")
|
||||
assert := internal.NewAssert(t, "TestSchedule")
|
||||
|
||||
var res []string
|
||||
appendStr := func(s string) {
|
||||
res = append(res, s)
|
||||
}
|
||||
t.Run("Single call", func(t *testing.T) {
|
||||
res := []string{}
|
||||
appendStr := func(s string) {
|
||||
res = append(res, s)
|
||||
}
|
||||
stop := Schedule(200*time.Millisecond, appendStr, "*")
|
||||
close(stop)
|
||||
|
||||
stop := Schedule(1*time.Second, appendStr, "*")
|
||||
time.Sleep(5 * time.Second)
|
||||
close(stop)
|
||||
time.Sleep(400 * time.Millisecond)
|
||||
|
||||
t.Log(res)
|
||||
assert.Equal([]string{"*"}, res)
|
||||
})
|
||||
|
||||
t.Run("Multiple calls", func(t *testing.T) {
|
||||
res := []string{}
|
||||
appendStr := func(s string) {
|
||||
res = append(res, s)
|
||||
}
|
||||
stop := Schedule(200*time.Millisecond, appendStr, "*")
|
||||
|
||||
time.Sleep(800 * time.Millisecond)
|
||||
|
||||
close(stop)
|
||||
|
||||
assert.Equal([]string{"*", "*", "*", "*"}, res)
|
||||
})
|
||||
|
||||
// todo: in github action, for now, this test is not working sometimes
|
||||
// res maybe [* * * * *] or [* * * * * *]
|
||||
// expected := []string{"*", "*", "*", "*", "*"}
|
||||
// assert.Equal(expected, res)
|
||||
}
|
||||
|
||||
func TestPipeline(t *testing.T) {
|
||||
|
||||
236
maputil/map.go
236
maputil/map.go
@@ -70,7 +70,12 @@ func ValuesBy[K comparable, V any, T any](m map[K]V, mapper func(item V) T) []T
|
||||
// Merge maps, next key will overwrite previous key.
|
||||
// Play: https://go.dev/play/p/H95LENF1uB-
|
||||
func Merge[K comparable, V any](maps ...map[K]V) map[K]V {
|
||||
result := make(map[K]V, 0)
|
||||
size := 0
|
||||
for i := range maps {
|
||||
size += len(maps[i])
|
||||
}
|
||||
|
||||
result := make(map[K]V, size)
|
||||
|
||||
for _, m := range maps {
|
||||
for k, v := range m {
|
||||
@@ -310,7 +315,7 @@ func HasKey[K comparable, V any](m map[K]V, key K) bool {
|
||||
}
|
||||
|
||||
// MapToStruct converts map to struct
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/7wYyVfX38Dp
|
||||
func MapToStruct(m map[string]any, structObj any) error {
|
||||
for k, v := range m {
|
||||
err := setStructField(structObj, k, v)
|
||||
@@ -389,6 +394,7 @@ func getFieldNameByJsonTag(structObj any, jsonTag string) string {
|
||||
}
|
||||
|
||||
// ToSortedSlicesDefault converts a map to two slices sorted by key: one for the keys and another for the values.
|
||||
// Play: https://go.dev/play/p/43gEM2po-qy
|
||||
func ToSortedSlicesDefault[K constraints.Ordered, V any](m map[K]V) ([]K, []V) {
|
||||
keys := make([]K, 0, len(m))
|
||||
|
||||
@@ -413,6 +419,7 @@ func ToSortedSlicesDefault[K constraints.Ordered, V any](m map[K]V) ([]K, []V) {
|
||||
|
||||
// ToSortedSlicesWithComparator converts a map to two slices sorted by key and using a custom comparison function:
|
||||
// one for the keys and another for the values.
|
||||
// Play: https://go.dev/play/p/0nlPo6YLdt3
|
||||
func ToSortedSlicesWithComparator[K comparable, V any](m map[K]V, comparator func(a, b K) bool) ([]K, []V) {
|
||||
keys := make([]K, 0, len(m))
|
||||
|
||||
@@ -434,3 +441,228 @@ func ToSortedSlicesWithComparator[K comparable, V any](m map[K]V, comparator fun
|
||||
|
||||
return keys, sortedValues
|
||||
}
|
||||
|
||||
// GetOrSet returns value of the given key or set the given value value if not present.
|
||||
// Play: https://go.dev/play/p/IVQwO1OkEJC
|
||||
func GetOrSet[K comparable, V any](m map[K]V, key K, value V) V {
|
||||
if v, ok := m[key]; ok {
|
||||
return v
|
||||
}
|
||||
|
||||
m[key] = value
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
// SortByKey sorts the map by its keys and returns a new map with sorted keys.
|
||||
// Play: todo
|
||||
func SortByKey[K constraints.Ordered, V any](m map[K]V, less func(a, b K) bool) (sortedKeysMap map[K]V) {
|
||||
keys := make([]K, 0, len(m))
|
||||
for k := range m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
sort.Slice(keys, func(i, j int) bool {
|
||||
return less(keys[i], keys[j])
|
||||
})
|
||||
|
||||
sortedKeysMap = make(map[K]V, len(m))
|
||||
for _, k := range keys {
|
||||
sortedKeysMap[k] = m[k]
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var mapHandlers = map[reflect.Kind]func(reflect.Value, reflect.Value) error{
|
||||
reflect.String: convertNormal,
|
||||
reflect.Int: convertNormal,
|
||||
reflect.Int16: convertNormal,
|
||||
reflect.Int32: convertNormal,
|
||||
reflect.Int64: convertNormal,
|
||||
reflect.Uint: convertNormal,
|
||||
reflect.Uint16: convertNormal,
|
||||
reflect.Uint32: convertNormal,
|
||||
reflect.Uint64: convertNormal,
|
||||
reflect.Float32: convertNormal,
|
||||
reflect.Float64: convertNormal,
|
||||
reflect.Uint8: convertNormal,
|
||||
reflect.Int8: convertNormal,
|
||||
reflect.Struct: convertNormal,
|
||||
reflect.Complex64: convertNormal,
|
||||
reflect.Complex128: convertNormal,
|
||||
}
|
||||
|
||||
var _ = func() struct{} {
|
||||
mapHandlers[reflect.Map] = convertMap
|
||||
mapHandlers[reflect.Array] = convertSlice
|
||||
mapHandlers[reflect.Slice] = convertSlice
|
||||
return struct{}{}
|
||||
}()
|
||||
|
||||
// MapTo try to map any interface to struct or base type
|
||||
/*
|
||||
Eg:
|
||||
v := map[string]interface{}{
|
||||
"service":map[string]interface{}{
|
||||
"ip":"127.0.0.1",
|
||||
"port":1234,
|
||||
},
|
||||
version:"v1.0.01"
|
||||
}
|
||||
|
||||
type Target struct {
|
||||
Service struct {
|
||||
Ip string `json:"ip"`
|
||||
Port int `json:"port"`
|
||||
} `json:"service"`
|
||||
Ver string `json:"version"`
|
||||
}
|
||||
|
||||
var dist Target
|
||||
if err := maputil.MapTo(v,&dist); err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Println(dist)
|
||||
|
||||
*/
|
||||
// Play: https://go.dev/play/p/4K7KBEPgS5M
|
||||
func MapTo(src any, dst any) error {
|
||||
dstRef := reflect.ValueOf(dst)
|
||||
|
||||
if dstRef.Kind() != reflect.Ptr {
|
||||
return fmt.Errorf("dst is not ptr")
|
||||
}
|
||||
|
||||
dstElem := dstRef.Type().Elem()
|
||||
if dstElem.Kind() == reflect.Struct {
|
||||
srcMap := src.(map[string]interface{})
|
||||
return MapToStruct(srcMap, dst)
|
||||
}
|
||||
|
||||
dstRef = reflect.Indirect(dstRef)
|
||||
|
||||
srcRef := reflect.ValueOf(src)
|
||||
if srcRef.Kind() == reflect.Ptr || srcRef.Kind() == reflect.Interface {
|
||||
srcRef = srcRef.Elem()
|
||||
}
|
||||
|
||||
if f, ok := mapHandlers[srcRef.Kind()]; ok {
|
||||
return f(srcRef, dstRef)
|
||||
}
|
||||
|
||||
return fmt.Errorf("no implemented:%s", srcRef.Type())
|
||||
}
|
||||
|
||||
func convertNormal(src reflect.Value, dst reflect.Value) error {
|
||||
if dst.CanSet() {
|
||||
if src.Type() == dst.Type() {
|
||||
dst.Set(src)
|
||||
} else if src.CanConvert(dst.Type()) {
|
||||
dst.Set(src.Convert(dst.Type()))
|
||||
} else {
|
||||
return fmt.Errorf("can not convert:%s:%s", src.Type().String(), dst.Type().String())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertSlice(src reflect.Value, dst reflect.Value) error {
|
||||
if dst.Kind() != reflect.Array && dst.Kind() != reflect.Slice {
|
||||
return fmt.Errorf("error type:%s", dst.Type().String())
|
||||
}
|
||||
l := src.Len()
|
||||
target := reflect.MakeSlice(dst.Type(), l, l)
|
||||
if dst.CanSet() {
|
||||
dst.Set(target)
|
||||
}
|
||||
for i := 0; i < l; i++ {
|
||||
srcValue := src.Index(i)
|
||||
if srcValue.Kind() == reflect.Ptr || srcValue.Kind() == reflect.Interface {
|
||||
srcValue = srcValue.Elem()
|
||||
}
|
||||
if f, ok := mapHandlers[srcValue.Kind()]; ok {
|
||||
err := f(srcValue, dst.Index(i))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertMap(src reflect.Value, dst reflect.Value) error {
|
||||
if src.Kind() != reflect.Map || dst.Kind() != reflect.Struct {
|
||||
if src.Kind() == reflect.Interface && dst.IsValid() {
|
||||
return convertMap(src.Elem(), dst)
|
||||
} else {
|
||||
return fmt.Errorf("src or dst type error,%s,%s", src.Type().String(), dst.Type().String())
|
||||
}
|
||||
}
|
||||
dstType := dst.Type()
|
||||
num := dstType.NumField()
|
||||
|
||||
exist := map[string]int{}
|
||||
|
||||
for i := 0; i < num; i++ {
|
||||
k := dstType.Field(i).Tag.Get("json")
|
||||
if k == "" {
|
||||
k = dstType.Field(i).Name
|
||||
}
|
||||
if strings.Contains(k, ",") {
|
||||
taglist := strings.Split(k, ",")
|
||||
if taglist[0] == "" {
|
||||
k = dstType.Field(i).Name
|
||||
} else {
|
||||
k = taglist[0]
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
exist[k] = i
|
||||
}
|
||||
|
||||
keys := src.MapKeys()
|
||||
|
||||
for _, key := range keys {
|
||||
if index, ok := exist[key.String()]; ok {
|
||||
v := dst.Field(index)
|
||||
|
||||
if v.Kind() == reflect.Struct {
|
||||
err := convertMap(src.MapIndex(key), v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if v.CanSet() {
|
||||
if v.Type() == src.MapIndex(key).Elem().Type() {
|
||||
v.Set(src.MapIndex(key).Elem())
|
||||
} else if src.MapIndex(key).Elem().CanConvert(v.Type()) {
|
||||
v.Set(src.MapIndex(key).Elem().Convert(v.Type()))
|
||||
} else if f, ok := mapHandlers[src.MapIndex(key).Elem().Kind()]; ok && f != nil {
|
||||
err := f(src.MapIndex(key).Elem(), v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("error type:d(%s)s(%s)", v.Type(), src.Type())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetOrDefault returns the value of the given key or a default value if the key is not present.
|
||||
// Play: todo
|
||||
func GetOrDefault[K comparable, V any](m map[K]V, key K, defaultValue V) V {
|
||||
if v, ok := m[key]; ok {
|
||||
return v
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
@@ -524,3 +524,308 @@ func ExampleToSortedSlicesWithComparator() {
|
||||
// [3 2 1]
|
||||
// [c b a]
|
||||
}
|
||||
|
||||
func ExampleGetOrSet() {
|
||||
m := map[int]string{
|
||||
1: "a",
|
||||
}
|
||||
|
||||
result1 := GetOrSet(m, 1, "1")
|
||||
result2 := GetOrSet(m, 2, "b")
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
|
||||
// Output:
|
||||
// a
|
||||
// b
|
||||
}
|
||||
|
||||
func ExampleSortByKey() {
|
||||
m := map[int]string{
|
||||
3: "c",
|
||||
1: "a",
|
||||
4: "d",
|
||||
2: "b",
|
||||
}
|
||||
|
||||
result := SortByKey(m, func(a, b int) bool {
|
||||
return a < b
|
||||
})
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// map[1:a 2:b 3:c 4:d]
|
||||
}
|
||||
|
||||
func ExampleOrderedMap_Set() {
|
||||
om := NewOrderedMap[string, int]()
|
||||
|
||||
om.Set("a", 1)
|
||||
om.Set("b", 2)
|
||||
om.Set("c", 3)
|
||||
|
||||
val1, ok := om.Get("a")
|
||||
fmt.Println(val1, ok)
|
||||
|
||||
val2, ok := om.Get("d")
|
||||
fmt.Println(val2, ok)
|
||||
|
||||
// Output:
|
||||
// 1 true
|
||||
// 0 false
|
||||
}
|
||||
|
||||
func ExampleOrderedMap_Get() {
|
||||
om := NewOrderedMap[string, int]()
|
||||
|
||||
om.Set("a", 1)
|
||||
om.Set("b", 2)
|
||||
om.Set("c", 3)
|
||||
|
||||
val1, ok := om.Get("a")
|
||||
fmt.Println(val1, ok)
|
||||
|
||||
val2, ok := om.Get("d")
|
||||
fmt.Println(val2, ok)
|
||||
|
||||
// Output:
|
||||
// 1 true
|
||||
// 0 false
|
||||
}
|
||||
|
||||
func ExampleOrderedMap_Front() {
|
||||
om := NewOrderedMap[string, int]()
|
||||
|
||||
om.Set("a", 1)
|
||||
om.Set("b", 2)
|
||||
om.Set("c", 3)
|
||||
|
||||
frontElement, ok := om.Front()
|
||||
fmt.Println(frontElement)
|
||||
fmt.Println(ok)
|
||||
|
||||
// Output:
|
||||
// {a 1}
|
||||
// true
|
||||
}
|
||||
|
||||
func ExampleOrderedMap_Back() {
|
||||
om := NewOrderedMap[string, int]()
|
||||
|
||||
om.Set("a", 1)
|
||||
om.Set("b", 2)
|
||||
om.Set("c", 3)
|
||||
|
||||
backElement, ok := om.Back()
|
||||
fmt.Println(backElement)
|
||||
fmt.Println(ok)
|
||||
|
||||
// Output:
|
||||
// {c 3}
|
||||
// true
|
||||
}
|
||||
|
||||
func ExampleOrderedMap_Delete() {
|
||||
om := NewOrderedMap[string, int]()
|
||||
|
||||
om.Set("a", 1)
|
||||
om.Set("b", 2)
|
||||
om.Set("c", 3)
|
||||
|
||||
om.Delete("b")
|
||||
|
||||
fmt.Println(om.Keys())
|
||||
|
||||
// Output:
|
||||
// [a c]
|
||||
}
|
||||
|
||||
func ExampleOrderedMap_Clear() {
|
||||
om := NewOrderedMap[string, int]()
|
||||
|
||||
om.Set("a", 1)
|
||||
om.Set("b", 2)
|
||||
om.Set("c", 3)
|
||||
|
||||
om.Clear()
|
||||
|
||||
fmt.Println(om.Keys())
|
||||
|
||||
// Output:
|
||||
// []
|
||||
}
|
||||
|
||||
func ExampleOrderedMap_Len() {
|
||||
om := NewOrderedMap[string, int]()
|
||||
|
||||
om.Set("a", 1)
|
||||
om.Set("b", 2)
|
||||
om.Set("c", 3)
|
||||
|
||||
om.Len()
|
||||
|
||||
fmt.Println(om.Len())
|
||||
|
||||
// Output:
|
||||
// 3
|
||||
}
|
||||
|
||||
func ExampleOrderedMap_Keys() {
|
||||
om := NewOrderedMap[string, int]()
|
||||
|
||||
om.Set("a", 1)
|
||||
om.Set("b", 2)
|
||||
om.Set("c", 3)
|
||||
|
||||
keys := om.Keys()
|
||||
|
||||
fmt.Println(keys)
|
||||
|
||||
// Output:
|
||||
// [a b c]
|
||||
}
|
||||
|
||||
func ExampleOrderedMap_Values() {
|
||||
om := NewOrderedMap[string, int]()
|
||||
|
||||
om.Set("a", 1)
|
||||
om.Set("b", 2)
|
||||
om.Set("c", 3)
|
||||
|
||||
values := om.Values()
|
||||
|
||||
fmt.Println(values)
|
||||
|
||||
// Output:
|
||||
// [1 2 3]
|
||||
}
|
||||
|
||||
func ExampleOrderedMap_Contains() {
|
||||
om := NewOrderedMap[string, int]()
|
||||
|
||||
om.Set("a", 1)
|
||||
om.Set("b", 2)
|
||||
om.Set("c", 3)
|
||||
|
||||
result1 := om.Contains("a")
|
||||
result2 := om.Contains("d")
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
}
|
||||
|
||||
func ExampleOrderedMap_Range() {
|
||||
om := NewOrderedMap[string, int]()
|
||||
|
||||
om.Set("a", 1)
|
||||
om.Set("b", 2)
|
||||
om.Set("c", 3)
|
||||
|
||||
om.Range(func(key string, value int) bool {
|
||||
fmt.Println(key, value)
|
||||
return true
|
||||
})
|
||||
|
||||
// Output:
|
||||
// a 1
|
||||
// b 2
|
||||
// c 3
|
||||
}
|
||||
|
||||
func ExampleOrderedMap_Elements() {
|
||||
om := NewOrderedMap[string, int]()
|
||||
|
||||
om.Set("a", 1)
|
||||
om.Set("b", 2)
|
||||
om.Set("c", 3)
|
||||
|
||||
elements := om.Elements()
|
||||
|
||||
fmt.Println(elements)
|
||||
|
||||
// Output:
|
||||
// [{a 1} {b 2} {c 3}]
|
||||
}
|
||||
|
||||
func ExampleOrderedMap_Iter() {
|
||||
om := NewOrderedMap[string, int]()
|
||||
|
||||
om.Set("a", 1)
|
||||
om.Set("b", 2)
|
||||
om.Set("c", 3)
|
||||
|
||||
for elem := range om.Iter() {
|
||||
fmt.Println(elem)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// {a 1}
|
||||
// {b 2}
|
||||
// {c 3}
|
||||
}
|
||||
|
||||
func ExampleOrderedMap_ReverseIter() {
|
||||
om := NewOrderedMap[string, int]()
|
||||
|
||||
om.Set("a", 1)
|
||||
om.Set("b", 2)
|
||||
om.Set("c", 3)
|
||||
|
||||
for elem := range om.ReverseIter() {
|
||||
fmt.Println(elem)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// {c 3}
|
||||
// {b 2}
|
||||
// {a 1}
|
||||
}
|
||||
|
||||
func ExampleOrderedMap_SortByKey() {
|
||||
om := NewOrderedMap[int, string]()
|
||||
|
||||
om.Set(3, "c")
|
||||
om.Set(1, "a")
|
||||
om.Set(4, "d")
|
||||
om.Set(2, "b")
|
||||
|
||||
om.SortByKey(func(a, b int) bool {
|
||||
return a < b
|
||||
})
|
||||
|
||||
fmt.Println(om.Elements())
|
||||
|
||||
// Output:
|
||||
// [{1 a} {2 b} {3 c} {4 d}]
|
||||
}
|
||||
|
||||
func ExampleOrderedMap_MarshalJSON() {
|
||||
om := NewOrderedMap[string, int]()
|
||||
|
||||
om.Set("a", 1)
|
||||
om.Set("b", 2)
|
||||
om.Set("c", 3)
|
||||
|
||||
b, _ := om.MarshalJSON()
|
||||
|
||||
fmt.Println(string(b))
|
||||
|
||||
// Output:
|
||||
// {"a":1,"b":2,"c":3}
|
||||
}
|
||||
|
||||
func ExampleOrderedMap_UnmarshalJSON() {
|
||||
// om := NewOrderedMap[string, int]()
|
||||
|
||||
// data := []byte(`{"a":1,"b":2,"c":3}`)
|
||||
|
||||
// om.UnmarshalJSON(data)
|
||||
|
||||
// fmt.Println(om.Elements())
|
||||
}
|
||||
|
||||
@@ -107,14 +107,14 @@ func TestMerge(t *testing.T) {
|
||||
2: "b",
|
||||
}
|
||||
m2 := map[int]string{
|
||||
1: "1",
|
||||
3: "2",
|
||||
2: "c",
|
||||
3: "d",
|
||||
}
|
||||
|
||||
expected := map[int]string{
|
||||
1: "1",
|
||||
2: "b",
|
||||
3: "2",
|
||||
1: "a",
|
||||
2: "c",
|
||||
3: "d",
|
||||
}
|
||||
acturl := Merge(m1, m2)
|
||||
|
||||
@@ -691,3 +691,200 @@ func TestToSortedSlicesWithComparator(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOrSet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestGetOrSet")
|
||||
|
||||
m := map[int]string{
|
||||
1: "a",
|
||||
}
|
||||
|
||||
result1 := GetOrSet(m, 1, "1")
|
||||
result2 := GetOrSet(m, 2, "b")
|
||||
|
||||
assert.Equal("a", result1)
|
||||
assert.Equal("b", result2)
|
||||
}
|
||||
|
||||
func TestSortByKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSortByKey")
|
||||
|
||||
m1 := map[int]string{
|
||||
3: "c",
|
||||
1: "a",
|
||||
4: "d",
|
||||
2: "b",
|
||||
}
|
||||
expected1 := map[int]string{
|
||||
1: "a",
|
||||
2: "b",
|
||||
3: "c",
|
||||
4: "d",
|
||||
}
|
||||
|
||||
result1 := SortByKey(m1, func(a, b int) bool {
|
||||
return a < b
|
||||
})
|
||||
|
||||
assert.Equal(expected1, result1)
|
||||
|
||||
m2 := map[string]int{
|
||||
"c": 3,
|
||||
"a": 1,
|
||||
"d": 4,
|
||||
"b": 2,
|
||||
}
|
||||
expected2 := map[string]int{
|
||||
"d": 4,
|
||||
"c": 3,
|
||||
"b": 2,
|
||||
"a": 1,
|
||||
}
|
||||
|
||||
result2 := SortByKey(m2, func(a, b string) bool {
|
||||
return a > b
|
||||
})
|
||||
|
||||
assert.Equal(expected2, result2)
|
||||
}
|
||||
|
||||
type (
|
||||
Person struct {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
Phone string `json:"phone"`
|
||||
Address *Address `json:"address"`
|
||||
}
|
||||
|
||||
Address struct {
|
||||
Street string `json:"street"`
|
||||
Number int `json:"number"`
|
||||
}
|
||||
)
|
||||
|
||||
func TestStructType(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestStructType")
|
||||
|
||||
src := map[string]interface{}{
|
||||
"name": "Nothin",
|
||||
"age": 28,
|
||||
"phone": "123456789",
|
||||
"address": map[string]interface{}{
|
||||
"street": "test",
|
||||
"number": 1,
|
||||
},
|
||||
}
|
||||
|
||||
var p Person
|
||||
err := MapTo(src, &p)
|
||||
assert.IsNil(err)
|
||||
|
||||
assert.Equal(src["name"], p.Name)
|
||||
assert.Equal(src["age"], p.Age)
|
||||
assert.Equal(src["phone"], p.Phone)
|
||||
assert.Equal("test", p.Address.Street)
|
||||
assert.Equal(1, p.Address.Number)
|
||||
}
|
||||
|
||||
func TestBaseType(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestBaseType")
|
||||
|
||||
tc := map[string]interface{}{
|
||||
"i32": -32,
|
||||
"i8": -8,
|
||||
"i16": -16,
|
||||
"i64": -64,
|
||||
"i": -1,
|
||||
"u32": 32,
|
||||
"u8": 8,
|
||||
"u16": 16,
|
||||
"u64": 64,
|
||||
"u": 1,
|
||||
"tf": true,
|
||||
"f32": 1.32,
|
||||
"f64": 1.64,
|
||||
"str": "hello mapto",
|
||||
"complex": 1 + 3i,
|
||||
}
|
||||
|
||||
type BaseType struct {
|
||||
I int `json:"i"`
|
||||
I8 int8 `json:"i8"`
|
||||
I16 int16 `json:"i16"`
|
||||
I32 int32 `json:"i32"`
|
||||
I64 int64 `json:"i64"`
|
||||
U uint `json:"u"`
|
||||
U8 uint8 `json:"u8"`
|
||||
U16 uint16 `json:"u16"`
|
||||
U32 uint32 `json:"u32"`
|
||||
U64 uint64 `json:"u64"`
|
||||
F32 float32 `json:"f32"`
|
||||
F64 float64 `json:"f64"`
|
||||
Tf bool `json:"tf"`
|
||||
Str string `json:"str"`
|
||||
Comp complex128 `json:"complex"`
|
||||
}
|
||||
|
||||
var dist BaseType
|
||||
err := MapTo(tc, &dist)
|
||||
assert.IsNil(err)
|
||||
|
||||
var number float64
|
||||
|
||||
MapTo(tc["i"], &number)
|
||||
assert.EqualValues(-1, number)
|
||||
|
||||
MapTo(tc["i8"], &number)
|
||||
assert.EqualValues(-8, number)
|
||||
|
||||
MapTo(tc["i16"], &number)
|
||||
assert.EqualValues(-16, number)
|
||||
|
||||
MapTo(tc["i32"], &number)
|
||||
assert.EqualValues(-32, number)
|
||||
|
||||
MapTo(tc["i64"], &number)
|
||||
assert.EqualValues(-64, number)
|
||||
|
||||
MapTo(tc["u"], &number)
|
||||
assert.EqualValues(1, number)
|
||||
|
||||
MapTo(tc["u8"], &number)
|
||||
assert.EqualValues(8, number)
|
||||
|
||||
MapTo(tc["u16"], &number)
|
||||
assert.EqualValues(16, number)
|
||||
|
||||
MapTo(tc["u32"], &number)
|
||||
assert.EqualValues(32, number)
|
||||
|
||||
MapTo(tc["u64"], &number)
|
||||
assert.EqualValues(64, number)
|
||||
}
|
||||
|
||||
func TestGetOrDefault(t *testing.T) {
|
||||
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "GetOrDefault")
|
||||
|
||||
m1 := map[int]string{
|
||||
3: "c",
|
||||
1: "a",
|
||||
4: "d",
|
||||
2: "b",
|
||||
}
|
||||
result1 := GetOrDefault(m1, 1, "123")
|
||||
assert.Equal("a", result1)
|
||||
|
||||
result2 := GetOrDefault(m1, 5, "123")
|
||||
assert.Equal("123", result2)
|
||||
}
|
||||
|
||||
431
maputil/orderedmap.go
Normal file
431
maputil/orderedmap.go
Normal file
@@ -0,0 +1,431 @@
|
||||
package maputil
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// OrderedMap is a map that maintains the order of keys.
|
||||
type OrderedMap[K comparable, V any] struct {
|
||||
mu sync.RWMutex
|
||||
|
||||
data map[K]V
|
||||
order *list.List
|
||||
index map[K]*list.Element
|
||||
}
|
||||
|
||||
// NewOrderedMap creates a new OrderedMap.
|
||||
func NewOrderedMap[K comparable, V any]() *OrderedMap[K, V] {
|
||||
return &OrderedMap[K, V]{
|
||||
data: make(map[K]V),
|
||||
order: list.New(),
|
||||
index: make(map[K]*list.Element),
|
||||
}
|
||||
}
|
||||
|
||||
// Sets the given key-value pair.
|
||||
// Play: todo
|
||||
func (om *OrderedMap[K, V]) Set(key K, value V) {
|
||||
om.mu.Lock()
|
||||
defer om.mu.Unlock()
|
||||
|
||||
if elem, ok := om.index[key]; ok {
|
||||
elem.Value = value
|
||||
om.order.MoveToBack(elem)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
om.data[key] = value
|
||||
|
||||
elem := om.order.PushBack(key)
|
||||
om.index[key] = elem
|
||||
}
|
||||
|
||||
// Get returns the value for the given key.
|
||||
// Play: todo
|
||||
func (om *OrderedMap[K, V]) Get(key K) (V, bool) {
|
||||
om.mu.RLock()
|
||||
defer om.mu.RUnlock()
|
||||
|
||||
value, ok := om.data[key]
|
||||
|
||||
return value, ok
|
||||
}
|
||||
|
||||
// Delete deletes the given key.
|
||||
// Play: todo
|
||||
func (om *OrderedMap[K, V]) Delete(key K) {
|
||||
om.mu.Lock()
|
||||
defer om.mu.Unlock()
|
||||
|
||||
if elem, ok := om.index[key]; ok {
|
||||
om.order.Remove(elem)
|
||||
delete(om.data, key)
|
||||
delete(om.index, key)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear clears the map.
|
||||
// Play: todo
|
||||
func (om *OrderedMap[K, V]) Clear() {
|
||||
om.mu.Lock()
|
||||
defer om.mu.Unlock()
|
||||
|
||||
om.data = make(map[K]V)
|
||||
om.order.Init()
|
||||
om.index = make(map[K]*list.Element)
|
||||
}
|
||||
|
||||
// Front returns the first key-value pair.
|
||||
// Play: todo
|
||||
func (om *OrderedMap[K, V]) Front() (struct {
|
||||
Key K
|
||||
Value V
|
||||
}, bool) {
|
||||
om.mu.RLock()
|
||||
defer om.mu.RUnlock()
|
||||
|
||||
if elem := om.order.Front(); elem != nil {
|
||||
key := elem.Value.(K)
|
||||
value := om.data[key]
|
||||
|
||||
return struct {
|
||||
Key K
|
||||
Value V
|
||||
}{
|
||||
Key: key,
|
||||
Value: value,
|
||||
}, true
|
||||
}
|
||||
|
||||
return struct {
|
||||
Key K
|
||||
Value V
|
||||
}{}, false
|
||||
}
|
||||
|
||||
// Back returns the last key-value pair.
|
||||
// Play: todo
|
||||
func (om *OrderedMap[K, V]) Back() (struct {
|
||||
Key K
|
||||
Value V
|
||||
}, bool) {
|
||||
om.mu.RLock()
|
||||
defer om.mu.RUnlock()
|
||||
|
||||
if elem := om.order.Back(); elem != nil {
|
||||
key := elem.Value.(K)
|
||||
value := om.data[key]
|
||||
|
||||
return struct {
|
||||
Key K
|
||||
Value V
|
||||
}{
|
||||
Key: key,
|
||||
Value: value,
|
||||
}, true
|
||||
}
|
||||
|
||||
return struct {
|
||||
Key K
|
||||
Value V
|
||||
}{}, false
|
||||
}
|
||||
|
||||
// Range calls the given function for each key-value pair.
|
||||
// Play: todo
|
||||
func (om *OrderedMap[K, V]) Range(iteratee func(key K, value V) bool) {
|
||||
om.mu.RLock()
|
||||
defer om.mu.RUnlock()
|
||||
|
||||
for elem := om.order.Front(); elem != nil; elem = elem.Next() {
|
||||
key := elem.Value.(K)
|
||||
value := om.data[key]
|
||||
|
||||
if !iteratee(key, value) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Keys returns the keys in order.
|
||||
// Play: todo
|
||||
func (om *OrderedMap[K, V]) Keys() []K {
|
||||
om.mu.RLock()
|
||||
defer om.mu.RUnlock()
|
||||
|
||||
keys := make([]K, 0, len(om.data))
|
||||
|
||||
for elem := om.order.Front(); elem != nil; elem = elem.Next() {
|
||||
keys = append(keys, elem.Value.(K))
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
// Values returns the values in order.
|
||||
// Play: todo
|
||||
func (om *OrderedMap[K, V]) Values() []V {
|
||||
om.mu.RLock()
|
||||
defer om.mu.RUnlock()
|
||||
|
||||
values := make([]V, 0, len(om.data))
|
||||
|
||||
for elem := om.order.Front(); elem != nil; elem = elem.Next() {
|
||||
key := elem.Value.(K)
|
||||
values = append(values, om.data[key])
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
|
||||
// Len returns the number of key-value pairs.
|
||||
// Play: todo
|
||||
func (om *OrderedMap[K, V]) Len() int {
|
||||
om.mu.RLock()
|
||||
defer om.mu.RUnlock()
|
||||
|
||||
return len(om.data)
|
||||
}
|
||||
|
||||
// Contains returns true if the given key exists.
|
||||
// Play: todo
|
||||
func (om *OrderedMap[K, V]) Contains(key K) bool {
|
||||
om.mu.RLock()
|
||||
defer om.mu.RUnlock()
|
||||
|
||||
_, ok := om.data[key]
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// Elements returns the key-value pairs in order.
|
||||
// Play: todo
|
||||
func (om *OrderedMap[K, V]) Elements() []struct {
|
||||
Key K
|
||||
Value V
|
||||
} {
|
||||
om.mu.RLock()
|
||||
defer om.mu.RUnlock()
|
||||
|
||||
elements := make([]struct {
|
||||
Key K
|
||||
Value V
|
||||
}, 0, len(om.data))
|
||||
|
||||
for elem := om.order.Front(); elem != nil; elem = elem.Next() {
|
||||
key := elem.Value.(K)
|
||||
value := om.data[key]
|
||||
elements = append(elements, struct {
|
||||
Key K
|
||||
Value V
|
||||
}{Key: key, Value: value})
|
||||
}
|
||||
|
||||
return elements
|
||||
}
|
||||
|
||||
// Iter returns a channel that yields key-value pairs in order.
|
||||
// Play: todo
|
||||
func (om *OrderedMap[K, V]) Iter() <-chan struct {
|
||||
Key K
|
||||
Value V
|
||||
} {
|
||||
ch := make(chan struct {
|
||||
Key K
|
||||
Value V
|
||||
})
|
||||
|
||||
go func() {
|
||||
om.mu.RLock()
|
||||
defer om.mu.RUnlock()
|
||||
defer close(ch)
|
||||
|
||||
for elem := om.order.Front(); elem != nil; elem = elem.Next() {
|
||||
key := elem.Value.(K)
|
||||
value := om.data[key]
|
||||
|
||||
ch <- struct {
|
||||
Key K
|
||||
Value V
|
||||
}{Key: key, Value: value}
|
||||
}
|
||||
}()
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
// ReverseIter returns a channel that yields key-value pairs in reverse order.
|
||||
// Play: todo
|
||||
func (om *OrderedMap[K, V]) ReverseIter() <-chan struct {
|
||||
Key K
|
||||
Value V
|
||||
} {
|
||||
ch := make(chan struct {
|
||||
Key K
|
||||
Value V
|
||||
})
|
||||
|
||||
go func() {
|
||||
om.mu.RLock()
|
||||
defer om.mu.RUnlock()
|
||||
defer close(ch)
|
||||
|
||||
for elem := om.order.Back(); elem != nil; elem = elem.Prev() {
|
||||
key := elem.Value.(K)
|
||||
value := om.data[key]
|
||||
|
||||
ch <- struct {
|
||||
Key K
|
||||
Value V
|
||||
}{Key: key, Value: value}
|
||||
}
|
||||
}()
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
// SortByValue sorts the map by key given less function.
|
||||
// Play: todo
|
||||
func (om *OrderedMap[K, V]) SortByKey(less func(a, b K) bool) {
|
||||
om.mu.Lock()
|
||||
defer om.mu.Unlock()
|
||||
|
||||
keys := make([]K, 0, om.order.Len())
|
||||
for elem := om.order.Front(); elem != nil; elem = elem.Next() {
|
||||
keys = append(keys, elem.Value.(K))
|
||||
}
|
||||
|
||||
sort.Slice(keys, func(i, j int) bool {
|
||||
return less(keys[i], keys[j])
|
||||
})
|
||||
|
||||
om.order.Init()
|
||||
om.index = make(map[K]*list.Element)
|
||||
for _, key := range keys {
|
||||
elem := om.order.PushBack(key)
|
||||
om.index[key] = elem
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
// Play: todo
|
||||
func (om *OrderedMap[K, V]) MarshalJSON() ([]byte, error) {
|
||||
om.mu.RLock()
|
||||
defer om.mu.RUnlock()
|
||||
|
||||
tempMap := make(map[string]V)
|
||||
for e := om.order.Front(); e != nil; e = e.Next() {
|
||||
key := e.Value.(K)
|
||||
keyStr, err := keyToString(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tempMap[keyStr] = om.data[key]
|
||||
}
|
||||
|
||||
return json.Marshal(tempMap)
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
// Play: todo
|
||||
func (om *OrderedMap[K, V]) UnmarshalJSON(data []byte) error {
|
||||
om.mu.Lock()
|
||||
defer om.mu.Unlock()
|
||||
|
||||
tempMap := make(map[string]V)
|
||||
if err := json.Unmarshal(data, &tempMap); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
om.data = make(map[K]V)
|
||||
om.order.Init()
|
||||
om.index = make(map[K]*list.Element)
|
||||
|
||||
for keyStr, value := range tempMap {
|
||||
key, err := stringToKey[K](keyStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
om.data[key] = value
|
||||
elem := om.order.PushBack(key)
|
||||
om.index[key] = elem
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func keyToString[K any](key K) (string, error) {
|
||||
switch v := any(key).(type) {
|
||||
case int:
|
||||
return strconv.Itoa(v), nil
|
||||
case float64:
|
||||
return strconv.FormatFloat(v, 'f', -1, 64), nil
|
||||
case string:
|
||||
return v, nil
|
||||
default:
|
||||
// 使用反射将未知类型转换为字符串
|
||||
rv := reflect.ValueOf(key)
|
||||
switch rv.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return strconv.FormatInt(rv.Int(), 10), nil
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return strconv.FormatUint(rv.Uint(), 10), nil
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return strconv.FormatFloat(rv.Float(), 'f', -1, 64), nil
|
||||
case reflect.String:
|
||||
return rv.String(), nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported key type: %T", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func stringToKey[K any](s string) (K, error) {
|
||||
var zero K
|
||||
switch any(zero).(type) {
|
||||
case int:
|
||||
value, err := strconv.Atoi(s)
|
||||
return any(value).(K), err
|
||||
case float64:
|
||||
value, err := strconv.ParseFloat(s, 64)
|
||||
return any(value).(K), err
|
||||
case string:
|
||||
return any(s).(K), nil
|
||||
default:
|
||||
// 使用反射恢复未知类型的键
|
||||
rv := reflect.ValueOf(&zero).Elem()
|
||||
switch rv.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
val, err := strconv.ParseInt(s, 10, 64)
|
||||
if err != nil {
|
||||
return zero, err
|
||||
}
|
||||
rv.SetInt(val)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
val, err := strconv.ParseUint(s, 10, 64)
|
||||
if err != nil {
|
||||
return zero, err
|
||||
}
|
||||
rv.SetUint(val)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
val, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil {
|
||||
return zero, err
|
||||
}
|
||||
rv.SetFloat(val)
|
||||
case reflect.String:
|
||||
rv.SetString(s)
|
||||
default:
|
||||
return zero, fmt.Errorf("unsupported key type: %T", zero)
|
||||
}
|
||||
|
||||
return rv.Interface().(K), nil
|
||||
}
|
||||
}
|
||||
245
maputil/orderedmap_test.go
Normal file
245
maputil/orderedmap_test.go
Normal file
@@ -0,0 +1,245 @@
|
||||
package maputil
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
|
||||
func TestOrderedMap_Set_Get(t *testing.T) {
|
||||
om := NewOrderedMap[string, int]()
|
||||
|
||||
om.Set("a", 1)
|
||||
om.Set("b", 2)
|
||||
om.Set("c", 3)
|
||||
|
||||
assert := internal.NewAssert(t, "TestOrderedMap_Set_Get")
|
||||
|
||||
val, ok := om.Get("a")
|
||||
assert.Equal(1, val)
|
||||
assert.Equal(true, ok)
|
||||
|
||||
val, ok = om.Get("d")
|
||||
assert.Equal(false, ok)
|
||||
assert.Equal(0, val)
|
||||
}
|
||||
|
||||
func TestOrderedMap_Front_Back(t *testing.T) {
|
||||
om := NewOrderedMap[string, int]()
|
||||
|
||||
om.Set("a", 1)
|
||||
om.Set("b", 2)
|
||||
om.Set("c", 3)
|
||||
|
||||
assert := internal.NewAssert(t, "TestOrderedMap_Front_Back")
|
||||
|
||||
frontElement, ok := om.Front()
|
||||
assert.Equal("a", frontElement.Key)
|
||||
assert.Equal(1, frontElement.Value)
|
||||
assert.Equal(true, ok)
|
||||
|
||||
backElement, ok := om.Back()
|
||||
assert.Equal("c", backElement.Key)
|
||||
assert.Equal(3, backElement.Value)
|
||||
assert.Equal(true, ok)
|
||||
}
|
||||
|
||||
func TestOrderedMap_Delete_Clear(t *testing.T) {
|
||||
om := NewOrderedMap[string, int]()
|
||||
|
||||
om.Set("a", 1)
|
||||
om.Set("b", 2)
|
||||
om.Set("c", 3)
|
||||
|
||||
assert := internal.NewAssert(t, "TestOrderedMap_Delete_Clear")
|
||||
|
||||
assert.Equal(3, om.Len())
|
||||
|
||||
om.Delete("b")
|
||||
assert.Equal(2, om.Len())
|
||||
|
||||
om.Clear()
|
||||
assert.Equal(0, om.Len())
|
||||
}
|
||||
|
||||
func TestOrderedMap_Keys_Values(t *testing.T) {
|
||||
om := NewOrderedMap[string, int]()
|
||||
|
||||
om.Set("a", 1)
|
||||
om.Set("b", 2)
|
||||
om.Set("c", 3)
|
||||
|
||||
assert := internal.NewAssert(t, "TestOrderedMap_Keys_Values")
|
||||
|
||||
assert.Equal([]string{"a", "b", "c"}, om.Keys())
|
||||
assert.Equal([]int{1, 2, 3}, om.Values())
|
||||
}
|
||||
|
||||
func TestOrderedMap_Contains(t *testing.T) {
|
||||
om := NewOrderedMap[string, int]()
|
||||
|
||||
om.Set("a", 1)
|
||||
om.Set("b", 2)
|
||||
om.Set("c", 3)
|
||||
|
||||
assert := internal.NewAssert(t, "TestOrderedMap_Contains")
|
||||
|
||||
assert.Equal(true, om.Contains("a"))
|
||||
assert.Equal(false, om.Contains("d"))
|
||||
}
|
||||
|
||||
func TestOrderedMap_Eelements(t *testing.T) {
|
||||
om := NewOrderedMap[string, int]()
|
||||
|
||||
om.Set("a", 1)
|
||||
om.Set("b", 2)
|
||||
om.Set("c", 3)
|
||||
|
||||
assert := internal.NewAssert(t, "TestOrderedMap_Eelements")
|
||||
|
||||
elements := []struct {
|
||||
Key string
|
||||
Value int
|
||||
}{
|
||||
{"a", 1},
|
||||
{"b", 2},
|
||||
{"c", 3},
|
||||
}
|
||||
|
||||
assert.Equal(elements, om.Elements())
|
||||
}
|
||||
|
||||
func TestOrderedMap_Range(t *testing.T) {
|
||||
om := NewOrderedMap[string, int]()
|
||||
|
||||
om.Set("a", 1)
|
||||
om.Set("b", 2)
|
||||
om.Set("c", 3)
|
||||
om.Set("d", 4)
|
||||
|
||||
assert := internal.NewAssert(t, "TestOrderedMap_Range")
|
||||
|
||||
var keys []string
|
||||
om.Range(func(key string, value int) bool {
|
||||
keys = append(keys, key)
|
||||
return key != "c"
|
||||
})
|
||||
|
||||
assert.Equal([]string{"a", "b", "c"}, keys)
|
||||
}
|
||||
|
||||
func TestOrderedMap_Iter(t *testing.T) {
|
||||
om := NewOrderedMap[string, int]()
|
||||
|
||||
om.Set("a", 1)
|
||||
om.Set("b", 2)
|
||||
om.Set("c", 3)
|
||||
|
||||
assert := internal.NewAssert(t, "TestOrderedMap_Iter")
|
||||
|
||||
var items []struct {
|
||||
Key string
|
||||
Value int
|
||||
}
|
||||
|
||||
iterCh := om.Iter()
|
||||
|
||||
for item := range iterCh {
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
expected := []struct {
|
||||
Key string
|
||||
Value int
|
||||
}{
|
||||
{"a", 1},
|
||||
{"b", 2},
|
||||
{"c", 3},
|
||||
}
|
||||
|
||||
assert.Equal(expected, items)
|
||||
}
|
||||
|
||||
func TestOrderedMap_ReverseIter(t *testing.T) {
|
||||
om := NewOrderedMap[string, int]()
|
||||
|
||||
om.Set("a", 1)
|
||||
om.Set("b", 2)
|
||||
om.Set("c", 3)
|
||||
|
||||
assert := internal.NewAssert(t, "TestOrderedMap_ReverseIter")
|
||||
|
||||
var items []struct {
|
||||
Key string
|
||||
Value int
|
||||
}
|
||||
|
||||
iterCh := om.ReverseIter()
|
||||
|
||||
for item := range iterCh {
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
expected := []struct {
|
||||
Key string
|
||||
Value int
|
||||
}{
|
||||
{"c", 3},
|
||||
{"b", 2},
|
||||
{"a", 1},
|
||||
}
|
||||
|
||||
assert.Equal(expected, items)
|
||||
}
|
||||
|
||||
func TestOrderedMap_SortByKey(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestOrderedMap_SortByKey")
|
||||
|
||||
om := NewOrderedMap[string, int]()
|
||||
|
||||
om.Set("d", 4)
|
||||
om.Set("b", 2)
|
||||
om.Set("c", 3)
|
||||
om.Set("a", 1)
|
||||
|
||||
om.SortByKey(func(a, b string) bool {
|
||||
return a < b
|
||||
})
|
||||
|
||||
assert.Equal([]string{"a", "b", "c", "d"}, om.Keys())
|
||||
}
|
||||
|
||||
func TestOrderedMap_MarshalJSON(t *testing.T) {
|
||||
om := NewOrderedMap[string, int]()
|
||||
|
||||
om.Set("a", 1)
|
||||
om.Set("b", 2)
|
||||
om.Set("c", 3)
|
||||
|
||||
assert := internal.NewAssert(t, "TestOrderedMap_MarshalJSON")
|
||||
|
||||
jsonBytes, err := om.MarshalJSON()
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("MarshalJSON error: %v", err)
|
||||
}
|
||||
|
||||
assert.Equal(`{"a":1,"b":2,"c":3}`, string(jsonBytes))
|
||||
}
|
||||
|
||||
func TestOrderedMap_UnmarshalJSON(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestOrderedMap_UnmarshalJSON")
|
||||
|
||||
jsonStr := `{"a":1,"b":2,"c":3}`
|
||||
|
||||
om := NewOrderedMap[string, int]()
|
||||
err := om.UnmarshalJSON([]byte(jsonStr))
|
||||
if err != nil {
|
||||
t.Errorf("MarshalJSON error: %v", err)
|
||||
}
|
||||
|
||||
assert.Equal(3, om.Len())
|
||||
assert.Equal(true, om.Contains("a"))
|
||||
assert.Equal(true, om.Contains("b"))
|
||||
assert.Equal(true, om.Contains("c"))
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
package maputil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var mapHandlers = map[reflect.Kind]func(reflect.Value, reflect.Value) error{
|
||||
reflect.String: convertNormal,
|
||||
reflect.Int: convertNormal,
|
||||
reflect.Int16: convertNormal,
|
||||
reflect.Int32: convertNormal,
|
||||
reflect.Int64: convertNormal,
|
||||
reflect.Uint: convertNormal,
|
||||
reflect.Uint16: convertNormal,
|
||||
reflect.Uint32: convertNormal,
|
||||
reflect.Uint64: convertNormal,
|
||||
reflect.Float32: convertNormal,
|
||||
reflect.Float64: convertNormal,
|
||||
reflect.Uint8: convertNormal,
|
||||
reflect.Int8: convertNormal,
|
||||
reflect.Struct: convertNormal,
|
||||
reflect.Complex64: convertNormal,
|
||||
reflect.Complex128: convertNormal,
|
||||
}
|
||||
|
||||
var _ = func() struct{} {
|
||||
mapHandlers[reflect.Map] = convertMap
|
||||
mapHandlers[reflect.Array] = convertSlice
|
||||
mapHandlers[reflect.Slice] = convertSlice
|
||||
return struct{}{}
|
||||
}()
|
||||
|
||||
// MapTo try to map any interface to struct or base type
|
||||
/*
|
||||
Eg:
|
||||
v := map[string]interface{}{
|
||||
"service":map[string]interface{}{
|
||||
"ip":"127.0.0.1",
|
||||
"port":1234,
|
||||
},
|
||||
version:"v1.0.01"
|
||||
}
|
||||
|
||||
type Target struct {
|
||||
Service struct {
|
||||
Ip string `json:"ip"`
|
||||
Port int `json:"port"`
|
||||
} `json:"service"`
|
||||
Ver string `json:"version"`
|
||||
}
|
||||
|
||||
var dist Target
|
||||
if err := maputil.MapTo(v,&dist); err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Println(dist)
|
||||
|
||||
*/
|
||||
// Play: https://go.dev/play/p/4K7KBEPgS5M
|
||||
func MapTo(src any, dst any) error {
|
||||
dstRef := reflect.ValueOf(dst)
|
||||
|
||||
if dstRef.Kind() != reflect.Ptr {
|
||||
return fmt.Errorf("dst is not ptr")
|
||||
}
|
||||
|
||||
dstElem := dstRef.Type().Elem()
|
||||
if dstElem.Kind() == reflect.Struct {
|
||||
srcMap := src.(map[string]interface{})
|
||||
return MapToStruct(srcMap, dst)
|
||||
}
|
||||
|
||||
dstRef = reflect.Indirect(dstRef)
|
||||
|
||||
srcRef := reflect.ValueOf(src)
|
||||
if srcRef.Kind() == reflect.Ptr || srcRef.Kind() == reflect.Interface {
|
||||
srcRef = srcRef.Elem()
|
||||
}
|
||||
|
||||
if f, ok := mapHandlers[srcRef.Kind()]; ok {
|
||||
return f(srcRef, dstRef)
|
||||
}
|
||||
|
||||
return fmt.Errorf("no implemented:%s", srcRef.Type())
|
||||
}
|
||||
|
||||
func convertNormal(src reflect.Value, dst reflect.Value) error {
|
||||
if dst.CanSet() {
|
||||
if src.Type() == dst.Type() {
|
||||
dst.Set(src)
|
||||
} else if src.CanConvert(dst.Type()) {
|
||||
dst.Set(src.Convert(dst.Type()))
|
||||
} else {
|
||||
return fmt.Errorf("can not convert:%s:%s", src.Type().String(), dst.Type().String())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertSlice(src reflect.Value, dst reflect.Value) error {
|
||||
if dst.Kind() != reflect.Array && dst.Kind() != reflect.Slice {
|
||||
return fmt.Errorf("error type:%s", dst.Type().String())
|
||||
}
|
||||
l := src.Len()
|
||||
target := reflect.MakeSlice(dst.Type(), l, l)
|
||||
if dst.CanSet() {
|
||||
dst.Set(target)
|
||||
}
|
||||
for i := 0; i < l; i++ {
|
||||
srcValue := src.Index(i)
|
||||
if srcValue.Kind() == reflect.Ptr || srcValue.Kind() == reflect.Interface {
|
||||
srcValue = srcValue.Elem()
|
||||
}
|
||||
if f, ok := mapHandlers[srcValue.Kind()]; ok {
|
||||
err := f(srcValue, dst.Index(i))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertMap(src reflect.Value, dst reflect.Value) error {
|
||||
if src.Kind() != reflect.Map || dst.Kind() != reflect.Struct {
|
||||
if src.Kind() == reflect.Interface && dst.IsValid() {
|
||||
return convertMap(src.Elem(), dst)
|
||||
} else {
|
||||
return fmt.Errorf("src or dst type error,%s,%s", src.Type().String(), dst.Type().String())
|
||||
}
|
||||
}
|
||||
dstType := dst.Type()
|
||||
num := dstType.NumField()
|
||||
|
||||
exist := map[string]int{}
|
||||
|
||||
for i := 0; i < num; i++ {
|
||||
k := dstType.Field(i).Tag.Get("json")
|
||||
if k == "" {
|
||||
k = dstType.Field(i).Name
|
||||
}
|
||||
if strings.Contains(k, ",") {
|
||||
taglist := strings.Split(k, ",")
|
||||
if taglist[0] == "" {
|
||||
k = dstType.Field(i).Name
|
||||
} else {
|
||||
k = taglist[0]
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
exist[k] = i
|
||||
}
|
||||
|
||||
keys := src.MapKeys()
|
||||
|
||||
for _, key := range keys {
|
||||
if index, ok := exist[key.String()]; ok {
|
||||
v := dst.Field(index)
|
||||
|
||||
if v.Kind() == reflect.Struct {
|
||||
err := convertMap(src.MapIndex(key), v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if v.CanSet() {
|
||||
if v.Type() == src.MapIndex(key).Elem().Type() {
|
||||
v.Set(src.MapIndex(key).Elem())
|
||||
} else if src.MapIndex(key).Elem().CanConvert(v.Type()) {
|
||||
v.Set(src.MapIndex(key).Elem().Convert(v.Type()))
|
||||
} else if f, ok := mapHandlers[src.MapIndex(key).Elem().Kind()]; ok && f != nil {
|
||||
err := f(src.MapIndex(key).Elem(), v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("error type:d(%s)s(%s)", v.Type(), src.Type())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
package maputil
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
|
||||
type (
|
||||
Person struct {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
Phone string `json:"phone"`
|
||||
Address *Address `json:"address"`
|
||||
}
|
||||
|
||||
Address struct {
|
||||
Street string `json:"street"`
|
||||
Number int `json:"number"`
|
||||
}
|
||||
)
|
||||
|
||||
func TestStructType(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestStructType")
|
||||
|
||||
src := map[string]interface{}{
|
||||
"name": "Nothin",
|
||||
"age": 28,
|
||||
"phone": "123456789",
|
||||
"address": map[string]interface{}{
|
||||
"street": "test",
|
||||
"number": 1,
|
||||
},
|
||||
}
|
||||
|
||||
var p Person
|
||||
err := MapTo(src, &p)
|
||||
assert.IsNil(err)
|
||||
|
||||
assert.Equal(src["name"], p.Name)
|
||||
assert.Equal(src["age"], p.Age)
|
||||
assert.Equal(src["phone"], p.Phone)
|
||||
assert.Equal("test", p.Address.Street)
|
||||
assert.Equal(1, p.Address.Number)
|
||||
}
|
||||
|
||||
func TestBaseType(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestBaseType")
|
||||
|
||||
tc := map[string]interface{}{
|
||||
"i32": -32,
|
||||
"i8": -8,
|
||||
"i16": -16,
|
||||
"i64": -64,
|
||||
"i": -1,
|
||||
"u32": 32,
|
||||
"u8": 8,
|
||||
"u16": 16,
|
||||
"u64": 64,
|
||||
"u": 1,
|
||||
"tf": true,
|
||||
"f32": 1.32,
|
||||
"f64": 1.64,
|
||||
"str": "hello mapto",
|
||||
"complex": 1 + 3i,
|
||||
}
|
||||
|
||||
type BaseType struct {
|
||||
I int `json:"i"`
|
||||
I8 int8 `json:"i8"`
|
||||
I16 int16 `json:"i16"`
|
||||
I32 int32 `json:"i32"`
|
||||
I64 int64 `json:"i64"`
|
||||
U uint `json:"u"`
|
||||
U8 uint8 `json:"u8"`
|
||||
U16 uint16 `json:"u16"`
|
||||
U32 uint32 `json:"u32"`
|
||||
U64 uint64 `json:"u64"`
|
||||
F32 float32 `json:"f32"`
|
||||
F64 float64 `json:"f64"`
|
||||
Tf bool `json:"tf"`
|
||||
Str string `json:"str"`
|
||||
Comp complex128 `json:"complex"`
|
||||
}
|
||||
|
||||
var dist BaseType
|
||||
err := MapTo(tc, &dist)
|
||||
assert.IsNil(err)
|
||||
|
||||
var number float64
|
||||
|
||||
MapTo(tc["i"], &number)
|
||||
assert.EqualValues(-1, number)
|
||||
|
||||
MapTo(tc["i8"], &number)
|
||||
assert.EqualValues(-8, number)
|
||||
|
||||
MapTo(tc["i16"], &number)
|
||||
assert.EqualValues(-16, number)
|
||||
|
||||
MapTo(tc["i32"], &number)
|
||||
assert.EqualValues(-32, number)
|
||||
|
||||
MapTo(tc["i64"], &number)
|
||||
assert.EqualValues(-64, number)
|
||||
|
||||
MapTo(tc["u"], &number)
|
||||
assert.EqualValues(1, number)
|
||||
|
||||
MapTo(tc["u8"], &number)
|
||||
assert.EqualValues(8, number)
|
||||
|
||||
MapTo(tc["u16"], &number)
|
||||
assert.EqualValues(16, number)
|
||||
|
||||
MapTo(tc["u32"], &number)
|
||||
assert.EqualValues(32, number)
|
||||
|
||||
MapTo(tc["u64"], &number)
|
||||
assert.EqualValues(64, number)
|
||||
}
|
||||
@@ -102,6 +102,7 @@ type HttpRequest struct {
|
||||
|
||||
// HttpClientConfig contains some configurations for http client
|
||||
type HttpClientConfig struct {
|
||||
Timeout time.Duration
|
||||
SSLEnabled bool
|
||||
TLSConfig *tls.Config
|
||||
Compressed bool
|
||||
@@ -113,9 +114,10 @@ type HttpClientConfig struct {
|
||||
|
||||
// defaultHttpClientConfig defalut client config.
|
||||
var defaultHttpClientConfig = &HttpClientConfig{
|
||||
Timeout: 50 * time.Second,
|
||||
Compressed: false,
|
||||
HandshakeTimeout: 20 * time.Second,
|
||||
ResponseTimeout: 40 * time.Second,
|
||||
HandshakeTimeout: 10 * time.Second,
|
||||
ResponseTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
// HttpClient is used for sending http request.
|
||||
@@ -131,6 +133,7 @@ type HttpClient struct {
|
||||
func NewHttpClient() *HttpClient {
|
||||
client := &HttpClient{
|
||||
Client: &http.Client{
|
||||
Timeout: defaultHttpClientConfig.Timeout,
|
||||
Transport: &http.Transport{
|
||||
TLSHandshakeTimeout: defaultHttpClientConfig.HandshakeTimeout,
|
||||
ResponseHeaderTimeout: defaultHttpClientConfig.ResponseTimeout,
|
||||
|
||||
@@ -21,21 +21,48 @@ import (
|
||||
// GetInternalIp return internal ipv4.
|
||||
// Play: https://go.dev/play/p/5mbu-gFp7ei
|
||||
func GetInternalIp() string {
|
||||
addr, err := net.InterfaceAddrs()
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
for _, a := range addr {
|
||||
if ipNet, ok := a.(*net.IPNet); ok && !ipNet.IP.IsLoopback() {
|
||||
if ipNet.IP.To4() != nil {
|
||||
return ipNet.IP.String()
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
var ip net.IP
|
||||
switch v := addr.(type) {
|
||||
case *net.IPNet:
|
||||
ip = v.IP
|
||||
case *net.IPAddr:
|
||||
ip = v.IP
|
||||
}
|
||||
|
||||
if ip != nil && (ip.IsLinkLocalUnicast() || ip.IsGlobalUnicast()) {
|
||||
continue
|
||||
}
|
||||
|
||||
if ipv4 := ip.To4(); ipv4 != nil {
|
||||
return ipv4.String()
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// func GetInternalIp() string {
|
||||
// addr, err := net.InterfaceAddrs()
|
||||
// if err != nil {
|
||||
// panic(err.Error())
|
||||
// }
|
||||
// for _, a := range addr {
|
||||
// if ipNet, ok := a.(*net.IPNet); ok && !ipNet.IP.IsLoopback() {
|
||||
// if ipNet.IP.To4() != nil {
|
||||
// return ipNet.IP.String()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// return ""
|
||||
// }
|
||||
|
||||
// GetRequestPublicIp return the requested public ip.
|
||||
// Play: https://go.dev/play/p/kxU-YDc_eBo
|
||||
func GetRequestPublicIp(req *http.Request) string {
|
||||
|
||||
@@ -11,17 +11,24 @@ import (
|
||||
// Of returns a pointer to the value `v`.
|
||||
// Play: https://go.dev/play/p/HFd70x4DrMj
|
||||
func Of[T any](v T) *T {
|
||||
if IsNil(v) {
|
||||
return nil
|
||||
}
|
||||
return &v
|
||||
}
|
||||
|
||||
// Unwrap returns the value from the pointer.
|
||||
// Play: https://go.dev/play/p/cgeu3g7cjWb
|
||||
//
|
||||
// Play: https://go.dev/play/p/cgeu3g7cjWb
|
||||
// Deprecated: Please use UnwrapOr
|
||||
func Unwrap[T any](p *T) T {
|
||||
return *p
|
||||
}
|
||||
|
||||
// UnwarpOr returns the value from the pointer or fallback if the pointer is nil.
|
||||
// Play: https://go.dev/play/p/mmNaLC38W8C
|
||||
//
|
||||
// Play: https://go.dev/play/p/mmNaLC38W8C
|
||||
// Deprecated: Please use UnwrapOr
|
||||
func UnwarpOr[T any](p *T, fallback T) T {
|
||||
if p == nil {
|
||||
return fallback
|
||||
@@ -30,7 +37,9 @@ func UnwarpOr[T any](p *T, fallback T) T {
|
||||
}
|
||||
|
||||
// UnwarpOrDefault returns the value from the pointer or the default value if the pointer is nil.
|
||||
// Play: https://go.dev/play/p/ZnGIHf8_o4E
|
||||
//
|
||||
// Play: https://go.dev/play/p/ZnGIHf8_o4E
|
||||
// Deprecated: Please use UnwrapOr
|
||||
func UnwarpOrDefault[T any](p *T) T {
|
||||
var v T
|
||||
|
||||
@@ -40,9 +49,24 @@ func UnwarpOrDefault[T any](p *T) T {
|
||||
return *p
|
||||
}
|
||||
|
||||
// UnwrapOr returns the value from the pointer or fallback if the pointer is nil.
|
||||
func UnwrapOr[T any](p *T, fallback ...T) T {
|
||||
if !IsNil(p) {
|
||||
return *p
|
||||
}
|
||||
if len(fallback) > 0 {
|
||||
return fallback[0]
|
||||
}
|
||||
var t T
|
||||
return t
|
||||
}
|
||||
|
||||
// ExtractPointer returns the underlying value by the given interface type
|
||||
// Play: https://go.dev/play/p/D7HFjeWU2ZP
|
||||
func ExtractPointer(value any) any {
|
||||
if IsNil(value) {
|
||||
return value
|
||||
}
|
||||
t := reflect.TypeOf(value)
|
||||
v := reflect.ValueOf(value)
|
||||
|
||||
@@ -56,3 +80,8 @@ func ExtractPointer(value any) any {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsNil returns true if the given interface is nil or the underlying value is nil.
|
||||
func IsNil(i interface{}) bool {
|
||||
return i == nil || (reflect.ValueOf(i).Kind() == reflect.Ptr && reflect.ValueOf(i).IsNil())
|
||||
}
|
||||
|
||||
@@ -90,3 +90,61 @@ func ExampleExtractPointer() {
|
||||
// Output:
|
||||
// 1
|
||||
}
|
||||
|
||||
func ExampleIsNil() {
|
||||
a := 1
|
||||
b := &a
|
||||
c := &b
|
||||
d := &c
|
||||
e := &d
|
||||
var f *int
|
||||
|
||||
result1 := IsNil(a)
|
||||
result2 := IsNil(b)
|
||||
result3 := IsNil(c)
|
||||
result4 := IsNil(d)
|
||||
result5 := IsNil(e)
|
||||
result6 := IsNil(f)
|
||||
result7 := IsNil(nil)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
fmt.Println(result5)
|
||||
fmt.Println(result6)
|
||||
fmt.Println(result7)
|
||||
|
||||
// Output:
|
||||
// false
|
||||
// false
|
||||
// false
|
||||
// false
|
||||
// false
|
||||
// true
|
||||
// true
|
||||
}
|
||||
|
||||
func ExampleUnwrapOr() {
|
||||
a := 123
|
||||
b := "abc"
|
||||
|
||||
var c *int
|
||||
var d *string
|
||||
|
||||
result1 := UnwrapOr(&a, 456)
|
||||
result2 := UnwrapOr(&b, "efg")
|
||||
result3 := UnwrapOr(c, 456)
|
||||
result4 := UnwrapOr(d, "def")
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
|
||||
// Output:
|
||||
// 123
|
||||
// abc
|
||||
// 456
|
||||
// def
|
||||
}
|
||||
|
||||
@@ -47,10 +47,10 @@ func TestUnwarpOr(t *testing.T) {
|
||||
assert.Equal("def", UnwarpOr(d, "def"))
|
||||
}
|
||||
|
||||
func TestUnwarpOrDefault(t *testing.T) {
|
||||
func TestUnwrapOrDefault(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestUnwarpOrDefault")
|
||||
assert := internal.NewAssert(t, "TestUnwrapOrDefault")
|
||||
|
||||
a := 123
|
||||
b := "abc"
|
||||
|
||||
187
random/random.go
187
random/random.go
@@ -23,6 +23,7 @@ const (
|
||||
UpperLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
Letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
SymbolChars = "!@#$%^&*()_+-=[]{}|;':\",./<>?"
|
||||
AllChars = Numeral + LowwerLetters + UpperLetters + SymbolChars
|
||||
)
|
||||
|
||||
var rn = rand.NewSource(time.Now().UnixNano())
|
||||
@@ -31,6 +32,27 @@ func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// RandBool generates a random boolean value (true or false).
|
||||
// Play: todo
|
||||
func RandBool() bool {
|
||||
return rand.Intn(2) == 1
|
||||
}
|
||||
|
||||
// RandBoolSlice generates a random boolean slice of specified length.
|
||||
// Play: todo
|
||||
func RandBoolSlice(length int) []bool {
|
||||
if length <= 0 {
|
||||
return []bool{}
|
||||
}
|
||||
|
||||
result := make([]bool, length)
|
||||
for i := range result {
|
||||
result[i] = RandBool()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// RandInt generate random int between [min, max).
|
||||
// Play: https://go.dev/play/p/pXyyAAI5YxD
|
||||
func RandInt(min, max int) int {
|
||||
@@ -42,9 +64,54 @@ func RandInt(min, max int) int {
|
||||
min, max = max, min
|
||||
}
|
||||
|
||||
if min == 0 && max == math.MaxInt {
|
||||
return rand.Int()
|
||||
}
|
||||
|
||||
return rand.Intn(max-min) + min
|
||||
}
|
||||
|
||||
// RandIntSlice generates a slice of random integers.
|
||||
// The generated integers are between min and max (exclusive).
|
||||
// Play: todo
|
||||
func RandIntSlice(length, min, max int) []int {
|
||||
if length <= 0 || min > max {
|
||||
return []int{}
|
||||
}
|
||||
|
||||
result := make([]int, length)
|
||||
for i := range result {
|
||||
result[i] = RandInt(min, max)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// RandUniqueIntSlice generate a slice of random int of length that do not repeat.
|
||||
// Play: https://go.dev/play/p/uBkRSOz73Ec
|
||||
func RandUniqueIntSlice(length, min, max int) []int {
|
||||
if min > max {
|
||||
return []int{}
|
||||
}
|
||||
if length > max-min {
|
||||
length = max - min
|
||||
}
|
||||
|
||||
nums := make([]int, length)
|
||||
used := make(map[int]struct{}, length)
|
||||
for i := 0; i < length; {
|
||||
r := RandInt(min, max)
|
||||
if _, use := used[r]; use {
|
||||
continue
|
||||
}
|
||||
used[r] = struct{}{}
|
||||
nums[i] = r
|
||||
i++
|
||||
}
|
||||
|
||||
return nums
|
||||
}
|
||||
|
||||
// RandFloat generate random float64 number between [min, max) with specific precision.
|
||||
// Play: https://go.dev/play/p/zbD_tuobJtr
|
||||
func RandFloat(min, max float64, precision int) float64 {
|
||||
@@ -61,6 +128,24 @@ func RandFloat(min, max float64, precision int) float64 {
|
||||
return mathutil.RoundToFloat(n, precision)
|
||||
}
|
||||
|
||||
// RandFloats generate a slice of random float64 numbers of length that do not repeat.
|
||||
// Play: https://go.dev/play/p/I3yndUQ-rhh
|
||||
func RandFloats(length int, min, max float64, precision int) []float64 {
|
||||
nums := make([]float64, length)
|
||||
used := make(map[float64]struct{}, length)
|
||||
for i := 0; i < length; {
|
||||
r := RandFloat(min, max, precision)
|
||||
if _, use := used[r]; use {
|
||||
continue
|
||||
}
|
||||
used[r] = struct{}{}
|
||||
nums[i] = r
|
||||
i++
|
||||
}
|
||||
|
||||
return nums
|
||||
}
|
||||
|
||||
// RandBytes generate random byte slice.
|
||||
// Play: https://go.dev/play/p/EkiLESeXf8d
|
||||
func RandBytes(length int) []byte {
|
||||
@@ -76,12 +161,69 @@ func RandBytes(length int) []byte {
|
||||
return b
|
||||
}
|
||||
|
||||
// RandString generate random alpha string of specified length.
|
||||
// RandString generate random alphabeta string of specified length.
|
||||
// Play: https://go.dev/play/p/W2xvRUXA7Mi
|
||||
func RandString(length int) string {
|
||||
return random(Letters, length)
|
||||
}
|
||||
|
||||
// RandString generate a slice of random string of length strLen based on charset.
|
||||
// chartset should be one of the following: random.Numeral, random.LowwerLetters, random.UpperLetters
|
||||
// random.Letters, random.SymbolChars, random.AllChars. or a combination of them.
|
||||
// Play: todo
|
||||
func RandStringSlice(charset string, sliceLen, strLen int) []string {
|
||||
if sliceLen <= 0 || strLen <= 0 {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
result := make([]string, sliceLen)
|
||||
|
||||
for i := range result {
|
||||
result[i] = random(charset, strLen)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// RandFromGivenSlice generate a random element from given slice.
|
||||
// Play: todo
|
||||
func RandFromGivenSlice[T any](slice []T) T {
|
||||
if len(slice) == 0 {
|
||||
var zero T
|
||||
return zero
|
||||
}
|
||||
return slice[rand.Intn(len(slice))]
|
||||
}
|
||||
|
||||
// RandSliceFromGivenSlice generate a random slice of length num from given slice.
|
||||
// - If repeatable is true, the generated slice may contain duplicate elements.
|
||||
//
|
||||
// Play: todo
|
||||
func RandSliceFromGivenSlice[T any](slice []T, num int, repeatable bool) []T {
|
||||
if num <= 0 || len(slice) == 0 {
|
||||
return slice
|
||||
}
|
||||
|
||||
if !repeatable && num > len(slice) {
|
||||
num = len(slice)
|
||||
}
|
||||
|
||||
result := make([]T, num)
|
||||
if repeatable {
|
||||
for i := range result {
|
||||
result[i] = slice[rand.Intn(len(slice))]
|
||||
}
|
||||
} else {
|
||||
shuffled := make([]T, len(slice))
|
||||
copy(shuffled, slice)
|
||||
rand.Shuffle(len(shuffled), func(i, j int) {
|
||||
shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
|
||||
})
|
||||
result = shuffled[:num]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// RandUpper generate a random upper case string of specified length.
|
||||
// Play: https://go.dev/play/p/29QfOh0DVuh
|
||||
func RandUpper(length int) string {
|
||||
@@ -185,46 +327,3 @@ func UUIdV4() (string, error) {
|
||||
|
||||
return fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:]), nil
|
||||
}
|
||||
|
||||
// RandUniqueIntSlice generate a slice of random int of length n that do not repeat.
|
||||
// Play: https://go.dev/play/p/uBkRSOz73Ec
|
||||
func RandUniqueIntSlice(n, min, max int) []int {
|
||||
if min > max {
|
||||
return []int{}
|
||||
}
|
||||
if n > max-min {
|
||||
n = max - min
|
||||
}
|
||||
|
||||
nums := make([]int, n)
|
||||
used := make(map[int]struct{}, n)
|
||||
for i := 0; i < n; {
|
||||
r := RandInt(min, max)
|
||||
if _, use := used[r]; use {
|
||||
continue
|
||||
}
|
||||
used[r] = struct{}{}
|
||||
nums[i] = r
|
||||
i++
|
||||
}
|
||||
|
||||
return nums
|
||||
}
|
||||
|
||||
// RandFloats generate a slice of random float64 numbers of length n that do not repeat.
|
||||
// Play: https://go.dev/play/p/I3yndUQ-rhh
|
||||
func RandFloats(n int, min, max float64, precision int) []float64 {
|
||||
nums := make([]float64, n)
|
||||
used := make(map[float64]struct{}, n)
|
||||
for i := 0; i < n; {
|
||||
r := RandFloat(min, max, precision)
|
||||
if _, use := used[r]; use {
|
||||
continue
|
||||
}
|
||||
used[r] = struct{}{}
|
||||
nums[i] = r
|
||||
i++
|
||||
}
|
||||
|
||||
return nums
|
||||
}
|
||||
|
||||
@@ -43,6 +43,21 @@ func ExampleRandString() {
|
||||
// 6
|
||||
}
|
||||
|
||||
func ExampleRandFromGivenSlice() {
|
||||
goods := []string{"apple", "banana", "cherry", "elderberry", "fig", "grape", "honeydew", "kiwi", "lemon",
|
||||
"mango", "nectarine", "orange"}
|
||||
|
||||
result := RandFromGivenSlice(goods)
|
||||
fmt.Println(result)
|
||||
}
|
||||
|
||||
func ExampleRandSliceFromGivenSlice() {
|
||||
goods := []string{"apple", "banana", "cherry", "elderberry", "fig", "grape", "honeydew", "kiwi", "lemon",
|
||||
"mango", "nectarine", "orange"}
|
||||
chosen3goods := RandSliceFromGivenSlice(goods, 3, false)
|
||||
fmt.Println(chosen3goods)
|
||||
}
|
||||
|
||||
func ExampleRandUpper() {
|
||||
pattern := `^[A-Z]+$`
|
||||
reg := regexp.MustCompile(pattern)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
"github.com/duke-git/lancet/v2/validator"
|
||||
)
|
||||
|
||||
func TestRandString(t *testing.T) {
|
||||
@@ -196,3 +197,167 @@ func TestRandFloats(t *testing.T) {
|
||||
|
||||
assert.Equal(len(numbers), 5)
|
||||
}
|
||||
|
||||
func TestRandIntSlice(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestRandIntSlice")
|
||||
|
||||
t.Run("empty slice", func(t *testing.T) {
|
||||
numbers := RandIntSlice(-1, 1, 5)
|
||||
assert.Equal([]int{}, numbers)
|
||||
|
||||
numbers = RandIntSlice(0, 1, 5)
|
||||
assert.Equal([]int{}, numbers)
|
||||
|
||||
numbers = RandIntSlice(3, 5, 1)
|
||||
assert.Equal([]int{}, numbers)
|
||||
})
|
||||
|
||||
t.Run("random int slice", func(t *testing.T) {
|
||||
numbers := RandIntSlice(5, 1, 1)
|
||||
assert.Equal([]int{1, 1, 1, 1, 1}, numbers)
|
||||
|
||||
numbers = RandIntSlice(5, 1, 5)
|
||||
assert.Equal(5, len(numbers))
|
||||
})
|
||||
}
|
||||
|
||||
func TestRandStringSlice(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestRandStringSlice")
|
||||
|
||||
t.Run("empty slice", func(t *testing.T) {
|
||||
strs := RandStringSlice(Letters, -1, -1)
|
||||
assert.Equal([]string{}, strs)
|
||||
|
||||
strs = RandStringSlice(Letters, 0, 0)
|
||||
assert.Equal([]string{}, strs)
|
||||
|
||||
strs = RandStringSlice(Letters, -1, 0)
|
||||
assert.Equal([]string{}, strs)
|
||||
|
||||
strs = RandStringSlice(Letters, 0, -1)
|
||||
assert.Equal([]string{}, strs)
|
||||
|
||||
strs = RandStringSlice(Letters, 1, 0)
|
||||
assert.Equal([]string{}, strs)
|
||||
|
||||
strs = RandStringSlice(Letters, 0, 1)
|
||||
assert.Equal([]string{}, strs)
|
||||
})
|
||||
|
||||
t.Run("random string slice", func(t *testing.T) {
|
||||
strs := RandStringSlice(Letters, 4, 6)
|
||||
assert.Equal(4, len(strs))
|
||||
|
||||
for _, s := range strs {
|
||||
assert.Equal(true, validator.IsAlpha(s))
|
||||
assert.Equal(6, len(s))
|
||||
}
|
||||
})
|
||||
|
||||
// fail test: chinese character is not supported for now
|
||||
// t.Run("random string slice of chinese ", func(t *testing.T) {
|
||||
// strs := RandStringSlice("你好你好你好你好你好你好你好你好你好", 4, 6)
|
||||
// t.Log(strs)
|
||||
|
||||
// assert.Equal(4, len(strs))
|
||||
// for _, s := range strs {
|
||||
// assert.Equal(true, validator.ContainChinese(s))
|
||||
// assert.Equal(6, len(s))
|
||||
// }
|
||||
// })
|
||||
}
|
||||
|
||||
func TestRandFromGivenSlice(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestRandFromGivenSlice")
|
||||
|
||||
randomSet := []any{"a", 8, "王", true, 1.1}
|
||||
result := RandFromGivenSlice(randomSet)
|
||||
find := false
|
||||
for _, v := range randomSet {
|
||||
if v == result {
|
||||
find = true
|
||||
}
|
||||
}
|
||||
assert.Equal(true, find)
|
||||
|
||||
emptyAnyRandomSet := []any{}
|
||||
emptyAnyResult := RandFromGivenSlice(emptyAnyRandomSet)
|
||||
assert.IsNil(emptyAnyResult)
|
||||
|
||||
emptyIntRandomSet := []int{}
|
||||
emtpyIntResult := RandFromGivenSlice(emptyIntRandomSet)
|
||||
assert.Equal(0, emtpyIntResult)
|
||||
}
|
||||
|
||||
func TestRandSliceFromGivenSlice(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestRandSliceFromGivenSlice")
|
||||
|
||||
randomSet := []any{"a", 8, "王", true, 1.1}
|
||||
repeatableResult := RandSliceFromGivenSlice(randomSet, 8, true)
|
||||
assert.Equal(8, len(repeatableResult))
|
||||
unrepeatableResult := RandSliceFromGivenSlice(randomSet, 8, false)
|
||||
assert.Equal(len(randomSet), len(unrepeatableResult))
|
||||
|
||||
var findCount int
|
||||
for _, v := range repeatableResult {
|
||||
for _, vv := range randomSet {
|
||||
if v == vv {
|
||||
findCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
assert.Equal(8, findCount)
|
||||
findCount = 0
|
||||
|
||||
for _, v := range unrepeatableResult {
|
||||
for _, vv := range randomSet {
|
||||
if v == vv {
|
||||
findCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
assert.Equal(len(randomSet), findCount)
|
||||
|
||||
emptyAnyRandomSet := []any{}
|
||||
emptyAnyResult := RandSliceFromGivenSlice(emptyAnyRandomSet, 3, true)
|
||||
assert.Equal([]any{}, emptyAnyResult)
|
||||
|
||||
emptyIntRandomSet := []int{}
|
||||
emtpyIntResult := RandSliceFromGivenSlice(emptyIntRandomSet, 3, true)
|
||||
assert.Equal([]int{}, emtpyIntResult)
|
||||
|
||||
}
|
||||
|
||||
func TestRandBool(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestRandBool")
|
||||
|
||||
result := RandBool()
|
||||
assert.Equal(true, result == true || result == false)
|
||||
}
|
||||
|
||||
func TestRandBoolSlice(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestRandBoolSlice")
|
||||
|
||||
t.Run("empty slice", func(t *testing.T) {
|
||||
bools := RandBoolSlice(-1)
|
||||
assert.Equal([]bool{}, bools)
|
||||
|
||||
bools = RandBoolSlice(0)
|
||||
assert.Equal([]bool{}, bools)
|
||||
})
|
||||
|
||||
t.Run("random bool slice", func(t *testing.T) {
|
||||
bools := RandBoolSlice(6)
|
||||
assert.Equal(6, len(bools))
|
||||
|
||||
for _, b := range bools {
|
||||
assert.Equal(true, b == true || b == false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func RetryTimes(n uint) Option {
|
||||
}
|
||||
|
||||
// RetryWithCustomBackoff set abitary custom backoff strategy
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/jIm_o2vb5Y4
|
||||
func RetryWithCustomBackoff(backoffStrategy BackoffStrategy) Option {
|
||||
if backoffStrategy == nil {
|
||||
panic("programming error: backoffStrategy must be not nil")
|
||||
@@ -57,7 +57,7 @@ func RetryWithCustomBackoff(backoffStrategy BackoffStrategy) Option {
|
||||
}
|
||||
|
||||
// RetryWithLinearBackoff set linear strategy backoff
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/PDet2ZQZwcB
|
||||
func RetryWithLinearBackoff(interval time.Duration) Option {
|
||||
if interval <= 0 {
|
||||
panic("programming error: retry interval should not be lower or equal to 0")
|
||||
@@ -71,7 +71,7 @@ func RetryWithLinearBackoff(interval time.Duration) Option {
|
||||
}
|
||||
|
||||
// RetryWithExponentialWithJitterBackoff set exponential strategy backoff
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/xp1avQmn16X
|
||||
func RetryWithExponentialWithJitterBackoff(interval time.Duration, base uint64, maxJitter time.Duration) Option {
|
||||
if interval <= 0 {
|
||||
panic("programming error: retry interval should not be lower or equal to 0")
|
||||
|
||||
149
slice/slice.go
149
slice/slice.go
@@ -109,18 +109,18 @@ func Compact[T comparable](slice []T) []T {
|
||||
|
||||
// Concat creates a new slice concatenating slice with any additional slices.
|
||||
// Play: https://go.dev/play/p/gPt-q7zr5mk
|
||||
func Concat[T any](slice []T, slices ...[]T) []T {
|
||||
totalLen := len(slice)
|
||||
|
||||
func Concat[T any](slices ...[]T) []T {
|
||||
totalLen := 0
|
||||
for _, v := range slices {
|
||||
totalLen += len(v)
|
||||
if totalLen < 0 {
|
||||
panic("len out of range")
|
||||
}
|
||||
}
|
||||
|
||||
result := make([]T, 0, totalLen)
|
||||
|
||||
result = append(result, slice...)
|
||||
for _, s := range slices {
|
||||
result = append(result, s...)
|
||||
for _, v := range slices {
|
||||
result = append(result, v...)
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -771,29 +771,105 @@ func UpdateAt[T any](slice []T, index int, value T) []T {
|
||||
// Unique remove duplicate elements in slice.
|
||||
// Play: https://go.dev/play/p/AXw0R3ZTE6a
|
||||
func Unique[T comparable](slice []T) []T {
|
||||
result := []T{}
|
||||
exists := map[T]bool{}
|
||||
for _, t := range slice {
|
||||
if exists[t] {
|
||||
result := make([]T, 0, len(slice))
|
||||
seen := make(map[T]struct{}, len(slice))
|
||||
|
||||
for i := range slice {
|
||||
if _, ok := seen[slice[i]]; ok {
|
||||
continue
|
||||
}
|
||||
exists[t] = true
|
||||
result = append(result, t)
|
||||
|
||||
seen[slice[i]] = struct{}{}
|
||||
|
||||
result = append(result, slice[i])
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// UniqueBy call iteratee func with every item of slice, then remove duplicated.
|
||||
// Play: https://go.dev/play/p/UR323iZLDpv
|
||||
func UniqueBy[T comparable](slice []T, iteratee func(item T) T) []T {
|
||||
result := []T{}
|
||||
// UniqueBy removes duplicate elements from the input slice based on the values returned by the iteratee function.
|
||||
// The function maintains the order of the elements.
|
||||
// Play: todo
|
||||
func UniqueBy[T any, U comparable](slice []T, iteratee func(item T) U) []T {
|
||||
result := make([]T, 0, len(slice))
|
||||
seen := make(map[U]struct{}, len(slice))
|
||||
|
||||
for _, v := range slice {
|
||||
val := iteratee(v)
|
||||
result = append(result, val)
|
||||
for i := range slice {
|
||||
key := iteratee(slice[i])
|
||||
if _, ok := seen[key]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
seen[key] = struct{}{}
|
||||
|
||||
result = append(result, slice[i])
|
||||
}
|
||||
|
||||
return Unique(result)
|
||||
return result
|
||||
}
|
||||
|
||||
// UniqueByComparator removes duplicate elements from the input slice using the provided comparator function.
|
||||
// The function maintains the order of the elements.
|
||||
// Play: todo
|
||||
func UniqueByComparator[T comparable](slice []T, comparator func(item T, other T) bool) []T {
|
||||
result := make([]T, 0, len(slice))
|
||||
seen := make([]T, 0, len(slice))
|
||||
|
||||
for _, item := range slice {
|
||||
duplicate := false
|
||||
for _, seenItem := range seen {
|
||||
if comparator(item, seenItem) {
|
||||
duplicate = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !duplicate {
|
||||
seen = append(seen, item)
|
||||
result = append(result, item)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// UniqueByField remove duplicate elements in struct slice by struct field.
|
||||
// Play: https://go.dev/play/p/6cifcZSPIGu
|
||||
func UniqueByField[T any](slice []T, field string) ([]T, error) {
|
||||
seen := map[any]struct{}{}
|
||||
|
||||
var result []T
|
||||
for _, item := range slice {
|
||||
val, err := getField(item, field)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get field %s failed: %v", field, err)
|
||||
}
|
||||
if _, ok := seen[val]; !ok {
|
||||
seen[val] = struct{}{}
|
||||
result = append(result, item)
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func getField[T any](item T, field string) (interface{}, error) {
|
||||
v := reflect.ValueOf(item)
|
||||
t := reflect.TypeOf(item)
|
||||
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
v = v.Elem()
|
||||
}
|
||||
if v.Kind() != reflect.Struct {
|
||||
return nil, fmt.Errorf("data type %T not support, shuld be struct or pointer to struct", item)
|
||||
}
|
||||
|
||||
f := v.FieldByName(field)
|
||||
if !f.IsValid() {
|
||||
return nil, fmt.Errorf("field name %s not found", field)
|
||||
}
|
||||
|
||||
return v.FieldByName(field).Interface(), nil
|
||||
}
|
||||
|
||||
// Union creates a slice of unique elements, in order, from all given slices.
|
||||
@@ -833,20 +909,11 @@ func UnionBy[T any, V comparable](predicate func(item T) V, slices ...[]T) []T {
|
||||
return result
|
||||
}
|
||||
|
||||
// Deprecated: Please use Concat() function instead.
|
||||
// Merge all given slices into one slice.
|
||||
// Play: https://go.dev/play/p/lbjFp784r9N
|
||||
func Merge[T any](slices ...[]T) []T {
|
||||
totalLen := 0
|
||||
for _, v := range slices {
|
||||
totalLen += len(v)
|
||||
}
|
||||
result := make([]T, 0, totalLen)
|
||||
|
||||
for _, v := range slices {
|
||||
result = append(result, v...)
|
||||
}
|
||||
|
||||
return result
|
||||
return Concat(slices...)
|
||||
}
|
||||
|
||||
// Intersection creates a slice of unique elements that included by all slices.
|
||||
@@ -1254,7 +1321,7 @@ func Partition[T any](slice []T, predicates ...func(item T) bool) [][]T {
|
||||
}
|
||||
|
||||
// Breaks a list into two parts at the point where the predicate for the first time is true.
|
||||
// Play: Todo
|
||||
// Play: https://go.dev/play/p/yLYcBTyeQIz
|
||||
func Break[T any](values []T, predicate func(T) bool) ([]T, []T) {
|
||||
a := make([]T, 0)
|
||||
b := make([]T, 0)
|
||||
@@ -1289,7 +1356,7 @@ func Random[T any](slice []T) (val T, idx int) {
|
||||
}
|
||||
|
||||
// RightPadding adds padding to the right end of a slice.
|
||||
// Play: Todo
|
||||
// Play: https://go.dev/play/p/0_2rlLEMBXL
|
||||
func RightPadding[T any](slice []T, paddingValue T, paddingLength int) []T {
|
||||
if paddingLength == 0 {
|
||||
return slice
|
||||
@@ -1301,7 +1368,7 @@ func RightPadding[T any](slice []T, paddingValue T, paddingLength int) []T {
|
||||
}
|
||||
|
||||
// LeftPadding adds padding to the left begin of a slice.
|
||||
// Play: Todo
|
||||
// Play: https://go.dev/play/p/jlQVoelLl2k
|
||||
func LeftPadding[T any](slice []T, paddingValue T, paddingLength int) []T {
|
||||
if paddingLength == 0 {
|
||||
return slice
|
||||
@@ -1319,3 +1386,15 @@ func LeftPadding[T any](slice []T, paddingValue T, paddingLength int) []T {
|
||||
|
||||
return paddedSlice
|
||||
}
|
||||
|
||||
// Frequency counts the frequency of each element in the slice.
|
||||
// Play: todo
|
||||
func Frequency[T comparable](slice []T) map[T]int {
|
||||
result := make(map[T]int)
|
||||
|
||||
for _, v := range slice {
|
||||
result[v]++
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
237
slice/slice_concurrent.go
Normal file
237
slice/slice_concurrent.go
Normal file
@@ -0,0 +1,237 @@
|
||||
// Copyright 2024 dudaodong@gmail.com. All rights resulterved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
package slice
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ForEachConcurrent applies the iteratee function to each item in the slice concurrently.
|
||||
// Play: todo
|
||||
func ForEachConcurrent[T any](slice []T, iteratee func(index int, item T), numThreads int) {
|
||||
sliceLen := len(slice)
|
||||
if sliceLen == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if numThreads <= 0 {
|
||||
numThreads = 1
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
chunkSize := (sliceLen + numThreads - 1) / numThreads
|
||||
|
||||
for i := 0; i < numThreads; i++ {
|
||||
start := i * chunkSize
|
||||
end := start + chunkSize
|
||||
|
||||
if start >= sliceLen {
|
||||
break
|
||||
}
|
||||
|
||||
if end > sliceLen {
|
||||
end = sliceLen
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func(start, end int) {
|
||||
defer wg.Done()
|
||||
|
||||
for j := start; j < end; j++ {
|
||||
iteratee(j, slice[j])
|
||||
}
|
||||
}(start, end)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// MapConcurrent applies the iteratee function to each item in the slice concurrently.
|
||||
// Play: todo
|
||||
func MapConcurrent[T any, U any](slice []T, iteratee func(index int, item T) U, numThreads int) []U {
|
||||
result := make([]U, len(slice))
|
||||
var wg sync.WaitGroup
|
||||
|
||||
workerChan := make(chan struct{}, numThreads)
|
||||
|
||||
for index, item := range slice {
|
||||
wg.Add(1)
|
||||
|
||||
workerChan <- struct{}{}
|
||||
|
||||
go func(i int, v T) {
|
||||
defer wg.Done()
|
||||
|
||||
result[i] = iteratee(i, v)
|
||||
|
||||
<-workerChan
|
||||
}(index, item)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ReduceConcurrent reduces the slice to a single value by applying the reducer function to each item in the slice concurrently.
|
||||
// Play: todo
|
||||
func ReduceConcurrent[T any](slice []T, initial T, reducer func(index int, item T, agg T) T, numThreads int) T {
|
||||
if numThreads <= 0 {
|
||||
numThreads = 1
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var mu sync.Mutex
|
||||
|
||||
sliceLen := len(slice)
|
||||
chunkSize := (sliceLen + numThreads - 1) / numThreads
|
||||
results := make([]T, numThreads)
|
||||
|
||||
for i := 0; i < numThreads; i++ {
|
||||
start := i * chunkSize
|
||||
end := start + chunkSize
|
||||
if end > sliceLen {
|
||||
end = sliceLen
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func(i, start, end int) {
|
||||
defer wg.Done()
|
||||
tempResult := initial
|
||||
for j := start; j < end; j++ {
|
||||
tempResult = reducer(j, slice[j], tempResult)
|
||||
}
|
||||
mu.Lock()
|
||||
results[i] = tempResult
|
||||
mu.Unlock()
|
||||
}(i, start, end)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
result := initial
|
||||
for i, r := range results {
|
||||
result = reducer(i, result, r)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// FilterConcurrent applies the provided filter function `predicate` to each element of the input slice concurrently.
|
||||
// Play: todo
|
||||
func FilterConcurrent[T any](slice []T, predicate func(index int, item T) bool, numThreads int) []T {
|
||||
result := make([]T, 0)
|
||||
var wg sync.WaitGroup
|
||||
|
||||
workerChan := make(chan struct{}, numThreads)
|
||||
|
||||
for index, item := range slice {
|
||||
wg.Add(1)
|
||||
|
||||
workerChan <- struct{}{}
|
||||
|
||||
go func(i int, v T) {
|
||||
defer wg.Done()
|
||||
|
||||
if predicate(i, v) {
|
||||
result = append(result, v)
|
||||
}
|
||||
|
||||
<-workerChan
|
||||
}(index, item)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// UniqueByParallel removes duplicate elements from the slice by parallel
|
||||
// The comparator function is used to compare the elements
|
||||
// The numThreads parameter specifies the number of threads to use
|
||||
// If numThreads is less than or equal to 0, it will be set to 1
|
||||
// The comparator function should return true if the two elements are equal
|
||||
// Play: todo
|
||||
func UniqueByConcurrent[T comparable](slice []T, comparator func(item T, other T) bool, numThreads int) []T {
|
||||
if numThreads <= 0 {
|
||||
numThreads = 1
|
||||
} else if numThreads > len(slice) {
|
||||
numThreads = len(slice)
|
||||
}
|
||||
|
||||
maxThreads := runtime.NumCPU()
|
||||
if numThreads > maxThreads {
|
||||
numThreads = maxThreads
|
||||
}
|
||||
|
||||
removeDuplicate := func(items []T, comparator func(item T, other T) bool) []T {
|
||||
var result []T
|
||||
for _, item := range items {
|
||||
seen := false
|
||||
for _, r := range result {
|
||||
if comparator(item, r) {
|
||||
seen = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !seen {
|
||||
result = append(result, item)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
chunkSize := (len(slice) + numThreads - 1) / numThreads
|
||||
|
||||
chunks := make([][]T, 0, numThreads)
|
||||
for i := 0; i < len(slice); i += chunkSize {
|
||||
end := i + chunkSize
|
||||
if end > len(slice) {
|
||||
end = len(slice)
|
||||
}
|
||||
chunks = append(chunks, slice[i:end])
|
||||
}
|
||||
|
||||
type resultChunk struct {
|
||||
index int
|
||||
data []T
|
||||
}
|
||||
resultCh := make(chan resultChunk, numThreads)
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i, chunk := range chunks {
|
||||
wg.Add(1)
|
||||
go func(index int, chunk []T) {
|
||||
defer wg.Done()
|
||||
resultCh <- resultChunk{index, removeDuplicate(chunk, comparator)}
|
||||
}(i, chunk)
|
||||
}
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(resultCh)
|
||||
}()
|
||||
|
||||
results := make([][]T, len(chunks))
|
||||
for r := range resultCh {
|
||||
results[r.index] = r.data
|
||||
}
|
||||
|
||||
result := []T{}
|
||||
seen := make(map[T]bool)
|
||||
|
||||
for _, chunk := range results {
|
||||
for _, item := range chunk {
|
||||
if !seen[item] {
|
||||
seen[item] = true
|
||||
result = append(result, item)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ExampleContain() {
|
||||
@@ -246,6 +247,21 @@ func ExampleFilter() {
|
||||
// [2 4]
|
||||
}
|
||||
|
||||
func ExampleFilterConcurrent() {
|
||||
nums := []int{1, 2, 3, 4, 5}
|
||||
|
||||
isEven := func(i, num int) bool {
|
||||
return num%2 == 0
|
||||
}
|
||||
|
||||
result := FilterConcurrent(nums, isEven, 2)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// [2 4]
|
||||
}
|
||||
|
||||
func ExampleCount() {
|
||||
nums := []int{1, 2, 3, 3, 4}
|
||||
|
||||
@@ -413,6 +429,23 @@ func ExampleForEach() {
|
||||
// [2 3 4]
|
||||
}
|
||||
|
||||
func ExampleForEachConcurrent() {
|
||||
nums := []int{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
|
||||
result := make([]int, len(nums))
|
||||
|
||||
addOne := func(index int, value int) {
|
||||
result[index] = value + 1
|
||||
}
|
||||
|
||||
ForEachConcurrent(nums, addOne, 4)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// [2 3 4 5 6 7 8 9]
|
||||
}
|
||||
|
||||
func ExampleForEachWithBreak() {
|
||||
numbers := []int{1, 2, 3, 4, 5}
|
||||
|
||||
@@ -494,6 +527,18 @@ func ExampleReduce() {
|
||||
// 6
|
||||
}
|
||||
|
||||
func ExampleReduceConcurrent() {
|
||||
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
|
||||
result := ReduceConcurrent(nums, 0, func(_ int, item, agg int) int {
|
||||
return agg + item
|
||||
}, 1)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// 55
|
||||
}
|
||||
|
||||
func ExampleReduceBy() {
|
||||
result1 := ReduceBy([]int{1, 2, 3, 4}, 0, func(_ int, item int, agg int) int {
|
||||
return agg + item
|
||||
@@ -777,7 +822,46 @@ func ExampleUniqueBy() {
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// [1 2 0]
|
||||
// [1 2 3]
|
||||
}
|
||||
|
||||
func ExampleUniqueByComparator() {
|
||||
uniqueNums := UniqueByComparator([]int{1, 2, 3, 1, 2, 4, 5, 6, 4}, func(item int, other int) bool {
|
||||
return item == other
|
||||
})
|
||||
|
||||
caseInsensitiveStrings := UniqueByComparator([]string{"apple", "banana", "Apple", "cherry", "Banana", "date"}, func(item string, other string) bool {
|
||||
return strings.ToLower(item) == strings.ToLower(other)
|
||||
})
|
||||
|
||||
fmt.Println(uniqueNums)
|
||||
fmt.Println(caseInsensitiveStrings)
|
||||
|
||||
// Output:
|
||||
// [1 2 3 4 5 6]
|
||||
// [apple banana cherry date]
|
||||
}
|
||||
|
||||
func ExampleUniqueByField() {
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
users := []User{
|
||||
{ID: 1, Name: "a"},
|
||||
{ID: 2, Name: "b"},
|
||||
{ID: 1, Name: "c"},
|
||||
}
|
||||
|
||||
result, err := UniqueByField(users, "ID")
|
||||
if err != nil {
|
||||
}
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// [{1 a} {2 b}]
|
||||
}
|
||||
|
||||
func ExampleUnion() {
|
||||
@@ -1099,6 +1183,7 @@ func ExampleRandom() {
|
||||
if idx >= 0 && idx < len(nums) && Contain(nums, val) {
|
||||
fmt.Println("okk")
|
||||
}
|
||||
|
||||
// Output:
|
||||
// okk
|
||||
}
|
||||
@@ -1108,6 +1193,7 @@ func ExampleSetToDefaultIf() {
|
||||
modifiedStrs, count := SetToDefaultIf(strs, func(s string) bool { return "a" == s })
|
||||
fmt.Println(modifiedStrs)
|
||||
fmt.Println(count)
|
||||
|
||||
// Output:
|
||||
// [ b c d ]
|
||||
// 3
|
||||
@@ -1130,6 +1216,7 @@ func ExampleRightPadding() {
|
||||
nums := []int{1, 2, 3, 4, 5}
|
||||
padded := RightPadding(nums, 0, 3)
|
||||
fmt.Println(padded)
|
||||
|
||||
// Output:
|
||||
// [1 2 3 4 5 0 0 0]
|
||||
}
|
||||
@@ -1138,6 +1225,39 @@ func ExampleLeftPadding() {
|
||||
nums := []int{1, 2, 3, 4, 5}
|
||||
padded := LeftPadding(nums, 0, 3)
|
||||
fmt.Println(padded)
|
||||
|
||||
// Output:
|
||||
// [0 0 0 1 2 3 4 5]
|
||||
}
|
||||
|
||||
func ExampleUniqueByConcurrent() {
|
||||
nums := []int{1, 2, 3, 1, 2, 4, 5, 6, 4, 7}
|
||||
comparator := func(item int, other int) bool { return item == other }
|
||||
|
||||
result := UniqueByConcurrent(nums, comparator, 4)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// [1 2 3 4 5 6 7]
|
||||
}
|
||||
|
||||
func ExampleMapConcurrent() {
|
||||
nums := []int{1, 2, 3, 4, 5, 6}
|
||||
result := MapConcurrent(nums, func(_, n int) int { return n * n }, 4)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// [1 4 9 16 25 36]
|
||||
}
|
||||
|
||||
func ExampleFrequency() {
|
||||
strs := []string{"a", "b", "b", "c", "c", "c"}
|
||||
result := Frequency(strs)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// map[a:1 b:2 c:3]
|
||||
}
|
||||
|
||||
@@ -2,11 +2,13 @@ package slice
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
|
||||
func TestContain(t *testing.T) {
|
||||
@@ -14,12 +16,20 @@ func TestContain(t *testing.T) {
|
||||
|
||||
assert := internal.NewAssert(t, "TestContain")
|
||||
|
||||
assert.Equal(true, Contain([]string{"a", "b", "c"}, "a"))
|
||||
assert.Equal(false, Contain([]string{"a", "b", "c"}, "d"))
|
||||
assert.Equal(true, Contain([]string{""}, ""))
|
||||
assert.Equal(false, Contain([]string{}, ""))
|
||||
tests := []struct {
|
||||
slice []string
|
||||
give string
|
||||
want bool
|
||||
}{
|
||||
{[]string{"a", "b", "c"}, "a", true},
|
||||
{[]string{"a", "b", "c"}, "d", false},
|
||||
{[]string{""}, "", true},
|
||||
{[]string{}, "", false},
|
||||
}
|
||||
|
||||
assert.Equal(true, Contain([]int{1, 2, 3}, 1))
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.want, Contain(tt.slice, tt.give))
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainBy(t *testing.T) {
|
||||
@@ -28,32 +38,53 @@ func TestContainBy(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestContainBy")
|
||||
|
||||
type foo struct {
|
||||
A string
|
||||
B int
|
||||
a string
|
||||
b int
|
||||
}
|
||||
|
||||
array1 := []foo{{A: "1", B: 1}, {A: "2", B: 2}}
|
||||
result1 := ContainBy(array1, func(f foo) bool { return f.A == "1" && f.B == 1 })
|
||||
result2 := ContainBy(array1, func(f foo) bool { return f.A == "2" && f.B == 1 })
|
||||
tests := []struct {
|
||||
slice []foo
|
||||
predicateFn func(f foo) bool
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
[]foo{{a: "1", b: 1}, {a: "2", b: 2}},
|
||||
func(f foo) bool { return f.a == "1" && f.b == 1 },
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]foo{{a: "1", b: 1}, {a: "2", b: 2}},
|
||||
func(f foo) bool { return f.a == "2" && f.b == 1 },
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
array2 := []string{"a", "b", "c"}
|
||||
result3 := ContainBy(array2, func(t string) bool { return t == "a" })
|
||||
result4 := ContainBy(array2, func(t string) bool { return t == "d" })
|
||||
|
||||
assert.Equal(true, result1)
|
||||
assert.Equal(false, result2)
|
||||
assert.Equal(true, result3)
|
||||
assert.Equal(false, result4)
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.want, ContainBy(tt.slice, tt.predicateFn))
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainSubSlice(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestContainSubSlice")
|
||||
assert.Equal(true, ContainSubSlice([]string{"a", "a", "b", "c"}, []string{"a", "a"}))
|
||||
assert.Equal(false, ContainSubSlice([]string{"a", "a", "b", "c"}, []string{"a", "d"}))
|
||||
assert.Equal(true, ContainSubSlice([]int{1, 2, 3}, []int{1, 2}))
|
||||
assert.Equal(false, ContainSubSlice([]int{1, 2, 3}, []int{0, 1}))
|
||||
|
||||
tests := []struct {
|
||||
slice []string
|
||||
subSlice []string
|
||||
want bool
|
||||
}{
|
||||
{[]string{"a", "b", "c"}, []string{"a", "b"}, true},
|
||||
{[]string{"a", "b", "c"}, []string{"a", "d"}, false},
|
||||
{[]string{"a", "b", "c"}, []string{"a", "b", "c"}, true},
|
||||
{[]string{"a", "b", "c"}, []string{"a", "b", "c", "d"}, false},
|
||||
{[]string{"a", "b", ""}, []string{"a", ""}, true},
|
||||
{[]string{""}, []string{""}, true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.want, ContainSubSlice(tt.slice, tt.subSlice))
|
||||
}
|
||||
}
|
||||
|
||||
func TestChunk(t *testing.T) {
|
||||
@@ -61,29 +92,24 @@ func TestChunk(t *testing.T) {
|
||||
|
||||
assert := internal.NewAssert(t, "TestChunk")
|
||||
|
||||
arr := []string{"a", "b", "c", "d", "e"}
|
||||
tests := []struct {
|
||||
slice []string
|
||||
chuanSize int
|
||||
want [][]string
|
||||
}{
|
||||
{[]string{"a", "b", "c", "d", "e"}, -1, [][]string{}},
|
||||
{[]string{"a", "b", "c", "d", "e"}, 0, [][]string{}},
|
||||
{[]string{"a", "b", "c", "d", "e"}, 1, [][]string{{"a"}, {"b"}, {"c"}, {"d"}, {"e"}}},
|
||||
{[]string{"a", "b", "c", "d", "e"}, 2, [][]string{{"a", "b"}, {"c", "d"}, {"e"}}},
|
||||
{[]string{"a", "b", "c", "d", "e"}, 3, [][]string{{"a", "b", "c"}, {"d", "e"}}},
|
||||
{[]string{"a", "b", "c", "d", "e"}, 4, [][]string{{"a", "b", "c", "d"}, {"e"}}},
|
||||
{[]string{"a", "b", "c", "d", "e"}, 5, [][]string{{"a", "b", "c", "d", "e"}}},
|
||||
{[]string{"a", "b", "c", "d", "e"}, 6, [][]string{{"a", "b", "c", "d", "e"}}},
|
||||
}
|
||||
|
||||
assert.Equal([][]string{}, Chunk(arr, -1))
|
||||
|
||||
assert.Equal([][]string{}, Chunk(arr, 0))
|
||||
|
||||
r1 := [][]string{{"a"}, {"b"}, {"c"}, {"d"}, {"e"}}
|
||||
assert.Equal(r1, Chunk(arr, 1))
|
||||
|
||||
r2 := [][]string{{"a", "b"}, {"c", "d"}, {"e"}}
|
||||
assert.Equal(r2, Chunk(arr, 2))
|
||||
|
||||
r3 := [][]string{{"a", "b", "c"}, {"d", "e"}}
|
||||
assert.Equal(r3, Chunk(arr, 3))
|
||||
|
||||
r4 := [][]string{{"a", "b", "c", "d"}, {"e"}}
|
||||
assert.Equal(r4, Chunk(arr, 4))
|
||||
|
||||
r5 := [][]string{{"a", "b", "c", "d", "e"}}
|
||||
assert.Equal(r5, Chunk(arr, 5))
|
||||
|
||||
r6 := [][]string{{"a", "b", "c", "d", "e"}}
|
||||
assert.Equal(r6, Chunk(arr, 6))
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.want, Chunk(tt.slice, tt.chuanSize))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompact(t *testing.T) {
|
||||
@@ -132,6 +158,7 @@ func TestEqual(t *testing.T) {
|
||||
|
||||
assert.Equal(true, Equal(slice1, slice2))
|
||||
assert.Equal(false, Equal(slice1, slice3))
|
||||
assert.Equal(false, Equal(slice2, slice3))
|
||||
}
|
||||
|
||||
// go test -fuzz=Fuzz -fuzztime=10s .
|
||||
@@ -161,12 +188,27 @@ func TestEvery(t *testing.T) {
|
||||
|
||||
assert := internal.NewAssert(t, "TestEvery")
|
||||
|
||||
nums := []int{1, 2, 3, 5}
|
||||
isEven := func(i, num int) bool {
|
||||
return num%2 == 0
|
||||
}
|
||||
isOdd := func(i, num int) bool {
|
||||
return num%2 == 1
|
||||
}
|
||||
|
||||
assert.Equal(false, Every(nums, isEven))
|
||||
tests := []struct {
|
||||
slice []int
|
||||
predicateFn func(i, num int) bool
|
||||
want bool
|
||||
}{
|
||||
{[]int{1, 3, 5, 7}, isOdd, true},
|
||||
{[]int{2, 4, 6, 8}, isEven, true},
|
||||
{[]int{1, 2, 3, 4}, isOdd, false},
|
||||
{[]int{1, 2, 3, 4}, isEven, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.want, Every(tt.slice, tt.predicateFn))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNone(t *testing.T) {
|
||||
@@ -174,12 +216,27 @@ func TestNone(t *testing.T) {
|
||||
|
||||
assert := internal.NewAssert(t, "TestNone")
|
||||
|
||||
nums := []int{1, 2, 3, 5}
|
||||
check := func(i, num int) bool {
|
||||
isEven := func(i, num int) bool {
|
||||
return num%2 == 0
|
||||
}
|
||||
isOdd := func(i, num int) bool {
|
||||
return num%2 == 1
|
||||
}
|
||||
|
||||
assert.Equal(false, None(nums, check))
|
||||
tests := []struct {
|
||||
slice []int
|
||||
predicateFn func(i, num int) bool
|
||||
want bool
|
||||
}{
|
||||
{[]int{1, 3, 5, 7}, isEven, true},
|
||||
{[]int{2, 4, 6, 8}, isOdd, true},
|
||||
{[]int{1, 2, 3, 4}, isOdd, false},
|
||||
{[]int{1, 2, 3, 4}, isEven, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.want, None(tt.slice, tt.predicateFn))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSome(t *testing.T) {
|
||||
@@ -187,12 +244,27 @@ func TestSome(t *testing.T) {
|
||||
|
||||
assert := internal.NewAssert(t, "TestSome")
|
||||
|
||||
nums := []int{1, 2, 3, 5}
|
||||
hasEven := func(i, num int) bool {
|
||||
isEven := func(i, num int) bool {
|
||||
return num%2 == 0
|
||||
}
|
||||
isOdd := func(i, num int) bool {
|
||||
return num%2 == 1
|
||||
}
|
||||
|
||||
assert.Equal(true, Some(nums, hasEven))
|
||||
tests := []struct {
|
||||
slice []int
|
||||
predicateFn func(i, num int) bool
|
||||
want bool
|
||||
}{
|
||||
{[]int{1, 3, 5, 7}, isEven, false},
|
||||
{[]int{2, 4, 6, 8}, isOdd, false},
|
||||
{[]int{1, 2, 3, 4}, isOdd, true},
|
||||
{[]int{1, 2, 3, 4}, isEven, true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.want, Some(tt.slice, tt.predicateFn))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
@@ -200,33 +272,37 @@ func TestFilter(t *testing.T) {
|
||||
|
||||
assert := internal.NewAssert(t, "TestFilter")
|
||||
|
||||
nums := []int{1, 2, 3, 4, 5}
|
||||
isEven := func(i, num int) bool {
|
||||
return num%2 == 0
|
||||
}
|
||||
t.Run("filter int slice", func(t *testing.T) {
|
||||
nums := []int{1, 2, 3, 4, 5}
|
||||
isEven := func(i, num int) bool {
|
||||
return num%2 == 0
|
||||
}
|
||||
|
||||
assert.Equal([]int{2, 4}, Filter(nums, isEven))
|
||||
assert.Equal([]int{2, 4}, Filter(nums, isEven))
|
||||
})
|
||||
|
||||
type student struct {
|
||||
name string
|
||||
age int
|
||||
}
|
||||
students := []student{
|
||||
{"a", 10},
|
||||
{"b", 11},
|
||||
{"c", 12},
|
||||
{"d", 13},
|
||||
{"e", 14},
|
||||
}
|
||||
studentsOfAageGreat12 := []student{
|
||||
{"d", 13},
|
||||
{"e", 14},
|
||||
}
|
||||
filterFunc := func(i int, s student) bool {
|
||||
return s.age > 12
|
||||
}
|
||||
t.Run("filter struct slice", func(t *testing.T) {
|
||||
type student struct {
|
||||
name string
|
||||
age int
|
||||
}
|
||||
students := []student{
|
||||
{"a", 10},
|
||||
{"b", 11},
|
||||
{"c", 12},
|
||||
{"d", 13},
|
||||
{"e", 14},
|
||||
}
|
||||
studentsOfAgeGreat12 := []student{
|
||||
{"d", 13},
|
||||
{"e", 14},
|
||||
}
|
||||
filterFunc := func(i int, s student) bool {
|
||||
return s.age > 12
|
||||
}
|
||||
|
||||
assert.Equal(studentsOfAageGreat12, Filter(students, filterFunc))
|
||||
assert.Equal(studentsOfAgeGreat12, Filter(students, filterFunc))
|
||||
})
|
||||
}
|
||||
|
||||
func TestGroupBy(t *testing.T) {
|
||||
@@ -247,6 +323,8 @@ func TestGroupBy(t *testing.T) {
|
||||
func TestGroupWith(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestGroupWith")
|
||||
|
||||
nums := []float64{6.1, 4.2, 6.3}
|
||||
floor := func(num float64) float64 {
|
||||
return math.Floor(num)
|
||||
@@ -255,9 +333,8 @@ func TestGroupWith(t *testing.T) {
|
||||
4: {4.2},
|
||||
6: {6.1, 6.3},
|
||||
}
|
||||
actual := GroupWith(nums, floor)
|
||||
assert := internal.NewAssert(t, "TestGroupWith")
|
||||
assert.Equal(expected, actual)
|
||||
|
||||
assert.Equal(expected, GroupWith(nums, floor))
|
||||
}
|
||||
|
||||
func TestCount(t *testing.T) {
|
||||
@@ -293,8 +370,15 @@ func TestFind(t *testing.T) {
|
||||
result, ok := Find(nums, even)
|
||||
|
||||
assert := internal.NewAssert(t, "TestFind")
|
||||
|
||||
assert.Equal(true, ok)
|
||||
assert.Equal(2, *result)
|
||||
|
||||
_, ok = Find(nums, func(_ int, v int) bool {
|
||||
return v == 6
|
||||
})
|
||||
|
||||
assert.Equal(false, ok)
|
||||
}
|
||||
|
||||
func TestFindBy(t *testing.T) {
|
||||
@@ -356,19 +440,6 @@ func TestFindLast(t *testing.T) {
|
||||
assert.Equal(4, *result)
|
||||
}
|
||||
|
||||
func TestFindFoundNothing(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
nums := []int{1, 1, 1, 1, 1, 1}
|
||||
findFunc := func(i, num int) bool {
|
||||
return num > 1
|
||||
}
|
||||
_, ok := Find(nums, findFunc)
|
||||
|
||||
assert := internal.NewAssert(t, "TestFindFoundNothing")
|
||||
assert.Equal(false, ok)
|
||||
}
|
||||
|
||||
func TestFlatten(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -408,6 +479,73 @@ func TestForEach(t *testing.T) {
|
||||
assert.Equal([]int{3, 4, 5, 6, 7}, result)
|
||||
}
|
||||
|
||||
func TestForEachConcurrent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestForEachConcurrent")
|
||||
|
||||
t.Run("single thread", func(t *testing.T) {
|
||||
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||
result := make([]int, len(numbers))
|
||||
|
||||
addOne := func(index int, value int) {
|
||||
result[index] = value + 1
|
||||
}
|
||||
|
||||
ForEachConcurrent(numbers, addOne, 1)
|
||||
|
||||
expected := []int{2, 3, 4, 5, 6, 7, 8, 9, 10}
|
||||
assert.Equal(expected, result)
|
||||
})
|
||||
|
||||
t.Run("normal", func(t *testing.T) {
|
||||
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||
result := make([]int, len(numbers))
|
||||
|
||||
addOne := func(index int, value int) {
|
||||
result[index] = value + 1
|
||||
}
|
||||
|
||||
ForEachConcurrent(numbers, addOne, 4)
|
||||
|
||||
expected := []int{2, 3, 4, 5, 6, 7, 8, 9, 10}
|
||||
assert.Equal(expected, result)
|
||||
})
|
||||
|
||||
t.Run("negative threads", func(t *testing.T) {
|
||||
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||
result := make([]int, len(numbers))
|
||||
|
||||
addOne := func(index int, value int) {
|
||||
result[index] = value + 1
|
||||
}
|
||||
|
||||
ForEachConcurrent(numbers, addOne, -4)
|
||||
|
||||
expected := []int{2, 3, 4, 5, 6, 7, 8, 9, 10}
|
||||
assert.Equal(expected, result)
|
||||
})
|
||||
|
||||
t.Run("high number threads", func(t *testing.T) {
|
||||
numbers := make([]int, 1000)
|
||||
for i := range numbers {
|
||||
numbers[i] = i
|
||||
}
|
||||
|
||||
result := make([]int, len(numbers))
|
||||
|
||||
addOne := func(index int, value int) {
|
||||
result[index] = value + 1
|
||||
}
|
||||
|
||||
ForEachConcurrent(numbers, addOne, 50)
|
||||
|
||||
for i, item := range numbers {
|
||||
assert.Equal(item+1, result[i])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestForEachWithBreak(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -518,6 +656,44 @@ func TestReduce(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestReduceConcurrent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestReduceConcurrent")
|
||||
|
||||
t.Run("basic", func(t *testing.T) {
|
||||
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
|
||||
result := ReduceConcurrent(nums, 0, func(_ int, item, agg int) int {
|
||||
return agg + item
|
||||
}, 4)
|
||||
assert.Equal(55, result)
|
||||
})
|
||||
|
||||
t.Run("empty slice", func(t *testing.T) {
|
||||
nums := []int{}
|
||||
result := ReduceConcurrent(nums, 0, func(_ int, item, agg int) int {
|
||||
return agg + item
|
||||
}, 4)
|
||||
assert.Equal(0, result)
|
||||
})
|
||||
|
||||
t.Run("single thread", func(t *testing.T) {
|
||||
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
|
||||
result := ReduceConcurrent(nums, 0, func(_ int, item, agg int) int {
|
||||
return agg + item
|
||||
}, 1)
|
||||
assert.Equal(55, result)
|
||||
})
|
||||
|
||||
t.Run("negative threads", func(t *testing.T) {
|
||||
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
|
||||
result := ReduceConcurrent(nums, 0, func(_ int, item, agg int) int {
|
||||
return agg + item
|
||||
}, -1)
|
||||
assert.Equal(55, result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestReduceBy(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -526,14 +702,12 @@ func TestReduceBy(t *testing.T) {
|
||||
result1 := ReduceBy([]int{1, 2, 3, 4}, 0, func(_ int, item int, agg int) int {
|
||||
return agg + item
|
||||
})
|
||||
assert.Equal(10, result1)
|
||||
|
||||
result2 := ReduceBy([]int{1, 2, 3, 4}, "", func(_ int, item int, agg string) string {
|
||||
return agg + fmt.Sprintf("%v", item)
|
||||
})
|
||||
|
||||
assert.Equal(10, result1)
|
||||
assert.Equal("1234", result2)
|
||||
|
||||
}
|
||||
|
||||
func TestReduceRight(t *testing.T) {
|
||||
@@ -585,15 +759,23 @@ func TestDeleteAt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestDeleteAt")
|
||||
arr := []int{1, 2, 3, 4, 5}
|
||||
|
||||
assert.Equal([]int{2, 3, 4, 5}, DeleteAt(arr, 0))
|
||||
assert.Equal([]int{1, 2, 3, 4}, DeleteAt(arr, 4))
|
||||
tests := []struct {
|
||||
slice []int
|
||||
deletePos int
|
||||
wang []int
|
||||
}{
|
||||
{[]int{1, 2, 3, 4, 5}, 0, []int{2, 3, 4, 5}},
|
||||
{[]int{1, 2, 3, 4, 5}, 1, []int{1, 3, 4, 5}},
|
||||
{[]int{1, 2, 3, 4, 5}, 2, []int{1, 2, 4, 5}},
|
||||
{[]int{1, 2, 3, 4, 5}, 3, []int{1, 2, 3, 5}},
|
||||
{[]int{1, 2, 3, 4, 5}, 4, []int{1, 2, 3, 4}},
|
||||
{[]int{1, 2, 3, 4, 5}, 5, []int{1, 2, 3, 4}},
|
||||
}
|
||||
|
||||
assert.Equal([]int{1, 2, 3, 4}, DeleteAt(arr, 5))
|
||||
assert.Equal([]int{1, 2, 3, 4}, DeleteAt(arr, 6))
|
||||
|
||||
assert.Equal([]int{1, 2, 3, 4, 5}, arr)
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.wang, DeleteAt(tt.slice, tt.deletePos))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteRange(t *testing.T) {
|
||||
@@ -613,16 +795,24 @@ func TestDrop(t *testing.T) {
|
||||
|
||||
assert := internal.NewAssert(t, "TestDrop")
|
||||
|
||||
assert.Equal([]int{}, Drop([]int{}, 0))
|
||||
assert.Equal([]int{}, Drop([]int{}, 1))
|
||||
assert.Equal([]int{}, Drop([]int{}, -1))
|
||||
tests := []struct {
|
||||
slice []int
|
||||
dropNum int
|
||||
want []int
|
||||
}{
|
||||
{[]int{}, 0, []int{}},
|
||||
{[]int{}, 1, []int{}},
|
||||
{[]int{}, -1, []int{}},
|
||||
{[]int{1, 2, 3, 4, 5}, -1, []int{1, 2, 3, 4, 5}},
|
||||
{[]int{1, 2, 3, 4, 5}, 0, []int{1, 2, 3, 4, 5}},
|
||||
{[]int{1, 2, 3, 4, 5}, 1, []int{2, 3, 4, 5}},
|
||||
{[]int{1, 2, 3, 4, 5}, 5, []int{}},
|
||||
{[]int{1, 2, 3, 4, 5}, 6, []int{}},
|
||||
}
|
||||
|
||||
assert.Equal([]int{1, 2, 3, 4, 5}, Drop([]int{1, 2, 3, 4, 5}, 0))
|
||||
assert.Equal([]int{2, 3, 4, 5}, Drop([]int{1, 2, 3, 4, 5}, 1))
|
||||
assert.Equal([]int{}, Drop([]int{1, 2, 3, 4, 5}, 5))
|
||||
assert.Equal([]int{}, Drop([]int{1, 2, 3, 4, 5}, 6))
|
||||
|
||||
assert.Equal([]int{1, 2, 3, 4, 5}, Drop([]int{1, 2, 3, 4, 5}, -1))
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.want, Drop(tt.slice, tt.dropNum))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDropRight(t *testing.T) {
|
||||
@@ -630,16 +820,23 @@ func TestDropRight(t *testing.T) {
|
||||
|
||||
assert := internal.NewAssert(t, "TestDropRight")
|
||||
|
||||
assert.Equal([]int{}, DropRight([]int{}, 0))
|
||||
assert.Equal([]int{}, DropRight([]int{}, 1))
|
||||
assert.Equal([]int{}, DropRight([]int{}, -1))
|
||||
|
||||
assert.Equal([]int{1, 2, 3, 4, 5}, DropRight([]int{1, 2, 3, 4, 5}, 0))
|
||||
assert.Equal([]int{1, 2, 3, 4}, DropRight([]int{1, 2, 3, 4, 5}, 1))
|
||||
assert.Equal([]int{}, DropRight([]int{1, 2, 3, 4, 5}, 5))
|
||||
assert.Equal([]int{}, DropRight([]int{1, 2, 3, 4, 5}, 6))
|
||||
|
||||
assert.Equal([]int{1, 2, 3, 4, 5}, DropRight([]int{1, 2, 3, 4, 5}, -1))
|
||||
tests := []struct {
|
||||
slice []int
|
||||
dropNum int
|
||||
want []int
|
||||
}{
|
||||
{[]int{}, 0, []int{}},
|
||||
{[]int{}, 1, []int{}},
|
||||
{[]int{}, -1, []int{}},
|
||||
{[]int{1, 2, 3, 4, 5}, -1, []int{1, 2, 3, 4, 5}},
|
||||
{[]int{1, 2, 3, 4, 5}, 0, []int{1, 2, 3, 4, 5}},
|
||||
{[]int{1, 2, 3, 4, 5}, 1, []int{1, 2, 3, 4}},
|
||||
{[]int{}, 5, []int{}},
|
||||
{[]int{}, 6, []int{}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.want, DropRight(tt.slice, tt.dropNum))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDropWhile(t *testing.T) {
|
||||
@@ -647,22 +844,19 @@ func TestDropWhile(t *testing.T) {
|
||||
|
||||
assert := internal.NewAssert(t, "TestDropWhile")
|
||||
|
||||
numbers := []int{1, 2, 3, 4, 5}
|
||||
tests := []struct {
|
||||
slice []int
|
||||
fn func(int) bool
|
||||
want []int
|
||||
}{
|
||||
{[]int{1, 2, 3, 4, 5}, func(n int) bool { return n != 2 }, []int{2, 3, 4, 5}},
|
||||
{[]int{1, 2, 3, 4, 5}, func(n int) bool { return true }, []int{}},
|
||||
{[]int{1, 2, 3, 4, 5}, func(n int) bool { return n == 0 }, []int{1, 2, 3, 4, 5}},
|
||||
}
|
||||
|
||||
r1 := DropWhile(numbers, func(n int) bool {
|
||||
return n != 2
|
||||
})
|
||||
assert.Equal([]int{2, 3, 4, 5}, r1)
|
||||
|
||||
r2 := DropWhile(numbers, func(n int) bool {
|
||||
return true
|
||||
})
|
||||
assert.Equal([]int{}, r2)
|
||||
|
||||
r3 := DropWhile(numbers, func(n int) bool {
|
||||
return n == 0
|
||||
})
|
||||
assert.Equal([]int{1, 2, 3, 4, 5}, r3)
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.want, DropWhile(tt.slice, tt.fn))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDropRightWhile(t *testing.T) {
|
||||
@@ -670,22 +864,19 @@ func TestDropRightWhile(t *testing.T) {
|
||||
|
||||
assert := internal.NewAssert(t, "TestDropRightWhile")
|
||||
|
||||
numbers := []int{1, 2, 3, 4, 5}
|
||||
tests := []struct {
|
||||
slice []int
|
||||
fn func(int) bool
|
||||
want []int
|
||||
}{
|
||||
{[]int{1, 2, 3, 4, 5}, func(n int) bool { return n != 2 }, []int{1, 2}},
|
||||
{[]int{1, 2, 3, 4, 5}, func(n int) bool { return true }, []int{}},
|
||||
{[]int{1, 2, 3, 4, 5}, func(n int) bool { return n == 0 }, []int{1, 2, 3, 4, 5}},
|
||||
}
|
||||
|
||||
r1 := DropRightWhile(numbers, func(n int) bool {
|
||||
return n != 2
|
||||
})
|
||||
assert.Equal([]int{1, 2}, r1)
|
||||
|
||||
r2 := DropRightWhile(numbers, func(n int) bool {
|
||||
return true
|
||||
})
|
||||
assert.Equal([]int{}, r2)
|
||||
|
||||
r3 := DropRightWhile(numbers, func(n int) bool {
|
||||
return n == 0
|
||||
})
|
||||
assert.Equal([]int{1, 2, 3, 4, 5}, r3)
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.want, DropRightWhile(tt.slice, tt.fn))
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertAt(t *testing.T) {
|
||||
@@ -693,15 +884,25 @@ func TestInsertAt(t *testing.T) {
|
||||
|
||||
assert := internal.NewAssert(t, "TestInsertAt")
|
||||
|
||||
strs := []string{"a", "b", "c"}
|
||||
assert.Equal([]string{"a", "b", "c"}, InsertAt(strs, -1, "1"))
|
||||
assert.Equal([]string{"a", "b", "c"}, InsertAt(strs, 4, "1"))
|
||||
assert.Equal([]string{"1", "a", "b", "c"}, InsertAt(strs, 0, "1"))
|
||||
assert.Equal([]string{"a", "1", "b", "c"}, InsertAt(strs, 1, "1"))
|
||||
assert.Equal([]string{"a", "b", "1", "c"}, InsertAt(strs, 2, "1"))
|
||||
assert.Equal([]string{"a", "b", "c", "1"}, InsertAt(strs, 3, "1"))
|
||||
assert.Equal([]string{"1", "2", "3", "a", "b", "c"}, InsertAt(strs, 0, []string{"1", "2", "3"}))
|
||||
assert.Equal([]string{"a", "b", "c", "1", "2", "3"}, InsertAt(strs, 3, []string{"1", "2", "3"}))
|
||||
tests := []struct {
|
||||
slice []string
|
||||
insertPos int
|
||||
insertValue any
|
||||
want []string
|
||||
}{
|
||||
{[]string{"a", "b", "c"}, -1, "1", []string{"a", "b", "c"}},
|
||||
{[]string{"a", "b", "c"}, 4, "1", []string{"a", "b", "c"}},
|
||||
{[]string{"a", "b", "c"}, 0, "1", []string{"1", "a", "b", "c"}},
|
||||
{[]string{"a", "b", "c"}, 1, "1", []string{"a", "1", "b", "c"}},
|
||||
{[]string{"a", "b", "c"}, 2, "1", []string{"a", "b", "1", "c"}},
|
||||
{[]string{"a", "b", "c"}, 3, "1", []string{"a", "b", "c", "1"}},
|
||||
{[]string{"a", "b", "c"}, 0, []string{"1", "2", "3"}, []string{"1", "2", "3", "a", "b", "c"}},
|
||||
{[]string{"a", "b", "c"}, 3, []string{"1", "2", "3"}, []string{"a", "b", "c", "1", "2", "3"}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.want, InsertAt(tt.slice, tt.insertPos, tt.insertValue))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateAt(t *testing.T) {
|
||||
@@ -732,7 +933,87 @@ func TestUniqueBy(t *testing.T) {
|
||||
actual := UniqueBy([]int{1, 2, 3, 4, 5, 6}, func(val int) int {
|
||||
return val % 4
|
||||
})
|
||||
assert.Equal([]int{1, 2, 3, 0}, actual)
|
||||
|
||||
assert.Equal([]int{1, 2, 3, 4}, actual)
|
||||
}
|
||||
|
||||
func TestUniqueByField(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestUniqueByField")
|
||||
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
users := []User{
|
||||
{ID: 1, Name: "a"},
|
||||
{ID: 2, Name: "b"},
|
||||
{ID: 1, Name: "c"},
|
||||
}
|
||||
|
||||
uniqueUsers, err := UniqueByField(users, "ID")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
assert.Equal([]User{
|
||||
{ID: 1, Name: "a"},
|
||||
{ID: 2, Name: "b"},
|
||||
}, uniqueUsers)
|
||||
}
|
||||
|
||||
func TestUniqueByComparator(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestUniqueByComparator")
|
||||
|
||||
t.Run("equal comparison", func(t *testing.T) {
|
||||
nums := []int{1, 2, 3, 1, 2, 4, 5, 6, 4, 7}
|
||||
comparator := func(item int, other int) bool {
|
||||
return item == other
|
||||
}
|
||||
result := UniqueByComparator(nums, comparator)
|
||||
|
||||
assert.Equal([]int{1, 2, 3, 4, 5, 6, 7}, result)
|
||||
})
|
||||
|
||||
t.Run("unique struct slice by field", func(t *testing.T) {
|
||||
type student struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
students := []student{
|
||||
{Name: "a", Age: 11},
|
||||
{Name: "b", Age: 12},
|
||||
{Name: "a", Age: 13},
|
||||
{Name: "c", Age: 14},
|
||||
}
|
||||
|
||||
comparator := func(item, other student) bool { return item.Name == other.Name }
|
||||
|
||||
result := UniqueByComparator(students, comparator)
|
||||
|
||||
assert.Equal([]student{
|
||||
{Name: "a", Age: 11},
|
||||
{Name: "b", Age: 12},
|
||||
{Name: "c", Age: 14},
|
||||
}, result)
|
||||
})
|
||||
|
||||
t.Run("case-insensitive string comparison", func(t *testing.T) {
|
||||
stringSlice := []string{"apple", "banana", "Apple", "cherry", "Banana", "date"}
|
||||
caseInsensitiveComparator := func(item, other string) bool {
|
||||
return strings.ToLower(item) == strings.ToLower(other)
|
||||
}
|
||||
|
||||
result := UniqueByComparator(stringSlice, caseInsensitiveComparator)
|
||||
|
||||
assert.Equal([]string{"apple", "banana", "cherry", "date"}, result)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestUnion(t *testing.T) {
|
||||
@@ -1420,3 +1701,118 @@ func TestRightPaddingAndLeftPadding(t *testing.T) {
|
||||
padded := LeftPadding(RightPadding(nums, 0, 3), 0, 3)
|
||||
assert.Equal([]int{0, 0, 0, 1, 2, 3, 4, 5, 0, 0, 0}, padded)
|
||||
}
|
||||
|
||||
func TestUniqueByConcurrent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestUniqueByConcurrent")
|
||||
|
||||
nums := []int{1, 2, 3, 1, 2, 4, 5, 6, 4, 7}
|
||||
comparator := func(item int, other int) bool { return item == other }
|
||||
|
||||
result := UniqueByConcurrent(nums, comparator, 4)
|
||||
|
||||
assert.Equal([]int{1, 2, 3, 4, 5, 6, 7}, result)
|
||||
}
|
||||
|
||||
func TestMapConcurrent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestMapConcurrent")
|
||||
|
||||
t.Run("empty slice", func(t *testing.T) {
|
||||
actual := MapConcurrent([]int{}, func(_, n int) int { return n * n }, 4)
|
||||
assert.Equal([]int{}, actual)
|
||||
})
|
||||
|
||||
t.Run("single thread", func(t *testing.T) {
|
||||
nums := []int{1, 2, 3, 4, 5, 6}
|
||||
expected := []int{1, 4, 9, 16, 25, 36}
|
||||
actual := MapConcurrent(nums, func(_, n int) int { return n * n }, 1)
|
||||
assert.Equal(expected, actual)
|
||||
})
|
||||
|
||||
t.Run("multiple threads", func(t *testing.T) {
|
||||
nums := []int{1, 2, 3, 4, 5, 6}
|
||||
expected := []int{1, 4, 9, 16, 25, 36}
|
||||
actual := MapConcurrent(nums, func(_, n int) int { return n * n }, 4)
|
||||
assert.Equal(expected, actual)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestFilterConcurrent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestFilterConcurrent")
|
||||
|
||||
t.Run("empty slice", func(t *testing.T) {
|
||||
actual := FilterConcurrent([]int{}, func(_, n int) bool { return n != 0 }, 4)
|
||||
assert.Equal([]int{}, actual)
|
||||
})
|
||||
|
||||
t.Run("single thread", func(t *testing.T) {
|
||||
nums := []int{1, 2, 3, 4, 5, 6}
|
||||
expected := []int{4, 5, 6}
|
||||
actual := FilterConcurrent(nums, func(_, n int) bool { return n > 3 }, 1)
|
||||
assert.Equal(expected, actual)
|
||||
})
|
||||
|
||||
t.Run("multiple threads", func(t *testing.T) {
|
||||
nums := []int{1, 2, 3, 4, 5, 6}
|
||||
expected := []int{4, 5, 6}
|
||||
actual := FilterConcurrent(nums, func(_, n int) bool { return n > 3 }, 4)
|
||||
assert.Equal(expected, actual)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFrequency(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestFrequency")
|
||||
|
||||
t.Run("empty slice", func(t *testing.T) {
|
||||
result := Frequency([]int{})
|
||||
assert.Equal(map[int]int{}, result)
|
||||
})
|
||||
|
||||
t.Run("int slice", func(t *testing.T) {
|
||||
nums := []int{1, 2, 2, 3, 3, 3}
|
||||
expected := map[int]int{1: 1, 2: 2, 3: 3}
|
||||
result := Frequency(nums)
|
||||
|
||||
assert.Equal(expected, result)
|
||||
})
|
||||
|
||||
t.Run("string slice", func(t *testing.T) {
|
||||
strs := []string{"a", "b", "b", "c", "c", "c"}
|
||||
expected := map[string]int{"a": 1, "b": 2, "c": 3}
|
||||
result := Frequency(strs)
|
||||
|
||||
assert.Equal(expected, result)
|
||||
})
|
||||
|
||||
t.Run("struct slice", func(t *testing.T) {
|
||||
type student struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
students := []student{
|
||||
{Name: "a", Age: 11},
|
||||
{Name: "b", Age: 12},
|
||||
{Name: "a", Age: 13},
|
||||
{Name: "c", Age: 14},
|
||||
}
|
||||
|
||||
expected := map[student]int{
|
||||
{Name: "a", Age: 11}: 1,
|
||||
{Name: "a", Age: 13}: 1,
|
||||
{Name: "b", Age: 12}: 1,
|
||||
{Name: "c", Age: 14}: 1,
|
||||
}
|
||||
result := Frequency(students)
|
||||
|
||||
assert.Equal(expected, result)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,13 +5,18 @@ package strutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/rand"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// used in `Shuffle` function
|
||||
var rng = rand.New(rand.NewSource(int64(time.Now().UnixNano())))
|
||||
|
||||
// CamelCase coverts string to camelCase string. Non letters and numbers will be ignored.
|
||||
// Play: https://go.dev/play/p/9eXP3tn2tUy
|
||||
func CamelCase(s string) string {
|
||||
@@ -70,7 +75,7 @@ func LowerFirst(s string) string {
|
||||
return string(r) + s[size:]
|
||||
}
|
||||
|
||||
// PadStart pads string on the left and right side if it's shorter than size.
|
||||
// Pad pads string on the left and right side if it's shorter than size.
|
||||
// Padding characters are truncated if they exceed size.
|
||||
// Play: https://go.dev/play/p/NzImQq-VF8q
|
||||
func Pad(source string, size int, padStr string) string {
|
||||
@@ -621,6 +626,8 @@ func HammingDistance(a, b string) (int, error) {
|
||||
// Concat uses the strings.Builder to concatenate the input strings.
|
||||
// - `length` is the expected length of the concatenated string.
|
||||
// - if you are unsure about the length of the string to be concatenated, please pass 0 or a negative number.
|
||||
//
|
||||
// Play: todo
|
||||
func Concat(length int, str ...string) string {
|
||||
if len(str) == 0 {
|
||||
return ""
|
||||
@@ -638,3 +645,93 @@ func Concat(length int, str ...string) string {
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// Ellipsis truncates a string to a specified length and appends an ellipsis.
|
||||
// Play: todo
|
||||
func Ellipsis(str string, length int) string {
|
||||
str = strings.TrimSpace(str)
|
||||
|
||||
if length <= 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
runes := []rune(str)
|
||||
|
||||
if len(runes) <= length {
|
||||
return str
|
||||
}
|
||||
|
||||
return string(runes[:length]) + "..."
|
||||
}
|
||||
|
||||
// Shuffle the order of characters of given string.
|
||||
// Play: todo
|
||||
func Shuffle(str string) string {
|
||||
runes := []rune(str)
|
||||
|
||||
for i := len(runes) - 1; i > 0; i-- {
|
||||
j := rng.Intn(i + 1)
|
||||
runes[i], runes[j] = runes[j], runes[i]
|
||||
}
|
||||
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
// Rotate rotates the string by the specified number of characters.
|
||||
// Play: todo
|
||||
func Rotate(str string, shift int) string {
|
||||
if shift == 0 {
|
||||
return str
|
||||
}
|
||||
|
||||
runes := []rune(str)
|
||||
length := len(runes)
|
||||
if length == 0 {
|
||||
return str
|
||||
}
|
||||
|
||||
shift = shift % length
|
||||
|
||||
if shift < 0 {
|
||||
shift = length + shift
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
sb.Grow(length)
|
||||
|
||||
sb.WriteString(string(runes[length-shift:]))
|
||||
sb.WriteString(string(runes[:length-shift]))
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// TemplateReplace replaces the placeholders in the template string with the corresponding values in the data map.
|
||||
// The placeholders are enclosed in curly braces, e.g. {key}.
|
||||
// for example, the template string is "Hello, {name}!", and the data map is {"name": "world"},
|
||||
// the result will be "Hello, world!".
|
||||
// Play: todo
|
||||
func TemplateReplace(template string, data map[string]string) string {
|
||||
re := regexp.MustCompile(`\{(\w+)\}`)
|
||||
|
||||
result := re.ReplaceAllStringFunc(template, func(s string) string {
|
||||
key := strings.Trim(s, "{}")
|
||||
if val, ok := data[key]; ok {
|
||||
return val
|
||||
}
|
||||
|
||||
return s
|
||||
})
|
||||
|
||||
result = strings.ReplaceAll(result, "{{", "{")
|
||||
result = strings.ReplaceAll(result, "}}", "}")
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// RegexMatchAllGroups Matches all subgroups in a string using a regular expression and returns the result.
|
||||
// Play: todo
|
||||
func RegexMatchAllGroups(pattern, str string) [][]string {
|
||||
re := regexp.MustCompile(pattern)
|
||||
matches := re.FindAllStringSubmatch(str, -1)
|
||||
return matches
|
||||
}
|
||||
|
||||
@@ -694,3 +694,62 @@ func ExampleConcat() {
|
||||
// Go Language
|
||||
// An apple a day,keeps the doctor away
|
||||
}
|
||||
|
||||
func ExampleEllipsis() {
|
||||
result1 := Ellipsis("hello world", 5)
|
||||
result2 := Ellipsis("你好,世界!", 2)
|
||||
result3 := Ellipsis("😀😃😄😁😆", 3)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
|
||||
// Output:
|
||||
// hello...
|
||||
// 你好...
|
||||
// 😀😃😄...
|
||||
}
|
||||
|
||||
func ExampleRotate() {
|
||||
result1 := Rotate("Hello", 0)
|
||||
result2 := Rotate("Hello", 1)
|
||||
result3 := Rotate("Hello", 2)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
|
||||
// Output:
|
||||
// Hello
|
||||
// oHell
|
||||
// loHel
|
||||
}
|
||||
|
||||
func ExampleTemplateReplace() {
|
||||
template := `Hello, my name is {name}, I'm {age} years old.`
|
||||
data := map[string]string{
|
||||
"name": "Bob",
|
||||
"age": "20",
|
||||
}
|
||||
|
||||
result := TemplateReplace(template, data)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// Hello, my name is Bob, I'm 20 years old.
|
||||
}
|
||||
|
||||
func ExampleRegexMatchAllGroups() {
|
||||
pattern := `(\w+\.+\w+)@(\w+)\.(\w+)`
|
||||
str := "Emails: john.doe@example.com and jane.doe@example.com"
|
||||
|
||||
result := RegexMatchAllGroups(pattern, str)
|
||||
|
||||
fmt.Println(result[0])
|
||||
fmt.Println(result[1])
|
||||
|
||||
// Output:
|
||||
// [john.doe@example.com john.doe example com]
|
||||
// [jane.doe@example.com jane.doe example com]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package strutil
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
@@ -12,19 +11,22 @@ func TestCamelCase(t *testing.T) {
|
||||
|
||||
assert := internal.NewAssert(t, "TestCamelCase")
|
||||
|
||||
cases := map[string]string{
|
||||
"": "",
|
||||
"foobar": "foobar",
|
||||
"&FOO:BAR$BAZ": "fooBarBaz",
|
||||
"fooBar": "fooBar",
|
||||
"FOObar": "foObar",
|
||||
"$foo%": "foo",
|
||||
" $#$Foo 22 bar ": "foo22Bar",
|
||||
"Foo-#1😄$_%^&*(1bar": "foo11Bar",
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"", ""},
|
||||
{"foobar", "foobar"},
|
||||
{"&FOO:BAR$BAZ", "fooBarBaz"},
|
||||
{"fooBar", "fooBar"},
|
||||
{"FOObar", "foObar"},
|
||||
{"$foo%", "foo"},
|
||||
{" $#$Foo 22 bar ", "foo22Bar"},
|
||||
{"Foo-#1😄$_%^&*(1bar", "foo11Bar"},
|
||||
}
|
||||
|
||||
for k, v := range cases {
|
||||
assert.Equal(v, CamelCase(k))
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.expected, CamelCase(tt.input))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,19 +35,22 @@ func TestCapitalize(t *testing.T) {
|
||||
|
||||
assert := internal.NewAssert(t, "TestCapitalize")
|
||||
|
||||
cases := map[string]string{
|
||||
"": "",
|
||||
"Foo": "Foo",
|
||||
"_foo": "_foo",
|
||||
"foobar": "Foobar",
|
||||
"fooBar": "Foobar",
|
||||
"foo Bar": "Foo bar",
|
||||
"foo-bar": "Foo-bar",
|
||||
"$foo%": "$foo%",
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"", ""},
|
||||
{"Foo", "Foo"},
|
||||
{"_foo", "_foo"},
|
||||
{"foobar", "Foobar"},
|
||||
{"fooBar", "Foobar"},
|
||||
{"foo Bar", "Foo bar"},
|
||||
{"foo-bar", "Foo-bar"},
|
||||
{"$foo%", "$foo%"},
|
||||
}
|
||||
|
||||
for k, v := range cases {
|
||||
assert.Equal(v, Capitalize(k))
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.expected, Capitalize(tt.input))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,23 +59,26 @@ func TestKebabCase(t *testing.T) {
|
||||
|
||||
assert := internal.NewAssert(t, "TestKebabCase")
|
||||
|
||||
cases := map[string]string{
|
||||
"": "",
|
||||
"foo-bar": "foo-bar",
|
||||
"--Foo---Bar-": "foo-bar",
|
||||
"Foo Bar-": "foo-bar",
|
||||
"foo_Bar": "foo-bar",
|
||||
"fooBar": "foo-bar",
|
||||
"FOOBAR": "foobar",
|
||||
"FOO_BAR": "foo-bar",
|
||||
"__FOO_BAR__": "foo-bar",
|
||||
"$foo@Bar": "foo-bar",
|
||||
" $#$Foo 22 bar ": "foo-22-bar",
|
||||
"Foo-#1😄$_%^&*(1bar": "foo-1-1-bar",
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"", ""},
|
||||
{"foo-bar", "foo-bar"},
|
||||
{"--Foo---Bar-", "foo-bar"},
|
||||
{"Foo Bar-", "foo-bar"},
|
||||
{"foo_Bar", "foo-bar"},
|
||||
{"fooBar", "foo-bar"},
|
||||
{"FOOBAR", "foobar"},
|
||||
{"FOO_BAR", "foo-bar"},
|
||||
{"__FOO_BAR__", "foo-bar"},
|
||||
{"$foo@Bar", "foo-bar"},
|
||||
{" $#$Foo 22 bar ", "foo-22-bar"},
|
||||
{"Foo-#1😄$_%^&*(1bar", "foo-1-1-bar"},
|
||||
}
|
||||
|
||||
for k, v := range cases {
|
||||
assert.Equal(v, KebabCase(k))
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.expected, KebabCase(tt.input))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,23 +87,26 @@ func TestUpperKebabCase(t *testing.T) {
|
||||
|
||||
assert := internal.NewAssert(t, "TestUpperKebabCase")
|
||||
|
||||
cases := map[string]string{
|
||||
"": "",
|
||||
"foo-bar": "FOO-BAR",
|
||||
"--Foo---Bar-": "FOO-BAR",
|
||||
"Foo Bar-": "FOO-BAR",
|
||||
"foo_Bar": "FOO-BAR",
|
||||
"fooBar": "FOO-BAR",
|
||||
"FOOBAR": "FOOBAR",
|
||||
"FOO_BAR": "FOO-BAR",
|
||||
"__FOO_BAR__": "FOO-BAR",
|
||||
"$foo@Bar": "FOO-BAR",
|
||||
" $#$Foo 22 bar ": "FOO-22-BAR",
|
||||
"Foo-#1😄$_%^&*(1bar": "FOO-1-1-BAR",
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"", ""},
|
||||
{"foo-bar", "FOO-BAR"},
|
||||
{"--Foo---Bar-", "FOO-BAR"},
|
||||
{"Foo Bar-", "FOO-BAR"},
|
||||
{"foo_Bar", "FOO-BAR"},
|
||||
{"fooBar", "FOO-BAR"},
|
||||
{"FOOBAR", "FOOBAR"},
|
||||
{"FOO_BAR", "FOO-BAR"},
|
||||
{"__FOO_BAR__", "FOO-BAR"},
|
||||
{"$foo@Bar", "FOO-BAR"},
|
||||
{" $#$Foo 22 bar ", "FOO-22-BAR"},
|
||||
{"Foo-#1😄$_%^&*(1bar", "FOO-1-1-BAR"},
|
||||
}
|
||||
|
||||
for k, v := range cases {
|
||||
assert.Equal(v, UpperKebabCase(k))
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.expected, UpperKebabCase(tt.input))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,23 +115,26 @@ func TestSnakeCase(t *testing.T) {
|
||||
|
||||
assert := internal.NewAssert(t, "TestSnakeCase")
|
||||
|
||||
cases := map[string]string{
|
||||
"": "",
|
||||
"foo-bar": "foo_bar",
|
||||
"--Foo---Bar-": "foo_bar",
|
||||
"Foo Bar-": "foo_bar",
|
||||
"foo_Bar": "foo_bar",
|
||||
"fooBar": "foo_bar",
|
||||
"FOOBAR": "foobar",
|
||||
"FOO_BAR": "foo_bar",
|
||||
"__FOO_BAR__": "foo_bar",
|
||||
"$foo@Bar": "foo_bar",
|
||||
" $#$Foo 22 bar ": "foo_22_bar",
|
||||
"Foo-#1😄$_%^&*(1bar": "foo_1_1_bar",
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"", ""},
|
||||
{"foo-bar", "foo_bar"},
|
||||
{"--Foo---Bar-", "foo_bar"},
|
||||
{"Foo Bar-", "foo_bar"},
|
||||
{"foo_Bar", "foo_bar"},
|
||||
{"fooBar", "foo_bar"},
|
||||
{"FOOBAR", "foobar"},
|
||||
{"FOO_BAR", "foo_bar"},
|
||||
{"__FOO_BAR__", "foo_bar"},
|
||||
{"$foo@Bar", "foo_bar"},
|
||||
{" $#$Foo 22 bar ", "foo_22_bar"},
|
||||
{"Foo-#1😄$_%^&*(1bar", "foo_1_1_bar"},
|
||||
}
|
||||
|
||||
for k, v := range cases {
|
||||
assert.Equal(v, SnakeCase(k))
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.expected, SnakeCase(tt.input))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,23 +143,26 @@ func TestUpperSnakeCase(t *testing.T) {
|
||||
|
||||
assert := internal.NewAssert(t, "TestUpperSnakeCase")
|
||||
|
||||
cases := map[string]string{
|
||||
"": "",
|
||||
"foo-bar": "FOO_BAR",
|
||||
"--Foo---Bar-": "FOO_BAR",
|
||||
"Foo Bar-": "FOO_BAR",
|
||||
"foo_Bar": "FOO_BAR",
|
||||
"fooBar": "FOO_BAR",
|
||||
"FOOBAR": "FOOBAR",
|
||||
"FOO_BAR": "FOO_BAR",
|
||||
"__FOO_BAR__": "FOO_BAR",
|
||||
"$foo@Bar": "FOO_BAR",
|
||||
" $#$Foo 22 bar ": "FOO_22_BAR",
|
||||
"Foo-#1😄$_%^&*(1bar": "FOO_1_1_BAR",
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"", ""},
|
||||
{"foo-bar", "FOO_BAR"},
|
||||
{"--Foo---Bar-", "FOO_BAR"},
|
||||
{"Foo Bar-", "FOO_BAR"},
|
||||
{"foo_Bar", "FOO_BAR"},
|
||||
{"fooBar", "FOO_BAR"},
|
||||
{"FOOBAR", "FOOBAR"},
|
||||
{"FOO_BAR", "FOO_BAR"},
|
||||
{"__FOO_BAR__", "FOO_BAR"},
|
||||
{"$foo@Bar", "FOO_BAR"},
|
||||
{" $#$Foo 22 bar ", "FOO_22_BAR"},
|
||||
{"Foo-#1😄$_%^&*(1bar", "FOO_1_1_BAR"},
|
||||
}
|
||||
|
||||
for k, v := range cases {
|
||||
assert.Equal(v, UpperSnakeCase(k))
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.expected, UpperSnakeCase(tt.input))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,16 +171,19 @@ func TestUpperFirst(t *testing.T) {
|
||||
|
||||
assert := internal.NewAssert(t, "TestLowerFirst")
|
||||
|
||||
cases := map[string]string{
|
||||
"": "",
|
||||
"foo": "Foo",
|
||||
"bAR": "BAR",
|
||||
"FOo": "FOo",
|
||||
"fOo大": "FOo大",
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"", ""},
|
||||
{"foo", "Foo"},
|
||||
{"bAR", "BAR"},
|
||||
{"FOo", "FOo"},
|
||||
{"fOo大", "FOo大"},
|
||||
}
|
||||
|
||||
for k, v := range cases {
|
||||
assert.Equal(v, UpperFirst(k))
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.expected, UpperFirst(tt.input))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,16 +192,19 @@ func TestLowerFirst(t *testing.T) {
|
||||
|
||||
assert := internal.NewAssert(t, "TestLowerFirst")
|
||||
|
||||
cases := map[string]string{
|
||||
"": "",
|
||||
"foo": "foo",
|
||||
"bAR": "bAR",
|
||||
"FOo": "fOo",
|
||||
"fOo大": "fOo大",
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"", ""},
|
||||
{"foo", "foo"},
|
||||
{"bAR", "bAR"},
|
||||
{"FOo", "fOo"},
|
||||
{"fOo大", "fOo大"},
|
||||
}
|
||||
|
||||
for k, v := range cases {
|
||||
assert.Equal(v, LowerFirst(k))
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.expected, LowerFirst(tt.input))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,23 +213,48 @@ func TestPad(t *testing.T) {
|
||||
|
||||
assert := internal.NewAssert(t, "TestPad")
|
||||
|
||||
assert.Equal("a ", Pad("a", 2, ""))
|
||||
assert.Equal("a", Pad("a", 1, "b"))
|
||||
assert.Equal("ab", Pad("a", 2, "b"))
|
||||
assert.Equal("mabcdm", Pad("abcd", 6, "m"))
|
||||
tests := []struct {
|
||||
input string
|
||||
padSize int
|
||||
padChar string
|
||||
expected string
|
||||
}{
|
||||
{"", 0, "", ""},
|
||||
{"a", 0, "", "a"},
|
||||
{"a", 1, "", "a"},
|
||||
{"a", 2, "", "a "},
|
||||
{"a", 1, "b", "a"},
|
||||
{"a", 2, "b", "ab"},
|
||||
{"abcd", 6, "m", "mabcdm"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.expected, Pad(tt.input, tt.padSize, tt.padChar))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPadEnd(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestPadEnd")
|
||||
|
||||
assert.Equal("a ", PadEnd("a", 2, " "))
|
||||
assert.Equal("a", PadEnd("a", 1, "b"))
|
||||
assert.Equal("ab", PadEnd("a", 2, "b"))
|
||||
assert.Equal("abcdmn", PadEnd("abcd", 6, "mno"))
|
||||
assert.Equal("abcdmm", PadEnd("abcd", 6, "m"))
|
||||
assert.Equal("abcaba", PadEnd("abc", 6, "ab"))
|
||||
tests := []struct {
|
||||
input string
|
||||
padSize int
|
||||
padChar string
|
||||
expected string
|
||||
}{
|
||||
{"a", 2, " ", "a "},
|
||||
{"a", 1, "b", "a"},
|
||||
{"a", 2, "b", "ab"},
|
||||
{"abcd", 6, "mno", "abcdmn"},
|
||||
{"abcd", 6, "m", "abcdmm"},
|
||||
{"abcd", 6, "ab", "abcdab"},
|
||||
}
|
||||
|
||||
assert.NotEqual("ba", PadEnd("a", 2, "b"))
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.expected, PadEnd(tt.input, tt.padSize, tt.padChar))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPadStart(t *testing.T) {
|
||||
@@ -214,13 +262,22 @@ func TestPadStart(t *testing.T) {
|
||||
|
||||
assert := internal.NewAssert(t, "TestPadStart")
|
||||
|
||||
assert.Equal("a", PadStart("a", 1, "b"))
|
||||
assert.Equal("ba", PadStart("a", 2, "b"))
|
||||
assert.Equal("mnabcd", PadStart("abcd", 6, "mno"))
|
||||
assert.Equal("mmabcd", PadStart("abcd", 6, "m"))
|
||||
assert.Equal("abaabc", PadStart("abc", 6, "ab"))
|
||||
tests := []struct {
|
||||
input string
|
||||
padSize int
|
||||
padChar string
|
||||
expected string
|
||||
}{
|
||||
{"a", 1, "b", "a"},
|
||||
{"a", 2, "b", "ba"},
|
||||
{"abcd", 6, "mno", "mnabcd"},
|
||||
{"abcd", 6, "m", "mmabcd"},
|
||||
{"abc", 6, "ab", "abaabc"},
|
||||
}
|
||||
|
||||
assert.NotEqual("ab", PadStart("a", 2, "b"))
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.expected, PadStart(tt.input, tt.padSize, tt.padChar))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBefore(t *testing.T) {
|
||||
@@ -228,12 +285,21 @@ func TestBefore(t *testing.T) {
|
||||
|
||||
assert := internal.NewAssert(t, "TestBefore")
|
||||
|
||||
assert.Equal("lancet", Before("lancet", ""))
|
||||
assert.Equal("", Before("lancet", "lancet"))
|
||||
assert.Equal("lancet", Before("lancet", "abcdef"))
|
||||
tests := []struct {
|
||||
input string
|
||||
char string
|
||||
expected string
|
||||
}{
|
||||
{"lancet", "", "lancet"},
|
||||
{"lancet", "lancet", ""},
|
||||
{"lancet", "abcdef", "lancet"},
|
||||
{"github.com/test/lancet", "/", "github.com"},
|
||||
{"github.com/test/lancet", "test", "github.com/"},
|
||||
}
|
||||
|
||||
assert.Equal("github.com", Before("github.com/test/lancet", "/"))
|
||||
assert.Equal("github.com/", Before("github.com/test/lancet", "test"))
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.expected, Before(tt.input, tt.char))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBeforeLast(t *testing.T) {
|
||||
@@ -243,7 +309,6 @@ func TestBeforeLast(t *testing.T) {
|
||||
|
||||
assert.Equal("lancet", BeforeLast("lancet", ""))
|
||||
assert.Equal("lancet", BeforeLast("lancet", "abcdef"))
|
||||
|
||||
assert.Equal("github.com/test", BeforeLast("github.com/test/lancet", "/"))
|
||||
assert.Equal("github.com/test/", BeforeLast("github.com/test/test/lancet", "test"))
|
||||
}
|
||||
@@ -257,7 +322,6 @@ func TestAfter(t *testing.T) {
|
||||
assert.Equal("", After("lancet", "lancet"))
|
||||
assert.Equal("test/lancet", After("github.com/test/lancet", "/"))
|
||||
assert.Equal("/lancet", After("github.com/test/lancet", "test"))
|
||||
|
||||
assert.Equal("lancet", After("lancet", "abcdef"))
|
||||
}
|
||||
|
||||
@@ -403,10 +467,8 @@ func TestStringToBytes(t *testing.T) {
|
||||
|
||||
assert := internal.NewAssert(t, "TestStringToBytes")
|
||||
|
||||
str := "abc"
|
||||
bytes := StringToBytes(str)
|
||||
|
||||
assert.Equal(reflect.DeepEqual(bytes, []byte{'a', 'b', 'c'}), true)
|
||||
bytes := StringToBytes("abc")
|
||||
assert.Equal(bytes, []byte{'a', 'b', 'c'})
|
||||
}
|
||||
|
||||
func TestBytesToString(t *testing.T) {
|
||||
@@ -414,16 +476,15 @@ func TestBytesToString(t *testing.T) {
|
||||
|
||||
assert := internal.NewAssert(t, "TestBytesToString")
|
||||
|
||||
bytes := []byte{'a', 'b', 'c'}
|
||||
str := BytesToString(bytes)
|
||||
|
||||
assert.Equal(str == "abc", true)
|
||||
str := BytesToString([]byte{'a', 'b', 'c'})
|
||||
assert.Equal("abc", str)
|
||||
}
|
||||
|
||||
func TestIsBlank(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestIsBlank")
|
||||
|
||||
assert.Equal(IsBlank(""), true)
|
||||
assert.Equal(IsBlank("\t\v\f\n"), true)
|
||||
|
||||
@@ -434,6 +495,7 @@ func TestIsNotBlank(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestIsBlank")
|
||||
|
||||
assert.Equal(IsNotBlank(""), false)
|
||||
assert.Equal(IsNotBlank(" "), false)
|
||||
assert.Equal(IsNotBlank("\t\v\f\n"), false)
|
||||
@@ -446,6 +508,7 @@ func TestHasPrefixAny(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestHasPrefixAny")
|
||||
|
||||
str := "foo bar"
|
||||
prefixes := []string{"fo", "xyz", "hello"}
|
||||
notMatches := []string{"oom", "world"}
|
||||
@@ -458,6 +521,7 @@ func TestHasSuffixAny(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestHasSuffixAny")
|
||||
|
||||
str := "foo bar"
|
||||
suffixes := []string{"bar", "xyz", "hello"}
|
||||
notMatches := []string{"oom", "world"}
|
||||
@@ -503,6 +567,7 @@ func TestTrim(t *testing.T) {
|
||||
|
||||
assert.Equal("$ ab cd $", Trim(str1))
|
||||
assert.Equal("ab cd", Trim(str1, "$"))
|
||||
|
||||
assert.Equal("abcd", Trim("\nabcd"))
|
||||
}
|
||||
|
||||
@@ -525,19 +590,27 @@ func TestHideString(t *testing.T) {
|
||||
|
||||
assert := internal.NewAssert(t, "TestTrim")
|
||||
|
||||
str := "13242658976"
|
||||
tests := []struct {
|
||||
input string
|
||||
start int
|
||||
end int
|
||||
replacedChar string
|
||||
expected string
|
||||
}{
|
||||
{"13242658976", 0, -1, "*", "13242658976"},
|
||||
{"13242658976", 0, 0, "*", "13242658976"},
|
||||
{"13242658976", 0, 4, "*", "****2658976"},
|
||||
{"13242658976", 3, 3, "*", "13242658976"},
|
||||
{"13242658976", 3, 4, "*", "132*2658976"},
|
||||
{"13242658976", 3, 7, "*", "132****8976"},
|
||||
{"13242658976", 3, 11, "*", "132********"},
|
||||
{"13242658976", 7, 100, "*", "1324265****"},
|
||||
{"13242658976", 100, 100, "*", "13242658976"},
|
||||
}
|
||||
|
||||
assert.Equal("13242658976", HideString(str, 0, -1, "*"))
|
||||
assert.Equal("13242658976", HideString(str, 0, 0, "*"))
|
||||
assert.Equal("****2658976", HideString(str, 0, 4, "*"))
|
||||
|
||||
assert.Equal("13242658976", HideString(str, 3, 3, "*"))
|
||||
assert.Equal("132*2658976", HideString(str, 3, 4, "*"))
|
||||
assert.Equal("132****8976", HideString(str, 3, 7, "*"))
|
||||
assert.Equal("1324265****", HideString(str, 7, 11, "*"))
|
||||
|
||||
assert.Equal("1324265****", HideString(str, 7, 100, "*"))
|
||||
assert.Equal("13242658976", HideString(str, 100, 100, "*"))
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.expected, HideString(tt.input, tt.start, tt.end, tt.replacedChar))
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainsAll(t *testing.T) {
|
||||
@@ -593,23 +666,34 @@ func TestHammingDistance(t *testing.T) {
|
||||
return c
|
||||
}
|
||||
|
||||
assert.Equal(0, hd(" ", " "))
|
||||
assert.Equal(1, hd(" ", "c"))
|
||||
assert.Equal(1, hd("a", "d"))
|
||||
assert.Equal(1, hd("a", " "))
|
||||
assert.Equal(1, hd("a", "f"))
|
||||
tests := []struct {
|
||||
strA string
|
||||
strB string
|
||||
hammingDistance int
|
||||
}{
|
||||
{" ", " ", 0},
|
||||
{" ", "c", 1},
|
||||
{"a", "d", 1},
|
||||
{"a", " ", 1},
|
||||
{"a", "f", 1},
|
||||
|
||||
assert.Equal(0, hd("", ""))
|
||||
assert.Equal(-1, hd("abc", "ab"))
|
||||
assert.Equal(3, hd("abc", "def"))
|
||||
assert.Equal(-1, hd("kitten", "sitting"))
|
||||
assert.Equal(1, hd("ö", "ü"))
|
||||
assert.Equal(0, hd("日本語", "日本語"))
|
||||
assert.Equal(3, hd("日本語", "語日本"))
|
||||
{"", "", 0},
|
||||
{"abc", "ab", -1},
|
||||
{"abc", "def", 3},
|
||||
{"kitten", "sitting", -1},
|
||||
{"ö", "ü", 1},
|
||||
{"日本語", "日本語", 0},
|
||||
{"日本語", "語日本", 3},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.hammingDistance, hd(tt.strA, tt.strB))
|
||||
}
|
||||
}
|
||||
|
||||
func TestConcat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestConcat")
|
||||
|
||||
assert.Equal("", Concat(0))
|
||||
@@ -620,3 +704,152 @@ func TestConcat(t *testing.T) {
|
||||
assert.Equal("你好,世界!", Concat(0, "你好", ",", "", "世界!", ""))
|
||||
assert.Equal("Hello World!", Concat(0, "Hello", " Wo", "r", "ld!", ""))
|
||||
}
|
||||
|
||||
func TestEllipsis(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestEllipsis")
|
||||
|
||||
tests := []struct {
|
||||
input string
|
||||
length int
|
||||
expected string
|
||||
}{
|
||||
{"", 0, ""},
|
||||
{"hello world", 0, ""},
|
||||
{"hello world", -1, ""},
|
||||
{"hello world", 5, "hello..."},
|
||||
{"hello world", 11, "hello world"},
|
||||
{"你好,世界!", 2, "你好..."},
|
||||
{"😀😃😄😁😆", 3, "😀😃😄..."},
|
||||
{"This is a test.", 10, "This is a ..."},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.expected, Ellipsis(tt.input, tt.length))
|
||||
}
|
||||
}
|
||||
|
||||
func TestShuffle(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestShuffle")
|
||||
|
||||
assert.Equal("", Shuffle(""))
|
||||
assert.Equal("a", Shuffle("a"))
|
||||
|
||||
str := "hello"
|
||||
shuffledStr := Shuffle(str)
|
||||
assert.Equal(5, len(shuffledStr))
|
||||
}
|
||||
|
||||
func TestRotate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestRotate")
|
||||
|
||||
tests := []struct {
|
||||
input string
|
||||
shift int
|
||||
expected string
|
||||
}{
|
||||
{"", 1, ""},
|
||||
{"a", 0, "a"},
|
||||
{"a", 1, "a"},
|
||||
{"a", -1, "a"},
|
||||
|
||||
{"Hello", -2, "lloHe"},
|
||||
{"Hello", 1, "oHell"},
|
||||
{"Hello, world!", 3, "ld!Hello, wor"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.expected, Rotate(tt.input, tt.shift))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateReplace(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestTemplateReplace")
|
||||
|
||||
t.Run("basic", func(t *testing.T) {
|
||||
template := `Hello, my name is {name}, I'm {age} years old.`
|
||||
data := map[string]string{
|
||||
"name": "Bob",
|
||||
"age": "20",
|
||||
}
|
||||
|
||||
expected := `Hello, my name is Bob, I'm 20 years old.`
|
||||
result := TemplateReplace(template, data)
|
||||
|
||||
assert.Equal(expected, result)
|
||||
})
|
||||
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
template := `Hello, my name is {name}, I'm {age} years old.`
|
||||
data := map[string]string{
|
||||
"name": "Bob",
|
||||
}
|
||||
|
||||
expected := `Hello, my name is Bob, I'm {age} years old.`
|
||||
result := TemplateReplace(template, data)
|
||||
|
||||
assert.Equal(expected, result)
|
||||
})
|
||||
|
||||
t.Run("brackets", func(t *testing.T) {
|
||||
template := `Hello, my name is {name}, I'm {{age}} years old.`
|
||||
data := map[string]string{
|
||||
"name": "Bob",
|
||||
"age": "20",
|
||||
}
|
||||
|
||||
expected := `Hello, my name is Bob, I'm {20} years old.`
|
||||
result := TemplateReplace(template, data)
|
||||
|
||||
assert.Equal(expected, result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRegexMatchAllGroups(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestRegexMatchAllGroups")
|
||||
|
||||
tests := []struct {
|
||||
pattern string
|
||||
str string
|
||||
expected [][]string
|
||||
}{
|
||||
{
|
||||
pattern: `(\w+\.+\w+)@(\w+)\.(\w+)`,
|
||||
str: "Emails: john.doe@example.com and jane.doe@example.com",
|
||||
expected: [][]string{{"john.doe@example.com", "john.doe", "example", "com"}, {"jane.doe@example.com", "jane.doe", "example", "com"}},
|
||||
},
|
||||
{
|
||||
pattern: `(\d+)`,
|
||||
str: "No numbers here!",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
pattern: `(\d{3})-(\d{2})-(\d{4})`,
|
||||
str: "My number is 123-45-6789",
|
||||
expected: [][]string{{"123-45-6789", "123", "45", "6789"}},
|
||||
},
|
||||
{
|
||||
pattern: `(\w+)\s(\d+)`,
|
||||
str: "Item A 123, Item B 456",
|
||||
expected: [][]string{{"A 123", "A", "123"}, {"B 456", "B", "456"}},
|
||||
},
|
||||
{
|
||||
pattern: `(\d{2})-(\d{2})-(\d{4})`,
|
||||
str: "Dates: 01-01-2020, 12-31-1999",
|
||||
expected: [][]string{{"01-01-2020", "01", "01", "2020"}, {"12-31-1999", "12", "31", "1999"}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
result := RegexMatchAllGroups(tt.pattern, tt.str)
|
||||
assert.Equal(tt.expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
190
system/os.go
190
system/os.go
@@ -6,9 +6,12 @@ package system
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/duke-git/lancet/v2/validator"
|
||||
@@ -132,3 +135,190 @@ func byteToString(data []byte, charset string) string {
|
||||
func GetOsBits() int {
|
||||
return 32 << (^uint(0) >> 63)
|
||||
}
|
||||
|
||||
// StartProcess start a new process with the specified name and arguments.
|
||||
// Play: todo
|
||||
func StartProcess(command string, args ...string) (int, error) {
|
||||
cmd := exec.Command(command, args...)
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return cmd.Process.Pid, nil
|
||||
}
|
||||
|
||||
// StopProcess stop a process by pid.
|
||||
// Play: todo
|
||||
func StopProcess(pid int) error {
|
||||
process, err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return process.Signal(os.Kill)
|
||||
}
|
||||
|
||||
// KillProcess kill a process by pid.
|
||||
// Play: todo
|
||||
func KillProcess(pid int) error {
|
||||
process, err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return process.Kill()
|
||||
}
|
||||
|
||||
// ProcessInfo contains detailed information about a process.
|
||||
type ProcessInfo struct {
|
||||
PID int
|
||||
CPU string
|
||||
Memory string
|
||||
State string
|
||||
User string
|
||||
Cmd string
|
||||
Threads []string
|
||||
IOStats string
|
||||
StartTime string
|
||||
ParentPID int
|
||||
NetworkConnections string
|
||||
}
|
||||
|
||||
// GetProcessInfo retrieves detailed process information by pid.
|
||||
// Play: todo
|
||||
func GetProcessInfo(pid int) (*ProcessInfo, error) {
|
||||
var cmd *exec.Cmd
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
cmd = exec.Command("tasklist", "/FI", fmt.Sprintf("PID eq %d", pid), "/FO", "CSV", "/V")
|
||||
} else {
|
||||
cmd = exec.Command("ps", "-p", strconv.Itoa(pid), "-o", "pid,%cpu,%mem,state,user,comm")
|
||||
}
|
||||
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
processInfo, err := parseProcessInfo(output, pid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
processInfo.Threads, _ = getThreadsInfo(pid)
|
||||
processInfo.IOStats, _ = getIOStats(pid)
|
||||
processInfo.StartTime, _ = getProcessStartTime(pid)
|
||||
processInfo.ParentPID, _ = getParentProcess(pid)
|
||||
processInfo.NetworkConnections, _ = getNetworkConnections(pid)
|
||||
}
|
||||
|
||||
return processInfo, nil
|
||||
}
|
||||
|
||||
// parseProcessInfo parses the output of `ps` or `tasklist` to fill the ProcessInfo structure.
|
||||
func parseProcessInfo(output []byte, pid int) (*ProcessInfo, error) {
|
||||
lines := strings.Split(string(output), "\n")
|
||||
|
||||
if len(lines) < 2 {
|
||||
return nil, fmt.Errorf("no process found with PID %d", pid)
|
||||
}
|
||||
|
||||
var processInfo ProcessInfo
|
||||
if runtime.GOOS == "windows" {
|
||||
fields := strings.Split(lines[1], "\",\"")
|
||||
if len(fields) < 9 {
|
||||
return nil, fmt.Errorf("unexpected tasklist output format")
|
||||
}
|
||||
|
||||
processInfo = ProcessInfo{
|
||||
PID: pid,
|
||||
CPU: "N/A",
|
||||
Memory: fields[4], // Memory usage in K
|
||||
State: fields[5],
|
||||
User: "N/A",
|
||||
Cmd: fields[8],
|
||||
}
|
||||
} else {
|
||||
fields := strings.Fields(lines[1])
|
||||
if len(fields) < 6 {
|
||||
return nil, fmt.Errorf("unexpected ps output format")
|
||||
}
|
||||
|
||||
processInfo = ProcessInfo{
|
||||
PID: pid,
|
||||
CPU: fields[1],
|
||||
Memory: fields[2],
|
||||
State: fields[3],
|
||||
User: fields[4],
|
||||
Cmd: fields[5],
|
||||
}
|
||||
}
|
||||
|
||||
return &processInfo, nil
|
||||
}
|
||||
|
||||
func getThreadsInfo(pid int) ([]string, error) {
|
||||
cmd := exec.Command("ps", "-T", "-p", strconv.Itoa(pid))
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lines := strings.Split(string(output), "\n")
|
||||
|
||||
var threads []string
|
||||
for _, line := range lines[1:] {
|
||||
if strings.TrimSpace(line) != "" {
|
||||
threads = append(threads, line)
|
||||
}
|
||||
}
|
||||
|
||||
return threads, nil
|
||||
}
|
||||
|
||||
func getIOStats(pid int) (string, error) {
|
||||
filePath := fmt.Sprintf("/proc/%d/io", pid)
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func getProcessStartTime(pid int) (string, error) {
|
||||
cmd := exec.Command("ps", "-p", strconv.Itoa(pid), "-o", "lstart=")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return strings.TrimSpace(string(output)), nil
|
||||
}
|
||||
|
||||
func getParentProcess(pid int) (int, error) {
|
||||
cmd := exec.Command("ps", "-o", "ppid=", "-p", strconv.Itoa(pid))
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
ppid, err := strconv.Atoi(strings.TrimSpace(string(output)))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return ppid, nil
|
||||
}
|
||||
|
||||
func getNetworkConnections(pid int) (string, error) {
|
||||
cmd := exec.Command("lsof", "-p", strconv.Itoa(pid), "-i")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package system
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ExampleSetOsEnv() {
|
||||
err := SetOsEnv("foo", "abc")
|
||||
@@ -75,3 +78,56 @@ func ExampleGetOsBits() {
|
||||
// Output:
|
||||
// 64
|
||||
}
|
||||
|
||||
func ExampleStartProcess() {
|
||||
pid, err := StartProcess("sleep", "2")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(pid)
|
||||
}
|
||||
|
||||
func ExampleStopProcess() {
|
||||
pid, err := StartProcess("sleep", "10")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
err = StopProcess(pid)
|
||||
|
||||
fmt.Println(err)
|
||||
|
||||
// Output:
|
||||
// <nil>
|
||||
}
|
||||
|
||||
func ExampleKillProcess() {
|
||||
pid, err := StartProcess("sleep", "3")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
err = KillProcess(pid)
|
||||
|
||||
fmt.Println(err)
|
||||
|
||||
// Output:
|
||||
// <nil>
|
||||
}
|
||||
|
||||
func ExampleGetProcessInfo() {
|
||||
pid, err := StartProcess("ls", "-a")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
processInfo, err := GetProcessInfo(pid)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(processInfo)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
@@ -53,7 +55,9 @@ func TestExecCommand(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestExecCommand")
|
||||
|
||||
// linux or mac
|
||||
stdout, stderr, err := ExecCommand("ls")
|
||||
stdout, stderr, err := ExecCommand("ls", func(cmd *exec.Cmd) {
|
||||
cmd.Dir = "/"
|
||||
})
|
||||
t.Log("std out: ", stdout)
|
||||
t.Log("std err: ", stderr)
|
||||
assert.Equal("", stderr)
|
||||
@@ -74,16 +78,6 @@ func TestExecCommand(t *testing.T) {
|
||||
assert.IsNotNil(err)
|
||||
}
|
||||
|
||||
// func TestExecCommandWithOption(t *testing.T) {
|
||||
// assert := internal.NewAssert(t, "TestExecCommandWithOption")
|
||||
|
||||
// stdout, stderr, err := ExecCommand("ls", WithForeground())
|
||||
// t.Log("std out: ", stdout)
|
||||
// t.Log("std err: ", stderr)
|
||||
// assert.Equal("", stderr)
|
||||
// assert.IsNil(err)
|
||||
// }
|
||||
|
||||
func TestGetOsBits(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -95,3 +89,56 @@ func TestGetOsBits(t *testing.T) {
|
||||
t.Error("os is not 32 or 64 bits")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartProcess(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestStartProcess")
|
||||
|
||||
pid, err := StartProcess("ls", "-a")
|
||||
|
||||
assert.IsNil(err)
|
||||
assert.Equal(true, pid > 0)
|
||||
}
|
||||
|
||||
func TestKillProcess(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestKillProcess")
|
||||
|
||||
pid, err := StartProcess("ls")
|
||||
assert.IsNil(err)
|
||||
assert.Equal(true, pid > 0)
|
||||
|
||||
err = KillProcess(pid)
|
||||
assert.IsNil(err)
|
||||
}
|
||||
|
||||
func TestStopProcess(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestStopProcess")
|
||||
|
||||
pid, err := StartProcess("ls")
|
||||
assert.IsNil(err)
|
||||
assert.Equal(true, pid > 0)
|
||||
|
||||
err = StopProcess(pid)
|
||||
assert.IsNil(err)
|
||||
}
|
||||
|
||||
func TestGetProcessInfo(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestGetProcessInfo")
|
||||
|
||||
pid, err := StartProcess("ls", "-a")
|
||||
assert.IsNil(err)
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
processInfo, err := GetProcessInfo(pid)
|
||||
assert.IsNil(err)
|
||||
|
||||
t.Log(processInfo)
|
||||
}
|
||||
|
||||
@@ -214,7 +214,7 @@ func IsIpV4(ipstr string) bool {
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(ipstr, ".")
|
||||
return ip.To4() != nil
|
||||
}
|
||||
|
||||
// IsIpV6 check if the string is a ipv6 address.
|
||||
@@ -224,7 +224,7 @@ func IsIpV6(ipstr string) bool {
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(ipstr, ":")
|
||||
return ip.To4() == nil && len(ip) == net.IPv6len
|
||||
}
|
||||
|
||||
// IsPort check if the string is a valid net port.
|
||||
|
||||
Reference in New Issue
Block a user