1
0
mirror of https://github.com/duke-git/lancet.git synced 2026-02-05 05:12:26 +08:00

Compare commits

...

109 Commits

Author SHA1 Message Date
dudaodong
5d6ab72059 release v2.3.3 2024-10-09 15:20:30 +08:00
dudaodong
30eb2c72b0 doc: rename parameter name of Comma 2024-09-27 10:23:29 +08:00
dudaodong
adf18a2e47 fix: fix bug of Comma, issue #248 2024-09-27 10:18:37 +08:00
dudaodong
f99a8ef3cf refactoring: defect #247 2024-09-20 19:29:31 +08:00
Emmanuel Ferdman
fcdf1d5839 doc: update contribution references (#246)
Signed-off-by: Emmanuel Ferdman <emmanuelferdman@gmail.com>
2024-09-19 09:03:50 +08:00
dudaodong
ee0afed963 Merge branch 'v2' into rc 2024-09-16 09:10:23 +08:00
Kyden
1d94896c9b fix(strutil): rename PadStart to Pad. (#245) 2024-09-15 20:29:12 +08:00
dudaodong
69cf9bbcf0 doc: add new functions in system package 2024-09-13 16:21:57 +08:00
dudaodong
2bbcb85286 feat: add GetProcessInfo for system package 2024-09-13 16:10:09 +08:00
dudaodong
e58c9b797b feat: add StartProcess, StopProcess, KillProcess for system package 2024-09-13 15:09:44 +08:00
dudaodong
3e1ac5e0b5 test: remove unstable test item 2024-09-10 15:59:09 +08:00
dudaodong
8869e0440d doc: update doc for GetOrDefault 2024-09-10 15:54:54 +08:00
yunxuan
69f9c74bcb feat(maputil): add GetValue (#243)
* add maputil.GetValue

* rename GetOrDefault
2024-09-10 15:08:36 +08:00
dudaodong
84ebc7ce71 doc: add doc for new functions of release v2.3.3 2024-09-10 14:47:38 +08:00
dudaodong
c745097749 feat: add AesGcmEncrypt and AesGcmDecrypt in cryptor package 2024-09-10 10:37:47 +08:00
dudaodong
ba75e58e5f feat: add datetime package doc 2024-09-09 14:42:01 +08:00
dudaodong
7e85a0ed7d doc: update doc 2024-09-09 11:51:10 +08:00
dudaodong
2268a0312f doc: update doc 2024-09-09 11:49:53 +08:00
dudaodong
da84d95aa3 feat: add GenerateDatetimesBetween in datetime package 2024-09-09 11:35:17 +08:00
dudaodong
48244d6711 feat: add DaysBetween in datetime package 2024-09-09 10:55:18 +08:00
dudaodong
5e3337a52e feat: add TrackFuncTime in datetime package 2024-09-06 15:51:56 +08:00
dudaodong
c3372e18b1 feat: add Frequency in slice package 2024-09-06 15:06:35 +08:00
dudaodong
90e5a0bfb2 feat: add RegexMatchAllGroups 2024-09-06 14:19:57 +08:00
dudaodong
93be25920f feat: add TemplateReplace 2024-09-06 11:04:54 +08:00
dudaodong
f9e5ec9096 feat: add Rotate for string 2024-09-03 16:36:11 +08:00
dudaodong
601df5dc12 feat: add Rotate for string 2024-09-03 16:35:38 +08:00
dudaodong
63216d9b1c feat: add Shuffle for string 2024-09-03 15:54:05 +08:00
dudaodong
c32a19868d refactoring: make test clear 2024-09-03 15:33:32 +08:00
dudaodong
71e914019b feat: add Ellipsis for string 2024-09-03 14:36:12 +08:00
dudaodong
9824db0056 Merge branch 'rc' 2024-09-02 14:31:59 +08:00
dudaodong
ba9188a29a doc: update check link 2024-09-02 14:28:26 +08:00
dudaodong
8625fbd8d3 doc: update doc for ExecCommand function 2024-09-02 11:22:11 +08:00
dudaodong
81b29baf30 doc: add doc for OrderedMap 2024-09-02 11:19:05 +08:00
dudaodong
5a38e34063 Merge branch 'rc' of github.com:duke-git/lancet into rc 2024-08-30 16:55:10 +08:00
DerekTond
159168dd7b fix 删除文档中废弃的RetryDuration函数 (#240)
* fix 修改文档中已经删除的函数RetryDuration,替换为RetryWithLinearBackoff

* fix 删除主文档中的废弃函数

---------

Co-authored-by: dongyue16 <dongyue16@tal.com>
2024-08-29 19:37:42 +08:00
dudaodong
ec092a009a test: we should write clean unit test code 2024-08-28 16:06:08 +08:00
dudaodong
ca40b5d6c6 refactoring: rename SortByKeys to SortByKey 2024-08-28 10:58:14 +08:00
dudaodong
a6d39a3bba feat: add OrderedMap for issue #237 2024-08-28 10:53:53 +08:00
dudaodong
38148978cf refactoring: change unit variable to package internal 2024-08-26 16:25:57 +08:00
dudaodong
3e8c3bd396 feat: add SortbyKeys for map 2024-08-23 11:21:29 +08:00
dudaodong
30971c1aab doc: rename CONTRIBUTION.md and add github start chart 2024-08-21 10:28:26 +08:00
dudaodong
bc260277bc fix: fix DecimalBytes and BinaryBytes issue #232 2024-08-19 11:36:49 +08:00
dudaodong
c0b200f846 feat: add ReduceConcurrent 2024-08-15 17:48:26 +08:00
dudaodong
305847993c feat: add ForEachConcurrent 2024-08-15 16:44:22 +08:00
dudaodong
f5d70728c3 refactoring: rename param and change its order 2024-08-15 15:50:48 +08:00
残念
c2a5335bc6 feat: add RandSliceFromGivenSlice function (#236) 2024-08-15 15:20:36 +08:00
残念
7b4e060f85 feat: add RandFromGivenSlice function (#235)
* feat: add RandFromGivenSlice function

* feat: add RandFromGivenSlice function
2024-08-14 19:36:12 +08:00
dudaodong
a360372aa9 feat: add FilterConcurrent 2024-08-14 11:19:10 +08:00
dudaodong
7f78a6b11e refactoring: rename slice_parallel to slice_concurrent 2024-08-14 10:52:36 +08:00
dudaodong
5c53cb5867 feat: add MapConcurrent 2024-08-14 10:45:35 +08:00
dudaodong
f7e9d5dc47 doc: add doc for UniqueByComparator and UniqueByParallel 2024-08-13 11:00:47 +08:00
dudaodong
5c580ed013 test: update test for Schedule 2024-08-09 15:00:19 +08:00
dudaodong
0f9764f41e feat: add Throttle function 2024-08-09 14:28:15 +08:00
dudaodong
0bc5f82554 doc: add RandBool, RandBoolSlice 2024-08-09 11:47:36 +08:00
dudaodong
e91965b013 feat: add RandStringSlice, RandBool, RandBoolSlice 2024-08-09 11:44:56 +08:00
dudaodong
483a286d8e feat: add RandIntSlice function 2024-08-09 10:46:08 +08:00
dudaodong
3f8e306ced doc: update deprecated warning text 2024-08-08 15:44:19 +08:00
dudaodong
0b29f0520d feat: add Debounce function 2024-08-08 15:19:38 +08:00
dudaodong
8611ec0c10 feat: add UniqueByComparator 2024-08-08 11:23:11 +08:00
dudaodong
286e10d189 refactoring: memory optimization for unique slice 2024-08-08 10:51:33 +08:00
dudaodong
3e7f94b03e fix: fix UniqueBy bug 2024-08-08 10:40:23 +08:00
dudaodong
356351896d feat: add UniqueByParallel for slice 2024-08-08 10:20:52 +08:00
dudaodong
9be124211e refact: preallocate in Merge map 2024-08-01 11:00:24 +08:00
dudaodong
f467658481 Merge branch 'rc' into v2 2024-07-31 10:50:29 +08:00
zyfx
5c9d0e396e Update condition.go (#234) 2024-07-31 10:16:03 +08:00
dudaodong
8f74460c1b fix: remove scientific notation in DecimalBytes 2024-07-30 14:17:17 +08:00
dudaodong
d5752499bf fix: remove scientific notation in DecimalBytes 2024-07-29 11:42:56 +08:00
dudaodong
eb7cf76eae doc: fix doc error 2024-07-18 11:44:00 +08:00
dudaodong
4af074d181 doc: add play ground demo 2024-07-18 11:34:16 +08:00
dudaodong
73fb8fefd2 release v2.3.2 2024-07-18 10:52:54 +08:00
dudaodong
9cf535055d doc: update readme file 2024-07-18 10:50:29 +08:00
dudaodong
8be7b3e396 rename UnwrapOr 2024-07-18 10:12:16 +08:00
梧桐
dac706d700 🐛 Fixing a bug. about pointer package function (#230)
update:
1. UnwarpOr
2. UnwarpOrDefault
3. ExtractPointer

add:
1. UnwrapOr
2. IsNil
2024-07-18 10:05:07 +08:00
dudaodong
2097277a7d doc: add doc for UniqueByField 2024-06-25 15:20:03 +08:00
dudaodong
95b516e278 feat: add UniqueByField 2024-06-24 19:36:02 +08:00
dudaodong
ca373b00a7 feat: add GetOrSet 2024-06-24 17:29:12 +08:00
dudaodong
a220220f09 feat: add GetOrSet 2024-06-24 17:28:51 +08:00
dudaodong
aeef0418a4 fix: fix bug of CopyDir 2024-06-24 17:09:31 +08:00
dudaodong
9b7d8d7abf doc: update vitepress version 2024-06-11 10:06:33 +08:00
dudaodong
4d21e81263 feat: add Timeout config for http client 2024-06-04 16:28:00 +08:00
残念
ce2397422e perf(slice): make a clearer panic description (#223) 2024-05-30 16:55:30 +08:00
Cannian
4b3a62b36a perf(validator): check Ipv4、Ipv6 by more graceful method (#220) 2024-05-25 08:58:33 +08:00
dudaodong
e054680d20 doc: ignoreDeadLinks in doc 2024-05-14 11:42:48 +08:00
dudaodong
5381842eec doc: update doc for v2.3.1 2024-05-14 11:26:14 +08:00
dudaodong
6e0498514c doc: update doc for v2.3.1 2024-05-14 11:25:01 +08:00
dudaodong
967e6a3493 doc: update doc for v2.3.1 2024-05-14 10:47:27 +08:00
dudaodong
5b24801e49 merge qa branch 2024-05-14 10:11:29 +08:00
dudaodong
974ba525a6 Merge branch 'rc' into v2 2024-05-14 10:10:45 +08:00
chentong
f0235c40b6 fix: json tag omitempty convert error (#218) 2024-05-14 10:08:56 +08:00
dudaodong
712a215ea6 reset 2024-05-14 10:02:56 +08:00
dudaodong
7893f828d3 fix: fix get tag failed 2024-05-13 17:49:34 +08:00
Yang Li
53fa210f09 refactor slice.Unique() (#215) 2024-05-09 10:43:59 +08:00
dudaodong
de9ee08be4 test: update net error handle 2024-04-18 14:23:36 +08:00
dudaodong
5381450bea feat: fix email validation failed 2024-04-18 14:18:33 +08:00
Cannian
6853d627f4 refactor(slice): optimize function (#211) 2024-04-06 09:16:28 +08:00
Joker-desire
e461acdb72 fix(netutil): Add proxy IP to send request (#210)
* fix(netutil): Add proxy ip to send request

* fix(netutil): Add proxy IP to send request

---------

Co-authored-by: 杨崟 <yangyin@addcn.com>
2024-04-03 16:52:53 +08:00
dudaodong
2a796adf85 fix: support nest struct in StructToUrlValues 2024-04-02 17:38:40 +08:00
Cannian
5e6e8d82a8 feat(maputil): add ToSortedSlicesDefault method and ToSortedSlicesWithComparator method (#206) 2024-03-31 21:04:55 +08:00
Cannian
e9280b8c25 add Concat method (#204)
* feat(strutil): add Concat method

* feat(strutil): add Concat method
2024-03-25 10:26:37 +08:00
dudaodong
bb6f10a1fb rename CONTRIBUTING file 2024-03-17 10:30:24 +08:00
dudaodong
33b4cffe60 rename CONTRIBUTING file 2024-03-17 10:30:07 +08:00
Cannian
2b765b49e0 refactor(set): pop method randomly removes an element and return (#202) 2024-03-17 10:28:25 +08:00
dudaodong
004dbdc32e doc: update doc for RightPadding and LeftPadding 2024-03-12 09:52:02 +08:00
donutloop
ab50e8120a Slice: padding (#201)
* LeftPadding adds padding to the left begin of a slice.
* RightPadding adds padding to the right end of a slice.
2024-03-10 21:24:17 +08:00
dudaodong
73c97af7d8 doc: update doc for Break function 2024-03-08 21:39:06 +08:00
donutloop
5e8a065eaa Slice: break (#200)
Splits a slice into two based on a predicate function. It starts appending to the second slice after the first element that matches the predicate. All elements after the first match are included in the second slice, regardless of whether they match the predicate or not.
2024-03-07 14:11:19 +08:00
dudaodong
aa74400607 doc: fix doc text error 2024-03-06 16:19:20 +08:00
dudaodong
a6eaaef563 doc: add go playground demo 2024-03-06 15:28:55 +08:00
dudaodong
1b31014f81 doc: add go playground demo 2024-03-06 14:52:35 +08:00
94 changed files with 10978 additions and 1773 deletions

View File

@@ -1,4 +1,4 @@
# Lancet Contributing Guide
# Lancet Contribution Guide
Hi! Thank you for choosing Lancet.

234
README.md
View File

@@ -4,7 +4,7 @@
<br/>
![Go version](https://img.shields.io/badge/go-%3E%3Dv1.18-9cf)
[![Release](https://img.shields.io/badge/release-2.3.0-green.svg)](https://github.com/duke-git/lancet/releases)
[![Release](https://img.shields.io/badge/release-2.3.3-green.svg)](https://github.com/duke-git/lancet/releases)
[![GoDoc](https://godoc.org/github.com/duke-git/lancet/v2?status.svg)](https://pkg.go.dev/github.com/duke-git/lancet/v2)
[![Go Report Card](https://goreportcard.com/badge/github.com/duke-git/lancet/v2)](https://goreportcard.com/report/github.com/duke-git/lancet/v2)
[![test](https://github.com/duke-git/lancet/actions/workflows/codecov.yml/badge.svg?branch=main&event=push)](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. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
@@ -660,9 +676,12 @@ import "github.com/duke-git/lancet/v2/fileutil"
- **<big>CreateDir</big>** : create directory in absolute path.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#CreateDir)]
[[play](https://go.dev/play/p/qUuCe1OGQnM)]
- **<big>CopyFile</big>** :copy src file to dest file.
- **<big>CopyFile</big>** : copy src file to dest file.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#CopyFile)]
[[play](https://go.dev/play/p/Jg9AMJMLrJi)]
- **<big>CopyDir</big>** : copy src directory to dest directory.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#CopyDir)]
[[play](https://go.dev/play/p/YAyFTA_UuPb)]
- **<big>FileMode</big>** : return file's mode and permission.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#FileMode)]
[[play](https://go.dev/play/p/2l2hI42fA3p)]
@@ -731,8 +750,10 @@ import "github.com/duke-git/lancet/v2/fileutil"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#ReadFile)]
- **<big>ChunkRead</big>** : reads a block from the file at the specified offset and returns all lines within the block.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#ChunkRead)]
[[play](https://go.dev/play/p/r0hPmKWhsgf)]
- **<big>ParallelChunkRead</big>** : reads the file in parallel and send each chunk of lines to the specified channel.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#ParallelChunkRead)]
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#ParallelChunkRead)]
[[play](https://go.dev/play/p/teMXnCsdSEw)]
<h3 id="formatter"> 10. Formatter contains some functions for data formatting. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
@@ -742,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.
@@ -787,28 +808,42 @@ 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)]
- **<big>Pipeline</big>** : takes a list of functions and returns a function whose param will be passed into the functions one by one.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/function.md#Pipeline)]
[[play](https://go.dev/play/p/mPdUVvj6HD6)]
- **<big>AcceptIf</big>** : returns another function of the same signature as the apply function but also includes a bool value to indicate success or failure.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/function.md#AcceptIf)]
[[play](https://go.dev/play/p/XlXHHtzCf7d)]
- **<big>And</big>** : returns a composed predicate that represents the logical AND of a list of predicates.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/function.md#And)]
[[play](https://go.dev/play/p/dTBHJMQ0zD2)]
- **<big>Or</big>** : returns a composed predicate that represents the logical OR of a list of predicates.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/function.md#Or)]
[[play](https://go.dev/play/p/LitCIsDFNDA)]
- **<big>Negate</big>** : returns a predicate that represents the logical negation of this predicate.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/function.md#Negate)]
[[play](https://go.dev/play/p/jbI8BtgFnVE)]
- **<big>Nor</big>** : returns a composed predicate that represents the logical NOR of a list of predicates.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/function.md#Nor)]
[[play](https://go.dev/play/p/2KdCoBEOq84)]
- **<big>Nand</big>** : returns a composed predicate that represents the logical Nand of a list of predicates.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/function.md#Nand)]
[[play](https://go.dev/play/p/Rb-FdNGpgSO)]
- **<big>Xnor</big>** : returns a composed predicate that represents the logical XNOR of a list of predicates.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/function.md#Xnor)]
[[play](https://go.dev/play/p/FJxko8SFbqc)]
- **<big>Watcher</big>** : Watcher is used for record code execution time. can start/stop/reset the watch timer. get the elapsed time of function execution.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/function.md#Watcher)]
[[play](https://go.dev/play/p/l2yrOpCLd1I)]
@@ -888,6 +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)]
@@ -912,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. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
@@ -959,12 +1067,16 @@ import "github.com/duke-git/lancet/v2/mathutil"
[[play](https://go.dev/play/p/aumarSHIGzP)]
- **<big>CeilToFloat</big>** : round float up n decimal places.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/mathutil.md#CeilToFloat)]
[[play](https://go.dev/play/p/8hOeSADZPCo)]
- **<big>CeilToString</big>** : round float up n decimal places, then conver to string.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/mathutil.md#CeilToString)]
[[play](https://go.dev/play/p/wy5bYEyUKKG)]
- **<big>FloorToFloat</big>** : round float down n decimal places.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/mathutil.md#FloorToFloat)]
[[play](https://go.dev/play/p/vbCBrQHZEED)]
- **<big>FloorToString</big>** : round float down n decimal places, then conver to string.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/mathutil.md#FloorToString)]
[[play](https://go.dev/play/p/Qk9KPd2IdDb)]
- **<big>Range</big>** : Creates a slice of numbers from start with specified count, element step is 1.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/mathutil.md#Range)]
[[play](https://go.dev/play/p/9ke2opxa8ZP)]
@@ -1006,6 +1118,7 @@ import "github.com/duke-git/lancet/v2/mathutil"
[[play](https://go.dev/play/p/fsyBh1Os-1d)]
- **<big>Div</big>** : returns the result of x divided by y.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/mathutil.md#Div)]
[[play](https://go.dev/play/p/WLxDdGXXYat)]
<h3 id="netutil"> 14. Netutil package contains functions to get net information and send http request. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
@@ -1098,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.
@@ -1137,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. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
@@ -1180,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. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
@@ -1291,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)]
@@ -1318,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)]
@@ -1333,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)]
@@ -1384,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)]
@@ -1413,6 +1562,18 @@ import "github.com/duke-git/lancet/v2/slice"
[[play](https://go.dev/play/p/UzpGQptWppw)]
- **<big>SetToDefaultIf</big>** : set elements to their default value if they match the given predicate.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#SetToDefaultIf)]
[[play](https://go.dev/play/p/9AXGlPRC0-A)]
- **<big>Break</big>** : breaks a list into two parts at the point where the predicate for the first time is true.
[[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. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
@@ -1447,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)]
@@ -1660,8 +1824,25 @@ import "github.com/duke-git/lancet/v2/strutil"
[[play](https://go.dev/play/p/HzLC9vsTwkf)]
- **<big>SubInBetween</big>** : return substring between the start and end position(excluded) of source string.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/strutil.md#SubInBetween)]
[[play](https://go.dev/play/p/EDbaRvjeNsv)]
- **<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. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
@@ -1698,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. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
@@ -2001,11 +2195,15 @@ import "github.com/duke-git/lancet/v2/xerror"
## How to Contribute
#### [Contributing Guide](./CONTRIBUTING.en-US.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
[![Star History Chart](https://api.star-history.com/svg?repos=duke-git/lancet&type=Date)](https://star-history.com/#duke-git/lancet&Date)

View File

@@ -4,7 +4,7 @@
<br/>
![Go version](https://img.shields.io/badge/go-%3E%3Dv1.18-9cf)
[![Release](https://img.shields.io/badge/release-2.3.0-green.svg)](https://github.com/duke-git/lancet/releases)
[![Release](https://img.shields.io/badge/release-2.3.3-green.svg)](https://github.com/duke-git/lancet/releases)
[![GoDoc](https://godoc.org/github.com/duke-git/lancet/v2?status.svg)](https://pkg.go.dev/github.com/duke-git/lancet/v2)
[![Go Report Card](https://goreportcard.com/badge/github.com/duke-git/lancet/v2)](https://goreportcard.com/report/github.com/duke-git/lancet/v2)
[![test](https://github.com/duke-git/lancet/actions/workflows/codecov.yml/badge.svg?branch=main&event=push)](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 日期时间处理包,格式化日期,比较日期。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="datetime"> 7. datetime日期时间处理包格式化日期比较日期。&nbsp; &nbsp; &nbsp; &nbsp;<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。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
@@ -659,9 +675,12 @@ import "github.com/duke-git/lancet/v2/fileutil"
- **<big>CreateDir</big>** : 创建嵌套目录,例如/a/, /a/b/。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#CreateDir)]
[[play](https://go.dev/play/p/qUuCe1OGQnM)]
- **<big>CopyFile</big>** :拷贝文件,会覆盖原有的文件。
- **<big>CopyFile</big>** : 拷贝文件,会覆盖原有的文件。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#CopyFile)]
[[play](https://go.dev/play/p/Jg9AMJMLrJi)]
- **<big>CopyDir</big>** : 拷贝目录。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#CopyDir)]
[[play](https://go.dev/play/p/YAyFTA_UuPb)]
- **<big>FileMode</big>** : 获取文件 mode 信息。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#FileMode)]
[[play](https://go.dev/play/p/2l2hI42fA3p)]
@@ -730,8 +749,10 @@ import "github.com/duke-git/lancet/v2/fileutil"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#ReadFile)]
- **<big>ChunkRead</big>** : 从文件的指定偏移读取块并返回块内所有行。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#ChunkRead)]
[[play](https://go.dev/play/p/r0hPmKWhsgf)]
- **<big>ParallelChunkRead</big>** : 并行读取文件并将每个块的行发送到指定通道。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#ParallelChunkRead)]
[[play](https://go.dev/play/p/teMXnCsdSEw)]
@@ -743,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 字符串。
@@ -788,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)]
@@ -799,22 +826,31 @@ import "github.com/duke-git/lancet/v2/function"
[[play](https://go.dev/play/p/mPdUVvj6HD6)]
- **<big>AcceptIf</big>** : AcceptIf函数会返回另一个函数该函数的签名与apply函数相同但同时还会包含一个布尔值来表示成功或失败。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/function.md#AcceptIf)]
[[play](https://go.dev/play/p/XlXHHtzCf7d)]
- **<big>And</big>** : 返回一个复合谓词判断函数该判断函数表示一组谓词的逻辑and操作。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/function.md#And)]
[[play](https://go.dev/play/p/dTBHJMQ0zD2)]
- **<big>Or</big>** : 返回一个复合谓词判断函数该判断函数表示一组谓词的逻辑or操作。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/function.md#Or)]
[[play](https://go.dev/play/p/LitCIsDFNDA)]
- **<big>Negate</big>** : 返回一个谓词函数,该谓词函数表示当前谓词的逻辑否定。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/function.md#Negate)]
[[play](https://go.dev/play/p/jbI8BtgFnVE)]
- **<big>Nor</big>** : 返回一个复合谓词判断函数该判断函数表示一组谓词的逻辑非或nor的操作。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/function.md#Nor)]
[[play](https://go.dev/play/p/2KdCoBEOq84)]
- **<big>Nand</big>** : 返回一个复合谓词判断函数该判断函数表示一组谓词的逻辑非与nand的操作。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/function.md#Nand)]
[[play](https://go.dev/play/p/Rb-FdNGpgSO)]
- **<big>Xnor</big>** : 返回一个复合谓词判断函数该判断函数表示一组谓词的逻辑异或xnor的操作。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/function.md#Xnor)]
[[play](https://go.dev/play/p/FJxko8SFbqc)]
- **<big>Watcher</big>** : Watcher 用于记录代码执行时间。可以启动/停止/重置手表定时器。获取函数执行的时间。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/function.md#Watcher)]
[[play](https://go.dev/play/p/l2yrOpCLd1I)]
<h3 id="maputil"> 12. maputil 包括一些操作 map 的函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
@@ -889,6 +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)]
@@ -913,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 包实现了一些数学计算的函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
@@ -960,12 +1068,16 @@ import "github.com/duke-git/lancet/v2/mathutil"
[[play](https://go.dev/play/p/aumarSHIGzP)]
- **<big>CeilToFloat</big>** : 向上舍入进一法保留n位小数。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/mathutil.md#CeilToFloat)]
[[play](https://go.dev/play/p/8hOeSADZPCo)]
- **<big>CeilToString</big>** : 向上舍入进一法保留n位小数返回字符串。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/mathutil.md#CeilToString)]
[[play](https://go.dev/play/p/wy5bYEyUKKG)]
- **<big>FloorToFloat</big>** : 向下舍入去尾法保留n位小数。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/mathutil.md#FloorToFloat)]
[[play](https://go.dev/play/p/vbCBrQHZEED)]
- **<big>FloorToString</big>** : 向下舍入去尾法保留n位小数返回字符串。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/mathutil.md#FloorToString)]
[[play](https://go.dev/play/p/Qk9KPd2IdDb)]
- **<big>Range</big>** : 根据指定的起始值和数量,创建一个数字切片。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/mathutil.md#Range)]
[[play](https://go.dev/play/p/9ke2opxa8ZP)]
@@ -1007,7 +1119,7 @@ import "github.com/duke-git/lancet/v2/mathutil"
[[play](https://go.dev/play/p/fsyBh1Os-1d)]
- **<big>Div</big>** : 除法运算。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/mathutil.md#Div)]
[[play](https://go.dev/play/p/WLxDdGXXYat)]
<h3 id="netutil"> 14. netutil 网络包支持获取 ip 地址,发送 http 请求。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
@@ -1139,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>** : 生成给定长度的随机符号字符串。
@@ -1151,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。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
@@ -1170,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)]
@@ -1180,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)]
@@ -1288,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)]
@@ -1318,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)]
@@ -1333,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)]
@@ -1384,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)]
@@ -1412,7 +1560,18 @@ import "github.com/duke-git/lancet/v2/slice"
[[play](https://go.dev/play/p/UzpGQptWppw)]
- **<big>SetToDefaultIf</big>** : 根据给定给定的predicate判定函数来修改切片中的元素。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#SetToDefaultIf)]
[[play](https://go.dev/play/p/9AXGlPRC0-A)]
- **<big>Break</big>** : 根据判断函数将切片分成两部分。它开始附加到与函数匹配的第一个元素之后的第二个切片。第一个匹配之后的所有元素都包含在第二个切片中,无论它们是否与函数匹配。
[[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 实现,功能有限。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
@@ -1441,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)]
@@ -1502,6 +1664,7 @@ import "github.com/duke-git/lancet/v2/stream"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/stream.md#ToSlice)]
[[play](https://go.dev/play/p/jI6_iZZuVFE)]
<h3 id="structs"> 20. structs 提供操作 struct, tag, field 的相关函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
@@ -1663,8 +1826,25 @@ import "github.com/duke-git/lancet/v2/strutil"
[[play](https://go.dev/play/p/HzLC9vsTwkf)]
- **<big>SubInBetween</big>** : 获取字符串中指定的起始字符串start和终止字符串end直接的子字符串。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/strutil.md#SubInBetween)]
[[play](https://go.dev/play/p/EDbaRvjeNsv)]
- **<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 的相关函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
@@ -1701,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 包实现一个元组数据类型。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
@@ -1936,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)]
@@ -2004,7 +2198,7 @@ import "github.com/duke-git/lancet/v2/xerror"
## 如何贡献代码
#### [贡献代码指南](./CONTRIBUTING.zh-CN.md)
#### [代码贡献指南](./CONTRIBUTION.zh-CN.md)
## 贡献者
@@ -2013,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
[![Star History Chart](https://api.star-history.com/svg?repos=duke-git/lancet&type=Date)](https://star-history.com/#duke-git/lancet&Date)

View File

@@ -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.

View File

@@ -397,6 +397,7 @@ func GbkToUtf8(bs []byte) ([]byte, error) {
}
// ToStdBase64 convert data to standard base64 encoding.
// Play: https://go.dev/play/p/_fLJqJD3NMo
func ToStdBase64(value any) string {
if value == nil || (reflect.ValueOf(value).Kind() == reflect.Ptr && reflect.ValueOf(value).IsNil()) {
return ""
@@ -418,6 +419,7 @@ func ToStdBase64(value any) string {
}
// ToUrlBase64 convert data to URL base64 encoding.
// Play: https://go.dev/play/p/C_d0GlvEeUR
func ToUrlBase64(value any) string {
if value == nil || (reflect.ValueOf(value).Kind() == reflect.Ptr && reflect.ValueOf(value).IsNil()) {
return ""
@@ -439,6 +441,7 @@ func ToUrlBase64(value any) string {
}
// ToRawStdBase64 convert data to raw standard base64 encoding.
// Play: https://go.dev/play/p/wSAr3sfkDcv
func ToRawStdBase64(value any) string {
if value == nil || (reflect.ValueOf(value).Kind() == reflect.Ptr && reflect.ValueOf(value).IsNil()) {
return ""
@@ -460,6 +463,7 @@ func ToRawStdBase64(value any) string {
}
// ToRawUrlBase64 convert data to raw URL base64 encoding.
// Play: https://go.dev/play/p/HwdDPFcza1O
func ToRawUrlBase64(value any) string {
if value == nil || (reflect.ValueOf(value).Kind() == reflect.Ptr && reflect.ValueOf(value).IsNil()) {
return ""

View File

@@ -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

View File

@@ -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"

View File

@@ -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))
}

View File

@@ -187,10 +187,11 @@ func (s Set[T]) EachWithBreak(iteratee func(item T) bool) {
// Pop delete the top element of set then return it, if set is empty, return nil-value of T and false.
func (s Set[T]) Pop() (v T, ok bool) {
if len(s) > 0 {
items := s.Values()
item := items[len(s)-1]
delete(s, item)
return item, true
for item := range s {
v = item
delete(s, item)
return v, true
}
}
return v, false

View File

@@ -243,25 +243,34 @@ func TestEachWithBreak(t *testing.T) {
// assert.Equal(6, sum)
}
// func TestPop(t *testing.T) {
// assert := internal.NewAssert(t, "TestPop")
func TestPop(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestSet_Pop")
// s := New[int]()
s := New[int]()
// val, ok := s.Pop()
// assert.Equal(0, val)
// assert.Equal(false, ok)
val, ok := s.Pop()
assert.Equal(0, val)
assert.Equal(false, ok)
// s.Add(1)
// s.Add(2)
// s.Add(3)
s = New(1, 2, 3, 4, 5)
sl := s.ToSlice()
// // s = New(1, 2, 3, 4, 5)
val, ok = s.Pop()
assert.Equal(false, s.Contain(val))
assert.Equal(true, ok)
assert.Equal(len(sl)-1, s.Size())
// val, ok = s.Pop()
// assert.Equal(3, val)
// assert.Equal(true, ok)
// }
var found bool
for _, v := range sl {
if v == val {
found = true
}
}
assert.Equal(true, found)
}
func TestSet_ToSlice(t *testing.T) {
t.Parallel()

View File

@@ -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
}

View File

@@ -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>
}

View File

@@ -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")
}
})
}

View File

@@ -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>',
},
},
})

View File

@@ -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',
},
],
},

View File

@@ -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',
},
],
},

View File

@@ -891,7 +891,7 @@ func main() {
func ToStdBase64(value any) string
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/_fLJqJD3NMo)</span></b>
```go
package main
@@ -963,7 +963,7 @@ func main() {
func ToUrlBase64(value any) string
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/C_d0GlvEeUR)</span></b>
```go
package main
@@ -1032,7 +1032,7 @@ func main() {
func ToRawStdBase64(value any) string
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/wSAr3sfkDcv)</span></b>
```go
package main
@@ -1064,7 +1064,7 @@ func main() {
fmt.Println(afterEncode)
floatVal := 123.456
afterEncode = convertor.ToRawStdBase64(floatVal)
afterEncode := convertor.ToRawStdBase64(floatVal)
fmt.Println(afterEncode)
boolVal := true
@@ -1090,7 +1090,7 @@ func main() {
<p>值转换为 ToRawUrlBase64 编码的字符串。error 类型的数据也会把 error 的原因进行编码,复杂的结构会转为 JSON 格式的字符串</p>
<b>函数签名:</b>
<b>函数签名:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/HwdDPFcza1O)</span></b>
```go
func ToRawUrlBase64(value any) string
@@ -1109,7 +1109,7 @@ import (
func main() {
stringVal := "hello"
afterEncode = convertor.ToRawUrlBase64(stringVal)
afterEncode := convertor.ToRawUrlBase64(stringVal)
fmt.Println(afterEncode)
byteSliceVal := []byte("hello")
@@ -1132,11 +1132,11 @@ func main() {
fmt.Println(afterEncode)
boolVal := true
afterEncode = convertor.ToRawStdBase64(boolVal)
afterEncode = convertor.ToRawUrlBase64(boolVal)
fmt.Println(afterEncode)
errVal := errors.New("err")
afterEncode = convertor.ToRawStdBase64(errVal)
afterEncode = convertor.ToRawUrlBase64(errVal)
fmt.Println(afterEncode)
// Output:

View File

@@ -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>

View File

@@ -31,6 +31,7 @@ import (
- [Iterate](#Iterate)
- [Keys](#Keys)
- [Values](#Values)
- [FilterByValue](#FilterByValue)
<div STYLE="page-break-after: always;"></div>
@@ -276,7 +277,7 @@ func main() {
### <span id="Values">Values</span>
<p>返回hashmap所有值的切片 (随机顺序).</p>
<p>返回hashmap所有值的切片 (随机顺序)</p>
<b>函数签名:</b>
@@ -306,3 +307,40 @@ func main() {
```
### <span id="FilterByValue">FilterByValue</span>
<p>返回一个过滤后的HashMap。 如果任何值与 perdicate 函数不匹配,则返回 nil否则返回包含选定值的 HashMap。</p>
<b>函数签名:</b>
```go
func (hm *HashMap) FilterByValue(perdicate func(value any) bool) *HashMap
```
<b>示例:</b>
```go
package main
import (
"fmt"
hashmap "github.com/duke-git/lancet/v2/datastructure/hashmap"
)
func main() {
hm := hashmap.NewHashMap()
hm.Put("a", 1)
hm.Put("b", 2)
hm.Put("c", 3)
hm.Put("d", 4)
hm.Put("e", 5)
hm.Put("f", 6)
filteredHM := hm.FilterByValue(func(value any) bool {
return value.(int) == 1 || value.(int) == 3
})
fmt.Println(filteredHM.Size()) //2
}
```

View File

@@ -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>

View File

@@ -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>
}
```

View File

@@ -976,7 +976,7 @@ func main() {
func ChunkRead(file *os.File, offset int64, size int, bufPool *sync.Pool) ([]string, error)
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/r0hPmKWhsgf)</span></b>
```go
package main
@@ -1035,7 +1035,7 @@ func main() {
func ParallelChunkRead(filePath string, linesCh chan<- []string, chunkSizeMB, maxGoroutine int) error
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/teMXnCsdSEw)</span></b>
```go
package main

View File

@@ -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>

View File

@@ -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>
@@ -424,7 +475,7 @@ func longRunningTask() {
func And[T any](predicates ...func(T) bool) func(T) bool
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/dTBHJMQ0zD2)</span></b>
```go
package main
@@ -461,7 +512,7 @@ func main() {
func Or[T any](predicates ...func(T) bool) func(T) bool
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/LitCIsDFNDA)</span></b>
```go
package main
@@ -496,7 +547,7 @@ func main() {
func Negate[T any](predicate func(T) bool) func(T) bool
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/jbI8BtgFnVE)</span></b>
```go
package main
@@ -536,7 +587,7 @@ func main() {
func Nor[T any](predicates ...func(T) bool) func(T) bool
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/2KdCoBEOq84)</span></b>
```go
package main
@@ -578,7 +629,7 @@ func main() {
func Nand[T any](predicates ...func(T) bool) func(T) bool
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/Rb-FdNGpgSO)</span></b>
```go
package main
@@ -615,7 +666,7 @@ func main() {
func Xnor[T any](predicates ...func(T) bool) func(T) bool
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/FJxko8SFbqc)</span></b>
```go
package main
@@ -652,7 +703,7 @@ func main() {
func AcceptIf[T any](predicate func(T) bool, apply func(T) T) func(T) (T, bool)
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/XlXHHtzCf7d)</span></b>
```go
package main
@@ -663,9 +714,8 @@ import (
)
func main() {
adder := AcceptIf(
And(
adder := function.AcceptIf(
function.And(
func(x int) bool {
return x > 10
}, func(x int) bool {
@@ -691,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

View File

@@ -508,7 +508,7 @@ func main() {
func CeilToFloat[T constraints.Float | constraints.Integer](x T, n int) float64
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/8hOeSADZPCo)</span></b>
```go
package main
@@ -544,7 +544,7 @@ func main() {
func CeilToString[T constraints.Float | constraints.Integer](x T, n int) string
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/wy5bYEyUKKG)</span></b>
```go
package main
@@ -577,10 +577,10 @@ func main() {
<b>函数签名:</b>
```go
func CeilToString[T constraints.Float | constraints.Integer](x T, n int) string
func FloorToFloat[T constraints.Float | constraints.Integer](x T, n int) float64
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/vbCBrQHZEED)</span></b>
```go
package main
@@ -613,10 +613,10 @@ func main() {
<b>函数签名:</b>
```go
func CeilToString[T constraints.Float | constraints.Integer](x T, n int) string
func FloorToString[T constraints.Float | constraints.Integer](x T, n int) string
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/Qk9KPd2IdDb)</span></b>
```go
package main
@@ -1130,7 +1130,7 @@ func main() {
### <span id="Div">Div</span>
<p>除法运算.</p>
<p>除法运算</p>
<b>函数签名:</b>
@@ -1138,7 +1138,7 @@ func main() {
func Div[T constraints.Float | constraints.Integer](x T, y T) float64
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/WLxDdGXXYat)</span></b>
```go
package main
@@ -1156,6 +1156,7 @@ func main() {
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
// Output:
// 2.25
// 0.5

View File

@@ -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>

View File

@@ -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)

View File

@@ -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)
}
```

View File

@@ -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

View File

@@ -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)
@@ -94,6 +102,10 @@ import (
- [Join](#Join)
- [Partition](#Partition)
- [SetToDefaultIf](#SetToDefaultIf)
- [Break](#Break)
- [RightPadding](#RightPadding)
- [LeftPadding](#LeftPadding)
- [Frequency](#Frequency)
<div STYLE="page-break-after: always;"></div>
@@ -319,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>
@@ -893,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
@@ -965,10 +1013,12 @@ func main() {
}
```
### <span id="FindLast">FindLast(废弃:使用 FindLastBy)</span>
### <span id="FindLast">FindLast</span>
<p>遍历slice的元素返回最后一个通过predicate函数真值测试的元素。</p>
> ⚠️ 本函数已弃用,使用`FindLastBy`代替。
<b>函数签名:</b>
```go
@@ -1132,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>
@@ -1240,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
@@ -1269,10 +1358,12 @@ func main() {
}
```
### <span id="InterfaceSlice">InterfaceSlice(已弃用: 使用 go1.18+泛型代替)</span>
### <span id="InterfaceSlice">InterfaceSlice</span>
<p>将值转换为接口切片</p>
> ⚠️ 本函数已弃用使用go1.18+泛型代替。
<b>函数签名:</b>
```go
@@ -1437,7 +1528,7 @@ func main() {
### <span id="Map">Map</span>
<p>对slice中的每个元素执行map函数以创建一个新切片</p>
<p>对slice中的每个元素执行map函数以创建一个新切片</p>
<b>函数签名:</b>
@@ -1469,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>
@@ -1543,6 +1666,8 @@ func main() {
<p>合并多个切片(不会消除重复元素).</p>
> ⚠️ 本函数已弃用,使用`Concat`代替。
<b>函数签名:</b>
```go
@@ -1602,7 +1727,9 @@ func main() {
### <span id="Reduce">Reduce</span>
<p>将切片中的元素依次运行iteratee函数返回运行结果(废弃建议使用ReduceBy)</p>
<p>将切片中的元素依次运行iteratee函数返回运行结果</p>
> ⚠️ 本函数已弃用,使用`ReduceBy`代替。
<b>函数签名:</b>
@@ -1634,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>
@@ -2128,10 +2287,12 @@ func main() {
}
```
### <span id="StringSlice">StringSlice(已弃用: 使用 go1.18+泛型代替)</span>
### <span id="StringSlice">StringSlice</span>
<p>将接口切片转换为字符串切片</p>
> ⚠️ 本函数已弃用使用go1.18+泛型代替。
<b>函数签名:</b>
```go
@@ -2280,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>
@@ -2305,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}]
}
```
@@ -2581,7 +2849,7 @@ func main() {
func SetToDefaultIf[T any](slice []T, predicate func(T) bool) ([]T, int)
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/9AXGlPRC0-A)</span></b>
```go
import (
@@ -2591,13 +2859,129 @@ 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
}
```
### <span id="Break">Break</span>
<p>根据判断函数将切片分成两部分。它开始附加到与函数匹配的第一个元素之后的第二个切片。第一个匹配之后的所有元素都包含在第二个切片中,无论它们是否与函数匹配。</p>
<b>函数签名:</b>
```go
func Break[T any](values []T, predicate func(T) bool) ([]T, []T)
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/yLYcBTyeQIz)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
nums := []int{1, 2, 3, 4, 5}
even := func(n int) bool { return n%2 == 0 }
resultEven, resultAfterFirstEven := slice.Break(nums, even)
fmt.Println(resultEven)
fmt.Println(resultAfterFirstEven)
// Output:
// [1]
// [2 3 4 5]
}
```
### <span id="RightPadding">RightPadding</span>
<p>在切片的右部添加元素。</p>
<b>函数签名:</b>
```go
func RightPadding[T any](slice []T, paddingValue T, paddingLength int) []T
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/0_2rlLEMBXL)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
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]
}
```
### <span id="LeftPadding">LeftPadding</span>
<p>在切片的左部添加元素。</p>
<b>函数签名:</b>
```go
func LeftPadding[T any](slice []T, paddingValue T, paddingLength int) []T
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/jlQVoelLl2k)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
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]
}
```
### <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]
}
```

View File

@@ -62,6 +62,13 @@ import (
- [RemoveWhiteSpace](#RemoveWhiteSpace)
- [SubInBetween](#SubInBetween)
- [HammingDistance](#HammingDistance)
- [Concat](#Concat)
- [Ellipsis](#Ellipsis)
- [Shuffle](#Shuffle)
- [Rotate](#Rotate)
- [TemplateReplace](#TemplateReplace)
- [RegexMatchAllGroups](#RegexMatchAllGroups)
<div STYLE="page-break-after: always;"></div>
@@ -1475,7 +1482,7 @@ func main() {
func SubInBetween(str string, start string, end string) string
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/EDbaRvjeNsv)</span></b>
```go
import (
@@ -1505,10 +1512,10 @@ func main() {
<b>函数签名:</b>
```go
HammingDistance(a, b string) (int, error)
func HammingDistance(a, b string) (int, error)
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/glNdQEA9HUi)</span></b>
```go
import (
@@ -1528,4 +1535,197 @@ func main() {
// 0
// 1
}
```
### <span id="Concat">Concat</span>
<p>拼接字符串。length是拼接后字符串的长度如果不确定则传0或负数。</p>
<b>函数签名:</b>
```go
func Concat(length int, 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() {
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 daykeeps 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]
}
```

View File

@@ -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-alinuxdirwindowsping 127.0.0.1。在linux中使用/bin/bash-c执行命令在windows中使用powershell.exe执行命令。</p>
<p>执行shell命令返回命令的stdout和stderr字符串如果出现错误则返回错误。参数`command`是一个完整的命令字符串如ls-alinuxdirwindowsping 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)
}
```

View File

@@ -571,6 +571,7 @@ func main() {
}
```
### <span id="EncodeByte">EncodeByte</span>
<p>Encode data to byte slice.</p>
@@ -610,7 +611,7 @@ func main() {
func DecodeByte(data []byte, target any) error
```
<b>Example:<span style="float:right;display:inline-block;"></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/zI6xsmuQRbn)</span></b>
```go
package main
@@ -636,69 +637,6 @@ func main() {
}
```
### <span id="DeepClone">DeepClone</span>
<p>Creates a deep copy of passed item, can't clone unexported field of struct.</p>
<b>Signature:</b>
```go
func DeepClone[T any](src T) T
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/j4DP5dquxnk)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/convertor"
)
func main() {
type Struct struct {
Str string
Int int
Float float64
Bool bool
Nil interface{}
unexported string
}
cases := []interface{}{
true,
1,
0.1,
map[string]int{
"a": 1,
"b": 2,
},
&Struct{
Str: "test",
Int: 1,
Float: 0.1,
Bool: true,
Nil: nil,
// unexported: "can't be cloned",
},
}
for _, item := range cases {
cloned := convertor.DeepClone(item)
isPointerEqual := &cloned == &item
fmt.Println(cloned, isPointerEqual)
}
// Output:
// true false
// 1 false
// 0.1 false
// map[a:1 b:2] false
// &{test 1 0.1 true <nil> } false
}
```
### <span id="CopyProperties">CopyProperties</span>
@@ -779,41 +717,6 @@ func main() {
}
```
### <span id="ToInterface">ToInterface</span>
<p>Converts reflect value to its interface type.</p>
<b>Signature:</b>
```go
func ToInterface(v reflect.Value) (value interface{}, ok bool)
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/syqw0-WG7Xd)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/convertor"
)
func main() {
val := reflect.ValueOf("abc")
iVal, ok := convertor.ToInterface(val)
fmt.Printf("%T\n", iVal)
fmt.Printf("%v\n", iVal)
fmt.Println(ok)
// Output:
// string
// abc
// true
}
```
### <span id="Utf8ToGbk">Utf8ToGbk</span>
<p>Converts utf8 encoding data to GBK encoding data.</p>
@@ -883,7 +786,7 @@ func main() {
### <span id="ToStdBase64">ToStdBase64</span>
<p>Convert a value to a string encoded in standard Base64. Error data of type "error" will also be encoded, and complex structures will be converted to a JSON-formatted string.</p>
<p>Convert a value to a string encoded in standard Base64. Error data of type "error" will also be encoded, and complex structures will be converted to a JSON formatted string.</p>
<b>Signature:</b>
@@ -891,7 +794,7 @@ func main() {
func ToStdBase64(value any) string
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/_fLJqJD3NMo)</span></b>
```go
package main
@@ -953,9 +856,11 @@ func main() {
```
### <span id="ToUrlBase64">ToUrlBase64</span>
<p>Convert a value to a string encoded in url Base64. Error data of type "error" will also be encoded, and complex structures will be converted to a JSON-formatted string.</p>
<p>Convert a value to a string encoded in url Base64. Error data of type "error" will also be encoded, and complex structures will be converted to a JSON formatted string.</p>
<b>Signature:</b>
@@ -963,7 +868,7 @@ func main() {
func ToUrlBase64(value any) string
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/C_d0GlvEeUR)</span></b>
```go
package main
@@ -1024,7 +929,7 @@ func main() {
### <span id="ToRawStdBase64">ToRawStdBase64</span>
<p>Convert a value to a string encoded in raw standard Base64. Error data of type "error" will also be encoded, and complex structures will be converted to a JSON-formatted string.</p>
<p>Convert a value to a string encoded in raw standard Base64. Error data of type "error" will also be encoded, and complex structures will be converted to a JSON formatted string.</p>
<b>Signature:</b>
@@ -1032,7 +937,7 @@ func main() {
func ToRawStdBase64(value any) string
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/wSAr3sfkDcv)</span></b>
```go
package main
@@ -1045,7 +950,7 @@ import (
func main() {
stringVal := "hello"
afterEncode = convertor.ToRawStdBase64(stringVal)
afterEncode := convertor.ToRawStdBase64(stringVal)
fmt.Println(afterEncode)
byteSliceVal := []byte("hello")
@@ -1088,7 +993,7 @@ func main() {
### <span id="ToRawUrlBase64">ToRawUrlBase64</span>
<p> Convert a value to a string encoded in raw url Base64. Error data of type "error" will also be encoded, and complex structures will be converted to a JSON-formatted string.</p>
<p> Convert a value to a string encoded in raw url Base64. Error data of type "error" will also be encoded, and complex structures will be converted to a JSON formatted string.</p>
<b>Signature:</b>
@@ -1096,7 +1001,7 @@ func main() {
func ToRawUrlBase64(value any) string
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/HwdDPFcza1O)</span></b>
```go
package main
@@ -1109,7 +1014,7 @@ import (
func main() {
stringVal := "hello"
afterEncode = convertor.ToRawUrlBase64(stringVal)
afterEncode := convertor.ToRawUrlBase64(stringVal)
fmt.Println(afterEncode)
byteSliceVal := []byte("hello")
@@ -1132,11 +1037,11 @@ func main() {
fmt.Println(afterEncode)
boolVal := true
afterEncode = convertor.ToRawStdBase64(boolVal)
afterEncode = convertor.ToRawUrlBase64(boolVal)
fmt.Println(afterEncode)
errVal := errors.New("err")
afterEncode = convertor.ToRawStdBase64(errVal)
afterEncode = convertor.ToRawUrlBase64(errVal)
fmt.Println(afterEncode)
// Output:
@@ -1148,4 +1053,67 @@ func main() {
// dHJ1ZQ
// ZXJy
}
```
### <span id="DeepClone">DeepClone</span>
<p>Creates a deep copy of passed item, can't clone unexported field of struct.</p>
<b>Signature:</b>
```go
func DeepClone[T any](src T) T
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/j4DP5dquxnk)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/convertor"
)
func main() {
type Struct struct {
Str string
Int int
Float float64
Bool bool
Nil interface{}
unexported string
}
cases := []interface{}{
true,
1,
0.1,
map[string]int{
"a": 1,
"b": 2,
},
&Struct{
Str: "test",
Int: 1,
Float: 0.1,
Bool: true,
Nil: nil,
},
}
for _, item := range cases {
cloned := convertor.DeepClone(item)
isPointerEqual := &cloned == &item
fmt.Println(cloned, isPointerEqual)
}
// Output:
// true false
// 1 false
// 0.1 false
// map[a:1 b:2] false
// &{test 1 0.1 true <nil> } false
}
```

View File

@@ -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>

View File

@@ -32,6 +32,7 @@ import (
- [Iterate](#Iterate)
- [Keys](#Keys)
- [Values](#Values)
- [FilterByValue](#FilterByValue)
<div STYLE="page-break-after: always;"></div>
@@ -311,4 +312,77 @@ func main() {
}
```
### <span id="FilterByValue">FilterByValue</span>
<p>Returns a filtered HashMap.</p>
<b>Signature:</b>
```go
func (hm *HashMap) FilterByValue(perdicate func(value any) bool) *HashMap
```
<b>Example:</b>
```go
package main
import (
"fmt"
hashmap "github.com/duke-git/lancet/v2/datastructure/hashmap"
)
func main() {
hm := hashmap.NewHashMap()
hm.Put("a", 1)
hm.Put("b", 2)
hm.Put("c", 3)
hm.Put("d", 4)
hm.Put("e", 5)
hm.Put("f", 6)
filteredHM := hm.FilterByValue(func(value any) bool {
return value.(int) == 1 || value.(int) == 3
})
fmt.Println(filteredHM.Size()) //2
}
```
### <span id="ToInterface">ToInterface</span>
<p>Converts reflect value to its interface type.</p>
<b>Signature:</b>
```go
func ToInterface(v reflect.Value) (value interface{}, ok bool)
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/syqw0-WG7Xd)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/convertor"
)
func main() {
val := reflect.ValueOf("abc")
iVal, ok := convertor.ToInterface(val)
fmt.Printf("%T\n", iVal)
fmt.Printf("%v\n", iVal)
fmt.Println(ok)
// Output:
// string
// abc
// true
}
```

View File

@@ -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>

View File

@@ -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>
}
```

View File

@@ -167,7 +167,7 @@ func main() {
### <span id="CopyDir">CopyDir</span>
<p>copy src directory to dst directory, it will copy all files and directories recursively. the access permission will be the same as the source directory. if dstPath exists, it will return an error.</p>
<p>Copy src directory to dst directory, it will copy all files and directories recursively. the access permission will be the same as the source directory. if dstPath exists, it will return an error.</p>
<b>Signature:</b>
@@ -974,7 +974,7 @@ func main() {
func ChunkRead(file *os.File, offset int64, size int, bufPool *sync.Pool) ([]string, error)
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/r0hPmKWhsgf)</span></b>
```go
package main
@@ -1033,7 +1033,7 @@ func main() {
func ParallelChunkRead(filePath string, linesCh chan<- []string, chunkSizeMB, maxGoroutine int) error
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/teMXnCsdSEw)</span></b>
```go
package main

View File

@@ -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>

View File

@@ -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
@@ -423,7 +474,7 @@ func longRunningTask() {
func And[T any](predicates ...func(T) bool) func(T) bool
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/dTBHJMQ0zD2)</span></b>
```go
package main
@@ -460,7 +511,7 @@ func main() {
func Or[T any](predicates ...func(T) bool) func(T) bool
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/LitCIsDFNDA)</span></b>
```go
package main
@@ -495,7 +546,7 @@ func main() {
func Negate[T any](predicate func(T) bool) func(T) bool
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/jbI8BtgFnVE)</span></b>
```go
package main
@@ -535,7 +586,7 @@ func main() {
func Nor[T any](predicates ...func(T) bool) func(T) bool
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/2KdCoBEOq84)</span></b>
```go
package main
@@ -577,7 +628,7 @@ func main() {
func Nand[T any](predicates ...func(T) bool) func(T) bool
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/Rb-FdNGpgSO)</span></b>
```go
package main
@@ -614,7 +665,7 @@ func main() {
func Xnor[T any](predicates ...func(T) bool) func(T) bool
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/FJxko8SFbqc)</span></b>
```go
package main
@@ -643,9 +694,7 @@ func main() {
### <span id="AcceptIf">AcceptIf</span>
<p>AcceptIf returns another function of the same signature as the apply function but also includes a bool value to indicate success or failure.
A predicate function that takes an argument of type T and returns a bool.
An apply function that also takes an argument of type T and returns a modified value of the same type.</p>
<p>AcceptIf returns another function of the same signature as the apply function but also includes a bool value to indicate success or failure. A predicate function that takes an argument of type T and returns a bool. An apply function that also takes an argument of type T and returns a modified value of the same type.</p>
<b>Signature:</b>
@@ -653,7 +702,7 @@ An apply function that also takes an argument of type T and returns a modified v
func AcceptIf[T any](predicate func(T) bool, apply func(T) T) func(T) (T, bool)
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/XlXHHtzCf7d)</span></b>
```go
package main
@@ -665,8 +714,8 @@ import (
func main() {
adder := AcceptIf(
And(
adder := function.AcceptIf(
function.And(
func(x int) bool {
return x > 10
}, func(x int) bool {
@@ -691,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

View File

@@ -508,7 +508,7 @@ func main() {
func CeilToFloat[T constraints.Float | constraints.Integer](x T, n int) float64
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/8hOeSADZPCo)</span></b>
```go
package main
@@ -544,7 +544,7 @@ func main() {
func CeilToString[T constraints.Float | constraints.Integer](x T, n int) string
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/wy5bYEyUKKG)</span></b>
```go
package main
@@ -577,10 +577,10 @@ func main() {
<b>Signature:</b>
```go
func CeilToString[T constraints.Float | constraints.Integer](x T, n int) string
func FloorToFloat[T constraints.Float | constraints.Integer](x T, n int) float64
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/vbCBrQHZEED)</span></b>
```go
package main
@@ -613,10 +613,10 @@ func main() {
<b>Signature:</b>
```go
func CeilToString[T constraints.Float | constraints.Integer](x T, n int) string
func FloorToString[T constraints.Float | constraints.Integer](x T, n int) string
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/Qk9KPd2IdDb)</span></b>
```go
package main
@@ -1130,7 +1130,7 @@ func main() {
### <span id="Div">Div</span>
<p>returns the result of x divided by y.</p>
<p>Returns the result of x divided by y.</p>
<b>Signature:</b>
@@ -1138,7 +1138,7 @@ func main() {
func Div[T constraints.Float | constraints.Integer](x T, y T) float64
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/WLxDdGXXYat)</span></b>
```go
package main

View File

@@ -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>

View File

@@ -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)

View File

@@ -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)
}
```

View File

@@ -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

View File

@@ -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)
@@ -93,6 +101,11 @@ import (
- [KeyBy](#KeyBy)
- [Join](#Join)
- [Partition](#Partition)
- [SetToDefaultIf](#SetToDefaultIf)
- [Break](#Break)
- [RightPadding](#RightPadding)
- [LeftPadding](#LeftPadding)
- [Frequency](#Frequency)
<div STYLE="page-break-after: always;"></div>
@@ -317,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>
@@ -890,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
@@ -962,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
@@ -1129,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>
@@ -1237,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
@@ -1266,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
@@ -1466,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>
@@ -1540,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
@@ -1599,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>
@@ -1631,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>
@@ -2125,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
@@ -2277,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>
@@ -2302,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}]
}
```
@@ -2577,7 +2844,7 @@ func main() {
func SetToDefaultIf[T any](slice []T, predicate func(T) bool) ([]T, int)
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/9AXGlPRC0-A)</span></b>
```go
import (
@@ -2587,13 +2854,129 @@ 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
}
```
### <span id="Break">Break</span>
<p>Splits a slice into two based on a predicate function. It starts appending to the second slice after the first element that matches the predicate. All elements after the first match are included in the second slice, regardless of whether they match the predicate or not.</p>
<b>Signature:</b>
```go
func Break[T any](values []T, predicate func(T) bool) ([]T, []T)
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/yLYcBTyeQIz)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
nums := []int{1, 2, 3, 4, 5}
even := func(n int) bool { return n%2 == 0 }
resultEven, resultAfterFirstEven := slice.Break(nums, even)
fmt.Println(resultEven)
fmt.Println(resultAfterFirstEven)
// Output:
// [1]
// [2 3 4 5]
}
```
### <span id="RightPadding">RightPadding</span>
<p>RightPadding adds padding to the right end of a slice.</p>
<b>Signature:</b>
```go
func RightPadding[T any](slice []T, paddingValue T, paddingLength int) []T
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/0_2rlLEMBXL)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
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]
}
```
### <span id="LeftPadding">LeftPadding</span>
<p>LeftPadding adds padding to the left begin of a slice.</p>
<b>Signature:</b>
```go
func LeftPadding[T any](slice []T, paddingValue T, paddingLength int) []T
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/jlQVoelLl2k)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
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]
}
```
### <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]
}
```

View File

@@ -62,6 +62,12 @@ import (
- [RemoveWhiteSpace](#RemoveWhiteSpace)
- [SubInBetween](#SubInBetween)
- [HammingDistance](#HammingDistance)
- [Concat](#Concat)
- [Ellipsis](#Ellipsis)
- [Shuffle](#Shuffle)
- [Rotate](#Rotate)
- [TemplateReplace](#TemplateReplace)
- [RegexMatchAllGroups](#RegexMatchAllGroups)
<div STYLE="page-break-after: always;"></div>
@@ -1099,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)
@@ -1477,7 +1483,7 @@ func main() {
func SubInBetween(str string, start string, end string) string
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/EDbaRvjeNsv)</span></b>
```go
import (
@@ -1507,10 +1513,10 @@ func main() {
<b>Signature:</b>
```go
HammingDistance(a, b string) (int, error)
func HammingDistance(a, b string) (int, error)
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/glNdQEA9HUi)</span></b>
```go
import (
@@ -1530,4 +1536,196 @@ func main() {
// 0
// 1
}
```
### <span id="Concat">Concat</span>
<p>Concatenates strings. <b>length</b> is the length of the concatenated string. If unsure, pass 0 or a negative number.</p>
<b>Signature:</b>
```go
func Concat(length int, 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() {
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 daykeeps 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]
}
```

View File

@@ -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)
}
```

View File

@@ -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>

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,6 @@
"docs:preview": "vitepress preview"
},
"devDependencies": {
"vitepress": "^1.0.0-rc.4"
"vitepress": "^1.2.3"
}
}

View File

@@ -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.
@@ -869,7 +854,7 @@ func isCsvSupportedType(v interface{}) bool {
}
// ChunkRead reads a block from the file at the specified offset and returns all lines within the block
// Play: todo
// Play: https://go.dev/play/p/r0hPmKWhsgf
func ChunkRead(file *os.File, offset int64, size int, bufPool *sync.Pool) ([]string, error) {
buf := bufPool.Get().([]byte)[:size] // 从Pool获取缓冲区并调整大小
n, err := file.ReadAt(buf, offset) // 从指定偏移读取数据到缓冲区
@@ -901,7 +886,7 @@ func ChunkRead(file *os.File, offset int64, size int, bufPool *sync.Pool) ([]str
// chunkSizeMB 分块的大小单位MB设置为0时使用默认100MB,设置过大反而不利,视情调整
// maxGoroutine 并发读取分块的数量设置为0时使用CPU核心数
// linesCh用于接收返回结果的通道。
// Play: todo
// Play: https://go.dev/play/p/teMXnCsdSEw
func ParallelChunkRead(filePath string, linesCh chan<- []string, chunkSizeMB, maxGoroutine int) error {
if chunkSizeMB == 0 {
chunkSizeMB = 100

View File

@@ -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

View File

@@ -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) {

View File

@@ -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.

View File

@@ -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) {

View File

@@ -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
}
@@ -139,6 +201,7 @@ func Pipeline[T any](funcs ...func(T) T) func(T) T {
// AcceptIf returns another function of the same signature as the apply function but also includes a bool value to indicate success or failure.
// A predicate function that takes an argument of type T and returns a bool.
// An apply function that also takes an argument of type T and returns a modified value of the same type.
// Play: https://go.dev/play/p/XlXHHtzCf7d
func AcceptIf[T any](predicate func(T) bool, apply func(T) T) func(T) (T, bool) {
if predicate == nil {
panic("programming error: predicate must be not nil")

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -2,6 +2,7 @@ package function
// And returns a composed predicate that represents the logical AND of a list of predicates.
// It evaluates to true only if all predicates evaluate to true for the given value.
// Play: https://go.dev/play/p/dTBHJMQ0zD2
func And[T any](predicates ...func(T) bool) func(T) bool {
if len(predicates) < 2 {
panic("programming error: predicates count must be at least 2")
@@ -18,6 +19,7 @@ func And[T any](predicates ...func(T) bool) func(T) bool {
// Nand returns a composed predicate that represents the logical NAND of a list of predicates.
// It evaluates to true only if all predicates evaluate to false for the given value.
// Play: https://go.dev/play/p/Rb-FdNGpgSO
func Nand[T any](predicates ...func(T) bool) func(T) bool {
if len(predicates) < 2 {
panic("programming error: predicates count must be at least 2")
@@ -33,6 +35,7 @@ func Nand[T any](predicates ...func(T) bool) func(T) bool {
}
// Negate returns a predicate that represents the logical negation of this predicate.
// Play: https://go.dev/play/p/jbI8BtgFnVE
func Negate[T any](predicate func(T) bool) func(T) bool {
return func(value T) bool {
return !predicate(value)
@@ -41,6 +44,7 @@ func Negate[T any](predicate func(T) bool) func(T) bool {
// Or returns a composed predicate that represents the logical OR of a list of predicates.
// It evaluates to true if at least one of the predicates evaluates to true for the given value.
// Play: https://go.dev/play/p/LitCIsDFNDA
func Or[T any](predicates ...func(T) bool) func(T) bool {
if len(predicates) < 2 {
panic("programming error: predicates count must be at least 2")
@@ -57,6 +61,7 @@ func Or[T any](predicates ...func(T) bool) func(T) bool {
// Nor returns a composed predicate that represents the logical NOR of a list of predicates.
// It evaluates to true only if all predicates evaluate to false for the given value.
// Play: https://go.dev/play/p/2KdCoBEOq84
func Nor[T any](predicates ...func(T) bool) func(T) bool {
if len(predicates) < 2 {
panic("programming error: predicates count must be at least 2")
@@ -73,6 +78,7 @@ func Nor[T any](predicates ...func(T) bool) func(T) bool {
// Xnor returns a composed predicate that represents the logical XNOR of a list of predicates.
// It evaluates to true only if all predicates evaluate to true or false for the given value.
// Play: https://go.dev/play/p/FJxko8SFbqc
func Xnor[T any](predicates ...func(T) bool) func(T) bool {
if len(predicates) < 2 {
panic("programming error: predicates count must be at least 2")

View File

@@ -7,6 +7,10 @@ package maputil
import (
"fmt"
"reflect"
"sort"
"strings"
"golang.org/x/exp/constraints"
"github.com/duke-git/lancet/v2/slice"
)
@@ -66,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 {
@@ -306,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)
@@ -375,8 +384,7 @@ func getFieldNameByJsonTag(structObj any, jsonTag string) string {
for i := 0; i < s.NumField(); i++ {
field := s.Field(i)
tag := field.Tag
name := tag.Get("json")
name, _, _ := strings.Cut(tag.Get("json"), ",")
if name == jsonTag {
return field.Name
}
@@ -384,3 +392,277 @@ func getFieldNameByJsonTag(structObj any, jsonTag string) string {
return ""
}
// 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))
// store the maps keys into a slice
for k := range m {
keys = append(keys, k)
}
// sort the slice of keys
sort.Slice(keys, func(i, j int) bool {
return keys[i] < keys[j]
})
// adjust the order of values according to the sorted keys
sortedValues := make([]V, len(keys))
for i, k := range keys {
sortedValues[i] = m[k]
}
return keys, sortedValues
}
// 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))
// store the maps keys into a slice
for k := range m {
keys = append(keys, k)
}
// sort the key slice using the provided comparison function
sort.Slice(keys, func(i, j int) bool {
return comparator(keys[i], keys[j])
})
// adjust the order of values according to the sorted keys
sortedValues := make([]V, len(keys))
for i, k := range keys {
sortedValues[i] = m[k]
}
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
}

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"sort"
"strconv"
"time"
)
func ExampleKeys() {
@@ -450,3 +451,381 @@ func ExampleHasKey() {
// true
// false
}
func ExampleMapToStruct() {
personReqMap := map[string]any{
"name": "Nothin",
"max_age": 35,
"page": 1,
"pageSize": 10,
}
type PersonReq struct {
Name string `json:"name"`
MaxAge int `json:"max_age"`
Page int `json:"page"`
PageSize int `json:"pageSize"`
}
var personReq PersonReq
_ = MapToStruct(personReqMap, &personReq)
fmt.Println(personReq)
// Output:
// {Nothin 35 1 10}
}
func ExampleToSortedSlicesDefault() {
m := map[int]string{
1: "a",
3: "c",
2: "b",
}
keys, values := ToSortedSlicesDefault(m)
fmt.Println(keys)
fmt.Println(values)
// Output:
// [1 2 3]
// [a b c]
}
func ExampleToSortedSlicesWithComparator() {
m1 := map[time.Time]string{
time.Date(2024, 3, 31, 0, 0, 0, 0, time.UTC): "today",
time.Date(2024, 3, 30, 0, 0, 0, 0, time.UTC): "yesterday",
time.Date(2024, 4, 1, 0, 0, 0, 0, time.UTC): "tomorrow",
}
keys1, values1 := ToSortedSlicesWithComparator(m1, func(a, b time.Time) bool {
return a.Before(b)
})
m2 := map[int]string{
1: "a",
3: "c",
2: "b",
}
keys2, values2 := ToSortedSlicesWithComparator(m2, func(a, b int) bool {
return a > b
})
fmt.Println(keys1)
fmt.Println(values1)
fmt.Println(keys2)
fmt.Println(values2)
// Output:
// [2024-03-30 00:00:00 +0000 UTC 2024-03-31 00:00:00 +0000 UTC 2024-04-01 00:00:00 +0000 UTC]
// [yesterday today tomorrow]
// [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())
}

View File

@@ -1,9 +1,11 @@
package maputil
import (
"math/cmplx"
"sort"
"strconv"
"testing"
"time"
"github.com/duke-git/lancet/v2/internal"
)
@@ -105,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)
@@ -483,7 +485,7 @@ func TestMapToStruct(t *testing.T) {
Name string `json:"name"`
Age int `json:"age"`
Phone string `json:"phone"`
Addr *Address `json:"address"`
Addr *Address `json:"address,omitempty"`
}
Address struct {
@@ -511,3 +513,378 @@ func TestMapToStruct(t *testing.T) {
assert.Equal("test", p.Addr.Street)
assert.Equal(1, p.Addr.Number)
}
func TestToSortedSlicesDefault(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestToSortedSlicesDefault")
testCases1 := []struct {
name string
inputMap map[string]any
expKeys []string
expValues []any
}{
{
name: "Empty Map",
inputMap: map[string]any{},
expKeys: []string{},
expValues: []any{},
},
{
name: "Unsorted Map",
inputMap: map[string]any{"c": 3, "a": 1.6, "b": 2},
expKeys: []string{"a", "b", "c"},
expValues: []any{1.6, 2, 3},
},
}
for _, tc := range testCases1 {
t.Run(tc.name, func(t *testing.T) {
keys, values := ToSortedSlicesDefault(tc.inputMap)
assert.Equal(tc.expKeys, keys)
assert.Equal(tc.expValues, values)
})
}
testCases2 := map[int64]string{
7: "seven",
3: "three",
5: "five",
}
actualK2, actualV2 := ToSortedSlicesDefault(testCases2)
case2Keys := []int64{3, 5, 7}
case2Values := []string{"three", "five", "seven"}
assert.Equal(case2Keys, actualK2)
assert.Equal(case2Values, actualV2)
}
func TestToSortedSlicesWithComparator(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestToSortedSlicesWithComparator")
type Account struct {
ID int
Name string
CreateTime time.Time
FavorComplexNumber complex128
Signal chan struct{}
}
type testCase struct {
name string
inputMap map[Account]any
lessFunc func(a, b Account) bool
expKeys []Account
expValues []any
}
now := time.Now()
tomorrow := now.AddDate(0, 0, 1)
yesterday := now.AddDate(0, 0, -1)
account1 := Account{ID: 1, Name: "cya", CreateTime: now, FavorComplexNumber: complex(1.2, 3),
Signal: make(chan struct{}, 10)}
account2 := Account{ID: 2, Name: "Cannian", CreateTime: tomorrow, FavorComplexNumber: complex(1.2, 2),
Signal: make(chan struct{}, 7)}
account3 := Account{ID: 3, Name: "Clauviou", CreateTime: yesterday, FavorComplexNumber: complex(3, 4),
Signal: make(chan struct{}, 3)}
account1.Signal <- struct{}{}
account2.Signal <- struct{}{}
account2.Signal <- struct{}{}
testCases := []testCase{
{
name: "Sorted by Account ID",
inputMap: map[Account]any{
account1: 100,
account2: 200,
account3: 300,
},
lessFunc: func(a, b Account) bool { return a.ID < b.ID },
expKeys: []Account{account1, account2, account3},
expValues: []any{100, 200, 300},
},
{
name: "Reverse Sorted by Account ID",
inputMap: map[Account]any{
account1: 100,
account2: 200,
account3: 300,
},
lessFunc: func(a, b Account) bool { return a.ID > b.ID },
expKeys: []Account{account3, account2, account1},
expValues: []any{300, 200, 100},
},
{
name: "Sorted by Account Name",
inputMap: map[Account]any{
account1: 100,
account2: 200,
account3: 300,
},
lessFunc: func(a, b Account) bool { return a.Name < b.Name },
expKeys: []Account{account2, account3, account1},
expValues: []any{200, 300, 100},
},
{
name: "Empty Map",
inputMap: map[Account]any{},
lessFunc: func(a, b Account) bool { return a.ID < b.ID },
expKeys: []Account{},
expValues: []any{},
},
{
name: "Sorted by Account CreateTime",
inputMap: map[Account]any{
account1: "now",
account2: "tomorrow",
account3: "yesterday",
},
lessFunc: func(a, b Account) bool { return a.CreateTime.Before(b.CreateTime) },
expKeys: []Account{account3, account1, account2},
expValues: []any{"yesterday", "now", "tomorrow"},
},
{
name: "Sorted by Account FavorComplexNumber",
inputMap: map[Account]any{
account1: "1.2+3i",
account2: "1.2+2i",
account3: "3+4i",
},
lessFunc: func(a, b Account) bool { return cmplx.Abs(a.FavorComplexNumber) < cmplx.Abs(b.FavorComplexNumber) },
expKeys: []Account{account2, account1, account3},
expValues: []any{"1.2+2i", "1.2+3i", "3+4i"},
},
{
name: "Sort by the buffer capacity of the channel",
inputMap: map[Account]any{
account1: 10,
account2: 7,
account3: 3,
},
lessFunc: func(a, b Account) bool {
return cap(a.Signal) < cap(b.Signal)
},
expKeys: []Account{account3, account2, account1},
expValues: []any{3, 7, 10},
},
{
name: "Sort by the number of elements in the channel",
inputMap: map[Account]any{
account1: 1,
account2: 2,
account3: 0,
},
lessFunc: func(a, b Account) bool { return len(a.Signal) < len(b.Signal) },
expKeys: []Account{account3, account1, account2},
expValues: []any{0, 1, 2},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
keys, values := ToSortedSlicesWithComparator(tc.inputMap, tc.lessFunc)
assert.Equal(tc.expKeys, keys)
assert.Equal(tc.expValues, values)
})
}
}
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
View 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
View 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"))
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -101,7 +101,7 @@ func TruncRound[T constraints.Float | constraints.Integer](x T, n int) T {
}
// FloorToFloat round down to n decimal places.
// Play: todo
// Play: https://go.dev/play/p/vbCBrQHZEED
func FloorToFloat[T constraints.Float | constraints.Integer](x T, n int) float64 {
tmp := math.Pow(10.0, float64(n))
x *= T(tmp)
@@ -110,7 +110,7 @@ func FloorToFloat[T constraints.Float | constraints.Integer](x T, n int) float64
}
// FloorToString round down to n decimal places.
// Play: todo
// Play: https://go.dev/play/p/Qk9KPd2IdDb
func FloorToString[T constraints.Float | constraints.Integer](x T, n int) string {
tmp := math.Pow(10.0, float64(n))
x *= T(tmp)
@@ -120,7 +120,7 @@ func FloorToString[T constraints.Float | constraints.Integer](x T, n int) string
}
// CeilToFloat round up to n decimal places.
// Play: todo
// Play: https://go.dev/play/p/8hOeSADZPCo
func CeilToFloat[T constraints.Float | constraints.Integer](x T, n int) float64 {
tmp := math.Pow(10.0, float64(n))
x *= T(tmp)
@@ -129,7 +129,7 @@ func CeilToFloat[T constraints.Float | constraints.Integer](x T, n int) float64
}
// CeilToString round up to n decimal places.
// Play: todo
// Play: https://go.dev/play/p/wy5bYEyUKKG
func CeilToString[T constraints.Float | constraints.Integer](x T, n int) string {
tmp := math.Pow(10.0, float64(n))
x *= T(tmp)
@@ -391,7 +391,7 @@ func Abs[T constraints.Integer | constraints.Float](x T) T {
}
// Div returns the result of x divided by y.
// Play: todo
// Play: https://go.dev/play/p/WLxDdGXXYat
func Div[T constraints.Float | constraints.Integer](x T, y T) float64 {
return float64(x) / float64(y)
}

View File

@@ -28,7 +28,6 @@ import (
"strings"
"time"
"github.com/duke-git/lancet/v2/convertor"
"github.com/duke-git/lancet/v2/slice"
)
@@ -103,19 +102,22 @@ type HttpRequest struct {
// HttpClientConfig contains some configurations for http client
type HttpClientConfig struct {
Timeout time.Duration
SSLEnabled bool
TLSConfig *tls.Config
Compressed bool
HandshakeTimeout time.Duration
ResponseTimeout time.Duration
Verbose bool
Proxy *url.URL
}
// 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,
@@ -164,6 +167,11 @@ func NewHttpClientWithConfig(config *HttpClientConfig) *HttpClient {
client.TLS = config.TLSConfig
}
if config.Proxy != nil {
transport := client.Client.Transport.(*http.Transport)
transport.Proxy = http.ProxyURL(config.Proxy)
}
return client
}
@@ -363,11 +371,20 @@ func validateRequest(req *HttpRequest) error {
// Play: https://go.dev/play/p/pFqMkM40w9z
func StructToUrlValues(targetStruct any) (url.Values, error) {
result := url.Values{}
s, err := convertor.StructToMap(targetStruct)
var m map[string]interface{}
jsonBytes, err := json.Marshal(targetStruct)
if err != nil {
return nil, err
}
for k, v := range s {
err = json.Unmarshal(jsonBytes, &m)
if err != nil {
return nil, err
}
for k, v := range m {
result.Add(k, fmt.Sprintf("%v", v))
}

View File

@@ -5,12 +5,12 @@ import (
"encoding/json"
"io"
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
"net/url"
"os"
"testing"
"time"
"github.com/duke-git/lancet/v2/internal"
)
@@ -23,7 +23,8 @@ func TestHttpGet(t *testing.T) {
resp, err := HttpGet(url, header)
if err != nil {
log.Fatal(err)
t.Log("net error: " + err.Error())
return
}
body, _ := io.ReadAll(resp.Body)
@@ -44,8 +45,10 @@ func TestHttpPost(t *testing.T) {
resp, err := HttpPost(url, header, nil, bodyParams)
if err != nil {
log.Fatal(err)
t.Log("net error: " + err.Error())
return
}
body, _ := io.ReadAll(resp.Body)
t.Log("response: ", resp.StatusCode, string(body))
}
@@ -54,21 +57,18 @@ func TestHttpPostFormData(t *testing.T) {
apiUrl := "https://jsonplaceholder.typicode.com/todos"
header := map[string]string{
"Content-Type": "application/x-www-form-urlencoded",
// "Content-Type": "multipart/form-data",
}
postData := url.Values{}
postData.Add("userId", "1")
postData.Add("title", "TestToDo")
// postData := make(map[string]string)
// postData["userId"] = "1"
// postData["title"] = "title"
resp, err := HttpPost(apiUrl, header, nil, postData)
if err != nil {
log.Fatal(err)
t.Log("net error: " + err.Error())
return
}
body, _ := io.ReadAll(resp.Body)
t.Log("response: ", resp.StatusCode, string(body))
}
@@ -88,8 +88,10 @@ func TestHttpPut(t *testing.T) {
resp, err := HttpPut(url, header, nil, bodyParams)
if err != nil {
log.Fatal(err)
t.Log("net error: ", err.Error())
return
}
body, _ := io.ReadAll(resp.Body)
t.Log("response: ", resp.StatusCode, string(body))
}
@@ -109,8 +111,10 @@ func TestHttpPatch(t *testing.T) {
resp, err := HttpPatch(url, header, nil, bodyParams)
if err != nil {
log.Fatal(err)
t.Log("net error: ", err.Error())
return
}
body, _ := io.ReadAll(resp.Body)
t.Log("response: ", resp.StatusCode, string(body))
}
@@ -119,8 +123,10 @@ func TestHttpDelete(t *testing.T) {
url := "https://jsonplaceholder.typicode.com/todos/1"
resp, err := HttpDelete(url)
if err != nil {
log.Fatal(err)
t.Log("net error: ", err.Error())
return
}
body, _ := io.ReadAll(resp.Body)
t.Log("response: ", resp.StatusCode, string(body))
}
@@ -147,7 +153,8 @@ func TestParseResponse(t *testing.T) {
resp, err := HttpGet(url, header)
if err != nil {
log.Fatal(err)
t.Log("net error: ", err.Error())
return
}
type Todo struct {
@@ -160,8 +167,10 @@ func TestParseResponse(t *testing.T) {
toDoResp := &Todo{}
err = ParseHttpResponse(resp, toDoResp)
if err != nil {
log.Fatal(err)
t.Log("net error: ", err.Error())
return
}
t.Log("response: ", toDoResp)
}
@@ -178,7 +187,8 @@ func TestHttpClient_Get(t *testing.T) {
httpClient := NewHttpClient()
resp, err := httpClient.SendRequest(request)
if err != nil || resp.StatusCode != 200 {
log.Fatal(err)
t.Log("net error: ", err.Error())
return
}
type Todo struct {
@@ -215,7 +225,8 @@ func TestHttpClent_Post(t *testing.T) {
httpClient := NewHttpClient()
resp, err := httpClient.SendRequest(request)
if err != nil {
log.Fatal(err)
t.Log("net error: ", err.Error())
return
}
body, _ := io.ReadAll(resp.Body)
@@ -227,16 +238,25 @@ func TestStructToUrlValues(t *testing.T) {
assert := internal.NewAssert(t, "TestStructToUrlValues")
type CommReq struct {
Version string `json:"version"`
}
type TodoQuery struct {
Id int `json:"id"`
UserId int `json:"userId"`
Name string `json:"name,omitempty"`
Id int `json:"id"`
UserId int `json:"userId"`
Name string `json:"name,omitempty"`
CommReq `json:",inline"`
}
item1 := TodoQuery{
Id: 1,
UserId: 123,
Name: "",
CommReq: CommReq{
Version: "1.0",
},
}
todoValues, err := StructToUrlValues(item1)
if err != nil {
t.Errorf("params is invalid: %v", err)
@@ -245,19 +265,10 @@ func TestStructToUrlValues(t *testing.T) {
assert.Equal("1", todoValues.Get("id"))
assert.Equal("123", todoValues.Get("userId"))
assert.Equal("", todoValues.Get("name"))
item2 := TodoQuery{
Id: 2,
UserId: 456,
}
queryValues2, _ := StructToUrlValues(item2)
assert.Equal("2", queryValues2.Get("id"))
assert.Equal("456", queryValues2.Get("userId"))
assert.Equal("", queryValues2.Get("name"))
assert.Equal("1.0", todoValues.Get("version"))
}
func handleFileRequest(t *testing.T, w http.ResponseWriter, r *http.Request) {
func handleFileRequest(t *testing.T, _ http.ResponseWriter, r *http.Request) {
err := r.ParseMultipartForm(1024)
if err != nil {
t.Fatal(err)
@@ -361,3 +372,25 @@ func TestSendRequestWithFilePath(t *testing.T) {
t.Fatalf("expected %d, got %d", http.StatusOK, resp.StatusCode)
}
}
func TestProxy(t *testing.T) {
config := &HttpClientConfig{
HandshakeTimeout: 20 * time.Second,
ResponseTimeout: 40 * time.Second,
// Use the proxy ip to add it here
//Proxy: &url.URL{
// Scheme: "http",
// Host: "46.17.63.166:18888",
//},
}
httpClient := NewHttpClientWithConfig(config)
resp, err := httpClient.Get("https://www.ipplus360.com/getLocation")
if err != nil {
t.Log("net error: ", err.Error())
return
}
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected %d, got %d", http.StatusOK, resp.StatusCode)
}
}

View File

@@ -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 {

View File

@@ -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())
}

View File

@@ -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
}

View File

@@ -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"

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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)
}
})
}

View File

@@ -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")

View File

@@ -50,12 +50,23 @@ func ContainBy[T any](slice []T, predicate func(item T) bool) bool {
// ContainSubSlice check if the slice contain a given subslice or not.
// Play: https://go.dev/play/p/bcuQ3UT6Sev
func ContainSubSlice[T comparable](slice, subSlice []T) bool {
for _, v := range subSlice {
if !Contain(slice, v) {
if len(subSlice) == 0 {
return true
}
if len(slice) == 0 {
return false
}
elementMap := make(map[T]struct{}, len(slice))
for _, item := range slice {
elementMap[item] = struct{}{}
}
for _, item := range subSlice {
if _, ok := elementMap[item]; !ok {
return false
}
}
return true
}
@@ -81,26 +92,32 @@ func Chunk[T any](slice []T, size int) [][]T {
return result
}
// Compact creates an slice with all falsey values removed. The values false, nil, 0, and "" are falsey.
// Compact creates a slice with all falsey values removed. The values false, nil, 0, and "" are falsey.
// Play: https://go.dev/play/p/pO5AnxEr3TK
func Compact[T comparable](slice []T) []T {
var zero T
result := []T{}
result := make([]T, 0, len(slice))
for _, v := range slice {
if v != zero {
result = append(result, v)
}
}
return result
return result[:len(result):len(result)]
}
// 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 {
result := append([]T{}, 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)
for _, v := range slices {
result = append(result, v...)
@@ -109,7 +126,7 @@ func Concat[T any](slice []T, slices ...[]T) []T {
return result
}
// Difference creates an slice of whose element in slice but not in comparedSlice.
// Difference creates a slice of whose element in slice but not in comparedSlice.
// Play: https://go.dev/play/p/VXvadzLzhDa
func Difference[T comparable](slice, comparedSlice []T) []T {
result := []T{}
@@ -754,36 +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{}
result := make([]T, 0, len(slice))
seen := make(map[T]struct{}, len(slice))
for i := 0; i < len(slice); i++ {
v := slice[i]
skip := true
for j := range result {
if v == result[j] {
skip = false
for i := range slice {
if _, ok := seen[slice[i]]; ok {
continue
}
seen[slice[i]] = struct{}{}
result = append(result, slice[i])
}
return result
}
// 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 i := range slice {
key := iteratee(slice[i])
if _, ok := seen[key]; ok {
continue
}
seen[key] = struct{}{}
result = append(result, slice[i])
}
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 skip {
result = append(result, v)
if !duplicate {
seen = append(seen, item)
result = append(result, item)
}
}
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{}
// 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{}{}
for _, v := range slice {
val := iteratee(v)
result = append(result, val)
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 Unique(result)
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.
@@ -823,16 +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 {
result := make([]T, 0)
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.
@@ -1173,7 +1254,7 @@ func AppendIfAbsent[T comparable](slice []T, item T) []T {
// SetToDefaultIf sets elements to their default value if they match the given predicate.
// It retains the positions of the elements in the slice.
// It returns slice of T and the count of modified slice items
// Play: todo
// Play: https://go.dev/play/p/9AXGlPRC0-A
func SetToDefaultIf[T any](slice []T, predicate func(T) bool) ([]T, int) {
var count int
for i := 0; i < len(slice); i++ {
@@ -1239,6 +1320,30 @@ func Partition[T any](slice []T, predicates ...func(item T) bool) [][]T {
return result
}
// Breaks a list into two parts at the point where the predicate for the first time is true.
// 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)
if len(values) == 0 {
return a, b
}
matched := false
for _, value := range values {
if !matched && predicate(value) {
matched = true
}
if matched {
b = append(b, value)
} else {
a = append(a, value)
}
}
return a, b
}
// Random get a random item of slice, return idx=-1 when slice is empty
// Play: https://go.dev/play/p/UzpGQptWppw
func Random[T any](slice []T) (val T, idx int) {
@@ -1249,3 +1354,47 @@ func Random[T any](slice []T) (val T, idx int) {
idx = random.RandInt(0, len(slice))
return slice[idx], idx
}
// RightPadding adds padding to the right end of a slice.
// Play: https://go.dev/play/p/0_2rlLEMBXL
func RightPadding[T any](slice []T, paddingValue T, paddingLength int) []T {
if paddingLength == 0 {
return slice
}
for i := 0; i < paddingLength; i++ {
slice = append(slice, paddingValue)
}
return slice
}
// LeftPadding adds padding to the left begin of a slice.
// Play: https://go.dev/play/p/jlQVoelLl2k
func LeftPadding[T any](slice []T, paddingValue T, paddingLength int) []T {
if paddingLength == 0 {
return slice
}
paddedSlice := make([]T, len(slice)+paddingLength)
i := 0
for ; i < paddingLength; i++ {
paddedSlice[i] = paddingValue
}
for j := 0; j < len(slice); j++ {
paddedSlice[i] = slice[j]
i++
}
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
View 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
}

View File

@@ -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,7 +1193,71 @@ func ExampleSetToDefaultIf() {
modifiedStrs, count := SetToDefaultIf(strs, func(s string) bool { return "a" == s })
fmt.Println(modifiedStrs)
fmt.Println(count)
// Output:
// [ b c d ]
// 3
}
func ExampleBreak() {
nums := []int{1, 2, 3, 4, 5}
even := func(n int) bool { return n%2 == 0 }
resultEven, resultAfterFirstEven := Break(nums, even)
fmt.Println(resultEven)
fmt.Println(resultAfterFirstEven)
// Output:
// [1]
// [2 3 4 5]
}
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]
}
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]
}

View File

@@ -5,6 +5,7 @@ import (
"math"
"reflect"
"strconv"
"strings"
"testing"
"github.com/duke-git/lancet/v2/internal"
@@ -15,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) {
@@ -29,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) {
@@ -62,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) {
@@ -109,6 +134,19 @@ func TestConcat(t *testing.T) {
assert.Equal([]int{1, 2, 3, 4, 5}, Concat([]int{1, 2, 3}, []int{4}, []int{5}))
}
func BenchmarkConcat(b *testing.B) {
slice1 := []int{1, 2, 3}
slice2 := []int{4, 5, 6}
slice3 := []int{7, 8, 9}
for i := 0; i < b.N; i++ {
result := Concat(slice1, slice2, slice3)
if len(result) == 0 {
b.Fatal("unexpected empty result")
}
}
}
func TestEqual(t *testing.T) {
t.Parallel()
@@ -120,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 .
@@ -149,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) {
@@ -162,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) {
@@ -175,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) {
@@ -188,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) {
@@ -235,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)
@@ -243,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) {
@@ -281,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) {
@@ -344,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()
@@ -396,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()
@@ -506,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()
@@ -514,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) {
@@ -573,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) {
@@ -601,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) {
@@ -618,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) {
@@ -635,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) {
@@ -658,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) {
@@ -681,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) {
@@ -720,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) {
@@ -1357,3 +1650,169 @@ func TestSetToDefaultIf(t *testing.T) {
assert.Equal(2, count)
})
}
func TestBreak(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestBreak")
// Test with integers
nums := []int{1, 2, 3, 4, 5}
even := func(n int) bool { return n%2 == 0 }
resultEven, resultAfterFirstEven := Break(nums, even)
assert.Equal([]int{1}, resultEven)
assert.Equal([]int{2, 3, 4, 5}, resultAfterFirstEven)
// Test with strings
strings := []string{"apple", "banana", "cherry", "date", "elderberry"}
startsWithA := func(s string) bool { return s[0] == 'a' }
resultStartsWithA, resultAfterFirstStartsWithA := Break(strings, startsWithA)
assert.Equal([]string{}, resultStartsWithA)
assert.Equal([]string{"apple", "banana", "cherry", "date", "elderberry"}, resultAfterFirstStartsWithA)
// Test with empty slice
emptySlice := []int{}
resultEmpty, _ := Break(emptySlice, even)
assert.Equal([]int{}, resultEmpty)
// Test with all elements satisfying the predicate
allEven := []int{2, 4, 6, 8, 10}
emptyResult, resultAllEven := Break(allEven, even)
assert.Equal([]int{2, 4, 6, 8, 10}, resultAllEven)
assert.Equal([]int{}, emptyResult)
// Test with no elements satisfying the predicate
allOdd := []int{1, 3, 5, 7, 9}
resultAllOdd, emptyResult := Break(allOdd, even)
assert.Equal([]int{1, 3, 5, 7, 9}, resultAllOdd)
assert.Equal([]int{}, emptyResult)
}
func TestRightPaddingAndLeftPadding(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "RightPaddingAndLeftPadding")
// Test with integers
nums := []int{1, 2, 3, 4, 5}
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)
})
}

View File

@@ -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 {
@@ -585,7 +590,7 @@ func RemoveWhiteSpace(str string, repalceAll bool) string {
}
// SubInBetween return substring between the start and end position(excluded) of source string.
// Play: todo
// Play: https://go.dev/play/p/EDbaRvjeNsv
func SubInBetween(str string, start string, end string) string {
if _, after, ok := strings.Cut(str, start); ok {
if before, _, ok := strings.Cut(after, end); ok {
@@ -599,7 +604,7 @@ func SubInBetween(str string, start string, end string) string {
// HammingDistance calculates the Hamming distance between two strings.
// The Hamming distance is the number of positions at which the corresponding symbols are different.
// This func returns an error if the input strings are of unequal lengths.
// Play: todo
// Play: https://go.dev/play/p/glNdQEA9HUi
func HammingDistance(a, b string) (int, error) {
if len(a) != len(b) {
return -1, errors.New("a length and b length are unequal")
@@ -617,3 +622,116 @@ func HammingDistance(a, b string) (int, error) {
return distance, nil
}
// 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 ""
}
sb := strings.Builder{}
if length <= 0 {
sb.Grow(len(str[0]) * len(str))
} else {
sb.Grow(length)
}
for _, s := range str {
sb.WriteString(s)
}
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
}

View File

@@ -680,3 +680,76 @@ func ExampleHammingDistance() {
// 3
// 1
}
func ExampleConcat() {
result1 := Concat(12, "Hello", " ", "World", "!")
result2 := Concat(11, "Go", " ", "Language")
result3 := 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 daykeeps 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]
}

View File

@@ -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) {
@@ -561,6 +634,7 @@ func TestContainsAny(t *testing.T) {
}
func TestRemoveWhiteSpace(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestRemoveWhiteSpace")
str := " hello \r\n \t world"
@@ -571,6 +645,7 @@ func TestRemoveWhiteSpace(t *testing.T) {
}
func TestSubInBetween(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestSubInBetween")
str := "abcde"
@@ -583,6 +658,7 @@ func TestSubInBetween(t *testing.T) {
}
func TestHammingDistance(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "HammingDistance")
hd := func(a, b string) int {
@@ -590,17 +666,190 @@ 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))
assert.Equal("a", Concat(1, "a"))
assert.Equal("ab", Concat(2, "a", "b"))
assert.Equal("abc", Concat(3, "a", "b", "c"))
assert.Equal("abc", Concat(3, "a", "", "b", "c", ""))
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)
}
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -8,6 +8,7 @@ import (
"encoding/json"
"fmt"
"net"
"net/mail"
"net/url"
"reflect"
"regexp"
@@ -24,7 +25,7 @@ var (
intStrMatcher *regexp.Regexp = regexp.MustCompile(`^[\+-]?\d+$`)
urlMatcher *regexp.Regexp = regexp.MustCompile(`^((ftp|http|https?):\/\/)?(\S+(:\S*)?@)?((([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(([a-zA-Z0-9]+([-\.][a-zA-Z0-9]+)*)|((www\.)?))?(([a-z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-z\x{00a1}-\x{ffff}]{2,}))?))(:(\d{1,5}))?((\/|\?|#)[^\s]*)?$`)
dnsMatcher *regexp.Regexp = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$`)
emailMatcher *regexp.Regexp = regexp.MustCompile(`\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*`)
emailMatcher *regexp.Regexp = regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`)
chineseMobileMatcher *regexp.Regexp = regexp.MustCompile(`^1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\d|9\d)\d{8}$`)
chineseIdMatcher *regexp.Regexp = regexp.MustCompile(`^(\d{17})([0-9]|X|x)$`)
chineseMatcher *regexp.Regexp = regexp.MustCompile("[\u4e00-\u9fa5]")
@@ -213,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.
@@ -223,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.
@@ -264,7 +265,10 @@ func IsDns(dns string) bool {
// IsEmail check if the string is a email address.
// Play: https://go.dev/play/p/Os9VaFlT33G
func IsEmail(email string) bool {
return emailMatcher.MatchString(email)
_, err := mail.ParseAddress(email)
return err == nil
// return emailMatcher.MatchString(email)
}
// IsChineseMobile check if the string is chinese mobile number.

View File

@@ -285,6 +285,7 @@ func TestIsEmail(t *testing.T) {
assert := internal.NewAssert(t, "TestIsEmail")
assert.Equal(true, IsEmail("abc@xyz.com"))
assert.Equal(false, IsEmail("@abc@xyz.com"))
assert.Equal(false, IsEmail("a.b@@com"))
}