mirror of
https://github.com/duke-git/lancet.git
synced 2026-02-04 12:52:28 +08:00
Compare commits
1125 Commits
v2.0.7
...
be45a259db
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be45a259db | ||
|
|
6f703fe577 | ||
|
|
cb8d93c499 | ||
|
|
ae1014c572 | ||
|
|
d5b9e67330 | ||
|
|
a81403766f | ||
|
|
6307d624cb | ||
|
|
2f9f8b3f3d | ||
|
|
db5d9407bb | ||
|
|
55ee000684 | ||
|
|
fc7f2509ca | ||
|
|
a97d27c32e | ||
|
|
c176ba378e | ||
|
|
a3a24fc381 | ||
|
|
9caf2ffb1c | ||
|
|
1a5c31fd02 | ||
|
|
c175b202de | ||
|
|
d818219672 | ||
|
|
539078e6b8 | ||
|
|
cdefbde9f5 | ||
|
|
1e57a743af | ||
|
|
7d4184a365 | ||
|
|
1b73483945 | ||
|
|
622aacaf44 | ||
|
|
e78ac65605 | ||
|
|
03f0d4d905 | ||
|
|
b2ae71c983 | ||
|
|
093f4a2286 | ||
|
|
f7ada6093c | ||
|
|
47e82aad39 | ||
|
|
9e813d236b | ||
|
|
72a23d2cb9 | ||
|
|
27667f8b3a | ||
|
|
0b5dc86d70 | ||
|
|
d88bba07dd | ||
|
|
d7f3354b98 | ||
|
|
f4dee28ebb | ||
|
|
c841a5b88c | ||
|
|
333038634b | ||
|
|
ef0fed23b2 | ||
|
|
1e0ee1fac1 | ||
|
|
4947327ed6 | ||
|
|
ceb706b874 | ||
|
|
3849337919 | ||
|
|
f4427b9fbc | ||
|
|
0ef45b533b | ||
|
|
169f280c9c | ||
|
|
e74126a0bd | ||
|
|
f7be4190f1 | ||
|
|
8089b71bfd | ||
|
|
5b9543255a | ||
|
|
bf859387f4 | ||
|
|
a8a92844f3 | ||
|
|
6dfdadd34e | ||
|
|
8120c4db78 | ||
|
|
0d0f213d36 | ||
|
|
41d4bbf0e3 | ||
|
|
fed1d5220e | ||
|
|
29a8318d6e | ||
|
|
3696213e5d | ||
|
|
90a3b87b67 | ||
|
|
a7b28ee864 | ||
|
|
f2823014f2 | ||
|
|
4181c42805 | ||
|
|
a09f5623d6 | ||
|
|
a9c75b081d | ||
|
|
db479ef1bc | ||
|
|
df8121fbbd | ||
|
|
0e7297cb97 | ||
|
|
2e619e48a3 | ||
|
|
3069acba4a | ||
|
|
fc43138a0e | ||
|
|
1e56e9964c | ||
|
|
f861e18bc3 | ||
|
|
e27df00fa8 | ||
|
|
23e61f1acf | ||
|
|
cb308f628c | ||
|
|
7653afa919 | ||
|
|
a0d97cf38e | ||
|
|
1f6bab467c | ||
|
|
2e5b9bc200 | ||
|
|
ab89f0aee1 | ||
|
|
1f64e02df4 | ||
|
|
d4b425e39c | ||
|
|
a1652c7523 | ||
|
|
ecafed511c | ||
|
|
4ffff0e3f3 | ||
|
|
8ebb8a028e | ||
|
|
995ffb799f | ||
|
|
3cd546d7f2 | ||
|
|
7fb49515ce | ||
|
|
8f3ea60636 | ||
|
|
58a37b7e8d | ||
|
|
6bd61460d3 | ||
|
|
05b85a2131 | ||
|
|
1eb793420e | ||
|
|
05f9854945 | ||
|
|
3be706e23f | ||
|
|
b5e7312353 | ||
|
|
95a894e53f | ||
|
|
8322951475 | ||
|
|
6b2c91b0f6 | ||
|
|
ec161f335d | ||
|
|
d1a8f37a71 | ||
|
|
a769257017 | ||
|
|
f3579fc142 | ||
|
|
de877e5278 | ||
|
|
08f14d2b08 | ||
|
|
0ed2b11ba1 | ||
|
|
643edc1468 | ||
|
|
e2ff83649a | ||
|
|
a7fecfc73b | ||
|
|
8bbae69175 | ||
|
|
840ea8f3c5 | ||
|
|
a4e89bd7c1 | ||
|
|
2015d36b08 | ||
|
|
921f218ef7 | ||
|
|
0a2cc9c928 | ||
|
|
ed93aae970 | ||
|
|
1671f7856a | ||
|
|
1008dd4956 | ||
|
|
a254ebdc8e | ||
|
|
0bc11001a4 | ||
|
|
85d98ad915 | ||
|
|
cb08613ac3 | ||
|
|
bad1b05224 | ||
|
|
527328739a | ||
|
|
213e2b4ead | ||
|
|
5d6ab72059 | ||
|
|
30eb2c72b0 | ||
|
|
adf18a2e47 | ||
|
|
f99a8ef3cf | ||
|
|
fcdf1d5839 | ||
|
|
ee0afed963 | ||
|
|
1d94896c9b | ||
|
|
69cf9bbcf0 | ||
|
|
2bbcb85286 | ||
|
|
e58c9b797b | ||
|
|
3e1ac5e0b5 | ||
|
|
8869e0440d | ||
|
|
69f9c74bcb | ||
|
|
84ebc7ce71 | ||
|
|
c745097749 | ||
|
|
ba75e58e5f | ||
|
|
7e85a0ed7d | ||
|
|
2268a0312f | ||
|
|
da84d95aa3 | ||
|
|
48244d6711 | ||
|
|
5e3337a52e | ||
|
|
c3372e18b1 | ||
|
|
90e5a0bfb2 | ||
|
|
93be25920f | ||
|
|
f9e5ec9096 | ||
|
|
601df5dc12 | ||
|
|
63216d9b1c | ||
|
|
c32a19868d | ||
|
|
71e914019b | ||
|
|
9824db0056 | ||
|
|
ba9188a29a | ||
|
|
8625fbd8d3 | ||
|
|
81b29baf30 | ||
|
|
5a38e34063 | ||
|
|
159168dd7b | ||
|
|
ec092a009a | ||
|
|
ca40b5d6c6 | ||
|
|
a6d39a3bba | ||
|
|
38148978cf | ||
|
|
3e8c3bd396 | ||
|
|
30971c1aab | ||
|
|
bc260277bc | ||
|
|
c0b200f846 | ||
|
|
305847993c | ||
|
|
f5d70728c3 | ||
|
|
c2a5335bc6 | ||
|
|
7b4e060f85 | ||
|
|
a360372aa9 | ||
|
|
7f78a6b11e | ||
|
|
5c53cb5867 | ||
|
|
f7e9d5dc47 | ||
|
|
5c580ed013 | ||
|
|
0f9764f41e | ||
|
|
0bc5f82554 | ||
|
|
e91965b013 | ||
|
|
483a286d8e | ||
|
|
3f8e306ced | ||
|
|
0b29f0520d | ||
|
|
8611ec0c10 | ||
|
|
286e10d189 | ||
|
|
3e7f94b03e | ||
|
|
356351896d | ||
|
|
9be124211e | ||
|
|
f467658481 | ||
|
|
5c9d0e396e | ||
|
|
8f74460c1b | ||
|
|
d5752499bf | ||
|
|
eb7cf76eae | ||
|
|
4af074d181 | ||
|
|
73fb8fefd2 | ||
|
|
9cf535055d | ||
|
|
8be7b3e396 | ||
|
|
dac706d700 | ||
|
|
2097277a7d | ||
|
|
95b516e278 | ||
|
|
ca373b00a7 | ||
|
|
a220220f09 | ||
|
|
aeef0418a4 | ||
|
|
9b7d8d7abf | ||
|
|
4d21e81263 | ||
|
|
ce2397422e | ||
|
|
4b3a62b36a | ||
|
|
e054680d20 | ||
|
|
5381842eec | ||
|
|
6e0498514c | ||
|
|
967e6a3493 | ||
|
|
5b24801e49 | ||
|
|
974ba525a6 | ||
|
|
f0235c40b6 | ||
|
|
712a215ea6 | ||
|
|
7893f828d3 | ||
|
|
53fa210f09 | ||
|
|
de9ee08be4 | ||
|
|
5381450bea | ||
|
|
6853d627f4 | ||
|
|
e461acdb72 | ||
|
|
2a796adf85 | ||
|
|
5e6e8d82a8 | ||
|
|
e9280b8c25 | ||
|
|
bb6f10a1fb | ||
|
|
33b4cffe60 | ||
|
|
2b765b49e0 | ||
|
|
004dbdc32e | ||
|
|
ab50e8120a | ||
|
|
73c97af7d8 | ||
|
|
5e8a065eaa | ||
|
|
aa74400607 | ||
|
|
a6eaaef563 | ||
|
|
1b31014f81 | ||
|
|
036847577d | ||
|
|
d21edd1cde | ||
|
|
c58c50327c | ||
|
|
9bfdc686f8 | ||
|
|
5ca8f6ef6f | ||
|
|
a54d4c79a0 | ||
|
|
f7b54986aa | ||
|
|
92fae4273b | ||
|
|
0d29f5437a | ||
|
|
e95d7c82cd | ||
|
|
e138043289 | ||
|
|
aabfcb7bde | ||
|
|
0b5e884371 | ||
|
|
3d1bd08434 | ||
|
|
a62ad71791 | ||
|
|
c02c4f813b | ||
|
|
235d2f2486 | ||
|
|
e9380a3d9f | ||
|
|
81d13c2f1a | ||
|
|
7290296849 | ||
|
|
8a8460a592 | ||
|
|
7a98c431d3 | ||
|
|
606d887230 | ||
|
|
473f9c9f3e | ||
|
|
9ff3d0e79c | ||
|
|
5db1d07d6d | ||
|
|
6c6d14828a | ||
|
|
0e1593c67b | ||
|
|
6c7f38d8b3 | ||
|
|
069812e0ee | ||
|
|
4a539a23c8 | ||
|
|
0b1dab0399 | ||
|
|
805e2543d0 | ||
|
|
a3d518da76 | ||
|
|
e3e2d8394c | ||
|
|
0eeaa06055 | ||
|
|
a43bc554ee | ||
|
|
aebab7c944 | ||
|
|
665bad4ca3 | ||
|
|
e4901e99e9 | ||
|
|
4277e8eca5 | ||
|
|
fdc93c8cc7 | ||
|
|
860a499f98 | ||
|
|
2e1c2276a5 | ||
|
|
d367397dab | ||
|
|
66fd8cf651 | ||
|
|
a6be1828b9 | ||
|
|
8f5d297572 | ||
|
|
a1a4fdc598 | ||
|
|
1610076d22 | ||
|
|
cacbf97223 | ||
|
|
cd156dba5f | ||
|
|
3a71a8697d | ||
|
|
c88fd3db86 | ||
|
|
27d19d1717 | ||
|
|
da24bae6b4 | ||
|
|
3cd9d6b68c | ||
|
|
874d09f331 | ||
|
|
fdf251ac98 | ||
|
|
7ec2533b7a | ||
|
|
9fd0603f4a | ||
|
|
9f7b416a8d | ||
|
|
bf4b2b5fd6 | ||
|
|
22af59565e | ||
|
|
f9e047f190 | ||
|
|
fa298b740d | ||
|
|
6d4fc981b6 | ||
|
|
4c21fe700c | ||
|
|
b7370e8ef8 | ||
|
|
38920e3be6 | ||
|
|
a630a7cda9 | ||
|
|
66dfd9c4fd | ||
|
|
be62aaac9b | ||
|
|
e0c9ccbce3 | ||
|
|
d2d1e5a055 | ||
|
|
bbc58c7e46 | ||
|
|
ac2ecceaec | ||
|
|
a06bb8ee6a | ||
|
|
27b5702fd3 | ||
|
|
b2c3fa0ab8 | ||
|
|
4afc838937 | ||
|
|
3482f80d1c | ||
|
|
565f2893b9 | ||
|
|
1b1b10d0ee | ||
|
|
c5c3888ffc | ||
|
|
11214986cc | ||
|
|
0bc7b83e59 | ||
|
|
6225418074 | ||
|
|
ddd265de78 | ||
|
|
80e48f06ca | ||
|
|
0b976e9a4c | ||
|
|
96320069f4 | ||
|
|
c5297ec329 | ||
|
|
aa4b61ff85 | ||
|
|
7dbd7002a3 | ||
|
|
a995db445a | ||
|
|
6e5b67bee7 | ||
|
|
52b8ea8166 | ||
|
|
6fe8a9efe7 | ||
|
|
dcef06e9da | ||
|
|
8f410bf9cb | ||
|
|
9cd6eb4ddf | ||
|
|
bf581162ee | ||
|
|
bd984fa378 | ||
|
|
d7f23e2dee | ||
|
|
3802c715c3 | ||
|
|
4b12173f24 | ||
|
|
31c618c187 | ||
|
|
6497b321b0 | ||
|
|
bda78201f5 | ||
|
|
0753ea2801 | ||
|
|
e25b53712b | ||
|
|
56c9327a86 | ||
|
|
11eb559998 | ||
|
|
61a612a06a | ||
|
|
2351ab4714 | ||
|
|
88eec858b4 | ||
|
|
14c37b5a5f | ||
|
|
ad8b1d424c | ||
|
|
a9f01d8a69 | ||
|
|
781b89d51a | ||
|
|
073c77e751 | ||
|
|
91a0d3077d | ||
|
|
17b34f8f19 | ||
|
|
8ff37f0eff | ||
|
|
f445ecbaf8 | ||
|
|
b698fec50f | ||
|
|
b38bb66b34 | ||
|
|
bdfdeaf496 | ||
|
|
172c44c07a | ||
|
|
534263eb08 | ||
|
|
a25b1ac7e3 | ||
|
|
d84f9777ea | ||
|
|
f9caaf8063 | ||
|
|
f198711d1c | ||
|
|
19378ca4d1 | ||
|
|
71c7733eb0 | ||
|
|
20786c360b | ||
|
|
51fafa110e | ||
|
|
07fc453b74 | ||
|
|
b309044981 | ||
|
|
541e6d4ea3 | ||
|
|
4037b96cc4 | ||
|
|
8e484c4a6f | ||
|
|
dd339563bc | ||
|
|
9567dcc57f | ||
|
|
de104ebeb6 | ||
|
|
eebd95395f | ||
|
|
56265647ff | ||
|
|
756da3719d | ||
|
|
f3008ac7c4 | ||
|
|
c6583d3b83 | ||
|
|
a3d7bac72e | ||
|
|
74b4d53514 | ||
|
|
a08afe0d77 | ||
|
|
ee680aa84c | ||
|
|
caa74064f0 | ||
|
|
abc94c0b26 | ||
|
|
86bcdce07b | ||
|
|
789f5d933d | ||
|
|
156fd2ef5d | ||
|
|
ea036b3a81 | ||
|
|
3daca8b331 | ||
|
|
21a952d2be | ||
|
|
03d331dfb6 | ||
|
|
ef79e1305e | ||
|
|
7c2670d019 | ||
|
|
31f64c0a98 | ||
|
|
28b8986cbe | ||
|
|
b4ff33ce80 | ||
|
|
25fa117712 | ||
|
|
0c9f539e3d | ||
|
|
05bd1afaa6 | ||
|
|
51cb665110 | ||
|
|
47c368e8f2 | ||
|
|
3127360b28 | ||
|
|
5b6def5ff0 | ||
|
|
33d99ad7d4 | ||
|
|
8bd31d39c3 | ||
|
|
a418549525 | ||
|
|
1f849efe1c | ||
|
|
8ad374bb21 | ||
|
|
a69d886565 | ||
|
|
cf58542b4a | ||
|
|
51f166d1d9 | ||
|
|
fbeb031b40 | ||
|
|
095cfc0aab | ||
|
|
e66ab154bc | ||
|
|
91bc1a512c | ||
|
|
8753255026 | ||
|
|
9cf1f13fec | ||
|
|
5d78bae4bb | ||
|
|
27777eecc0 | ||
|
|
1a7e0e8792 | ||
|
|
02b7aa8f33 | ||
|
|
f188d3d08f | ||
|
|
0c924b859e | ||
|
|
c5a5a07462 | ||
|
|
1ff5dd6df0 | ||
|
|
b5f86a488c | ||
|
|
1390b7a964 | ||
|
|
c3e28a9fc0 | ||
|
|
e924429d6e | ||
|
|
7079b1f704 | ||
|
|
40cad365c0 | ||
|
|
6386ab908d | ||
|
|
bb563724c7 | ||
|
|
72924d4486 | ||
|
|
231f8b04b4 | ||
|
|
daa932fee3 | ||
|
|
1b0691f1d5 | ||
|
|
0b6a00bd99 | ||
|
|
4ab98664bb | ||
|
|
31eb5f4d1f | ||
|
|
0ae5d17a06 | ||
|
|
4558d7a3c2 | ||
|
|
4715301240 | ||
|
|
1c58ee23f1 | ||
|
|
2a303d5e4b | ||
|
|
326e7881a6 | ||
|
|
31e8b12674 | ||
|
|
a0431d9435 | ||
|
|
0456b65cc7 | ||
|
|
989b4dd791 | ||
|
|
a76b02fbba | ||
|
|
2d7747738a | ||
|
|
fe0cb04137 | ||
|
|
fe0264f628 | ||
|
|
65396cee32 | ||
|
|
544702b460 | ||
|
|
d8936cdcb5 | ||
|
|
d4b97d6b20 | ||
|
|
5caa14c838 | ||
|
|
0d0848ac67 | ||
|
|
d5334f892f | ||
|
|
36ef5b3bd3 | ||
|
|
6f48b00c88 | ||
|
|
7b744c299e | ||
|
|
154ec56780 | ||
|
|
584aabdf62 | ||
|
|
1b754a6264 | ||
|
|
06a558d797 | ||
|
|
423779d3ff | ||
|
|
78c17a9e7b | ||
|
|
529b9317d0 | ||
|
|
dc838f645b | ||
|
|
7f559c4940 | ||
|
|
acb08dfd90 | ||
|
|
ffe46fd06d | ||
|
|
b419e46b9b | ||
|
|
8fbcdfd515 | ||
|
|
f03f48210c | ||
|
|
742944e2f0 | ||
|
|
c0b491ad78 | ||
|
|
e9abae2a92 | ||
|
|
8229de2f10 | ||
|
|
ab364744b6 | ||
|
|
17ff84fa1f | ||
|
|
bf50baa07d | ||
|
|
7d56da8108 | ||
|
|
16c2df711b | ||
|
|
015f8c3f5c | ||
|
|
ba25701d89 | ||
|
|
a0cb4bb266 | ||
|
|
d6ba7497f9 | ||
|
|
7da931e0a0 | ||
|
|
8f49078eb3 | ||
|
|
3050d93703 | ||
|
|
efcfbfb6a1 | ||
|
|
844b7a2c3b | ||
|
|
c8a536eafc | ||
|
|
0fd268face | ||
|
|
8ced7e887f | ||
|
|
957568ed5c | ||
|
|
fffabd0ffa | ||
|
|
e839af3ef9 | ||
|
|
1650e70d04 | ||
|
|
23382b2b76 | ||
|
|
daa3aa3da2 | ||
|
|
be355a23f3 | ||
|
|
95af83b0cb | ||
|
|
1efdbc0973 | ||
|
|
9b829aa695 | ||
|
|
bfa9091c09 | ||
|
|
c11d63c2e2 | ||
|
|
83832daeb1 | ||
|
|
7311f84772 | ||
|
|
a63d78111e | ||
|
|
e2fc2f1bc9 | ||
|
|
23a0135947 | ||
|
|
013b6457bb | ||
|
|
e08a62b0ba | ||
|
|
850800a233 | ||
|
|
a6a8fd88bc | ||
|
|
2f6ee84443 | ||
|
|
b1c6614549 | ||
|
|
a8761eefb0 | ||
|
|
cbf8cfdffa | ||
|
|
286a187942 | ||
|
|
b787e99528 | ||
|
|
e54c9b2850 | ||
|
|
8944109c4c | ||
|
|
10e3732f32 | ||
|
|
a415597c6b | ||
|
|
69b32fd043 | ||
|
|
75ed359084 | ||
|
|
2c71b6375c | ||
|
|
2894bec80c | ||
|
|
09ec5b97a6 | ||
|
|
46ecb117a5 | ||
|
|
388171e739 | ||
|
|
6fbaf2b005 | ||
|
|
53a91cad71 | ||
|
|
a51a182fb2 | ||
|
|
64982f0c89 | ||
|
|
f38f69ce17 | ||
|
|
a33ea3d013 | ||
|
|
e35462fb14 | ||
|
|
af106a4a8e | ||
|
|
ec7232ec40 | ||
|
|
67c1b54b5a | ||
|
|
e149ae2f72 | ||
|
|
b1fcfce188 | ||
|
|
259dbce85e | ||
|
|
78aa679670 | ||
|
|
3beb769f09 | ||
|
|
8af02ff95b | ||
|
|
ba4485d8c0 | ||
|
|
8a1dd40738 | ||
|
|
85e2806531 | ||
|
|
1616d3d1be | ||
|
|
aa8e0d5c12 | ||
|
|
f05477d9cf | ||
|
|
cd91e16b26 | ||
|
|
9fcf046fb3 | ||
|
|
c3f1bc39d7 | ||
|
|
f5784b0f46 | ||
|
|
c86a8a479d | ||
|
|
e2aeb8ec07 | ||
|
|
654ba15aaf | ||
|
|
945c59896b | ||
|
|
219e31d929 | ||
|
|
82cb86b35c | ||
|
|
f93c561f5d | ||
|
|
4888909208 | ||
|
|
33c8875d14 | ||
|
|
99cb1b13a3 | ||
|
|
42ec189995 | ||
|
|
424c291813 | ||
|
|
4311b9ac66 | ||
|
|
581e338889 | ||
|
|
f399425c2a | ||
|
|
dcdb29334d | ||
|
|
4859f3ca23 | ||
|
|
9f2528842e | ||
|
|
6066d6669f | ||
|
|
e3804e9534 | ||
|
|
2f51397d2c | ||
|
|
11217a11c7 | ||
|
|
fa81ee143e | ||
|
|
62891f20f8 | ||
|
|
5e79eaa9dd | ||
|
|
aaf45012e4 | ||
|
|
48c17ea1ce | ||
|
|
e90283c3f9 | ||
|
|
f82c4a305d | ||
|
|
05997603a9 | ||
|
|
6d5ec807f7 | ||
|
|
f73c7e7e86 | ||
|
|
c137428b9e | ||
|
|
9f68620b37 | ||
|
|
2cdbba56a4 | ||
|
|
01a3b139c0 | ||
|
|
fcb3b97b45 | ||
|
|
52ea64bc33 | ||
|
|
14bc08c6d6 | ||
|
|
47bdd6718a | ||
|
|
18d27604e6 | ||
|
|
66bd339e3a | ||
|
|
04abb7a3ea | ||
|
|
975c303a31 | ||
|
|
3b8dd94a5c | ||
|
|
815791c0b6 | ||
|
|
a12a691ee6 | ||
|
|
247cf89947 | ||
|
|
c3fad62d8c | ||
|
|
a8a96be21b | ||
|
|
8e297769b2 | ||
|
|
98cdf1c040 | ||
|
|
d7976e31a4 | ||
|
|
5b11a8b457 | ||
|
|
d4a16534f2 | ||
|
|
ecf0688788 | ||
|
|
90f9cad1ea | ||
|
|
4a298876e9 | ||
|
|
74474cd9ef | ||
|
|
f23f18457e | ||
|
|
18f01ffd75 | ||
|
|
c53d541a6b | ||
|
|
5b9b4c4344 | ||
|
|
6e7300bbbf | ||
|
|
3685aee02b | ||
|
|
046f3e0bf9 | ||
|
|
c01c9d14b4 | ||
|
|
f198191063 | ||
|
|
8bdd46bda4 | ||
|
|
e29b56c3c3 | ||
|
|
c357fc68c8 | ||
|
|
bc25e7a037 | ||
|
|
217350042b | ||
|
|
e56a8a1ef5 | ||
|
|
6453f755a6 | ||
|
|
027abd6ad5 | ||
|
|
91503b1656 | ||
|
|
e2522cd29b | ||
|
|
da69b77892 | ||
|
|
05772d8d7d | ||
|
|
3b497532f3 | ||
|
|
1cd3be508c | ||
|
|
a41d461910 | ||
|
|
cde5946bf0 | ||
|
|
c28803b25e | ||
|
|
f09e521783 | ||
|
|
0aa41f337d | ||
|
|
48814a720a | ||
|
|
04b79b3dfe | ||
|
|
302007ebdf | ||
|
|
965e5fbcda | ||
|
|
70d0adde42 | ||
|
|
0b80074bb7 | ||
|
|
90945a0399 | ||
|
|
47dccd63af | ||
|
|
4ae7e59829 | ||
|
|
8f0c60cade | ||
|
|
3f6aef1432 | ||
|
|
a714e04470 | ||
|
|
7456621153 | ||
|
|
73ac9825e9 | ||
|
|
930bb9c839 | ||
|
|
3d8f1be212 | ||
|
|
13a4ed59fa | ||
|
|
c799d10ce9 | ||
|
|
5ab322ade2 | ||
|
|
d0ffc61842 | ||
|
|
5e66bc6227 | ||
|
|
7261b281ad | ||
|
|
f79693804b | ||
|
|
534c7a0abc | ||
|
|
4eaff47d38 | ||
|
|
3e019522c7 | ||
|
|
0734f220b3 | ||
|
|
2d2c277090 | ||
|
|
ef1e548dfc | ||
|
|
924589d2da | ||
|
|
77f32f4cc6 | ||
|
|
1755dd249b | ||
|
|
7a25688ec1 | ||
|
|
51a6912eb3 | ||
|
|
28d0428b50 | ||
|
|
6a9eb645bb | ||
|
|
3857b342f6 | ||
|
|
71aa91a58d | ||
|
|
081908bce3 | ||
|
|
66efe61834 | ||
|
|
94f7f3e0e5 | ||
|
|
9d2c9806d1 | ||
|
|
e523d4af6e | ||
|
|
f3801bcd8f | ||
|
|
5767aad303 | ||
|
|
f7aaa1ed2a | ||
|
|
ef19a414bc | ||
|
|
65704dea06 | ||
|
|
5eac358bb0 | ||
|
|
8e36ef5cef | ||
|
|
21b0d2ec0e | ||
|
|
32ca975204 | ||
|
|
ec740e442c | ||
|
|
ed98ad53ec | ||
|
|
af7b9d2710 | ||
|
|
f08b368001 | ||
|
|
be8a0558f8 | ||
|
|
46de539e22 | ||
|
|
36fb3abe9e | ||
|
|
fee8d325b7 | ||
|
|
8784be1583 | ||
|
|
84cd68de7f | ||
|
|
a774c060ce | ||
|
|
730f061b95 | ||
|
|
2eb08f3404 | ||
|
|
4c1496b648 | ||
|
|
c2ae784f27 | ||
|
|
e71cecefea | ||
|
|
572e53aa14 | ||
|
|
ca9ecb9c8a | ||
|
|
8bf0786abe | ||
|
|
61cb8395e3 | ||
|
|
dde8a41daf | ||
|
|
d7518e01af | ||
|
|
be9fa7acaa | ||
|
|
2629a731cc | ||
|
|
54c7f90b7f | ||
|
|
770bc88b88 | ||
|
|
4cc1722f81 | ||
|
|
d0260b2841 | ||
|
|
57bd64cae7 | ||
|
|
c383719496 | ||
|
|
4b196a72b1 | ||
|
|
888381a06c | ||
|
|
a554eb7ef4 | ||
|
|
26ff90122b | ||
|
|
75b27c6540 | ||
|
|
a7e77fa98d | ||
|
|
abf392117a | ||
|
|
d231d9f572 | ||
|
|
97447d058e | ||
|
|
040e112aa6 | ||
|
|
afb021b4b5 | ||
|
|
5075774000 | ||
|
|
6bba44dc50 | ||
|
|
422022c74d | ||
|
|
87fcf97e2d | ||
|
|
05d1f348d4 | ||
|
|
6f2f1f3004 | ||
|
|
9cd9d1aeb5 | ||
|
|
71b27c0aa9 | ||
|
|
09a379ec6d | ||
|
|
b4b9b03835 | ||
|
|
48c7794b01 | ||
|
|
8e3911833d | ||
|
|
ebe494051b | ||
|
|
c35bda6a65 | ||
|
|
1fe4cdc429 | ||
|
|
6a79e322e3 | ||
|
|
17e8d2bb6d | ||
|
|
325be0d6a1 | ||
|
|
ea0f96a8c0 | ||
|
|
82cbb54787 | ||
|
|
585d33cafa | ||
|
|
bc4cf35e15 | ||
|
|
a3bc20af1d | ||
|
|
61338b6b46 | ||
|
|
bc3c080ac3 | ||
|
|
d3fab15af3 | ||
|
|
6e3e411d46 | ||
|
|
f976941e36 | ||
|
|
4c5524354c | ||
|
|
2c6e9a3fb9 | ||
|
|
5f2c3edff4 | ||
|
|
b422d98702 | ||
|
|
6f27e0bfbf | ||
|
|
ce3b6b461e | ||
|
|
0d6ad4f0d2 | ||
|
|
c875a7f8b8 | ||
|
|
0c62d117a1 | ||
|
|
c260ce493d | ||
|
|
9ffe96d3ef | ||
|
|
d4bba76dc8 | ||
|
|
adaa3ebc43 | ||
|
|
61d38ae3b8 | ||
|
|
c85d910044 | ||
|
|
d95a7c6101 | ||
|
|
bce3641ec6 | ||
|
|
ceb134b2fd | ||
|
|
4b9b1d32c5 | ||
|
|
395e0883c7 | ||
|
|
a930511054 | ||
|
|
7380721ccc | ||
|
|
936011dc3b | ||
|
|
bc0ee08f38 | ||
|
|
f1afd753d4 | ||
|
|
b05a0a91c3 | ||
|
|
da2eb66657 | ||
|
|
ad20159de2 | ||
|
|
278733d3d1 | ||
|
|
2f184907ff | ||
|
|
50fcc718ee | ||
|
|
cc68feb52d | ||
|
|
ca2a51b37e | ||
|
|
be444f521d | ||
|
|
e21dd07d46 | ||
|
|
4044deac70 | ||
|
|
d9c6294775 | ||
|
|
d8505d1a5f | ||
|
|
6498c7d68a | ||
|
|
5f0211f0c4 | ||
|
|
1de2e2cedd | ||
|
|
c8f8b1b7d9 | ||
|
|
3062eb7789 | ||
|
|
64d5486cc6 | ||
|
|
6d57891f66 | ||
|
|
927245e47f | ||
|
|
31fdbee0b5 | ||
|
|
3712819994 | ||
|
|
6d7dec1cea | ||
|
|
9b6a004dbc | ||
|
|
3ad6f4bd9e | ||
|
|
b8c6746f31 | ||
|
|
0bf8bbf4cb | ||
|
|
a6ba1028c5 | ||
|
|
cc54dd7ec9 | ||
|
|
54834dba4c | ||
|
|
e996d4c945 | ||
|
|
ae92ae7666 | ||
|
|
526e568d0e | ||
|
|
1dc5e8ac23 | ||
|
|
b5f7b0e670 | ||
|
|
39c576248c | ||
|
|
eb164d1536 | ||
|
|
68e170080c | ||
|
|
652b09135c | ||
|
|
bff24c89bc | ||
|
|
49a460eef8 | ||
|
|
a58e52e53c | ||
|
|
b07356423f | ||
|
|
005dd9d2ab | ||
|
|
65315dafb1 | ||
|
|
b06fb6736d | ||
|
|
b9f0854950 | ||
|
|
6a2dd328ad | ||
|
|
dd1147f6d0 | ||
|
|
6da7ce64af | ||
|
|
b7e5d946f1 | ||
|
|
687db4ce79 | ||
|
|
a9a4bb8841 | ||
|
|
bc6cb5f61b | ||
|
|
2c57266f8e | ||
|
|
57e49c9520 | ||
|
|
985c9a5d9a | ||
|
|
5cfb11f036 | ||
|
|
5b3d48a1e7 | ||
|
|
d0576e028f | ||
|
|
76bdec2b54 | ||
|
|
fa20aba3a7 | ||
|
|
7a4a429e23 | ||
|
|
a70ec6ad1e | ||
|
|
e435fa271b | ||
|
|
1533d00891 | ||
|
|
8b99641de0 | ||
|
|
251f899f18 | ||
|
|
00407e5182 | ||
|
|
4e457ad672 | ||
|
|
7d8d9c3543 | ||
|
|
1197e8d1b6 | ||
|
|
13bbe19ab2 | ||
|
|
2725575d2f | ||
|
|
037d2729ce | ||
|
|
09d98745b0 | ||
|
|
af5cfe6da1 | ||
|
|
d59259bbe0 | ||
|
|
3189628d54 | ||
|
|
62c5e251a5 | ||
|
|
6e6444c8c0 | ||
|
|
dd613e98b2 | ||
|
|
2d905ab03e | ||
|
|
205fedb197 | ||
|
|
4c864da62d | ||
|
|
263ab7e316 | ||
|
|
809b7a53df | ||
|
|
61c43daabb | ||
|
|
18914ee2cd | ||
|
|
0a8058956f | ||
|
|
a044da7d2f | ||
|
|
f8b785c4cb | ||
|
|
82c8a04c35 | ||
|
|
280ecb5cef | ||
|
|
ec27ad4c40 | ||
|
|
d66f92cd68 | ||
|
|
d8ed692651 | ||
|
|
a16de97d1d | ||
|
|
6f458e4367 | ||
|
|
37c7508ad0 | ||
|
|
acb5844b15 | ||
|
|
76f4eeea16 | ||
|
|
5466a23019 | ||
|
|
5692982dd1 | ||
|
|
c39c8914fb | ||
|
|
29bdca1bd2 | ||
|
|
eb66d038ac | ||
|
|
a99ada5ee1 | ||
|
|
a87faf5453 | ||
|
|
ab6fec2f69 | ||
|
|
7b290989f5 | ||
|
|
5722c724e6 | ||
|
|
f84584ca04 | ||
|
|
80cbbdc787 | ||
|
|
be148e07ba | ||
|
|
d36ab5cc3a | ||
|
|
2b17329094 | ||
|
|
f869a0a670 | ||
|
|
be000a4bd6 | ||
|
|
81efa800ea | ||
|
|
a783de57a8 | ||
|
|
a622959a78 | ||
|
|
f709dd53ce | ||
|
|
ee9e9625e2 | ||
|
|
84da7d4f27 | ||
|
|
089fd4e13c | ||
|
|
6c40e02324 | ||
|
|
a270b1b634 | ||
|
|
260fb795d3 | ||
|
|
8c036f830c | ||
|
|
bf332b9f1c | ||
|
|
6b6cd66f9f | ||
|
|
5399c2290e | ||
|
|
6248293c49 | ||
|
|
eced25b76d | ||
|
|
96a4327aa7 | ||
|
|
fcfbdea597 | ||
|
|
87896f917a | ||
|
|
b8563ed646 | ||
|
|
1ccf0af2b3 | ||
|
|
6314889c6a | ||
|
|
294bd5a5ed | ||
|
|
ca44815fd5 | ||
|
|
bcd1cabf80 | ||
|
|
4edefcca67 | ||
|
|
fab24c8d12 | ||
|
|
604acd9b07 | ||
|
|
531cb19fd1 | ||
|
|
4f0161ca53 | ||
|
|
73362b7f69 | ||
|
|
b289f2975b | ||
|
|
90ce2705ca | ||
|
|
35e1d09ce3 | ||
|
|
d67d8fad3a | ||
|
|
c5e6d01a31 | ||
|
|
6d6c3f692f | ||
|
|
c695837b16 | ||
|
|
ef28b52963 | ||
|
|
18b2b6ff7c | ||
|
|
6a05a123f4 | ||
|
|
f7c33f258d | ||
|
|
04058dd7da | ||
|
|
72e1d92fa1 | ||
|
|
063df0f0d1 | ||
|
|
fc10689b25 | ||
|
|
9239bcfdc3 | ||
|
|
c984815dea | ||
|
|
301cb5db87 | ||
|
|
cc1bacff74 | ||
|
|
f49f75b371 | ||
|
|
b30f4a7bab | ||
|
|
982cb8932b | ||
|
|
9107eb4b32 | ||
|
|
1acf977b24 | ||
|
|
5c878d0873 | ||
|
|
ab0716a472 | ||
|
|
3c2e0ca5b3 | ||
|
|
551e66ba29 | ||
|
|
4eeeabb227 | ||
|
|
b3437fdddf | ||
|
|
312dcab369 | ||
|
|
0cb89f4f46 | ||
|
|
a2dec87995 | ||
|
|
9094cb29bf | ||
|
|
ddb992ad2f | ||
|
|
24f18aaaec | ||
|
|
36169874e5 | ||
|
|
23aeb6ac99 | ||
|
|
a1984f0a59 | ||
|
|
c95db23d2c | ||
|
|
fbf251d805 | ||
|
|
337f08a04b | ||
|
|
c6a7371049 | ||
|
|
b697858038 | ||
|
|
5446f7e33c | ||
|
|
553f63e76b | ||
|
|
fc3e94df58 | ||
|
|
5c66f38a5b | ||
|
|
70e213b3f7 | ||
|
|
c1b7500bcb | ||
|
|
3d7600a9e4 | ||
|
|
0299c454ab | ||
|
|
741af66404 | ||
|
|
ef5d0379a1 | ||
|
|
105ab49763 | ||
|
|
b5ba9ba573 | ||
|
|
ac3baac5c6 | ||
|
|
a82b5dd206 | ||
|
|
ac0fb5ef25 | ||
|
|
9bd1c205fe | ||
|
|
6ccf9fd3cf | ||
|
|
c02ef2c62d | ||
|
|
6414031754 | ||
|
|
5336130570 | ||
|
|
bfa46d46a2 | ||
|
|
aab28b914c | ||
|
|
336e454ce7 | ||
|
|
35a50bd792 | ||
|
|
2fd23f02f6 | ||
|
|
aad5b447c9 | ||
|
|
cece13e929 | ||
|
|
ecf325a06c | ||
|
|
44370aef5e | ||
|
|
1782b11ec4 | ||
|
|
fe6495123c | ||
|
|
d442f564ce | ||
|
|
f35446cc13 | ||
|
|
506a8b4776 | ||
|
|
47ecfbfd5f | ||
|
|
82f7401368 | ||
|
|
068c878d1e | ||
|
|
47b2402345 | ||
|
|
a245716f99 | ||
|
|
71c85bb8f0 | ||
|
|
5edafec393 | ||
|
|
957878dd98 | ||
|
|
12e979cf3c | ||
|
|
ded42f8ff5 | ||
|
|
d06cde3fcf | ||
|
|
9cd8bfb8e0 | ||
|
|
51c9877224 | ||
|
|
f7f6427919 | ||
|
|
56b6844a2d | ||
|
|
cce56f0479 | ||
|
|
3dbd7d8980 | ||
|
|
3625921912 | ||
|
|
1faaf54986 | ||
|
|
efe1fba267 | ||
|
|
0c43cb3f68 | ||
|
|
c25111e349 | ||
|
|
a61ab5716e | ||
|
|
628404dc7f | ||
|
|
bab0a27e40 | ||
|
|
fc0e104591 | ||
|
|
e83e9a1651 | ||
|
|
3d2e6295c4 | ||
|
|
221fe44e63 | ||
|
|
7930f517ae | ||
|
|
0f61321d5b | ||
|
|
7ddeeb51e5 | ||
|
|
484d2845b3 | ||
|
|
bbbc6b6941 | ||
|
|
acf028cdcd | ||
|
|
31e43ec356 | ||
|
|
9f45e68fef | ||
|
|
d2df99a6f0 | ||
|
|
713c341831 | ||
|
|
0b05a6dd6f | ||
|
|
d21c101caf | ||
|
|
bf49db60d9 | ||
|
|
d57fa3b603 | ||
|
|
b3a29ce82d | ||
|
|
1d8a37d6e8 | ||
|
|
ef2d8e14b0 | ||
|
|
0cbb3dd97e | ||
|
|
38a7f9423f | ||
|
|
2c0ab9e922 | ||
|
|
9aa3b742ff | ||
|
|
6bfaba750d | ||
|
|
69f8bee935 | ||
|
|
07d04a9cd0 | ||
|
|
6f17ba9116 | ||
|
|
56d1e5e95c | ||
|
|
11d86d0270 | ||
|
|
d1c20a1da8 | ||
|
|
f87ab89207 | ||
|
|
9ebb0c7920 | ||
|
|
31a5ed11a3 | ||
|
|
a706d488e6 | ||
|
|
61251fb0f1 | ||
|
|
747dc30b02 | ||
|
|
d54f27d9a9 | ||
|
|
dbca2f6be4 | ||
|
|
2ef9b56d22 | ||
|
|
52ecde7e24 | ||
|
|
a7dce5e4d3 | ||
|
|
4718da44f4 | ||
|
|
1aa1f95524 | ||
|
|
d922b2e778 | ||
|
|
5a7b3c4a37 | ||
|
|
1c2b1a2f02 | ||
|
|
c68440ecf8 | ||
|
|
6a48b3f99e | ||
|
|
bab9ae32d4 | ||
|
|
f0e6e94c5e | ||
|
|
a6fe155781 | ||
|
|
d5be9009fc | ||
|
|
ca47be41f6 | ||
|
|
ff5db30208 | ||
|
|
a429d46072 | ||
|
|
7b84fbfd94 | ||
|
|
fc6f618885 | ||
|
|
7f03c3b0a2 | ||
|
|
4b4386bd47 | ||
|
|
10a1706613 | ||
|
|
d097f356fd | ||
|
|
bc9bacaa55 |
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
liberapay: Duke_Du
|
||||
patreon: DukeDu
|
||||
8
.github/workflows/codecov.yml
vendored
8
.github/workflows/codecov.yml
vendored
@@ -3,11 +3,11 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
# - v2
|
||||
- rc
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
# - v2
|
||||
- rc
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -17,8 +17,10 @@ jobs:
|
||||
fetch-depth: 2
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: "1.18"
|
||||
go-version: "1.20"
|
||||
- name: Run coverage
|
||||
run: go test -v ./... -coverprofile=coverage.txt -covermode=atomic
|
||||
- name: Run govet
|
||||
run: go vet -v ./...
|
||||
- name: Upload coverage to Codecov
|
||||
run: bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -6,4 +6,10 @@ fileutil/*.txt
|
||||
fileutil/*.zip
|
||||
fileutil/*.link
|
||||
fileutil/unzip/*
|
||||
cryptor/*.pem
|
||||
fileutil/tempdir/*
|
||||
slice/testdata/*
|
||||
# cryptor/*.pem
|
||||
test
|
||||
docs/node_modules
|
||||
docs/.vitepress/cache
|
||||
docs/.vitepress/dist
|
||||
|
||||
37
CONTRIBUTION.md
Normal file
37
CONTRIBUTION.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Lancet Contribution Guide
|
||||
|
||||
Hi! Thank you for choosing Lancet.
|
||||
|
||||
Lancet is a powerful, efficient, and reusable util function library of go. It makes Go dev easier by taking the hassle out of working with concurrency, net, math, slice, string, etc.
|
||||
|
||||
We are excited that you are interested in contributing to lancet. Before submitting your contribution though, please make sure to take a moment and read through the following guidelines.
|
||||
|
||||
## Issue Guidelines
|
||||
|
||||
- Issues are exclusively for bug reports, feature requests and design-related topics. Other questions may be closed directly.
|
||||
|
||||
- Before submitting an issue, please check if similar problems have already been issued.
|
||||
|
||||
- Please specify which version of Lancet and Go you are using, and provide OS information. [Go Playground](https://go.dev/play/) is recommended to build a live demo so that your issue can be reproduced clearly.
|
||||
|
||||
## Pull Request Guidelines
|
||||
|
||||
- Fork this repository to your own account. Do not create branches here.
|
||||
|
||||
- Commit info should be formatted as `type(scope): info about commit`. eg. `fix(package): [scrollbar] fix xxx bug`.
|
||||
|
||||
1. type: type must be one of [chore, docs, feat, fix, refactor, release, test].
|
||||
|
||||
2. scope: scope must be one of [package, file, internal].
|
||||
|
||||
3. header: header must not be longer than 72 characters.
|
||||
|
||||
- Rebase before creating a PR to keep commit history clear.
|
||||
|
||||
- Before submitting a PR, please execute the unit test command: `go test -v ./...` to ensure that all unit test tasks should pass.
|
||||
|
||||
- Make sure PRs are created to `rc` branch instead of other branch.
|
||||
|
||||
- If your PR fixes a bug, please provide a description about the related bug.
|
||||
|
||||
- If the PR is for a new feature, make sure to complete the relevant documentation (/lancet/docs/en/api/packages).
|
||||
37
CONTRIBUTION.zh-CN.md
Normal file
37
CONTRIBUTION.zh-CN.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Lancet 贡献指南
|
||||
|
||||
Hi! 首先感谢你使用 Lancet。
|
||||
|
||||
lancet(柳叶刀)是一个功能强大、全面、高效、可复用的go语言工具函数库。它消除了处理并发、网络、数学、切片、字符串等的麻烦,使 Go 开发变得更容易。
|
||||
|
||||
Lancet 的成长离不开大家的支持,如果你愿意为 Lancet 贡献代码或提供建议,请阅读以下内容。
|
||||
|
||||
## Issue 规范
|
||||
|
||||
- issue 仅用于提交 Bug 或 Feature 以及设计相关的内容,其它内容可能会被直接关闭。
|
||||
|
||||
- 在提交 issue 之前,请搜索相关内容是否已被提出。
|
||||
|
||||
- 请说明 Lancet 和 Go 的版本号,并提供操作系统信息。推荐使用 [Go Playground](https://go.dev/play/) 生成在线 demo,这能够更直观地重现问题。
|
||||
|
||||
## Pull Request 规范
|
||||
|
||||
- 请先 fork 一份到自己的项目下,不要直接在仓库下建分支。
|
||||
|
||||
- commit 信息要以 `type(scope): 描述信息` 的形式填写,例如 `fix(package): [scrollbar] fix xxx bug`。
|
||||
|
||||
1. type: 必须是 chore, docs, feat, fix, refactor, release, test 其中的一个。
|
||||
|
||||
2. scope: 必须是 package, file, internal 其中的一个。
|
||||
|
||||
3. header: 描述信息不要超过 72 个字符。
|
||||
|
||||
- 提交 PR 前请 rebase,确保 commit 记录的整洁。
|
||||
|
||||
- 提交 PR 前请执行单元测试命令:go test -v ./...,确保所有单元测试任务通过。
|
||||
|
||||
- 确保 PR 是提交到 `rc` 分支,而不是其他分支。
|
||||
|
||||
- 如果是修复 bug,请在 PR 中给出描述信息。
|
||||
|
||||
- 如果PR是新功能,确保完成相关文档(/lancet/docs/api/packages)。
|
||||
2494
README_zh-CN.md
2494
README_zh-CN.md
File diff suppressed because it is too large
Load Diff
15
SECURITY.md
Normal file
15
SECURITY.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
Here is the lancet version and compatibility with go language version.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------|
|
||||
| 2.x.x | +go v1.18 |
|
||||
| 1.x.x | +go v1.12 |
|
||||
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
For now, there is no public website to report a vulnerability, If you find security issue in lancet, you can send it to me via my email `lanliddd.2007@163.com`.
|
||||
we can discuss it. I am appreciate if someone can create a public page for reporting vulnerability.
|
||||
@@ -1,36 +0,0 @@
|
||||
package algorithm
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
|
||||
func TestLRUCache(t *testing.T) {
|
||||
asssert := internal.NewAssert(t, "TestLRUCache")
|
||||
|
||||
cache := NewLRUCache[int, int](2)
|
||||
|
||||
cache.Put(1, 1)
|
||||
cache.Put(2, 2)
|
||||
|
||||
_, ok := cache.Get(0)
|
||||
asssert.Equal(false, ok)
|
||||
|
||||
v, ok := cache.Get(1)
|
||||
asssert.Equal(true, ok)
|
||||
asssert.Equal(1, v)
|
||||
|
||||
v, ok = cache.Get(2)
|
||||
asssert.Equal(true, ok)
|
||||
asssert.Equal(2, v)
|
||||
|
||||
cache.Put(3, 3)
|
||||
v, ok = cache.Get(1)
|
||||
asssert.Equal(false, ok)
|
||||
asssert.NotEqual(1, v)
|
||||
|
||||
v, ok = cache.Get(3)
|
||||
asssert.Equal(true, ok)
|
||||
asssert.Equal(3, v)
|
||||
}
|
||||
@@ -26,7 +26,7 @@ type LRUCache[K comparable, V any] struct {
|
||||
length int
|
||||
}
|
||||
|
||||
// NewLRUCache return a LRUCache pointer
|
||||
// NewLRUCache creates a LRUCache pointer instance.
|
||||
func NewLRUCache[K comparable, V any](capacity int) *LRUCache[K, V] {
|
||||
return &LRUCache[K, V]{
|
||||
cache: make(map[K]*lruNode[K, V], capacity),
|
||||
@@ -37,20 +37,22 @@ func NewLRUCache[K comparable, V any](capacity int) *LRUCache[K, V] {
|
||||
}
|
||||
}
|
||||
|
||||
// Get value of key from lru cache
|
||||
// Get value of key from lru cache.
|
||||
// Play: https://go.dev/play/p/iUynEfOP8G0
|
||||
func (l *LRUCache[K, V]) Get(key K) (V, bool) {
|
||||
var value V
|
||||
|
||||
node, ok := l.cache[key]
|
||||
if ok {
|
||||
l.moveToHead(node)
|
||||
l.moveToTail(node)
|
||||
return node.value, true
|
||||
}
|
||||
|
||||
return value, false
|
||||
}
|
||||
|
||||
// Put value of key into lru cache
|
||||
// Put value of key into lru cache.
|
||||
// Play: https://go.dev/play/p/iUynEfOP8G0
|
||||
func (l *LRUCache[K, V]) Put(key K, value V) {
|
||||
node, ok := l.cache[key]
|
||||
if !ok {
|
||||
@@ -64,11 +66,28 @@ func (l *LRUCache[K, V]) Put(key K, value V) {
|
||||
}
|
||||
} else {
|
||||
node.value = value
|
||||
l.moveToHead(node)
|
||||
l.moveToTail(node)
|
||||
}
|
||||
l.length = len(l.cache)
|
||||
}
|
||||
|
||||
// Delete item from lru cache.
|
||||
func (l *LRUCache[K, V]) Delete(key K) bool {
|
||||
node, ok := l.cache[key]
|
||||
if ok {
|
||||
key := l.deleteNode(node)
|
||||
delete(l.cache, key)
|
||||
return true
|
||||
}
|
||||
l.length = len(l.cache)
|
||||
return false
|
||||
}
|
||||
|
||||
// Len returns the number of items in the cache.
|
||||
func (l *LRUCache[K, V]) Len() int {
|
||||
return l.length
|
||||
}
|
||||
|
||||
func (l *LRUCache[K, V]) addNode(node *lruNode[K, V]) {
|
||||
if l.tail != nil {
|
||||
l.tail.next = node
|
||||
@@ -93,7 +112,7 @@ func (l *LRUCache[K, V]) deleteNode(node *lruNode[K, V]) K {
|
||||
return node.key
|
||||
}
|
||||
|
||||
func (l *LRUCache[K, V]) moveToHead(node *lruNode[K, V]) {
|
||||
func (l *LRUCache[K, V]) moveToTail(node *lruNode[K, V]) {
|
||||
if l.tail == node {
|
||||
return
|
||||
}
|
||||
79
algorithm/lrucache_example_test.go
Normal file
79
algorithm/lrucache_example_test.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package algorithm
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleLRUCache_Put() {
|
||||
cache := NewLRUCache[int, int](2)
|
||||
|
||||
cache.Put(1, 1)
|
||||
cache.Put(2, 2)
|
||||
|
||||
result1, ok1 := cache.Get(1)
|
||||
result2, ok2 := cache.Get(2)
|
||||
result3, ok3 := cache.Get(3)
|
||||
|
||||
fmt.Println(result1, ok1)
|
||||
fmt.Println(result2, ok2)
|
||||
fmt.Println(result3, ok3)
|
||||
|
||||
// Output:
|
||||
// 1 true
|
||||
// 2 true
|
||||
// 0 false
|
||||
}
|
||||
|
||||
func ExampleLRUCache_Get() {
|
||||
cache := NewLRUCache[int, int](2)
|
||||
|
||||
cache.Put(1, 1)
|
||||
cache.Put(2, 2)
|
||||
|
||||
result1, ok1 := cache.Get(1)
|
||||
result2, ok2 := cache.Get(2)
|
||||
result3, ok3 := cache.Get(3)
|
||||
|
||||
fmt.Println(result1, ok1)
|
||||
fmt.Println(result2, ok2)
|
||||
fmt.Println(result3, ok3)
|
||||
|
||||
// Output:
|
||||
// 1 true
|
||||
// 2 true
|
||||
// 0 false
|
||||
}
|
||||
|
||||
func ExampleLRUCache_Delete() {
|
||||
cache := NewLRUCache[int, int](2)
|
||||
|
||||
cache.Put(1, 1)
|
||||
cache.Put(2, 2)
|
||||
|
||||
result1, ok1 := cache.Get(1)
|
||||
|
||||
ok2 := cache.Delete(2)
|
||||
|
||||
_, ok3 := cache.Get(2)
|
||||
|
||||
fmt.Println(result1, ok1)
|
||||
fmt.Println(ok2)
|
||||
fmt.Println(ok3)
|
||||
|
||||
// Output:
|
||||
// 1 true
|
||||
// true
|
||||
// false
|
||||
}
|
||||
|
||||
func ExampleLRUCache_Len() {
|
||||
cache := NewLRUCache[int, int](2)
|
||||
|
||||
cache.Put(1, 1)
|
||||
cache.Put(2, 2)
|
||||
|
||||
result := cache.Len()
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// 2
|
||||
}
|
||||
34
algorithm/lrucache_test.go
Normal file
34
algorithm/lrucache_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package algorithm
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
|
||||
func TestLRUCache(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestLRUCache")
|
||||
|
||||
cache := NewLRUCache[int, int](3)
|
||||
|
||||
cache.Put(1, 1)
|
||||
cache.Put(2, 2)
|
||||
cache.Put(3, 3)
|
||||
|
||||
assert.Equal(3, cache.Len())
|
||||
|
||||
v, ok := cache.Get(1)
|
||||
assert.Equal(true, ok)
|
||||
assert.Equal(1, v)
|
||||
|
||||
v, ok = cache.Get(2)
|
||||
assert.Equal(true, ok)
|
||||
assert.Equal(2, v)
|
||||
|
||||
ok = cache.Delete(2)
|
||||
assert.Equal(true, ok)
|
||||
|
||||
_, ok = cache.Get(2)
|
||||
assert.Equal(false, ok)
|
||||
}
|
||||
@@ -1,27 +1,29 @@
|
||||
// Copyright 2021 dudaodong@gmail.com. All rights reserved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
// Package algorithm contain some basic algorithm functions. eg. sort, search, list, linklist, stack, queue, tree, graph. TODO
|
||||
// Package algorithm contain some basic algorithm functions. eg. sort, search, list, linklist, stack, queue, tree, graph.
|
||||
package algorithm
|
||||
|
||||
import "github.com/duke-git/lancet/v2/lancetconstraints"
|
||||
import "github.com/duke-git/lancet/v2/constraints"
|
||||
|
||||
// Search algorithms see https://github.com/TheAlgorithms/Go/tree/master/search
|
||||
|
||||
// LinearSearch Simple linear search algorithm that iterates over all elements of an slice
|
||||
// If a target is found, the index of the target is returned. Else the function return -1
|
||||
func LinearSearch[T any](slice []T, target T, comparator lancetconstraints.Comparator) int {
|
||||
// LinearSearch return the index of target in slice base on equal function.
|
||||
// If not found return -1
|
||||
// Play: https://go.dev/play/p/IsS7rgn5s3x
|
||||
func LinearSearch[T any](slice []T, target T, equal func(a, b T) bool) int {
|
||||
for i, v := range slice {
|
||||
if comparator.Compare(v, target) == 0 {
|
||||
if equal(v, target) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// BinarySearch search for target within a sorted slice, recursive call itself.
|
||||
// If a target is found, the index of the target is returned. Else the function return -1
|
||||
func BinarySearch[T any](sortedSlice []T, target T, lowIndex, highIndex int, comparator lancetconstraints.Comparator) int {
|
||||
// BinarySearch return the index of target within a sorted slice, use binary search (recursive call itself).
|
||||
// If not found return -1.
|
||||
// Play: https://go.dev/play/p/t6MeGiUSN47
|
||||
func BinarySearch[T any](sortedSlice []T, target T, lowIndex, highIndex int, comparator constraints.Comparator) int {
|
||||
if highIndex < lowIndex || len(sortedSlice) == 0 {
|
||||
return -1
|
||||
}
|
||||
@@ -39,9 +41,10 @@ func BinarySearch[T any](sortedSlice []T, target T, lowIndex, highIndex int, com
|
||||
return midIndex
|
||||
}
|
||||
|
||||
// BinaryIterativeSearch search for target within a sorted slice.
|
||||
// If a target is found, the index of the target is returned. Else the function return -1
|
||||
func BinaryIterativeSearch[T any](sortedSlice []T, target T, lowIndex, highIndex int, comparator lancetconstraints.Comparator) int {
|
||||
// BinaryIterativeSearch return the index of target within a sorted slice, use binary search (no recursive).
|
||||
// If not found return -1.
|
||||
// Play: https://go.dev/play/p/Anozfr8ZLH3
|
||||
func BinaryIterativeSearch[T any](sortedSlice []T, target T, lowIndex, highIndex int, comparator constraints.Comparator) int {
|
||||
startIndex := lowIndex
|
||||
endIndex := highIndex
|
||||
|
||||
|
||||
51
algorithm/search_example_test.go
Normal file
51
algorithm/search_example_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package algorithm
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleLinearSearch() {
|
||||
numbers := []int{3, 4, 5, 3, 2, 1}
|
||||
|
||||
equalFunc := func(a, b int) bool {
|
||||
return a == b
|
||||
}
|
||||
|
||||
result1 := LinearSearch(numbers, 3, equalFunc)
|
||||
result2 := LinearSearch(numbers, 6, equalFunc)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
|
||||
// Output:
|
||||
// 0
|
||||
// -1
|
||||
}
|
||||
|
||||
func ExampleBinarySearch() {
|
||||
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
comparator := &intComparator{}
|
||||
|
||||
result1 := BinarySearch(numbers, 5, 0, len(numbers)-1, comparator)
|
||||
result2 := BinarySearch(numbers, 9, 0, len(numbers)-1, comparator)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
|
||||
// Output:
|
||||
// 4
|
||||
// -1
|
||||
}
|
||||
|
||||
func ExampleBinaryIterativeSearch() {
|
||||
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
comparator := &intComparator{}
|
||||
|
||||
result1 := BinaryIterativeSearch(numbers, 5, 0, len(numbers)-1, comparator)
|
||||
result2 := BinaryIterativeSearch(numbers, 9, 0, len(numbers)-1, comparator)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
|
||||
// Output:
|
||||
// 4
|
||||
// -1
|
||||
}
|
||||
@@ -6,28 +6,36 @@ import (
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
|
||||
var sortedNumbers = []int{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
|
||||
func TestLinearSearch(t *testing.T) {
|
||||
asssert := internal.NewAssert(t, "TestLinearSearch")
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestLinearSearch")
|
||||
|
||||
comparator := &intComparator{}
|
||||
asssert.Equal(4, LinearSearch(sortedNumbers, 5, comparator))
|
||||
asssert.Equal(-1, LinearSearch(sortedNumbers, 9, comparator))
|
||||
numbers := []int{3, 4, 5, 3, 2, 1}
|
||||
equalFunc := func(a, b int) bool {
|
||||
return a == b
|
||||
}
|
||||
|
||||
assert.Equal(0, LinearSearch(numbers, 3, equalFunc))
|
||||
assert.Equal(-1, LinearSearch(numbers, 6, equalFunc))
|
||||
}
|
||||
|
||||
func TestBinarySearch(t *testing.T) {
|
||||
asssert := internal.NewAssert(t, "TestBinarySearch")
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestBinarySearch")
|
||||
|
||||
sortedNumbers := []int{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
comparator := &intComparator{}
|
||||
asssert.Equal(4, BinarySearch(sortedNumbers, 5, 0, len(sortedNumbers)-1, comparator))
|
||||
asssert.Equal(-1, BinarySearch(sortedNumbers, 9, 0, len(sortedNumbers)-1, comparator))
|
||||
|
||||
assert.Equal(4, BinarySearch(sortedNumbers, 5, 0, len(sortedNumbers)-1, comparator))
|
||||
assert.Equal(-1, BinarySearch(sortedNumbers, 9, 0, len(sortedNumbers)-1, comparator))
|
||||
}
|
||||
|
||||
func TestBinaryIterativeSearch(t *testing.T) {
|
||||
asssert := internal.NewAssert(t, "TestBinaryIterativeSearch")
|
||||
assert := internal.NewAssert(t, "TestBinaryIterativeSearch")
|
||||
|
||||
sortedNumbers := []int{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
comparator := &intComparator{}
|
||||
asssert.Equal(4, BinaryIterativeSearch(sortedNumbers, 5, 0, len(sortedNumbers)-1, comparator))
|
||||
asssert.Equal(-1, BinaryIterativeSearch(sortedNumbers, 9, 0, len(sortedNumbers)-1, comparator))
|
||||
|
||||
assert.Equal(4, BinaryIterativeSearch(sortedNumbers, 5, 0, len(sortedNumbers)-1, comparator))
|
||||
assert.Equal(-1, BinaryIterativeSearch(sortedNumbers, 9, 0, len(sortedNumbers)-1, comparator))
|
||||
}
|
||||
|
||||
@@ -1,25 +1,31 @@
|
||||
// Copyright 2021 dudaodong@gmail.com. All rights reserved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
// Package algorithm contain some basic algorithm functions. eg. sort, search
|
||||
package algorithm
|
||||
|
||||
import "github.com/duke-git/lancet/v2/lancetconstraints"
|
||||
import "github.com/duke-git/lancet/v2/constraints"
|
||||
|
||||
// BubbleSort use bubble to sort slice.
|
||||
func BubbleSort[T any](slice []T, comparator lancetconstraints.Comparator) {
|
||||
// BubbleSort applys the bubble sort algorithm to sort the collection, will change the original collection data.
|
||||
// Play: https://go.dev/play/p/GNdv7Jg2Taj
|
||||
func BubbleSort[T any](slice []T, comparator constraints.Comparator) {
|
||||
for i := 0; i < len(slice); i++ {
|
||||
breakTag := false
|
||||
for j := 0; j < len(slice)-1-i; j++ {
|
||||
isCurrGreatThanNext := comparator.Compare(slice[j], slice[j+1]) == 1
|
||||
if isCurrGreatThanNext {
|
||||
swap(slice, j, j+1)
|
||||
breakTag = true
|
||||
}
|
||||
}
|
||||
if !breakTag {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// InsertionSort use insertion to sort slice.
|
||||
func InsertionSort[T any](slice []T, comparator lancetconstraints.Comparator) {
|
||||
// InsertionSort applys the insertion sort algorithm to sort the collection, will change the original collection data.
|
||||
// Play: https://go.dev/play/p/G5LJiWgJJW6
|
||||
func InsertionSort[T any](slice []T, comparator constraints.Comparator) {
|
||||
for i := 0; i < len(slice); i++ {
|
||||
for j := i; j > 0; j-- {
|
||||
isPreLessThanCurrent := comparator.Compare(slice[j], slice[j-1]) == -1
|
||||
@@ -32,8 +38,9 @@ func InsertionSort[T any](slice []T, comparator lancetconstraints.Comparator) {
|
||||
}
|
||||
}
|
||||
|
||||
// SelectionSort use selection to sort slice.
|
||||
func SelectionSort[T any](slice []T, comparator lancetconstraints.Comparator) {
|
||||
// SelectionSort applys the selection sort algorithm to sort the collection, will change the original collection data.
|
||||
// Play: https://go.dev/play/p/oXovbkekayS
|
||||
func SelectionSort[T any](slice []T, comparator constraints.Comparator) {
|
||||
for i := 0; i < len(slice); i++ {
|
||||
min := i
|
||||
for j := i + 1; j < len(slice); j++ {
|
||||
@@ -45,8 +52,9 @@ func SelectionSort[T any](slice []T, comparator lancetconstraints.Comparator) {
|
||||
}
|
||||
}
|
||||
|
||||
// ShellSort shell sort slice.
|
||||
func ShellSort[T any](slice []T, comparator lancetconstraints.Comparator) {
|
||||
// ShellSort applys the shell sort algorithm to sort the collection, will change the original collection data.
|
||||
// Play: https://go.dev/play/p/3ibkszpJEu3
|
||||
func ShellSort[T any](slice []T, comparator constraints.Comparator) {
|
||||
size := len(slice)
|
||||
|
||||
gap := 1
|
||||
@@ -64,17 +72,22 @@ func ShellSort[T any](slice []T, comparator lancetconstraints.Comparator) {
|
||||
}
|
||||
}
|
||||
|
||||
// QuickSort quick sorting for slice, lowIndex is 0 and highIndex is len(slice)-1
|
||||
func QuickSort[T any](slice []T, lowIndex, highIndex int, comparator lancetconstraints.Comparator) {
|
||||
// QuickSort quick sorting for slice, lowIndex is 0 and highIndex is len(slice)-1.
|
||||
// Play: https://go.dev/play/p/7Y7c1Elk3ax
|
||||
func QuickSort[T any](slice []T, comparator constraints.Comparator) {
|
||||
quickSort(slice, 0, len(slice)-1, comparator)
|
||||
}
|
||||
|
||||
func quickSort[T any](slice []T, lowIndex, highIndex int, comparator constraints.Comparator) {
|
||||
if lowIndex < highIndex {
|
||||
p := partition(slice, lowIndex, highIndex, comparator)
|
||||
QuickSort(slice, lowIndex, p-1, comparator)
|
||||
QuickSort(slice, p+1, highIndex, comparator)
|
||||
quickSort(slice, lowIndex, p-1, comparator)
|
||||
quickSort(slice, p+1, highIndex, comparator)
|
||||
}
|
||||
}
|
||||
|
||||
// partition split slice into two parts
|
||||
func partition[T any](slice []T, lowIndex, highIndex int, comparator lancetconstraints.Comparator) int {
|
||||
func partition[T any](slice []T, lowIndex, highIndex int, comparator constraints.Comparator) int {
|
||||
p := slice[highIndex]
|
||||
i := lowIndex
|
||||
for j := lowIndex; j < highIndex; j++ {
|
||||
@@ -89,8 +102,9 @@ func partition[T any](slice []T, lowIndex, highIndex int, comparator lancetconst
|
||||
return i
|
||||
}
|
||||
|
||||
// HeapSort use heap to sort slice
|
||||
func HeapSort[T any](slice []T, comparator lancetconstraints.Comparator) {
|
||||
// HeapSort applys the heap sort algorithm to sort the collection, will change the original collection data.
|
||||
// Play: https://go.dev/play/p/u6Iwa1VZS_f
|
||||
func HeapSort[T any](slice []T, comparator constraints.Comparator) {
|
||||
size := len(slice)
|
||||
|
||||
for i := size/2 - 1; i >= 0; i-- {
|
||||
@@ -102,7 +116,7 @@ func HeapSort[T any](slice []T, comparator lancetconstraints.Comparator) {
|
||||
}
|
||||
}
|
||||
|
||||
func sift[T any](slice []T, lowIndex, highIndex int, comparator lancetconstraints.Comparator) {
|
||||
func sift[T any](slice []T, lowIndex, highIndex int, comparator constraints.Comparator) {
|
||||
i := lowIndex
|
||||
j := 2*i + 1
|
||||
|
||||
@@ -122,12 +136,13 @@ func sift[T any](slice []T, lowIndex, highIndex int, comparator lancetconstraint
|
||||
slice[i] = temp
|
||||
}
|
||||
|
||||
// MergeSort merge sorting for slice
|
||||
func MergeSort[T any](slice []T, comparator lancetconstraints.Comparator) {
|
||||
// MergeSort applys the merge sort algorithm to sort the collection, will change the original collection data.
|
||||
// Play: https://go.dev/play/p/ydinn9YzUJn
|
||||
func MergeSort[T any](slice []T, comparator constraints.Comparator) {
|
||||
mergeSort(slice, 0, len(slice)-1, comparator)
|
||||
}
|
||||
|
||||
func mergeSort[T any](slice []T, lowIndex, highIndex int, comparator lancetconstraints.Comparator) {
|
||||
func mergeSort[T any](slice []T, lowIndex, highIndex int, comparator constraints.Comparator) {
|
||||
if lowIndex < highIndex {
|
||||
mid := (lowIndex + highIndex) / 2
|
||||
mergeSort(slice, lowIndex, mid, comparator)
|
||||
@@ -136,7 +151,7 @@ func mergeSort[T any](slice []T, lowIndex, highIndex int, comparator lancetconst
|
||||
}
|
||||
}
|
||||
|
||||
func merge[T any](slice []T, lowIndex, midIndex, highIndex int, comparator lancetconstraints.Comparator) {
|
||||
func merge[T any](slice []T, lowIndex, midIndex, highIndex int, comparator constraints.Comparator) {
|
||||
i := lowIndex
|
||||
j := midIndex + 1
|
||||
temp := []T{}
|
||||
@@ -163,8 +178,9 @@ func merge[T any](slice []T, lowIndex, midIndex, highIndex int, comparator lance
|
||||
}
|
||||
}
|
||||
|
||||
// CountSort use count sorting for slice
|
||||
func CountSort[T any](slice []T, comparator lancetconstraints.Comparator) []T {
|
||||
// CountSort applys the count sort algorithm to sort the collection, don't change the original collection data.
|
||||
// Play: https://go.dev/play/p/tB-Umgm0DrP
|
||||
func CountSort[T any](slice []T, comparator constraints.Comparator) []T {
|
||||
size := len(slice)
|
||||
out := make([]T, size)
|
||||
|
||||
93
algorithm/sort_example_test.go
Normal file
93
algorithm/sort_example_test.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package algorithm
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleBubbleSort() {
|
||||
numbers := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
|
||||
BubbleSort(numbers, comparator)
|
||||
|
||||
fmt.Println(numbers)
|
||||
// Output:
|
||||
// [1 2 3 4 5 6]
|
||||
}
|
||||
|
||||
func ExampleCountSort() {
|
||||
numbers := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
|
||||
sortedNumber := CountSort(numbers, comparator)
|
||||
|
||||
fmt.Println(numbers)
|
||||
fmt.Println(sortedNumber)
|
||||
// Output:
|
||||
// [2 1 5 3 6 4]
|
||||
// [1 2 3 4 5 6]
|
||||
}
|
||||
|
||||
func ExampleHeapSort() {
|
||||
numbers := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
|
||||
HeapSort(numbers, comparator)
|
||||
|
||||
fmt.Println(numbers)
|
||||
// Output:
|
||||
// [1 2 3 4 5 6]
|
||||
}
|
||||
|
||||
func ExampleMergeSort() {
|
||||
numbers := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
|
||||
MergeSort(numbers, comparator)
|
||||
|
||||
fmt.Println(numbers)
|
||||
// Output:
|
||||
// [1 2 3 4 5 6]
|
||||
}
|
||||
|
||||
func ExampleInsertionSort() {
|
||||
numbers := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
|
||||
InsertionSort(numbers, comparator)
|
||||
|
||||
fmt.Println(numbers)
|
||||
// Output:
|
||||
// [1 2 3 4 5 6]
|
||||
}
|
||||
|
||||
func ExampleSelectionSort() {
|
||||
numbers := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
|
||||
SelectionSort(numbers, comparator)
|
||||
|
||||
fmt.Println(numbers)
|
||||
// Output:
|
||||
// [1 2 3 4 5 6]
|
||||
}
|
||||
|
||||
func ExampleShellSort() {
|
||||
numbers := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
|
||||
ShellSort(numbers, comparator)
|
||||
|
||||
fmt.Println(numbers)
|
||||
// Output:
|
||||
// [1 2 3 4 5 6]
|
||||
}
|
||||
|
||||
func ExampleQuickSort() {
|
||||
numbers := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
|
||||
QuickSort(numbers, comparator)
|
||||
|
||||
fmt.Println(numbers)
|
||||
// Output:
|
||||
// [1 2 3 4 5 6]
|
||||
}
|
||||
@@ -16,7 +16,7 @@ type people struct {
|
||||
// PeopleAageComparator sort people slice by age field
|
||||
type peopleAgeComparator struct{}
|
||||
|
||||
// Compare implements github.com/duke-git/lancet/v2/lancetconstraints/constraints.go/Comparator
|
||||
// Compare implements github.com/duke-git/lancet/v2/constraints/constraints.go/Comparator
|
||||
func (pc *peopleAgeComparator) Compare(v1 any, v2 any) int {
|
||||
p1, _ := v1.(people)
|
||||
p2, _ := v2.(people)
|
||||
@@ -30,14 +30,6 @@ func (pc *peopleAgeComparator) Compare(v1 any, v2 any) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// var peoples = []people{
|
||||
// {Name: "a", Age: 20},
|
||||
// {Name: "b", Age: 10},
|
||||
// {Name: "c", Age: 17},
|
||||
// {Name: "d", Age: 8},
|
||||
// {Name: "e", Age: 28},
|
||||
// }
|
||||
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
@@ -53,10 +45,9 @@ func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// var intSlice = []int{2, 1, 5, 3, 6, 4}
|
||||
|
||||
func TestBubbleSortForStructSlice(t *testing.T) {
|
||||
asssert := internal.NewAssert(t, "TestBubbleSortForStructSlice")
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestBubbleSortForStructSlice")
|
||||
|
||||
peoples := []people{
|
||||
{Name: "a", Age: 20},
|
||||
@@ -71,20 +62,23 @@ func TestBubbleSortForStructSlice(t *testing.T) {
|
||||
expected := "[{d 8} {b 10} {c 17} {a 20} {e 28}]"
|
||||
actual := fmt.Sprintf("%v", peoples)
|
||||
|
||||
asssert.Equal(expected, actual)
|
||||
assert.Equal(expected, actual)
|
||||
}
|
||||
|
||||
func TestBubbleSortForIntSlice(t *testing.T) {
|
||||
asssert := internal.NewAssert(t, "TestBubbleSortForIntSlice")
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestBubbleSortForIntSlice")
|
||||
|
||||
numbers := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
BubbleSort(numbers, comparator)
|
||||
|
||||
asssert.Equal([]int{1, 2, 3, 4, 5, 6}, numbers)
|
||||
assert.Equal([]int{1, 2, 3, 4, 5, 6}, numbers)
|
||||
}
|
||||
|
||||
func TestInsertionSort(t *testing.T) {
|
||||
asssert := internal.NewAssert(t, "TestInsertionSort")
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestInsertionSort")
|
||||
|
||||
peoples := []people{
|
||||
{Name: "a", Age: 20},
|
||||
@@ -99,11 +93,12 @@ func TestInsertionSort(t *testing.T) {
|
||||
expected := "[{d 8} {b 10} {c 17} {a 20} {e 28}]"
|
||||
actual := fmt.Sprintf("%v", peoples)
|
||||
|
||||
asssert.Equal(expected, actual)
|
||||
assert.Equal(expected, actual)
|
||||
}
|
||||
|
||||
func TestSelectionSort(t *testing.T) {
|
||||
asssert := internal.NewAssert(t, "TestSelectionSort")
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestSelectionSort")
|
||||
|
||||
peoples := []people{
|
||||
{Name: "a", Age: 20},
|
||||
@@ -118,11 +113,12 @@ func TestSelectionSort(t *testing.T) {
|
||||
expected := "[{d 8} {b 10} {c 17} {a 20} {e 28}]"
|
||||
actual := fmt.Sprintf("%v", peoples)
|
||||
|
||||
asssert.Equal(expected, actual)
|
||||
assert.Equal(expected, actual)
|
||||
}
|
||||
|
||||
func TestShellSort(t *testing.T) {
|
||||
asssert := internal.NewAssert(t, "TestShellSort")
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestShellSort")
|
||||
|
||||
peoples := []people{
|
||||
{Name: "a", Age: 20},
|
||||
@@ -137,11 +133,12 @@ func TestShellSort(t *testing.T) {
|
||||
expected := "[{d 8} {b 10} {c 17} {a 20} {e 28}]"
|
||||
actual := fmt.Sprintf("%v", peoples)
|
||||
|
||||
asssert.Equal(expected, actual)
|
||||
assert.Equal(expected, actual)
|
||||
}
|
||||
|
||||
func TestQuickSort(t *testing.T) {
|
||||
asssert := internal.NewAssert(t, "TestQuickSort")
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestQuickSort")
|
||||
|
||||
peoples := []people{
|
||||
{Name: "a", Age: 20},
|
||||
@@ -151,16 +148,17 @@ func TestQuickSort(t *testing.T) {
|
||||
{Name: "e", Age: 28},
|
||||
}
|
||||
comparator := &peopleAgeComparator{}
|
||||
QuickSort(peoples, 0, len(peoples)-1, comparator)
|
||||
QuickSort(peoples, comparator)
|
||||
|
||||
expected := "[{d 8} {b 10} {c 17} {a 20} {e 28}]"
|
||||
actual := fmt.Sprintf("%v", peoples)
|
||||
|
||||
asssert.Equal(expected, actual)
|
||||
assert.Equal(expected, actual)
|
||||
}
|
||||
|
||||
func TestHeapSort(t *testing.T) {
|
||||
asssert := internal.NewAssert(t, "TestHeapSort")
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestHeapSort")
|
||||
|
||||
peoples := []people{
|
||||
{Name: "a", Age: 20},
|
||||
@@ -175,11 +173,12 @@ func TestHeapSort(t *testing.T) {
|
||||
expected := "[{d 8} {b 10} {c 17} {a 20} {e 28}]"
|
||||
actual := fmt.Sprintf("%v", peoples)
|
||||
|
||||
asssert.Equal(expected, actual)
|
||||
assert.Equal(expected, actual)
|
||||
}
|
||||
|
||||
func TestMergeSort(t *testing.T) {
|
||||
asssert := internal.NewAssert(t, "TestMergeSort")
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestMergeSort")
|
||||
|
||||
peoples := []people{
|
||||
{Name: "a", Age: 20},
|
||||
@@ -194,11 +193,12 @@ func TestMergeSort(t *testing.T) {
|
||||
expected := "[{d 8} {b 10} {c 17} {a 20} {e 28}]"
|
||||
actual := fmt.Sprintf("%v", peoples)
|
||||
|
||||
asssert.Equal(expected, actual)
|
||||
assert.Equal(expected, actual)
|
||||
}
|
||||
|
||||
func TestCountSort(t *testing.T) {
|
||||
asssert := internal.NewAssert(t, "TestCountSort")
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestCountSort")
|
||||
|
||||
peoples := []people{
|
||||
{Name: "a", Age: 20},
|
||||
@@ -209,10 +209,9 @@ func TestCountSort(t *testing.T) {
|
||||
}
|
||||
comparator := &peopleAgeComparator{}
|
||||
sortedPeopleByAge := CountSort(peoples, comparator)
|
||||
t.Log(sortedPeopleByAge)
|
||||
|
||||
expected := "[{d 8} {b 10} {c 17} {a 20} {e 28}]"
|
||||
actual := fmt.Sprintf("%v", sortedPeopleByAge)
|
||||
|
||||
asssert.Equal(expected, actual)
|
||||
assert.Equal(expected, actual)
|
||||
}
|
||||
72
compare/compare.go
Normal file
72
compare/compare.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright 2023 dudaodong@gmail.com. All rights resulterved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
// Package compare provides a lightweight comparison function on any type.
|
||||
// reference: https://github.com/stretchr/testify
|
||||
package compare
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/duke-git/lancet/v2/convertor"
|
||||
"github.com/duke-git/lancet/v2/mathutil"
|
||||
"golang.org/x/exp/constraints"
|
||||
)
|
||||
|
||||
// operator type
|
||||
const (
|
||||
equal = "eq"
|
||||
lessThan = "lt"
|
||||
greaterThan = "gt"
|
||||
lessOrEqual = "le"
|
||||
greaterOrEqual = "ge"
|
||||
)
|
||||
|
||||
var (
|
||||
timeType = reflect.TypeOf(time.Time{})
|
||||
bytesType = reflect.TypeOf([]byte{})
|
||||
)
|
||||
|
||||
// Equal checks if two values are equal or not. (check both type and value)
|
||||
// Play: https://go.dev/play/p/wmVxR-to4lz
|
||||
func Equal(left, right any) bool {
|
||||
return compareValue(equal, left, right)
|
||||
}
|
||||
|
||||
// EqualValue checks if two values are equal or not. (check value only)
|
||||
// Play: https://go.dev/play/p/fxnna_LLD9u
|
||||
func EqualValue(left, right any) bool {
|
||||
ls, rs := convertor.ToString(left), convertor.ToString(right)
|
||||
return ls == rs
|
||||
}
|
||||
|
||||
// LessThan checks if value `left` less than value `right`.
|
||||
// Play: https://go.dev/play/p/cYh7FQQj0ne
|
||||
func LessThan(left, right any) bool {
|
||||
return compareValue(lessThan, left, right)
|
||||
}
|
||||
|
||||
// GreaterThan checks if value `left` greater than value `right`.
|
||||
// Play: https://go.dev/play/p/9-NYDFZmIMp
|
||||
func GreaterThan(left, right any) bool {
|
||||
return compareValue(greaterThan, left, right)
|
||||
}
|
||||
|
||||
// LessOrEqual checks if value `left` less than or equal to value `right`.
|
||||
// Play: https://go.dev/play/p/e4T_scwoQzp
|
||||
func LessOrEqual(left, right any) bool {
|
||||
return compareValue(lessOrEqual, left, right)
|
||||
}
|
||||
|
||||
// GreaterOrEqual checks if value `left` greater than or equal to value `right`.
|
||||
// Play: https://go.dev/play/p/vx8mP0U8DFk
|
||||
func GreaterOrEqual(left, right any) bool {
|
||||
return compareValue(greaterOrEqual, left, right)
|
||||
}
|
||||
|
||||
// InDelta checks if two values are equal or not within a delta.
|
||||
// Play: https://go.dev/play/p/TuDdcNtMkjo
|
||||
func InDelta[T constraints.Integer | constraints.Float](left, right T, delta float64) bool {
|
||||
return float64(mathutil.Abs(left-right)) <= delta
|
||||
}
|
||||
196
compare/compare_example_test.go
Normal file
196
compare/compare_example_test.go
Normal file
@@ -0,0 +1,196 @@
|
||||
package compare
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ExampleEqual() {
|
||||
result1 := Equal(1, 1)
|
||||
result2 := Equal("1", "1")
|
||||
result3 := Equal([]int{1, 2, 3}, []int{1, 2, 3})
|
||||
result4 := Equal(map[int]string{1: "a", 2: "b"}, map[int]string{1: "a", 2: "b"})
|
||||
|
||||
result5 := Equal(1, "1")
|
||||
result6 := Equal(1, int64(1))
|
||||
result7 := Equal([]int{1, 2}, []int{1, 2, 3})
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
fmt.Println(result5)
|
||||
fmt.Println(result6)
|
||||
fmt.Println(result7)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// false
|
||||
// false
|
||||
// false
|
||||
}
|
||||
|
||||
func ExampleEqualValue() {
|
||||
result1 := EqualValue(1, 1)
|
||||
result2 := EqualValue(int(1), int64(1))
|
||||
result3 := EqualValue(1, "1")
|
||||
result4 := EqualValue(1, "2")
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// false
|
||||
}
|
||||
|
||||
func ExampleLessThan() {
|
||||
result1 := LessThan(1, 2)
|
||||
result2 := LessThan(1.1, 2.2)
|
||||
result3 := LessThan("a", "b")
|
||||
|
||||
time1 := time.Now()
|
||||
time2 := time1.Add(time.Second)
|
||||
result4 := LessThan(time1, time2)
|
||||
|
||||
result5 := LessThan(2, 1)
|
||||
result6 := LessThan(1, int64(2))
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
fmt.Println(result5)
|
||||
fmt.Println(result6)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// false
|
||||
// false
|
||||
}
|
||||
|
||||
func ExampleGreaterThan() {
|
||||
result1 := GreaterThan(2, 1)
|
||||
result2 := GreaterThan(2.2, 1.1)
|
||||
result3 := GreaterThan("b", "a")
|
||||
|
||||
time1 := time.Now()
|
||||
time2 := time1.Add(time.Second)
|
||||
result4 := GreaterThan(time2, time1)
|
||||
|
||||
result5 := GreaterThan(1, 2)
|
||||
result6 := GreaterThan(int64(2), 1)
|
||||
result7 := GreaterThan("b", "c")
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
fmt.Println(result5)
|
||||
fmt.Println(result6)
|
||||
fmt.Println(result7)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// false
|
||||
// false
|
||||
// false
|
||||
}
|
||||
|
||||
func ExampleLessOrEqual() {
|
||||
result1 := LessOrEqual(1, 1)
|
||||
result2 := LessOrEqual(1.1, 2.2)
|
||||
result3 := LessOrEqual("a", "b")
|
||||
|
||||
time1 := time.Now()
|
||||
time2 := time1.Add(time.Second)
|
||||
result4 := LessOrEqual(time1, time2)
|
||||
|
||||
result5 := LessOrEqual(2, 1)
|
||||
result6 := LessOrEqual(1, int64(2))
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
fmt.Println(result5)
|
||||
fmt.Println(result6)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// false
|
||||
// false
|
||||
}
|
||||
|
||||
func ExampleGreaterOrEqual() {
|
||||
result1 := GreaterOrEqual(1, 1)
|
||||
result2 := GreaterOrEqual(2.2, 1.1)
|
||||
result3 := GreaterOrEqual("b", "b")
|
||||
|
||||
time1 := time.Now()
|
||||
time2 := time1.Add(time.Second)
|
||||
result4 := GreaterOrEqual(time2, time1)
|
||||
|
||||
result5 := GreaterOrEqual(1, 2)
|
||||
result6 := GreaterOrEqual(int64(2), 1)
|
||||
result7 := GreaterOrEqual("b", "c")
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
fmt.Println(result5)
|
||||
fmt.Println(result6)
|
||||
fmt.Println(result7)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// false
|
||||
// false
|
||||
// false
|
||||
}
|
||||
|
||||
func ExampleInDelta() {
|
||||
result1 := InDelta(1, 1, 0)
|
||||
result2 := InDelta(1, 2, 0)
|
||||
|
||||
result3 := InDelta(2.0/3.0, 0.66667, 0.001)
|
||||
result4 := InDelta(2.0/3.0, 0.0, 0.001)
|
||||
|
||||
result5 := InDelta(float64(74.96)-float64(20.48), 54.48, 0)
|
||||
result6 := InDelta(float64(74.96)-float64(20.48), 54.48, 1e-14)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
fmt.Println(result5)
|
||||
fmt.Println(result6)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
// true
|
||||
// false
|
||||
// false
|
||||
// true
|
||||
}
|
||||
291
compare/compare_internal.go
Normal file
291
compare/compare_internal.go
Normal file
@@ -0,0 +1,291 @@
|
||||
package compare
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/duke-git/lancet/v2/convertor"
|
||||
)
|
||||
|
||||
func compareValue(operator string, left, right any) bool {
|
||||
leftType, rightType := reflect.TypeOf(left), reflect.TypeOf(right)
|
||||
|
||||
if leftType.Kind() != rightType.Kind() {
|
||||
return false
|
||||
}
|
||||
|
||||
switch leftType.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
||||
reflect.Float32, reflect.Float64, reflect.Bool, reflect.String:
|
||||
return compareBasicValue(operator, left, right)
|
||||
|
||||
case reflect.Struct, reflect.Slice, reflect.Map:
|
||||
return compareRefValue(operator, left, right, leftType.Kind())
|
||||
|
||||
case reflect.Ptr:
|
||||
if leftVal, ok := left.(*big.Int); ok {
|
||||
if rightVal, ok := right.(*big.Int); ok {
|
||||
return compareBigInt(operator, leftVal, rightVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func compareRefValue(operator string, leftObj, rightObj any, kind reflect.Kind) bool {
|
||||
leftVal, rightVal := reflect.ValueOf(leftObj), reflect.ValueOf(rightObj)
|
||||
|
||||
switch kind {
|
||||
case reflect.Struct:
|
||||
|
||||
// compare time
|
||||
if leftVal.CanConvert(timeType) {
|
||||
timeObj1, ok := leftObj.(time.Time)
|
||||
if !ok {
|
||||
timeObj1 = leftVal.Convert(timeType).Interface().(time.Time)
|
||||
}
|
||||
|
||||
timeObj2, ok := rightObj.(time.Time)
|
||||
if !ok {
|
||||
timeObj2 = rightVal.Convert(timeType).Interface().(time.Time)
|
||||
}
|
||||
|
||||
return compareBasicValue(operator, timeObj1.UnixNano(), timeObj2.UnixNano())
|
||||
}
|
||||
|
||||
// for other struct type, only process equal operator
|
||||
switch operator {
|
||||
case equal:
|
||||
return objectsAreEqualValues(leftObj, rightObj)
|
||||
}
|
||||
|
||||
case reflect.Slice:
|
||||
// compare []byte
|
||||
if leftVal.CanConvert(bytesType) {
|
||||
bytesObj1, ok := leftObj.([]byte)
|
||||
if !ok {
|
||||
bytesObj1 = leftVal.Convert(bytesType).Interface().([]byte)
|
||||
}
|
||||
bytesObj2, ok := rightObj.([]byte)
|
||||
if !ok {
|
||||
bytesObj2 = rightVal.Convert(bytesType).Interface().([]byte)
|
||||
}
|
||||
|
||||
switch operator {
|
||||
case equal:
|
||||
if bytes.Equal(bytesObj1, bytesObj2) {
|
||||
return true
|
||||
}
|
||||
case lessThan:
|
||||
if bytes.Compare(bytesObj1, bytesObj2) == -1 {
|
||||
return true
|
||||
}
|
||||
case greaterThan:
|
||||
if bytes.Compare(bytesObj1, bytesObj2) == 1 {
|
||||
return true
|
||||
}
|
||||
case lessOrEqual:
|
||||
if bytes.Compare(bytesObj1, bytesObj2) <= 0 {
|
||||
return true
|
||||
}
|
||||
case greaterOrEqual:
|
||||
if bytes.Compare(bytesObj1, bytesObj2) >= 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// for other type slice, only process equal operator
|
||||
switch operator {
|
||||
case equal:
|
||||
return reflect.DeepEqual(leftObj, rightObj)
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
// only process equal operator
|
||||
switch operator {
|
||||
case equal:
|
||||
return reflect.DeepEqual(leftObj, rightObj)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func objectsAreEqualValues(expected, actual interface{}) bool {
|
||||
if objectsAreEqual(expected, actual) {
|
||||
return true
|
||||
}
|
||||
|
||||
actualType := reflect.TypeOf(actual)
|
||||
if actualType == nil {
|
||||
return false
|
||||
}
|
||||
expectedValue := reflect.ValueOf(expected)
|
||||
if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) {
|
||||
// Attempt comparison after type conversion
|
||||
return reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func objectsAreEqual(expected, actual interface{}) bool {
|
||||
if expected == nil || actual == nil {
|
||||
return expected == actual
|
||||
}
|
||||
|
||||
exp, ok := expected.([]byte)
|
||||
if !ok {
|
||||
return reflect.DeepEqual(expected, actual)
|
||||
}
|
||||
|
||||
act, ok := actual.([]byte)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if exp == nil || act == nil {
|
||||
return exp == nil && act == nil
|
||||
}
|
||||
return bytes.Equal(exp, act)
|
||||
}
|
||||
|
||||
// compareBasic compare basic value: integer, float, string, bool
|
||||
func compareBasicValue(operator string, leftValue, rightValue any) bool {
|
||||
if leftValue == nil && rightValue == nil && operator == equal {
|
||||
return true
|
||||
}
|
||||
|
||||
switch leftVal := leftValue.(type) {
|
||||
case int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64:
|
||||
left, err := convertor.ToBigInt(leftValue)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
right, err := convertor.ToBigInt(rightValue)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return compareBigInt(operator, left, right)
|
||||
|
||||
case float32, float64:
|
||||
left, err := convertor.ToFloat(leftValue)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
right, err := convertor.ToFloat(rightValue)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return compareFloats(operator, left, right)
|
||||
|
||||
case string:
|
||||
left := leftVal
|
||||
switch right := rightValue.(type) {
|
||||
case string:
|
||||
return compareStrings(operator, left, right)
|
||||
}
|
||||
|
||||
case bool:
|
||||
left := leftVal
|
||||
switch right := rightValue.(type) {
|
||||
case bool:
|
||||
return compareBools(operator, left, right)
|
||||
}
|
||||
|
||||
case json.Number:
|
||||
if left, err := leftVal.Float64(); err == nil {
|
||||
switch rightVal := rightValue.(type) {
|
||||
case json.Number:
|
||||
if right, err := rightVal.Float64(); err == nil {
|
||||
return compareFloats(operator, left, right)
|
||||
}
|
||||
case float32, float64:
|
||||
right, err := convertor.ToFloat(rightValue)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return compareFloats(operator, left, right)
|
||||
|
||||
case int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64:
|
||||
right, err := convertor.ToBigInt(rightValue)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
left, err := convertor.ToBigInt(left)
|
||||
return compareBigInt(operator, left, right)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// compareBigInt compares two big.Int values based on the operator
|
||||
func compareBigInt(operator string, left, right *big.Int) bool {
|
||||
switch operator {
|
||||
case equal:
|
||||
return left.Cmp(right) == 0
|
||||
case lessThan:
|
||||
return left.Cmp(right) < 0
|
||||
case greaterThan:
|
||||
return left.Cmp(right) > 0
|
||||
case lessOrEqual:
|
||||
return left.Cmp(right) <= 0
|
||||
case greaterOrEqual:
|
||||
return left.Cmp(right) >= 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// compareFloats compares two float64 values based on the operator
|
||||
func compareFloats(operator string, left, right float64) bool {
|
||||
switch operator {
|
||||
case equal:
|
||||
return left == right
|
||||
case lessThan:
|
||||
return left < right
|
||||
case greaterThan:
|
||||
return left > right
|
||||
case lessOrEqual:
|
||||
return left <= right
|
||||
case greaterOrEqual:
|
||||
return left >= right
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// compareStrings compares two string values based on the operator
|
||||
func compareStrings(operator string, left, right string) bool {
|
||||
switch operator {
|
||||
case equal:
|
||||
return left == right
|
||||
case lessThan:
|
||||
return left < right
|
||||
case greaterThan:
|
||||
return left > right
|
||||
case lessOrEqual:
|
||||
return left <= right
|
||||
case greaterOrEqual:
|
||||
return left >= right
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// compareBools compares two boolean values based on the operator
|
||||
func compareBools(operator string, left, right bool) bool {
|
||||
switch operator {
|
||||
case equal:
|
||||
return left == right
|
||||
}
|
||||
return false
|
||||
}
|
||||
203
compare/compare_test.go
Normal file
203
compare/compare_test.go
Normal file
@@ -0,0 +1,203 @@
|
||||
package compare
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
|
||||
func TestEqual(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestEqual")
|
||||
|
||||
tests := []struct {
|
||||
left any
|
||||
right any
|
||||
want bool
|
||||
}{
|
||||
{1, 1, true},
|
||||
{int64(1), int64(1), true},
|
||||
{"a", "a", true},
|
||||
{true, true, true},
|
||||
{[]int{1, 2, 3}, []int{1, 2, 3}, true},
|
||||
{map[int]string{1: "a", 2: "b"}, map[int]string{1: "a", 2: "b"}, true},
|
||||
{1, 2, false},
|
||||
{1, int64(1), false},
|
||||
{"a", "b", false},
|
||||
{true, false, false},
|
||||
{[]int{1, 2}, []int{1, 2, 3}, false},
|
||||
{map[int]string{1: "a", 2: "b"}, map[int]string{1: "a"}, false},
|
||||
// {time.Now(), time.Now(), true},
|
||||
// {time.Now(), time.Now().Add(time.Second), false},
|
||||
{[]byte("hello"), []byte("hello"), true},
|
||||
{[]byte("hello"), []byte("world"), false},
|
||||
{json.Number("123"), json.Number("123"), true},
|
||||
{json.Number("123"), json.Number("124"), false},
|
||||
|
||||
{big.NewInt(123), big.NewInt(123), true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.want, Equal(tt.left, tt.right))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestEqualValue(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestEqualValue")
|
||||
|
||||
tests := []struct {
|
||||
left any
|
||||
right any
|
||||
want bool
|
||||
}{
|
||||
{1, 1, true},
|
||||
{int64(1), int64(1), true},
|
||||
{"a", "a", true},
|
||||
{true, true, true},
|
||||
{[]int{1, 2, 3}, []int{1, 2, 3}, true},
|
||||
{map[int]string{1: "a", 2: "b"}, map[int]string{1: "a", 2: "b"}, true},
|
||||
{1, 2, false},
|
||||
{1, int64(1), true},
|
||||
{"a", "b", false},
|
||||
{true, false, false},
|
||||
{[]int{1, 2}, []int{1, 2, 3}, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.want, EqualValue(tt.left, tt.right))
|
||||
}
|
||||
}
|
||||
|
||||
func TestLessThan(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestLessThan")
|
||||
|
||||
tests := []struct {
|
||||
left any
|
||||
right any
|
||||
want bool
|
||||
}{
|
||||
{1, 2, true},
|
||||
{1.1, 2.2, true},
|
||||
{"a", "b", true},
|
||||
{time.Now(), time.Now().Add(time.Second), true},
|
||||
{[]byte("hello1"), []byte("hello2"), true},
|
||||
{json.Number("123"), json.Number("124"), true},
|
||||
{645680099112988673, 645680099112988675, true},
|
||||
{1, 1, false},
|
||||
{1, int64(1), false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.want, LessThan(tt.left, tt.right))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGreaterThan(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestGreaterThan")
|
||||
|
||||
tests := []struct {
|
||||
left any
|
||||
right any
|
||||
want bool
|
||||
}{
|
||||
{2, 1, true},
|
||||
{2.2, 1.1, true},
|
||||
{"b", "a", true},
|
||||
{time.Now().Add(time.Second), time.Now(), true},
|
||||
{[]byte("hello2"), []byte("hello1"), true},
|
||||
{json.Number("124"), json.Number("123"), true},
|
||||
{645680099112988675, 645680099112988673, true},
|
||||
{1, 1, false},
|
||||
{1, int64(1), false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.want, GreaterThan(tt.left, tt.right))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestLessOrEqual(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestLessOrEqual")
|
||||
|
||||
tests := []struct {
|
||||
left any
|
||||
right any
|
||||
want bool
|
||||
}{
|
||||
{1, 2, true},
|
||||
{1, 1, true},
|
||||
{1.1, 2.2, true},
|
||||
{"a", "b", true},
|
||||
{time.Now(), time.Now().Add(time.Second), true},
|
||||
{[]byte("hello1"), []byte("hello2"), true},
|
||||
{json.Number("123"), json.Number("124"), true},
|
||||
{645680099112988673, 645680099112988675, true},
|
||||
{2, 1, false},
|
||||
{1, int64(2), false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.want, LessOrEqual(tt.left, tt.right))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGreaterOrEqual(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestGreaterThan")
|
||||
|
||||
tests := []struct {
|
||||
left any
|
||||
right any
|
||||
want bool
|
||||
}{
|
||||
{2, 1, true},
|
||||
{1, 1, true},
|
||||
{2.2, 1.1, true},
|
||||
{"b", "b", true},
|
||||
{time.Now().Add(time.Second), time.Now(), true},
|
||||
{[]byte("hello2"), []byte("hello1"), true},
|
||||
{json.Number("124"), json.Number("123"), true},
|
||||
{645680099112988675, 645680099112988673, true},
|
||||
{1, 2, false},
|
||||
{int64(2), 1, false},
|
||||
{"b", "c", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.want, GreaterOrEqual(tt.left, tt.right))
|
||||
}
|
||||
}
|
||||
|
||||
func TestInDelta(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestInDelta")
|
||||
|
||||
tests := []struct {
|
||||
left float64
|
||||
right float64
|
||||
delta float64
|
||||
want bool
|
||||
}{
|
||||
{1, 1, 0, true},
|
||||
{1, 2, 0, false},
|
||||
{2.0 / 3.0, 0.66667, 0.001, true},
|
||||
{2.0 / 3.0, 0.0, 0.001, false},
|
||||
{float64(74.96) - float64(20.48), 54.48, 0, false},
|
||||
{float64(74.96) - float64(20.48), 54.48, 1e-14, true},
|
||||
{float64(float32(80.45)), float64(80.45), 0, false},
|
||||
{float64(float32(80.45)), float64(80.45), 1e-5, true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.want, InDelta(tt.left, tt.right, tt.delta))
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2021 dudaodong@gmail.com. All rights reserved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
// Package concurrency contain some functions to support concurrent programming. eg, goroutine, channel, async.
|
||||
// Package concurrency contain some functions to support concurrent programming. eg, goroutine, channel.
|
||||
package concurrency
|
||||
|
||||
import (
|
||||
@@ -11,17 +11,18 @@ import (
|
||||
|
||||
// Channel is a logic object which can generate or manipulate go channel
|
||||
// all methods of Channel are in the book tilted《Concurrency in Go》
|
||||
type Channel struct {
|
||||
type Channel[T any] struct {
|
||||
}
|
||||
|
||||
// NewChannel return a Channel instance
|
||||
func NewChannel() *Channel {
|
||||
return &Channel{}
|
||||
func NewChannel[T any]() *Channel[T] {
|
||||
return &Channel[T]{}
|
||||
}
|
||||
|
||||
// Generate a data of type any chan, put param `values` into the chan
|
||||
func (c *Channel) Generate(ctx context.Context, values ...any) <-chan any {
|
||||
dataStream := make(chan any)
|
||||
// Generate creates channel, then put values into the channel.
|
||||
// Play: https://go.dev/play/p/7aB4KyMMp9A
|
||||
func (c *Channel[T]) Generate(ctx context.Context, values ...T) <-chan T {
|
||||
dataStream := make(chan T)
|
||||
|
||||
go func() {
|
||||
defer close(dataStream)
|
||||
@@ -38,9 +39,10 @@ func (c *Channel) Generate(ctx context.Context, values ...any) <-chan any {
|
||||
return dataStream
|
||||
}
|
||||
|
||||
// Repeat return a data of type any chan, put param `values` into the chan repeatly until cancel the context.
|
||||
func (c *Channel) Repeat(ctx context.Context, values ...any) <-chan any {
|
||||
dataStream := make(chan any)
|
||||
// Repeat create channel, put values into the channel repeatly until cancel the context.
|
||||
// Play: https://go.dev/play/p/k5N_ALVmYjE
|
||||
func (c *Channel[T]) Repeat(ctx context.Context, values ...T) <-chan T {
|
||||
dataStream := make(chan T)
|
||||
|
||||
go func() {
|
||||
defer close(dataStream)
|
||||
@@ -57,10 +59,11 @@ func (c *Channel) Repeat(ctx context.Context, values ...any) <-chan any {
|
||||
return dataStream
|
||||
}
|
||||
|
||||
// RepeatFn return a chan, excutes fn repeatly, and put the result into retruned chan
|
||||
// until close the `done` channel
|
||||
func (c *Channel) RepeatFn(ctx context.Context, fn func() any) <-chan any {
|
||||
dataStream := make(chan any)
|
||||
// RepeatFn create a channel, excutes fn repeatly, and put the result into the channel
|
||||
// until close context.
|
||||
// Play: https://go.dev/play/p/4J1zAWttP85
|
||||
func (c *Channel[T]) RepeatFn(ctx context.Context, fn func() T) <-chan T {
|
||||
dataStream := make(chan T)
|
||||
|
||||
go func() {
|
||||
defer close(dataStream)
|
||||
@@ -75,9 +78,10 @@ func (c *Channel) RepeatFn(ctx context.Context, fn func() any) <-chan any {
|
||||
return dataStream
|
||||
}
|
||||
|
||||
// Take return a chan whose values are tahken from another chan
|
||||
func (c *Channel) Take(ctx context.Context, valueStream <-chan any, number int) <-chan any {
|
||||
takeStream := make(chan any)
|
||||
// Take create a channel whose values are taken from another channel with limit number.
|
||||
// Play: https://go.dev/play/p/9Utt-1pDr2J
|
||||
func (c *Channel[T]) Take(ctx context.Context, valueStream <-chan T, number int) <-chan T {
|
||||
takeStream := make(chan T)
|
||||
|
||||
go func() {
|
||||
defer close(takeStream)
|
||||
@@ -94,16 +98,17 @@ func (c *Channel) Take(ctx context.Context, valueStream <-chan any, number int)
|
||||
return takeStream
|
||||
}
|
||||
|
||||
// FanIn merge multiple channels into one channel
|
||||
func (c *Channel) FanIn(ctx context.Context, channels ...<-chan any) <-chan any {
|
||||
out := make(chan any)
|
||||
// FanIn merge multiple channels into one channel.
|
||||
// Play: https://go.dev/play/p/2VYFMexEvTm
|
||||
func (c *Channel[T]) FanIn(ctx context.Context, channels ...<-chan T) <-chan T {
|
||||
out := make(chan T)
|
||||
|
||||
go func() {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(channels))
|
||||
|
||||
for _, c := range channels {
|
||||
go func(c <-chan any) {
|
||||
go func(c <-chan T) {
|
||||
defer wg.Done()
|
||||
for v := range c {
|
||||
select {
|
||||
@@ -121,10 +126,11 @@ func (c *Channel) FanIn(ctx context.Context, channels ...<-chan any) <-chan any
|
||||
return out
|
||||
}
|
||||
|
||||
// Tee split one chanel into two channels
|
||||
func (c *Channel) Tee(ctx context.Context, in <-chan any) (<-chan any, <-chan any) {
|
||||
out1 := make(chan any)
|
||||
out2 := make(chan any)
|
||||
// Tee split one chanel into two channels, until cancel the context.
|
||||
// Play: https://go.dev/play/p/3TQPKnCirrP
|
||||
func (c *Channel[T]) Tee(ctx context.Context, in <-chan T) (<-chan T, <-chan T) {
|
||||
out1 := make(chan T)
|
||||
out2 := make(chan T)
|
||||
|
||||
go func() {
|
||||
defer close(out1)
|
||||
@@ -147,39 +153,44 @@ func (c *Channel) Tee(ctx context.Context, in <-chan any) (<-chan any, <-chan an
|
||||
return out1, out2
|
||||
}
|
||||
|
||||
// Bridge link multiply channels into one channel
|
||||
func (c *Channel) Bridge(ctx context.Context, chanStream <-chan <-chan any) <-chan any {
|
||||
valStream := make(chan any)
|
||||
|
||||
// Bridge link multiply channels into one channel.
|
||||
// Play: https://go.dev/play/p/qmWSy1NVF-Y
|
||||
func (c *Channel[T]) Bridge(ctx context.Context, chanStream <-chan <-chan T) <-chan T {
|
||||
valStream := make(chan T)
|
||||
go func() {
|
||||
defer close(valStream)
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
defer wg.Wait()
|
||||
for {
|
||||
var stream <-chan any
|
||||
var stream <-chan T
|
||||
select {
|
||||
case maybeStream, ok := <-chanStream:
|
||||
if ok == false {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
stream = maybeStream
|
||||
wg.Add(1)
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
|
||||
for val := range c.OrDone(ctx, stream) {
|
||||
select {
|
||||
case valStream <- val:
|
||||
case <-ctx.Done():
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for val := range c.OrDone(ctx, stream) {
|
||||
select {
|
||||
case valStream <- val:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}()
|
||||
|
||||
return valStream
|
||||
}
|
||||
|
||||
// Or read one or more channels into one channel, will close when any readin channel is closed
|
||||
func (c *Channel) Or(channels ...<-chan any) <-chan any {
|
||||
// Or read one or more channels into one channel, will close when any readin channel is closed.
|
||||
// Play: https://go.dev/play/p/Wqz9rwioPww
|
||||
func (c *Channel[T]) Or(channels ...<-chan T) <-chan T {
|
||||
switch len(channels) {
|
||||
case 0:
|
||||
return nil
|
||||
@@ -187,7 +198,7 @@ func (c *Channel) Or(channels ...<-chan any) <-chan any {
|
||||
return channels[0]
|
||||
}
|
||||
|
||||
orDone := make(chan any)
|
||||
orDone := make(chan T)
|
||||
|
||||
go func() {
|
||||
defer close(orDone)
|
||||
@@ -199,17 +210,12 @@ func (c *Channel) Or(channels ...<-chan any) <-chan any {
|
||||
case <-channels[1]:
|
||||
}
|
||||
default:
|
||||
m := len(channels) / 2
|
||||
select {
|
||||
case <-c.Or(channels[:m]...):
|
||||
case <-c.Or(channels[m:]...):
|
||||
case <-channels[0]:
|
||||
case <-channels[1]:
|
||||
case <-channels[2]:
|
||||
case <-c.Or(append(channels[3:], orDone)...):
|
||||
}
|
||||
// select {
|
||||
// case <-channels[0]:
|
||||
// case <-channels[1]:
|
||||
// case <-channels[2]:
|
||||
// case <-c.Or(append(channels[3:], orDone)...):
|
||||
// }
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -217,8 +223,9 @@ func (c *Channel) Or(channels ...<-chan any) <-chan any {
|
||||
}
|
||||
|
||||
// OrDone read a channel into another channel, will close until cancel context.
|
||||
func (c *Channel) OrDone(ctx context.Context, channel <-chan any) <-chan any {
|
||||
valStream := make(chan any)
|
||||
// Play: https://go.dev/play/p/lm_GoS6aDjo
|
||||
func (c *Channel[T]) OrDone(ctx context.Context, channel <-chan T) <-chan T {
|
||||
valStream := make(chan T)
|
||||
|
||||
go func() {
|
||||
defer close(valStream)
|
||||
|
||||
358
concurrency/channel_example_test.go
Normal file
358
concurrency/channel_example_test.go
Normal file
@@ -0,0 +1,358 @@
|
||||
package concurrency
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ExampleChannel_Generate() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
c := NewChannel[int]()
|
||||
intStream := c.Generate(ctx, 1, 2, 3)
|
||||
|
||||
fmt.Println(<-intStream)
|
||||
fmt.Println(<-intStream)
|
||||
fmt.Println(<-intStream)
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
// 2
|
||||
// 3
|
||||
}
|
||||
|
||||
func ExampleChannel_Repeat() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
c := NewChannel[int]()
|
||||
intStream := c.Take(ctx, c.Repeat(ctx, 1, 2), 4)
|
||||
|
||||
for v := range intStream {
|
||||
fmt.Println(v)
|
||||
}
|
||||
// Output:
|
||||
// 1
|
||||
// 2
|
||||
// 1
|
||||
// 2
|
||||
}
|
||||
|
||||
func ExampleChannel_RepeatFn() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
fn := func() string {
|
||||
return "hello"
|
||||
}
|
||||
|
||||
c := NewChannel[string]()
|
||||
intStream := c.Take(ctx, c.RepeatFn(ctx, fn), 3)
|
||||
|
||||
for v := range intStream {
|
||||
fmt.Println(v)
|
||||
}
|
||||
// Output:
|
||||
// hello
|
||||
// hello
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleChannel_Take() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
numbers := make(chan int, 5)
|
||||
numbers <- 1
|
||||
numbers <- 2
|
||||
numbers <- 3
|
||||
numbers <- 4
|
||||
numbers <- 5
|
||||
defer close(numbers)
|
||||
|
||||
c := NewChannel[int]()
|
||||
intStream := c.Take(ctx, numbers, 3)
|
||||
|
||||
for v := range intStream {
|
||||
fmt.Println(v)
|
||||
}
|
||||
// Output:
|
||||
// 1
|
||||
// 2
|
||||
// 3
|
||||
}
|
||||
|
||||
func ExampleChannel_FanIn() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
c := NewChannel[int]()
|
||||
channels := make([]<-chan int, 2)
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
channels[i] = c.Take(ctx, c.Repeat(ctx, i), 2)
|
||||
}
|
||||
|
||||
chs := c.FanIn(ctx, channels...)
|
||||
|
||||
for v := range chs {
|
||||
fmt.Println(v) //1 1 0 0 or 0 0 1 1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func ExampleChannel_Or() {
|
||||
sig := func(after time.Duration) <-chan any {
|
||||
c := make(chan any)
|
||||
go func() {
|
||||
defer close(c)
|
||||
time.Sleep(after)
|
||||
}()
|
||||
return c
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
|
||||
c := NewChannel[any]()
|
||||
<-c.Or(
|
||||
sig(1*time.Second),
|
||||
sig(2*time.Second),
|
||||
sig(3*time.Second),
|
||||
)
|
||||
|
||||
if time.Since(start).Seconds() < 2 {
|
||||
fmt.Println("ok")
|
||||
}
|
||||
// Output:
|
||||
// ok
|
||||
}
|
||||
|
||||
func ExampleChannel_OrDone() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
c := NewChannel[int]()
|
||||
intStream := c.Take(ctx, c.Repeat(ctx, 1), 3)
|
||||
|
||||
for v := range c.OrDone(ctx, intStream) {
|
||||
fmt.Println(v)
|
||||
}
|
||||
// Output:
|
||||
// 1
|
||||
// 1
|
||||
// 1
|
||||
}
|
||||
|
||||
func ExampleChannel_Tee() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
c := NewChannel[int]()
|
||||
intStream := c.Take(ctx, c.Repeat(ctx, 1), 2)
|
||||
|
||||
ch1, ch2 := c.Tee(ctx, intStream)
|
||||
|
||||
for v := range ch1 {
|
||||
fmt.Println(v)
|
||||
fmt.Println(<-ch2)
|
||||
}
|
||||
// Output:
|
||||
// 1
|
||||
// 1
|
||||
// 1
|
||||
// 1
|
||||
}
|
||||
|
||||
func ExampleChannel_Bridge() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
m1 := make(map[int]int)
|
||||
m2 := make(map[int]int)
|
||||
c := NewChannel[int]()
|
||||
genVals := func() <-chan <-chan int {
|
||||
out := make(chan (<-chan int))
|
||||
go func() {
|
||||
defer close(out)
|
||||
for i := 1; i <= 5; i++ {
|
||||
stream := make(chan int, 1)
|
||||
stream <- i
|
||||
m1[i]++
|
||||
close(stream)
|
||||
out <- stream
|
||||
}
|
||||
}()
|
||||
return out
|
||||
}
|
||||
|
||||
for v := range c.Bridge(ctx, genVals()) {
|
||||
m2[v]++
|
||||
}
|
||||
for k, v := range m1 {
|
||||
fmt.Println(m2[k] == v)
|
||||
}
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
}
|
||||
|
||||
func ExampleKeyedLocker_Do() {
|
||||
locker := NewKeyedLocker[string](2 * time.Second)
|
||||
|
||||
task := func() {
|
||||
fmt.Println("Executing task...")
|
||||
time.Sleep(1 * time.Second)
|
||||
fmt.Println("Task completed.")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := locker.Do(ctx, "mykey", task); err != nil {
|
||||
log.Fatalf("Error executing task: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("Task successfully executed.")
|
||||
}
|
||||
|
||||
ctx2, cancel2 := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel2()
|
||||
|
||||
if err := locker.Do(ctx2, "mykey", task); err != nil {
|
||||
log.Fatalf("Error executing task: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("Task successfully executed.")
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Executing task...
|
||||
// Task completed.
|
||||
// Task successfully executed.
|
||||
// Executing task...
|
||||
// Task completed.
|
||||
// Task successfully executed.
|
||||
}
|
||||
|
||||
func ExampleRWKeyedLocker_Lock() {
|
||||
locker := NewRWKeyedLocker[string](2 * time.Second)
|
||||
|
||||
// Simulate a key
|
||||
key := "resource_key"
|
||||
|
||||
fn := func() {
|
||||
fmt.Println("Starting write operation...")
|
||||
// Simulate write operation, assuming it takes 2 seconds
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
fmt.Println("Write operation completed!")
|
||||
}
|
||||
|
||||
// Acquire the write lock and execute the operation
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Execute the lock operation with a 3-second timeout
|
||||
err := locker.Lock(ctx, key, fn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//output:
|
||||
//Starting write operation...
|
||||
//Write operation completed!
|
||||
}
|
||||
|
||||
func ExampleRWKeyedLocker_RLock() {
|
||||
locker := NewRWKeyedLocker[string](2 * time.Second)
|
||||
|
||||
// Simulate a key
|
||||
key := "resource_key"
|
||||
|
||||
fn := func() {
|
||||
fmt.Println("Starting write operation...")
|
||||
// Simulate write operation, assuming it takes 2 seconds
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
fmt.Println("Write operation completed!")
|
||||
}
|
||||
|
||||
// Acquire the write lock and execute the operation
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Execute the lock operation with a 3-second timeout
|
||||
err := locker.RLock(ctx, key, fn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//output:
|
||||
//Starting write operation...
|
||||
//Write operation completed!
|
||||
}
|
||||
|
||||
func ExampleTryKeyedLocker() {
|
||||
locker := NewTryKeyedLocker[string]()
|
||||
|
||||
key := "resource_key"
|
||||
|
||||
if locker.TryLock(key) {
|
||||
fmt.Println("Lock acquired")
|
||||
time.Sleep(1 * time.Second)
|
||||
// Unlock after work is done
|
||||
locker.Unlock(key)
|
||||
fmt.Println("Lock released")
|
||||
} else {
|
||||
fmt.Println("Lock failed")
|
||||
}
|
||||
|
||||
//output:
|
||||
//Lock acquired
|
||||
//Lock released
|
||||
}
|
||||
|
||||
func ExampleTryKeyedLocker_TryLock() {
|
||||
locker := NewTryKeyedLocker[string]()
|
||||
|
||||
key := "resource_key"
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
if locker.TryLock(key) {
|
||||
time.Sleep(2 * time.Second)
|
||||
locker.Unlock(key)
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
if locker.TryLock(key) {
|
||||
fmt.Println("Lock acquired")
|
||||
locker.Unlock(key)
|
||||
} else {
|
||||
fmt.Println("Lock failed")
|
||||
}
|
||||
|
||||
// wait for the goroutine to finish
|
||||
<-done
|
||||
|
||||
fmt.Println("Retrying...")
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
if locker.TryLock(key) {
|
||||
fmt.Println("Lock acquired")
|
||||
locker.Unlock(key)
|
||||
fmt.Println("Lock released")
|
||||
} else {
|
||||
fmt.Println("Lock failed")
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Lock failed
|
||||
// Retrying...
|
||||
// Lock acquired
|
||||
// Lock released
|
||||
}
|
||||
@@ -9,34 +9,30 @@ import (
|
||||
)
|
||||
|
||||
func TestGenerate(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestGenerate")
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
c := NewChannel()
|
||||
c := NewChannel[int]()
|
||||
intStream := c.Generate(ctx, 1, 2, 3)
|
||||
|
||||
// for v := range intStream {
|
||||
// t.Log(v) //1, 2, 3
|
||||
// }
|
||||
assert.Equal(1, <-intStream)
|
||||
assert.Equal(2, <-intStream)
|
||||
assert.Equal(3, <-intStream)
|
||||
}
|
||||
|
||||
func TestRepeat(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestRepeat")
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
c := NewChannel()
|
||||
c := NewChannel[int]()
|
||||
intStream := c.Take(ctx, c.Repeat(ctx, 1, 2), 5)
|
||||
|
||||
// for v := range intStream {
|
||||
// t.Log(v) //1, 2, 1, 2, 1
|
||||
// }
|
||||
assert.Equal(1, <-intStream)
|
||||
assert.Equal(2, <-intStream)
|
||||
assert.Equal(1, <-intStream)
|
||||
@@ -45,34 +41,32 @@ func TestRepeat(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRepeatFn(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestRepeatFn")
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
fn := func() any {
|
||||
fn := func() string {
|
||||
s := "a"
|
||||
return s
|
||||
}
|
||||
c := NewChannel()
|
||||
c := NewChannel[string]()
|
||||
dataStream := c.Take(ctx, c.RepeatFn(ctx, fn), 3)
|
||||
|
||||
// for v := range dataStream {
|
||||
// t.Log(v) //a, a, a
|
||||
// }
|
||||
|
||||
assert.Equal("a", <-dataStream)
|
||||
assert.Equal("a", <-dataStream)
|
||||
assert.Equal("a", <-dataStream)
|
||||
}
|
||||
|
||||
func TestTake(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestTake")
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
numbers := make(chan any, 5)
|
||||
numbers := make(chan int, 5)
|
||||
numbers <- 1
|
||||
numbers <- 2
|
||||
numbers <- 3
|
||||
@@ -80,7 +74,7 @@ func TestTake(t *testing.T) {
|
||||
numbers <- 5
|
||||
defer close(numbers)
|
||||
|
||||
c := NewChannel()
|
||||
c := NewChannel[int]()
|
||||
intStream := c.Take(ctx, numbers, 3)
|
||||
|
||||
assert.Equal(1, <-intStream)
|
||||
@@ -89,13 +83,14 @@ func TestTake(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFanIn(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestFanIn")
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
c := NewChannel()
|
||||
channels := make([]<-chan any, 3)
|
||||
c := NewChannel[int]()
|
||||
channels := make([]<-chan int, 3)
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
channels[i] = c.Take(ctx, c.Repeat(ctx, i), 3)
|
||||
@@ -111,6 +106,7 @@ func TestFanIn(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestOr(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestOr")
|
||||
|
||||
sig := func(after time.Duration) <-chan any {
|
||||
@@ -124,7 +120,7 @@ func TestOr(t *testing.T) {
|
||||
|
||||
start := time.Now()
|
||||
|
||||
c := NewChannel()
|
||||
c := NewChannel[any]()
|
||||
<-c.Or(
|
||||
sig(1*time.Second),
|
||||
sig(2*time.Second),
|
||||
@@ -133,63 +129,60 @@ func TestOr(t *testing.T) {
|
||||
sig(5*time.Second),
|
||||
)
|
||||
|
||||
t.Logf("done after %v", time.Since(start))
|
||||
|
||||
assert.Equal(1, 1)
|
||||
assert.Equal(true, time.Since(start).Seconds() < 2)
|
||||
}
|
||||
|
||||
func TestOrDone(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestOrDone")
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
c := NewChannel()
|
||||
c := NewChannel[int]()
|
||||
intStream := c.Take(ctx, c.Repeat(ctx, 1), 3)
|
||||
|
||||
var res any
|
||||
for val := range c.OrDone(ctx, intStream) {
|
||||
t.Logf("%v", val)
|
||||
res = val
|
||||
assert.Equal(1, val)
|
||||
}
|
||||
|
||||
assert.Equal(1, res)
|
||||
}
|
||||
|
||||
func TestTee(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestTee")
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
c := NewChannel()
|
||||
c := NewChannel[int]()
|
||||
inStream := c.Take(ctx, c.Repeat(ctx, 1), 4)
|
||||
|
||||
out1, out2 := c.Tee(ctx, inStream)
|
||||
for val := range out1 {
|
||||
val1 := val
|
||||
val2 := <-out2
|
||||
// t.Log("val1 is", val1)
|
||||
// t.Log("val2 is", val2)
|
||||
assert.Equal(1, val1)
|
||||
assert.Equal(1, val2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBridge(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestBridge")
|
||||
|
||||
m1 := make(map[int]int)
|
||||
m2 := make(map[int]int)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
c := NewChannel()
|
||||
genVals := func() <-chan <-chan any {
|
||||
chanStream := make(chan (<-chan any))
|
||||
c := NewChannel[int]()
|
||||
genVals := func() <-chan <-chan int {
|
||||
chanStream := make(chan (<-chan int))
|
||||
go func() {
|
||||
defer close(chanStream)
|
||||
for i := 0; i < 10; i++ {
|
||||
stream := make(chan any, 1)
|
||||
stream := make(chan int, 1)
|
||||
stream <- i
|
||||
m1[i]++
|
||||
close(stream)
|
||||
chanStream <- stream
|
||||
}
|
||||
@@ -197,10 +190,11 @@ func TestBridge(t *testing.T) {
|
||||
return chanStream
|
||||
}
|
||||
|
||||
index := 0
|
||||
for val := range c.Bridge(ctx, genVals()) {
|
||||
// t.Logf("%v ", val) //0 1 2 3 4 5 6 7 8 9
|
||||
assert.Equal(index, val)
|
||||
index++
|
||||
m2[val]++
|
||||
}
|
||||
|
||||
for k, v := range m1 {
|
||||
assert.Equal(m2[k], v)
|
||||
}
|
||||
}
|
||||
|
||||
257
concurrency/keyed_locker.go
Normal file
257
concurrency/keyed_locker.go
Normal file
@@ -0,0 +1,257 @@
|
||||
// Copyright 2025 dudaodong@gmail.com. All rights reserved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
// Package concurrency contain some functions to support concurrent programming. eg, goroutine, channel, locker.
|
||||
package concurrency
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// KeyedLocker is a simple implementation of a keyed locker that allows for non-blocking lock acquisition.
|
||||
type KeyedLocker[K comparable] struct {
|
||||
locks sync.Map
|
||||
ttl time.Duration
|
||||
}
|
||||
|
||||
type lockEntry struct {
|
||||
mu sync.Mutex
|
||||
ref int32
|
||||
timer atomic.Pointer[time.Timer]
|
||||
}
|
||||
|
||||
// NewKeyedLocker creates a new KeyedLocker with the specified TTL for lock expiration.
|
||||
// The TTL is used to automatically release locks that are no longer held.
|
||||
// Play: https://go.dev/play/p/GzeyC33T5rw
|
||||
func NewKeyedLocker[K comparable](ttl time.Duration) *KeyedLocker[K] {
|
||||
return &KeyedLocker[K]{ttl: ttl}
|
||||
}
|
||||
|
||||
// Do acquires a lock for the specified key and executes the provided function.
|
||||
// It returns an error if the context is canceled before the function completes.
|
||||
// Play: https://go.dev/play/p/GzeyC33T5rw
|
||||
func (l *KeyedLocker[K]) Do(ctx context.Context, key K, fn func()) error {
|
||||
entry := l.acquire(key)
|
||||
defer l.release(key, entry, key)
|
||||
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
entry.mu.Lock()
|
||||
defer entry.mu.Unlock()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
default:
|
||||
fn()
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-done:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// acquire tries to acquire a lock for the specified key.
|
||||
func (l *KeyedLocker[K]) acquire(key K) *lockEntry {
|
||||
lock, _ := l.locks.LoadOrStore(key, &lockEntry{})
|
||||
entry := lock.(*lockEntry)
|
||||
|
||||
atomic.AddInt32(&entry.ref, 1)
|
||||
if t := entry.timer.Swap(nil); t != nil {
|
||||
t.Stop()
|
||||
}
|
||||
|
||||
return entry
|
||||
}
|
||||
|
||||
// release releases the lock for the specified key.
|
||||
func (l *KeyedLocker[K]) release(key K, entry *lockEntry, rawKey K) {
|
||||
if atomic.AddInt32(&entry.ref, -1) == 0 {
|
||||
entry.mu.Lock()
|
||||
defer entry.mu.Unlock()
|
||||
|
||||
if entry.ref == 0 {
|
||||
if t := entry.timer.Swap(nil); t != nil {
|
||||
t.Stop()
|
||||
}
|
||||
|
||||
l.locks.Delete(rawKey)
|
||||
} else {
|
||||
if entry.timer.Load() == nil {
|
||||
t := time.AfterFunc(l.ttl, func() {
|
||||
l.release(key, entry, rawKey)
|
||||
})
|
||||
entry.timer.Store(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RWKeyedLocker is a read-write version of KeyedLocker.
|
||||
type RWKeyedLocker[K comparable] struct {
|
||||
locks sync.Map
|
||||
ttl time.Duration
|
||||
}
|
||||
|
||||
type rwLockEntry struct {
|
||||
mu sync.RWMutex
|
||||
ref int32
|
||||
timer atomic.Pointer[time.Timer]
|
||||
}
|
||||
|
||||
// NewRWKeyedLocker creates a new RWKeyedLocker with the specified TTL for lock expiration.
|
||||
// The TTL is used to automatically release locks that are no longer held.
|
||||
// Play: https://go.dev/play/p/CkaJWWwZm9
|
||||
func NewRWKeyedLocker[K comparable](ttl time.Duration) *RWKeyedLocker[K] {
|
||||
return &RWKeyedLocker[K]{ttl: ttl}
|
||||
}
|
||||
|
||||
// RLock acquires a read lock for the specified key and executes the provided function.
|
||||
// It returns an error if the context is canceled before the function completes.
|
||||
// Play: https://go.dev/play/p/ZrCr8sMo77T
|
||||
func (l *RWKeyedLocker[K]) RLock(ctx context.Context, key K, fn func()) error {
|
||||
entry := l.acquire(key)
|
||||
defer l.release(entry, key)
|
||||
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
entry.mu.RLock()
|
||||
defer entry.mu.RUnlock()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
default:
|
||||
fn()
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-done:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Lock acquires a write lock for the specified key and executes the provided function.
|
||||
// It returns an error if the context is canceled before the function completes.
|
||||
// Play: https://go.dev/play/p/WgAcXbOPKGk
|
||||
func (l *RWKeyedLocker[K]) Lock(ctx context.Context, key K, fn func()) error {
|
||||
entry := l.acquire(key)
|
||||
defer l.release(entry, key)
|
||||
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
entry.mu.Lock()
|
||||
defer entry.mu.Unlock()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
default:
|
||||
fn()
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-done:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// acquire tries to acquire a read lock for the specified key.
|
||||
func (l *RWKeyedLocker[K]) acquire(key K) *rwLockEntry {
|
||||
actual, _ := l.locks.LoadOrStore(key, &rwLockEntry{})
|
||||
entry := actual.(*rwLockEntry)
|
||||
atomic.AddInt32(&entry.ref, 1)
|
||||
|
||||
if t := entry.timer.Swap(nil); t != nil {
|
||||
t.Stop()
|
||||
}
|
||||
return entry
|
||||
}
|
||||
|
||||
// release releases the lock for the specified key.
|
||||
func (l *RWKeyedLocker[K]) release(entry *rwLockEntry, rawKey K) {
|
||||
if atomic.AddInt32(&entry.ref, -1) == 0 {
|
||||
timer := time.AfterFunc(l.ttl, func() {
|
||||
if atomic.LoadInt32(&entry.ref) == 0 {
|
||||
l.locks.Delete(rawKey)
|
||||
}
|
||||
})
|
||||
entry.timer.Store(timer)
|
||||
}
|
||||
}
|
||||
|
||||
// TryKeyedLocker is a non-blocking version of KeyedLocker.
|
||||
// It allows for trying to acquire a lock without blocking if the lock is already held.
|
||||
type TryKeyedLocker[K comparable] struct {
|
||||
mu sync.Mutex
|
||||
locks map[K]*casMutex
|
||||
}
|
||||
|
||||
// NewTryKeyedLocker creates a new TryKeyedLocker.
|
||||
// Play: https://go.dev/play/p/VG9qLvyetE2
|
||||
func NewTryKeyedLocker[K comparable]() *TryKeyedLocker[K] {
|
||||
return &TryKeyedLocker[K]{locks: make(map[K]*casMutex)}
|
||||
}
|
||||
|
||||
// TryLock tries to acquire a lock for the specified key.
|
||||
// It returns true if the lock was acquired, false otherwise.
|
||||
// Play: https://go.dev/play/p/VG9qLvyetE2
|
||||
func (l *TryKeyedLocker[K]) TryLock(key K) bool {
|
||||
l.mu.Lock()
|
||||
|
||||
lock, ok := l.locks[key]
|
||||
if !ok {
|
||||
lock = &casMutex{}
|
||||
l.locks[key] = lock
|
||||
}
|
||||
l.mu.Unlock()
|
||||
|
||||
return lock.TryLock()
|
||||
}
|
||||
|
||||
// Unlock releases the lock for the specified key.
|
||||
// Play: https://go.dev/play/p/VG9qLvyetE2
|
||||
func (l *TryKeyedLocker[K]) Unlock(key K) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
lock, ok := l.locks[key]
|
||||
if ok {
|
||||
lock.Unlock()
|
||||
if lock.lock == 0 {
|
||||
delete(l.locks, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// casMutex is a simple mutex that uses atomic operations to provide a non-blocking lock.
|
||||
type casMutex struct {
|
||||
lock int32
|
||||
}
|
||||
|
||||
// TryLock tries to acquire the lock without blocking.
|
||||
// It returns true if the lock was acquired, false otherwise.
|
||||
func (m *casMutex) TryLock() bool {
|
||||
return atomic.CompareAndSwapInt32(&m.lock, 0, 1)
|
||||
}
|
||||
|
||||
// Unlock releases the lock.
|
||||
func (m *casMutex) Unlock() {
|
||||
atomic.StoreInt32(&m.lock, 0)
|
||||
}
|
||||
230
concurrency/keyed_locker_test.go
Normal file
230
concurrency/keyed_locker_test.go
Normal file
@@ -0,0 +1,230 @@
|
||||
package concurrency
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
|
||||
func TestKeyedLocker_SerialExecutionSameKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestKeyedLocker_SerialExecutionSameKey")
|
||||
|
||||
locker := NewKeyedLocker[string](100 * time.Millisecond)
|
||||
|
||||
var result []int
|
||||
var mu sync.Mutex
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
for i := 0; i < 5; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
err := locker.Do(context.Background(), "key1", func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
result = append(result, i)
|
||||
})
|
||||
|
||||
assert.IsNil(err)
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
assert.Equal(5, len(result))
|
||||
}
|
||||
|
||||
func TestKeyedLocker_ParallelExecutionDifferentKeys(t *testing.T) {
|
||||
locker := NewKeyedLocker[string](100 * time.Millisecond)
|
||||
|
||||
start := time.Now()
|
||||
wg := sync.WaitGroup{}
|
||||
for i := 0; i < 5; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
key := "key" + strconv.Itoa(i)
|
||||
locker.Do(context.Background(), key, func() {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
})
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
elapsed := time.Since(start)
|
||||
|
||||
if elapsed > 100*time.Millisecond {
|
||||
t.Errorf("parallel execution took too long: %s", elapsed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyedLocker_ContextTimeout(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestKeyedLocker_ContextTimeout")
|
||||
|
||||
locker := NewKeyedLocker[string](100 * time.Millisecond)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
// Lock key before calling
|
||||
go func() {
|
||||
_ = locker.Do(context.Background(), "key-timeout", func() {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
})
|
||||
}()
|
||||
|
||||
time.Sleep(1 * time.Millisecond) // ensure lock is acquired first
|
||||
|
||||
err := locker.Do(ctx, "key-timeout", func() {
|
||||
t.Error("should not execute")
|
||||
})
|
||||
|
||||
assert.IsNotNil(err)
|
||||
}
|
||||
|
||||
func TestKeyedLocker_LockReleaseAfterTTL(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestKeyedLocker_LockReleaseAfterTTL")
|
||||
|
||||
locker := NewKeyedLocker[string](50 * time.Millisecond)
|
||||
|
||||
err := locker.Do(context.Background(), "ttl-key", func() {})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Wait for TTL to pass
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
err = locker.Do(context.Background(), "ttl-key", func() {})
|
||||
assert.IsNil(err)
|
||||
}
|
||||
|
||||
func TestRWKeyedLocker_LockAndUnlock(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestKeyedLocker_LockReleaseAfterTTL")
|
||||
|
||||
locker := NewRWKeyedLocker[string](500 * time.Millisecond)
|
||||
|
||||
var locked bool
|
||||
err := locker.Lock(context.Background(), "key1", func() {
|
||||
locked = true
|
||||
})
|
||||
|
||||
assert.IsNil(err)
|
||||
assert.Equal(true, locked)
|
||||
}
|
||||
|
||||
func TestRWKeyedLocker_RLockParallel(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestKeyedLocker_LockReleaseAfterTTL")
|
||||
|
||||
locker := NewRWKeyedLocker[string](1 * time.Second)
|
||||
|
||||
var mu sync.Mutex
|
||||
var count int
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
err := locker.RLock(context.Background(), "shared-key", func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
mu.Lock()
|
||||
count++
|
||||
mu.Unlock()
|
||||
})
|
||||
|
||||
assert.IsNil(err)
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
assert.Equal(5, count)
|
||||
}
|
||||
|
||||
func TestRWKeyedLocker_LockTimeout(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestRWKeyedLocker_LockTimeout")
|
||||
|
||||
locker := NewRWKeyedLocker[string](1 * time.Second)
|
||||
|
||||
start := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
locker.Lock(context.Background(), "key-timeout", func() {
|
||||
close(start)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
})
|
||||
}()
|
||||
|
||||
<-start
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
err := locker.Lock(ctx, "key-timeout", func() {
|
||||
t.Error("should not reach here")
|
||||
})
|
||||
|
||||
assert.IsNotNil(err)
|
||||
}
|
||||
|
||||
func TestTryKeyedLocker_SimpleLockUnlock(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestTryKeyedLocker_SimpleLockUnlock")
|
||||
|
||||
locker := NewTryKeyedLocker[string]()
|
||||
|
||||
ok := locker.TryLock("key1")
|
||||
assert.Equal(true, ok)
|
||||
|
||||
ok = locker.TryLock("key1")
|
||||
assert.Equal(false, ok)
|
||||
|
||||
locker.Unlock("key1")
|
||||
|
||||
ok = locker.TryLock("key1")
|
||||
assert.Equal(true, ok)
|
||||
|
||||
locker.Unlock("key1")
|
||||
}
|
||||
|
||||
func TestTryKeyedLocker_ParallelTry(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestTryKeyedLocker_ParallelTry")
|
||||
|
||||
locker := NewTryKeyedLocker[string]()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var mu sync.Mutex
|
||||
var count int
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
ok := locker.TryLock("key" + strconv.Itoa(i))
|
||||
mu.Lock()
|
||||
if ok {
|
||||
count++
|
||||
}
|
||||
mu.Unlock()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
if ok {
|
||||
locker.Unlock("key" + strconv.Itoa(i))
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
assert.Equal(5, count)
|
||||
assert.Equal(0, len(locker.locks))
|
||||
}
|
||||
88
condition/condition.go
Normal file
88
condition/condition.go
Normal file
@@ -0,0 +1,88 @@
|
||||
// Copyright 2021 dudaodong@gmail.com. All rights reserved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
// Package condition contains some functions for conditional judgment. eg. And, Or, TernaryOperator ...
|
||||
// The implementation of this package refers to the implementation of carlmjohnson's truthy package, you may find more
|
||||
// useful information in truthy(https://github.com/carlmjohnson/truthy), thanks carlmjohnson.
|
||||
package condition
|
||||
|
||||
import "reflect"
|
||||
|
||||
// Bool returns the truthy value of anything.
|
||||
// If the value's type has a Bool() bool method, the method is called and returned.
|
||||
// If the type has an IsZero() bool method, the opposite value is returned.
|
||||
// Slices and maps are truthy if they have a length greater than zero.
|
||||
// All other types are truthy if they are not their zero value.
|
||||
// Play: https://go.dev/play/p/ETzeDJRSvhm
|
||||
func Bool[T any](value T) bool {
|
||||
switch m := any(value).(type) {
|
||||
case interface{ Bool() bool }:
|
||||
return m.Bool()
|
||||
case interface{ IsZero() bool }:
|
||||
return !m.IsZero()
|
||||
}
|
||||
return reflectValue(&value)
|
||||
}
|
||||
|
||||
func reflectValue(vp any) bool {
|
||||
switch rv := reflect.ValueOf(vp).Elem(); rv.Kind() {
|
||||
case reflect.Map, reflect.Slice:
|
||||
return rv.Len() != 0
|
||||
default:
|
||||
is := rv.IsZero()
|
||||
return !is
|
||||
}
|
||||
}
|
||||
|
||||
// And returns true if both a and b are truthy.
|
||||
// Play: https://go.dev/play/p/W1SSUmt6pvr
|
||||
func And[T, U any](a T, b U) bool {
|
||||
return Bool(a) && Bool(b)
|
||||
}
|
||||
|
||||
// Or returns false if neither a nor b is truthy.
|
||||
// Play: https://go.dev/play/p/UlQTxHaeEkq
|
||||
func Or[T, U any](a T, b U) bool {
|
||||
return Bool(a) || Bool(b)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return Bool(a) != Bool(b)
|
||||
}
|
||||
|
||||
// Nor returns true if neither a nor b is truthy.
|
||||
// Play: https://go.dev/play/p/g2j08F_zZky
|
||||
func Nor[T, U any](a T, b U) bool {
|
||||
return !(Bool(a) || Bool(b))
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return Bool(a) == Bool(b)
|
||||
}
|
||||
|
||||
// Nand returns false if both a and b are truthy.
|
||||
// Play: https://go.dev/play/p/vSRMLxLIbq8
|
||||
func Nand[T, U any](a T, b U) bool {
|
||||
return !Bool(a) || !Bool(b)
|
||||
}
|
||||
|
||||
// TernaryOperator checks the value of param `isTrue`, if true return ifValue else return elseValue.
|
||||
// Play: https://go.dev/play/p/ElllPZY0guT
|
||||
func Ternary[T, U any](isTrue T, ifValue U, elseValue U) U {
|
||||
if Bool(isTrue) {
|
||||
return ifValue
|
||||
} else {
|
||||
return elseValue
|
||||
}
|
||||
}
|
||||
|
||||
// TernaryOperator checks the value of param `isTrue`, if true return ifValue else return elseValue.
|
||||
// Play: https://go.dev/play/p/ElllPZY0guT
|
||||
// Deprecated: Use Ternary instead.
|
||||
func TernaryOperator[T, U any](isTrue T, ifValue U, elseValue U) U {
|
||||
return Ternary(isTrue, ifValue, elseValue)
|
||||
}
|
||||
163
condition/condition_example_test.go
Normal file
163
condition/condition_example_test.go
Normal file
@@ -0,0 +1,163 @@
|
||||
package condition
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleBool() {
|
||||
// bool
|
||||
result1 := Bool(false)
|
||||
result2 := Bool(true)
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
|
||||
// integer
|
||||
result3 := Bool(0)
|
||||
result4 := Bool(1)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
|
||||
// string
|
||||
result5 := Bool("")
|
||||
result6 := Bool(" ")
|
||||
fmt.Println(result5)
|
||||
fmt.Println(result6)
|
||||
|
||||
// slice
|
||||
var nums = []int{}
|
||||
result7 := Bool(nums)
|
||||
nums = append(nums, 1, 2)
|
||||
result8 := Bool(nums)
|
||||
fmt.Println(result7)
|
||||
fmt.Println(result8)
|
||||
|
||||
// Output:
|
||||
// false
|
||||
// true
|
||||
// false
|
||||
// true
|
||||
// false
|
||||
// true
|
||||
// false
|
||||
// true
|
||||
}
|
||||
|
||||
func ExampleAnd() {
|
||||
result1 := And(0, 1)
|
||||
result2 := And(0, "")
|
||||
result3 := And(0, "0")
|
||||
result4 := And(1, "0")
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
|
||||
// Output:
|
||||
// false
|
||||
// false
|
||||
// false
|
||||
// true
|
||||
}
|
||||
|
||||
func ExampleOr() {
|
||||
result1 := Or(0, "")
|
||||
result2 := Or(0, 1)
|
||||
result3 := Or(0, "0")
|
||||
result4 := Or(1, "0")
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
|
||||
// Output:
|
||||
// false
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
}
|
||||
|
||||
func ExampleXor() {
|
||||
result1 := Xor(0, 0)
|
||||
result2 := Xor(1, 1)
|
||||
result3 := Xor(0, 1)
|
||||
result4 := Xor(1, 0)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
|
||||
// Output:
|
||||
// false
|
||||
// false
|
||||
// true
|
||||
// true
|
||||
}
|
||||
|
||||
func ExampleNor() {
|
||||
result1 := Nor(0, 0)
|
||||
result2 := Nor(1, 1)
|
||||
result3 := Nor(0, 1)
|
||||
result4 := Nor(1, 0)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
// false
|
||||
// false
|
||||
}
|
||||
|
||||
func ExampleXnor() {
|
||||
result1 := Xnor(0, 0)
|
||||
result2 := Xnor(1, 1)
|
||||
result3 := Xnor(0, 1)
|
||||
result4 := Xnor(1, 0)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
// false
|
||||
// false
|
||||
}
|
||||
|
||||
func ExampleNand() {
|
||||
result1 := Nand(0, 0)
|
||||
result2 := Nand(1, 0)
|
||||
result3 := Nand(0, 1)
|
||||
result4 := Nand(1, 1)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// false
|
||||
}
|
||||
|
||||
func ExampleTernary() {
|
||||
conditionTrue := 2 > 1
|
||||
result1 := Ternary(conditionTrue, 0, 1)
|
||||
fmt.Println(result1)
|
||||
|
||||
conditionFalse := 2 > 3
|
||||
result2 := Ternary(conditionFalse, 0, 1)
|
||||
fmt.Println(result2)
|
||||
|
||||
// Output:
|
||||
// 0
|
||||
// 1
|
||||
}
|
||||
136
condition/condition_test.go
Normal file
136
condition/condition_test.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package condition
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
|
||||
type TestStruct struct{}
|
||||
|
||||
func TestBool(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestBool")
|
||||
|
||||
// bool
|
||||
assert.Equal(false, Bool(false))
|
||||
assert.Equal(true, Bool(true))
|
||||
|
||||
// integer
|
||||
assert.Equal(false, Bool(0))
|
||||
assert.Equal(true, Bool(1))
|
||||
|
||||
// float
|
||||
assert.Equal(false, Bool(0.0))
|
||||
assert.Equal(true, Bool(0.1))
|
||||
|
||||
// string
|
||||
assert.Equal(false, Bool(""))
|
||||
assert.Equal(true, Bool(" "))
|
||||
assert.Equal(true, Bool("0"))
|
||||
|
||||
// slice
|
||||
var nums [2]int
|
||||
assert.Equal(false, Bool(nums))
|
||||
nums = [2]int{0, 1}
|
||||
assert.Equal(true, Bool(nums))
|
||||
|
||||
// map
|
||||
assert.Equal(false, Bool(map[string]string{}))
|
||||
assert.Equal(true, Bool(map[string]string{"a": "a"}))
|
||||
|
||||
// channel
|
||||
var ch chan int
|
||||
assert.Equal(false, Bool(ch))
|
||||
ch = make(chan int)
|
||||
assert.Equal(true, Bool(ch))
|
||||
|
||||
// interface
|
||||
var err error
|
||||
assert.Equal(false, Bool(err))
|
||||
err = errors.New("error message")
|
||||
assert.Equal(true, Bool(err))
|
||||
|
||||
// struct
|
||||
assert.Equal(false, Bool(struct{}{}))
|
||||
assert.Equal(true, Bool(time.Now()))
|
||||
|
||||
// struct pointer
|
||||
ts := TestStruct{}
|
||||
assert.Equal(false, Bool(ts))
|
||||
assert.Equal(true, Bool(&ts))
|
||||
}
|
||||
|
||||
func TestAnd(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestAnd")
|
||||
assert.Equal(false, And(0, 1))
|
||||
assert.Equal(false, And(0, ""))
|
||||
assert.Equal(false, And(0, "0"))
|
||||
assert.Equal(true, And(1, "0"))
|
||||
}
|
||||
|
||||
func TestOr(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestOr")
|
||||
assert.Equal(false, Or(0, ""))
|
||||
assert.Equal(true, Or(0, 1))
|
||||
assert.Equal(true, Or(0, "0"))
|
||||
assert.Equal(true, Or(1, "0"))
|
||||
}
|
||||
|
||||
func TestXor(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestOr")
|
||||
assert.Equal(false, Xor(0, 0))
|
||||
assert.Equal(true, Xor(0, 1))
|
||||
assert.Equal(true, Xor(1, 0))
|
||||
assert.Equal(false, Xor(1, 1))
|
||||
}
|
||||
|
||||
func TestNor(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestNor")
|
||||
assert.Equal(true, Nor(0, 0))
|
||||
assert.Equal(false, Nor(0, 1))
|
||||
assert.Equal(false, Nor(1, 0))
|
||||
assert.Equal(false, Nor(1, 1))
|
||||
}
|
||||
|
||||
func TestXnor(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestXnor")
|
||||
assert.Equal(true, Xnor(0, 0))
|
||||
assert.Equal(false, Xnor(0, 1))
|
||||
assert.Equal(false, Xnor(1, 0))
|
||||
assert.Equal(true, Xnor(1, 1))
|
||||
}
|
||||
|
||||
func TestNand(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestNand")
|
||||
assert.Equal(true, Nand(0, 0))
|
||||
assert.Equal(true, Nand(0, 1))
|
||||
assert.Equal(true, Nand(1, 0))
|
||||
assert.Equal(false, Nand(1, 1))
|
||||
}
|
||||
|
||||
func TestTernary(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestTernary")
|
||||
|
||||
trueValue := "1"
|
||||
falseValue := "0"
|
||||
|
||||
assert.Equal(trueValue, Ternary(true, trueValue, falseValue))
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
// Copyright 2021 dudaodong@gmail.com. All rights reserved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
// Package lancetconstraints contain some comstomer constraints.
|
||||
package lancetconstraints
|
||||
// Package constraints contain some custom interface.
|
||||
package constraints
|
||||
|
||||
// Comparator is for comparing two values
|
||||
type Comparator interface {
|
||||
@@ -11,8 +11,3 @@ type Comparator interface {
|
||||
// Descending order: should return 1 -> v1 < v2, 0 -> v1 = v2, -1 -> v1 > v2
|
||||
Compare(v1, v2 any) int
|
||||
}
|
||||
|
||||
// Number contains all types of number and uintptr, used for generics constraint
|
||||
type Number interface {
|
||||
int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | uintptr | float32 | float64
|
||||
}
|
||||
@@ -6,22 +6,32 @@ package convertor
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/duke-git/lancet/v2/structs"
|
||||
"golang.org/x/text/encoding/simplifiedchinese"
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
// ToBool convert string to a boolean
|
||||
// ToBool convert string to boolean.
|
||||
// Play: https://go.dev/play/p/ARht2WnGdIN
|
||||
func ToBool(s string) (bool, error) {
|
||||
return strconv.ParseBool(s)
|
||||
}
|
||||
|
||||
// ToBytes convert interface to bytes
|
||||
// ToBytes convert value to byte slice.
|
||||
// Play: https://go.dev/play/p/fAMXYFDvOvr
|
||||
func ToBytes(value any) ([]byte, error) {
|
||||
v := reflect.ValueOf(value)
|
||||
|
||||
@@ -62,9 +72,10 @@ func ToBytes(value any) ([]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// ToChar convert string to char slice
|
||||
// ToChar convert string to char slice.
|
||||
// Play: https://go.dev/play/p/JJ1SvbFkVdM
|
||||
func ToChar(s string) []string {
|
||||
c := make([]string, 0)
|
||||
c := make([]string, 0, len(s))
|
||||
if len(s) == 0 {
|
||||
c = append(c, "")
|
||||
}
|
||||
@@ -74,133 +85,218 @@ func ToChar(s string) []string {
|
||||
return c
|
||||
}
|
||||
|
||||
// ToChannel convert a slice of elements to a read-only channel.
|
||||
// Play: https://go.dev/play/p/hOx_oYZbAnL
|
||||
func ToChannel[T any](array []T) <-chan T {
|
||||
ch := make(chan T)
|
||||
|
||||
go func() {
|
||||
for _, item := range array {
|
||||
ch <- item
|
||||
}
|
||||
close(ch)
|
||||
}()
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
// ToString convert value to string
|
||||
// for number, string, []byte, will convert to string
|
||||
// for other type (slice, map, array, struct) will call json.Marshal.
|
||||
// Play: https://go.dev/play/p/nF1zOOslpQq
|
||||
func ToString(value any) string {
|
||||
res := ""
|
||||
if value == nil {
|
||||
return res
|
||||
return ""
|
||||
}
|
||||
rv := reflect.ValueOf(value)
|
||||
if rv.Kind() == reflect.Ptr {
|
||||
if rv.IsNil() {
|
||||
return ""
|
||||
}
|
||||
return ToString(rv.Elem().Interface())
|
||||
}
|
||||
|
||||
v := reflect.ValueOf(value)
|
||||
|
||||
switch value.(type) {
|
||||
case float32, float64:
|
||||
res = strconv.FormatFloat(v.Float(), 'f', -1, 64)
|
||||
return res
|
||||
case int, int8, int16, int32, int64:
|
||||
res = strconv.FormatInt(v.Int(), 10)
|
||||
return res
|
||||
case uint, uint8, uint16, uint32, uint64:
|
||||
res = strconv.FormatUint(v.Uint(), 10)
|
||||
return res
|
||||
switch val := value.(type) {
|
||||
case float32:
|
||||
return strconv.FormatFloat(float64(val), 'f', -1, 32)
|
||||
case float64:
|
||||
return strconv.FormatFloat(val, 'f', -1, 64)
|
||||
case int:
|
||||
return strconv.FormatInt(int64(val), 10)
|
||||
case int8:
|
||||
return strconv.FormatInt(int64(val), 10)
|
||||
case int16:
|
||||
return strconv.FormatInt(int64(val), 10)
|
||||
case int32:
|
||||
return strconv.FormatInt(int64(val), 10)
|
||||
case int64:
|
||||
return strconv.FormatInt(val, 10)
|
||||
case uint:
|
||||
return strconv.FormatUint(uint64(val), 10)
|
||||
case uint8:
|
||||
return strconv.FormatUint(uint64(val), 10)
|
||||
case uint16:
|
||||
return strconv.FormatUint(uint64(val), 10)
|
||||
case uint32:
|
||||
return strconv.FormatUint(uint64(val), 10)
|
||||
case uint64:
|
||||
return strconv.FormatUint(val, 10)
|
||||
case string:
|
||||
res = v.String()
|
||||
return res
|
||||
return val
|
||||
case []byte:
|
||||
res = string(v.Bytes())
|
||||
return res
|
||||
return string(val)
|
||||
default:
|
||||
newValue, _ := json.Marshal(value)
|
||||
res = string(newValue)
|
||||
return res
|
||||
b, err := json.Marshal(val)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
|
||||
// ToJson convert value to a valid json string
|
||||
// ToJson convert value to a json string.
|
||||
// Play: https://go.dev/play/p/2rLIkMmXWvR
|
||||
func ToJson(value any) (string, error) {
|
||||
res, err := json.Marshal(value)
|
||||
result, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(res), nil
|
||||
return string(result), nil
|
||||
}
|
||||
|
||||
// ToFloat convert value to a float64, if input is not a float return 0.0 and error
|
||||
// ToFloat convert value to float64, if input is not a float return 0.0 and error.
|
||||
// Play: https://go.dev/play/p/4YTmPCibqHJ
|
||||
func ToFloat(value any) (float64, error) {
|
||||
v := reflect.ValueOf(value)
|
||||
|
||||
res := 0.0
|
||||
result := 0.0
|
||||
err := fmt.Errorf("ToInt: unvalid interface type %T", value)
|
||||
switch value.(type) {
|
||||
case int, int8, int16, int32, int64:
|
||||
res = float64(v.Int())
|
||||
return res, nil
|
||||
result = float64(v.Int())
|
||||
return result, nil
|
||||
case uint, uint8, uint16, uint32, uint64:
|
||||
res = float64(v.Uint())
|
||||
return res, nil
|
||||
result = float64(v.Uint())
|
||||
return result, nil
|
||||
case float32, float64:
|
||||
res = v.Float()
|
||||
return res, nil
|
||||
result = v.Float()
|
||||
return result, nil
|
||||
case string:
|
||||
res, err = strconv.ParseFloat(v.String(), 64)
|
||||
result, err = strconv.ParseFloat(v.String(), 64)
|
||||
if err != nil {
|
||||
res = 0.0
|
||||
result = 0.0
|
||||
}
|
||||
return res, err
|
||||
return result, err
|
||||
default:
|
||||
return res, err
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
|
||||
// ToInt convert value to a int64, if input is not a numeric format return 0 and error
|
||||
// ToInt convert value to int64 value, if input is not numerical, return 0 and error.
|
||||
// Play: https://go.dev/play/p/9_h9vIt-QZ_b
|
||||
func ToInt(value any) (int64, error) {
|
||||
v := reflect.ValueOf(value)
|
||||
|
||||
var res int64
|
||||
err := fmt.Errorf("ToInt: invalid interface type %T", value)
|
||||
var result int64
|
||||
err := fmt.Errorf("ToInt: invalid value type %T", value)
|
||||
switch value.(type) {
|
||||
case int, int8, int16, int32, int64:
|
||||
res = v.Int()
|
||||
return res, nil
|
||||
result = v.Int()
|
||||
return result, nil
|
||||
case uint, uint8, uint16, uint32, uint64:
|
||||
res = int64(v.Uint())
|
||||
return res, nil
|
||||
result = int64(v.Uint())
|
||||
return result, nil
|
||||
case float32, float64:
|
||||
res = int64(v.Float())
|
||||
return res, nil
|
||||
result = int64(v.Float())
|
||||
return result, nil
|
||||
case string:
|
||||
res, err = strconv.ParseInt(v.String(), 0, 64)
|
||||
result, err = strconv.ParseInt(v.String(), 0, 64)
|
||||
if err != nil {
|
||||
res = 0
|
||||
result = 0
|
||||
}
|
||||
return res, err
|
||||
return result, err
|
||||
default:
|
||||
return res, err
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
|
||||
// ToPointer returns a pointer to passed value.
|
||||
// Play: https://go.dev/play/p/ASf_etHNlw1
|
||||
func ToPointer[T any](value T) *T {
|
||||
return &value
|
||||
}
|
||||
|
||||
// ToPointers convert a slice of values to a slice of pointers.
|
||||
// Play: todo
|
||||
func ToPointers[T any](values []T) []*T {
|
||||
result := make([]*T, len(values))
|
||||
for i := range values {
|
||||
result[i] = &values[i]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// FromPointer returns the value pointed to by the pointer.
|
||||
// Play: todo
|
||||
func FromPointer[T any](ptr *T) T {
|
||||
if ptr == nil {
|
||||
var zeroValue T
|
||||
return zeroValue
|
||||
}
|
||||
|
||||
return *ptr
|
||||
}
|
||||
|
||||
// FromPointers convert a slice of pointers to a slice of values.
|
||||
// Play: todo
|
||||
func FromPointers[T any](pointers []*T) []T {
|
||||
result := make([]T, len(pointers))
|
||||
for i, ptr := range pointers {
|
||||
if ptr == nil {
|
||||
var zeroValue T
|
||||
result[i] = zeroValue
|
||||
} else {
|
||||
result[i] = *ptr
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ToMap convert a slice of structs to a map based on iteratee function.
|
||||
// Play: https://go.dev/play/p/tVFy7E-t24l
|
||||
func ToMap[T any, K comparable, V any](array []T, iteratee func(T) (K, V)) map[K]V {
|
||||
result := make(map[K]V, len(array))
|
||||
for _, item := range array {
|
||||
k, v := iteratee(item)
|
||||
result[k] = v
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// StructToMap convert struct to map, only convert exported struct field
|
||||
// map key is specified same as struct field tag `json` value
|
||||
// map key is specified same as struct field tag `json` value.
|
||||
// Play: https://go.dev/play/p/KYGYJqNUBOI
|
||||
func StructToMap(value any) (map[string]any, error) {
|
||||
v := reflect.ValueOf(value)
|
||||
t := reflect.TypeOf(value)
|
||||
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
if t.Kind() != reflect.Struct {
|
||||
return nil, fmt.Errorf("data type %T not support, shuld be struct or pointer to struct", value)
|
||||
}
|
||||
|
||||
res := make(map[string]any)
|
||||
|
||||
fieldNum := t.NumField()
|
||||
pattern := `^[A-Z]`
|
||||
regex := regexp.MustCompile(pattern)
|
||||
for i := 0; i < fieldNum; i++ {
|
||||
name := t.Field(i).Name
|
||||
tag := t.Field(i).Tag.Get("json")
|
||||
if regex.MatchString(name) && tag != "" {
|
||||
//res[name] = v.Field(i).Interface()
|
||||
res[tag] = v.Field(i).Interface()
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
return structs.ToMap(value)
|
||||
}
|
||||
|
||||
// ColorHexToRGB convert hex color to rgb color
|
||||
// MapToSlice convert map to slice based on iteratee function.
|
||||
// Play: https://go.dev/play/p/dmX4Ix5V6Wl
|
||||
func MapToSlice[T any, K comparable, V any](aMap map[K]V, iteratee func(K, V) T) []T {
|
||||
result := make([]T, 0, len(aMap))
|
||||
|
||||
for k, v := range aMap {
|
||||
result = append(result, iteratee(k, v))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ColorHexToRGB convert hex color to rgb color.
|
||||
// Play: https://go.dev/play/p/o7_ft-JCJBV
|
||||
func ColorHexToRGB(colorHex string) (red, green, blue int) {
|
||||
colorHex = strings.TrimPrefix(colorHex, "#")
|
||||
color64, err := strconv.ParseInt(colorHex, 16, 32)
|
||||
@@ -211,7 +307,8 @@ func ColorHexToRGB(colorHex string) (red, green, blue int) {
|
||||
return color >> 16, (color & 0x00FF00) >> 8, color & 0x0000FF
|
||||
}
|
||||
|
||||
// ColorRGBToHex convert rgb color to hex color
|
||||
// ColorRGBToHex convert rgb color to hex color.
|
||||
// Play: https://go.dev/play/p/nzKS2Ro87J1
|
||||
func ColorRGBToHex(red, green, blue int) string {
|
||||
r := strconv.FormatInt(int64(red), 16)
|
||||
g := strconv.FormatInt(int64(green), 16)
|
||||
@@ -229,3 +326,233 @@ func ColorRGBToHex(red, green, blue int) string {
|
||||
|
||||
return "#" + r + g + b
|
||||
}
|
||||
|
||||
// EncodeByte encode data to byte slice.
|
||||
// Play: https://go.dev/play/p/DVmM1G5JfuP
|
||||
func EncodeByte(data any) ([]byte, error) {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
encoder := gob.NewEncoder(buffer)
|
||||
err := encoder.Encode(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
// DecodeByte decode byte slice data to target object.
|
||||
// Play: https://go.dev/play/p/zI6xsmuQRbn
|
||||
func DecodeByte(data []byte, target any) error {
|
||||
buffer := bytes.NewBuffer(data)
|
||||
decoder := gob.NewDecoder(buffer)
|
||||
return decoder.Decode(target)
|
||||
}
|
||||
|
||||
// DeepClone creates a deep copy of passed item.
|
||||
// can't clone unexported field of struct
|
||||
// Play: https://go.dev/play/p/j4DP5dquxnk
|
||||
func DeepClone[T any](src T) T {
|
||||
c := cloner{
|
||||
ptrs: map[reflect.Type]map[uintptr]reflect.Value{},
|
||||
}
|
||||
result := c.clone(reflect.ValueOf(src))
|
||||
if result.Kind() == reflect.Invalid {
|
||||
var zeroValue T
|
||||
return zeroValue
|
||||
}
|
||||
|
||||
return result.Interface().(T)
|
||||
}
|
||||
|
||||
// CopyProperties copies each field from the source into the destination. It recursively copies struct pointers and interfaces that contain struct pointers.
|
||||
// use json.Marshal/Unmarshal, so json tag should be set for fields of dst and src struct.
|
||||
// Play: https://go.dev/play/p/oZujoB5Sgg5
|
||||
func CopyProperties[T, U any](dst T, src U) error {
|
||||
dstType, srcType := reflect.TypeOf(dst), reflect.TypeOf(src)
|
||||
|
||||
if dstType.Kind() != reflect.Ptr || dstType.Elem().Kind() != reflect.Struct {
|
||||
return errors.New("CopyProperties: parameter dst should be struct pointer")
|
||||
}
|
||||
|
||||
if srcType.Kind() == reflect.Ptr {
|
||||
srcType = srcType.Elem()
|
||||
}
|
||||
if srcType.Kind() != reflect.Struct {
|
||||
return errors.New("CopyProperties: parameter src should be a struct or struct pointer")
|
||||
}
|
||||
|
||||
bytes, err := json.Marshal(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CopyProperties: unable to marshal src: %s", err)
|
||||
}
|
||||
err = json.Unmarshal(bytes, dst)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CopyProperties: unable to unmarshal into dst: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToInterface converts reflect value to its interface type.
|
||||
// Play: https://go.dev/play/p/syqw0-WG7Xd
|
||||
func ToInterface(v reflect.Value) (value interface{}, ok bool) {
|
||||
if v.IsValid() && v.CanInterface() {
|
||||
return v.Interface(), true
|
||||
}
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
return v.Bool(), true
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int(), true
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return v.Uint(), true
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float(), true
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
return v.Complex(), true
|
||||
case reflect.String:
|
||||
return v.String(), true
|
||||
case reflect.Ptr:
|
||||
return ToInterface(v.Elem())
|
||||
case reflect.Interface:
|
||||
return ToInterface(v.Elem())
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
// Utf8ToGbk convert utf8 encoding data to GBK encoding data.
|
||||
// Play: https://go.dev/play/p/9FlIaFLArIL
|
||||
func Utf8ToGbk(bs []byte) ([]byte, error) {
|
||||
r := transform.NewReader(bytes.NewReader(bs), simplifiedchinese.GBK.NewEncoder())
|
||||
b, err := io.ReadAll(r)
|
||||
return b, err
|
||||
}
|
||||
|
||||
// GbkToUtf8 convert GBK encoding data to utf8 encoding data.
|
||||
// Play: https://go.dev/play/p/OphmHCN_9u8
|
||||
func GbkToUtf8(bs []byte) ([]byte, error) {
|
||||
r := transform.NewReader(bytes.NewReader(bs), simplifiedchinese.GBK.NewDecoder())
|
||||
b, err := io.ReadAll(r)
|
||||
return b, err
|
||||
}
|
||||
|
||||
// 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 ""
|
||||
}
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
return base64.StdEncoding.EncodeToString(v)
|
||||
case string:
|
||||
return base64.StdEncoding.EncodeToString([]byte(v))
|
||||
case error:
|
||||
return base64.StdEncoding.EncodeToString([]byte(v.Error()))
|
||||
default:
|
||||
marshal, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(marshal)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 ""
|
||||
}
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
return base64.URLEncoding.EncodeToString(v)
|
||||
case string:
|
||||
return base64.URLEncoding.EncodeToString([]byte(v))
|
||||
case error:
|
||||
return base64.URLEncoding.EncodeToString([]byte(v.Error()))
|
||||
default:
|
||||
marshal, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return base64.URLEncoding.EncodeToString(marshal)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 ""
|
||||
}
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
return base64.RawStdEncoding.EncodeToString(value.([]byte))
|
||||
case string:
|
||||
return base64.RawStdEncoding.EncodeToString([]byte(value.(string)))
|
||||
case error:
|
||||
return base64.RawStdEncoding.EncodeToString([]byte(value.(error).Error()))
|
||||
default:
|
||||
marshal, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return base64.RawStdEncoding.EncodeToString(marshal)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 ""
|
||||
}
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
return base64.RawURLEncoding.EncodeToString(value.([]byte))
|
||||
case string:
|
||||
return base64.RawURLEncoding.EncodeToString([]byte(value.(string)))
|
||||
case error:
|
||||
return base64.RawURLEncoding.EncodeToString([]byte(value.(error).Error()))
|
||||
default:
|
||||
marshal, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return base64.RawURLEncoding.EncodeToString(marshal)
|
||||
}
|
||||
}
|
||||
|
||||
// ToBigInt converts an integer of any supported type (int, int64, uint64, etc.) to *big.Int
|
||||
// Play: https://go.dev/play/p/X3itkCxwB_x
|
||||
func ToBigInt[T any](v T) (*big.Int, error) {
|
||||
result := new(big.Int)
|
||||
|
||||
switch v := any(v).(type) {
|
||||
case int:
|
||||
result.SetInt64(int64(v)) // Convert to int64 for big.Int
|
||||
case int8:
|
||||
result.SetInt64(int64(v))
|
||||
case int16:
|
||||
result.SetInt64(int64(v))
|
||||
case int32:
|
||||
result.SetInt64(int64(v))
|
||||
case int64:
|
||||
result.SetInt64(v)
|
||||
case uint:
|
||||
result.SetUint64(uint64(v)) // Convert to uint64 for big.Int
|
||||
case uint8:
|
||||
result.SetUint64(uint64(v))
|
||||
case uint16:
|
||||
result.SetUint64(uint64(v))
|
||||
case uint32:
|
||||
result.SetUint64(uint64(v))
|
||||
case uint64:
|
||||
result.SetUint64(v)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported type: %T", v)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
582
convertor/convertor_example_test.go
Normal file
582
convertor/convertor_example_test.go
Normal file
@@ -0,0 +1,582 @@
|
||||
package convertor
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/duke-git/lancet/v2/validator"
|
||||
)
|
||||
|
||||
func ExampleToBool() {
|
||||
cases := []string{"1", "true", "True", "false", "False", "0", "123", "0.0", "abc"}
|
||||
|
||||
for i := 0; i < len(cases); i++ {
|
||||
result, _ := ToBool(cases[i])
|
||||
fmt.Println(result)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// false
|
||||
// false
|
||||
// false
|
||||
// false
|
||||
// false
|
||||
// false
|
||||
}
|
||||
|
||||
func ExampleToBytes() {
|
||||
result1, _ := ToBytes(1)
|
||||
result2, _ := ToBytes("abc")
|
||||
result3, _ := ToBytes(true)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
|
||||
// Output:
|
||||
// [0 0 0 0 0 0 0 1]
|
||||
// [97 98 99]
|
||||
// [116 114 117 101]
|
||||
}
|
||||
|
||||
func ExampleToChar() {
|
||||
result1 := ToChar("")
|
||||
result2 := ToChar("abc")
|
||||
result3 := ToChar("1 2#3")
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
|
||||
// Output:
|
||||
// []
|
||||
// [a b c]
|
||||
// [1 2 # 3]
|
||||
}
|
||||
|
||||
func ExampleToChannel() {
|
||||
ch := ToChannel([]int{1, 2, 3})
|
||||
result1 := <-ch
|
||||
result2 := <-ch
|
||||
result3 := <-ch
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
// 2
|
||||
// 3
|
||||
}
|
||||
|
||||
func ExampleToString() {
|
||||
result1 := ToString("")
|
||||
result2 := ToString(nil)
|
||||
result3 := ToString(0)
|
||||
result4 := ToString(1.23)
|
||||
result5 := ToString(true)
|
||||
result6 := ToString(false)
|
||||
result7 := ToString([]int{1, 2, 3})
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
fmt.Println(result5)
|
||||
fmt.Println(result6)
|
||||
fmt.Println(result7)
|
||||
|
||||
// Output:
|
||||
//
|
||||
//
|
||||
// 0
|
||||
// 1.23
|
||||
// true
|
||||
// false
|
||||
// [1,2,3]
|
||||
}
|
||||
|
||||
func ExampleToJson() {
|
||||
|
||||
aMap := map[string]int{"a": 1, "b": 2, "c": 3}
|
||||
result1, err := ToJson(aMap)
|
||||
if err != nil {
|
||||
fmt.Printf("%v", err)
|
||||
}
|
||||
fmt.Println(result1)
|
||||
|
||||
// Output:
|
||||
// {"a":1,"b":2,"c":3}
|
||||
}
|
||||
|
||||
func ExampleToFloat() {
|
||||
result1, _ := ToFloat("")
|
||||
result2, _ := ToFloat("abc")
|
||||
result3, _ := ToFloat("-1")
|
||||
result4, _ := ToFloat("-.11")
|
||||
result5, _ := ToFloat("1.23e3")
|
||||
result6, _ := ToFloat(true)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
fmt.Println(result5)
|
||||
fmt.Println(result6)
|
||||
|
||||
// Output:
|
||||
// 0
|
||||
// 0
|
||||
// -1
|
||||
// -0.11
|
||||
// 1230
|
||||
// 0
|
||||
}
|
||||
|
||||
func ExampleToInt() {
|
||||
result1, _ := ToInt("123")
|
||||
result2, _ := ToInt("-123")
|
||||
result3, _ := ToInt(float64(12.3))
|
||||
result4, _ := ToInt("abc")
|
||||
result5, _ := ToInt(true)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
fmt.Println(result5)
|
||||
|
||||
// Output:
|
||||
// 123
|
||||
// -123
|
||||
// 12
|
||||
// 0
|
||||
// 0
|
||||
}
|
||||
|
||||
func ExampleToPointer() {
|
||||
result := ToPointer(123)
|
||||
fmt.Println(*result)
|
||||
|
||||
// Output:
|
||||
// 123
|
||||
}
|
||||
|
||||
func ExampleToMap() {
|
||||
type Message struct {
|
||||
name string
|
||||
code int
|
||||
}
|
||||
messages := []Message{
|
||||
{name: "Hello", code: 100},
|
||||
{name: "Hi", code: 101},
|
||||
}
|
||||
result := ToMap(messages, func(msg Message) (int, string) {
|
||||
return msg.code, msg.name
|
||||
})
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// map[100:Hello 101:Hi]
|
||||
}
|
||||
|
||||
func ExampleStructToMap() {
|
||||
type People struct {
|
||||
Name string `json:"name"`
|
||||
age int
|
||||
}
|
||||
p := People{
|
||||
"test",
|
||||
100,
|
||||
}
|
||||
pm, _ := StructToMap(p)
|
||||
|
||||
fmt.Println(pm)
|
||||
|
||||
// Output:
|
||||
// map[name:test]
|
||||
}
|
||||
|
||||
func ExampleMapToSlice() {
|
||||
aMap := map[string]int{"a": 1, "b": 2, "c": 3}
|
||||
result := MapToSlice(aMap, func(key string, value int) string {
|
||||
return key + ":" + strconv.Itoa(value)
|
||||
})
|
||||
|
||||
fmt.Println(result) //[]string{"a:1", "c:3", "b:2"} (random order)
|
||||
}
|
||||
|
||||
func ExampleColorHexToRGB() {
|
||||
colorHex := "#003366"
|
||||
r, g, b := ColorHexToRGB(colorHex)
|
||||
|
||||
fmt.Println(r, g, b)
|
||||
|
||||
// Output:
|
||||
// 0 51 102
|
||||
}
|
||||
|
||||
func ExampleColorRGBToHex() {
|
||||
r := 0
|
||||
g := 51
|
||||
b := 102
|
||||
colorHex := ColorRGBToHex(r, g, b)
|
||||
|
||||
fmt.Println(colorHex)
|
||||
|
||||
// Output:
|
||||
// #003366
|
||||
}
|
||||
|
||||
func ExampleEncodeByte() {
|
||||
byteData, _ := EncodeByte("abc")
|
||||
fmt.Println(byteData)
|
||||
|
||||
// Output:
|
||||
// [6 12 0 3 97 98 99]
|
||||
}
|
||||
|
||||
func ExampleDecodeByte() {
|
||||
var obj string
|
||||
byteData := []byte{6, 12, 0, 3, 97, 98, 99}
|
||||
err := DecodeByte(byteData, &obj)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(obj)
|
||||
|
||||
// Output:
|
||||
// abc
|
||||
}
|
||||
|
||||
func ExampleDeepClone() {
|
||||
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 := 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
|
||||
}
|
||||
|
||||
func ExampleCopyProperties() {
|
||||
type Disk struct {
|
||||
Name string `json:"name"`
|
||||
Total string `json:"total"`
|
||||
Used string `json:"used"`
|
||||
Percent float64 `json:"percent"`
|
||||
}
|
||||
|
||||
type DiskVO struct {
|
||||
Name string `json:"name"`
|
||||
Total string `json:"total"`
|
||||
Used string `json:"used"`
|
||||
Percent float64 `json:"percent"`
|
||||
}
|
||||
|
||||
type Indicator struct {
|
||||
Id string `json:"id"`
|
||||
Ip string `json:"ip"`
|
||||
UpTime string `json:"upTime"`
|
||||
LoadAvg string `json:"loadAvg"`
|
||||
Cpu int `json:"cpu"`
|
||||
Disk []Disk `json:"disk"`
|
||||
Stop chan bool `json:"-"`
|
||||
}
|
||||
|
||||
type IndicatorVO struct {
|
||||
Id string `json:"id"`
|
||||
Ip string `json:"ip"`
|
||||
UpTime string `json:"upTime"`
|
||||
LoadAvg string `json:"loadAvg"`
|
||||
Cpu int64 `json:"cpu"`
|
||||
Disk []DiskVO `json:"disk"`
|
||||
}
|
||||
|
||||
indicator := &Indicator{Id: "001", Ip: "127.0.0.1", Cpu: 1, Disk: []Disk{
|
||||
{Name: "disk-001", Total: "100", Used: "1", Percent: 10},
|
||||
{Name: "disk-002", Total: "200", Used: "1", Percent: 20},
|
||||
{Name: "disk-003", Total: "300", Used: "1", Percent: 30},
|
||||
}}
|
||||
|
||||
indicatorVO := IndicatorVO{}
|
||||
|
||||
CopyProperties(&indicatorVO, indicator)
|
||||
|
||||
fmt.Println(indicatorVO.Id)
|
||||
fmt.Println(indicatorVO.Ip)
|
||||
fmt.Println(len(indicatorVO.Disk))
|
||||
|
||||
// Output:
|
||||
// 001
|
||||
// 127.0.0.1
|
||||
// 3
|
||||
}
|
||||
|
||||
func ExampleToInterface() {
|
||||
val := reflect.ValueOf("abc")
|
||||
iVal, ok := ToInterface(val)
|
||||
|
||||
fmt.Printf("%T\n", iVal)
|
||||
fmt.Printf("%v\n", iVal)
|
||||
fmt.Println(ok)
|
||||
|
||||
// Output:
|
||||
// string
|
||||
// abc
|
||||
// true
|
||||
}
|
||||
|
||||
func ExampleUtf8ToGbk() {
|
||||
utf8Data := []byte("hello")
|
||||
gbkData, _ := Utf8ToGbk(utf8Data)
|
||||
|
||||
fmt.Println(utf8.Valid(utf8Data))
|
||||
fmt.Println(validator.IsGBK(gbkData))
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
}
|
||||
|
||||
func ExampleGbkToUtf8() {
|
||||
gbkData, _ := Utf8ToGbk([]byte("hello"))
|
||||
utf8Data, _ := GbkToUtf8(gbkData)
|
||||
|
||||
fmt.Println(utf8.Valid(utf8Data))
|
||||
fmt.Println(string(utf8Data))
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleToStdBase64() {
|
||||
// if you want to see the result, please use 'base64.StdEncoding.DecodeString()' to decode the result
|
||||
|
||||
afterEncode := ToStdBase64(nil)
|
||||
fmt.Println(afterEncode)
|
||||
|
||||
stringVal := "hello"
|
||||
afterEncode = ToStdBase64(stringVal)
|
||||
fmt.Println(afterEncode)
|
||||
|
||||
byteSliceVal := []byte("hello")
|
||||
afterEncode = ToStdBase64(byteSliceVal)
|
||||
fmt.Println(afterEncode)
|
||||
|
||||
intVal := 123
|
||||
afterEncode = ToStdBase64(intVal)
|
||||
fmt.Println(afterEncode)
|
||||
|
||||
mapVal := map[string]any{"a": "hi", "b": 2, "c": struct {
|
||||
A string
|
||||
B int
|
||||
}{"hello", 3}}
|
||||
afterEncode = ToStdBase64(mapVal)
|
||||
fmt.Println(afterEncode)
|
||||
|
||||
floatVal := 123.456
|
||||
afterEncode = ToStdBase64(floatVal)
|
||||
fmt.Println(afterEncode)
|
||||
|
||||
boolVal := true
|
||||
afterEncode = ToStdBase64(boolVal)
|
||||
fmt.Println(afterEncode)
|
||||
|
||||
errVal := errors.New("err")
|
||||
afterEncode = ToStdBase64(errVal)
|
||||
fmt.Println(afterEncode)
|
||||
|
||||
// Output:
|
||||
//
|
||||
// aGVsbG8=
|
||||
// aGVsbG8=
|
||||
// MTIz
|
||||
// eyJhIjoiaGkiLCJiIjoyLCJjIjp7IkEiOiJoZWxsbyIsIkIiOjN9fQ==
|
||||
// MTIzLjQ1Ng==
|
||||
// dHJ1ZQ==
|
||||
// ZXJy
|
||||
}
|
||||
|
||||
func ExampleToUrlBase64() {
|
||||
// if you want to see the result, please use 'base64.URLEncoding.DecodeString()' to decode the result
|
||||
|
||||
stringVal := "hello"
|
||||
afterEncode := ToUrlBase64(stringVal)
|
||||
fmt.Println(afterEncode)
|
||||
|
||||
byteSliceVal := []byte("hello")
|
||||
afterEncode = ToUrlBase64(byteSliceVal)
|
||||
fmt.Println(afterEncode)
|
||||
|
||||
intVal := 123
|
||||
afterEncode = ToUrlBase64(intVal)
|
||||
fmt.Println(afterEncode)
|
||||
|
||||
mapVal := map[string]any{"a": "hi", "b": 2, "c": struct {
|
||||
A string
|
||||
B int
|
||||
}{"hello", 3}}
|
||||
afterEncode = ToUrlBase64(mapVal)
|
||||
fmt.Println(afterEncode)
|
||||
|
||||
floatVal := 123.456
|
||||
afterEncode = ToUrlBase64(floatVal)
|
||||
fmt.Println(afterEncode)
|
||||
|
||||
boolVal := true
|
||||
afterEncode = ToUrlBase64(boolVal)
|
||||
fmt.Println(afterEncode)
|
||||
|
||||
errVal := errors.New("err")
|
||||
afterEncode = ToUrlBase64(errVal)
|
||||
fmt.Println(afterEncode)
|
||||
|
||||
// Output:
|
||||
// aGVsbG8=
|
||||
// aGVsbG8=
|
||||
// MTIz
|
||||
// eyJhIjoiaGkiLCJiIjoyLCJjIjp7IkEiOiJoZWxsbyIsIkIiOjN9fQ==
|
||||
// MTIzLjQ1Ng==
|
||||
// dHJ1ZQ==
|
||||
// ZXJy
|
||||
}
|
||||
|
||||
func ExampleToRawStdBase64() {
|
||||
// if you want to see the result, please use 'base64.RawStdEncoding.DecodeString()' to decode the result
|
||||
stringVal := "hello"
|
||||
afterEncode := ToRawStdBase64(stringVal)
|
||||
fmt.Println(afterEncode)
|
||||
|
||||
byteSliceVal := []byte("hello")
|
||||
afterEncode = ToRawStdBase64(byteSliceVal)
|
||||
fmt.Println(afterEncode)
|
||||
|
||||
intVal := 123
|
||||
afterEncode = ToRawStdBase64(intVal)
|
||||
fmt.Println(afterEncode)
|
||||
|
||||
mapVal := map[string]any{"a": "hi", "b": 2, "c": struct {
|
||||
A string
|
||||
B int
|
||||
}{"hello", 3}}
|
||||
afterEncode = ToRawStdBase64(mapVal)
|
||||
fmt.Println(afterEncode)
|
||||
|
||||
floatVal := 123.456
|
||||
afterEncode = ToRawStdBase64(floatVal)
|
||||
fmt.Println(afterEncode)
|
||||
|
||||
boolVal := true
|
||||
afterEncode = ToRawStdBase64(boolVal)
|
||||
fmt.Println(afterEncode)
|
||||
|
||||
errVal := errors.New("err")
|
||||
afterEncode = ToRawStdBase64(errVal)
|
||||
fmt.Println(afterEncode)
|
||||
|
||||
// Output:
|
||||
// aGVsbG8
|
||||
// aGVsbG8
|
||||
// MTIz
|
||||
// eyJhIjoiaGkiLCJiIjoyLCJjIjp7IkEiOiJoZWxsbyIsIkIiOjN9fQ
|
||||
// MTIzLjQ1Ng
|
||||
// dHJ1ZQ
|
||||
// ZXJy
|
||||
}
|
||||
|
||||
func ExampleToRawUrlBase64() {
|
||||
// if you want to see the result, please use 'base64.RawURLEncoding.DecodeString()' to decode the result
|
||||
|
||||
stringVal := "hello"
|
||||
afterEncode := ToRawUrlBase64(stringVal)
|
||||
fmt.Println(afterEncode)
|
||||
|
||||
byteSliceVal := []byte("hello")
|
||||
afterEncode = ToRawUrlBase64(byteSliceVal)
|
||||
fmt.Println(afterEncode)
|
||||
|
||||
intVal := 123
|
||||
afterEncode = ToRawUrlBase64(intVal)
|
||||
fmt.Println(afterEncode)
|
||||
|
||||
mapVal := map[string]any{"a": "hi", "b": 2, "c": struct {
|
||||
A string
|
||||
B int
|
||||
}{"hello", 3}}
|
||||
afterEncode = ToRawUrlBase64(mapVal)
|
||||
fmt.Println(afterEncode)
|
||||
|
||||
floatVal := 123.456
|
||||
afterEncode = ToRawUrlBase64(floatVal)
|
||||
fmt.Println(afterEncode)
|
||||
|
||||
boolVal := true
|
||||
afterEncode = ToRawUrlBase64(boolVal)
|
||||
fmt.Println(afterEncode)
|
||||
|
||||
errVal := errors.New("err")
|
||||
afterEncode = ToRawUrlBase64(errVal)
|
||||
fmt.Println(afterEncode)
|
||||
|
||||
// Output:
|
||||
// aGVsbG8
|
||||
// aGVsbG8
|
||||
// MTIz
|
||||
// eyJhIjoiaGkiLCJiIjoyLCJjIjp7IkEiOiJoZWxsbyIsIkIiOjN9fQ
|
||||
// MTIzLjQ1Ng
|
||||
// dHJ1ZQ
|
||||
// ZXJy
|
||||
}
|
||||
|
||||
func ExampleToBigInt() {
|
||||
n := 9876543210
|
||||
bigInt, _ := ToBigInt(n)
|
||||
|
||||
fmt.Println(bigInt)
|
||||
// Output:
|
||||
// 9876543210
|
||||
}
|
||||
216
convertor/convertor_internal.go
Normal file
216
convertor/convertor_internal.go
Normal file
@@ -0,0 +1,216 @@
|
||||
// Copyright 2023 dudaodong@gmail.com. All rights reserved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
// Package convertor implements some functions to convert data.
|
||||
package convertor
|
||||
|
||||
import "reflect"
|
||||
|
||||
type cloner struct {
|
||||
ptrs map[reflect.Type]map[uintptr]reflect.Value
|
||||
}
|
||||
|
||||
// clone return a duplicate of passed item.
|
||||
func (c *cloner) clone(v reflect.Value) reflect.Value {
|
||||
switch v.Kind() {
|
||||
case reflect.Invalid:
|
||||
return reflect.ValueOf(nil)
|
||||
|
||||
// bool
|
||||
case reflect.Bool:
|
||||
return reflect.ValueOf(v.Bool())
|
||||
|
||||
//int
|
||||
case reflect.Int:
|
||||
return reflect.ValueOf(int(v.Int()))
|
||||
case reflect.Int8:
|
||||
return reflect.ValueOf(int8(v.Int()))
|
||||
case reflect.Int16:
|
||||
return reflect.ValueOf(int16(v.Int()))
|
||||
case reflect.Int32:
|
||||
return reflect.ValueOf(int32(v.Int()))
|
||||
case reflect.Int64:
|
||||
return reflect.ValueOf(v.Int())
|
||||
|
||||
// uint
|
||||
case reflect.Uint:
|
||||
return reflect.ValueOf(uint(v.Uint()))
|
||||
case reflect.Uint8:
|
||||
return reflect.ValueOf(uint8(v.Uint()))
|
||||
case reflect.Uint16:
|
||||
return reflect.ValueOf(uint16(v.Uint()))
|
||||
case reflect.Uint32:
|
||||
return reflect.ValueOf(uint32(v.Uint()))
|
||||
case reflect.Uint64:
|
||||
return reflect.ValueOf(v.Uint())
|
||||
|
||||
// float
|
||||
case reflect.Float32:
|
||||
return reflect.ValueOf(float32(v.Float()))
|
||||
case reflect.Float64:
|
||||
return reflect.ValueOf(v.Float())
|
||||
|
||||
// complex
|
||||
case reflect.Complex64:
|
||||
return reflect.ValueOf(complex64(v.Complex()))
|
||||
case reflect.Complex128:
|
||||
return reflect.ValueOf(v.Complex())
|
||||
|
||||
// string
|
||||
case reflect.String:
|
||||
return reflect.ValueOf(v.String())
|
||||
|
||||
// array
|
||||
case reflect.Array, reflect.Slice:
|
||||
return c.cloneArray(v)
|
||||
|
||||
// map
|
||||
case reflect.Map:
|
||||
return c.cloneMap(v)
|
||||
|
||||
// Ptr
|
||||
case reflect.Ptr:
|
||||
return c.clonePtr(v)
|
||||
|
||||
// struct
|
||||
case reflect.Struct:
|
||||
return c.cloneStruct(v)
|
||||
|
||||
// func
|
||||
case reflect.Func:
|
||||
return v
|
||||
|
||||
// interface
|
||||
case reflect.Interface:
|
||||
return c.clone(v.Elem())
|
||||
|
||||
}
|
||||
|
||||
return reflect.Zero(v.Type())
|
||||
}
|
||||
|
||||
func (c *cloner) cloneArray(v reflect.Value) reflect.Value {
|
||||
if v.IsNil() {
|
||||
return reflect.Zero(v.Type())
|
||||
}
|
||||
|
||||
arr := reflect.MakeSlice(v.Type(), v.Len(), v.Len())
|
||||
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
val := c.clone(v.Index(i))
|
||||
|
||||
if !val.IsValid() {
|
||||
continue
|
||||
}
|
||||
|
||||
item := arr.Index(i)
|
||||
if !item.CanSet() {
|
||||
continue
|
||||
}
|
||||
|
||||
item.Set(val.Convert(item.Type()))
|
||||
}
|
||||
|
||||
return arr
|
||||
}
|
||||
|
||||
func (c *cloner) cloneMap(v reflect.Value) reflect.Value {
|
||||
if v.IsNil() {
|
||||
return reflect.Zero(v.Type())
|
||||
}
|
||||
|
||||
clonedMap := reflect.MakeMap(v.Type())
|
||||
|
||||
for _, key := range v.MapKeys() {
|
||||
value := v.MapIndex(key)
|
||||
clonedKey := c.clone(key)
|
||||
clonedValue := c.clone(value)
|
||||
|
||||
if !isNillable(clonedKey) || !clonedKey.IsNil() {
|
||||
clonedKey = clonedKey.Convert(key.Type())
|
||||
}
|
||||
|
||||
if (!isNillable(clonedValue) || !clonedValue.IsNil()) && clonedValue.IsValid() {
|
||||
clonedValue = clonedValue.Convert(value.Type())
|
||||
}
|
||||
|
||||
if !clonedValue.IsValid() {
|
||||
clonedValue = reflect.Zero(clonedMap.Type().Elem())
|
||||
}
|
||||
|
||||
clonedMap.SetMapIndex(clonedKey, clonedValue)
|
||||
}
|
||||
|
||||
return clonedMap
|
||||
}
|
||||
|
||||
func isNillable(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Chan, reflect.Interface, reflect.Ptr, reflect.Func:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *cloner) clonePtr(v reflect.Value) reflect.Value {
|
||||
if v.IsNil() {
|
||||
return reflect.Zero(v.Type())
|
||||
}
|
||||
|
||||
var newVal reflect.Value
|
||||
|
||||
if v.Elem().CanAddr() {
|
||||
ptrs, exists := c.ptrs[v.Type()]
|
||||
if exists {
|
||||
if newVal, exists := ptrs[v.Elem().UnsafeAddr()]; exists {
|
||||
return newVal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newVal = c.clone(v.Elem())
|
||||
|
||||
if v.Elem().CanAddr() {
|
||||
ptrs, exists := c.ptrs[v.Type()]
|
||||
if exists {
|
||||
if newVal, exists := ptrs[v.Elem().UnsafeAddr()]; exists {
|
||||
return newVal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clonedPtr := reflect.New(newVal.Type())
|
||||
clonedPtr.Elem().Set(newVal)
|
||||
|
||||
return clonedPtr
|
||||
}
|
||||
|
||||
func (c *cloner) cloneStruct(v reflect.Value) reflect.Value {
|
||||
clonedStructPtr := reflect.New(v.Type())
|
||||
clonedStruct := clonedStructPtr.Elem()
|
||||
|
||||
if v.CanAddr() {
|
||||
ptrs := c.ptrs[clonedStructPtr.Type()]
|
||||
if ptrs == nil {
|
||||
ptrs = make(map[uintptr]reflect.Value)
|
||||
c.ptrs[clonedStructPtr.Type()] = ptrs
|
||||
}
|
||||
ptrs[v.UnsafeAddr()] = clonedStructPtr
|
||||
}
|
||||
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
newStructValue := clonedStruct.Field(i)
|
||||
if !newStructValue.CanSet() {
|
||||
continue
|
||||
}
|
||||
|
||||
clonedVal := c.clone(v.Field(i))
|
||||
if !clonedVal.IsValid() {
|
||||
continue
|
||||
}
|
||||
|
||||
newStructValue.Set(clonedVal.Convert(newStructValue.Type()))
|
||||
}
|
||||
|
||||
return clonedStruct
|
||||
}
|
||||
@@ -1,13 +1,25 @@
|
||||
package convertor
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
"github.com/duke-git/lancet/v2/validator"
|
||||
)
|
||||
|
||||
func TestToChar(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestToChar")
|
||||
|
||||
cases := []string{"", "abc", "1 2#3"}
|
||||
@@ -21,7 +33,23 @@ func TestToChar(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestToChannel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestToChannel")
|
||||
|
||||
ch := ToChannel([]int{1, 2, 3})
|
||||
assert.Equal(1, <-ch)
|
||||
assert.Equal(2, <-ch)
|
||||
assert.Equal(3, <-ch)
|
||||
|
||||
_, ok := <-ch
|
||||
assert.Equal(false, ok)
|
||||
}
|
||||
|
||||
func TestToBool(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestToBool")
|
||||
|
||||
cases := []string{"1", "true", "True", "false", "False", "0", "123", "0.0", "abc"}
|
||||
@@ -34,6 +62,8 @@ func TestToBool(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestToBytes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestToBytes")
|
||||
|
||||
cases := []any{
|
||||
@@ -60,6 +90,8 @@ func TestToBytes(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestToInt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestToInt")
|
||||
|
||||
cases := []any{"123", "-123", 123,
|
||||
@@ -76,6 +108,8 @@ func TestToInt(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestToFloat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestToFloat")
|
||||
|
||||
cases := []any{
|
||||
@@ -94,6 +128,8 @@ func TestToFloat(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestToString(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestToString")
|
||||
|
||||
aMap := make(map[string]int)
|
||||
@@ -106,21 +142,35 @@ func TestToString(t *testing.T) {
|
||||
}
|
||||
aStruct := TestStruct{Name: "TestStruct"}
|
||||
|
||||
i32Val := int32(123)
|
||||
i64Val := int64(123)
|
||||
iZeroVal := 0
|
||||
fVal := 12.3
|
||||
sVal := "abc"
|
||||
var iNilPointer *int
|
||||
var sNilPointer *string
|
||||
|
||||
cases := []any{
|
||||
"", nil,
|
||||
int(0), int8(1), int16(-1), int32(123), int64(123),
|
||||
uint(123), uint8(123), uint16(123), uint32(123), uint64(123),
|
||||
float64(12.3), float32(12.3),
|
||||
true, false,
|
||||
[]int{1, 2, 3}, aMap, aStruct, []byte{104, 101, 108, 108, 111}}
|
||||
[]int{1, 2, 3}, aMap, aStruct, []byte{104, 101, 108, 108, 111},
|
||||
&i32Val, &i64Val, &fVal, &sVal, &aStruct, iNilPointer, sNilPointer,
|
||||
&iZeroVal,
|
||||
}
|
||||
|
||||
expected := []string{
|
||||
"", "",
|
||||
"0", "1", "-1",
|
||||
"123", "123", "123", "123", "123", "123", "123",
|
||||
"12.3", "12.300000190734863",
|
||||
"12.3", "12.3",
|
||||
"true", "false",
|
||||
"[1,2,3]", "{\"a\":1,\"b\":2,\"c\":3}", "{\"Name\":\"TestStruct\"}", "hello"}
|
||||
"[1,2,3]", "{\"a\":1,\"b\":2,\"c\":3}", "{\"Name\":\"TestStruct\"}", "hello",
|
||||
"123", "123", "12.3", "abc", "{\"Name\":\"TestStruct\"}", "", "",
|
||||
"0",
|
||||
}
|
||||
|
||||
for i := 0; i < len(cases); i++ {
|
||||
actual := ToString(cases[i])
|
||||
@@ -128,6 +178,8 @@ func TestToString(t *testing.T) {
|
||||
}
|
||||
}
|
||||
func TestToJson(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestToJson")
|
||||
|
||||
var aMap = map[string]int{"a": 1, "b": 2, "c": 3}
|
||||
@@ -142,23 +194,83 @@ func TestToJson(t *testing.T) {
|
||||
assert.Equal("{\"Name\":\"TestStruct\"}", structJsonStr)
|
||||
}
|
||||
|
||||
func TestToMap(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestToMap")
|
||||
|
||||
type Message struct {
|
||||
name string
|
||||
code int
|
||||
}
|
||||
messages := []Message{
|
||||
{name: "Hello", code: 100},
|
||||
{name: "Hi", code: 101},
|
||||
}
|
||||
result := ToMap(messages, func(msg Message) (int, string) {
|
||||
return msg.code, msg.name
|
||||
})
|
||||
expected := map[int]string{100: "Hello", 101: "Hi"}
|
||||
|
||||
assert.Equal(expected, result)
|
||||
}
|
||||
|
||||
func TestStructToMap(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestStructToMap")
|
||||
|
||||
type People struct {
|
||||
Name string `json:"name"`
|
||||
age int
|
||||
}
|
||||
p := People{
|
||||
"test",
|
||||
100,
|
||||
}
|
||||
pm, _ := StructToMap(p)
|
||||
var expected = map[string]any{"name": "test"}
|
||||
assert.Equal(expected, pm)
|
||||
t.Run("StructToMap", func(_ *testing.T) {
|
||||
type People struct {
|
||||
Name string `json:"name"`
|
||||
age int
|
||||
}
|
||||
p := People{
|
||||
"test",
|
||||
100,
|
||||
}
|
||||
pm, _ := StructToMap(p)
|
||||
var expected = map[string]any{"name": "test"}
|
||||
assert.Equal(expected, pm)
|
||||
})
|
||||
|
||||
t.Run("StructToMapWithJsonAttr", func(_ *testing.T) {
|
||||
type People struct {
|
||||
Name string `json:"name,omitempty"` // json tag with attribute
|
||||
Phone string `json:"phone"` // json tag without attribute
|
||||
Sex string `json:"-"` // ignore
|
||||
age int // no tag
|
||||
}
|
||||
p := People{
|
||||
Phone: "1111",
|
||||
Sex: "male",
|
||||
age: 100,
|
||||
}
|
||||
pm, _ := StructToMap(p)
|
||||
var expected = map[string]any{"phone": "1111"}
|
||||
assert.Equal(expected, pm)
|
||||
})
|
||||
}
|
||||
|
||||
func TestMapToSlice(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestMapToSlice")
|
||||
|
||||
aMap := map[string]int{"a": 1, "b": 2, "c": 3}
|
||||
result := MapToSlice(aMap, func(key string, value int) string {
|
||||
return key + ":" + strconv.Itoa(value)
|
||||
})
|
||||
|
||||
assert.Equal(3, len(result))
|
||||
assert.Equal(true, slice.Contain(result, "a:1"))
|
||||
assert.Equal(true, slice.Contain(result, "b:2"))
|
||||
assert.Equal(true, slice.Contain(result, "c:3"))
|
||||
}
|
||||
|
||||
func TestColorHexToRGB(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
colorHex := "#003366"
|
||||
r, g, b := ColorHexToRGB(colorHex)
|
||||
colorRGB := fmt.Sprintf("%d,%d,%d", r, g, b)
|
||||
@@ -169,6 +281,8 @@ func TestColorHexToRGB(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestColorRGBToHex(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r := 0
|
||||
g := 51
|
||||
b := 102
|
||||
@@ -178,3 +292,615 @@ func TestColorRGBToHex(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestColorRGBToHex")
|
||||
assert.Equal(expected, colorHex)
|
||||
}
|
||||
|
||||
func TestToPointer(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestToPointer")
|
||||
result := ToPointer(123)
|
||||
|
||||
assert.Equal(*result, 123)
|
||||
}
|
||||
|
||||
func TestToPointers(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestToPointers")
|
||||
|
||||
intVals := []int{1, 2, 3}
|
||||
result := ToPointers(intVals)
|
||||
|
||||
assert.Equal(3, len(result))
|
||||
assert.Equal(1, *result[0])
|
||||
assert.Equal(2, *result[1])
|
||||
assert.Equal(3, *result[2])
|
||||
|
||||
stringVals := []string{"a", "b", "c"}
|
||||
resultStr := ToPointers(stringVals)
|
||||
assert.Equal(3, len(resultStr))
|
||||
assert.Equal("a", *resultStr[0])
|
||||
assert.Equal("b", *resultStr[1])
|
||||
assert.Equal("c", *resultStr[2])
|
||||
}
|
||||
|
||||
func TestFromPointer(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestFromPointer")
|
||||
|
||||
intVal := 123
|
||||
pointer := &intVal
|
||||
result := FromPointer(pointer)
|
||||
|
||||
assert.Equal(123, result)
|
||||
|
||||
stringVal := "abc"
|
||||
stringPointer := &stringVal
|
||||
resultStr := FromPointer(stringPointer)
|
||||
|
||||
assert.Equal("abc", resultStr)
|
||||
}
|
||||
|
||||
func TestFromPointers(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestFromPointers")
|
||||
|
||||
intPointers := []*int{new(int), new(int), new(int)}
|
||||
*intPointers[0] = 1
|
||||
*intPointers[1] = 2
|
||||
*intPointers[2] = 3
|
||||
|
||||
result := FromPointers(intPointers)
|
||||
|
||||
assert.Equal(3, len(result))
|
||||
assert.Equal(1, result[0])
|
||||
assert.Equal(2, result[1])
|
||||
assert.Equal(3, result[2])
|
||||
|
||||
stringPointers := []*string{new(string), new(string), new(string)}
|
||||
*stringPointers[0] = "a"
|
||||
*stringPointers[1] = "b"
|
||||
*stringPointers[2] = "c"
|
||||
|
||||
resultStr := FromPointers(stringPointers)
|
||||
|
||||
assert.Equal(3, len(resultStr))
|
||||
assert.Equal("a", resultStr[0])
|
||||
assert.Equal("b", resultStr[1])
|
||||
assert.Equal("c", resultStr[2])
|
||||
}
|
||||
|
||||
func TestEncodeByte(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestEncodeByte")
|
||||
|
||||
byteData, _ := EncodeByte("abc")
|
||||
expected := []byte{6, 12, 0, 3, 97, 98, 99}
|
||||
|
||||
assert.Equal(expected, byteData)
|
||||
}
|
||||
|
||||
func TestDecodeByte(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestDecodeByte")
|
||||
|
||||
var obj string
|
||||
byteData := []byte{6, 12, 0, 3, 97, 98, 99}
|
||||
err := DecodeByte(byteData, &obj)
|
||||
assert.IsNil(err)
|
||||
assert.Equal("abc", obj)
|
||||
}
|
||||
|
||||
func TestDeepClone(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// assert := internal.NewAssert(t, "TestDeepClone")
|
||||
|
||||
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",
|
||||
},
|
||||
[]interface{}{1, &Struct{Str: "test"}, Struct{Str: "test2"}},
|
||||
}
|
||||
|
||||
for i, item := range cases {
|
||||
cloned := DeepClone(item)
|
||||
|
||||
if &cloned == &item {
|
||||
t.Fatalf("[TestDeepClone case #%d failed]: equal pointer", i)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(item, cloned) {
|
||||
t.Fatalf("[TestDeepClone case #%d failed] unequal objects", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyProperties(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestCopyProperties")
|
||||
|
||||
type Disk struct {
|
||||
Name string `json:"name"`
|
||||
Total string `json:"total"`
|
||||
Used string `json:"used"`
|
||||
Percent float64 `json:"percent"`
|
||||
}
|
||||
|
||||
type DiskVO struct {
|
||||
Name string `json:"name"`
|
||||
Total string `json:"total"`
|
||||
Used string `json:"used"`
|
||||
Percent float64 `json:"percent"`
|
||||
}
|
||||
|
||||
type Indicator struct {
|
||||
Id string `json:"id"`
|
||||
Ip string `json:"ip"`
|
||||
UpTime string `json:"upTime"`
|
||||
LoadAvg string `json:"loadAvg"`
|
||||
Cpu int `json:"cpu"`
|
||||
Disk []Disk `json:"disk"`
|
||||
Stop chan bool `json:"-"`
|
||||
}
|
||||
|
||||
type IndicatorVO struct {
|
||||
Id string `json:"id"`
|
||||
Ip string `json:"ip"`
|
||||
UpTime string `json:"upTime"`
|
||||
LoadAvg string `json:"loadAvg"`
|
||||
Cpu int64 `json:"cpu"`
|
||||
Disk []DiskVO `json:"disk"`
|
||||
}
|
||||
|
||||
indicator := &Indicator{Id: "001", Ip: "127.0.0.1", Cpu: 1, Disk: []Disk{
|
||||
{Name: "disk-001", Total: "100", Used: "1", Percent: 10},
|
||||
{Name: "disk-002", Total: "200", Used: "1", Percent: 20},
|
||||
{Name: "disk-003", Total: "300", Used: "1", Percent: 30},
|
||||
}}
|
||||
|
||||
indicatorVO := IndicatorVO{}
|
||||
|
||||
err := CopyProperties(&indicatorVO, indicator)
|
||||
|
||||
assert.IsNil(err)
|
||||
assert.Equal("001", indicatorVO.Id)
|
||||
assert.Equal("127.0.0.1", indicatorVO.Ip)
|
||||
assert.Equal(3, len(indicatorVO.Disk))
|
||||
}
|
||||
|
||||
func TestToInterface(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestToInterface")
|
||||
|
||||
cases := []reflect.Value{
|
||||
reflect.ValueOf("abc"),
|
||||
reflect.ValueOf(int(0)), reflect.ValueOf(int8(1)), reflect.ValueOf(int16(-1)), reflect.ValueOf(int32(123)), reflect.ValueOf(int64(123)),
|
||||
reflect.ValueOf(uint(123)), reflect.ValueOf(uint8(123)), reflect.ValueOf(uint16(123)), reflect.ValueOf(uint32(123)), reflect.ValueOf(uint64(123)),
|
||||
reflect.ValueOf(float64(12.3)), reflect.ValueOf(float32(12.3)),
|
||||
reflect.ValueOf(true), reflect.ValueOf(false),
|
||||
}
|
||||
|
||||
expected := []interface{}{
|
||||
"abc",
|
||||
0, int8(1), int16(-1), int32(123), int64(123),
|
||||
uint(123), uint8(123), uint16(123), uint32(123), uint64(123),
|
||||
float64(12.3), float32(12.3),
|
||||
true, false,
|
||||
}
|
||||
|
||||
for i := 0; i < len(cases); i++ {
|
||||
actual, _ := ToInterface(cases[i])
|
||||
assert.Equal(expected[i], actual)
|
||||
}
|
||||
|
||||
nilVal, ok := ToInterface(reflect.ValueOf(nil))
|
||||
assert.EqualValues(nil, nilVal)
|
||||
assert.Equal(false, ok)
|
||||
}
|
||||
|
||||
func TestUtf8ToGbk(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestUtf8ToGbk")
|
||||
|
||||
utf8Data := []byte("hello")
|
||||
gbkData, err := Utf8ToGbk(utf8Data)
|
||||
|
||||
assert.Equal(true, utf8.Valid(utf8Data))
|
||||
assert.Equal(true, validator.IsGBK(gbkData))
|
||||
assert.IsNil(err)
|
||||
}
|
||||
|
||||
func TestGbkToUtf8(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestGbkToUtf8")
|
||||
|
||||
gbkData, err := Utf8ToGbk([]byte("hello"))
|
||||
utf8Data, err := GbkToUtf8(gbkData)
|
||||
|
||||
assert.IsNil(err)
|
||||
assert.Equal(true, utf8.Valid(utf8Data))
|
||||
assert.Equal("hello", string(utf8Data))
|
||||
}
|
||||
|
||||
func TestToStdBase64(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestToStdBase64")
|
||||
|
||||
r1 := ToStdBase64("abc")
|
||||
d1, _ := base64.StdEncoding.DecodeString(r1)
|
||||
assert.Equal("abc", string(d1))
|
||||
|
||||
r2 := ToStdBase64([]byte("abc"))
|
||||
d2, _ := base64.StdEncoding.DecodeString(r2)
|
||||
assert.Equal("abc", string(d2))
|
||||
|
||||
r3 := ToStdBase64(123)
|
||||
d3, _ := base64.StdEncoding.DecodeString(r3)
|
||||
assert.Equal("123", string(d3))
|
||||
|
||||
r4 := ToStdBase64(11.11)
|
||||
d4, _ := base64.StdEncoding.DecodeString(r4)
|
||||
assert.Equal("11.11", string(d4))
|
||||
|
||||
r5 := ToStdBase64(map[string]any{"name": "duke", "quantity": 1})
|
||||
d5, _ := base64.StdEncoding.DecodeString(r5)
|
||||
assert.Equal("{\"name\":\"duke\",\"quantity\":1}", string(d5))
|
||||
|
||||
r6 := ToStdBase64([]int64{7, 5, 9, 4, 23})
|
||||
d6, _ := base64.StdEncoding.DecodeString(r6)
|
||||
assert.Equal("[7,5,9,4,23]", string(d6))
|
||||
|
||||
r7 := ToStdBase64([]string{"7", "5", "9", "4", "23"})
|
||||
d7, _ := base64.StdEncoding.DecodeString(r7)
|
||||
assert.Equal("[\"7\",\"5\",\"9\",\"4\",\"23\"]", string(d7))
|
||||
|
||||
r8 := ToStdBase64(nil)
|
||||
d8, _ := base64.StdEncoding.DecodeString(r8)
|
||||
assert.Equal("", string(d8))
|
||||
|
||||
ch := make(chan int, 3)
|
||||
ch <- 1
|
||||
ch <- 2
|
||||
r9 := ToStdBase64(ch)
|
||||
d9, _ := base64.StdEncoding.DecodeString(r9)
|
||||
assert.Equal("", string(d9))
|
||||
|
||||
r10 := ToStdBase64(io.EOF)
|
||||
d10, _ := base64.StdEncoding.DecodeString(r10)
|
||||
assert.Equal("EOF", string(d10))
|
||||
|
||||
r11 := ToStdBase64(errors.New("test"))
|
||||
d11, _ := base64.StdEncoding.DecodeString(r11)
|
||||
assert.Equal("test", string(d11))
|
||||
|
||||
typedNil := (*int)(nil)
|
||||
r12 := ToStdBase64(typedNil)
|
||||
d12, _ := base64.StdEncoding.DecodeString(r12)
|
||||
assert.Equal("", string(d12))
|
||||
|
||||
type nilInterface interface {
|
||||
}
|
||||
var nI nilInterface = nil
|
||||
d13, _ := base64.StdEncoding.DecodeString(ToStdBase64(nI))
|
||||
assert.Equal("", string(d13))
|
||||
|
||||
var p unsafe.Pointer
|
||||
d14, _ := base64.StdEncoding.DecodeString(ToStdBase64(p))
|
||||
assert.Equal("", string(d14))
|
||||
}
|
||||
|
||||
func TestToUrlBase64(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestToUrlBase64")
|
||||
|
||||
r1 := ToUrlBase64("abc")
|
||||
d1, _ := base64.URLEncoding.DecodeString(r1)
|
||||
assert.Equal("abc", string(d1))
|
||||
|
||||
r2 := ToUrlBase64([]byte("abc"))
|
||||
d2, _ := base64.URLEncoding.DecodeString(r2)
|
||||
assert.Equal("abc", string(d2))
|
||||
|
||||
r3 := ToUrlBase64(123)
|
||||
d3, _ := base64.URLEncoding.DecodeString(r3)
|
||||
assert.Equal("123", string(d3))
|
||||
|
||||
r4 := ToUrlBase64(11.11)
|
||||
d4, _ := base64.URLEncoding.DecodeString(r4)
|
||||
assert.Equal("11.11", string(d4))
|
||||
|
||||
r5 := ToUrlBase64(map[string]any{"name": "duke", "quantity": 1})
|
||||
d5, _ := base64.URLEncoding.DecodeString(r5)
|
||||
assert.Equal("{\"name\":\"duke\",\"quantity\":1}", string(d5))
|
||||
|
||||
r6 := ToUrlBase64([]int64{7, 5, 9, 4, 23})
|
||||
d6, _ := base64.URLEncoding.DecodeString(r6)
|
||||
assert.Equal("[7,5,9,4,23]", string(d6))
|
||||
|
||||
r7 := ToUrlBase64([]string{"7", "5", "9", "4", "23"})
|
||||
d7, _ := base64.URLEncoding.DecodeString(r7)
|
||||
assert.Equal("[\"7\",\"5\",\"9\",\"4\",\"23\"]", string(d7))
|
||||
|
||||
r8 := ToUrlBase64(nil)
|
||||
d8, _ := base64.URLEncoding.DecodeString(r8)
|
||||
assert.Equal("", string(d8))
|
||||
|
||||
ch := make(chan int, 3)
|
||||
ch <- 1
|
||||
ch <- 2
|
||||
r9 := ToUrlBase64(ch)
|
||||
d9, _ := base64.URLEncoding.DecodeString(r9)
|
||||
assert.Equal("", string(d9))
|
||||
|
||||
r10 := ToUrlBase64(io.EOF)
|
||||
d10, _ := base64.URLEncoding.DecodeString(r10)
|
||||
assert.Equal("EOF", string(d10))
|
||||
|
||||
r11 := ToUrlBase64(errors.New("test"))
|
||||
d11, _ := base64.URLEncoding.DecodeString(r11)
|
||||
assert.Equal("test", string(d11))
|
||||
|
||||
typedNil := (*int)(nil)
|
||||
r12 := ToUrlBase64(typedNil)
|
||||
d12, _ := base64.URLEncoding.DecodeString(r12)
|
||||
assert.Equal("", string(d12))
|
||||
|
||||
type nilInterface interface {
|
||||
}
|
||||
var nI nilInterface = nil
|
||||
d13, _ := base64.URLEncoding.DecodeString(ToUrlBase64(nI))
|
||||
assert.Equal("", string(d13))
|
||||
|
||||
var p unsafe.Pointer
|
||||
d14, _ := base64.URLEncoding.DecodeString(ToUrlBase64(p))
|
||||
assert.Equal("", string(d14))
|
||||
|
||||
r15 := ToUrlBase64("4+3/4?=")
|
||||
d15, _ := base64.URLEncoding.DecodeString(r15)
|
||||
assert.Equal("4+3/4?=", string(d15))
|
||||
}
|
||||
|
||||
func TestToRawStdBase64(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestToRawStdBase64")
|
||||
|
||||
r1 := ToRawStdBase64("abc")
|
||||
d1, _ := base64.RawStdEncoding.DecodeString(r1)
|
||||
assert.Equal("abc", string(d1))
|
||||
|
||||
r2 := ToRawStdBase64([]byte("abc"))
|
||||
d2, _ := base64.RawStdEncoding.DecodeString(r2)
|
||||
assert.Equal("abc", string(d2))
|
||||
|
||||
r3 := ToRawStdBase64(123)
|
||||
d3, _ := base64.RawStdEncoding.DecodeString(r3)
|
||||
assert.Equal("123", string(d3))
|
||||
|
||||
r4 := ToRawStdBase64(11.11)
|
||||
d4, _ := base64.RawStdEncoding.DecodeString(r4)
|
||||
assert.Equal("11.11", string(d4))
|
||||
|
||||
r5 := ToRawStdBase64(map[string]any{"name": "duke", "quantity": 1})
|
||||
d5, _ := base64.RawStdEncoding.DecodeString(r5)
|
||||
assert.Equal("{\"name\":\"duke\",\"quantity\":1}", string(d5))
|
||||
|
||||
r6 := ToRawStdBase64([]int64{7, 5, 9, 4, 23})
|
||||
d6, _ := base64.RawStdEncoding.DecodeString(r6)
|
||||
assert.Equal("[7,5,9,4,23]", string(d6))
|
||||
|
||||
r7 := ToRawStdBase64([]string{"7", "5", "9", "4", "23"})
|
||||
d7, _ := base64.RawStdEncoding.DecodeString(r7)
|
||||
assert.Equal("[\"7\",\"5\",\"9\",\"4\",\"23\"]", string(d7))
|
||||
|
||||
r8 := ToRawStdBase64(nil)
|
||||
d8, _ := base64.RawStdEncoding.DecodeString(r8)
|
||||
assert.Equal("", string(d8))
|
||||
|
||||
ch := make(chan int, 3)
|
||||
ch <- 1
|
||||
ch <- 2
|
||||
r9 := ToRawStdBase64(ch)
|
||||
d9, _ := base64.RawStdEncoding.DecodeString(r9)
|
||||
assert.Equal("", string(d9))
|
||||
|
||||
r10 := ToRawStdBase64(io.EOF)
|
||||
d10, _ := base64.RawStdEncoding.DecodeString(r10)
|
||||
assert.Equal("EOF", string(d10))
|
||||
|
||||
r11 := ToRawStdBase64(errors.New("test"))
|
||||
d11, _ := base64.RawStdEncoding.DecodeString(r11)
|
||||
assert.Equal("test", string(d11))
|
||||
|
||||
typedNil := (*int)(nil)
|
||||
r12 := ToRawStdBase64(typedNil)
|
||||
d12, _ := base64.RawStdEncoding.DecodeString(r12)
|
||||
assert.Equal("", string(d12))
|
||||
|
||||
type nilInterface interface {
|
||||
}
|
||||
var nI nilInterface = nil
|
||||
d13, _ := base64.RawStdEncoding.DecodeString(ToRawStdBase64(nI))
|
||||
assert.Equal("", string(d13))
|
||||
|
||||
var p unsafe.Pointer
|
||||
d14, _ := base64.RawStdEncoding.DecodeString(ToRawStdBase64(p))
|
||||
assert.Equal("", string(d14))
|
||||
}
|
||||
|
||||
func TestToRawUrlBase64(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestToRawUrlBase64")
|
||||
|
||||
r1 := ToRawUrlBase64("abc")
|
||||
d1, _ := base64.RawURLEncoding.DecodeString(r1)
|
||||
assert.Equal("abc", string(d1))
|
||||
|
||||
r2 := ToRawUrlBase64([]byte("abc"))
|
||||
d2, _ := base64.RawURLEncoding.DecodeString(r2)
|
||||
assert.Equal("abc", string(d2))
|
||||
|
||||
r3 := ToRawUrlBase64(123)
|
||||
d3, _ := base64.RawURLEncoding.DecodeString(r3)
|
||||
assert.Equal("123", string(d3))
|
||||
|
||||
r4 := ToRawUrlBase64(11.11)
|
||||
d4, _ := base64.RawURLEncoding.DecodeString(r4)
|
||||
assert.Equal("11.11", string(d4))
|
||||
|
||||
r5 := ToRawUrlBase64(map[string]any{"name": "duke", "quantity": 1})
|
||||
d5, _ := base64.RawURLEncoding.DecodeString(r5)
|
||||
assert.Equal("{\"name\":\"duke\",\"quantity\":1}", string(d5))
|
||||
|
||||
r6 := ToRawUrlBase64([]int64{7, 5, 9, 4, 23})
|
||||
d6, _ := base64.RawURLEncoding.DecodeString(r6)
|
||||
assert.Equal("[7,5,9,4,23]", string(d6))
|
||||
|
||||
r7 := ToRawUrlBase64([]string{"7", "5", "9", "4", "23"})
|
||||
d7, _ := base64.RawURLEncoding.DecodeString(r7)
|
||||
assert.Equal("[\"7\",\"5\",\"9\",\"4\",\"23\"]", string(d7))
|
||||
|
||||
r8 := ToRawUrlBase64(nil)
|
||||
d8, _ := base64.RawURLEncoding.DecodeString(r8)
|
||||
assert.Equal("", string(d8))
|
||||
|
||||
ch := make(chan int, 3)
|
||||
ch <- 1
|
||||
ch <- 2
|
||||
r9 := ToRawUrlBase64(ch)
|
||||
d9, _ := base64.RawURLEncoding.DecodeString(r9)
|
||||
assert.Equal("", string(d9))
|
||||
|
||||
r10 := ToRawUrlBase64(io.EOF)
|
||||
d10, _ := base64.RawURLEncoding.DecodeString(r10)
|
||||
assert.Equal("EOF", string(d10))
|
||||
|
||||
r11 := ToRawUrlBase64(errors.New("test"))
|
||||
d11, _ := base64.RawURLEncoding.DecodeString(r11)
|
||||
assert.Equal("test", string(d11))
|
||||
|
||||
typedNil := (*int)(nil)
|
||||
r12 := ToRawUrlBase64(typedNil)
|
||||
d12, _ := base64.RawURLEncoding.DecodeString(r12)
|
||||
assert.Equal("", string(d12))
|
||||
|
||||
type nilInterface interface {
|
||||
}
|
||||
var nI nilInterface = nil
|
||||
d13, _ := base64.RawURLEncoding.DecodeString(ToRawUrlBase64(nI))
|
||||
assert.Equal("", string(d13))
|
||||
|
||||
var p unsafe.Pointer
|
||||
d14, _ := base64.RawURLEncoding.DecodeString(ToRawUrlBase64(p))
|
||||
assert.Equal("", string(d14))
|
||||
|
||||
r15 := ToRawUrlBase64("4+3/4?=")
|
||||
d15, _ := base64.RawURLEncoding.DecodeString(r15)
|
||||
assert.Equal("4+3/4?=", string(d15))
|
||||
}
|
||||
|
||||
func TestToBigInt(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestToBigInt")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input any
|
||||
want *big.Int
|
||||
hasErr bool
|
||||
}{
|
||||
{
|
||||
name: "int",
|
||||
input: 42,
|
||||
want: big.NewInt(42),
|
||||
},
|
||||
{
|
||||
name: "int8",
|
||||
input: int8(127),
|
||||
want: big.NewInt(127),
|
||||
},
|
||||
{
|
||||
name: "int16",
|
||||
input: int16(32000),
|
||||
want: big.NewInt(32000),
|
||||
},
|
||||
{
|
||||
name: "int32",
|
||||
input: int32(123456),
|
||||
want: big.NewInt(123456),
|
||||
},
|
||||
{
|
||||
name: "int64",
|
||||
input: int64(987654321),
|
||||
want: big.NewInt(987654321),
|
||||
},
|
||||
{
|
||||
name: "uint",
|
||||
input: uint(987654321),
|
||||
want: big.NewInt(987654321),
|
||||
},
|
||||
{
|
||||
name: "uint8",
|
||||
input: uint8(255),
|
||||
want: big.NewInt(255),
|
||||
},
|
||||
{
|
||||
name: "uint16",
|
||||
input: uint16(65535),
|
||||
want: big.NewInt(65535),
|
||||
},
|
||||
{
|
||||
name: "uint32",
|
||||
input: uint32(4294967295),
|
||||
want: big.NewInt(4294967295),
|
||||
},
|
||||
{
|
||||
name: "uint64",
|
||||
input: uint64(18446744073709551615),
|
||||
want: new(big.Int).SetUint64(18446744073709551615),
|
||||
},
|
||||
{
|
||||
name: "unsupported type",
|
||||
input: 3.14, // Unsupported type
|
||||
hasErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := ToBigInt(tt.input)
|
||||
if (err != nil) != tt.hasErr {
|
||||
t.Errorf("ToBigInt() error = %v, hasErr %v", err, tt.hasErr)
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
168
cryptor/aes.go
168
cryptor/aes.go
@@ -1,168 +0,0 @@
|
||||
// Copyright 2021 dudaodong@gmail.com. All rights reserved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
// Package cryptor implements some util functions to encrypt and decrypt.
|
||||
// Note:
|
||||
// 1. for aes crypt function, the `key` param length should be 16, 24 or 32. if not, will panic.
|
||||
package cryptor
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"io"
|
||||
)
|
||||
|
||||
// AesEcbEncrypt encrypt data with key use AES ECB algorithm
|
||||
// len(key) should be 16, 24 or 32
|
||||
func AesEcbEncrypt(data, key []byte) []byte {
|
||||
cipher, _ := aes.NewCipher(generateAesKey(key))
|
||||
length := (len(data) + aes.BlockSize) / aes.BlockSize
|
||||
plain := make([]byte, length*aes.BlockSize)
|
||||
copy(plain, data)
|
||||
pad := byte(len(plain) - len(data))
|
||||
for i := len(data); i < len(plain); i++ {
|
||||
plain[i] = pad
|
||||
}
|
||||
encrypted := make([]byte, len(plain))
|
||||
for bs, be := 0, cipher.BlockSize(); bs <= len(data); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
|
||||
cipher.Encrypt(encrypted[bs:be], plain[bs:be])
|
||||
}
|
||||
|
||||
return encrypted
|
||||
}
|
||||
|
||||
// AesEcbDecrypt decrypt data with key use AES ECB algorithm
|
||||
// len(key) should be 16, 24 or 32
|
||||
func AesEcbDecrypt(encrypted, key []byte) []byte {
|
||||
cipher, _ := aes.NewCipher(generateAesKey(key))
|
||||
decrypted := make([]byte, len(encrypted))
|
||||
//
|
||||
for bs, be := 0, cipher.BlockSize(); bs < len(encrypted); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
|
||||
cipher.Decrypt(decrypted[bs:be], encrypted[bs:be])
|
||||
}
|
||||
|
||||
trim := 0
|
||||
if len(decrypted) > 0 {
|
||||
trim = len(decrypted) - int(decrypted[len(decrypted)-1])
|
||||
}
|
||||
|
||||
return decrypted[:trim]
|
||||
}
|
||||
|
||||
// AesCbcEncrypt encrypt data with key use AES CBC algorithm
|
||||
// len(key) should be 16, 24 or 32
|
||||
func AesCbcEncrypt(data, key []byte) []byte {
|
||||
// len(key) should be 16, 24 or 32
|
||||
block, _ := aes.NewCipher(key)
|
||||
blockSize := block.BlockSize()
|
||||
data = pkcs7Padding(data, blockSize)
|
||||
blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
|
||||
|
||||
encrypted := make([]byte, len(data))
|
||||
blockMode.CryptBlocks(encrypted, data)
|
||||
return encrypted
|
||||
}
|
||||
|
||||
// AesCbcDecrypt decrypt data with key use AES CBC algorithm
|
||||
// len(key) should be 16, 24 or 32
|
||||
func AesCbcDecrypt(encrypted, key []byte) []byte {
|
||||
block, _ := aes.NewCipher(key)
|
||||
blockSize := block.BlockSize()
|
||||
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
|
||||
|
||||
decrypted := make([]byte, len(encrypted))
|
||||
blockMode.CryptBlocks(decrypted, encrypted)
|
||||
decrypted = pkcs7UnPadding(decrypted)
|
||||
return decrypted
|
||||
}
|
||||
|
||||
// AesCtrCrypt encrypt data with key use AES CTR algorithm
|
||||
// len(key) should be 16, 24 or 32
|
||||
func AesCtrCrypt(data, key []byte) []byte {
|
||||
block, _ := aes.NewCipher(key)
|
||||
|
||||
iv := bytes.Repeat([]byte("1"), block.BlockSize())
|
||||
stream := cipher.NewCTR(block, iv)
|
||||
|
||||
dst := make([]byte, len(data))
|
||||
stream.XORKeyStream(dst, data)
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
// AesCfbEncrypt encrypt data with key use AES CFB algorithm
|
||||
// len(key) should be 16, 24 or 32
|
||||
func AesCfbEncrypt(data, key []byte) []byte {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
encrypted := make([]byte, aes.BlockSize+len(data))
|
||||
iv := encrypted[:aes.BlockSize]
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
stream := cipher.NewCFBEncrypter(block, iv)
|
||||
stream.XORKeyStream(encrypted[aes.BlockSize:], data)
|
||||
return encrypted
|
||||
}
|
||||
|
||||
// AesCfbDecrypt decrypt data with key use AES CFB algorithm
|
||||
// len(encrypted) should be great than 16, len(key) should be 16, 24 or 32
|
||||
func AesCfbDecrypt(encrypted, key []byte) []byte {
|
||||
block, _ := aes.NewCipher(key)
|
||||
if len(encrypted) < aes.BlockSize {
|
||||
panic("encrypted data is too short")
|
||||
}
|
||||
iv := encrypted[:aes.BlockSize]
|
||||
encrypted = encrypted[aes.BlockSize:]
|
||||
|
||||
stream := cipher.NewCFBDecrypter(block, iv)
|
||||
stream.XORKeyStream(encrypted, encrypted)
|
||||
return encrypted
|
||||
}
|
||||
|
||||
// AesOfbEncrypt encrypt data with key use AES OFB algorithm
|
||||
// len(key) should be 16, 24 or 32
|
||||
func AesOfbEncrypt(data, key []byte) []byte {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
data = pkcs7Padding(data, aes.BlockSize)
|
||||
encrypted := make([]byte, aes.BlockSize+len(data))
|
||||
iv := encrypted[:aes.BlockSize]
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
stream := cipher.NewOFB(block, iv)
|
||||
stream.XORKeyStream(encrypted[aes.BlockSize:], data)
|
||||
return encrypted
|
||||
}
|
||||
|
||||
// AesOfbDecrypt decrypt data with key use AES OFB algorithm
|
||||
// len(key) should be 16, 24 or 32
|
||||
func AesOfbDecrypt(data, key []byte) []byte {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
iv := data[:aes.BlockSize]
|
||||
data = data[aes.BlockSize:]
|
||||
if len(data)%aes.BlockSize != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
decrypted := make([]byte, len(data))
|
||||
mode := cipher.NewOFB(block, iv)
|
||||
mode.XORKeyStream(decrypted, data)
|
||||
|
||||
decrypted = pkcs7UnPadding(decrypted)
|
||||
return decrypted
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package cryptor
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
|
||||
func TestAesEcbEncrypt(t *testing.T) {
|
||||
data := "hello world"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
aesEcbEncrypt := AesEcbEncrypt([]byte(data), []byte(key))
|
||||
aesEcbDecrypt := AesEcbDecrypt(aesEcbEncrypt, []byte(key))
|
||||
|
||||
assert := internal.NewAssert(t, "TestAesEcbEncrypt")
|
||||
assert.Equal(data, string(aesEcbDecrypt))
|
||||
}
|
||||
|
||||
func TestAesCbcEncrypt(t *testing.T) {
|
||||
data := "hello world"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
aesCbcEncrypt := AesCbcEncrypt([]byte(data), []byte(key))
|
||||
aesCbcDecrypt := AesCbcDecrypt(aesCbcEncrypt, []byte(key))
|
||||
|
||||
assert := internal.NewAssert(t, "TestAesCbcEncrypt")
|
||||
assert.Equal(data, string(aesCbcDecrypt))
|
||||
}
|
||||
|
||||
func TestAesCtrCrypt(t *testing.T) {
|
||||
data := "hello world"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
aesCtrCrypt := AesCtrCrypt([]byte(data), []byte(key))
|
||||
aesCtrDeCrypt := AesCtrCrypt(aesCtrCrypt, []byte(key))
|
||||
|
||||
assert := internal.NewAssert(t, "TestAesCtrCrypt")
|
||||
assert.Equal(data, string(aesCtrDeCrypt))
|
||||
}
|
||||
|
||||
func TestAesCfbEncrypt(t *testing.T) {
|
||||
data := "hello world"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
aesCfbEncrypt := AesCfbEncrypt([]byte(data), []byte(key))
|
||||
aesCfbDecrypt := AesCfbDecrypt(aesCfbEncrypt, []byte(key))
|
||||
|
||||
assert := internal.NewAssert(t, "TestAesCfbEncrypt")
|
||||
assert.Equal(data, string(aesCfbDecrypt))
|
||||
}
|
||||
|
||||
func TestAesOfbEncrypt(t *testing.T) {
|
||||
data := "hello world"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
aesOfbEncrypt := AesOfbEncrypt([]byte(data), []byte(key))
|
||||
aesOfbDecrypt := AesOfbDecrypt(aesOfbEncrypt, []byte(key))
|
||||
|
||||
assert := internal.NewAssert(t, "TestAesOfbEncrypt")
|
||||
assert.Equal(data, string(aesOfbDecrypt))
|
||||
}
|
||||
184
cryptor/basic.go
184
cryptor/basic.go
@@ -6,7 +6,6 @@
|
||||
package cryptor
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
@@ -19,101 +18,194 @@ import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// Base64StdEncode encode string with base64 encoding
|
||||
// Base64StdEncode encode string with base64 encoding.
|
||||
// Play: https://go.dev/play/p/VOaUyQUreoK
|
||||
func Base64StdEncode(s string) string {
|
||||
return base64.StdEncoding.EncodeToString([]byte(s))
|
||||
}
|
||||
|
||||
// Base64StdDecode decode a base64 encoded string
|
||||
// Base64StdDecode decode a base64 encoded string.
|
||||
// Play: https://go.dev/play/p/RWQylnJVgIe
|
||||
func Base64StdDecode(s string) string {
|
||||
b, _ := base64.StdEncoding.DecodeString(s)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// Md5String return the md5 value of string
|
||||
// Md5String return the md5 value of string.
|
||||
// Play: https://go.dev/play/p/1bLcVetbTOI
|
||||
func Md5String(s string) string {
|
||||
h := md5.New()
|
||||
h.Write([]byte(s))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
// Md5File return the md5 value of file
|
||||
func Md5File(filename string) (string, error) {
|
||||
if fileInfo, err := os.Stat(filename); err != nil {
|
||||
return "", err
|
||||
} else if fileInfo.IsDir() {
|
||||
return "", nil
|
||||
}
|
||||
// Md5StringWithBase64 return the md5 value of string with base64.
|
||||
// Play: https://go.dev/play/p/Lx4gH7Vdr5_y
|
||||
func Md5StringWithBase64(s string) string {
|
||||
h := md5.New()
|
||||
h.Write([]byte(s))
|
||||
return base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
// Md5Byte return the md5 string of byte slice.
|
||||
// Play: https://go.dev/play/p/suraalH8lyC
|
||||
func Md5Byte(data []byte) string {
|
||||
h := md5.New()
|
||||
h.Write(data)
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
// Md5ByteWithBase64 return the md5 string of byte slice with base64.
|
||||
// Play: https://go.dev/play/p/Tcb-Z7LN2ax
|
||||
func Md5ByteWithBase64(data []byte) string {
|
||||
h := md5.New()
|
||||
h.Write(data)
|
||||
return base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
// Md5File return the md5 value of file.
|
||||
func Md5File(filename string) (string, error) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
hash := md5.New()
|
||||
|
||||
chunkSize := 65536
|
||||
for buf, reader := make([]byte, chunkSize), bufio.NewReader(file); ; {
|
||||
n, err := reader.Read(buf)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
hash.Write(buf[:n])
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if stat.IsDir() {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
checksum := fmt.Sprintf("%x", hash.Sum(nil))
|
||||
return checksum, nil
|
||||
hash := md5.New()
|
||||
buf := make([]byte, 65536) // 64KB
|
||||
|
||||
for {
|
||||
n, err := file.Read(buf)
|
||||
if err != nil && err != io.EOF {
|
||||
return "", err
|
||||
}
|
||||
if n > 0 {
|
||||
hash.Write(buf[:n])
|
||||
}
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%x", hash.Sum(nil)), nil
|
||||
}
|
||||
|
||||
// HmacMd5 return the hmac hash of string use md5
|
||||
func HmacMd5(data, key string) string {
|
||||
// HmacMd5 return the hmac hash of string use md5.
|
||||
// Play: https://go.dev/play/p/uef0q1fz53I
|
||||
func HmacMd5(str, key string) string {
|
||||
h := hmac.New(md5.New, []byte(key))
|
||||
h.Write([]byte(str))
|
||||
return hex.EncodeToString(h.Sum([]byte("")))
|
||||
}
|
||||
|
||||
// HmacMd5WithBase64 return the hmac hash of string use md5 with base64.
|
||||
// https://go.dev/play/p/UY0ng2AefFC
|
||||
func HmacMd5WithBase64(data, key string) string {
|
||||
h := hmac.New(md5.New, []byte(key))
|
||||
h.Write([]byte(data))
|
||||
return hex.EncodeToString(h.Sum([]byte("")))
|
||||
return base64.StdEncoding.EncodeToString(h.Sum([]byte("")))
|
||||
}
|
||||
|
||||
// HmacSha1 return the hmac hash of string use sha1
|
||||
func HmacSha1(data, key string) string {
|
||||
// HmacSha1 return the hmac hash of string use sha1.
|
||||
// Play: https://go.dev/play/p/1UI4oQ4WXKM
|
||||
func HmacSha1(str, key string) string {
|
||||
h := hmac.New(sha1.New, []byte(key))
|
||||
h.Write([]byte(data))
|
||||
h.Write([]byte(str))
|
||||
return hex.EncodeToString(h.Sum([]byte("")))
|
||||
}
|
||||
|
||||
// HmacSha256 return the hmac hash of string use sha256
|
||||
func HmacSha256(data, key string) string {
|
||||
// HmacSha1WithBase64 return the hmac hash of string use sha1 with base64.
|
||||
// Play: https://go.dev/play/p/47JmmGrnF7B
|
||||
func HmacSha1WithBase64(str, key string) string {
|
||||
h := hmac.New(sha1.New, []byte(key))
|
||||
h.Write([]byte(str))
|
||||
return base64.StdEncoding.EncodeToString(h.Sum([]byte("")))
|
||||
}
|
||||
|
||||
// HmacSha256 return the hmac hash of string use sha256.
|
||||
// Play: https://go.dev/play/p/HhpwXxFhhC0
|
||||
func HmacSha256(str, key string) string {
|
||||
h := hmac.New(sha256.New, []byte(key))
|
||||
h.Write([]byte(data))
|
||||
h.Write([]byte(str))
|
||||
return hex.EncodeToString(h.Sum([]byte("")))
|
||||
}
|
||||
|
||||
// HmacSha512 return the hmac hash of string use sha512
|
||||
func HmacSha512(data, key string) string {
|
||||
// HmacSha256WithBase64 return the hmac hash of string use sha256 with base64.
|
||||
// Play: https://go.dev/play/p/EKbkUvPTLwO
|
||||
func HmacSha256WithBase64(str, key string) string {
|
||||
h := hmac.New(sha256.New, []byte(key))
|
||||
h.Write([]byte(str))
|
||||
return base64.StdEncoding.EncodeToString(h.Sum([]byte("")))
|
||||
}
|
||||
|
||||
// HmacSha512 return the hmac hash of string use sha512.
|
||||
// Play: https://go.dev/play/p/59Od6m4A0Ud
|
||||
func HmacSha512(str, key string) string {
|
||||
h := hmac.New(sha512.New, []byte(key))
|
||||
h.Write([]byte(data))
|
||||
h.Write([]byte(str))
|
||||
return hex.EncodeToString(h.Sum([]byte("")))
|
||||
}
|
||||
|
||||
// Sha1 return the sha1 value (SHA-1 hash algorithm) of string
|
||||
func Sha1(data string) string {
|
||||
// HmacSha512WithBase64 return the hmac hash of string use sha512 with base64.
|
||||
// Play: https://go.dev/play/p/c6dSe3E2ydU
|
||||
func HmacSha512WithBase64(str, key string) string {
|
||||
h := hmac.New(sha512.New, []byte(key))
|
||||
h.Write([]byte(str))
|
||||
return base64.StdEncoding.EncodeToString(h.Sum([]byte("")))
|
||||
}
|
||||
|
||||
// Sha1 return the sha1 value (SHA-1 hash algorithm) of string.
|
||||
// Play: https://go.dev/play/p/_m_uoD1deMT
|
||||
func Sha1(str string) string {
|
||||
sha1 := sha1.New()
|
||||
sha1.Write([]byte(data))
|
||||
sha1.Write([]byte(str))
|
||||
return hex.EncodeToString(sha1.Sum([]byte("")))
|
||||
}
|
||||
|
||||
// Sha256 return the sha256 value (SHA256 hash algorithm) of string
|
||||
func Sha256(data string) string {
|
||||
// Sha1WithBase64 return the sha1 value (SHA-1 hash algorithm) of base64 string.
|
||||
// Play: https://go.dev/play/p/fSyx-Gl2l2-
|
||||
func Sha1WithBase64(str string) string {
|
||||
sha1 := sha1.New()
|
||||
sha1.Write([]byte(str))
|
||||
return base64.StdEncoding.EncodeToString(sha1.Sum([]byte("")))
|
||||
}
|
||||
|
||||
// Sha256 return the sha256 value (SHA256 hash algorithm) of string.
|
||||
// Play: https://go.dev/play/p/tU9tfBMIAr1
|
||||
func Sha256(str string) string {
|
||||
sha256 := sha256.New()
|
||||
sha256.Write([]byte(data))
|
||||
sha256.Write([]byte(str))
|
||||
return hex.EncodeToString(sha256.Sum([]byte("")))
|
||||
}
|
||||
|
||||
// Sha512 return the sha512 value (SHA512 hash algorithm) of string
|
||||
func Sha512(data string) string {
|
||||
// Sha256WithBase64 return the sha256 value (SHA256 hash algorithm) of base64 string.
|
||||
// Play: https://go.dev/play/p/85IXJHIal1k
|
||||
func Sha256WithBase64(str string) string {
|
||||
sha256 := sha256.New()
|
||||
sha256.Write([]byte(str))
|
||||
return base64.StdEncoding.EncodeToString(sha256.Sum([]byte("")))
|
||||
}
|
||||
|
||||
// Sha512 return the sha512 value (SHA512 hash algorithm) of string.
|
||||
// Play: https://go.dev/play/p/3WsvLYZxsHa
|
||||
func Sha512(str string) string {
|
||||
sha512 := sha512.New()
|
||||
sha512.Write([]byte(data))
|
||||
sha512.Write([]byte(str))
|
||||
return hex.EncodeToString(sha512.Sum([]byte("")))
|
||||
}
|
||||
|
||||
// Sha512WithBase64 return the sha512 value (SHA512 hash algorithm) of base64 string.
|
||||
// Play: https://go.dev/play/p/q_fY2rA-k5I
|
||||
func Sha512WithBase64(str string) string {
|
||||
sha512 := sha512.New()
|
||||
sha512.Write([]byte(str))
|
||||
return base64.StdEncoding.EncodeToString(sha512.Sum([]byte("")))
|
||||
}
|
||||
|
||||
@@ -7,21 +7,51 @@ import (
|
||||
)
|
||||
|
||||
func TestBase64StdEncode(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestBase64StdEncode")
|
||||
assert.Equal("aGVsbG8gd29ybGQ=", Base64StdEncode("hello world"))
|
||||
}
|
||||
|
||||
func TestBase64StdDecode(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestBase64StdDecode")
|
||||
assert.Equal("hello world", Base64StdDecode("aGVsbG8gd29ybGQ="))
|
||||
}
|
||||
|
||||
func TestMd5String(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestMd5String")
|
||||
assert.Equal("5d41402abc4b2a76b9719d911017c592", Md5String("hello"))
|
||||
}
|
||||
|
||||
func TestMd5StringWithBase64(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestMd5StringWithBase64")
|
||||
assert.Equal("XUFAKrxLKna5cZ2REBfFkg==", Md5StringWithBase64("hello"))
|
||||
}
|
||||
|
||||
func TestMd5Byte(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestMd5Byte")
|
||||
data := []byte{'a'}
|
||||
assert.Equal("0cc175b9c0f1b6a831c399e269772661", Md5Byte(data))
|
||||
}
|
||||
|
||||
func TestMd5ByteWithBase64(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestMd5ByteWithBase64")
|
||||
assert.Equal("XUFAKrxLKna5cZ2REBfFkg==", Md5ByteWithBase64([]byte("hello")))
|
||||
}
|
||||
|
||||
func TestMd5File(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fileMd5, err := Md5File("./basic.go")
|
||||
assert := internal.NewAssert(t, "TestMd5File")
|
||||
assert.IsNotNil(fileMd5)
|
||||
@@ -29,11 +59,22 @@ func TestMd5File(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestHmacMd5(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestHmacMd5")
|
||||
assert.Equal("5f4c9faaff0a1ad3007d9ddc06abe36d", HmacMd5("hello world", "12345"))
|
||||
}
|
||||
|
||||
func TestHmacMd5WithBase64(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestHmacMd5WithBase64")
|
||||
assert.Equal("6DQwbquJLYclJdSRinpjmg==", HmacMd5WithBase64("hello", "12345"))
|
||||
}
|
||||
|
||||
func TestHmacSha1(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := "hello world"
|
||||
key := "12345"
|
||||
hmacSha1 := HmacSha1(s, key)
|
||||
@@ -43,17 +84,45 @@ func TestHmacSha1(t *testing.T) {
|
||||
assert.Equal(expected, hmacSha1)
|
||||
}
|
||||
|
||||
func TestHmacSha256(t *testing.T) {
|
||||
s := "hello world"
|
||||
func TestHmacSha1WithBase64(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := "hello"
|
||||
key := "12345"
|
||||
hmacSha256 := HmacSha256(s, key)
|
||||
hmacSha1 := HmacSha1WithBase64(s, key)
|
||||
expected := "XGqdsMzLkuNu0DI/0Jt/k23prOA="
|
||||
|
||||
assert := internal.NewAssert(t, "TestHmacSha1")
|
||||
assert.Equal(expected, hmacSha1)
|
||||
}
|
||||
|
||||
func TestHmacSha256(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
str := "hello world"
|
||||
key := "12345"
|
||||
hmacSha256 := HmacSha256(str, key)
|
||||
expected := "9dce2609f2d67d41f74c7f9efc8ccd44370d41ad2de52982627588dfe7289ab8"
|
||||
|
||||
assert := internal.NewAssert(t, "TestHmacSha256")
|
||||
assert.Equal(expected, hmacSha256)
|
||||
}
|
||||
|
||||
func TestHmacSha256WithBase64(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
str := "hello"
|
||||
key := "12345"
|
||||
hms := HmacSha256WithBase64(str, key)
|
||||
expected := "MVu5PE6YmGK6Ccti4F1zpfN2yzbw14btqwwyDQWf3nU="
|
||||
|
||||
assert := internal.NewAssert(t, "TestHmacSha256WithBase64")
|
||||
assert.Equal(expected, hms)
|
||||
}
|
||||
|
||||
func TestHmacSha512(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := "hello world"
|
||||
key := "12345"
|
||||
hmacSha512 := HmacSha512(s, key)
|
||||
@@ -63,7 +132,21 @@ func TestHmacSha512(t *testing.T) {
|
||||
assert.Equal(expected, hmacSha512)
|
||||
}
|
||||
|
||||
func TestHmacSha512WithBase64(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
str := "hello"
|
||||
key := "12345"
|
||||
hms := HmacSha512WithBase64(str, key)
|
||||
expected := "3Y8SkKndI9NU4lJtmi6c6M///dN8syCADRxsE9Lvw2Mog3ahlsVFja9T+OGqa0Wm2FYwPVwKIGS/+XhYYdSM/A=="
|
||||
|
||||
assert := internal.NewAssert(t, "TestHmacSha512WithBase64")
|
||||
assert.Equal(expected, hms)
|
||||
}
|
||||
|
||||
func TestSha1(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := "hello world"
|
||||
sha1 := Sha1(s)
|
||||
expected := "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed"
|
||||
@@ -72,7 +155,19 @@ func TestSha1(t *testing.T) {
|
||||
assert.Equal(expected, sha1)
|
||||
}
|
||||
|
||||
func TestSha1WithBase64(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
str := Sha1WithBase64("hello")
|
||||
expected := "qvTGHdzF6KLavt4PO0gs2a6pQ00="
|
||||
|
||||
assert := internal.NewAssert(t, "TestSha1WithBase64")
|
||||
assert.Equal(expected, str)
|
||||
}
|
||||
|
||||
func TestSha256(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := "hello world"
|
||||
sha256 := Sha256(s)
|
||||
expected := "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
|
||||
@@ -81,7 +176,19 @@ func TestSha256(t *testing.T) {
|
||||
assert.Equal(expected, sha256)
|
||||
}
|
||||
|
||||
func TestSha256WithBase64(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
str := Sha256WithBase64("hello")
|
||||
expected := "LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ="
|
||||
|
||||
assert := internal.NewAssert(t, "TestSha256WithBase64")
|
||||
assert.Equal(expected, str)
|
||||
}
|
||||
|
||||
func TestSha512(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := "hello world"
|
||||
sha512 := Sha512(s)
|
||||
expected := "309ecc489c12d6eb4cc40f50c902f2b4d0ed77ee511a7c7a9bcd3ca86d4cd86f989dd35bc5ff499670da34255b45b0cfd830e81f605dcf7dc5542e93ae9cd76f"
|
||||
@@ -89,3 +196,13 @@ func TestSha512(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestSha512")
|
||||
assert.Equal(expected, sha512)
|
||||
}
|
||||
|
||||
func TestSha512WithBase64(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
str := Sha512WithBase64("hello")
|
||||
expected := "m3HSJL1i83hdltRq0+o9czGb+8KJDKra4t/3JRlnPKcjI8PZm6XBHXx6zG4UuMXaDEZjR1wuXDre9G9zvN7AQw=="
|
||||
|
||||
assert := internal.NewAssert(t, "TestSha512WithBase64")
|
||||
assert.Equal(expected, str)
|
||||
}
|
||||
|
||||
859
cryptor/crypto.go
Normal file
859
cryptor/crypto.go
Normal file
@@ -0,0 +1,859 @@
|
||||
// Copyright 2021 dudaodong@gmail.com. All rights reserved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
// Package cryptor implements some util functions to encrypt and decrypt.
|
||||
// Note:
|
||||
// 1. for aes crypt function, the `key` param length should be 16, 24 or 32. if not, will panic.
|
||||
package cryptor
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/des"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// AesEcbEncrypt encrypt data with key use AES ECB algorithm
|
||||
// len(key) should be 16, 24 or 32.
|
||||
// Play: https://go.dev/play/p/jT5irszHx-j
|
||||
func AesEcbEncrypt(data, key []byte) []byte {
|
||||
if !isAesKeyLengthValid(len(key)) {
|
||||
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||
}
|
||||
|
||||
blockSize := aes.BlockSize
|
||||
dataLen := len(data)
|
||||
padding := blockSize - (dataLen % blockSize)
|
||||
paddedLen := dataLen + padding
|
||||
|
||||
paddedData := make([]byte, paddedLen)
|
||||
copy(paddedData, data)
|
||||
|
||||
for i := dataLen; i < paddedLen; i++ {
|
||||
paddedData[i] = byte(padding)
|
||||
}
|
||||
|
||||
cipher, err := aes.NewCipher(generateAesKey(key, len(key)))
|
||||
if err != nil {
|
||||
panic("aes: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
encrypted := make([]byte, paddedLen)
|
||||
for bs := 0; bs < paddedLen; bs += blockSize {
|
||||
cipher.Encrypt(encrypted[bs:], paddedData[bs:])
|
||||
}
|
||||
|
||||
return encrypted
|
||||
}
|
||||
|
||||
// AesEcbDecrypt decrypt data with key use AES ECB algorithm
|
||||
// len(key) should be 16, 24 or 32.
|
||||
// Play: https://go.dev/play/p/jT5irszHx-j
|
||||
func AesEcbDecrypt(encrypted, key []byte) []byte {
|
||||
if !isAesKeyLengthValid(len(key)) {
|
||||
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||
}
|
||||
|
||||
blockSize := aes.BlockSize
|
||||
if len(encrypted)%blockSize != 0 {
|
||||
panic("aes: encrypted data length is not a multiple of block size")
|
||||
}
|
||||
|
||||
cipher, err := aes.NewCipher(generateAesKey(key, len(key)))
|
||||
if err != nil {
|
||||
panic("aes: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
decrypted := make([]byte, len(encrypted))
|
||||
for i := 0; i < len(encrypted); i += blockSize {
|
||||
cipher.Decrypt(decrypted[i:], encrypted[i:])
|
||||
}
|
||||
|
||||
if len(decrypted) == 0 {
|
||||
return nil
|
||||
}
|
||||
padding := int(decrypted[len(decrypted)-1])
|
||||
if padding == 0 || padding > blockSize {
|
||||
panic("aes: invalid PKCS#7 padding")
|
||||
}
|
||||
for i := len(decrypted) - padding; i < len(decrypted); i++ {
|
||||
if decrypted[i] != byte(padding) {
|
||||
panic("aes: invalid PKCS#7 padding content")
|
||||
}
|
||||
}
|
||||
|
||||
return decrypted[:len(decrypted)-padding]
|
||||
}
|
||||
|
||||
// AesCbcEncrypt encrypt data with key use AES CBC algorithm
|
||||
// len(key) should be 16, 24 or 32.
|
||||
// Play: https://go.dev/play/p/IOq_g8_lKZD
|
||||
func AesCbcEncrypt(data, key []byte) []byte {
|
||||
if !isAesKeyLengthValid(len(key)) {
|
||||
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic("aes: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
padding := aes.BlockSize - len(data)%aes.BlockSize
|
||||
padded := append(data, bytes.Repeat([]byte{byte(padding)}, padding)...)
|
||||
|
||||
iv := make([]byte, aes.BlockSize)
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
panic("aes: failed to generate IV: " + err.Error())
|
||||
}
|
||||
|
||||
encrypted := make([]byte, len(padded))
|
||||
mode := cipher.NewCBCEncrypter(block, iv)
|
||||
mode.CryptBlocks(encrypted, padded)
|
||||
|
||||
return append(iv, encrypted...)
|
||||
}
|
||||
|
||||
// AesCbcDecrypt decrypt data with key use AES CBC algorithm
|
||||
// len(key) should be 16, 24 or 32.
|
||||
// Play: https://go.dev/play/p/IOq_g8_lKZD
|
||||
func AesCbcDecrypt(encrypted, key []byte) []byte {
|
||||
if !isAesKeyLengthValid(len(key)) {
|
||||
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||
}
|
||||
|
||||
if len(encrypted) < aes.BlockSize {
|
||||
panic("aes: ciphertext too short")
|
||||
}
|
||||
|
||||
if len(encrypted)%aes.BlockSize != 0 {
|
||||
panic("aes: ciphertext is not a multiple of the block size")
|
||||
}
|
||||
|
||||
iv := encrypted[:aes.BlockSize]
|
||||
ciphertext := encrypted[aes.BlockSize:]
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic("aes: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
decrypted := make([]byte, len(ciphertext))
|
||||
mode := cipher.NewCBCDecrypter(block, iv)
|
||||
mode.CryptBlocks(decrypted, ciphertext)
|
||||
|
||||
return pkcs7UnPadding(decrypted)
|
||||
}
|
||||
|
||||
// AesCtrCrypt encrypt data with key use AES CTR algorithm
|
||||
// len(key) should be 16, 24 or 32.
|
||||
// Play: https://go.dev/play/p/SpaZO0-5Nsp
|
||||
// deprecated: use AesCtrEncrypt and AesCtrDecrypt instead.
|
||||
func AesCtrCrypt(data, key []byte) []byte {
|
||||
if !isAesKeyLengthValid(len(key)) {
|
||||
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||
}
|
||||
|
||||
block, _ := aes.NewCipher(key)
|
||||
|
||||
iv := bytes.Repeat([]byte("1"), block.BlockSize())
|
||||
stream := cipher.NewCTR(block, iv)
|
||||
|
||||
dst := make([]byte, len(data))
|
||||
stream.XORKeyStream(dst, data)
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
// AesCtrEncrypt encrypt data with key use AES CTR algorithm
|
||||
// len(key) should be 16, 24 or 32.
|
||||
// Play: https://go.dev/play/p/x6pjPAvThRz
|
||||
func AesCtrEncrypt(data, key []byte) []byte {
|
||||
if !isAesKeyLengthValid(len(key)) {
|
||||
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic("aes: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
iv := make([]byte, aes.BlockSize)
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
panic("aes: failed to generate IV: " + err.Error())
|
||||
}
|
||||
|
||||
stream := cipher.NewCTR(block, iv)
|
||||
ciphertext := make([]byte, len(data))
|
||||
stream.XORKeyStream(ciphertext, data)
|
||||
|
||||
return append(iv, ciphertext...)
|
||||
}
|
||||
|
||||
// AesCtrDecrypt decrypt data with key use AES CTR algorithm
|
||||
// len(key) should be 16, 24 or 32.
|
||||
// Play: https://go.dev/play/p/x6pjPAvThRz
|
||||
func AesCtrDecrypt(encrypted, key []byte) []byte {
|
||||
if !isAesKeyLengthValid(len(key)) {
|
||||
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||
}
|
||||
if len(encrypted) < aes.BlockSize {
|
||||
panic("aes: invalid ciphertext length")
|
||||
}
|
||||
|
||||
iv := encrypted[:aes.BlockSize]
|
||||
ciphertext := encrypted[aes.BlockSize:]
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic("aes: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
stream := cipher.NewCTR(block, iv)
|
||||
plaintext := make([]byte, len(ciphertext))
|
||||
stream.XORKeyStream(plaintext, ciphertext)
|
||||
|
||||
return plaintext
|
||||
}
|
||||
|
||||
// AesCfbEncrypt encrypt data with key use AES CFB algorithm
|
||||
// len(key) should be 16, 24 or 32.
|
||||
// Play: https://go.dev/play/p/tfkF10B13kH
|
||||
func AesCfbEncrypt(data, key []byte) []byte {
|
||||
if !isAesKeyLengthValid(len(key)) {
|
||||
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic("aes: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
iv := make([]byte, aes.BlockSize)
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
panic("aes: failed to generate IV: " + err.Error())
|
||||
}
|
||||
|
||||
ciphertext := make([]byte, len(data))
|
||||
stream := cipher.NewCFBEncrypter(block, iv)
|
||||
stream.XORKeyStream(ciphertext, data)
|
||||
|
||||
return append(iv, ciphertext...)
|
||||
}
|
||||
|
||||
// AesCfbDecrypt decrypt data with key use AES CFB algorithm
|
||||
// len(encrypted) should be great than 16, len(key) should be 16, 24 or 32.
|
||||
// Play: https://go.dev/play/p/tfkF10B13kH
|
||||
func AesCfbDecrypt(encrypted, key []byte) []byte {
|
||||
if !isAesKeyLengthValid(len(key)) {
|
||||
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||
}
|
||||
|
||||
if len(encrypted) < aes.BlockSize {
|
||||
panic("aes: encrypted data too short")
|
||||
}
|
||||
|
||||
iv := encrypted[:aes.BlockSize]
|
||||
ciphertext := encrypted[aes.BlockSize:]
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic("aes: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
plaintext := make([]byte, len(ciphertext))
|
||||
stream := cipher.NewCFBDecrypter(block, iv)
|
||||
stream.XORKeyStream(plaintext, ciphertext)
|
||||
|
||||
return plaintext
|
||||
}
|
||||
|
||||
// AesOfbEncrypt encrypt data with key use AES OFB algorithm
|
||||
// len(key) should be 16, 24 or 32.
|
||||
// Play: https://go.dev/play/p/VtHxtkUj-3F
|
||||
func AesOfbEncrypt(data, key []byte) []byte {
|
||||
if !isAesKeyLengthValid(len(key)) {
|
||||
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic("aes: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
iv := make([]byte, aes.BlockSize)
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
panic("aes: failed to generate IV: " + err.Error())
|
||||
}
|
||||
|
||||
ciphertext := make([]byte, len(data))
|
||||
stream := cipher.NewOFB(block, iv)
|
||||
stream.XORKeyStream(ciphertext, data)
|
||||
|
||||
return append(iv, ciphertext...)
|
||||
}
|
||||
|
||||
// AesOfbDecrypt decrypt data with key use AES OFB algorithm
|
||||
// len(key) should be 16, 24 or 32.
|
||||
// Play: https://go.dev/play/p/VtHxtkUj-3F
|
||||
func AesOfbDecrypt(data, key []byte) []byte {
|
||||
if !isAesKeyLengthValid(len(key)) {
|
||||
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||
}
|
||||
|
||||
if len(data) < aes.BlockSize {
|
||||
panic("aes: encrypted data too short")
|
||||
}
|
||||
|
||||
iv := data[:aes.BlockSize]
|
||||
ciphertext := data[aes.BlockSize:]
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic("aes: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
plaintext := make([]byte, len(ciphertext))
|
||||
stream := cipher.NewOFB(block, iv)
|
||||
stream.XORKeyStream(plaintext, ciphertext)
|
||||
|
||||
return plaintext
|
||||
}
|
||||
|
||||
// AesGcmEncrypt encrypt data with key use AES GCM algorithm
|
||||
// Play: https://go.dev/play/p/rUt0-DmsPCs
|
||||
func AesGcmEncrypt(data, key []byte) []byte {
|
||||
if !isAesKeyLengthValid(len(key)) {
|
||||
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic("aes: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
panic("aes: failed to create GCM: " + err.Error())
|
||||
}
|
||||
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
panic("aes: failed to generate nonce: " + err.Error())
|
||||
}
|
||||
|
||||
ciphertext := gcm.Seal(nil, nonce, data, nil)
|
||||
|
||||
return append(nonce, ciphertext...)
|
||||
}
|
||||
|
||||
// AesGcmDecrypt decrypt data with key use AES GCM algorithm
|
||||
// Play: https://go.dev/play/p/rUt0-DmsPCs
|
||||
func AesGcmDecrypt(data, key []byte) []byte {
|
||||
if !isAesKeyLengthValid(len(key)) {
|
||||
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic("aes: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
panic("aes: failed to create GCM: " + err.Error())
|
||||
}
|
||||
|
||||
nonceSize := gcm.NonceSize()
|
||||
if len(data) < nonceSize {
|
||||
panic("aes: ciphertext too short")
|
||||
}
|
||||
|
||||
nonce, ciphertext := data[:nonceSize], data[nonceSize:]
|
||||
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
|
||||
if err != nil {
|
||||
panic("aes: decryption failed: " + err.Error())
|
||||
}
|
||||
|
||||
return plaintext
|
||||
}
|
||||
|
||||
// DesEcbEncrypt encrypt data with key use DES ECB algorithm
|
||||
// len(key) should be 8.
|
||||
// Play: https://go.dev/play/p/8qivmPeZy4P
|
||||
func DesEcbEncrypt(data, key []byte) []byte {
|
||||
cipher, err := des.NewCipher(generateDesKey(key))
|
||||
if err != nil {
|
||||
panic("des: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
blockSize := cipher.BlockSize()
|
||||
padded := pkcs5Padding(data, blockSize)
|
||||
encrypted := make([]byte, len(padded))
|
||||
|
||||
for i := 0; i < len(padded); i += blockSize {
|
||||
cipher.Encrypt(encrypted[i:], padded[i:])
|
||||
}
|
||||
|
||||
return encrypted
|
||||
}
|
||||
|
||||
// DesEcbDecrypt decrypt data with key use DES ECB algorithm
|
||||
// len(key) should be 8.
|
||||
// Play: https://go.dev/play/p/8qivmPeZy4P
|
||||
func DesEcbDecrypt(encrypted, key []byte) []byte {
|
||||
cipher, err := des.NewCipher(generateDesKey(key))
|
||||
if err != nil {
|
||||
panic("des: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
blockSize := cipher.BlockSize()
|
||||
if len(encrypted)%blockSize != 0 {
|
||||
panic("des: invalid encrypted data length")
|
||||
}
|
||||
|
||||
decrypted := make([]byte, len(encrypted))
|
||||
for i := 0; i < len(encrypted); i += blockSize {
|
||||
cipher.Decrypt(decrypted[i:], encrypted[i:])
|
||||
}
|
||||
|
||||
// Remove padding
|
||||
return pkcs5UnPadding(decrypted)
|
||||
}
|
||||
|
||||
// DesCbcEncrypt encrypt data with key use DES CBC algorithm
|
||||
// len(key) should be 8.
|
||||
// Play: https://go.dev/play/p/4cC4QvWfe3_1
|
||||
func DesCbcEncrypt(data, key []byte) []byte {
|
||||
if len(key) != 8 {
|
||||
panic("des: key length must be 8 bytes")
|
||||
}
|
||||
|
||||
block, err := des.NewCipher(key)
|
||||
if err != nil {
|
||||
panic("des: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
blockSize := block.BlockSize()
|
||||
data = pkcs7Padding(data, blockSize)
|
||||
|
||||
encrypted := make([]byte, blockSize+len(data))
|
||||
iv := encrypted[:blockSize]
|
||||
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
panic("des: failed to generate IV: " + err.Error())
|
||||
}
|
||||
|
||||
mode := cipher.NewCBCEncrypter(block, iv)
|
||||
mode.CryptBlocks(encrypted[blockSize:], data)
|
||||
|
||||
return encrypted
|
||||
}
|
||||
|
||||
// DesCbcDecrypt decrypt data with key use DES CBC algorithm
|
||||
// len(key) should be 8.
|
||||
// Play: https://go.dev/play/p/4cC4QvWfe3_1
|
||||
func DesCbcDecrypt(encrypted, key []byte) []byte {
|
||||
if len(key) != 8 {
|
||||
panic("des: key length must be 8 bytes")
|
||||
}
|
||||
|
||||
block, err := des.NewCipher(key)
|
||||
if err != nil {
|
||||
panic("des: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
blockSize := block.BlockSize()
|
||||
if len(encrypted) < blockSize || len(encrypted)%blockSize != 0 {
|
||||
panic("des: invalid encrypted data length")
|
||||
}
|
||||
|
||||
iv := encrypted[:blockSize]
|
||||
ciphertext := encrypted[blockSize:]
|
||||
|
||||
mode := cipher.NewCBCDecrypter(block, iv)
|
||||
mode.CryptBlocks(ciphertext, ciphertext)
|
||||
|
||||
return pkcs7UnPadding(ciphertext)
|
||||
}
|
||||
|
||||
// DesCtrCrypt encrypt data with key use DES CTR algorithm
|
||||
// len(key) should be 8.
|
||||
// Play: https://go.dev/play/p/9-T6OjKpcdw
|
||||
// deprecated: use DesCtrEncrypt and DesCtrDecrypt instead.
|
||||
func DesCtrCrypt(data, key []byte) []byte {
|
||||
size := len(key)
|
||||
if size != 8 {
|
||||
panic("key length shoud be 8")
|
||||
}
|
||||
|
||||
block, _ := des.NewCipher(key)
|
||||
|
||||
iv := bytes.Repeat([]byte("1"), block.BlockSize())
|
||||
stream := cipher.NewCTR(block, iv)
|
||||
|
||||
dst := make([]byte, len(data))
|
||||
stream.XORKeyStream(dst, data)
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
// DesCtrEncrypt encrypt data with key use DES CTR algorithm
|
||||
// len(key) should be 8.
|
||||
// Play: https://go.dev/play/p/S6p_WHCgH1d
|
||||
func DesCtrEncrypt(data, key []byte) []byte {
|
||||
if len(key) != 8 {
|
||||
panic("des: key length must be 8 bytes")
|
||||
}
|
||||
|
||||
block, err := des.NewCipher(key)
|
||||
if err != nil {
|
||||
panic("des: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
iv := make([]byte, block.BlockSize())
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
panic("des: failed to generate IV: " + err.Error())
|
||||
}
|
||||
|
||||
stream := cipher.NewCTR(block, iv)
|
||||
|
||||
encrypted := make([]byte, len(data))
|
||||
stream.XORKeyStream(encrypted, data)
|
||||
|
||||
// 返回前缀包含 IV,便于解密
|
||||
return append(iv, encrypted...)
|
||||
}
|
||||
|
||||
// DesCtrDecrypt decrypt data with key use DES CTR algorithm
|
||||
// len(key) should be 8.
|
||||
// Play: https://go.dev/play/p/S6p_WHCgH1d
|
||||
func DesCtrDecrypt(encrypted, key []byte) []byte {
|
||||
if len(key) != 8 {
|
||||
panic("des: key length must be 8 bytes")
|
||||
}
|
||||
|
||||
block, err := des.NewCipher(key)
|
||||
if err != nil {
|
||||
panic("des: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
blockSize := block.BlockSize()
|
||||
if len(encrypted) < blockSize {
|
||||
panic("des: ciphertext too short")
|
||||
}
|
||||
|
||||
iv := encrypted[:blockSize]
|
||||
ciphertext := encrypted[blockSize:]
|
||||
|
||||
stream := cipher.NewCTR(block, iv)
|
||||
|
||||
decrypted := make([]byte, len(ciphertext))
|
||||
stream.XORKeyStream(decrypted, ciphertext)
|
||||
|
||||
return decrypted
|
||||
}
|
||||
|
||||
// DesCfbEncrypt encrypt data with key use DES CFB algorithm
|
||||
// len(key) should be 8.
|
||||
// Play: https://go.dev/play/p/y-eNxcFBlxL
|
||||
func DesCfbEncrypt(data, key []byte) []byte {
|
||||
if len(key) != 8 {
|
||||
panic("des: key length must be 8 bytes")
|
||||
}
|
||||
|
||||
block, err := des.NewCipher(key)
|
||||
if err != nil {
|
||||
panic("des: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
iv := make([]byte, des.BlockSize)
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
panic("des: failed to generate IV: " + err.Error())
|
||||
}
|
||||
|
||||
encrypted := make([]byte, des.BlockSize+len(data))
|
||||
|
||||
copy(encrypted[:des.BlockSize], iv)
|
||||
|
||||
stream := cipher.NewCFBEncrypter(block, iv)
|
||||
stream.XORKeyStream(encrypted[des.BlockSize:], data)
|
||||
|
||||
return encrypted
|
||||
}
|
||||
|
||||
// DesCfbDecrypt decrypt data with key use DES CFB algorithm
|
||||
// len(encrypted) should be great than 16, len(key) should be 8.
|
||||
// Play: https://go.dev/play/p/y-eNxcFBlxL
|
||||
func DesCfbDecrypt(encrypted, key []byte) []byte {
|
||||
if len(key) != 8 {
|
||||
panic("des: key length must be 8 bytes")
|
||||
}
|
||||
|
||||
block, err := des.NewCipher(key)
|
||||
if err != nil {
|
||||
panic("des: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
if len(encrypted) < des.BlockSize {
|
||||
panic("des: encrypted data too short")
|
||||
}
|
||||
|
||||
iv := encrypted[:des.BlockSize]
|
||||
ciphertext := encrypted[des.BlockSize:]
|
||||
|
||||
stream := cipher.NewCFBDecrypter(block, iv)
|
||||
stream.XORKeyStream(ciphertext, ciphertext)
|
||||
|
||||
return ciphertext
|
||||
}
|
||||
|
||||
// DesOfbEncrypt encrypt data with key use DES OFB algorithm
|
||||
// len(key) should be 8.
|
||||
// Play: https://go.dev/play/p/74KmNadjN1J
|
||||
func DesOfbEncrypt(data, key []byte) []byte {
|
||||
if len(key) != 8 {
|
||||
panic("des: key length must be 8 bytes")
|
||||
}
|
||||
|
||||
block, err := des.NewCipher(key)
|
||||
if err != nil {
|
||||
panic("des: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
data = pkcs7Padding(data, des.BlockSize)
|
||||
|
||||
iv := make([]byte, des.BlockSize)
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
panic("des: failed to generate IV: " + err.Error())
|
||||
}
|
||||
|
||||
encrypted := make([]byte, des.BlockSize+len(data))
|
||||
copy(encrypted[:des.BlockSize], iv)
|
||||
|
||||
stream := cipher.NewOFB(block, iv)
|
||||
stream.XORKeyStream(encrypted[des.BlockSize:], data)
|
||||
|
||||
return encrypted
|
||||
}
|
||||
|
||||
// DesOfbDecrypt decrypt data with key use DES OFB algorithm
|
||||
// len(key) should be 8.
|
||||
// Play: https://go.dev/play/p/74KmNadjN1J
|
||||
func DesOfbDecrypt(data, key []byte) []byte {
|
||||
if len(key) != 8 {
|
||||
panic("des: key length must be 8 bytes")
|
||||
}
|
||||
|
||||
block, err := des.NewCipher(key)
|
||||
if err != nil {
|
||||
panic("des: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
if len(data) < des.BlockSize {
|
||||
panic("des: encrypted data too short")
|
||||
}
|
||||
|
||||
iv := data[:des.BlockSize]
|
||||
ciphertext := data[des.BlockSize:]
|
||||
|
||||
stream := cipher.NewOFB(block, iv)
|
||||
decrypted := make([]byte, len(ciphertext))
|
||||
stream.XORKeyStream(decrypted, ciphertext)
|
||||
|
||||
decrypted = pkcs7UnPadding(decrypted)
|
||||
|
||||
return decrypted
|
||||
}
|
||||
|
||||
// GenerateRsaKey create rsa private and public pemo file.
|
||||
// Play: https://go.dev/play/p/zutRHrDqs0X
|
||||
func GenerateRsaKey(keySize int, priKeyFile, pubKeyFile string) error {
|
||||
// private key
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, keySize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
derText := x509.MarshalPKCS1PrivateKey(privateKey)
|
||||
|
||||
block := pem.Block{
|
||||
Type: "rsa private key",
|
||||
Bytes: derText,
|
||||
}
|
||||
|
||||
file, err := os.Create(priKeyFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = pem.Encode(file, &block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file.Close()
|
||||
|
||||
// public key
|
||||
publicKey := privateKey.PublicKey
|
||||
|
||||
derpText, err := x509.MarshalPKIXPublicKey(&publicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
block = pem.Block{
|
||||
Type: "rsa public key",
|
||||
Bytes: derpText,
|
||||
}
|
||||
|
||||
file, err = os.Create(pubKeyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = pem.Encode(file, &block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RsaEncrypt encrypt data with ras algorithm.
|
||||
// Play: https://go.dev/play/p/7_zo6mrx-eX
|
||||
func RsaEncrypt(data []byte, pubKeyFileName string) []byte {
|
||||
file, err := os.Open(pubKeyFileName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fileInfo, err := file.Stat()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer file.Close()
|
||||
buf := make([]byte, fileInfo.Size())
|
||||
|
||||
_, err = file.Read(buf)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(buf)
|
||||
|
||||
pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pubKey := pubInterface.(*rsa.PublicKey)
|
||||
|
||||
cipherText, err := rsa.EncryptPKCS1v15(rand.Reader, pubKey, data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return cipherText
|
||||
}
|
||||
|
||||
// RsaDecrypt decrypt data with ras algorithm.
|
||||
// Play: https://go.dev/play/p/7_zo6mrx-eX
|
||||
func RsaDecrypt(data []byte, privateKeyFileName string) []byte {
|
||||
file, err := os.Open(privateKeyFileName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fileInfo, err := file.Stat()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
buf := make([]byte, fileInfo.Size())
|
||||
defer file.Close()
|
||||
|
||||
_, err = file.Read(buf)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(buf)
|
||||
|
||||
priKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
plainText, err := rsa.DecryptPKCS1v15(rand.Reader, priKey, data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return plainText
|
||||
}
|
||||
|
||||
// GenerateRsaKeyPair create rsa private and public key.
|
||||
// Play: https://go.dev/play/p/sSVmkfENKMz
|
||||
func GenerateRsaKeyPair(keySize int) (*rsa.PrivateKey, *rsa.PublicKey) {
|
||||
privateKey, _ := rsa.GenerateKey(rand.Reader, keySize)
|
||||
return privateKey, &privateKey.PublicKey
|
||||
}
|
||||
|
||||
// RsaEncryptOAEP encrypts the given data with RSA-OAEP.
|
||||
// Play: https://go.dev/play/p/sSVmkfENKMz
|
||||
func RsaEncryptOAEP(data []byte, label []byte, key rsa.PublicKey) ([]byte, error) {
|
||||
encryptedBytes, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, &key, data, label)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return encryptedBytes, nil
|
||||
}
|
||||
|
||||
// RsaDecryptOAEP decrypts the data with RSA-OAEP.
|
||||
// Play: https://go.dev/play/p/sSVmkfENKMz
|
||||
func RsaDecryptOAEP(ciphertext []byte, label []byte, key rsa.PrivateKey) ([]byte, error) {
|
||||
decryptedBytes, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, &key, ciphertext, label)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return decryptedBytes, nil
|
||||
}
|
||||
|
||||
// RsaSign signs the data with RSA.
|
||||
// Play: https://go.dev/play/p/qhsbf8BJ6Mf
|
||||
func RsaSign(hash crypto.Hash, data []byte, privateKeyFileName string) ([]byte, error) {
|
||||
privateKey, err := loadRasPrivateKey(privateKeyFileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hashed, err := hashData(hash, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rsa.SignPKCS1v15(rand.Reader, privateKey, hash, hashed)
|
||||
}
|
||||
|
||||
// RsaVerifySign verifies the signature of the data with RSA.
|
||||
// Play: https://go.dev/play/p/qhsbf8BJ6Mf
|
||||
func RsaVerifySign(hash crypto.Hash, data, signature []byte, pubKeyFileName string) error {
|
||||
publicKey, err := loadRsaPublicKey(pubKeyFileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hashed, err := hashData(hash, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return rsa.VerifyPKCS1v15(publicKey, hash, hashed, signature)
|
||||
}
|
||||
624
cryptor/crypto_example_test.go
Normal file
624
cryptor/crypto_example_test.go
Normal file
@@ -0,0 +1,624 @@
|
||||
package cryptor
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func ExampleAesEcbEncrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
encrypted := AesEcbEncrypt([]byte(data), []byte(key))
|
||||
|
||||
decrypted := AesEcbDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleAesEcbDecrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
encrypted := AesEcbEncrypt([]byte(data), []byte(key))
|
||||
|
||||
decrypted := AesEcbDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleAesCbcEncrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
encrypted := AesCbcEncrypt([]byte(data), []byte(key))
|
||||
|
||||
decrypted := AesCbcDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleAesCbcDecrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
encrypted := AesCbcEncrypt([]byte(data), []byte(key))
|
||||
|
||||
decrypted := AesCbcDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleAesCtrCrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
encrypted := AesCtrCrypt([]byte(data), []byte(key))
|
||||
decrypted := AesCtrCrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleAesCtrEncrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
enCrypt := AesCtrEncrypt([]byte(data), []byte(key))
|
||||
deCrypt := AesCtrDecrypt(enCrypt, []byte(key))
|
||||
|
||||
fmt.Println(string(deCrypt))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleAesCtrDecrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
enCrypt := AesCtrEncrypt([]byte(data), []byte(key))
|
||||
deCrypt := AesCtrDecrypt(enCrypt, []byte(key))
|
||||
|
||||
fmt.Println(string(deCrypt))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleAesCfbEncrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
encrypted := AesCfbEncrypt([]byte(data), []byte(key))
|
||||
|
||||
decrypted := AesCfbDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleAesCfbDecrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
encrypted := AesCfbEncrypt([]byte(data), []byte(key))
|
||||
|
||||
decrypted := AesCfbDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleAesOfbEncrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
encrypted := AesOfbEncrypt([]byte(data), []byte(key))
|
||||
|
||||
decrypted := AesOfbDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleAesOfbDecrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
encrypted := AesOfbEncrypt([]byte(data), []byte(key))
|
||||
|
||||
decrypted := AesOfbDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// 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"
|
||||
|
||||
encrypted := DesEcbEncrypt([]byte(data), []byte(key))
|
||||
|
||||
decrypted := DesEcbDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleDesEcbDecrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefgh"
|
||||
|
||||
encrypted := DesEcbEncrypt([]byte(data), []byte(key))
|
||||
|
||||
decrypted := DesEcbDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleDesCbcEncrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefgh"
|
||||
|
||||
encrypted := DesCbcEncrypt([]byte(data), []byte(key))
|
||||
|
||||
decrypted := DesCbcDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleDesCbcDecrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefgh"
|
||||
|
||||
encrypted := DesCbcEncrypt([]byte(data), []byte(key))
|
||||
|
||||
decrypted := DesCbcDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleDesCtrCrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefgh"
|
||||
|
||||
encrypted := DesCtrCrypt([]byte(data), []byte(key))
|
||||
decrypted := DesCtrCrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleDesCtrDecrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefgh"
|
||||
|
||||
enCrypt := DesCtrEncrypt([]byte(data), []byte(key))
|
||||
deCrypt := DesCtrDecrypt(enCrypt, []byte(key))
|
||||
|
||||
fmt.Println(string(deCrypt))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleDesCfbEncrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefgh"
|
||||
|
||||
encrypted := DesCfbEncrypt([]byte(data), []byte(key))
|
||||
|
||||
decrypted := DesCfbDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleDesCfbDecrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefgh"
|
||||
|
||||
encrypted := DesCfbEncrypt([]byte(data), []byte(key))
|
||||
|
||||
decrypted := DesCfbDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleDesOfbEncrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefgh"
|
||||
|
||||
encrypted := DesOfbEncrypt([]byte(data), []byte(key))
|
||||
|
||||
decrypted := DesOfbDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleDesOfbDecrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefgh"
|
||||
|
||||
encrypted := DesOfbEncrypt([]byte(data), []byte(key))
|
||||
|
||||
decrypted := DesOfbDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleGenerateRsaKey() {
|
||||
// Create ras private and public pem file
|
||||
err := GenerateRsaKey(4096, "rsa_private_example.pem", "rsa_public_example.pem")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("foo")
|
||||
|
||||
// Output:
|
||||
// foo
|
||||
}
|
||||
|
||||
func ExampleRsaEncrypt() {
|
||||
// Create ras private and public pem file
|
||||
err := GenerateRsaKey(4096, "rsa_private_example.pem", "rsa_public_example.pem")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data := []byte("hello")
|
||||
encrypted := RsaEncrypt(data, "rsa_public_example.pem")
|
||||
decrypted := RsaDecrypt(encrypted, "rsa_private_example.pem")
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleRsaDecrypt() {
|
||||
// Create ras private and public pem file
|
||||
err := GenerateRsaKey(4096, "rsa_private_example.pem", "rsa_public_example.pem")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data := []byte("hello")
|
||||
encrypted := RsaEncrypt(data, "rsa_public_example.pem")
|
||||
decrypted := RsaDecrypt(encrypted, "rsa_private_example.pem")
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleBase64StdEncode() {
|
||||
base64Str := Base64StdEncode("hello")
|
||||
|
||||
fmt.Println(base64Str)
|
||||
|
||||
// Output:
|
||||
// aGVsbG8=
|
||||
}
|
||||
|
||||
func ExampleBase64StdDecode() {
|
||||
str := Base64StdDecode("aGVsbG8=")
|
||||
|
||||
fmt.Println(str)
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleHmacMd5() {
|
||||
str := "hello"
|
||||
key := "12345"
|
||||
|
||||
hms := HmacMd5(str, key)
|
||||
fmt.Println(hms)
|
||||
|
||||
// Output:
|
||||
// e834306eab892d872525d4918a7a639a
|
||||
}
|
||||
|
||||
func ExampleHmacMd5WithBase64() {
|
||||
str := "hello"
|
||||
key := "12345"
|
||||
|
||||
hms := HmacMd5WithBase64(str, key)
|
||||
fmt.Println(hms)
|
||||
|
||||
// Output:
|
||||
// 6DQwbquJLYclJdSRinpjmg==
|
||||
}
|
||||
|
||||
func ExampleHmacSha1() {
|
||||
str := "hello"
|
||||
key := "12345"
|
||||
|
||||
hms := HmacSha1(str, key)
|
||||
fmt.Println(hms)
|
||||
|
||||
// Output:
|
||||
// 5c6a9db0cccb92e36ed0323fd09b7f936de9ace0
|
||||
}
|
||||
|
||||
func ExampleHmacSha1WithBase64() {
|
||||
str := "hello"
|
||||
key := "12345"
|
||||
|
||||
hms := HmacSha1WithBase64(str, key)
|
||||
fmt.Println(hms)
|
||||
|
||||
// Output:
|
||||
// XGqdsMzLkuNu0DI/0Jt/k23prOA=
|
||||
}
|
||||
|
||||
func ExampleHmacSha256() {
|
||||
str := "hello"
|
||||
key := "12345"
|
||||
|
||||
hms := HmacSha256(str, key)
|
||||
fmt.Println(hms)
|
||||
|
||||
// Output:
|
||||
// 315bb93c4e989862ba09cb62e05d73a5f376cb36f0d786edab0c320d059fde75
|
||||
}
|
||||
|
||||
func ExampleHmacSha256WithBase64() {
|
||||
str := "hello"
|
||||
key := "12345"
|
||||
|
||||
hms := HmacSha256WithBase64(str, key)
|
||||
fmt.Println(hms)
|
||||
|
||||
// Output:
|
||||
// MVu5PE6YmGK6Ccti4F1zpfN2yzbw14btqwwyDQWf3nU=
|
||||
}
|
||||
|
||||
func ExampleHmacSha512() {
|
||||
str := "hello"
|
||||
key := "12345"
|
||||
|
||||
hms := HmacSha512(str, key)
|
||||
fmt.Println(hms)
|
||||
|
||||
// Output:
|
||||
// dd8f1290a9dd23d354e2526d9a2e9ce8cffffdd37cb320800d1c6c13d2efc363288376a196c5458daf53f8e1aa6b45a6d856303d5c0a2064bff9785861d48cfc
|
||||
}
|
||||
|
||||
func ExampleHmacSha512WithBase64() {
|
||||
str := "hello"
|
||||
key := "12345"
|
||||
|
||||
hms := HmacSha512WithBase64(str, key)
|
||||
fmt.Println(hms)
|
||||
|
||||
// Output:
|
||||
// 3Y8SkKndI9NU4lJtmi6c6M///dN8syCADRxsE9Lvw2Mog3ahlsVFja9T+OGqa0Wm2FYwPVwKIGS/+XhYYdSM/A==
|
||||
}
|
||||
|
||||
func ExampleMd5String() {
|
||||
md5Str := Md5String("hello")
|
||||
fmt.Println(md5Str)
|
||||
|
||||
// Output:
|
||||
// 5d41402abc4b2a76b9719d911017c592
|
||||
}
|
||||
|
||||
func ExampleMd5StringWithBase64() {
|
||||
md5Str := Md5StringWithBase64("hello")
|
||||
fmt.Println(md5Str)
|
||||
|
||||
// Output:
|
||||
// XUFAKrxLKna5cZ2REBfFkg==
|
||||
}
|
||||
|
||||
func ExampleMd5Byte() {
|
||||
md5Str := Md5Byte([]byte{'a'})
|
||||
fmt.Println(md5Str)
|
||||
|
||||
// Output:
|
||||
// 0cc175b9c0f1b6a831c399e269772661
|
||||
}
|
||||
|
||||
func ExampleMd5ByteWithBase64() {
|
||||
md5Str := Md5ByteWithBase64([]byte("hello"))
|
||||
fmt.Println(md5Str)
|
||||
|
||||
// Output:
|
||||
// XUFAKrxLKna5cZ2REBfFkg==
|
||||
}
|
||||
|
||||
func ExampleSha1() {
|
||||
result := Sha1("hello")
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
|
||||
}
|
||||
|
||||
func ExampleSha1WithBase64() {
|
||||
result := Sha1WithBase64("hello")
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// qvTGHdzF6KLavt4PO0gs2a6pQ00=
|
||||
}
|
||||
|
||||
func ExampleSha256() {
|
||||
result := Sha256("hello")
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
|
||||
}
|
||||
|
||||
func ExampleSha256WithBase64() {
|
||||
result := Sha256WithBase64("hello")
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=
|
||||
}
|
||||
|
||||
func ExampleSha512() {
|
||||
result := Sha512("hello")
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// 9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043
|
||||
}
|
||||
|
||||
func ExampleSha512WithBase64() {
|
||||
result := Sha512WithBase64("hello")
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// m3HSJL1i83hdltRq0+o9czGb+8KJDKra4t/3JRlnPKcjI8PZm6XBHXx6zG4UuMXaDEZjR1wuXDre9G9zvN7AQw==
|
||||
}
|
||||
|
||||
func ExampleRsaEncryptOAEP() {
|
||||
pri, pub := GenerateRsaKeyPair(1024)
|
||||
|
||||
data := []byte("hello world")
|
||||
label := []byte("123456")
|
||||
|
||||
encrypted, err := RsaEncryptOAEP(data, label, *pub)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
decrypted, err := RsaDecryptOAEP([]byte(encrypted), label, *pri)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello world
|
||||
}
|
||||
|
||||
func ExampleRsaSign() {
|
||||
data := []byte("This is a test data for RSA signing")
|
||||
hash := crypto.SHA256
|
||||
|
||||
privateKey := "./rsa_private_example.pem"
|
||||
publicKey := "./rsa_public_example.pem"
|
||||
|
||||
signature, err := RsaSign(hash, data, privateKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = RsaVerifySign(hash, data, signature, publicKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("ok")
|
||||
|
||||
// Output:
|
||||
// ok
|
||||
}
|
||||
|
||||
func ExampleRsaVerifySign() {
|
||||
data := []byte("This is a test data for RSA signing")
|
||||
hash := crypto.SHA256
|
||||
|
||||
privateKey := "./rsa_private_example.pem"
|
||||
publicKey := "./rsa_public_example.pem"
|
||||
|
||||
signature, err := RsaSign(hash, data, privateKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = RsaVerifySign(hash, data, signature, publicKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("ok")
|
||||
|
||||
// Output:
|
||||
// ok
|
||||
}
|
||||
@@ -1,12 +1,23 @@
|
||||
package cryptor
|
||||
|
||||
import "bytes"
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func generateAesKey(key []byte) []byte {
|
||||
genKey := make([]byte, 16)
|
||||
func generateAesKey(key []byte, size int) []byte {
|
||||
genKey := make([]byte, size)
|
||||
copy(genKey, key)
|
||||
for i := 16; i < len(key); {
|
||||
for j := 0; j < 16 && i < len(key); j, i = j+1, i+1 {
|
||||
for i := size; i < len(key); {
|
||||
for j := 0; j < size && i < len(key); j, i = j+1, i+1 {
|
||||
genKey[j] ^= key[i]
|
||||
}
|
||||
}
|
||||
@@ -35,3 +46,139 @@ func pkcs7UnPadding(src []byte) []byte {
|
||||
unPadding := int(src[length-1])
|
||||
return src[:(length - unPadding)]
|
||||
}
|
||||
|
||||
func pkcs5Padding(data []byte, blockSize int) []byte {
|
||||
padding := blockSize - len(data)%blockSize
|
||||
padText := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
return append(data, padText...)
|
||||
}
|
||||
|
||||
func pkcs5UnPadding(data []byte) []byte {
|
||||
length := len(data)
|
||||
if length == 0 {
|
||||
return nil
|
||||
}
|
||||
padLen := int(data[length-1])
|
||||
if padLen == 0 || padLen > length {
|
||||
return nil
|
||||
}
|
||||
return data[:length-padLen]
|
||||
}
|
||||
|
||||
func isAesKeyLengthValid(n int) bool {
|
||||
return n == 16 || n == 24 || n == 32
|
||||
}
|
||||
|
||||
// loadRsaPrivateKey loads and parses a PEM encoded private key file.
|
||||
func loadRsaPublicKey(filename string) (*rsa.PublicKey, error) {
|
||||
pubKeyData, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(pubKeyData)
|
||||
if block == nil {
|
||||
return nil, errors.New("failed to decode PEM block containing the public key")
|
||||
}
|
||||
|
||||
var pubKey *rsa.PublicKey
|
||||
blockType := strings.ToUpper(block.Type)
|
||||
|
||||
if blockType == "RSA PUBLIC KEY" {
|
||||
pubKey, err = x509.ParsePKCS1PublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
key, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ok bool
|
||||
pubKey, ok = key.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, errors.New("failed to parse RSA private key")
|
||||
}
|
||||
}
|
||||
} else if blockType == "PUBLIC KEY" {
|
||||
key, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ok bool
|
||||
pubKey, ok = key.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, errors.New("failed to parse RSA private key")
|
||||
}
|
||||
|
||||
} else {
|
||||
return nil, errors.New("unsupported key type")
|
||||
}
|
||||
|
||||
return pubKey, nil
|
||||
}
|
||||
|
||||
// loadRsaPrivateKey loads and parses a PEM encoded private key file.
|
||||
func loadRasPrivateKey(filename string) (*rsa.PrivateKey, error) {
|
||||
priKeyData, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(priKeyData)
|
||||
if block == nil {
|
||||
return nil, errors.New("failed to decode PEM block containing the private key")
|
||||
}
|
||||
|
||||
var privateKey *rsa.PrivateKey
|
||||
blockType := strings.ToUpper(block.Type)
|
||||
|
||||
// PKCS#1 format
|
||||
if blockType == "RSA PRIVATE KEY" {
|
||||
privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if blockType == "PRIVATE KEY" { // PKCS#8 format
|
||||
priKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ok bool
|
||||
privateKey, ok = priKey.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, errors.New("failed to parse RSA private key")
|
||||
}
|
||||
} else {
|
||||
return nil, errors.New("unsupported key type")
|
||||
}
|
||||
|
||||
return privateKey, nil
|
||||
}
|
||||
|
||||
// hashData returns the hash value of the data, using the specified hash function
|
||||
func hashData(hash crypto.Hash, data []byte) ([]byte, error) {
|
||||
if !hash.Available() {
|
||||
return nil, errors.New("unsupported hash algorithm")
|
||||
}
|
||||
|
||||
var hashed []byte
|
||||
|
||||
switch hash {
|
||||
case crypto.SHA224:
|
||||
h := sha256.Sum224(data)
|
||||
hashed = h[:]
|
||||
case crypto.SHA256:
|
||||
h := sha256.Sum256(data)
|
||||
hashed = h[:]
|
||||
case crypto.SHA384:
|
||||
h := sha512.Sum384(data)
|
||||
hashed = h[:]
|
||||
case crypto.SHA512:
|
||||
h := sha512.Sum512(data)
|
||||
hashed = h[:]
|
||||
default:
|
||||
return nil, errors.New("unsupported hash algorithm")
|
||||
}
|
||||
|
||||
return hashed, nil
|
||||
}
|
||||
|
||||
234
cryptor/crypto_test.go
Normal file
234
cryptor/crypto_test.go
Normal file
@@ -0,0 +1,234 @@
|
||||
package cryptor
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"testing"
|
||||
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
|
||||
func TestAesEcbCrypt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := "hello world"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
aesEcbEncrypt := AesEcbEncrypt([]byte(data), []byte(key))
|
||||
aesEcbDecrypt := AesEcbDecrypt(aesEcbEncrypt, []byte(key))
|
||||
|
||||
assert := internal.NewAssert(t, "TestAesEcbCrypt")
|
||||
assert.Equal(data, string(aesEcbDecrypt))
|
||||
}
|
||||
|
||||
func TestAesCbcCrypt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := "hello world"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
aesCbcEncrypt := AesCbcEncrypt([]byte(data), []byte(key))
|
||||
aesCbcDecrypt := AesCbcDecrypt(aesCbcEncrypt, []byte(key))
|
||||
|
||||
assert := internal.NewAssert(t, "TestAesCbcCrypt")
|
||||
assert.Equal(data, string(aesCbcDecrypt))
|
||||
}
|
||||
|
||||
func TestAesCtrCrypt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := "hello world"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
aesCtrCrypt := AesCtrEncrypt([]byte(data), []byte(key))
|
||||
aesCtrDeCrypt := AesCtrDecrypt(aesCtrCrypt, []byte(key))
|
||||
|
||||
assert := internal.NewAssert(t, "TestAesCtrCrypt")
|
||||
assert.Equal(data, string(aesCtrDeCrypt))
|
||||
}
|
||||
|
||||
func TestAesCfbCrypt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := "hello world"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
aesCfbEncrypt := AesCfbEncrypt([]byte(data), []byte(key))
|
||||
aesCfbDecrypt := AesCfbDecrypt(aesCfbEncrypt, []byte(key))
|
||||
|
||||
assert := internal.NewAssert(t, "TestAesCfbCrypt")
|
||||
assert.Equal(data, string(aesCfbDecrypt))
|
||||
}
|
||||
|
||||
func TestAesOfbCrypt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := "hello world"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
aesOfbEncrypt := AesOfbEncrypt([]byte(data), []byte(key))
|
||||
aesOfbDecrypt := AesOfbDecrypt(aesOfbEncrypt, []byte(key))
|
||||
|
||||
assert := internal.NewAssert(t, "TestAesOfbEncrypt")
|
||||
assert.Equal(data, string(aesOfbDecrypt))
|
||||
}
|
||||
|
||||
func TestDesEcbCrypt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := "hello world"
|
||||
key := "abcdefgh"
|
||||
|
||||
desEcbEncrypt := DesEcbEncrypt([]byte(data), []byte(key))
|
||||
desEcbDecrypt := DesEcbDecrypt(desEcbEncrypt, []byte(key))
|
||||
|
||||
assert := internal.NewAssert(t, "TestDesEcbEncrypt")
|
||||
assert.Equal(data, string(desEcbDecrypt))
|
||||
}
|
||||
|
||||
func TestDesCbcCrypt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := "hello world"
|
||||
key := "abcdefgh"
|
||||
|
||||
desCbcEncrypt := DesCbcEncrypt([]byte(data), []byte(key))
|
||||
desCbcDecrypt := DesCbcDecrypt(desCbcEncrypt, []byte(key))
|
||||
|
||||
assert := internal.NewAssert(t, "TestDesCbcEncrypt")
|
||||
assert.Equal(data, string(desCbcDecrypt))
|
||||
}
|
||||
|
||||
func TestDesCtrCrypt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := "hello world"
|
||||
key := "abcdefgh"
|
||||
|
||||
desCtrCrypt := DesCtrEncrypt([]byte(data), []byte(key))
|
||||
desCtrDeCrypt := DesCtrDecrypt(desCtrCrypt, []byte(key))
|
||||
|
||||
assert := internal.NewAssert(t, "TestDesCtrCrypt")
|
||||
assert.Equal(data, string(desCtrDeCrypt))
|
||||
}
|
||||
|
||||
func TestDesCfbCrypt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := "hello world"
|
||||
key := "abcdefgh"
|
||||
|
||||
desCfbEncrypt := DesCfbEncrypt([]byte(data), []byte(key))
|
||||
desCfbDecrypt := DesCfbDecrypt(desCfbEncrypt, []byte(key))
|
||||
|
||||
assert := internal.NewAssert(t, "TestDesCfbEncrypt")
|
||||
assert.Equal(data, string(desCfbDecrypt))
|
||||
}
|
||||
|
||||
func TestDesOfbCrypt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := "hello world"
|
||||
key := "abcdefgh"
|
||||
|
||||
desOfbEncrypt := DesOfbEncrypt([]byte(data), []byte(key))
|
||||
desOfbDecrypt := DesOfbDecrypt(desOfbEncrypt, []byte(key))
|
||||
|
||||
assert := internal.NewAssert(t, "TestDesOfbEncrypt")
|
||||
assert.Equal(data, string(desOfbDecrypt))
|
||||
}
|
||||
|
||||
func TestRsaEncrypt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
err := GenerateRsaKey(4096, "./rsa_private_example.pem", "./rsa_public_example.pem")
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
data := []byte("hello world")
|
||||
encrypted := RsaEncrypt(data, "./rsa_public_example.pem")
|
||||
decrypted := RsaDecrypt(encrypted, "./rsa_private_example.pem")
|
||||
|
||||
assert := internal.NewAssert(t, "TestRsaEncrypt")
|
||||
assert.Equal(string(data), string(decrypted))
|
||||
}
|
||||
|
||||
func TestRsaEncryptOAEP(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestRsaEncrypt")
|
||||
t.Parallel()
|
||||
|
||||
pri, pub := GenerateRsaKeyPair(1024)
|
||||
|
||||
data := []byte("hello world")
|
||||
label := []byte("123456")
|
||||
|
||||
encrypted, err := RsaEncryptOAEP(data, label, *pub)
|
||||
assert.IsNil(err)
|
||||
|
||||
decrypted, err := RsaDecryptOAEP([]byte(encrypted), label, *pri)
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
func TestRsaSignAndVerify(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := []byte("This is a test data for RSA signing")
|
||||
hash := crypto.SHA256
|
||||
|
||||
t.Run("RSA Sign and Verify", func(t *testing.T) {
|
||||
privateKey := "./rsa_private_example.pem"
|
||||
publicKey := "./rsa_public_example.pem"
|
||||
|
||||
signature, err := RsaSign(hash, data, privateKey)
|
||||
if err != nil {
|
||||
t.Fatalf("RsaSign failed: %v", err)
|
||||
}
|
||||
|
||||
err = RsaVerifySign(hash, data, signature, publicKey)
|
||||
if err != nil {
|
||||
t.Fatalf("RsaVerifySign failed: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("RSA Sign and Verify Invalid Signature", func(t *testing.T) {
|
||||
publicKey := "./rsa_public_example.pem"
|
||||
|
||||
invalidSig := []byte("InvalidSignature")
|
||||
|
||||
err := RsaVerifySign(hash, data, invalidSig, publicKey)
|
||||
if err == nil {
|
||||
t.Fatalf("RsaVerifySign failed: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("RSA Sign and Verify With Different Hash", func(t *testing.T) {
|
||||
publicKey := "./rsa_public_example.pem"
|
||||
privateKey := "./rsa_private_example.pem"
|
||||
hashSign := crypto.SHA256
|
||||
hashVerify := crypto.SHA512
|
||||
|
||||
signature, err := RsaSign(hashSign, data, privateKey)
|
||||
if err != nil {
|
||||
t.Fatalf("RsaSign failed: %v", err)
|
||||
}
|
||||
|
||||
err = RsaVerifySign(hashVerify, data, signature, publicKey)
|
||||
if err == nil {
|
||||
t.Fatalf("RsaVerifySign failed: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
173
cryptor/des.go
173
cryptor/des.go
@@ -1,173 +0,0 @@
|
||||
// Copyright 2021 dudaodong@gmail.com. All rights reserved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
// Package cryptor implements some util functions to encrypt and decrypt.
|
||||
package cryptor
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/cipher"
|
||||
"crypto/des"
|
||||
"crypto/rand"
|
||||
"io"
|
||||
)
|
||||
|
||||
// DesEcbEncrypt encrypt data with key use DES ECB algorithm
|
||||
// len(key) should be 8
|
||||
func DesEcbEncrypt(data, key []byte) []byte {
|
||||
cipher, _ := des.NewCipher(generateDesKey(key))
|
||||
length := (len(data) + des.BlockSize) / des.BlockSize
|
||||
plain := make([]byte, length*des.BlockSize)
|
||||
copy(plain, data)
|
||||
|
||||
pad := byte(len(plain) - len(data))
|
||||
for i := len(data); i < len(plain); i++ {
|
||||
plain[i] = pad
|
||||
}
|
||||
|
||||
encrypted := make([]byte, len(plain))
|
||||
for bs, be := 0, cipher.BlockSize(); bs <= len(data); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
|
||||
cipher.Encrypt(encrypted[bs:be], plain[bs:be])
|
||||
}
|
||||
|
||||
return encrypted
|
||||
}
|
||||
|
||||
// DesEcbDecrypt decrypt data with key use DES ECB algorithm
|
||||
// len(key) should be 8
|
||||
func DesEcbDecrypt(encrypted, key []byte) []byte {
|
||||
cipher, _ := des.NewCipher(generateDesKey(key))
|
||||
decrypted := make([]byte, len(encrypted))
|
||||
|
||||
for bs, be := 0, cipher.BlockSize(); bs < len(encrypted); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
|
||||
cipher.Decrypt(decrypted[bs:be], encrypted[bs:be])
|
||||
}
|
||||
|
||||
trim := 0
|
||||
if len(decrypted) > 0 {
|
||||
trim = len(decrypted) - int(decrypted[len(decrypted)-1])
|
||||
}
|
||||
|
||||
return decrypted[:trim]
|
||||
}
|
||||
|
||||
// DesCbcEncrypt encrypt data with key use DES CBC algorithm
|
||||
// len(key) should be 8
|
||||
func DesCbcEncrypt(data, key []byte) []byte {
|
||||
block, _ := des.NewCipher(key)
|
||||
blockSize := block.BlockSize()
|
||||
data = pkcs7Padding(data, blockSize)
|
||||
blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
|
||||
|
||||
encrypted := make([]byte, len(data))
|
||||
blockMode.CryptBlocks(encrypted, data)
|
||||
|
||||
return encrypted
|
||||
}
|
||||
|
||||
// DesCbcDecrypt decrypt data with key use DES CBC algorithm
|
||||
// len(key) should be 8
|
||||
func DesCbcDecrypt(encrypted, key []byte) []byte {
|
||||
block, _ := des.NewCipher(key)
|
||||
blockSize := block.BlockSize()
|
||||
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
|
||||
|
||||
decrypted := make([]byte, len(encrypted))
|
||||
blockMode.CryptBlocks(decrypted, encrypted)
|
||||
decrypted = pkcs7UnPadding(decrypted)
|
||||
|
||||
return decrypted
|
||||
}
|
||||
|
||||
// DesCtrCrypt encrypt data with key use DES CTR algorithm
|
||||
// len(key) should be 8
|
||||
func DesCtrCrypt(data, key []byte) []byte {
|
||||
block, _ := des.NewCipher(key)
|
||||
|
||||
iv := bytes.Repeat([]byte("1"), block.BlockSize())
|
||||
stream := cipher.NewCTR(block, iv)
|
||||
|
||||
dst := make([]byte, len(data))
|
||||
stream.XORKeyStream(dst, data)
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
// DesCfbEncrypt encrypt data with key use DES CFB algorithm
|
||||
// len(key) should be 8
|
||||
func DesCfbEncrypt(data, key []byte) []byte {
|
||||
block, err := des.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
encrypted := make([]byte, des.BlockSize+len(data))
|
||||
iv := encrypted[:des.BlockSize]
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
stream := cipher.NewCFBEncrypter(block, iv)
|
||||
stream.XORKeyStream(encrypted[des.BlockSize:], data)
|
||||
|
||||
return encrypted
|
||||
}
|
||||
|
||||
// DesCfbDecrypt decrypt data with key use DES CFB algorithm
|
||||
// len(encrypted) should be great than 16, len(key) should be 8
|
||||
func DesCfbDecrypt(encrypted, key []byte) []byte {
|
||||
block, _ := des.NewCipher(key)
|
||||
if len(encrypted) < des.BlockSize {
|
||||
panic("encrypted data is too short")
|
||||
}
|
||||
iv := encrypted[:des.BlockSize]
|
||||
encrypted = encrypted[des.BlockSize:]
|
||||
|
||||
stream := cipher.NewCFBDecrypter(block, iv)
|
||||
stream.XORKeyStream(encrypted, encrypted)
|
||||
|
||||
return encrypted
|
||||
}
|
||||
|
||||
// DesOfbEncrypt encrypt data with key use DES OFB algorithm
|
||||
// len(key) should be 16, 24 or 32
|
||||
func DesOfbEncrypt(data, key []byte) []byte {
|
||||
block, err := des.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
data = pkcs7Padding(data, des.BlockSize)
|
||||
encrypted := make([]byte, des.BlockSize+len(data))
|
||||
iv := encrypted[:des.BlockSize]
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
stream := cipher.NewOFB(block, iv)
|
||||
stream.XORKeyStream(encrypted[des.BlockSize:], data)
|
||||
|
||||
return encrypted
|
||||
}
|
||||
|
||||
// DesOfbDecrypt decrypt data with key use DES OFB algorithm
|
||||
// len(key) should be 8
|
||||
func DesOfbDecrypt(data, key []byte) []byte {
|
||||
block, err := des.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
iv := data[:des.BlockSize]
|
||||
data = data[des.BlockSize:]
|
||||
if len(data)%des.BlockSize != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
decrypted := make([]byte, len(data))
|
||||
mode := cipher.NewOFB(block, iv)
|
||||
mode.XORKeyStream(decrypted, data)
|
||||
|
||||
decrypted = pkcs7UnPadding(decrypted)
|
||||
|
||||
return decrypted
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package cryptor
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
|
||||
func TestDesEcbEncrypt(t *testing.T) {
|
||||
data := "hello world"
|
||||
key := "abcdefgh"
|
||||
|
||||
desEcbEncrypt := DesEcbEncrypt([]byte(data), []byte(key))
|
||||
desEcbDecrypt := DesEcbDecrypt(desEcbEncrypt, []byte(key))
|
||||
|
||||
assert := internal.NewAssert(t, "TestDesEcbEncrypt")
|
||||
assert.Equal(data, string(desEcbDecrypt))
|
||||
}
|
||||
|
||||
func TestDesCbcEncrypt(t *testing.T) {
|
||||
data := "hello world"
|
||||
key := "abcdefgh"
|
||||
|
||||
desCbcEncrypt := DesCbcEncrypt([]byte(data), []byte(key))
|
||||
desCbcDecrypt := DesCbcDecrypt(desCbcEncrypt, []byte(key))
|
||||
|
||||
assert := internal.NewAssert(t, "TestDesCbcEncrypt")
|
||||
assert.Equal(data, string(desCbcDecrypt))
|
||||
}
|
||||
|
||||
func TestDesCtrCrypt(t *testing.T) {
|
||||
data := "hello world"
|
||||
key := "abcdefgh"
|
||||
|
||||
desCtrCrypt := DesCtrCrypt([]byte(data), []byte(key))
|
||||
desCtrDeCrypt := DesCtrCrypt(desCtrCrypt, []byte(key))
|
||||
|
||||
assert := internal.NewAssert(t, "TestDesCtrCrypt")
|
||||
assert.Equal(data, string(desCtrDeCrypt))
|
||||
}
|
||||
|
||||
func TestDesCfbEncrypt(t *testing.T) {
|
||||
data := "hello world"
|
||||
key := "abcdefgh"
|
||||
|
||||
desCfbEncrypt := DesCfbEncrypt([]byte(data), []byte(key))
|
||||
desCfbDecrypt := DesCfbDecrypt(desCfbEncrypt, []byte(key))
|
||||
|
||||
assert := internal.NewAssert(t, "TestDesCfbEncrypt")
|
||||
assert.Equal(data, string(desCfbDecrypt))
|
||||
}
|
||||
|
||||
func TestDesOfbEncrypt(t *testing.T) {
|
||||
data := "hello world"
|
||||
key := "abcdefgh"
|
||||
|
||||
desOfbEncrypt := DesOfbEncrypt([]byte(data), []byte(key))
|
||||
desOfbDecrypt := DesOfbDecrypt(desOfbEncrypt, []byte(key))
|
||||
|
||||
assert := internal.NewAssert(t, "TestDesOfbEncrypt")
|
||||
assert.Equal(data, string(desOfbDecrypt))
|
||||
}
|
||||
118
cryptor/rsa.go
118
cryptor/rsa.go
@@ -1,118 +0,0 @@
|
||||
// Copyright 2021 dudaodong@gmail.com. All rights reserved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
// Package cryptor implements some util functions to encrypt and decrypt.
|
||||
package cryptor
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"os"
|
||||
)
|
||||
|
||||
// GenerateRsaKey make a rsa private key, and return key file name
|
||||
// Generated key file is `rsa_private.pem` and `rsa_public.pem` in current path
|
||||
func GenerateRsaKey(keySize int, priKeyFile, pubKeyFile string) error {
|
||||
// private key
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, keySize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
derText := x509.MarshalPKCS1PrivateKey(privateKey)
|
||||
|
||||
block := pem.Block{
|
||||
Type: "rsa private key",
|
||||
Bytes: derText,
|
||||
}
|
||||
|
||||
//file,err := os.Create("rsa_private.pem")
|
||||
file, err := os.Create(priKeyFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pem.Encode(file, &block)
|
||||
file.Close()
|
||||
|
||||
// public key
|
||||
publicKey := privateKey.PublicKey
|
||||
|
||||
derpText, err := x509.MarshalPKIXPublicKey(&publicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
block = pem.Block{
|
||||
Type: "rsa public key",
|
||||
Bytes: derpText,
|
||||
}
|
||||
|
||||
//file,err = os.Create("rsa_public.pem")
|
||||
file, err = os.Create(pubKeyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pem.Encode(file, &block)
|
||||
file.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RsaEncrypt encrypt data with ras algorithm
|
||||
func RsaEncrypt(data []byte, pubKeyFileName string) []byte {
|
||||
file, err := os.Open(pubKeyFileName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fileInfo, err := file.Stat()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer file.Close()
|
||||
buf := make([]byte, fileInfo.Size())
|
||||
file.Read(buf)
|
||||
|
||||
block, _ := pem.Decode(buf)
|
||||
|
||||
pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pubKey := pubInterface.(*rsa.PublicKey)
|
||||
|
||||
cipherText, err := rsa.EncryptPKCS1v15(rand.Reader, pubKey, data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return cipherText
|
||||
}
|
||||
|
||||
// RsaDecrypt decrypt data with ras algorithm
|
||||
func RsaDecrypt(data []byte, privateKeyFileName string) []byte {
|
||||
file, err := os.Open(privateKeyFileName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fileInfo, err := file.Stat()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
buf := make([]byte, fileInfo.Size())
|
||||
defer file.Close()
|
||||
file.Read(buf)
|
||||
|
||||
block, _ := pem.Decode(buf)
|
||||
|
||||
priKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
plainText, err := rsa.DecryptPKCS1v15(rand.Reader, priKey, data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return plainText
|
||||
}
|
||||
51
cryptor/rsa_private.pem
Normal file
51
cryptor/rsa_private.pem
Normal file
@@ -0,0 +1,51 @@
|
||||
-----BEGIN rsa private key-----
|
||||
MIIJKAIBAAKCAgEA5IqWfYbW1NlTDWE2plFWqD6CTquA0Ar/1E66lY8OMtrdpxTm
|
||||
chirmoISWN0BD7r2tV9T/kHs44Sy3raAfkR5ixYF5FRkb63FdIAtoynsxS6MEE26
|
||||
fgWuDAa1xwNt+t/uivnpfJk25htpBNkKGT8ii0TPPLn1N15hMHenT6PzWVjGjQxW
|
||||
cp4dUA4gXTDuqaeu7Oy7Ku2yP90/ra+cjTV8DnBJ9enQhAfu1LivJO3FgmXeuC3u
|
||||
qhhM3t9ZbNy7tvJI5PXDCaS8iesBvCp5carTSXWLpFEgvIhXQIsXtxezKAP3gRd8
|
||||
r5JPQI9FZlxyUcyf+Htkn2A4qyKhFElnP1z9j0YLFhL/gQMdtNIY7yhSb29PyuCG
|
||||
7noz25swrfxbA7ZVppM0J19JNhlpqmusBKLBNqN+KMD8EDli7NLX17S9Dj2pQF9I
|
||||
8fVXPEhkdTb67rr7y3OUANKjh0Opnt3JLoj9u1X7BfCeUSR/qq3E8RFPeyxvP1tc
|
||||
5QVqKBG6UPBo8nLQqdzJhUd8a1YEfXIUbdHLs2q5WVFu2MDavIp634+fzfcIFRoB
|
||||
tdxf5aCdxLaLocsA/aLLyL1pK6K0rpdrSUtcEBMST26qIlU0Ht+SQtaMdE2gyjkM
|
||||
M5RpLh1q/gpow+0j3zu6f5tDsu3qwgjLIx78YCTGUuCpsVMzFK4tahYSPNkCAwEA
|
||||
AQKCAgEAuXz97X2uCW0lqjtXhp+HrN+nFUC/OJtkziTj7RUBmibnNX+SFdKOHMYr
|
||||
K/KbtO+y4rwvSLKrGHIQVxBas6DR4SALwij4p2erVgXehIo3gEZqKaVckoH7pAki
|
||||
KhdPgQmU6zkw1w7nbtWaY/Pf7WO/nrdHV+s56ilwykyi/9F6Ze7Wn43+7+ICuoHs
|
||||
pJZdblcJc4Qj2RC41nq0/zwD3NwnBvT+IlgWA8MIhaArjtZospAJtwSYq3czlMRE
|
||||
KUXyGOcGYMZS+RW6bFnPu6/hh271M67ymne6ESq7XkhGBDV5FCY8EItGiJ1AM47U
|
||||
7eJkap2gzKUhovUOqV9eyz9UTComJETl2kmcjpZeaxrZZxtPap23IzBu6PJoPuDg
|
||||
hgzRWh3BrkakLdq6Km2z+jDFEQhWeHsksozuKzln5USx9wovb9LDKKFSpKm4vzW+
|
||||
7YVLZnH4Z1m4wvWQJShvZur7kkM7aNfK+xcS81OBMtqKerjWhdHqfMRVvLvtG/ev
|
||||
ftPTwRy+u02w+FoYcassTS+lW+Pnhj0ZuOewsWoNvgWg1TpPEDBkY4UJOYQPkTrS
|
||||
bixTBVI3teSjMXmjEAif+BG2LBCl7pFY5SW9Jk6eRvmxt9rEly13C+4tcBHEJnXu
|
||||
eVUMurRuB/9BAKgj4qGHPQlG455mKHQzWJxJXBpRurh+U478xm0CggEBAOgduMra
|
||||
tPDhyhy01iIAzYsh0PW9p11AcCoJkDUE/5IUf03fVt3WKdSNauHZBKTulqaCHkvz
|
||||
+pjmSwPr4JdJKcJzwk5ncResFBYF177JmYXzJzwpIPRQFNP34heaUd9cVsFckddV
|
||||
G3jR7c1vD33VElexUM94BubZFEqF2lO3/y0sLwhd7prKhwy6mctJyvyDFk02psDc
|
||||
6XzBG/sMiZ6meWA1sP4QIM6+sYdZ9ihvTNWGb1+TGojvgOHCEQ3Hv9u/qwdkzFKj
|
||||
qo25pRMV4VNMQUvYIywSB7K5c0w1ccfOINzB8kgqpHhpimAHqZw9ix4H98YmvaXS
|
||||
rr0LP8ES9xVvelcCggEBAPwOs7pWvm/R3RAXZ8Erk9x68Fsn8wzoofsGP/BG5QCC
|
||||
r1fDJr9aJAVggXOVNDktWRKkN7Cnib+Z7ymL5br3FfYPy945cF2e8nkx2ri0VveF
|
||||
glkPCFLb3lF3iLKD8nJNtiv1rpu2v4Dj09+SzQYSIF3h55BaZGUCHLskR7Mhdd74
|
||||
iTBzPaQ3NC+n0xrv8o1EXjy9w/nBt07sZojrf82Ae3mIbEFYdEHUVDaCZKKeBT4b
|
||||
9W8Q5aAt56DIkSkaBAZZZhzbfxollmeKiwJ32+finR7LZ1Fj2cTHia19Ef85U8bE
|
||||
Ow7E2cTDZkqaqgi+pFescl518DQ47PCeXfnFP0g65E8CggEAUApHubO3J0VE06dM
|
||||
G8eZGTwc+VBf0RkyVFyd3JqPoojs6SZ1puN94yysyZpzLoiTbHF8DwbfyC/JeF2z
|
||||
QZfaDZKrUyv6ZIZTGtEC92g/R2B0jBtGoNiohft5fFgbmWEXDXBlXhKb+YqybN+6
|
||||
QNLjk1eynQgvoRUEGTqU8b+F/8a3pTP23muuLCaAeAhHNdHiM9f/oovK+9j/VA+b
|
||||
uRiAzDtXgBSBq6k4QIs2BfVzUkIcT6HDSasFD1RDWzQhJZ6vVEpe5rRHUL3OfYlS
|
||||
/M1TytqKLl09SFUIvCPFy3d5/4XljRsfQeJq8/hQdW8HdOCcgTjEttSyqr+hSWvH
|
||||
xh192wKCAQB0uSY3u2XTCH9zrTMJ/HErn+7gd76REsW4JmvDjEEOHHawkJnH8SlP
|
||||
KCKqcMTPWZWvEUcM0njytolPVw6ap0OPQD9reHP1lt64iwK7mB/R3gy/yztSi6kH
|
||||
VvCBoqLKlfwvnUUvrNBAEsER/rxc/FXqw+tlKMbnE7RUYXeml28rQzLcsfEws7PC
|
||||
Adi717QeATQWstYnObL2pHjTHSOA+ee0Hx3qoNith3M8DuQlfkH1QiNFPLDpnXhv
|
||||
N5IpU3fbrNihsm/InvFon3rCONkoKAQUt6LvyOqWusSiB5Im+9g06rhinXwvJ0Ge
|
||||
eMMW65nVU/FelwUWWeo3f08LlHE6tLL3AoIBABcc549sTCMhBmQS9prESurzhPVk
|
||||
HCFYlR/GdlfNmRtTYssk25xaG+tiaEzXi7DXBq20TGNOATgoX7l9EWIRXkDuR6hS
|
||||
aRXyn+CVU3y2dhpGZBf+EUWP0u9TNSJ/RFO0ViAriL5J9kjF9hFvG36B9kRSiMrn
|
||||
mCp9NCdtRYOHjzkvbY5uRMqP8H4/nAXNMA+odPnOPDUOIcH2ztaFOtQgZxa9x/eS
|
||||
eGmnJ/V/rLvS04uVw5d7juJstEspnM4MxPHSFEDN7NG13bN+c1jTY1/85icIeRBi
|
||||
nP47M2kDQ6RrEdgYfPbdlx1wGdTUIeAm3duptTiwpwa5Q2Js22TMJYRTnbM=
|
||||
-----END rsa private key-----
|
||||
51
cryptor/rsa_private_example.pem
Normal file
51
cryptor/rsa_private_example.pem
Normal file
@@ -0,0 +1,51 @@
|
||||
-----BEGIN rsa private key-----
|
||||
MIIJKAIBAAKCAgEA8O2hLVt/7v6rQF2eGX3J+QyteV45lI/5qlyqTe7CiwUg1bkJ
|
||||
2yLHaoGSWoVtthE/X7GZgsw57VEgnazZAeheXcxV8OJWBGuRfmXabtKIzSBF8uZj
|
||||
+H3iPDvJB7NggMh8teYsdBSn9Cm/L5FesDvg/KxUKKfBU4LSrhOMhBauIoOSVU65
|
||||
9y3kYgpzC/ormvCsJSe3KSdIhtLxiv+dnEHh9WvsXWB50lIw3TIC041aL5mwRvb5
|
||||
/JWcjtn5bgLEChrnEGF0RYUDMwm8hT1YsEN9Rb9fOx5ZTUZNRVHYjaCyUA+cG+gC
|
||||
72EjrkXY4If3pAESNcF+5NGGJaETd8rHzHsRFRVPXkwhRmkx9nBF+RjQhouqBTpt
|
||||
IcIO2rPlTC9H5Oeg+jVljlBJ7/ESeOlg2OEbYyOUJpAPMPcz5HiNSTRHuDWIDUuk
|
||||
48BOF9Vo00PJq/oXRzUa9HHhu6cosfZH4P4PkDthxq89migqUL+C8WqeZ8I+xFS8
|
||||
/1TpvjOnOYF5ADvByC69UGPEoh+o+ZpgWfAsCrb9Qwy9WAAZg0rsouoPqb4Qk6Vr
|
||||
88X6I20Ys9AayLzZ72ddoS5IweOoF5/xLfiicGLKckNwG6sy4f1LCFB2Xc79nYqJ
|
||||
zlwPGC3pKfEIAXUj1I/p+/MqaTDJQs72dhr0lTX5505XUBqrbX0BPGcZvIsCAwEA
|
||||
AQKCAgEA7rvJNlSwlHWt4/3gJ4pJlItHajg//kIcNv/TkZ3BEFhojN7qMUZpK9Rw
|
||||
3VnRuNOmZIBriPwtekcldphMAGPs/iz4C9V7Pq4IYaMzqxTbkcclCOfar+StRNpI
|
||||
/WR1f6cqTGRkMDI3qu2jENOPbDopWra4PgDcxI+hi/S7DDgdHP4bBoUYKSJEaBHK
|
||||
plei1ckeC0Mrb5AJge+MgRuBZdCywqnKcUyj6hCfcs+XlWE/uGMmFWutkuf9VmAP
|
||||
lT8QWqMFy2mF+U0wOmavc6eyNhbqDy7ugno0Kyo6bzIGz3AowMR3AAAKyIENBicY
|
||||
HIUeklitXq75umsT7j2KOO2qxag85ycQAC549AGO+OWdig29RjSBniame3w5EjNl
|
||||
sI2L2dms7IvFqkXVUEcytNEZF27Yg/oCh8CWPd5iSaa+/F2mYnXovA03lNrYYLwf
|
||||
lmrOQGEw6gC4Uj9xpf67oRy78rIg1dztEiiqO0yz15z+Kgv7YVF/GnGT7ieqgHrc
|
||||
j4L0vt0nShByzA9k8wpNsgZAPDKvSYIzLwrh/Br96eKg0nysnnJHnvnCjTX0tbUp
|
||||
vlk/YhlGde1/BKnw/6P/lKLqRXkoY3cbQfOinlLIiY6w0TGPdDuSMeHJ9pyftz3S
|
||||
I6Hi9Ty2Y9yYGkCcdWFcnLtQNq4WVwRd7S6wHVHAD1xYc/SRNgECggEBAPzM7+5d
|
||||
sucEpQnRmG+6E+YaYSbzN8ZPK4pC3NID4GlJ/EGCOnewQlaJO4DVJ00h9HnuwRN8
|
||||
X2jm4R5YXXHo+NptxWqYpydz6EvjFU2rJAhdy/qi+yJrKyIcmdUNe3BwvY6A0CW2
|
||||
pkkAou2Yrw2UxnGC20ou7klXf9tx7noSkHtz4uhO2goC3fGr3eN5W+NlbyMOZElE
|
||||
wNqW/cjxYTg9DcImlfC7V9/Ubwwq9te2YAAaGlbcuZHWOpzoCyzrv9g73WTeXSnA
|
||||
WaqjFSCDPQQNvtzckxxlRHkEKomy4inLNDBPf+cRdIuwomHe4KYtbkKCoQ381yg3
|
||||
GjYQ+pJUHWzKz0ECggEBAPP6OgVsxvNuAYv1QQgsfgZE95GW4019bn6cYVFT0eL+
|
||||
jdYr7tXv5DUmNtl4sM49IXNcggfpOEDEWSToMgVP2I14WOrDOIj3+U7EodY/qeVT
|
||||
UjFMTKnA4BXnt5geIE5T8Fgw3XlxHRAgiDWEXht7lU/pk2qHISl/Qf9ZYKpBU4i2
|
||||
USnfHBlzCnZc3lQKkm9QiH4DJLhFfTAZJ1IpN5TH4+h2o+Y6acDlDjQuG1ENFheu
|
||||
+p/+BLtIqDZu1r+AlHIvHUBP7Hw8n///oN8i54fCJZSMv7xCj2E+aaNa55dGjNcp
|
||||
YhkmBPcVZyJapW13/S1OgIQOdx06MmKSVu0a9mbSZMsCggEAPIK1f6Hv+7ox4urH
|
||||
iR7KOo7f6FnZZN94dYzRnHePFMS/29JXOmT3TA1nL8xVrvHMug77KjXgBJUXF5Nh
|
||||
Mq3oOyiBU6WchSYKWXfOlpu7cUE6XRD7+d4bIfwkmkmy3VQvG1gb+psArIK5fRPJ
|
||||
+v88jNkcsmIPaYDHOvjHc3LUIKi5jI+rQzAyffF8mEFpTEHwWzzLpnoNi4UO1DVq
|
||||
5vI+Q9XGmCvPueT4e7ohAbtGuV+GJHqK9KyJtRsZ6bO4ZQLXWJidRiwjimOk3/Zp
|
||||
+Xls0SL/F5Hp1Om5YOJvnj9ki5fL7rxP4Ev0Ymbd8Qj41nS8JkP6IEcoP/7Ka5I0
|
||||
xOC6wQKCAQAi5pWsNv5Szla8Ta4q3Cp+/RipI/uKFzpaNEabmrD4ls91Zr14ryNn
|
||||
EvtfqqsoJYiGdyJGvW8FnNDfvbOCHQTuX9vgYWLR/R8VzH0WJ+9G1d95G+APnH6x
|
||||
w3747L5UVh+YjgzwlWTB7NVvSmsn5Urbrp8e6wusYv7u5zszv7qSYPpFUhwz68gA
|
||||
XJKVVRnTgKK12/9BuPcKjV6ZmznPN7T7iRUzFwIPzPR2NG5F7uhNJQPHJVBJ9j/R
|
||||
4ZMou90AZIr7qzM6JnYA6fF6WgTi37v+fw/if2cBUytLafKdKkN1d/8Hd+/X5KDn
|
||||
Qi5N9Y8rDwLFYUhazvtsLGDw9B1xYgF3AoIBAChp2H++3PAEjJvOj0vjWnkKCfmz
|
||||
CZim1+SxobQTGEWeQJavRbsZAyVE2OeP7N58Rw2VeihS5qm3jxs9J7Xv0Fnq4zDn
|
||||
T4bHx2PgmdDoJpMlC1f08KtEovBc1W89x+IY6MYlPO7I9nKBMK2b9yWaI7GF1aQv
|
||||
dvfWhJZdmia0Tp0a8CZ8/LD4qmWztB1KIkbkqayPmFVVHtbJ3+XfIrTTDU6T1Tsf
|
||||
59vzFYXCFkjwtBm9OOpm4TSRsDN5iKZAjdSCHwoJilD+y7IqT/0WDAoTtgAPRJaU
|
||||
u10OOOy/GAuCtNRYLQ5EfXHYLtE06lsMRCHNgsxCtPlULiChbiSnTq0CBbc=
|
||||
-----END rsa private key-----
|
||||
14
cryptor/rsa_public.pem
Normal file
14
cryptor/rsa_public.pem
Normal file
@@ -0,0 +1,14 @@
|
||||
-----BEGIN rsa public key-----
|
||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5IqWfYbW1NlTDWE2plFW
|
||||
qD6CTquA0Ar/1E66lY8OMtrdpxTmchirmoISWN0BD7r2tV9T/kHs44Sy3raAfkR5
|
||||
ixYF5FRkb63FdIAtoynsxS6MEE26fgWuDAa1xwNt+t/uivnpfJk25htpBNkKGT8i
|
||||
i0TPPLn1N15hMHenT6PzWVjGjQxWcp4dUA4gXTDuqaeu7Oy7Ku2yP90/ra+cjTV8
|
||||
DnBJ9enQhAfu1LivJO3FgmXeuC3uqhhM3t9ZbNy7tvJI5PXDCaS8iesBvCp5carT
|
||||
SXWLpFEgvIhXQIsXtxezKAP3gRd8r5JPQI9FZlxyUcyf+Htkn2A4qyKhFElnP1z9
|
||||
j0YLFhL/gQMdtNIY7yhSb29PyuCG7noz25swrfxbA7ZVppM0J19JNhlpqmusBKLB
|
||||
NqN+KMD8EDli7NLX17S9Dj2pQF9I8fVXPEhkdTb67rr7y3OUANKjh0Opnt3JLoj9
|
||||
u1X7BfCeUSR/qq3E8RFPeyxvP1tc5QVqKBG6UPBo8nLQqdzJhUd8a1YEfXIUbdHL
|
||||
s2q5WVFu2MDavIp634+fzfcIFRoBtdxf5aCdxLaLocsA/aLLyL1pK6K0rpdrSUtc
|
||||
EBMST26qIlU0Ht+SQtaMdE2gyjkMM5RpLh1q/gpow+0j3zu6f5tDsu3qwgjLIx78
|
||||
YCTGUuCpsVMzFK4tahYSPNkCAwEAAQ==
|
||||
-----END rsa public key-----
|
||||
14
cryptor/rsa_public_example.pem
Normal file
14
cryptor/rsa_public_example.pem
Normal file
@@ -0,0 +1,14 @@
|
||||
-----BEGIN rsa public key-----
|
||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA8O2hLVt/7v6rQF2eGX3J
|
||||
+QyteV45lI/5qlyqTe7CiwUg1bkJ2yLHaoGSWoVtthE/X7GZgsw57VEgnazZAehe
|
||||
XcxV8OJWBGuRfmXabtKIzSBF8uZj+H3iPDvJB7NggMh8teYsdBSn9Cm/L5FesDvg
|
||||
/KxUKKfBU4LSrhOMhBauIoOSVU659y3kYgpzC/ormvCsJSe3KSdIhtLxiv+dnEHh
|
||||
9WvsXWB50lIw3TIC041aL5mwRvb5/JWcjtn5bgLEChrnEGF0RYUDMwm8hT1YsEN9
|
||||
Rb9fOx5ZTUZNRVHYjaCyUA+cG+gC72EjrkXY4If3pAESNcF+5NGGJaETd8rHzHsR
|
||||
FRVPXkwhRmkx9nBF+RjQhouqBTptIcIO2rPlTC9H5Oeg+jVljlBJ7/ESeOlg2OEb
|
||||
YyOUJpAPMPcz5HiNSTRHuDWIDUuk48BOF9Vo00PJq/oXRzUa9HHhu6cosfZH4P4P
|
||||
kDthxq89migqUL+C8WqeZ8I+xFS8/1TpvjOnOYF5ADvByC69UGPEoh+o+ZpgWfAs
|
||||
Crb9Qwy9WAAZg0rsouoPqb4Qk6Vr88X6I20Ys9AayLzZ72ddoS5IweOoF5/xLfii
|
||||
cGLKckNwG6sy4f1LCFB2Xc79nYqJzlwPGC3pKfEIAXUj1I/p+/MqaTDJQs72dhr0
|
||||
lTX5505XUBqrbX0BPGcZvIsCAwEAAQ==
|
||||
-----END rsa public key-----
|
||||
@@ -1,20 +0,0 @@
|
||||
package cryptor
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
|
||||
func TestRsaEncrypt(t *testing.T) {
|
||||
err := GenerateRsaKey(4096, "rsa_private.pem", "rsa_public.pem")
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
data := []byte("hello world")
|
||||
encrypted := RsaEncrypt(data, "rsa_public.pem")
|
||||
decrypted := RsaDecrypt(encrypted, "rsa_private.pem")
|
||||
|
||||
assert := internal.NewAssert(t, "TestRsaEncrypt")
|
||||
assert.Equal(string(data), string(decrypted))
|
||||
}
|
||||
217
datastructure/hashmap/hashmap.go
Normal file
217
datastructure/hashmap/hashmap.go
Normal file
@@ -0,0 +1,217 @@
|
||||
// Copyright 2021 dudaodong@gmail.com. All rights reserved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
// Package datastructure implements some data structure. hashmap structure.
|
||||
package datastructure
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var defaultMapCapacity uint64 = 1 << 10
|
||||
|
||||
type mapNode struct {
|
||||
key any
|
||||
value any
|
||||
next *mapNode
|
||||
}
|
||||
|
||||
// HashMap implements a hash map
|
||||
type HashMap struct {
|
||||
capacity uint64
|
||||
size uint64
|
||||
table []*mapNode
|
||||
}
|
||||
|
||||
// NewHashMap return a HashMap instance
|
||||
func NewHashMap() *HashMap {
|
||||
return &HashMap{
|
||||
capacity: defaultMapCapacity,
|
||||
table: make([]*mapNode, defaultMapCapacity),
|
||||
}
|
||||
}
|
||||
|
||||
// NewHashMapWithCapacity return a HashMap instance with given size and capacity
|
||||
func NewHashMapWithCapacity(size, capacity uint64) *HashMap {
|
||||
return &HashMap{
|
||||
size: size,
|
||||
capacity: capacity,
|
||||
table: make([]*mapNode, capacity),
|
||||
}
|
||||
}
|
||||
|
||||
// Get return the value of given key in hashmap
|
||||
func (hm *HashMap) Get(key any) any {
|
||||
hashValue := hm.hash(key)
|
||||
node := hm.table[hashValue]
|
||||
for node != nil {
|
||||
if reflect.DeepEqual(node.key, key) {
|
||||
return node.value
|
||||
}
|
||||
node = node.next
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetOrDefault return the value of given key in hashmap, if not found return default value
|
||||
func (hm *HashMap) GetOrDefault(key any, defaultValue any) any {
|
||||
value := hm.Get(key)
|
||||
if value == nil {
|
||||
return defaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Put new key value in hashmap
|
||||
func (hm *HashMap) Put(key any, value any) {
|
||||
hm.putValue(hm.hash(key), key, value)
|
||||
}
|
||||
|
||||
func (hm *HashMap) putValue(hash uint64, key, value any) {
|
||||
if hm.capacity == 0 {
|
||||
hm.capacity = defaultMapCapacity
|
||||
hm.table = make([]*mapNode, defaultMapCapacity)
|
||||
}
|
||||
|
||||
node := hm.table[hash]
|
||||
if node == nil {
|
||||
hm.table[hash] = newMapNode(key, value)
|
||||
} else if node.key == key {
|
||||
hm.table[hash] = newMapNodeWithNext(key, value, node)
|
||||
} else {
|
||||
hm.resize()
|
||||
hm.putValue(hash, value, value)
|
||||
}
|
||||
hm.size++
|
||||
}
|
||||
|
||||
// Delete item by given key in hashmap
|
||||
func (hm *HashMap) Delete(key any) {
|
||||
hash := hm.hash(key)
|
||||
node := hm.table[hash]
|
||||
if node == nil {
|
||||
return
|
||||
}
|
||||
|
||||
hm.table = append(hm.table[:hash], hm.table[hash+1:]...)
|
||||
hm.size--
|
||||
}
|
||||
|
||||
// Contains checks if given key is in hashmap or not
|
||||
func (hm *HashMap) Contains(key any) bool {
|
||||
node := hm.table[hm.hash(key)]
|
||||
for node != nil {
|
||||
if reflect.DeepEqual(node.key, key) {
|
||||
return true
|
||||
}
|
||||
node = node.next
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Iterate executes iteratee funcation for every key and value pair of hashmap (random order)
|
||||
func (hm *HashMap) Iterate(iteratee func(key, value any)) {
|
||||
if hm.size > 0 {
|
||||
for i := 0; i < len(hm.table); i++ {
|
||||
item := hm.table[i]
|
||||
if item != nil {
|
||||
iteratee(item.key, item.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FilterByValue returns a filtered HashMap.
|
||||
// If any value is not matching the perdicate function then it returns nil
|
||||
// otherwise it returns the HashMap with selected values.
|
||||
func (hm *HashMap) FilterByValue(perdicate func(value any) bool) *HashMap {
|
||||
var filteredHM *HashMap
|
||||
if hm.size > 0 {
|
||||
for i := 0; i < len(hm.table); i++ {
|
||||
item := hm.table[i]
|
||||
if item != nil && perdicate(item.value) {
|
||||
if filteredHM == nil {
|
||||
filteredHM = NewHashMap()
|
||||
}
|
||||
filteredHM.Put(item.key, item.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
return filteredHM
|
||||
}
|
||||
|
||||
// Keys returns a slice of the hashmap's keys (random order)
|
||||
func (hm *HashMap) Keys() []any {
|
||||
keys := make([]any, int(hm.size))
|
||||
index := 0
|
||||
if hm.size > 0 {
|
||||
hm.Iterate(func(key, value any) {
|
||||
keys[index] = key
|
||||
index++
|
||||
})
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
// Values returns a slice of the hashmap's keys (random order)
|
||||
func (hm *HashMap) Values() []any {
|
||||
values := make([]any, int(hm.size))
|
||||
index := 0
|
||||
if hm.size > 0 {
|
||||
hm.Iterate(func(key, value any) {
|
||||
values[index] = value
|
||||
index++
|
||||
})
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
|
||||
func (hm *HashMap) resize() {
|
||||
hm.capacity <<= 1
|
||||
|
||||
tempTable := hm.table
|
||||
|
||||
hm.table = make([]*mapNode, hm.capacity)
|
||||
|
||||
for i := 0; i < len(tempTable); i++ {
|
||||
node := tempTable[i]
|
||||
if node == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
hm.table[hm.hash(node.key)] = node
|
||||
}
|
||||
}
|
||||
|
||||
// Size returns current size of Hashmap
|
||||
func (hm *HashMap) Size() uint64 {
|
||||
return hm.size
|
||||
}
|
||||
|
||||
func (hm *HashMap) hash(key any) uint64 {
|
||||
h := fnv.New64a()
|
||||
_, _ = h.Write([]byte(fmt.Sprintf("%v", key)))
|
||||
|
||||
hashValue := h.Sum64()
|
||||
|
||||
return (hm.capacity - 1) & (hashValue ^ (hashValue >> 16))
|
||||
}
|
||||
|
||||
func newMapNode(key, value any) *mapNode {
|
||||
return &mapNode{
|
||||
key: key,
|
||||
value: value,
|
||||
}
|
||||
}
|
||||
|
||||
func newMapNodeWithNext(key, value any, next *mapNode) *mapNode {
|
||||
return &mapNode{
|
||||
key: key,
|
||||
value: value,
|
||||
next: next,
|
||||
}
|
||||
}
|
||||
128
datastructure/hashmap/hashmap_test.go
Normal file
128
datastructure/hashmap/hashmap_test.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package datastructure
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
|
||||
func TestHashMap_PutAndGet(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestHashMap_PutAndGet")
|
||||
|
||||
hm := NewHashMap()
|
||||
|
||||
hm.Put("abc", 3)
|
||||
assert.Equal(3, hm.Get("abc"))
|
||||
assert.IsNil(hm.Get("abcd"))
|
||||
|
||||
hm.Put("abc", 4)
|
||||
assert.Equal(4, hm.Get("abc"))
|
||||
}
|
||||
|
||||
func TestHashMap_Resize(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestHashMap_Resize")
|
||||
|
||||
hm := NewHashMapWithCapacity(3, 3)
|
||||
|
||||
for i := 0; i < 20; i++ {
|
||||
hm.Put(i, 10)
|
||||
}
|
||||
|
||||
assert.Equal(10, hm.Get(5))
|
||||
}
|
||||
|
||||
func TestHashMap_Delete(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestHashMap_Delete")
|
||||
|
||||
hm := NewHashMap()
|
||||
|
||||
hm.Put("abc", 3)
|
||||
assert.Equal(3, hm.Get("abc"))
|
||||
|
||||
hm.Delete("abc")
|
||||
assert.IsNil(hm.Get("abc"))
|
||||
}
|
||||
|
||||
func TestHashMap_Contains(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestHashMap_Contains")
|
||||
|
||||
hm := NewHashMap()
|
||||
assert.Equal(false, hm.Contains("abc"))
|
||||
|
||||
hm.Put("abc", 3)
|
||||
assert.Equal(true, hm.Contains("abc"))
|
||||
}
|
||||
|
||||
func TestHashMap_KeysValues(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestHashMap_KeysValues")
|
||||
|
||||
hm := NewHashMap()
|
||||
|
||||
hm.Put("a", 1)
|
||||
hm.Put("b", 2)
|
||||
hm.Put("c", 3)
|
||||
|
||||
keys := hm.Keys()
|
||||
values := hm.Values()
|
||||
|
||||
assert.Equal(3, len(values))
|
||||
assert.Equal(3, len(keys))
|
||||
}
|
||||
|
||||
func TestHashMap_Keys(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestHashMap_Keys")
|
||||
|
||||
hm := NewHashMap()
|
||||
|
||||
hm.Put("a", 1)
|
||||
hm.Put("b", 2)
|
||||
hm.Put("c", 3)
|
||||
|
||||
keys := hm.Keys()
|
||||
|
||||
assert.Equal(3, len(keys))
|
||||
}
|
||||
|
||||
func TestHashMap_GetOrDefault(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestHashMap_GetOrDefault")
|
||||
|
||||
hm := NewHashMap()
|
||||
|
||||
hm.Put("a", 1)
|
||||
hm.Put("b", 2)
|
||||
hm.Put("c", 3)
|
||||
|
||||
assert.Equal(1, hm.GetOrDefault("a", 5))
|
||||
assert.Equal(5, hm.GetOrDefault("d", 5))
|
||||
}
|
||||
|
||||
func TestHashMap_FilterByValue(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestHashMap_FilterByValue")
|
||||
|
||||
hm := 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
|
||||
})
|
||||
|
||||
assert.Equal(uint64(2), filteredHM.Size())
|
||||
}
|
||||
203
datastructure/heap/maxheap.go
Normal file
203
datastructure/heap/maxheap.go
Normal file
@@ -0,0 +1,203 @@
|
||||
// Copyright 2021 dudaodong@gmail.com. All rights reserved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
// Package datastructure implements some data structure. MaxHeap is a binary max heap.
|
||||
package datastructure
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/duke-git/lancet/v2/constraints"
|
||||
)
|
||||
|
||||
// MaxHeap implements a binary max heap
|
||||
// type T should implements Compare function in constraints.Comparator interface.
|
||||
type MaxHeap[T any] struct {
|
||||
data []T
|
||||
comparator constraints.Comparator
|
||||
}
|
||||
|
||||
// NewMaxHeap returns a MaxHeap instance with the given comparator.
|
||||
func NewMaxHeap[T any](comparator constraints.Comparator) *MaxHeap[T] {
|
||||
return &MaxHeap[T]{
|
||||
data: make([]T, 0),
|
||||
comparator: comparator,
|
||||
}
|
||||
}
|
||||
|
||||
// BuildMaxHeap builds a MaxHeap instance with data and given comparator.
|
||||
func BuildMaxHeap[T any](data []T, comparator constraints.Comparator) *MaxHeap[T] {
|
||||
heap := &MaxHeap[T]{
|
||||
data: make([]T, 0, len(data)),
|
||||
comparator: comparator,
|
||||
}
|
||||
|
||||
for _, v := range data {
|
||||
heap.Push(v)
|
||||
}
|
||||
|
||||
return heap
|
||||
}
|
||||
|
||||
// Push value into the heap
|
||||
func (h *MaxHeap[T]) Push(value T) {
|
||||
h.data = append(h.data, value)
|
||||
h.heapifyUp(len(h.data) - 1)
|
||||
}
|
||||
|
||||
// heapifyUp heapify the data from bottom to top
|
||||
func (h *MaxHeap[T]) heapifyUp(i int) {
|
||||
for h.comparator.Compare(h.data[parentIndex(i)], h.data[i]) < 0 {
|
||||
h.swap(parentIndex(i), i)
|
||||
i = parentIndex(i)
|
||||
}
|
||||
}
|
||||
|
||||
// Pop return the largest value, and remove it from the heap
|
||||
// if heap is empty, return zero value and fasle
|
||||
func (h *MaxHeap[T]) Pop() (T, bool) {
|
||||
var val T
|
||||
if h.Size() == 0 {
|
||||
return val, false
|
||||
}
|
||||
|
||||
val = h.data[0]
|
||||
l := len(h.data) - 1
|
||||
|
||||
h.data[0] = h.data[l]
|
||||
h.data = h.data[:l]
|
||||
h.heapifyDown(0)
|
||||
|
||||
return val, true
|
||||
}
|
||||
|
||||
// heapifyDown heapify the data from top to bottom
|
||||
func (h *MaxHeap[T]) heapifyDown(i int) {
|
||||
lastIndex := len(h.data) - 1
|
||||
l, r := leftChildIndex(i), rightChildIndex(i)
|
||||
childToCompare := 0
|
||||
|
||||
for l <= lastIndex {
|
||||
if l == lastIndex {
|
||||
childToCompare = l
|
||||
} else if h.comparator.Compare(h.data[l], h.data[r]) > 0 {
|
||||
childToCompare = l
|
||||
} else {
|
||||
childToCompare = r
|
||||
}
|
||||
|
||||
if h.comparator.Compare(h.data[i], h.data[childToCompare]) < 0 {
|
||||
h.swap(i, childToCompare)
|
||||
i = childToCompare
|
||||
l, r = leftChildIndex(i), rightChildIndex(i)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Peek returns the largest element from the heap without removing it.
|
||||
// if heap is empty, it returns zero value and false.
|
||||
func (h *MaxHeap[T]) Peek() (T, bool) {
|
||||
if h.Size() == 0 {
|
||||
var val T
|
||||
return val, false
|
||||
}
|
||||
|
||||
return h.data[0], true
|
||||
}
|
||||
|
||||
// Size return the number of elements in the heap
|
||||
func (h *MaxHeap[T]) Size() int {
|
||||
return len(h.data)
|
||||
}
|
||||
|
||||
// Data return data of the heap
|
||||
func (h *MaxHeap[T]) Data() []T {
|
||||
return h.data
|
||||
}
|
||||
|
||||
// PrintStructure print the structure of the heap
|
||||
func (h *MaxHeap[T]) PrintStructure() {
|
||||
level := 1
|
||||
data := h.data
|
||||
length := len(h.data)
|
||||
index := 0
|
||||
|
||||
list := [][]string{}
|
||||
temp := []string{}
|
||||
for index < length {
|
||||
start := powerTwo(level-1) - 1
|
||||
end := start + powerTwo(level-1) - 1
|
||||
|
||||
temp = append(temp, fmt.Sprintf("%v", data[index]))
|
||||
index++
|
||||
|
||||
if index > end || index >= length {
|
||||
list = append(list, temp)
|
||||
temp = []string{}
|
||||
|
||||
if index < length {
|
||||
level++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastNum := powerTwo(level - 1)
|
||||
lastLen := lastNum + (lastNum - 1)
|
||||
|
||||
heapTree := make([][]string, level)
|
||||
for i := 0; i < level; i++ {
|
||||
heapTree[i] = make([]string, lastLen)
|
||||
for j := 0; j < lastLen; j++ {
|
||||
heapTree[i][j] = ""
|
||||
}
|
||||
}
|
||||
|
||||
for k := 0; k < len(list); k++ {
|
||||
vals := list[k]
|
||||
tempLevel := level - k
|
||||
st := powerTwo(tempLevel-1) - 1
|
||||
for _, v := range vals {
|
||||
heapTree[k][st] = v
|
||||
gap := powerTwo(tempLevel)
|
||||
st = st + gap
|
||||
}
|
||||
}
|
||||
|
||||
for m := 0; m < level; m++ {
|
||||
for n := 0; n < lastLen; n++ {
|
||||
val := heapTree[m][n]
|
||||
if val == "" {
|
||||
fmt.Print(" ")
|
||||
} else {
|
||||
fmt.Print(val)
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
// parentIndex get parent index of the given index
|
||||
func parentIndex(i int) int {
|
||||
return (i - 1) / 2
|
||||
}
|
||||
|
||||
// leftChildIndex get left child index of the given index
|
||||
func leftChildIndex(i int) int {
|
||||
return 2*i + 1
|
||||
}
|
||||
|
||||
// rightChildIndex get right child index of the given index
|
||||
func rightChildIndex(i int) int {
|
||||
return 2*i + 2
|
||||
}
|
||||
|
||||
// swap two elements in the heap
|
||||
func (h *MaxHeap[T]) swap(i, j int) {
|
||||
h.data[i], h.data[j] = h.data[j], h.data[i]
|
||||
}
|
||||
|
||||
func powerTwo(n int) int {
|
||||
return 1 << n
|
||||
}
|
||||
98
datastructure/heap/maxheap_test.go
Normal file
98
datastructure/heap/maxheap_test.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package datastructure
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1, v2 any) int {
|
||||
val1, _ := v1.(int)
|
||||
val2, _ := v2.(int)
|
||||
|
||||
if val1 < val2 {
|
||||
return -1
|
||||
} else if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func TestMaxHeap_BuildMaxHeap(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestMaxHeap_BuildMaxHeap")
|
||||
|
||||
values := []int{6, 5, 2, 4, 7, 10, 12, 1, 3, 8, 9, 11}
|
||||
heap := BuildMaxHeap(values, &intComparator{})
|
||||
|
||||
expected := []int{12, 9, 11, 4, 8, 10, 7, 1, 3, 5, 6, 2}
|
||||
assert.Equal(expected, heap.data)
|
||||
|
||||
assert.Equal(12, heap.Size())
|
||||
}
|
||||
|
||||
func TestMaxHeap_Push(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestMaxHeap_Push")
|
||||
|
||||
heap := NewMaxHeap[int](&intComparator{})
|
||||
values := []int{6, 5, 2, 4, 7, 10, 12, 1, 3, 8, 9, 11}
|
||||
|
||||
for _, v := range values {
|
||||
heap.Push(v)
|
||||
}
|
||||
|
||||
expected := []int{12, 9, 11, 4, 8, 10, 7, 1, 3, 5, 6, 2}
|
||||
assert.Equal(expected, heap.data)
|
||||
|
||||
assert.Equal(12, heap.Size())
|
||||
|
||||
heap.PrintStructure()
|
||||
}
|
||||
|
||||
func TestMaxHeap_Pop(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestMaxHeap_Pop")
|
||||
|
||||
heap := NewMaxHeap[int](&intComparator{})
|
||||
|
||||
_, ok := heap.Pop()
|
||||
assert.Equal(false, ok)
|
||||
|
||||
values := []int{6, 5, 2, 4, 7, 10, 12, 1, 3, 8, 9, 11}
|
||||
for _, v := range values {
|
||||
heap.Push(v)
|
||||
}
|
||||
|
||||
val, ok := heap.Pop()
|
||||
assert.Equal(12, val)
|
||||
assert.Equal(true, ok)
|
||||
assert.Equal(11, heap.Size())
|
||||
}
|
||||
|
||||
func TestMaxHeap_Peek(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestMaxHeap_Peek")
|
||||
|
||||
heap := NewMaxHeap[int](&intComparator{})
|
||||
|
||||
_, ok := heap.Peek()
|
||||
assert.Equal(false, ok)
|
||||
|
||||
values := []int{6, 5, 2, 4, 7, 10, 12, 1, 3, 8, 9, 11}
|
||||
for _, v := range values {
|
||||
heap.Push(v)
|
||||
}
|
||||
|
||||
val, ok := heap.Peek()
|
||||
assert.Equal(12, val)
|
||||
assert.Equal(true, ok)
|
||||
|
||||
assert.Equal(12, heap.Size())
|
||||
}
|
||||
@@ -1,13 +1,16 @@
|
||||
// Copyright 2021 dudaodong@gmail.com. All rights reserved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
// Package datastructure contains some data structure. Link structure contains SinglyLink and DoublyLink.
|
||||
package datastructure
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/duke-git/lancet/v2/datastructure"
|
||||
)
|
||||
|
||||
// DoublyLink is a linked list. Whose node has a generic Value, Pre pointer points to a previous node of the link, Next pointer points to a next node of the link.
|
||||
// DoublyLink is a linked list. Whose node has a generic Value, Pre pointer points to a previous node of the dl, Next pointer points to a next node of the dl.
|
||||
type DoublyLink[T any] struct {
|
||||
Head *datastructure.LinkNode[T]
|
||||
length int
|
||||
@@ -19,30 +22,30 @@ func NewDoublyLink[T any]() *DoublyLink[T] {
|
||||
}
|
||||
|
||||
// InsertAtHead insert value into doubly linklist at head index
|
||||
func (link *DoublyLink[T]) InsertAtHead(value T) {
|
||||
func (dl *DoublyLink[T]) InsertAtHead(value T) {
|
||||
newNode := datastructure.NewLinkNode(value)
|
||||
size := link.Size()
|
||||
size := dl.Size()
|
||||
|
||||
if size == 0 {
|
||||
link.Head = newNode
|
||||
link.length++
|
||||
dl.Head = newNode
|
||||
dl.length++
|
||||
return
|
||||
}
|
||||
|
||||
newNode.Next = link.Head
|
||||
newNode.Next = dl.Head
|
||||
newNode.Pre = nil
|
||||
|
||||
link.Head.Pre = newNode
|
||||
link.Head = newNode
|
||||
dl.Head.Pre = newNode
|
||||
dl.Head = newNode
|
||||
|
||||
link.length++
|
||||
dl.length++
|
||||
}
|
||||
|
||||
// InsertAtTail insert value into doubly linklist at tail index
|
||||
func (link *DoublyLink[T]) InsertAtTail(value T) {
|
||||
current := link.Head
|
||||
func (dl *DoublyLink[T]) InsertAtTail(value T) {
|
||||
current := dl.Head
|
||||
if current == nil {
|
||||
link.InsertAtHead(value)
|
||||
dl.InsertAtHead(value)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -55,28 +58,29 @@ func (link *DoublyLink[T]) InsertAtTail(value T) {
|
||||
newNode.Pre = current
|
||||
current.Next = newNode
|
||||
|
||||
link.length++
|
||||
dl.length++
|
||||
}
|
||||
|
||||
// InsertAt insert value into doubly linklist at index
|
||||
func (link *DoublyLink[T]) InsertAt(index int, value T) error {
|
||||
size := link.length
|
||||
// param `index` should between [0, length], if index do not meet the conditions, do nothing
|
||||
func (dl *DoublyLink[T]) InsertAt(index int, value T) {
|
||||
size := dl.length
|
||||
if index < 0 || index > size {
|
||||
return errors.New("param index should between 0 and the length of doubly link.")
|
||||
return
|
||||
}
|
||||
|
||||
if index == 0 {
|
||||
link.InsertAtHead(value)
|
||||
return nil
|
||||
dl.InsertAtHead(value)
|
||||
return
|
||||
}
|
||||
|
||||
if index == size {
|
||||
link.InsertAtTail(value)
|
||||
return nil
|
||||
dl.InsertAtTail(value)
|
||||
return
|
||||
}
|
||||
|
||||
i := 0
|
||||
current := link.Head
|
||||
current := dl.Head
|
||||
|
||||
for current != nil {
|
||||
if i == index-1 {
|
||||
@@ -85,38 +89,36 @@ func (link *DoublyLink[T]) InsertAt(index int, value T) error {
|
||||
newNode.Pre = current
|
||||
|
||||
current.Next = newNode
|
||||
link.length++
|
||||
dl.length++
|
||||
|
||||
return nil
|
||||
return
|
||||
}
|
||||
i++
|
||||
current = current.Next
|
||||
}
|
||||
|
||||
return errors.New("doubly link list no exist")
|
||||
}
|
||||
|
||||
// DeleteAtHead delete value in doubly linklist at head index
|
||||
func (link *DoublyLink[T]) DeleteAtHead() error {
|
||||
if link.Head == nil {
|
||||
return errors.New("doubly link list no exist")
|
||||
func (dl *DoublyLink[T]) DeleteAtHead() {
|
||||
if dl.Head == nil {
|
||||
return
|
||||
}
|
||||
current := link.Head
|
||||
link.Head = current.Next
|
||||
link.Head.Pre = nil
|
||||
link.length--
|
||||
|
||||
return nil
|
||||
current := dl.Head
|
||||
dl.Head = current.Next
|
||||
dl.Head.Pre = nil
|
||||
dl.length--
|
||||
}
|
||||
|
||||
// DeleteAtTail delete value in doubly linklist at tail index
|
||||
func (link *DoublyLink[T]) DeleteAtTail() error {
|
||||
if link.Head == nil {
|
||||
return errors.New("doubly link list no exist")
|
||||
// DeleteAtTail delete value in doubly linklist at tail
|
||||
func (dl *DoublyLink[T]) DeleteAtTail() {
|
||||
if dl.Head == nil {
|
||||
return
|
||||
}
|
||||
current := link.Head
|
||||
|
||||
current := dl.Head
|
||||
if current.Next == nil {
|
||||
return link.DeleteAtHead()
|
||||
dl.DeleteAtHead()
|
||||
}
|
||||
|
||||
for current.Next.Next != nil {
|
||||
@@ -124,45 +126,44 @@ func (link *DoublyLink[T]) DeleteAtTail() error {
|
||||
}
|
||||
|
||||
current.Next = nil
|
||||
link.length--
|
||||
return nil
|
||||
dl.length--
|
||||
}
|
||||
|
||||
// DeleteAt delete value in doubly linklist at index
|
||||
func (link *DoublyLink[T]) DeleteAt(index int) error {
|
||||
if link.Head == nil {
|
||||
return errors.New("doubly link list no exist")
|
||||
// param `index` should be [0, len(DoublyLink)-1]
|
||||
func (dl *DoublyLink[T]) DeleteAt(index int) {
|
||||
if dl.Head == nil {
|
||||
return
|
||||
}
|
||||
current := link.Head
|
||||
|
||||
current := dl.Head
|
||||
if current.Next == nil || index == 0 {
|
||||
return link.DeleteAtHead()
|
||||
dl.DeleteAtHead()
|
||||
}
|
||||
|
||||
if index == link.length-1 {
|
||||
return link.DeleteAtTail()
|
||||
if index == dl.length-1 {
|
||||
dl.DeleteAtTail()
|
||||
}
|
||||
|
||||
if index < 0 || index > link.length-1 {
|
||||
return errors.New("param index should between 0 and link size -1.")
|
||||
if index < 0 || index > dl.length-1 {
|
||||
return
|
||||
}
|
||||
|
||||
i := 0
|
||||
for current != nil {
|
||||
if i == index-1 {
|
||||
current.Next = current.Next.Next
|
||||
link.length--
|
||||
return nil
|
||||
dl.length--
|
||||
return
|
||||
}
|
||||
i++
|
||||
current = current.Next
|
||||
}
|
||||
|
||||
return errors.New("delete error")
|
||||
}
|
||||
|
||||
// Reverse the linked list
|
||||
func (link *DoublyLink[T]) Reverse() {
|
||||
current := link.Head
|
||||
func (dl *DoublyLink[T]) Reverse() {
|
||||
current := dl.Head
|
||||
var temp *datastructure.LinkNode[T]
|
||||
|
||||
for current != nil {
|
||||
@@ -173,20 +174,20 @@ func (link *DoublyLink[T]) Reverse() {
|
||||
}
|
||||
|
||||
if temp != nil {
|
||||
link.Head = temp.Pre
|
||||
dl.Head = temp.Pre
|
||||
}
|
||||
}
|
||||
|
||||
// GetMiddleNode return node at middle index of linked list
|
||||
func (link *DoublyLink[T]) GetMiddleNode() *datastructure.LinkNode[T] {
|
||||
if link.Head == nil {
|
||||
func (dl *DoublyLink[T]) GetMiddleNode() *datastructure.LinkNode[T] {
|
||||
if dl.Head == nil {
|
||||
return nil
|
||||
}
|
||||
if link.Head.Next == nil {
|
||||
return link.Head
|
||||
if dl.Head.Next == nil {
|
||||
return dl.Head
|
||||
}
|
||||
fast := link.Head
|
||||
slow := link.Head
|
||||
fast := dl.Head
|
||||
slow := dl.Head
|
||||
|
||||
for fast != nil {
|
||||
fast = fast.Next
|
||||
@@ -202,24 +203,24 @@ func (link *DoublyLink[T]) GetMiddleNode() *datastructure.LinkNode[T] {
|
||||
}
|
||||
|
||||
// Size return the count of doubly linked list
|
||||
func (link *DoublyLink[T]) Size() int {
|
||||
return link.length
|
||||
func (dl *DoublyLink[T]) Size() int {
|
||||
return dl.length
|
||||
}
|
||||
|
||||
// Values return slice of all doubly linklist node value
|
||||
func (link *DoublyLink[T]) Values() []T {
|
||||
res := []T{}
|
||||
current := link.Head
|
||||
func (dl *DoublyLink[T]) Values() []T {
|
||||
result := make([]T, 0, dl.length)
|
||||
current := dl.Head
|
||||
for current != nil {
|
||||
res = append(res, current.Value)
|
||||
result = append(result, current.Value)
|
||||
current = current.Next
|
||||
}
|
||||
return res
|
||||
return result
|
||||
}
|
||||
|
||||
// Print all nodes info of a linked list
|
||||
func (link *DoublyLink[T]) Print() {
|
||||
current := link.Head
|
||||
func (dl *DoublyLink[T]) Print() {
|
||||
current := dl.Head
|
||||
info := "[ "
|
||||
for current != nil {
|
||||
info += fmt.Sprintf("%+v, ", current)
|
||||
@@ -229,13 +230,13 @@ func (link *DoublyLink[T]) Print() {
|
||||
fmt.Println(info)
|
||||
}
|
||||
|
||||
// IsEmpty checks if link is empty or not
|
||||
func (link *DoublyLink[T]) IsEmpty() bool {
|
||||
return link.length == 0
|
||||
// IsEmpty checks if dl is empty or not
|
||||
func (dl *DoublyLink[T]) IsEmpty() bool {
|
||||
return dl.length == 0
|
||||
}
|
||||
|
||||
// Clear checks if link is empty or not
|
||||
func (link *DoublyLink[T]) Clear() {
|
||||
link.Head = nil
|
||||
link.length = 0
|
||||
// Clear all nodes in doubly linklist
|
||||
func (dl *DoublyLink[T]) Clear() {
|
||||
dl.Head = nil
|
||||
dl.length = 0
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
)
|
||||
|
||||
func TestDoublyLink_InsertAtFirst(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestDoublyLink_InsertAtFirst")
|
||||
|
||||
link := NewDoublyLink[int]()
|
||||
@@ -22,6 +24,8 @@ func TestDoublyLink_InsertAtFirst(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDoublyLink_InsertAtTail(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestDoublyLink_InsertAtTail")
|
||||
|
||||
link := NewDoublyLink[int]()
|
||||
@@ -37,33 +41,30 @@ func TestDoublyLink_InsertAtTail(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDoublyLink_InsertAt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestDoublyLink_InsertAt")
|
||||
|
||||
link := NewDoublyLink[int]()
|
||||
|
||||
err := link.InsertAt(1, 1)
|
||||
assert.IsNotNil(err)
|
||||
|
||||
link.InsertAt(1, 1) //do nothing
|
||||
link.InsertAt(0, 1)
|
||||
link.InsertAt(1, 2)
|
||||
link.InsertAt(2, 4)
|
||||
link.InsertAt(2, 3)
|
||||
|
||||
link.Print()
|
||||
|
||||
expected := []int{1, 2, 3, 4}
|
||||
values := link.Values()
|
||||
|
||||
assert.Equal(expected, values)
|
||||
|
||||
}
|
||||
|
||||
func TestDoublyLink_DeleteAtHead(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestDoublyLink_DeleteAtHead")
|
||||
|
||||
link := NewDoublyLink[int]()
|
||||
err := link.DeleteAtHead()
|
||||
assert.IsNotNil(err)
|
||||
link.DeleteAtHead()
|
||||
|
||||
link.InsertAtTail(1)
|
||||
link.InsertAtTail(2)
|
||||
@@ -71,7 +72,6 @@ func TestDoublyLink_DeleteAtHead(t *testing.T) {
|
||||
link.InsertAtTail(4)
|
||||
|
||||
link.DeleteAtHead()
|
||||
link.Print()
|
||||
|
||||
expected := []int{2, 3, 4}
|
||||
values := link.Values()
|
||||
@@ -80,11 +80,12 @@ func TestDoublyLink_DeleteAtHead(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDoublyLink_DeleteAtTail(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestDoublyLink_DeleteAtTail")
|
||||
|
||||
link := NewDoublyLink[int]()
|
||||
err := link.DeleteAtTail()
|
||||
assert.IsNotNil(err)
|
||||
link.DeleteAtTail()
|
||||
|
||||
link.InsertAtTail(1)
|
||||
link.InsertAtTail(2)
|
||||
@@ -92,7 +93,6 @@ func TestDoublyLink_DeleteAtTail(t *testing.T) {
|
||||
link.InsertAtTail(4)
|
||||
|
||||
link.DeleteAtTail()
|
||||
link.Print()
|
||||
|
||||
expected := []int{1, 2, 3}
|
||||
values := link.Values()
|
||||
@@ -101,11 +101,12 @@ func TestDoublyLink_DeleteAtTail(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDoublyLink_DeleteAt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestDoublyLink_DeleteAt")
|
||||
|
||||
link := NewDoublyLink[int]()
|
||||
err := link.DeleteAt(0)
|
||||
assert.IsNotNil(err)
|
||||
link.DeleteAt(0)
|
||||
|
||||
link.InsertAtTail(1)
|
||||
link.InsertAtTail(2)
|
||||
@@ -113,11 +114,7 @@ func TestDoublyLink_DeleteAt(t *testing.T) {
|
||||
link.InsertAtTail(4)
|
||||
link.InsertAtTail(5)
|
||||
|
||||
err = link.DeleteAt(5)
|
||||
assert.IsNotNil(err)
|
||||
|
||||
err = link.DeleteAt(0)
|
||||
assert.IsNil(err)
|
||||
link.DeleteAt(0)
|
||||
assert.Equal([]int{2, 3, 4, 5}, link.Values())
|
||||
|
||||
link.DeleteAt(3)
|
||||
@@ -129,6 +126,8 @@ func TestDoublyLink_DeleteAt(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDoublyLink_Reverse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestDoublyLink_Reverse")
|
||||
|
||||
link := NewDoublyLink[int]()
|
||||
@@ -143,6 +142,8 @@ func TestDoublyLink_Reverse(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDoublyLink_GetMiddleNode(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestDoublyLink_GetMiddleNode")
|
||||
|
||||
link := NewDoublyLink[int]()
|
||||
@@ -162,10 +163,11 @@ func TestDoublyLink_GetMiddleNode(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDoublyLink_Clear(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestDoublyLink_Clear")
|
||||
|
||||
link := NewDoublyLink[int]()
|
||||
|
||||
assert.Equal(true, link.IsEmpty())
|
||||
assert.Equal(0, link.Size())
|
||||
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
// Copyright 2021 dudaodong@gmail.com. All rights reserved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
// Package datastructure contains some data structure. Link structure contains SinglyLink and DoublyLink.
|
||||
package datastructure
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/duke-git/lancet/v2/datastructure"
|
||||
)
|
||||
|
||||
// SinglyLink is a linked list. Whose node has a Value generics and Next pointer points to a next node of the link.
|
||||
// SinglyLink is a linked list. Whose node has a Value generics and Next pointer points to a next node of the sl.
|
||||
type SinglyLink[T any] struct {
|
||||
Head *datastructure.LinkNode[T]
|
||||
length int
|
||||
@@ -20,18 +23,18 @@ func NewSinglyLink[T any]() *SinglyLink[T] {
|
||||
}
|
||||
|
||||
// InsertAtHead insert value into singly linklist at head index
|
||||
func (link *SinglyLink[T]) InsertAtHead(value T) {
|
||||
func (sl *SinglyLink[T]) InsertAtHead(value T) {
|
||||
newNode := datastructure.NewLinkNode(value)
|
||||
newNode.Next = link.Head
|
||||
link.Head = newNode
|
||||
link.length++
|
||||
newNode.Next = sl.Head
|
||||
sl.Head = newNode
|
||||
sl.length++
|
||||
}
|
||||
|
||||
// InsertAtTail insert value into singly linklist at tail index
|
||||
func (link *SinglyLink[T]) InsertAtTail(value T) {
|
||||
current := link.Head
|
||||
func (sl *SinglyLink[T]) InsertAtTail(value T) {
|
||||
current := sl.Head
|
||||
if current == nil {
|
||||
link.InsertAtHead(value)
|
||||
sl.InsertAtHead(value)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -43,65 +46,63 @@ func (link *SinglyLink[T]) InsertAtTail(value T) {
|
||||
newNode.Next = nil
|
||||
current.Next = newNode
|
||||
|
||||
link.length++
|
||||
sl.length++
|
||||
}
|
||||
|
||||
// InsertAt insert value into singly linklist at index
|
||||
func (link *SinglyLink[T]) InsertAt(index int, value T) error {
|
||||
size := link.length
|
||||
// param `index` should between [0, len(SinglyLink)], if index do not meet the conditions, do nothing
|
||||
func (sl *SinglyLink[T]) InsertAt(index int, value T) {
|
||||
size := sl.length
|
||||
if index < 0 || index > size {
|
||||
return errors.New("param index should between 0 and the length of singly link.")
|
||||
return
|
||||
}
|
||||
|
||||
if index == 0 {
|
||||
link.InsertAtHead(value)
|
||||
return nil
|
||||
sl.InsertAtHead(value)
|
||||
return
|
||||
}
|
||||
|
||||
if index == size {
|
||||
link.InsertAtTail(value)
|
||||
return nil
|
||||
sl.InsertAtTail(value)
|
||||
return
|
||||
}
|
||||
|
||||
i := 0
|
||||
current := link.Head
|
||||
current := sl.Head
|
||||
|
||||
for current != nil {
|
||||
if i == index-1 {
|
||||
newNode := datastructure.NewLinkNode(value)
|
||||
newNode.Next = current.Next
|
||||
current.Next = newNode
|
||||
link.length++
|
||||
|
||||
return nil
|
||||
sl.length++
|
||||
return
|
||||
}
|
||||
i++
|
||||
current = current.Next
|
||||
}
|
||||
|
||||
return errors.New("singly link list no exist")
|
||||
}
|
||||
|
||||
// DeleteAtHead delete value in singly linklist at head index
|
||||
func (link *SinglyLink[T]) DeleteAtHead() error {
|
||||
if link.Head == nil {
|
||||
return errors.New("singly link list no exist")
|
||||
func (sl *SinglyLink[T]) DeleteAtHead() {
|
||||
if sl.Head == nil {
|
||||
return
|
||||
}
|
||||
current := link.Head
|
||||
link.Head = current.Next
|
||||
link.length--
|
||||
|
||||
return nil
|
||||
current := sl.Head
|
||||
sl.Head = current.Next
|
||||
sl.length--
|
||||
}
|
||||
|
||||
// DeleteAtTail delete value in singly linklist at tail index
|
||||
func (link *SinglyLink[T]) DeleteAtTail() error {
|
||||
if link.Head == nil {
|
||||
return errors.New("singly link list no exist")
|
||||
// DeleteAtTail delete value in singly linklist at tail
|
||||
func (sl *SinglyLink[T]) DeleteAtTail() {
|
||||
if sl.Head == nil {
|
||||
return
|
||||
}
|
||||
current := link.Head
|
||||
|
||||
current := sl.Head
|
||||
if current.Next == nil {
|
||||
return link.DeleteAtHead()
|
||||
sl.DeleteAtHead()
|
||||
}
|
||||
|
||||
for current.Next.Next != nil {
|
||||
@@ -109,68 +110,66 @@ func (link *SinglyLink[T]) DeleteAtTail() error {
|
||||
}
|
||||
|
||||
current.Next = nil
|
||||
link.length--
|
||||
return nil
|
||||
sl.length--
|
||||
}
|
||||
|
||||
// DeleteAt delete value in singly linklist at index
|
||||
func (link *SinglyLink[T]) DeleteAt(index int) error {
|
||||
if link.Head == nil {
|
||||
return errors.New("singly link list no exist")
|
||||
// param `index` should be [0, len(SinglyLink)-1]
|
||||
func (sl *SinglyLink[T]) DeleteAt(index int) {
|
||||
if sl.Head == nil {
|
||||
return
|
||||
}
|
||||
current := link.Head
|
||||
current := sl.Head
|
||||
if current.Next == nil || index == 0 {
|
||||
return link.DeleteAtHead()
|
||||
sl.DeleteAtHead()
|
||||
}
|
||||
|
||||
if index == link.length-1 {
|
||||
return link.DeleteAtTail()
|
||||
if index == sl.length-1 {
|
||||
sl.DeleteAtTail()
|
||||
}
|
||||
|
||||
if index < 0 || index > link.length-1 {
|
||||
return errors.New("param index should between 0 and link size -1.")
|
||||
if index < 0 || index > sl.length-1 {
|
||||
return
|
||||
}
|
||||
|
||||
i := 0
|
||||
for current != nil {
|
||||
if i == index-1 {
|
||||
current.Next = current.Next.Next
|
||||
link.length--
|
||||
return nil
|
||||
sl.length--
|
||||
return
|
||||
}
|
||||
i++
|
||||
current = current.Next
|
||||
}
|
||||
|
||||
return errors.New("delete error")
|
||||
}
|
||||
|
||||
// DeleteValue delete value in singly linklist
|
||||
func (link *SinglyLink[T]) DeleteValue(value T) {
|
||||
if link.Head == nil {
|
||||
func (sl *SinglyLink[T]) DeleteValue(value T) {
|
||||
if sl.Head == nil {
|
||||
return
|
||||
}
|
||||
dummyHead := datastructure.NewLinkNode(value)
|
||||
dummyHead.Next = link.Head
|
||||
dummyHead.Next = sl.Head
|
||||
current := dummyHead
|
||||
|
||||
for current.Next != nil {
|
||||
if reflect.DeepEqual(current.Next.Value, value) {
|
||||
current.Next = current.Next.Next
|
||||
link.length--
|
||||
sl.length--
|
||||
} else {
|
||||
current = current.Next
|
||||
}
|
||||
}
|
||||
|
||||
link.Head = dummyHead.Next
|
||||
sl.Head = dummyHead.Next
|
||||
}
|
||||
|
||||
// Reverse the linked list
|
||||
func (link *SinglyLink[T]) Reverse() {
|
||||
func (sl *SinglyLink[T]) Reverse() {
|
||||
var pre, next *datastructure.LinkNode[T]
|
||||
|
||||
current := link.Head
|
||||
current := sl.Head
|
||||
|
||||
for current != nil {
|
||||
next = current.Next
|
||||
@@ -179,19 +178,19 @@ func (link *SinglyLink[T]) Reverse() {
|
||||
current = next
|
||||
}
|
||||
|
||||
link.Head = pre
|
||||
sl.Head = pre
|
||||
}
|
||||
|
||||
// GetMiddleNode return node at middle index of linked list
|
||||
func (link *SinglyLink[T]) GetMiddleNode() *datastructure.LinkNode[T] {
|
||||
if link.Head == nil {
|
||||
func (sl *SinglyLink[T]) GetMiddleNode() *datastructure.LinkNode[T] {
|
||||
if sl.Head == nil {
|
||||
return nil
|
||||
}
|
||||
if link.Head.Next == nil {
|
||||
return link.Head
|
||||
if sl.Head.Next == nil {
|
||||
return sl.Head
|
||||
}
|
||||
fast := link.Head
|
||||
slow := link.Head
|
||||
fast := sl.Head
|
||||
slow := sl.Head
|
||||
|
||||
for fast != nil {
|
||||
fast = fast.Next
|
||||
@@ -207,35 +206,35 @@ func (link *SinglyLink[T]) GetMiddleNode() *datastructure.LinkNode[T] {
|
||||
}
|
||||
|
||||
// Size return the count of singly linked list
|
||||
func (link *SinglyLink[T]) Size() int {
|
||||
return link.length
|
||||
func (sl *SinglyLink[T]) Size() int {
|
||||
return sl.length
|
||||
}
|
||||
|
||||
// Values return slice of all singly linklist node value
|
||||
func (link *SinglyLink[T]) Values() []T {
|
||||
res := []T{}
|
||||
current := link.Head
|
||||
func (sl *SinglyLink[T]) Values() []T {
|
||||
result := make([]T, 0, sl.length)
|
||||
current := sl.Head
|
||||
for current != nil {
|
||||
res = append(res, current.Value)
|
||||
result = append(result, current.Value)
|
||||
current = current.Next
|
||||
}
|
||||
return res
|
||||
return result
|
||||
}
|
||||
|
||||
// IsEmpty checks if link is empty or not
|
||||
func (link *SinglyLink[T]) IsEmpty() bool {
|
||||
return link.length == 0
|
||||
// IsEmpty checks if sl is empty or not
|
||||
func (sl *SinglyLink[T]) IsEmpty() bool {
|
||||
return sl.length == 0
|
||||
}
|
||||
|
||||
// Clear checks if link is empty or not
|
||||
func (link *SinglyLink[T]) Clear() {
|
||||
link.Head = nil
|
||||
link.length = 0
|
||||
// Clear all the node in singly linklist
|
||||
func (sl *SinglyLink[T]) Clear() {
|
||||
sl.Head = nil
|
||||
sl.length = 0
|
||||
}
|
||||
|
||||
// Print all nodes info of a linked list
|
||||
func (link *SinglyLink[T]) Print() {
|
||||
current := link.Head
|
||||
func (sl *SinglyLink[T]) Print() {
|
||||
current := sl.Head
|
||||
info := "[ "
|
||||
for current != nil {
|
||||
info += fmt.Sprintf("%+v, ", current)
|
||||
|
||||
@@ -7,13 +7,14 @@ import (
|
||||
)
|
||||
|
||||
func TestSinglyLink_InsertAtFirst(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSinglyLink_InsertAtFirst")
|
||||
|
||||
link := NewSinglyLink[int]()
|
||||
link.InsertAtHead(1)
|
||||
link.InsertAtHead(2)
|
||||
link.InsertAtHead(3)
|
||||
link.Print()
|
||||
|
||||
expected := []int{3, 2, 1}
|
||||
values := link.Values()
|
||||
@@ -22,13 +23,14 @@ func TestSinglyLink_InsertAtFirst(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSinglyLink_InsertAtTail(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSinglyLink_InsertAtTail")
|
||||
|
||||
link := NewSinglyLink[int]()
|
||||
link.InsertAtTail(1)
|
||||
link.InsertAtTail(2)
|
||||
link.InsertAtTail(3)
|
||||
link.Print()
|
||||
|
||||
expected := []int{1, 2, 3}
|
||||
values := link.Values()
|
||||
@@ -37,29 +39,18 @@ func TestSinglyLink_InsertAtTail(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSinglyLink_InsertAt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSinglyLink_InsertAt")
|
||||
|
||||
link := NewSinglyLink[int]()
|
||||
|
||||
err := link.InsertAt(1, 1)
|
||||
assert.IsNotNil(err)
|
||||
link.InsertAt(1, 1) //do nothing
|
||||
|
||||
err = link.InsertAt(0, 1)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
err = link.InsertAt(1, 2)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
err = link.InsertAt(2, 4)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
err = link.InsertAt(2, 3)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
link.InsertAt(0, 1)
|
||||
link.InsertAt(1, 2)
|
||||
link.InsertAt(2, 4)
|
||||
link.InsertAt(2, 3)
|
||||
|
||||
link.Print()
|
||||
|
||||
@@ -70,11 +61,13 @@ func TestSinglyLink_InsertAt(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSinglyLink_DeleteAtHead(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSinglyLink_DeleteAtHead")
|
||||
|
||||
link := NewSinglyLink[int]()
|
||||
err := link.DeleteAtHead()
|
||||
assert.IsNotNil(err)
|
||||
|
||||
link.DeleteAtHead()
|
||||
|
||||
link.InsertAtTail(1)
|
||||
link.InsertAtTail(2)
|
||||
@@ -82,7 +75,6 @@ func TestSinglyLink_DeleteAtHead(t *testing.T) {
|
||||
link.InsertAtTail(4)
|
||||
|
||||
link.DeleteAtHead()
|
||||
link.Print()
|
||||
|
||||
expected := []int{2, 3, 4}
|
||||
values := link.Values()
|
||||
@@ -91,19 +83,17 @@ func TestSinglyLink_DeleteAtHead(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSinglyLink_DeleteAtTail(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSinglyLink_DeleteAtTail")
|
||||
|
||||
link := NewSinglyLink[int]()
|
||||
err := link.DeleteAtTail()
|
||||
assert.IsNotNil(err)
|
||||
|
||||
link.InsertAtTail(1)
|
||||
link.InsertAtTail(2)
|
||||
link.InsertAtTail(3)
|
||||
link.InsertAtTail(4)
|
||||
|
||||
link.DeleteAtTail()
|
||||
link.Print()
|
||||
|
||||
expected := []int{1, 2, 3}
|
||||
values := link.Values()
|
||||
@@ -112,10 +102,11 @@ func TestSinglyLink_DeleteAtTail(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSinglyLink_DeleteValue(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSinglyLink_DeleteValue")
|
||||
|
||||
link := NewSinglyLink[int]()
|
||||
|
||||
link.InsertAtTail(1)
|
||||
link.InsertAtTail(2)
|
||||
link.InsertAtTail(2)
|
||||
@@ -130,23 +121,18 @@ func TestSinglyLink_DeleteValue(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSinglyLink_DeleteAt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSinglyLink_DeleteAt")
|
||||
|
||||
link := NewSinglyLink[int]()
|
||||
err := link.DeleteAt(0)
|
||||
assert.IsNotNil(err)
|
||||
|
||||
link.InsertAtTail(1)
|
||||
link.InsertAtTail(2)
|
||||
link.InsertAtTail(3)
|
||||
link.InsertAtTail(4)
|
||||
link.InsertAtTail(5)
|
||||
|
||||
err = link.DeleteAt(5)
|
||||
assert.IsNotNil(err)
|
||||
|
||||
err = link.DeleteAt(0)
|
||||
assert.IsNil(err)
|
||||
link.DeleteAt(0)
|
||||
assert.Equal([]int{2, 3, 4, 5}, link.Values())
|
||||
|
||||
link.DeleteAt(3)
|
||||
@@ -158,6 +144,8 @@ func TestSinglyLink_DeleteAt(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSinglyLink_Reverse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSinglyLink_Reverse")
|
||||
|
||||
link := NewSinglyLink[int]()
|
||||
@@ -167,11 +155,12 @@ func TestSinglyLink_Reverse(t *testing.T) {
|
||||
link.InsertAtTail(4)
|
||||
|
||||
link.Reverse()
|
||||
link.Print()
|
||||
assert.Equal([]int{4, 3, 2, 1}, link.Values())
|
||||
}
|
||||
|
||||
func TestSinglyLink_GetMiddleNode(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSinglyLink_GetMiddleNode")
|
||||
|
||||
link := NewSinglyLink[int]()
|
||||
@@ -186,6 +175,7 @@ func TestSinglyLink_GetMiddleNode(t *testing.T) {
|
||||
link.InsertAtTail(5)
|
||||
link.InsertAtTail(6)
|
||||
link.InsertAtTail(7)
|
||||
|
||||
middle2 := link.GetMiddleNode()
|
||||
assert.Equal(4, middle2.Value)
|
||||
}
|
||||
|
||||
379
datastructure/list/copyonwritelist.go
Normal file
379
datastructure/list/copyonwritelist.go
Normal file
@@ -0,0 +1,379 @@
|
||||
package datastructure
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type CopyOnWriteList[T any] struct {
|
||||
data []T
|
||||
lock sync.Locker
|
||||
}
|
||||
|
||||
// NewCopyOnWriteList Creates an empty list.
|
||||
func NewCopyOnWriteList[T any](data []T) *CopyOnWriteList[T] {
|
||||
return &CopyOnWriteList[T]{data: data, lock: &sync.RWMutex{}}
|
||||
}
|
||||
|
||||
func (c *CopyOnWriteList[T]) getList() []T {
|
||||
return c.data
|
||||
}
|
||||
func (c *CopyOnWriteList[T]) setList(data []T) {
|
||||
c.data = data
|
||||
}
|
||||
|
||||
// Size returns the number of elements in this list.
|
||||
func (c *CopyOnWriteList[T]) Size() int {
|
||||
return len(c.getList())
|
||||
}
|
||||
|
||||
// IsEmpty returns true if this list contains no elements.
|
||||
func (c *CopyOnWriteList[T]) IsEmpty() bool {
|
||||
return c.Size() == 0
|
||||
}
|
||||
|
||||
// Contain returns true if this list contains the specified element.
|
||||
func (c *CopyOnWriteList[T]) Contain(e T) bool {
|
||||
list := c.getList()
|
||||
return indexOf(e, list, 0, c.Size()) >= 0
|
||||
}
|
||||
|
||||
// ValueOf returns the index of the first occurrence of the specified element in this list, or null if this list does not contain the element.
|
||||
func (c *CopyOnWriteList[T]) ValueOf(index int) (*T, bool) {
|
||||
list := c.getList()
|
||||
if index < 0 || index >= len(c.data) {
|
||||
return nil, false
|
||||
}
|
||||
return get(list, index), true
|
||||
}
|
||||
|
||||
// IndexOf returns the index of the first occurrence of the specified element in this list, or -1 if this list does not contain the element.
|
||||
func (c *CopyOnWriteList[T]) IndexOf(e T) int {
|
||||
list := c.getList()
|
||||
return indexOf(e, list, 0, c.Size())
|
||||
}
|
||||
|
||||
// indexOf returns the index of the first occurrence of the specified element in this list, or -1 if this list does not contain the element.
|
||||
// start the start position of the search (inclusive)
|
||||
// end the end position of the search (exclusive)
|
||||
func indexOf[T any](o T, e []T, start int, end int) int {
|
||||
if start >= end {
|
||||
return -1
|
||||
}
|
||||
for i := start; i < end; i++ {
|
||||
if reflect.DeepEqual(e[i], o) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// LastIndexOf returns the index of the last occurrence of the specified element in this list, or -1 if this list does not contain the element.
|
||||
func (c *CopyOnWriteList[T]) LastIndexOf(e T) int {
|
||||
list := c.getList()
|
||||
return lastIndexOf(e, list, 0, c.Size())
|
||||
}
|
||||
|
||||
// lastIndexOf returns the index of the last occurrence of the specified element in this list, or -1 if this list does not contain the element.
|
||||
// start the start position of the search (inclusive)
|
||||
// end the end position of the search (exclusive)
|
||||
func lastIndexOf[T any](o T, e []T, start int, end int) int {
|
||||
if start >= end {
|
||||
return -1
|
||||
}
|
||||
for i := end - 1; i >= start; i-- {
|
||||
if reflect.DeepEqual(e[i], o) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// LastIndexOfFunc returns the index of the last occurrence of the value in this list satisfying the
|
||||
// functional predicate f(T) bool
|
||||
// if not found return -1.
|
||||
func (l *CopyOnWriteList[T]) LastIndexOfFunc(f func(T) bool) int {
|
||||
index := -1
|
||||
data := l.getList()
|
||||
for i := len(data) - 1; i >= 0; i-- {
|
||||
if f(data[i]) {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
// IndexOfFunc returns the first index satisfying the functional predicate f(v) bool
|
||||
// if not found return -1.
|
||||
func (l *CopyOnWriteList[T]) IndexOfFunc(f func(T) bool) int {
|
||||
index := -1
|
||||
data := l.getList()
|
||||
for i, v := range data {
|
||||
if f(v) {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
// get returns the element at the specified position in this list.
|
||||
func get[T any](o []T, index int) *T {
|
||||
return &o[index]
|
||||
}
|
||||
|
||||
// Get returns the element at the specified position in this list.
|
||||
func (c *CopyOnWriteList[T]) Get(index int) *T {
|
||||
list := c.getList()
|
||||
if index < 0 || index >= len(list) {
|
||||
return nil
|
||||
}
|
||||
return get(list, index)
|
||||
}
|
||||
|
||||
func (c *CopyOnWriteList[T]) set(index int, e T) (oldValue *T) {
|
||||
lock := c.lock
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
list := c.getList()
|
||||
oldValue = get(list, index)
|
||||
|
||||
if reflect.DeepEqual(oldValue, e) {
|
||||
c.setList(list)
|
||||
} else {
|
||||
newList := make([]T, len(list))
|
||||
copy(newList, list)
|
||||
newList[index] = e
|
||||
c.setList(newList)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Set replaces the element at the specified position in this list with the specified element.
|
||||
func (c *CopyOnWriteList[T]) Set(index int, e T) (oldValue *T, ok bool) {
|
||||
list := c.getList()
|
||||
if index < 0 || index >= len(list) {
|
||||
return oldValue, false
|
||||
}
|
||||
return c.set(index, e), true
|
||||
}
|
||||
|
||||
// Add appends the specified element to the end of this list.
|
||||
func (c *CopyOnWriteList[T]) Add(e T) bool {
|
||||
lock := c.lock
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
list := c.getList()
|
||||
newList := make([]T, len(list)+1)
|
||||
copy(newList, list)
|
||||
newList[len(list)] = e
|
||||
c.setList(newList)
|
||||
return true
|
||||
}
|
||||
|
||||
// AddAll appends all the elements in the specified collection to the end of this list
|
||||
func (c *CopyOnWriteList[T]) AddAll(e []T) bool {
|
||||
lock := c.lock
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
list := c.getList()
|
||||
newList := make([]T, len(list)+len(e))
|
||||
copy(newList, list)
|
||||
copy(newList[len(list):], e)
|
||||
c.setList(newList)
|
||||
return true
|
||||
}
|
||||
|
||||
// AddByIndex inserts the specified element at the specified position in this list.
|
||||
func (c *CopyOnWriteList[T]) AddByIndex(index int, e T) bool {
|
||||
lock := c.lock
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
list := c.getList()
|
||||
length := len(list)
|
||||
if index < 0 || index > length {
|
||||
return false
|
||||
}
|
||||
var newList []T
|
||||
var numMove = length - index
|
||||
if numMove == 0 {
|
||||
newList = make([]T, length+1)
|
||||
copy(newList, list)
|
||||
} else {
|
||||
newList = make([]T, length+1)
|
||||
copy(newList, list[:index])
|
||||
copy(newList[index+1:], list[index:])
|
||||
}
|
||||
newList[index] = e
|
||||
c.setList(newList)
|
||||
return true
|
||||
}
|
||||
|
||||
// delete removes the element at the specified position in this list.
|
||||
func (c *CopyOnWriteList[T]) delete(index int) *T {
|
||||
lock := c.lock
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
list := c.getList()
|
||||
length := len(list)
|
||||
|
||||
oldValue := get(list, index)
|
||||
numMove := length - index - 1
|
||||
var newList []T
|
||||
if numMove == 0 {
|
||||
newList = make([]T, length-1)
|
||||
copy(newList, list[:index])
|
||||
} else {
|
||||
newList = make([]T, length-1)
|
||||
copy(newList, list[:index])
|
||||
copy(newList[index:], list[index+1:])
|
||||
}
|
||||
|
||||
c.setList(newList)
|
||||
return oldValue
|
||||
}
|
||||
|
||||
// DeleteAt removes the element at the specified position in this list.
|
||||
func (c *CopyOnWriteList[T]) DeleteAt(index int) (*T, bool) {
|
||||
list := c.getList()
|
||||
if index < 0 || index >= len(list) {
|
||||
return nil, false
|
||||
}
|
||||
return c.delete(index), true
|
||||
}
|
||||
|
||||
// DeleteBy removes the first occurrence of the specified element from this list, if it is present.
|
||||
func (c *CopyOnWriteList[T]) DeleteBy(o T) (*T, bool) {
|
||||
list := c.getList()
|
||||
index := indexOf(o, list, 0, len(list))
|
||||
if index == -1 {
|
||||
return nil, false
|
||||
}
|
||||
return c.delete(index), true
|
||||
}
|
||||
|
||||
// DeleteRange removes from this list all the elements whose index is between fromIndex, inclusive, and toIndex, exclusive.
|
||||
// left close and right open
|
||||
func (c *CopyOnWriteList[T]) DeleteRange(start int, end int) {
|
||||
lock := c.lock
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
list := c.getList()
|
||||
length := len(list)
|
||||
if start < 0 || end > length || start > end {
|
||||
return
|
||||
}
|
||||
var newList []T
|
||||
numMove := length - end
|
||||
if numMove == 0 {
|
||||
newList = make([]T, length-(end-start))
|
||||
copy(newList, list[:start])
|
||||
} else {
|
||||
newList = make([]T, length-(end-start))
|
||||
copy(newList, list[:start])
|
||||
copy(newList[start:], list[end:])
|
||||
}
|
||||
c.setList(newList)
|
||||
}
|
||||
|
||||
// DeleteIf removes all the elements of this collection that satisfy the given predicate.
|
||||
func (c *CopyOnWriteList[T]) DeleteIf(f func(T) bool) {
|
||||
lock := c.lock
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
list := c.getList()
|
||||
length := len(list)
|
||||
var newList []T
|
||||
for i := 0; i < length; i++ {
|
||||
if !f(list[i]) {
|
||||
newList = append(newList, list[i])
|
||||
}
|
||||
}
|
||||
c.setList(newList)
|
||||
}
|
||||
|
||||
// Equal returns true if the specified object is equal to this list.
|
||||
func (c *CopyOnWriteList[T]) Equal(other *[]T) bool {
|
||||
if other == nil {
|
||||
return false
|
||||
}
|
||||
if c.Size() != len(*other) {
|
||||
return false
|
||||
}
|
||||
list := c.getList()
|
||||
otherList := NewCopyOnWriteList(*other).getList()
|
||||
for i := 0; i < len(list); i++ {
|
||||
if !reflect.DeepEqual(list[i], otherList[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Clear removes all the elements from this list.
|
||||
func (c *CopyOnWriteList[T]) Clear() {
|
||||
lock := c.lock
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
list := c.getList()
|
||||
list = make([]T, 0)
|
||||
c.setList(list)
|
||||
}
|
||||
|
||||
// Merge a tow list to one, change the list
|
||||
func (c *CopyOnWriteList[T]) Merge(other []T) {
|
||||
lock := c.lock
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
list := c.getList()
|
||||
list = append(list, other...)
|
||||
c.setList(list)
|
||||
}
|
||||
|
||||
// ForEach performs the given action for each element of the Iterable until all elements have been processed
|
||||
// or the action throws an exception.
|
||||
func (c *CopyOnWriteList[T]) ForEach(f func(T)) {
|
||||
list := c.getList()
|
||||
for i := 0; i < len(list); i++ {
|
||||
f(list[i])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Sort sorts this list according to the order induced by the specified Comparator.
|
||||
func (c *CopyOnWriteList[T]) Sort(compare func(o1 T, o2 T) bool) {
|
||||
lock := c.lock
|
||||
lock.Lock()
|
||||
list := c.getList()
|
||||
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
return compare(list[i], list[j])
|
||||
})
|
||||
|
||||
c.setList(list)
|
||||
}
|
||||
|
||||
func (c *CopyOnWriteList[T]) SubList(start int, end int) (newList []T) {
|
||||
lock := c.lock
|
||||
lock.Lock()
|
||||
list := c.getList()
|
||||
length := len(list)
|
||||
defer lock.Unlock()
|
||||
if start < 0 || end > length || start > end {
|
||||
return []T{}
|
||||
}
|
||||
newList = make([]T, end-start)
|
||||
copy(newList, list[start:end])
|
||||
c.setList(newList)
|
||||
return
|
||||
}
|
||||
268
datastructure/list/copyonwritelist_test.go
Normal file
268
datastructure/list/copyonwritelist_test.go
Normal file
@@ -0,0 +1,268 @@
|
||||
package datastructure
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
|
||||
func TestCopyOnWriteList_ValueOf(t *testing.T) {
|
||||
list := NewCopyOnWriteList([]int{1, 2, 3, 4, 5})
|
||||
|
||||
assert := internal.NewAssert(t, "CopyOnWriteList_IndexOf")
|
||||
of, ok := list.ValueOf(3)
|
||||
assert.Equal(4, *of)
|
||||
assert.Equal(true, ok)
|
||||
|
||||
_, ok = list.ValueOf(6)
|
||||
assert.Equal(false, ok)
|
||||
}
|
||||
|
||||
func TestCopyOnWriteList_Contain(t *testing.T) {
|
||||
list := NewCopyOnWriteList([]int{1, 2, 3, 4, 5})
|
||||
assert := internal.NewAssert(t, "CopyOnWriteList_Contains")
|
||||
assert.Equal(true, list.Contain(3))
|
||||
}
|
||||
|
||||
func TestCopyOnWriteList_IsEmpty(t *testing.T) {
|
||||
list := NewCopyOnWriteList([]int{})
|
||||
assert := internal.NewAssert(t, "CopyOnWriteList_IsEmpty")
|
||||
assert.Equal(true, list.IsEmpty())
|
||||
}
|
||||
|
||||
func TestCopyOnWriteList_Size(t *testing.T) {
|
||||
list := NewCopyOnWriteList([]int{1, 2, 3, 4, 5})
|
||||
assert := internal.NewAssert(t, "CopyOnWriteList_size")
|
||||
assert.Equal(5, list.Size())
|
||||
}
|
||||
|
||||
func TestCopyOnWriteList_GetList(t *testing.T) {
|
||||
list := NewCopyOnWriteList([]int{1, 2, 3, 4, 5})
|
||||
assert := internal.NewAssert(t, "CopyOnWriteList_GetList")
|
||||
assert.Equal([]int{1, 2, 3, 4, 5}, list.getList())
|
||||
}
|
||||
|
||||
func TestCopyOnWriteList_Get(t *testing.T) {
|
||||
list := NewCopyOnWriteList([]int{1, 2, 3, 4, 5})
|
||||
assert := internal.NewAssert(t, "CopyOnWriteList_Get")
|
||||
i := list.Get(2)
|
||||
assert.Equal(3, *i)
|
||||
}
|
||||
|
||||
func TestCopyOnWriteList_Set(t *testing.T) {
|
||||
list := NewCopyOnWriteList([]int{1, 2, 3, 4, 5})
|
||||
assert := internal.NewAssert(t, "CopyOnWriteList_Set")
|
||||
list.Set(2, 6)
|
||||
assert.Equal(6, list.getList()[2])
|
||||
|
||||
list = NewCopyOnWriteList([]int{1, 2, 3, 4, 5})
|
||||
list.Set(0, 6)
|
||||
assert.Equal(6, list.getList()[0])
|
||||
|
||||
list = NewCopyOnWriteList([]int{1, 2, 3, 4, 5})
|
||||
list.Set(0, 1)
|
||||
assert.Equal(1, list.getList()[0])
|
||||
}
|
||||
|
||||
func TestCopyOnWriteList_Add(t *testing.T) {
|
||||
list := NewCopyOnWriteList([]int{1, 2, 3, 4, 5})
|
||||
assert := internal.NewAssert(t, "CopyOnWriteList_Add")
|
||||
list.Add(6)
|
||||
assert.Equal([]int{1, 2, 3, 4, 5, 6}, list.getList())
|
||||
}
|
||||
|
||||
func TestCopyOnWriteList_AddAll(t *testing.T) {
|
||||
list := NewCopyOnWriteList([]int{1, 2, 3, 4, 5})
|
||||
assert := internal.NewAssert(t, "CopyOnWriteList_AddAll")
|
||||
list.AddAll([]int{6, 7, 8})
|
||||
assert.Equal([]int{1, 2, 3, 4, 5, 6, 7, 8}, list.getList())
|
||||
}
|
||||
|
||||
func TestCopyOnWriteList_AddByIndex(t *testing.T) {
|
||||
list := NewCopyOnWriteList([]int{1, 2, 3, 4, 5})
|
||||
assert := internal.NewAssert(t, "CopyOnWriteList_AddByIndex")
|
||||
list.AddByIndex(2, 6)
|
||||
assert.Equal([]int{1, 2, 6, 3, 4, 5}, list.getList())
|
||||
|
||||
list = NewCopyOnWriteList([]int{1, 2, 3, 4, 5})
|
||||
list.AddByIndex(0, 6)
|
||||
assert.Equal([]int{6, 1, 2, 3, 4, 5}, list.getList())
|
||||
|
||||
list = NewCopyOnWriteList([]int{1, 2, 3, 4, 5})
|
||||
list.AddByIndex(5, 6)
|
||||
assert.Equal([]int{1, 2, 3, 4, 5, 6}, list.getList())
|
||||
}
|
||||
|
||||
func TestCopyOnWriteList_DeleteAt2(t *testing.T) {
|
||||
list := NewCopyOnWriteList([]int{1, 2, 3, 4, 5})
|
||||
assert := internal.NewAssert(t, "CopyOnWriteList_RemoveByIndex")
|
||||
list.DeleteAt(2)
|
||||
assert.Equal([]int{1, 2, 4, 5}, list.getList())
|
||||
|
||||
list = NewCopyOnWriteList([]int{1, 2, 3, 4, 5})
|
||||
list.DeleteAt(4)
|
||||
assert.Equal([]int{1, 2, 3, 4}, list.getList())
|
||||
}
|
||||
|
||||
func TestCopyOnWriteList_RemoveByValue(t *testing.T) {
|
||||
list := NewCopyOnWriteList([]int{1, 2, 3, 4, 5})
|
||||
assert := internal.NewAssert(t, "CopyOnWriteList_RemoveByValue")
|
||||
list.DeleteBy(3)
|
||||
assert.Equal([]int{1, 2, 4, 5}, list.getList())
|
||||
}
|
||||
|
||||
func TestCopyOnWriteList_DeleteRange(t *testing.T) {
|
||||
list := NewCopyOnWriteList([]int{1, 2, 3, 4, 5})
|
||||
assert := internal.NewAssert(t, "CopyOnWriteList_RemoveRange")
|
||||
list.DeleteRange(1, 3)
|
||||
assert.Equal([]int{1, 4, 5}, list.getList())
|
||||
|
||||
list = NewCopyOnWriteList([]int{1, 2, 3, 4, 5})
|
||||
list.DeleteRange(0, 5)
|
||||
assert.Equal([]int{}, list.getList())
|
||||
}
|
||||
|
||||
func TestCopyOnWriteList_LastIndexOf(t *testing.T) {
|
||||
list := NewCopyOnWriteList([]int{1, 2, 3, 4, 5, 3})
|
||||
assert := internal.NewAssert(t, "CopyOnWriteList_LastIndexOf")
|
||||
assert.Equal(5, list.LastIndexOf(3))
|
||||
}
|
||||
|
||||
func TestCopyOnWriteList_DeleteAt(t *testing.T) {
|
||||
list := NewCopyOnWriteList([]int{1, 2, 3, 4, 5, 3})
|
||||
assert := internal.NewAssert(t, "CopyOnWriteList_DeleteAt")
|
||||
list.DeleteAt(2)
|
||||
assert.Equal([]int{1, 2, 4, 5, 3}, list.getList())
|
||||
}
|
||||
|
||||
func TestCopyOnWriteList_DeleteBy(t *testing.T) {
|
||||
list := NewCopyOnWriteList([]int{1, 2, 3, 4, 5, 3})
|
||||
assert := internal.NewAssert(t, "CopyOnWriteList_DeleteBy")
|
||||
list.DeleteBy(3)
|
||||
assert.Equal([]int{1, 2, 4, 5, 3}, list.getList())
|
||||
}
|
||||
|
||||
func TestCopyOnWriteList_DeleteIf(t *testing.T) {
|
||||
list := NewCopyOnWriteList([]int{1, 2, 3, 4, 5, 3, 6})
|
||||
assert := internal.NewAssert(t, "CopyOnWriteList_DeleteIf")
|
||||
|
||||
list.DeleteIf(func(i int) bool {
|
||||
return i%2 == 0
|
||||
})
|
||||
|
||||
assert.Equal([]int{1, 3, 5, 3}, list.getList())
|
||||
}
|
||||
|
||||
func TestCopyOnWriteList_Equal(t *testing.T) {
|
||||
list := NewCopyOnWriteList([]int{1, 2, 3, 4, 5, 3, 6})
|
||||
assert := internal.NewAssert(t, "CopyOnWriteList_Equal")
|
||||
|
||||
assert.Equal(true, list.Equal(&[]int{1, 2, 3, 4, 5, 3, 6}))
|
||||
}
|
||||
|
||||
func TestCopyOnWriteList_ForEach(t *testing.T) {
|
||||
testList := make([]int, 0)
|
||||
list := NewCopyOnWriteList([]int{1, 2, 3, 4, 5, 3, 6})
|
||||
assert := internal.NewAssert(t, "CopyOnWriteList_ForEach")
|
||||
|
||||
list.ForEach(func(i int) {
|
||||
testList = append(testList, i)
|
||||
})
|
||||
assert.Equal([]int{1, 2, 3, 4, 5, 3, 6}, testList)
|
||||
|
||||
list.ForEach(func(i int) {
|
||||
list.Add(i)
|
||||
})
|
||||
assert.Equal([]int{1, 2, 3, 4, 5, 3, 6, 1, 2, 3, 4, 5, 3, 6}, list.getList())
|
||||
}
|
||||
|
||||
func TestCopyOnWriteList_Clear(t *testing.T) {
|
||||
list := NewCopyOnWriteList([]int{1, 2, 3, 4, 5, 3, 6})
|
||||
assert := internal.NewAssert(t, "CopyOnWriteList_Clear")
|
||||
|
||||
list.Clear()
|
||||
assert.Equal([]int{}, list.getList())
|
||||
}
|
||||
|
||||
func TestCopyOnWriteList_Merge(t *testing.T) {
|
||||
list := NewCopyOnWriteList([]int{1, 3, 5, 7, 9})
|
||||
assert := internal.NewAssert(t, "CopyOnWriteList_Merge")
|
||||
|
||||
list.Merge([]int{2, 4, 6, 8, 10})
|
||||
assert.Equal([]int{1, 3, 5, 7, 9, 2, 4, 6, 8, 10}, list.getList())
|
||||
|
||||
}
|
||||
|
||||
func TestCopyOnWriteList_Sort(t *testing.T) {
|
||||
list := NewCopyOnWriteList([]int{1, 3, 5, 7, 9, 2, 4, 6, 8, 10})
|
||||
assert := internal.NewAssert(t, "CopyOnWriteList_Sort")
|
||||
|
||||
list.Sort(func(i, j int) bool {
|
||||
return i < j
|
||||
})
|
||||
assert.Equal([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, list.getList())
|
||||
}
|
||||
|
||||
func TestCopyOnWriteList_IndexOf(t *testing.T) {
|
||||
list := NewCopyOnWriteList([]int{1, 3, 5, 7, 9, 2, 4, 6, 8, 10})
|
||||
assert := internal.NewAssert(t, "CopyOnWriteList_IndexOf")
|
||||
|
||||
assert.Equal(0, list.IndexOf(1))
|
||||
assert.Equal(9, list.IndexOf(10))
|
||||
assert.Equal(-1, list.IndexOf(11))
|
||||
}
|
||||
|
||||
func TestCopyOnWriteList_SubList(t *testing.T) {
|
||||
list := NewCopyOnWriteList([]int{1, 3, 5, 7, 9, 2, 4, 6, 8, 10})
|
||||
|
||||
assert := internal.NewAssert(t, "CopyOnWriteList_SubList")
|
||||
|
||||
list = NewCopyOnWriteList([]int{1, 3, 5, 7, 9, 2, 4, 6, 8, 10})
|
||||
subList := list.SubList(1, 3)
|
||||
assert.Equal([]int{3, 5}, subList)
|
||||
|
||||
list = NewCopyOnWriteList([]int{1, 3, 5, 7, 9, 2, 4, 6, 8, 10})
|
||||
subList = list.SubList(1, 1)
|
||||
assert.Equal([]int{}, subList)
|
||||
|
||||
list = NewCopyOnWriteList([]int{1, 3, 5, 7, 9, 2, 4, 6, 8, 10})
|
||||
assert.Equal(10, list.Size())
|
||||
subList = list.SubList(1, 10)
|
||||
assert.Equal([]int{3, 5, 7, 9, 2, 4, 6, 8, 10}, subList)
|
||||
|
||||
list = NewCopyOnWriteList([]int{1, 3, 5, 7, 9, 2, 4, 6, 8, 10})
|
||||
subList = list.SubList(11, 1)
|
||||
assert.Equal([]int{}, subList)
|
||||
}
|
||||
|
||||
func TestCopyOnWriteListIndexOfFunc(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestIndexOfFunc")
|
||||
|
||||
list := NewCopyOnWriteList([]int{1, 2, 3})
|
||||
i := list.IndexOfFunc(func(a int) bool { return a == 1 })
|
||||
assert.Equal(0, i)
|
||||
|
||||
i = list.IndexOfFunc(func(a int) bool { return a == 4 })
|
||||
assert.Equal(-1, i)
|
||||
}
|
||||
|
||||
func TestNewCopyOnWriteListLastIndexOfFunc(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestLastIndexOfFunc")
|
||||
|
||||
list := NewCopyOnWriteList([]int{1, 2, 3, 3, 3, 3, 4, 5, 6, 9})
|
||||
i := list.LastIndexOfFunc(func(a int) bool { return a == 3 })
|
||||
assert.Equal(5, i)
|
||||
|
||||
i = list.LastIndexOfFunc(func(a int) bool { return a == 10 })
|
||||
assert.Equal(-1, i)
|
||||
|
||||
i = list.LastIndexOfFunc(func(a int) bool { return a == 4 })
|
||||
assert.Equal(6, i)
|
||||
|
||||
i = list.LastIndexOfFunc(func(a int) bool { return a == 1 })
|
||||
assert.Equal(0, i)
|
||||
}
|
||||
@@ -1,24 +1,26 @@
|
||||
// Copyright 2021 dudaodong@gmail.com. All rights reserved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
// Package datastructure implements some data structure. eg. list, linklist, stack, queue, tree, graph.
|
||||
// Package datastructure contains some data structure. list is a linear table, implemented with slice.
|
||||
package datastructure
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/duke-git/lancet/v2/iterator"
|
||||
)
|
||||
|
||||
// List is a linear table, implemented with slice
|
||||
// List is a linear table, implemented with slice.
|
||||
type List[T any] struct {
|
||||
data []T
|
||||
}
|
||||
|
||||
// NewList return a pointer of List
|
||||
// NewList return a pointer of List.
|
||||
func NewList[T any](data []T) *List[T] {
|
||||
return &List[T]{data: data}
|
||||
}
|
||||
|
||||
// Data return list data
|
||||
// Data return list data.
|
||||
func (l *List[T]) Data() []T {
|
||||
return l.data
|
||||
}
|
||||
@@ -31,7 +33,7 @@ func (l *List[T]) ValueOf(index int) (*T, bool) {
|
||||
return &l.data[index], true
|
||||
}
|
||||
|
||||
// IndexOf reture the index of value. if not found return -1
|
||||
// IndexOf returns the index of value. if not found return -1.
|
||||
func (l *List[T]) IndexOf(value T) int {
|
||||
index := -1
|
||||
data := l.data
|
||||
@@ -44,7 +46,49 @@ func (l *List[T]) IndexOf(value T) int {
|
||||
return index
|
||||
}
|
||||
|
||||
// Contain checks if the value in the list or not
|
||||
// LastIndexOf returns the index of the last occurrence of the value in this list.
|
||||
// if not found return -1.
|
||||
func (l *List[T]) LastIndexOf(value T) int {
|
||||
index := -1
|
||||
data := l.data
|
||||
for i := len(data) - 1; i >= 0; i-- {
|
||||
if reflect.DeepEqual(data[i], value) {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
// IndexOfFunc returns the first index satisfying f(v)
|
||||
// if not found return -1.
|
||||
func (l *List[T]) IndexOfFunc(f func(T) bool) int {
|
||||
index := -1
|
||||
data := l.data
|
||||
for i, v := range data {
|
||||
if f(v) {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
// LastIndexOfFunc returns the index of the last occurrence of the value in this list satisfying f(data[i])
|
||||
// if not found return -1.
|
||||
func (l *List[T]) LastIndexOfFunc(f func(T) bool) int {
|
||||
index := -1
|
||||
data := l.data
|
||||
for i := len(data) - 1; i >= 0; i-- {
|
||||
if f(data[i]) {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
// Contain checks if the value in the list or not.
|
||||
func (l *List[T]) Contain(value T) bool {
|
||||
data := l.data
|
||||
for _, v := range data {
|
||||
@@ -55,22 +99,22 @@ func (l *List[T]) Contain(value T) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Push append value to the list data
|
||||
// Push append value to the list data.
|
||||
func (l *List[T]) Push(value T) {
|
||||
l.data = append(l.data, value)
|
||||
}
|
||||
|
||||
// InsertAtFirst insert value into list at first index
|
||||
// InsertAtFirst insert value into list at first index.
|
||||
func (l *List[T]) InsertAtFirst(value T) {
|
||||
l.InsertAt(0, value)
|
||||
}
|
||||
|
||||
// InsertAtLast insert value into list at last index
|
||||
// InsertAtLast insert value into list at last index.
|
||||
func (l *List[T]) InsertAtLast(value T) {
|
||||
l.InsertAt(len(l.data), value)
|
||||
}
|
||||
|
||||
// InsertAt insert value into list at index
|
||||
// InsertAt insert value into list at index.
|
||||
func (l *List[T]) InsertAt(index int, value T) {
|
||||
data := l.data
|
||||
size := len(data)
|
||||
@@ -81,7 +125,7 @@ func (l *List[T]) InsertAt(index int, value T) {
|
||||
l.data = append(data[:index], append([]T{value}, data[index:]...)...)
|
||||
}
|
||||
|
||||
// PopFirst delete the first value of list and return it
|
||||
// PopFirst delete the first value of list and return it.
|
||||
func (l *List[T]) PopFirst() (*T, bool) {
|
||||
if len(l.data) == 0 {
|
||||
return nil, false
|
||||
@@ -93,7 +137,7 @@ func (l *List[T]) PopFirst() (*T, bool) {
|
||||
return &v, true
|
||||
}
|
||||
|
||||
// PopLast delete the last value of list and return it
|
||||
// PopLast delete the last value of list and return it.
|
||||
func (l *List[T]) PopLast() (*T, bool) {
|
||||
size := len(l.data)
|
||||
if size == 0 {
|
||||
@@ -106,7 +150,7 @@ func (l *List[T]) PopLast() (*T, bool) {
|
||||
return &v, true
|
||||
}
|
||||
|
||||
// DeleteAt delete the value of list at index
|
||||
// DeleteAt delete the value of list at index.
|
||||
func (l *List[T]) DeleteAt(index int) {
|
||||
data := l.data
|
||||
size := len(data)
|
||||
@@ -114,13 +158,38 @@ func (l *List[T]) DeleteAt(index int) {
|
||||
return
|
||||
}
|
||||
if index == size-1 {
|
||||
data = append(data[:index])
|
||||
data = data[:index]
|
||||
} else {
|
||||
data = append(data[:index], data[index+1:]...)
|
||||
}
|
||||
l.data = data
|
||||
}
|
||||
|
||||
// DeleteIf delete all satisfying f(data[i]), returns count of removed elements
|
||||
func (l *List[T]) DeleteIf(f func(T) bool) int {
|
||||
data := l.data
|
||||
size := len(data)
|
||||
|
||||
var c int
|
||||
for index := 0; index < len(data); index++ {
|
||||
if !f(data[index]) {
|
||||
continue
|
||||
}
|
||||
if index == size-1 {
|
||||
data = data[:index]
|
||||
} else {
|
||||
data = append(data[:index], data[index+1:]...)
|
||||
index--
|
||||
}
|
||||
c++
|
||||
}
|
||||
|
||||
if c > 0 {
|
||||
l.data = data
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// UpdateAt update value of list at index, index shoud between 0 and list size -1
|
||||
func (l *List[T]) UpdateAt(index int, value T) {
|
||||
data := l.data
|
||||
@@ -132,8 +201,8 @@ func (l *List[T]) UpdateAt(index int, value T) {
|
||||
l.data = append(data[:index], append([]T{value}, data[index+1:]...)...)
|
||||
}
|
||||
|
||||
// Equtal compare list to other list, use reflect.DeepEqual
|
||||
func (l *List[T]) Equtal(other *List[T]) bool {
|
||||
// Equal compare list to other list, use reflect.DeepEqual.
|
||||
func (l *List[T]) Equal(other *List[T]) bool {
|
||||
if len(l.data) != len(other.data) {
|
||||
return false
|
||||
}
|
||||
@@ -147,17 +216,17 @@ func (l *List[T]) Equtal(other *List[T]) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsEmpty check if the list is empty or not
|
||||
// IsEmpty check if the list is empty or not.
|
||||
func (l *List[T]) IsEmpty() bool {
|
||||
return len(l.data) == 0
|
||||
}
|
||||
|
||||
// Clear the data of list
|
||||
// Clear the data of list.
|
||||
func (l *List[T]) Clear() {
|
||||
l.data = make([]T, 0, 0)
|
||||
l.data = make([]T, 0)
|
||||
}
|
||||
|
||||
// Clone return a copy of list
|
||||
// Clone return a copy of list.
|
||||
func (l *List[T]) Clone() *List[T] {
|
||||
cl := NewList(make([]T, len(l.data)))
|
||||
copy(cl.data, l.data)
|
||||
@@ -165,10 +234,10 @@ func (l *List[T]) Clone() *List[T] {
|
||||
return cl
|
||||
}
|
||||
|
||||
// Merge two list, return new list, don't change original list
|
||||
// Merge two list, return new list, don't change original list.
|
||||
func (l *List[T]) Merge(other *List[T]) *List[T] {
|
||||
l1, l2 := len(l.data), len(other.data)
|
||||
ml := NewList(make([]T, l1+l2, l1+l2))
|
||||
ml := NewList(make([]T, l1+l2))
|
||||
|
||||
data := append([]T{}, append(l.data, other.data...)...)
|
||||
ml.data = data
|
||||
@@ -176,12 +245,17 @@ func (l *List[T]) Merge(other *List[T]) *List[T] {
|
||||
return ml
|
||||
}
|
||||
|
||||
// Size return number of list data items
|
||||
// Size return number of list data items.
|
||||
func (l *List[T]) Size() int {
|
||||
return len(l.data)
|
||||
}
|
||||
|
||||
// Swap the value of index i and j in list
|
||||
// Cap return cap of the inner data.
|
||||
func (l *List[T]) Cap() int {
|
||||
return cap(l.data)
|
||||
}
|
||||
|
||||
// Swap the value of index i and j in list.
|
||||
func (l *List[T]) Swap(i, j int) {
|
||||
size := len(l.data)
|
||||
if i < 0 || i >= size || j < 0 || j >= size {
|
||||
@@ -190,19 +264,19 @@ func (l *List[T]) Swap(i, j int) {
|
||||
l.data[i], l.data[j] = l.data[j], l.data[i]
|
||||
}
|
||||
|
||||
// Reverse the item order of list
|
||||
// Reverse the item order of list.
|
||||
func (l *List[T]) Reverse() {
|
||||
for i, j := 0, len(l.data)-1; i < j; i, j = i+1, j-1 {
|
||||
l.data[i], l.data[j] = l.data[j], l.data[i]
|
||||
}
|
||||
}
|
||||
|
||||
// Unique remove duplicate items in list
|
||||
// Unique delete duplicate items in list.
|
||||
func (l *List[T]) Unique() {
|
||||
data := l.data
|
||||
size := len(data)
|
||||
|
||||
uniqueData := make([]T, 0, 0)
|
||||
uniqueData := make([]T, 0)
|
||||
for i := 0; i < size; i++ {
|
||||
value := data[i]
|
||||
skip := true
|
||||
@@ -220,26 +294,125 @@ func (l *List[T]) Unique() {
|
||||
l.data = uniqueData
|
||||
}
|
||||
|
||||
// Union creates a new list contain all element in list l and other, remove duplicate element.
|
||||
// Union creates a new list contain all element in list l and other, delete duplicate element.
|
||||
func (l *List[T]) Union(other *List[T]) *List[T] {
|
||||
res := NewList([]T{})
|
||||
result := NewList([]T{})
|
||||
|
||||
res.data = append(res.data, l.data...)
|
||||
res.data = append(res.data, other.data...)
|
||||
res.Unique()
|
||||
result.data = append(result.data, l.data...)
|
||||
result.data = append(result.data, other.data...)
|
||||
result.Unique()
|
||||
|
||||
return res
|
||||
return result
|
||||
}
|
||||
|
||||
// Intersection creates a new list whose element both be contained in list l and other
|
||||
// Intersection creates a new list whose element both be contained in list l and other.
|
||||
func (l *List[T]) Intersection(other *List[T]) *List[T] {
|
||||
res := NewList(make([]T, 0, 0))
|
||||
result := NewList(make([]T, 0))
|
||||
|
||||
for _, v := range l.data {
|
||||
if other.Contain(v) {
|
||||
res.data = append(res.data, v)
|
||||
result.data = append(result.data, v)
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
return result
|
||||
}
|
||||
|
||||
// Difference returns the difference between two collections.
|
||||
// return a list whose element in the original list, not in the given list.
|
||||
func (l *List[T]) Difference(other *List[T]) *List[T] {
|
||||
result := NewList(make([]T, 0))
|
||||
|
||||
intersectList := l.Intersection(other)
|
||||
|
||||
for _, v := range l.data {
|
||||
if !intersectList.Contain(v) {
|
||||
result.data = append(result.data, v)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// SymmetricDifference oppoiste operation of intersection function.
|
||||
func (l *List[T]) SymmetricDifference(other *List[T]) *List[T] {
|
||||
result := NewList(make([]T, 0))
|
||||
|
||||
intersectList := l.Intersection(other)
|
||||
|
||||
for _, v := range l.data {
|
||||
if !intersectList.Contain(v) {
|
||||
result.data = append(result.data, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range other.data {
|
||||
if !intersectList.Contain(v) {
|
||||
result.data = append(result.data, v)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// SubList returns a sub list of the original list between the specified fromIndex, inclusive, and toIndex, exclusive.
|
||||
func (l *List[T]) SubList(fromIndex, toIndex int) *List[T] {
|
||||
data := l.data[fromIndex:toIndex]
|
||||
subList := make([]T, len(data))
|
||||
copy(subList, data)
|
||||
return NewList(subList)
|
||||
}
|
||||
|
||||
// ForEach performs the given action for each element of the list.
|
||||
func (l *List[T]) ForEach(consumer func(T)) {
|
||||
for _, it := range l.data {
|
||||
consumer(it)
|
||||
}
|
||||
}
|
||||
|
||||
// RetainAll retains only the elements in this list that are contained in the given list.
|
||||
func (l *List[T]) RetainAll(list *List[T]) bool {
|
||||
return l.batchRemove(list, true)
|
||||
}
|
||||
|
||||
// DeleteAll removes from this list all of its elements that are contained in the given list.
|
||||
func (l *List[T]) DeleteAll(list *List[T]) bool {
|
||||
return l.batchRemove(list, false)
|
||||
}
|
||||
|
||||
func (l *List[T]) batchRemove(list *List[T], complement bool) bool {
|
||||
var (
|
||||
w = 0
|
||||
data = l.data
|
||||
size = len(data)
|
||||
)
|
||||
|
||||
for i := 0; i < size; i++ {
|
||||
if list.Contain(data[i]) == complement {
|
||||
data[w] = data[i]
|
||||
w++
|
||||
}
|
||||
}
|
||||
|
||||
if w != size {
|
||||
l.data = data[:w]
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Iterator returns an iterator over the elements in this list in proper sequence.
|
||||
func (l *List[T]) Iterator() iterator.Iterator[T] {
|
||||
return iterator.FromSlice(l.data)
|
||||
}
|
||||
|
||||
// ListToMap convert a list to a map based on iteratee function.
|
||||
func ListToMap[T any, K comparable, V any](list *List[T], iteratee func(T) (K, V)) map[K]V {
|
||||
result := make(map[K]V, list.Size())
|
||||
for _, item := range list.data {
|
||||
k, v := iteratee(item)
|
||||
result[k] = v
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
)
|
||||
|
||||
func TestListData(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestListData")
|
||||
|
||||
list := NewList([]int{1, 2, 3})
|
||||
@@ -14,6 +16,8 @@ func TestListData(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestValueOf(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestValueOf")
|
||||
|
||||
list := NewList([]int{1, 2, 3})
|
||||
@@ -26,6 +30,8 @@ func TestValueOf(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIndexOf(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestIndexOf")
|
||||
|
||||
list := NewList([]int{1, 2, 3})
|
||||
@@ -36,7 +42,60 @@ func TestIndexOf(t *testing.T) {
|
||||
assert.Equal(-1, i)
|
||||
}
|
||||
|
||||
func TestIndexOfFunc(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestIndexOf")
|
||||
|
||||
list := NewList([]int{1, 2, 3})
|
||||
i := list.IndexOfFunc(func(a int) bool { return a == 1 })
|
||||
assert.Equal(0, i)
|
||||
|
||||
i = list.IndexOfFunc(func(a int) bool { return a == 4 })
|
||||
assert.Equal(-1, i)
|
||||
}
|
||||
|
||||
func TestLastIndexOf(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestIndexOf")
|
||||
|
||||
list := NewList([]int{1, 2, 3, 3, 3, 3, 4, 5, 6, 9})
|
||||
i := list.LastIndexOf(3)
|
||||
assert.Equal(5, i)
|
||||
|
||||
i = list.LastIndexOf(10)
|
||||
assert.Equal(-1, i)
|
||||
|
||||
i = list.LastIndexOf(4)
|
||||
assert.Equal(6, i)
|
||||
|
||||
i = list.LastIndexOf(1)
|
||||
assert.Equal(0, i)
|
||||
}
|
||||
|
||||
func TestLastIndexOfFunc(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestIndexOf")
|
||||
|
||||
list := NewList([]int{1, 2, 3, 3, 3, 3, 4, 5, 6, 9})
|
||||
i := list.LastIndexOfFunc(func(a int) bool { return a == 3 })
|
||||
assert.Equal(5, i)
|
||||
|
||||
i = list.LastIndexOfFunc(func(a int) bool { return a == 10 })
|
||||
assert.Equal(-1, i)
|
||||
|
||||
i = list.LastIndexOfFunc(func(a int) bool { return a == 4 })
|
||||
assert.Equal(6, i)
|
||||
|
||||
i = list.LastIndexOfFunc(func(a int) bool { return a == 1 })
|
||||
assert.Equal(0, i)
|
||||
}
|
||||
|
||||
func TestContain(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestContain")
|
||||
|
||||
list := NewList([]int{1, 2, 3})
|
||||
@@ -45,6 +104,8 @@ func TestContain(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPush(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestPush")
|
||||
|
||||
list := NewList([]int{1, 2, 3})
|
||||
@@ -54,6 +115,8 @@ func TestPush(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInsertAtFirst(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestInsertAtFirst")
|
||||
|
||||
list := NewList([]int{1, 2, 3})
|
||||
@@ -63,6 +126,8 @@ func TestInsertAtFirst(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInsertAtLast(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestInsertAtLast")
|
||||
|
||||
list := NewList([]int{1, 2, 3})
|
||||
@@ -72,6 +137,8 @@ func TestInsertAtLast(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInsertAt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestInsertAt")
|
||||
|
||||
list := NewList([]int{1, 2, 3})
|
||||
@@ -90,6 +157,8 @@ func TestInsertAt(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPopFirst(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestPopFirst")
|
||||
|
||||
list := NewList([]int{1, 2, 3})
|
||||
@@ -105,6 +174,8 @@ func TestPopFirst(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPopLast(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestPopLast")
|
||||
|
||||
list := NewList([]int{1, 2, 3})
|
||||
@@ -120,6 +191,8 @@ func TestPopLast(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDeleteAt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestDeleteAt")
|
||||
|
||||
list := NewList([]int{1, 2, 3, 4})
|
||||
@@ -138,6 +211,8 @@ func TestDeleteAt(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUpdateAt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestUpdateAt")
|
||||
|
||||
list := NewList([]int{1, 2, 3, 4})
|
||||
@@ -155,18 +230,22 @@ func TestUpdateAt(t *testing.T) {
|
||||
assert.Equal([]int{5, 2, 3, 1}, list.Data())
|
||||
}
|
||||
|
||||
func TestEqutal(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestEqutal")
|
||||
func TestEqual(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestEqual")
|
||||
|
||||
list1 := NewList([]int{1, 2, 3, 4})
|
||||
list2 := NewList([]int{1, 2, 3, 4})
|
||||
list3 := NewList([]int{1, 2, 3})
|
||||
|
||||
assert.Equal(true, list1.Equtal(list2))
|
||||
assert.Equal(false, list1.Equtal(list3))
|
||||
assert.Equal(true, list1.Equal(list2))
|
||||
assert.Equal(false, list1.Equal(list3))
|
||||
}
|
||||
|
||||
func TestIsEmpty(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestIsEmpty")
|
||||
|
||||
list1 := NewList([]int{1, 2, 3, 4})
|
||||
@@ -177,6 +256,8 @@ func TestIsEmpty(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIsClear(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestIsClear")
|
||||
|
||||
list1 := NewList([]int{1, 2, 3, 4})
|
||||
@@ -187,15 +268,19 @@ func TestIsClear(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestClone(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestClone")
|
||||
|
||||
list1 := NewList([]int{1, 2, 3, 4})
|
||||
list2 := list1.Clone()
|
||||
|
||||
assert.Equal(true, list1.Equtal(list2))
|
||||
assert.Equal(true, list1.Equal(list2))
|
||||
}
|
||||
|
||||
func TestMerge(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestMerge")
|
||||
|
||||
list1 := NewList([]int{1, 2, 3, 4})
|
||||
@@ -203,10 +288,12 @@ func TestMerge(t *testing.T) {
|
||||
expected := NewList([]int{1, 2, 3, 4, 4, 5, 6})
|
||||
|
||||
list3 := list1.Merge(list2)
|
||||
assert.Equal(true, expected.Equtal(list3))
|
||||
assert.Equal(true, expected.Equal(list3))
|
||||
}
|
||||
|
||||
func TestSize(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSize")
|
||||
|
||||
list := NewList([]int{1, 2, 3, 4})
|
||||
@@ -216,7 +303,23 @@ func TestSize(t *testing.T) {
|
||||
assert.Equal(0, empty.Size())
|
||||
}
|
||||
|
||||
func TestCap(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestCap")
|
||||
|
||||
data := make([]int, 0, 100)
|
||||
list := NewList(data)
|
||||
assert.Equal(100, list.Cap())
|
||||
|
||||
data = make([]int, 0)
|
||||
list = NewList(data)
|
||||
assert.Equal(0, list.Cap())
|
||||
}
|
||||
|
||||
func TestSwap(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSwap")
|
||||
|
||||
list := NewList([]int{1, 2, 3, 4})
|
||||
@@ -224,10 +327,12 @@ func TestSwap(t *testing.T) {
|
||||
|
||||
list.Swap(0, 3)
|
||||
|
||||
assert.Equal(true, expected.Equtal(list))
|
||||
assert.Equal(true, expected.Equal(list))
|
||||
}
|
||||
|
||||
func TestReverse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestReverse")
|
||||
|
||||
list := NewList([]int{1, 2, 3, 4})
|
||||
@@ -235,10 +340,12 @@ func TestReverse(t *testing.T) {
|
||||
|
||||
list.Reverse()
|
||||
|
||||
assert.Equal(true, expected.Equtal(list))
|
||||
assert.Equal(true, expected.Equal(list))
|
||||
}
|
||||
|
||||
func TestUnique(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestUnique")
|
||||
|
||||
list := NewList([]int{1, 2, 2, 3, 4})
|
||||
@@ -246,10 +353,12 @@ func TestUnique(t *testing.T) {
|
||||
|
||||
list.Unique()
|
||||
|
||||
assert.Equal(true, expected.Equtal(list))
|
||||
assert.Equal(true, expected.Equal(list))
|
||||
}
|
||||
|
||||
func TestUnion(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestUnion")
|
||||
|
||||
list1 := NewList([]int{1, 2, 3, 4})
|
||||
@@ -257,10 +366,12 @@ func TestUnion(t *testing.T) {
|
||||
expected := NewList([]int{1, 2, 3, 4, 5, 6})
|
||||
|
||||
list3 := list1.Union(list2)
|
||||
assert.Equal(true, expected.Equtal(list3))
|
||||
assert.Equal(true, expected.Equal(list3))
|
||||
}
|
||||
|
||||
func TestIntersection(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestIntersection")
|
||||
|
||||
list1 := NewList([]int{1, 2, 3, 4})
|
||||
@@ -268,5 +379,153 @@ func TestIntersection(t *testing.T) {
|
||||
expected := NewList([]int{4})
|
||||
|
||||
list3 := list1.Intersection(list2)
|
||||
assert.Equal(true, expected.Equtal(list3))
|
||||
assert.Equal(true, expected.Equal(list3))
|
||||
}
|
||||
|
||||
func TestDifference(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestDifference")
|
||||
|
||||
list1 := NewList([]int{1, 2, 3})
|
||||
list2 := NewList([]int{1, 2, 4})
|
||||
expected := NewList([]int{3})
|
||||
|
||||
list3 := list1.Difference(list2)
|
||||
assert.Equal(true, expected.Equal(list3))
|
||||
}
|
||||
|
||||
func TestSymmetricDifference(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSymmetricDifference")
|
||||
|
||||
list1 := NewList([]int{1, 2, 3})
|
||||
list2 := NewList([]int{1, 2, 4})
|
||||
expected := NewList([]int{3, 4})
|
||||
|
||||
list3 := list1.SymmetricDifference(list2)
|
||||
assert.Equal(true, expected.Equal(list3))
|
||||
}
|
||||
|
||||
func TestSubSlice(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSubSlice")
|
||||
|
||||
list := NewList([]int{1, 2, 3, 4, 5, 8})
|
||||
subList := list.SubList(2, 5)
|
||||
|
||||
assert.Equal([]int{3, 4, 5}, subList.Data())
|
||||
}
|
||||
|
||||
func BenchmarkSubSlice(b *testing.B) {
|
||||
list := NewList([]int{1, 2, 3, 4, 5, 8})
|
||||
for n := 0; n < b.N; n++ {
|
||||
list.SubList(2, 5)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteIf(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestDeleteIf")
|
||||
|
||||
list := NewList([]int{1, 1, 1, 1, 2, 3, 1, 1, 4, 1, 1, 1, 1, 1, 1})
|
||||
|
||||
count := list.DeleteIf(func(a int) bool { return a == 1 })
|
||||
assert.Equal([]int{2, 3, 4}, list.Data())
|
||||
assert.Equal(12, count)
|
||||
|
||||
count = list.DeleteIf(func(a int) bool { return a == 5 })
|
||||
assert.Equal([]int{2, 3, 4}, list.Data())
|
||||
assert.Equal(0, count)
|
||||
}
|
||||
|
||||
func TestForEach(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestForEach")
|
||||
|
||||
list := NewList([]int{1, 2, 3, 4})
|
||||
rs := make([]int, 0)
|
||||
list.ForEach(func(i int) {
|
||||
rs = append(rs, i)
|
||||
})
|
||||
|
||||
assert.Equal([]int{1, 2, 3, 4}, rs)
|
||||
}
|
||||
|
||||
func TestRetainAll(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestRetainAll")
|
||||
|
||||
list := NewList([]int{1, 2, 3, 4})
|
||||
list1 := NewList([]int{1, 2, 3, 4})
|
||||
list2 := NewList([]int{1, 2, 3, 4})
|
||||
|
||||
retain := NewList([]int{1, 2})
|
||||
retain1 := NewList([]int{2, 3})
|
||||
retain2 := NewList([]int{1, 2, 5})
|
||||
|
||||
list.RetainAll(retain)
|
||||
list1.RetainAll(retain1)
|
||||
list2.RetainAll(retain2)
|
||||
|
||||
assert.Equal([]int{1, 2}, list.Data())
|
||||
assert.Equal([]int{2, 3}, list1.Data())
|
||||
assert.Equal([]int{1, 2}, list2.Data())
|
||||
}
|
||||
|
||||
func TestDeleteAll(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestDeleteAll")
|
||||
|
||||
list := NewList([]int{1, 2, 3, 4})
|
||||
list1 := NewList([]int{1, 2, 3, 4})
|
||||
list2 := NewList([]int{1, 2, 3, 4})
|
||||
|
||||
del := NewList([]int{1})
|
||||
del1 := NewList([]int{2, 3})
|
||||
del2 := NewList([]int{1, 2, 5})
|
||||
|
||||
list.DeleteAll(del)
|
||||
list1.DeleteAll(del1)
|
||||
list2.DeleteAll(del2)
|
||||
assert.Equal([]int{2, 3, 4}, list.Data())
|
||||
assert.Equal([]int{1, 4}, list1.Data())
|
||||
assert.Equal([]int{3, 4}, list2.Data())
|
||||
}
|
||||
|
||||
func TestIterator(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestIterator")
|
||||
|
||||
list := NewList([]int{1, 2, 3, 4})
|
||||
iterator := list.Iterator()
|
||||
|
||||
rs := make([]int, 0)
|
||||
for iterator.HasNext() {
|
||||
item, _ := iterator.Next()
|
||||
rs = append(rs, item)
|
||||
}
|
||||
|
||||
assert.Equal([]int{1, 2, 3, 4}, rs)
|
||||
}
|
||||
|
||||
func TestListToMap(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "ListToMap")
|
||||
|
||||
list := NewList([]int{1, 2, 3, 4})
|
||||
result := ListToMap(list, func(n int) (int, bool) {
|
||||
return n, n > 1
|
||||
})
|
||||
|
||||
expected := map[int]bool{1: false, 2: true, 3: true, 4: true}
|
||||
assert.Equal(expected, result)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2021 dudaodong@gmail.com. All rights reserved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
// Package datastructure implements some data structure. eg. list, linklist, stack, queue, tree, graph.
|
||||
// Package datastructure implements some data structure.
|
||||
package datastructure
|
||||
|
||||
// LinkNode is a linkedlist node, which have a Value and Pre points to previous node, Next points to a next node of the link.
|
||||
|
||||
108
datastructure/optional/optional.go
Normal file
108
datastructure/optional/optional.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package optional
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Optional is a type that may or may not contain a non-nil value.
|
||||
type Optional[T any] struct {
|
||||
value *T
|
||||
mu *sync.RWMutex
|
||||
}
|
||||
|
||||
// Default returns an default Optional instance.
|
||||
func Default[T any]() Optional[T] {
|
||||
return Optional[T]{mu: &sync.RWMutex{}}
|
||||
}
|
||||
|
||||
// Of returns an Optional with a non-nil value.
|
||||
func Of[T any](value T) Optional[T] {
|
||||
return Optional[T]{value: &value, mu: &sync.RWMutex{}}
|
||||
}
|
||||
|
||||
// FromNillable returns an Optional for a given value, which may be nil.
|
||||
func FromNillable[T any](value *T) Optional[T] {
|
||||
if value == nil {
|
||||
return Default[T]()
|
||||
}
|
||||
return Optional[T]{value: value, mu: &sync.RWMutex{}}
|
||||
}
|
||||
|
||||
// IsNotNil checks if there is a value present.
|
||||
func (o Optional[T]) IsNotNil() bool {
|
||||
o.mu.RLock()
|
||||
defer o.mu.RUnlock()
|
||||
|
||||
return o.value != nil
|
||||
}
|
||||
|
||||
// IsNil checks if the Optional is nil.
|
||||
func (o Optional[T]) IsNil() bool {
|
||||
return !o.IsNotNil()
|
||||
}
|
||||
|
||||
// IfNotNil performs the given action with the value if a value is not nil.
|
||||
func (o Optional[T]) IfNotNil(action func(value T)) {
|
||||
o.mu.RLock()
|
||||
defer o.mu.RUnlock()
|
||||
|
||||
if o.value != nil {
|
||||
action(*o.value)
|
||||
}
|
||||
}
|
||||
|
||||
// IfNotNilOrElse performs the action with the value if present, otherwise performs the fallback action.
|
||||
func (o Optional[T]) IfNotNilOrElse(action func(value T), fallbackAction func()) {
|
||||
o.mu.RLock()
|
||||
defer o.mu.RUnlock()
|
||||
|
||||
if o.value != nil {
|
||||
action(*o.value)
|
||||
} else {
|
||||
fallbackAction()
|
||||
}
|
||||
}
|
||||
|
||||
// Unwarp returns the value if not nil, otherwise panics.
|
||||
func (o Optional[T]) Unwarp() T {
|
||||
o.mu.RLock()
|
||||
defer o.mu.RUnlock()
|
||||
|
||||
if o.value == nil {
|
||||
panic("Optional.Get: no value present")
|
||||
}
|
||||
return *o.value
|
||||
}
|
||||
|
||||
// OrElse returns the value if is not nil, otherwise returns other.
|
||||
func (o Optional[T]) OrElse(other T) T {
|
||||
o.mu.RLock()
|
||||
defer o.mu.RUnlock()
|
||||
|
||||
if o.value != nil {
|
||||
return *o.value
|
||||
}
|
||||
return other
|
||||
}
|
||||
|
||||
// OrElseGet returns the value if is not nil, otherwise invokes action and returns the result.
|
||||
func (o Optional[T]) OrElseGet(action func() T) T {
|
||||
o.mu.RLock()
|
||||
defer o.mu.RUnlock()
|
||||
|
||||
if o.value != nil {
|
||||
return *o.value
|
||||
}
|
||||
return action()
|
||||
}
|
||||
|
||||
// OrElseTrigger returns the value if present, otherwise returns an error.
|
||||
func (o Optional[T]) OrElseTrigger(errorHandler func() error) (T, error) {
|
||||
o.mu.RLock()
|
||||
defer o.mu.RUnlock()
|
||||
|
||||
if o.value == nil {
|
||||
return *new(T), errorHandler()
|
||||
}
|
||||
return *o.value, nil
|
||||
}
|
||||
151
datastructure/optional/optional_test.go
Normal file
151
datastructure/optional/optional_test.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package optional
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
|
||||
func TestDefault(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestEmpty")
|
||||
opt := Default[int]()
|
||||
|
||||
assert.ShouldBeTrue(opt.IsNil())
|
||||
}
|
||||
|
||||
func TestOf(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestOf")
|
||||
value := 42
|
||||
opt := Of(value)
|
||||
|
||||
assert.ShouldBeTrue(opt.IsNotNil())
|
||||
assert.Equal(opt.Unwarp(), value)
|
||||
}
|
||||
|
||||
func TestFromNillable(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestOfNullable")
|
||||
var value *int = nil
|
||||
opt := FromNillable(value)
|
||||
|
||||
assert.ShouldBeFalse(opt.IsNotNil())
|
||||
|
||||
value = new(int)
|
||||
*value = 42
|
||||
opt = FromNillable(value)
|
||||
|
||||
assert.ShouldBeTrue(opt.IsNotNil())
|
||||
}
|
||||
|
||||
func TestOrElse(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestOrElse")
|
||||
optDefault := Default[int]()
|
||||
defaultValue := 100
|
||||
|
||||
val := optDefault.OrElse(defaultValue)
|
||||
assert.Equal(val, defaultValue)
|
||||
|
||||
optWithValue := Of(42)
|
||||
val = optWithValue.OrElse(defaultValue)
|
||||
assert.Equal(val, 42)
|
||||
}
|
||||
|
||||
func TestOrElseGetHappyPath(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestOrElseGetHappyPath")
|
||||
optWithValue := Of(42)
|
||||
action := func() int { return 100 }
|
||||
|
||||
val := optWithValue.OrElseGet(action)
|
||||
assert.Equal(val, 42)
|
||||
}
|
||||
|
||||
func TestOrElseGet(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestOrElseGet")
|
||||
optDefault := Default[int]()
|
||||
action := func() int { return 100 }
|
||||
|
||||
val := optDefault.OrElseGet(action)
|
||||
assert.Equal(val, action())
|
||||
}
|
||||
|
||||
func TestOrElseTrigger(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "OrElseTrigger")
|
||||
optDefault := Default[int]()
|
||||
_, err := optDefault.OrElseTrigger(func() error { return errors.New("no value") })
|
||||
|
||||
assert.Equal(err.Error(), "no value")
|
||||
|
||||
optWithValue := Of(42)
|
||||
val, err := optWithValue.OrElseTrigger(func() error { return errors.New("no value") })
|
||||
|
||||
assert.IsNil(err)
|
||||
assert.Equal(val, 42)
|
||||
}
|
||||
|
||||
func TestIfNotNil(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "IfNotNil")
|
||||
called := false
|
||||
action := func(value int) { called = true }
|
||||
|
||||
optDefault := Default[int]()
|
||||
optDefault.IfNotNil(action)
|
||||
|
||||
assert.ShouldBeFalse(called)
|
||||
|
||||
called = false // Reset for next test
|
||||
optWithValue := Of(42)
|
||||
optWithValue.IfNotNil(action)
|
||||
|
||||
assert.ShouldBeTrue(called)
|
||||
}
|
||||
|
||||
func TestIfNotNilOrElse(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestIfNotNilOrElse")
|
||||
|
||||
// Test when value is present
|
||||
calledWithValue := false
|
||||
valueAction := func(value int) { calledWithValue = true }
|
||||
fallbackAction := func() { t.Errorf("Empty action should not be called when value is present") }
|
||||
|
||||
optWithValue := Of(42)
|
||||
optWithValue.IfNotNilOrElse(valueAction, fallbackAction)
|
||||
|
||||
assert.ShouldBeTrue(calledWithValue)
|
||||
|
||||
// Test when value is not present
|
||||
calledWithEmpty := false
|
||||
valueAction = func(value int) { t.Errorf("Value action should not be called when value is not present") }
|
||||
fallbackAction = func() { calledWithEmpty = true }
|
||||
|
||||
optDefault := Default[int]()
|
||||
optDefault.IfNotNilOrElse(valueAction, fallbackAction)
|
||||
|
||||
assert.ShouldBeTrue(calledWithEmpty)
|
||||
}
|
||||
|
||||
func TestGetWithPanicStandard(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestGetWithPanicStandard")
|
||||
|
||||
// Test when value is present
|
||||
optWithValue := Of(42)
|
||||
func() {
|
||||
defer func() {
|
||||
r := recover()
|
||||
assert.IsNil(r)
|
||||
}()
|
||||
val := optWithValue.Unwarp()
|
||||
if val != 42 {
|
||||
t.Errorf("Expected Unwarp to return 42, got %v", val)
|
||||
}
|
||||
}()
|
||||
|
||||
// Test when value is not present
|
||||
optDefault := Default[int]()
|
||||
func() {
|
||||
defer func() {
|
||||
r := recover()
|
||||
assert.IsNotNil(r)
|
||||
}()
|
||||
_ = optDefault.Unwarp()
|
||||
}()
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
// Copyright 2021 dudaodong@gmail.com. All rights reserved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
// Package datastructure contains some data structure.
|
||||
// Queue structure contains ArrayQueue, LinkedQueue, CircularQueue, and PriorityQueue.
|
||||
package datastructure
|
||||
|
||||
import (
|
||||
@@ -7,7 +12,7 @@ import (
|
||||
|
||||
// ArrayQueue implements queue with slice
|
||||
type ArrayQueue[T any] struct {
|
||||
items []T
|
||||
data []T
|
||||
head int
|
||||
tail int
|
||||
capacity int
|
||||
@@ -16,7 +21,7 @@ type ArrayQueue[T any] struct {
|
||||
|
||||
func NewArrayQueue[T any](capacity int) *ArrayQueue[T] {
|
||||
return &ArrayQueue[T]{
|
||||
items: make([]T, 0, capacity),
|
||||
data: make([]T, 0, capacity),
|
||||
head: 0,
|
||||
tail: 0,
|
||||
capacity: capacity,
|
||||
@@ -24,16 +29,16 @@ func NewArrayQueue[T any](capacity int) *ArrayQueue[T] {
|
||||
}
|
||||
}
|
||||
|
||||
// Data return queue data
|
||||
// Data return slice of queue data
|
||||
func (q *ArrayQueue[T]) Data() []T {
|
||||
items := []T{}
|
||||
items := make([]T, 0, q.tail-q.head)
|
||||
for i := q.head; i < q.tail; i++ {
|
||||
items = append(items, q.items[i])
|
||||
items = append(items, q.data[i])
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
// Size return length of queue data
|
||||
// Size return number of elements in queue
|
||||
func (q *ArrayQueue[T]) Size() int {
|
||||
return q.size
|
||||
}
|
||||
@@ -43,42 +48,78 @@ func (q *ArrayQueue[T]) IsEmpty() bool {
|
||||
return q.size == 0
|
||||
}
|
||||
|
||||
// IsFull checks if queue is full or not
|
||||
func (q *ArrayQueue[T]) IsFull() bool {
|
||||
return q.size == q.capacity
|
||||
}
|
||||
|
||||
// Front return front value of queue
|
||||
func (q *ArrayQueue[T]) Front() T {
|
||||
return q.items[0]
|
||||
return q.data[q.head]
|
||||
}
|
||||
|
||||
// Back return back value of queue
|
||||
func (q *ArrayQueue[T]) Back() T {
|
||||
return q.items[q.size-1]
|
||||
return q.data[q.tail-1]
|
||||
}
|
||||
|
||||
// EnQueue put element into queue
|
||||
func (q *ArrayQueue[T]) Enqueue(item T) bool {
|
||||
if q.head == 0 && q.tail == q.capacity {
|
||||
return false
|
||||
} else if q.head != 0 && q.tail == q.capacity {
|
||||
for i := q.head; i < q.tail; i++ {
|
||||
q.items[i-q.head] = q.items[i]
|
||||
if q.tail < q.capacity {
|
||||
q.data = append(q.data, item)
|
||||
// q.tail++
|
||||
q.data[q.tail] = item
|
||||
} else {
|
||||
//upgrade
|
||||
if q.head > 0 {
|
||||
for i := 0; i < q.tail-q.head; i++ {
|
||||
q.data[i] = q.data[i+q.head]
|
||||
}
|
||||
q.tail -= q.head
|
||||
q.head = 0
|
||||
} else {
|
||||
if q.capacity < 65536 {
|
||||
if q.capacity == 0 {
|
||||
q.capacity = 1
|
||||
}
|
||||
q.capacity *= 2
|
||||
} else {
|
||||
q.capacity += 2 ^ 16
|
||||
}
|
||||
|
||||
tmp := make([]T, q.capacity, q.capacity)
|
||||
copy(tmp, q.data)
|
||||
q.data = tmp
|
||||
}
|
||||
q.tail = q.tail - q.head
|
||||
q.head = 0
|
||||
|
||||
q.data[q.tail] = item
|
||||
}
|
||||
|
||||
q.items = append(q.items, item)
|
||||
q.tail++
|
||||
q.size++
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// DeQueue remove head element of queue and return it, if queue is empty, return nil and error
|
||||
func (q *ArrayQueue[T]) Dequeue() (T, bool) {
|
||||
var item T
|
||||
if q.head == q.tail {
|
||||
if q.size == 0 {
|
||||
return item, false
|
||||
}
|
||||
item = q.items[q.head]
|
||||
|
||||
item = q.data[q.head]
|
||||
q.head++
|
||||
|
||||
if q.head >= 1024 || q.head*2 > q.tail {
|
||||
q.capacity -= q.head
|
||||
q.tail -= q.head
|
||||
tmp := make([]T, q.capacity, q.capacity)
|
||||
copy(tmp, q.data[q.head:])
|
||||
q.data = tmp
|
||||
q.head = 0
|
||||
}
|
||||
|
||||
q.size--
|
||||
return item, true
|
||||
}
|
||||
@@ -86,7 +127,7 @@ func (q *ArrayQueue[T]) Dequeue() (T, bool) {
|
||||
// Clear the queue data
|
||||
func (q *ArrayQueue[T]) Clear() {
|
||||
capacity := q.capacity
|
||||
q.items = make([]T, 0, capacity)
|
||||
q.data = make([]T, 0, capacity)
|
||||
q.head = 0
|
||||
q.tail = 0
|
||||
q.size = 0
|
||||
@@ -95,7 +136,7 @@ func (q *ArrayQueue[T]) Clear() {
|
||||
|
||||
// Contain checks if the value is in queue or not
|
||||
func (q *ArrayQueue[T]) Contain(value T) bool {
|
||||
for _, v := range q.items {
|
||||
for _, v := range q.data {
|
||||
if reflect.DeepEqual(v, value) {
|
||||
return true
|
||||
}
|
||||
@@ -107,7 +148,7 @@ func (q *ArrayQueue[T]) Contain(value T) bool {
|
||||
func (q *ArrayQueue[T]) Print() {
|
||||
info := "["
|
||||
for i := q.head; i < q.tail; i++ {
|
||||
info += fmt.Sprintf("%+v, ", q.items[i])
|
||||
info += fmt.Sprintf("%+v, ", q.data[i])
|
||||
}
|
||||
info += "]"
|
||||
fmt.Println(info)
|
||||
|
||||
@@ -7,24 +7,25 @@ import (
|
||||
)
|
||||
|
||||
func TestArrayQueue_Enqueue(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestArrayQueue_Enqueue")
|
||||
|
||||
queue := NewArrayQueue[int](5)
|
||||
queue := NewArrayQueue[int](2)
|
||||
queue.Enqueue(1)
|
||||
queue.Enqueue(2)
|
||||
queue.Enqueue(3)
|
||||
|
||||
expected := []int{1, 2, 3}
|
||||
data := queue.Data()
|
||||
size := queue.Size()
|
||||
|
||||
queue.Print()
|
||||
|
||||
assert.Equal(expected, data)
|
||||
assert.Equal([]int{1, 2, 3}, data)
|
||||
assert.Equal(3, size)
|
||||
}
|
||||
|
||||
func TestArrayQueue_Dequeue(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestArrayQueue_Dequeue")
|
||||
|
||||
queue := NewArrayQueue[int](4)
|
||||
@@ -35,12 +36,13 @@ func TestArrayQueue_Dequeue(t *testing.T) {
|
||||
val, ok := queue.Dequeue()
|
||||
assert.Equal(true, ok)
|
||||
|
||||
queue.Print()
|
||||
assert.Equal(1, val)
|
||||
assert.Equal([]int{2, 3}, queue.Data())
|
||||
}
|
||||
|
||||
func TestArrayQueue_Front(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestArrayQueue_Front")
|
||||
|
||||
queue := NewArrayQueue[int](4)
|
||||
@@ -50,13 +52,13 @@ func TestArrayQueue_Front(t *testing.T) {
|
||||
|
||||
val := queue.Front()
|
||||
|
||||
queue.Print()
|
||||
|
||||
assert.Equal(1, val)
|
||||
assert.Equal([]int{1, 2, 3}, queue.Data())
|
||||
}
|
||||
|
||||
func TestArrayQueue_Back(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestArrayQueue_Back")
|
||||
|
||||
queue := NewArrayQueue[int](4)
|
||||
@@ -66,13 +68,13 @@ func TestArrayQueue_Back(t *testing.T) {
|
||||
|
||||
val := queue.Back()
|
||||
|
||||
queue.Print()
|
||||
|
||||
assert.Equal(3, val)
|
||||
assert.Equal([]int{1, 2, 3}, queue.Data())
|
||||
}
|
||||
|
||||
func TestArrayQueue_Contain(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestArrayQueue_Contain")
|
||||
|
||||
queue := NewArrayQueue[int](4)
|
||||
@@ -85,6 +87,8 @@ func TestArrayQueue_Contain(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestArrayQueue_Clear(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestArrayQueue_Clear")
|
||||
|
||||
queue := NewArrayQueue[int](4)
|
||||
@@ -100,3 +104,16 @@ func TestArrayQueue_Clear(t *testing.T) {
|
||||
assert.Equal(true, queue.IsEmpty())
|
||||
assert.Equal(0, queue.Size())
|
||||
}
|
||||
|
||||
func TestArrayQueue_IsFull(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestArrayQueue_IsFull")
|
||||
|
||||
queue := NewArrayQueue[int](3)
|
||||
queue.Enqueue(1)
|
||||
queue.Enqueue(2)
|
||||
queue.Enqueue(3)
|
||||
|
||||
assert.Equal(true, queue.IsFull())
|
||||
}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
// Copyright 2021 dudaodong@gmail.com. All rights reserved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
// Package datastructure contains some data structure.
|
||||
// Queue structure contains ArrayQueue, LinkedQueue, CircularQueue, and PriorityQueue.
|
||||
package datastructure
|
||||
|
||||
import (
|
||||
@@ -7,21 +12,21 @@ import (
|
||||
)
|
||||
|
||||
// CircularQueue implements circular queue with slice,
|
||||
// last index of CircularQueue don't contain value, so acturl capacity is size - 1
|
||||
// last index of CircularQueue don't contain value, so acturl capacity is capacity - 1
|
||||
type CircularQueue[T any] struct {
|
||||
data []T
|
||||
front int
|
||||
rear int
|
||||
size int
|
||||
data []T
|
||||
front int
|
||||
rear int
|
||||
capacity int
|
||||
}
|
||||
|
||||
// NewCircularQueue return a empty CircularQueue pointer
|
||||
func NewCircularQueue[T any](size int) *CircularQueue[T] {
|
||||
data := make([]T, size)
|
||||
return &CircularQueue[T]{data: data, front: 0, rear: 0, size: size}
|
||||
func NewCircularQueue[T any](capacity int) *CircularQueue[T] {
|
||||
data := make([]T, capacity)
|
||||
return &CircularQueue[T]{data: data, front: 0, rear: 0, capacity: capacity}
|
||||
}
|
||||
|
||||
// Data return queue data
|
||||
// Data return slice of queue data
|
||||
func (q *CircularQueue[T]) Data() []T {
|
||||
data := []T{}
|
||||
|
||||
@@ -37,12 +42,12 @@ func (q *CircularQueue[T]) Data() []T {
|
||||
return data
|
||||
}
|
||||
|
||||
// Length return current data length of queue
|
||||
func (q *CircularQueue[T]) Length() int {
|
||||
if q.size == 0 {
|
||||
// Size return number of elements in circular queue
|
||||
func (q *CircularQueue[T]) Size() int {
|
||||
if q.capacity == 0 {
|
||||
return 0
|
||||
}
|
||||
return (q.rear - q.front + q.size) % q.size
|
||||
return (q.rear - q.front + q.capacity) % q.capacity
|
||||
}
|
||||
|
||||
// IsEmpty checks if queue is empty or not
|
||||
@@ -52,7 +57,7 @@ func (q *CircularQueue[T]) IsEmpty() bool {
|
||||
|
||||
// IsFull checks if queue is full or not
|
||||
func (q *CircularQueue[T]) IsFull() bool {
|
||||
return (q.rear+1)%q.size == q.front
|
||||
return (q.rear+1)%q.capacity == q.front
|
||||
}
|
||||
|
||||
// Front return front value of queue
|
||||
@@ -65,23 +70,23 @@ func (q *CircularQueue[T]) Back() T {
|
||||
if q.rear-1 >= 0 {
|
||||
return q.data[q.rear-1]
|
||||
}
|
||||
return q.data[q.size-1]
|
||||
return q.data[q.capacity-1]
|
||||
}
|
||||
|
||||
// EnQueue put element into queue
|
||||
func (q *CircularQueue[T]) EnQueue(value T) error {
|
||||
// Enqueue put element into queue
|
||||
func (q *CircularQueue[T]) Enqueue(value T) error {
|
||||
if q.IsFull() {
|
||||
return errors.New("queue is full!")
|
||||
}
|
||||
|
||||
q.data[q.rear] = value
|
||||
q.rear = (q.rear + 1) % q.size
|
||||
q.rear = (q.rear + 1) % q.capacity
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeQueue remove head element of queue and return it, if queue is empty, return nil and error
|
||||
func (q *CircularQueue[T]) DeQueue() (*T, error) {
|
||||
// Dequeue remove head element of queue and return it, if queue is empty, return nil and error
|
||||
func (q *CircularQueue[T]) Dequeue() (*T, error) {
|
||||
if q.IsEmpty() {
|
||||
return nil, errors.New("queue is empty")
|
||||
}
|
||||
@@ -89,7 +94,7 @@ func (q *CircularQueue[T]) DeQueue() (*T, error) {
|
||||
headItem := q.data[q.front]
|
||||
var t T
|
||||
q.data[q.front] = t
|
||||
q.front = (q.front + 1) % q.size
|
||||
q.front = (q.front + 1) % q.capacity
|
||||
|
||||
return &headItem, nil
|
||||
}
|
||||
@@ -99,7 +104,7 @@ func (q *CircularQueue[T]) Clear() {
|
||||
q.data = []T{}
|
||||
q.front = 0
|
||||
q.rear = 0
|
||||
q.size = 0
|
||||
q.capacity = 0
|
||||
}
|
||||
|
||||
// Contain checks if the value is in queue or not
|
||||
|
||||
@@ -6,143 +6,154 @@ import (
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
|
||||
func TestCircularQueue_EnQueue(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestCircularQueue_EnQueue")
|
||||
func TestCircularQueue_Enqueue(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestCircularQueue_Enqueue")
|
||||
|
||||
queue := NewCircularQueue[int](6)
|
||||
queue.EnQueue(1)
|
||||
queue.EnQueue(2)
|
||||
queue.EnQueue(3)
|
||||
queue.EnQueue(4)
|
||||
queue.EnQueue(5)
|
||||
|
||||
queue.Print()
|
||||
// assert.Equal([]int{1, 2, 3, 4, 5}, queue.Data())
|
||||
assert.Equal(5, queue.Length())
|
||||
err := queue.Enqueue(1)
|
||||
assert.IsNil(err)
|
||||
|
||||
err := queue.EnQueue(6)
|
||||
err = queue.Enqueue(2)
|
||||
assert.IsNil(err)
|
||||
|
||||
err = queue.Enqueue(3)
|
||||
assert.IsNil(err)
|
||||
|
||||
err = queue.Enqueue(4)
|
||||
assert.IsNil(err)
|
||||
|
||||
err = queue.Enqueue(5)
|
||||
assert.IsNil(err)
|
||||
|
||||
assert.Equal([]int{1, 2, 3, 4, 5}, queue.Data())
|
||||
assert.Equal(5, queue.Size())
|
||||
|
||||
err = queue.Enqueue(6)
|
||||
assert.IsNotNil(err)
|
||||
}
|
||||
|
||||
func TestCircularQueue_DeQueue(t *testing.T) {
|
||||
func TestCircularQueue_Dequeue(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestCircularQueue_DeQueue")
|
||||
|
||||
queue := NewCircularQueue[int](6)
|
||||
queue := NewCircularQueue[int](4)
|
||||
assert.Equal(true, queue.IsEmpty())
|
||||
|
||||
queue.EnQueue(1)
|
||||
queue.EnQueue(2)
|
||||
queue.EnQueue(3)
|
||||
queue.EnQueue(4)
|
||||
queue.EnQueue(5)
|
||||
err := queue.Enqueue(1)
|
||||
assert.IsNil(err)
|
||||
|
||||
val, err := queue.DeQueue()
|
||||
err = queue.Enqueue(2)
|
||||
assert.IsNil(err)
|
||||
|
||||
err = queue.Enqueue(3)
|
||||
assert.IsNil(err)
|
||||
|
||||
val, err := queue.Dequeue()
|
||||
assert.IsNil(err)
|
||||
|
||||
assert.Equal(1, *val)
|
||||
assert.Equal(false, queue.IsFull())
|
||||
|
||||
val, _ = queue.DeQueue()
|
||||
queue.Print()
|
||||
val, _ = queue.Dequeue()
|
||||
assert.Equal(2, *val)
|
||||
|
||||
queue.EnQueue(6)
|
||||
queue.Print()
|
||||
assert.Equal(false, queue.IsFull())
|
||||
}
|
||||
|
||||
func TestCircularQueue_Front(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestCircularQueue_Front")
|
||||
|
||||
queue := NewCircularQueue[int](6)
|
||||
queue.EnQueue(1)
|
||||
queue.EnQueue(2)
|
||||
queue.EnQueue(3)
|
||||
queue.EnQueue(4)
|
||||
queue.EnQueue(5)
|
||||
|
||||
queue.Print()
|
||||
err := queue.Enqueue(1)
|
||||
assert.IsNil(err)
|
||||
|
||||
queue.DeQueue()
|
||||
queue.DeQueue()
|
||||
queue.EnQueue(6)
|
||||
queue.EnQueue(7)
|
||||
err = queue.Enqueue(2)
|
||||
assert.IsNil(err)
|
||||
|
||||
queue.Print()
|
||||
err = queue.Enqueue(3)
|
||||
assert.IsNil(err)
|
||||
|
||||
val := queue.Front()
|
||||
assert.Equal(3, val)
|
||||
assert.Equal(5, queue.Length())
|
||||
assert.IsNil(err)
|
||||
assert.Equal(1, val)
|
||||
assert.Equal(3, queue.Size())
|
||||
}
|
||||
|
||||
func TestCircularQueue_Back(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestCircularQueue_Back")
|
||||
|
||||
queue := NewCircularQueue[int](6)
|
||||
queue := NewCircularQueue[int](3)
|
||||
assert.Equal(true, queue.IsEmpty())
|
||||
|
||||
queue.EnQueue(1)
|
||||
queue.EnQueue(2)
|
||||
queue.EnQueue(3)
|
||||
queue.EnQueue(4)
|
||||
queue.EnQueue(5)
|
||||
err := queue.Enqueue(1)
|
||||
assert.IsNil(err)
|
||||
|
||||
queue.Print()
|
||||
assert.Equal(5, queue.Back())
|
||||
err = queue.Enqueue(2)
|
||||
assert.IsNil(err)
|
||||
|
||||
queue.DeQueue()
|
||||
queue.DeQueue()
|
||||
queue.EnQueue(6)
|
||||
queue.EnQueue(7)
|
||||
assert.Equal(2, queue.Back())
|
||||
|
||||
queue.Print()
|
||||
assert.Equal(7, queue.Back())
|
||||
val, _ := queue.Dequeue()
|
||||
assert.Equal(1, *val)
|
||||
|
||||
err = queue.Enqueue(3)
|
||||
assert.IsNil(err)
|
||||
|
||||
assert.Equal(3, queue.Back())
|
||||
}
|
||||
|
||||
func TestCircularQueue_Contain(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestCircularQueue_Contain")
|
||||
|
||||
queue := NewCircularQueue[int](2)
|
||||
queue.EnQueue(1)
|
||||
err := queue.Enqueue(1)
|
||||
assert.IsNil(err)
|
||||
|
||||
assert.Equal(true, queue.Contain(1))
|
||||
assert.Equal(false, queue.Contain(2))
|
||||
}
|
||||
|
||||
func TestCircularQueue_Clear(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestCircularQueue_Clear")
|
||||
|
||||
queue := NewCircularQueue[int](3)
|
||||
assert.Equal(true, queue.IsEmpty())
|
||||
assert.Equal(0, queue.Length())
|
||||
assert.Equal(0, queue.Size())
|
||||
|
||||
err := queue.Enqueue(1)
|
||||
assert.IsNil(err)
|
||||
|
||||
queue.EnQueue(1)
|
||||
assert.Equal(false, queue.IsEmpty())
|
||||
assert.Equal(1, queue.Length())
|
||||
assert.Equal(1, queue.Size())
|
||||
|
||||
queue.Clear()
|
||||
assert.Equal(true, queue.IsEmpty())
|
||||
assert.Equal(0, queue.Length())
|
||||
assert.Equal(0, queue.Size())
|
||||
}
|
||||
|
||||
func TestCircularQueue_Data(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestCircularQueue_Data")
|
||||
|
||||
queue := NewCircularQueue[int](6)
|
||||
queue.EnQueue(1)
|
||||
queue.EnQueue(2)
|
||||
queue.EnQueue(3)
|
||||
queue.EnQueue(4)
|
||||
queue.EnQueue(5)
|
||||
queue := NewCircularQueue[int](3)
|
||||
err := queue.Enqueue(1)
|
||||
assert.IsNil(err)
|
||||
|
||||
queue.Print()
|
||||
assert.Equal([]int{1, 2, 3, 4, 5}, queue.Data())
|
||||
|
||||
queue.DeQueue()
|
||||
queue.DeQueue()
|
||||
queue.EnQueue(6)
|
||||
queue.EnQueue(7)
|
||||
|
||||
queue.Print()
|
||||
assert.Equal([]int{3, 4, 5, 6, 7}, queue.Data())
|
||||
err = queue.Enqueue(2)
|
||||
assert.IsNil(err)
|
||||
|
||||
assert.Equal([]int{1, 2}, queue.Data())
|
||||
}
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
// Copyright 2021 dudaodong@gmail.com. All rights reserved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
// Package datastructure contains some data structure.
|
||||
// Queue structure contains ArrayQueue, LinkedQueue, CircularQueue, and PriorityQueue.
|
||||
package datastructure
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/duke-git/lancet/v2/datastructure"
|
||||
)
|
||||
@@ -19,9 +25,9 @@ func NewLinkedQueue[T any]() *LinkedQueue[T] {
|
||||
return &LinkedQueue[T]{head: nil, tail: nil, length: 0}
|
||||
}
|
||||
|
||||
// Data return queue data
|
||||
// Data return slice of queue data
|
||||
func (q *LinkedQueue[T]) Data() []T {
|
||||
res := []T{}
|
||||
res := make([]T, 0, q.length)
|
||||
current := q.head
|
||||
|
||||
for current != nil {
|
||||
@@ -41,8 +47,8 @@ func (q *LinkedQueue[T]) IsEmpty() bool {
|
||||
return q.length == 0
|
||||
}
|
||||
|
||||
// EnQueue add element into queue
|
||||
func (q *LinkedQueue[T]) EnQueue(value T) {
|
||||
// Enqueue put element into queue
|
||||
func (q *LinkedQueue[T]) Enqueue(value T) {
|
||||
newNode := datastructure.NewQueueNode(value)
|
||||
|
||||
if q.IsEmpty() {
|
||||
@@ -55,8 +61,8 @@ func (q *LinkedQueue[T]) EnQueue(value T) {
|
||||
q.length++
|
||||
}
|
||||
|
||||
// DeQueue delete head element of queue then return it, if queue is empty, return nil and error
|
||||
func (q *LinkedQueue[T]) DeQueue() (*T, error) {
|
||||
// Dequeue delete head element of queue then return it, if queue is empty, return nil and error
|
||||
func (q *LinkedQueue[T]) Dequeue() (*T, error) {
|
||||
if q.IsEmpty() {
|
||||
return nil, errors.New("queue is empty")
|
||||
}
|
||||
@@ -102,3 +108,15 @@ func (q *LinkedQueue[T]) Print() {
|
||||
info += " ]"
|
||||
fmt.Println(info)
|
||||
}
|
||||
|
||||
// Contain checks if the value is in queue or not
|
||||
func (q *LinkedQueue[T]) Contain(value T) bool {
|
||||
current := q.head
|
||||
for current != nil {
|
||||
if reflect.DeepEqual(current.Value, value) {
|
||||
return true
|
||||
}
|
||||
current = current.Next
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -6,29 +6,31 @@ import (
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
|
||||
func TestLinkedQueue_EnQueue(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestLinkedQueue_EnQueue")
|
||||
func TestLinkedQueue_Enqueue(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestLinkedQueue_Enqueue")
|
||||
|
||||
queue := NewLinkedQueue[int]()
|
||||
queue.EnQueue(1)
|
||||
queue.EnQueue(2)
|
||||
queue.EnQueue(3)
|
||||
|
||||
queue.Print()
|
||||
queue.Enqueue(1)
|
||||
queue.Enqueue(2)
|
||||
queue.Enqueue(3)
|
||||
|
||||
assert.Equal([]int{1, 2, 3}, queue.Data())
|
||||
assert.Equal(3, queue.Size())
|
||||
}
|
||||
|
||||
func TestLinkedQueue_DeQueue(t *testing.T) {
|
||||
func TestLinkedQueue_Dequeue(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestLinkedQueue_DeQueue")
|
||||
|
||||
queue := NewLinkedQueue[int]()
|
||||
queue.EnQueue(1)
|
||||
queue.EnQueue(2)
|
||||
queue.EnQueue(3)
|
||||
queue.Enqueue(1)
|
||||
queue.Enqueue(2)
|
||||
queue.Enqueue(3)
|
||||
|
||||
val, _ := queue.DeQueue()
|
||||
val, _ := queue.Dequeue()
|
||||
|
||||
queue.Print()
|
||||
|
||||
@@ -37,15 +39,17 @@ func TestLinkedQueue_DeQueue(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLinkedQueue_Front(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestLinkedQueue_Front")
|
||||
|
||||
queue := NewLinkedQueue[int]()
|
||||
_, err := queue.Front()
|
||||
assert.IsNotNil(err)
|
||||
|
||||
queue.EnQueue(1)
|
||||
queue.EnQueue(2)
|
||||
queue.EnQueue(3)
|
||||
queue.Enqueue(1)
|
||||
queue.Enqueue(2)
|
||||
queue.Enqueue(3)
|
||||
|
||||
val, err := queue.Front()
|
||||
assert.Equal(1, *val)
|
||||
@@ -53,15 +57,17 @@ func TestLinkedQueue_Front(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLinkedQueue_Back(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestLinkedQueue_Back")
|
||||
|
||||
queue := NewLinkedQueue[int]()
|
||||
_, err := queue.Back()
|
||||
assert.IsNotNil(err)
|
||||
|
||||
queue.EnQueue(1)
|
||||
queue.EnQueue(2)
|
||||
queue.EnQueue(3)
|
||||
queue.Enqueue(1)
|
||||
queue.Enqueue(2)
|
||||
queue.Enqueue(3)
|
||||
|
||||
val, err := queue.Back()
|
||||
assert.Equal(3, *val)
|
||||
@@ -69,16 +75,32 @@ func TestLinkedQueue_Back(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLinkedQueue_Clear(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestLinkedQueue_Back")
|
||||
|
||||
queue := NewLinkedQueue[int]()
|
||||
assert.Equal(true, queue.IsEmpty())
|
||||
|
||||
queue.EnQueue(1)
|
||||
queue.EnQueue(2)
|
||||
queue.EnQueue(3)
|
||||
queue.Enqueue(1)
|
||||
queue.Enqueue(2)
|
||||
queue.Enqueue(3)
|
||||
assert.Equal(false, queue.IsEmpty())
|
||||
|
||||
queue.Clear()
|
||||
assert.Equal(true, queue.IsEmpty())
|
||||
}
|
||||
|
||||
func TestLinkedQueue_Contain(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestLinkedQueue_Contain")
|
||||
|
||||
queue := NewLinkedQueue[int]()
|
||||
queue.Enqueue(1)
|
||||
queue.Enqueue(2)
|
||||
queue.Enqueue(3)
|
||||
|
||||
assert.Equal(true, queue.Contain(1))
|
||||
assert.Equal(false, queue.Contain(4))
|
||||
}
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
// Copyright 2021 dudaodong@gmail.com. All rights reserved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
// Package datastructure contains some data structure.
|
||||
// Queue structure contains ArrayQueue, LinkedQueue, CircularQueue, and PriorityQueue.
|
||||
package datastructure
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/duke-git/lancet/v2/lancetconstraints"
|
||||
"github.com/duke-git/lancet/v2/constraints"
|
||||
)
|
||||
|
||||
// PriorityQueue is a priority queue implemented by binary heap tree
|
||||
// type T should implements Compare function in lancetconstraints.Comparator interface.
|
||||
// type T should implements Compare function in constraints.Comparator interface.
|
||||
type PriorityQueue[T any] struct {
|
||||
items []T
|
||||
size int
|
||||
comparator lancetconstraints.Comparator
|
||||
comparator constraints.Comparator
|
||||
}
|
||||
|
||||
// NewPriorityQueue return a pointer of PriorityQueue
|
||||
// param `comparator` is used to compare values in the queue
|
||||
func NewPriorityQueue[T any](capacity int, comparator lancetconstraints.Comparator) *PriorityQueue[T] {
|
||||
func NewPriorityQueue[T any](capacity int, comparator constraints.Comparator) *PriorityQueue[T] {
|
||||
return &PriorityQueue[T]{
|
||||
items: make([]T, capacity+1),
|
||||
size: 0,
|
||||
@@ -29,12 +34,17 @@ func (q *PriorityQueue[T]) IsEmpty() bool {
|
||||
return q.size == 0
|
||||
}
|
||||
|
||||
// Size get number of items in the queue
|
||||
func (q *PriorityQueue[T]) Size() int {
|
||||
return q.size
|
||||
}
|
||||
|
||||
// IsFull checks if the queue capacity is full or not
|
||||
func (q *PriorityQueue[T]) IsFull() bool {
|
||||
return q.size == len(q.items)-1
|
||||
}
|
||||
|
||||
// Data return data slice in the queue
|
||||
// Data return a slice of queue data
|
||||
func (q *PriorityQueue[T]) Data() []T {
|
||||
data := make([]T, q.size)
|
||||
for i := 1; i < q.size+1; i++ {
|
||||
|
||||
@@ -20,41 +20,55 @@ func (c *intComparator) Compare(v1, v2 any) int {
|
||||
return 0
|
||||
}
|
||||
func TestPriorityQueue_Enqueue(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestPriorityQueue_Enqueue")
|
||||
|
||||
comparator := &intComparator{}
|
||||
pq := NewPriorityQueue[int](10, comparator)
|
||||
pq := NewPriorityQueue[int](3, comparator)
|
||||
|
||||
assert.Equal(true, pq.IsEmpty())
|
||||
assert.Equal(false, pq.IsFull())
|
||||
|
||||
for i := 1; i < 11; i++ {
|
||||
pq.Enqueue(i)
|
||||
}
|
||||
err := pq.Enqueue(1)
|
||||
assert.IsNil(err)
|
||||
|
||||
err = pq.Enqueue(2)
|
||||
assert.IsNil(err)
|
||||
|
||||
err = pq.Enqueue(3)
|
||||
assert.IsNil(err)
|
||||
|
||||
assert.Equal(true, pq.IsFull())
|
||||
|
||||
queueData := pq.Data()
|
||||
assert.Equal([]int{10, 9, 6, 7, 8, 2, 5, 1, 4, 3}, queueData)
|
||||
assert.Equal([]int{3, 1, 2}, queueData)
|
||||
|
||||
}
|
||||
|
||||
func TestPriorityQueue_Dequeue(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestPriorityQueue_Dequeue")
|
||||
|
||||
comparator := &intComparator{}
|
||||
pq := NewPriorityQueue[int](10, comparator)
|
||||
pq := NewPriorityQueue[int](3, comparator)
|
||||
|
||||
_, ok := pq.Dequeue()
|
||||
assert.Equal(false, ok)
|
||||
|
||||
for i := 1; i < 11; i++ {
|
||||
pq.Enqueue(i)
|
||||
}
|
||||
err := pq.Enqueue(1)
|
||||
assert.IsNil(err)
|
||||
|
||||
err = pq.Enqueue(2)
|
||||
assert.IsNil(err)
|
||||
|
||||
err = pq.Enqueue(3)
|
||||
assert.IsNil(err)
|
||||
|
||||
assert.Equal(3, pq.Size())
|
||||
|
||||
val, ok := pq.Dequeue()
|
||||
assert.Equal(true, ok)
|
||||
assert.Equal(10, val)
|
||||
|
||||
assert.Equal([]int{9, 8, 6, 7, 3, 2, 5, 1, 4}, pq.Data())
|
||||
assert.Equal(3, val)
|
||||
}
|
||||
|
||||
@@ -1,29 +1,72 @@
|
||||
// Copyright 2021 dudaodong@gmail.com. All rights reserved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
// Package datastructure contains some data structure. Set is a data container, like slice, but element of set is not duplicate.
|
||||
package datastructure
|
||||
|
||||
// Set is a data container, like slice, but element of set is not duplicate
|
||||
type Set[T comparable] map[T]bool
|
||||
import "sort"
|
||||
|
||||
// NewSet return a instance of set
|
||||
func NewSet[T comparable](values ...T) Set[T] {
|
||||
set := make(Set[T])
|
||||
set.Add(values...)
|
||||
// Set is a data container, like slice, but element of set is not duplicate.
|
||||
type Set[T comparable] map[T]struct{}
|
||||
|
||||
// New create a instance of set from given values.
|
||||
func New[T comparable](items ...T) Set[T] {
|
||||
set := make(Set[T], len(items))
|
||||
set.Add(items...)
|
||||
return set
|
||||
}
|
||||
|
||||
// Add value to set
|
||||
func (s Set[T]) Add(values ...T) {
|
||||
for _, v := range values {
|
||||
s[v] = true
|
||||
// FromSlice create a set from given slice.
|
||||
func FromSlice[T comparable](items []T) Set[T] {
|
||||
set := make(Set[T], len(items))
|
||||
for _, item := range items {
|
||||
set.Add(item)
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
// Add items to set
|
||||
func (s Set[T]) Add(items ...T) {
|
||||
for _, v := range items {
|
||||
s[v] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// Contain checks if set contains value or not
|
||||
func (s Set[T]) Contain(value T) bool {
|
||||
_, ok := s[value]
|
||||
// AddIfNotExist checks if item exists in the set,
|
||||
// it adds the item to set and returns true if it does not exist in the set,
|
||||
// or else it does nothing and returns false.
|
||||
func (s Set[T]) AddIfNotExist(item T) bool {
|
||||
if !s.Contain(item) {
|
||||
if _, ok := s[item]; !ok {
|
||||
s[item] = struct{}{}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AddIfNotExistBy checks if item exists in the set and pass the `checker` function
|
||||
// it adds the item to set and returns true if it does not exists in the set and
|
||||
// function `checker` returns true, or else it does nothing and returns false.
|
||||
func (s Set[T]) AddIfNotExistBy(item T, checker func(element T) bool) bool {
|
||||
if !s.Contain(item) {
|
||||
if checker(item) {
|
||||
if _, ok := s[item]; !ok {
|
||||
s[item] = struct{}{}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Contain checks if set contains item or not
|
||||
func (s Set[T]) Contain(item T) bool {
|
||||
_, ok := s[item]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Contain checks if set contains other set
|
||||
// ContainAll checks if set contains other set
|
||||
func (s Set[T]) ContainAll(other Set[T]) bool {
|
||||
for k := range other {
|
||||
_, ok := s[k]
|
||||
@@ -36,14 +79,13 @@ func (s Set[T]) ContainAll(other Set[T]) bool {
|
||||
|
||||
// Clone return a copy of set
|
||||
func (s Set[T]) Clone() Set[T] {
|
||||
set := NewSet[T]()
|
||||
set.Add(s.Values()...)
|
||||
set := FromSlice(s.ToSlice())
|
||||
return set
|
||||
}
|
||||
|
||||
// Delete value of set
|
||||
func (s Set[T]) Delete(values ...T) {
|
||||
for _, v := range values {
|
||||
// Delete item of set
|
||||
func (s Set[T]) Delete(items ...T) {
|
||||
for _, v := range items {
|
||||
delete(s, v)
|
||||
}
|
||||
}
|
||||
@@ -58,7 +100,7 @@ func (s Set[T]) Equal(other Set[T]) bool {
|
||||
}
|
||||
|
||||
// Iterate call function by every element of set
|
||||
func (s Set[T]) Iterate(fn func(value T)) {
|
||||
func (s Set[T]) Iterate(fn func(item T)) {
|
||||
for v := range s {
|
||||
fn(v)
|
||||
}
|
||||
@@ -75,14 +117,11 @@ func (s Set[T]) Size() int {
|
||||
}
|
||||
|
||||
// Values return all values of set
|
||||
// Deprecated: Values function is deprecated and will be removed in future versions. Please use ToSlice() function instead.
|
||||
//
|
||||
// The ToSlice() function provides the same functionality as Values and returns a slice containing all values of the set.
|
||||
func (s Set[T]) Values() []T {
|
||||
values := make([]T, 0, 0)
|
||||
|
||||
s.Iterate(func(value T) {
|
||||
values = append(values, value)
|
||||
})
|
||||
|
||||
return values
|
||||
return s.ToSlice()
|
||||
}
|
||||
|
||||
// Union creates a new set contain all element of set s and other
|
||||
@@ -94,7 +133,7 @@ func (s Set[T]) Union(other Set[T]) Set[T] {
|
||||
|
||||
// Intersection creates a new set whose element both be contained in set s and other
|
||||
func (s Set[T]) Intersection(other Set[T]) Set[T] {
|
||||
set := NewSet[T]()
|
||||
set := New[T]()
|
||||
s.Iterate(func(value T) {
|
||||
if other.Contain(value) {
|
||||
set.Add(value)
|
||||
@@ -106,7 +145,7 @@ func (s Set[T]) Intersection(other Set[T]) Set[T] {
|
||||
|
||||
// SymmetricDifference creates a new set whose element is in set1 or set2, but not in both sets
|
||||
func (s Set[T]) SymmetricDifference(other Set[T]) Set[T] {
|
||||
set := NewSet[T]()
|
||||
set := New[T]()
|
||||
s.Iterate(func(value T) {
|
||||
if !other.Contain(value) {
|
||||
set.Add(value)
|
||||
@@ -122,9 +161,9 @@ func (s Set[T]) SymmetricDifference(other Set[T]) Set[T] {
|
||||
return set
|
||||
}
|
||||
|
||||
// Minus creates an set of whose element in origin set but not in compared set
|
||||
// Minus creates a set of whose element in origin set but not in compared set
|
||||
func (s Set[T]) Minus(comparedSet Set[T]) Set[T] {
|
||||
set := NewSet[T]()
|
||||
set := New[T]()
|
||||
|
||||
s.Iterate(func(value T) {
|
||||
if !comparedSet.Contain(value) {
|
||||
@@ -134,3 +173,48 @@ func (s Set[T]) Minus(comparedSet Set[T]) Set[T] {
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
// EachWithBreak iterates over elements of a set and invokes function for each element,
|
||||
// when iteratee return false, will break the for each loop.
|
||||
func (s Set[T]) EachWithBreak(iteratee func(item T) bool) {
|
||||
for _, v := range s.Values() {
|
||||
if !iteratee(v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
for item := range s {
|
||||
v = item
|
||||
delete(s, item)
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
|
||||
return v, false
|
||||
}
|
||||
|
||||
// ToSlice returns a slice containing all values of the set.
|
||||
func (s Set[T]) ToSlice() []T {
|
||||
if s.IsEmpty() {
|
||||
return []T{}
|
||||
}
|
||||
result := make([]T, 0, s.Size())
|
||||
s.Iterate(func(value T) {
|
||||
result = append(result, value)
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ToSortedSlice returns a sorted slice containing all values of the set.
|
||||
func (s Set[T]) ToSortedSlice(less func(v1, v2 T) bool) []T {
|
||||
result := s.ToSlice()
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
return less(result[i], result[j])
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1,26 +1,83 @@
|
||||
package datastructure
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
|
||||
func TestSet_FromSlice(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSet_FromSlice")
|
||||
|
||||
s1 := FromSlice([]int{1, 2, 2, 3})
|
||||
assert.Equal(3, s1.Size())
|
||||
assert.Equal(true, s1.Contain(1))
|
||||
assert.Equal(true, s1.Contain(2))
|
||||
assert.Equal(true, s1.Contain(3))
|
||||
|
||||
s2 := FromSlice([]int{})
|
||||
assert.Equal(0, s2.Size())
|
||||
}
|
||||
|
||||
func TestSet_Add(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSet_Add")
|
||||
|
||||
set := NewSet[int]()
|
||||
set := New[int]()
|
||||
set.Add(1, 2, 3)
|
||||
|
||||
expected := NewSet(1, 2, 3)
|
||||
cmpSet := New(1, 2, 3)
|
||||
|
||||
assert.Equal(true, set.Equal(expected))
|
||||
assert.Equal(true, set.Equal(cmpSet))
|
||||
}
|
||||
|
||||
func TestSet_AddIfNotExist(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSet_AddIfNotExist")
|
||||
|
||||
set := New[int]()
|
||||
set.Add(1, 2, 3)
|
||||
|
||||
assert.Equal(false, set.AddIfNotExist(1))
|
||||
assert.Equal(true, set.AddIfNotExist(4))
|
||||
assert.Equal(New(1, 2, 3, 4), set)
|
||||
}
|
||||
|
||||
func TestSet_AddIfNotExistBy(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSet_AddIfNotExistBy")
|
||||
|
||||
set := New[int]()
|
||||
set.Add(1, 2)
|
||||
|
||||
ok := set.AddIfNotExistBy(3, func(val int) bool {
|
||||
return val%2 != 0
|
||||
})
|
||||
|
||||
notOk := set.AddIfNotExistBy(4, func(val int) bool {
|
||||
return val%2 != 0
|
||||
})
|
||||
|
||||
assert.Equal(true, ok)
|
||||
assert.Equal(false, notOk)
|
||||
|
||||
assert.Equal(true, set.Contain(3))
|
||||
assert.Equal(false, set.Contain(4))
|
||||
}
|
||||
|
||||
func TestSet_Contain(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSet_Contain")
|
||||
|
||||
set := NewSet[int]()
|
||||
set := New[int]()
|
||||
set.Add(1, 2, 3)
|
||||
|
||||
assert.Equal(true, set.Contain(1))
|
||||
@@ -28,20 +85,24 @@ func TestSet_Contain(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSet_ContainAll(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSet_ContainAll")
|
||||
|
||||
set1 := NewSet(1, 2, 3)
|
||||
set2 := NewSet(1, 2)
|
||||
set3 := NewSet(1, 2, 3, 4)
|
||||
set1 := New(1, 2, 3)
|
||||
set2 := New(1, 2)
|
||||
set3 := New(1, 2, 3, 4)
|
||||
|
||||
assert.Equal(true, set1.ContainAll(set2))
|
||||
assert.Equal(false, set1.ContainAll(set3))
|
||||
}
|
||||
|
||||
func TestSet_Clone(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSet_Clone")
|
||||
|
||||
set1 := NewSet(1, 2, 3)
|
||||
set1 := New(1, 2, 3)
|
||||
set2 := set1.Clone()
|
||||
|
||||
assert.Equal(true, set1.Size() == set2.Size())
|
||||
@@ -49,32 +110,36 @@ func TestSet_Clone(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSet_Delete(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSet_Delete")
|
||||
|
||||
set := NewSet[int]()
|
||||
set := New[int]()
|
||||
set.Add(1, 2, 3)
|
||||
set.Delete(3)
|
||||
|
||||
expected := NewSet(1, 2)
|
||||
|
||||
assert.Equal(true, set.Equal(expected))
|
||||
assert.Equal(true, set.Equal(New(1, 2)))
|
||||
}
|
||||
|
||||
func TestSet_Equal(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSet_Equal")
|
||||
|
||||
set1 := NewSet(1, 2, 3)
|
||||
set2 := NewSet(1, 2, 3)
|
||||
set3 := NewSet(1, 2, 3, 4)
|
||||
set1 := New(1, 2, 3)
|
||||
set2 := New(1, 2, 3)
|
||||
set3 := New(1, 2, 3, 4)
|
||||
|
||||
assert.Equal(true, set1.Equal(set2))
|
||||
assert.Equal(false, set1.Equal(set3))
|
||||
}
|
||||
|
||||
func TestSet_Iterate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSet_Iterate")
|
||||
|
||||
set := NewSet(1, 2, 3)
|
||||
set := New(1, 2, 3)
|
||||
arr := []int{}
|
||||
set.Iterate(func(value int) {
|
||||
arr = append(arr, value)
|
||||
@@ -84,66 +149,187 @@ func TestSet_Iterate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSet_IsEmpty(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSet_IsEmpty")
|
||||
|
||||
set := NewSet[int]()
|
||||
set := New[int]()
|
||||
assert.Equal(true, set.IsEmpty())
|
||||
}
|
||||
|
||||
func TestSet_Size(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSet_Size")
|
||||
|
||||
set := NewSet(1, 2, 3)
|
||||
set := New(1, 2, 3)
|
||||
assert.Equal(3, set.Size())
|
||||
}
|
||||
|
||||
func TestSet_Values(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSet_Values")
|
||||
|
||||
set := NewSet(1, 2, 3)
|
||||
set := New(1, 2, 3)
|
||||
values := set.Values()
|
||||
|
||||
assert.Equal(3, len(values))
|
||||
}
|
||||
|
||||
func TestSet_Union(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSet_Union")
|
||||
|
||||
set1 := NewSet(1, 2, 3)
|
||||
set2 := NewSet(2, 3, 4, 5)
|
||||
expected := NewSet(1, 2, 3, 4, 5)
|
||||
set1 := New(1, 2, 3)
|
||||
set2 := New(2, 3, 4, 5)
|
||||
|
||||
unionSet := set1.Union(set2)
|
||||
|
||||
assert.Equal(expected, unionSet)
|
||||
assert.Equal(New(1, 2, 3, 4, 5), unionSet)
|
||||
}
|
||||
|
||||
func TestSet_Intersection(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSet_Intersection")
|
||||
|
||||
set1 := NewSet(1, 2, 3)
|
||||
set2 := NewSet(2, 3, 4, 5)
|
||||
expected := NewSet(2, 3)
|
||||
set1 := New(1, 2, 3)
|
||||
set2 := New(2, 3, 4, 5)
|
||||
intersectionSet := set1.Intersection(set2)
|
||||
|
||||
assert.Equal(expected, intersectionSet)
|
||||
assert.Equal(New(2, 3), intersectionSet)
|
||||
}
|
||||
|
||||
func TestSet_SymmetricDifference(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSet_SymmetricDifference")
|
||||
|
||||
set1 := NewSet(1, 2, 3)
|
||||
set2 := NewSet(2, 3, 4, 5)
|
||||
set1 := New(1, 2, 3)
|
||||
set2 := New(2, 3, 4, 5)
|
||||
|
||||
assert.Equal(NewSet(1, 4, 5), set1.SymmetricDifference(set2))
|
||||
assert.Equal(New(1, 4, 5), set1.SymmetricDifference(set2))
|
||||
}
|
||||
|
||||
func TestSet_Minus(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSet_Minus")
|
||||
|
||||
set1 := NewSet(1, 2, 3)
|
||||
set2 := NewSet(2, 3, 4, 5)
|
||||
set3 := NewSet(2, 3)
|
||||
set1 := New(1, 2, 3)
|
||||
set2 := New(2, 3, 4, 5)
|
||||
set3 := New(2, 3)
|
||||
|
||||
assert.Equal(NewSet(1), set1.Minus(set2))
|
||||
assert.Equal(NewSet(4, 5), set2.Minus(set3))
|
||||
assert.Equal(New(1), set1.Minus(set2))
|
||||
assert.Equal(New(4, 5), set2.Minus(set3))
|
||||
}
|
||||
|
||||
func TestEachWithBreak(t *testing.T) {
|
||||
// s := New(1, 2, 3, 4, 5)
|
||||
|
||||
// var sum int
|
||||
|
||||
// s.EachWithBreak(func(n int) bool {
|
||||
// if n > 3 {
|
||||
// return false
|
||||
// }
|
||||
// sum += n
|
||||
// return true
|
||||
// })
|
||||
|
||||
// assert := internal.NewAssert(t, "TestEachWithBreak")
|
||||
// assert.Equal(6, sum)
|
||||
}
|
||||
|
||||
func TestPop(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestSet_Pop")
|
||||
|
||||
s := New[int]()
|
||||
|
||||
val, ok := s.Pop()
|
||||
assert.Equal(0, val)
|
||||
assert.Equal(false, ok)
|
||||
|
||||
s = New(1, 2, 3, 4, 5)
|
||||
sl := s.ToSlice()
|
||||
|
||||
val, ok = s.Pop()
|
||||
assert.Equal(false, s.Contain(val))
|
||||
assert.Equal(true, ok)
|
||||
assert.Equal(len(sl)-1, s.Size())
|
||||
|
||||
var found bool
|
||||
|
||||
for _, v := range sl {
|
||||
if v == val {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
assert.Equal(true, found)
|
||||
}
|
||||
|
||||
func TestSet_ToSlice(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSet_ToSlice")
|
||||
|
||||
set1 := FromSlice([]int{6, 3, 1, 5, 6, 7, 1})
|
||||
set2 := FromSlice([]float64{-2.65, 4.25, 4.25 - 3.14, 0})
|
||||
set3 := New[string]()
|
||||
|
||||
slice1 := set1.ToSlice()
|
||||
slice2 := set2.ToSlice()
|
||||
slice3 := set3.ToSlice()
|
||||
|
||||
sort.Ints(slice1)
|
||||
sort.Float64s(slice2)
|
||||
|
||||
assert.Equal(5, len(slice1))
|
||||
assert.Equal(4, len(slice2))
|
||||
assert.Equal(0, len(slice3))
|
||||
|
||||
assert.Equal(true, reflect.DeepEqual(slice1, []int{1, 3, 5, 6, 7}))
|
||||
assert.Equal(true, reflect.DeepEqual(slice2, []float64{-2.65, 0, 1.11, 4.25}))
|
||||
assert.Equal("[]string", reflect.TypeOf(slice3).String())
|
||||
}
|
||||
|
||||
func TestSet_ToSortedSlice(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSet_ToSortedSlice")
|
||||
|
||||
set1 := FromSlice([]int{6, 3, 1, 5, 6, 7, 1})
|
||||
set2 := FromSlice([]float64{-2.65, 4.25, 4.25 - 3.14, 0})
|
||||
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
set3 := FromSlice([]Person{{"Tom", 20}, {"Jerry", 18}, {"Spike", 25}})
|
||||
|
||||
slice1 := set1.ToSortedSlice(func(v1, v2 int) bool {
|
||||
return v1 < v2
|
||||
})
|
||||
slice2 := set2.ToSortedSlice(func(v1, v2 float64) bool {
|
||||
return v2 < v1
|
||||
})
|
||||
slice3 := set3.ToSortedSlice(func(v1, v2 Person) bool {
|
||||
return v1.Age < v2.Age
|
||||
})
|
||||
|
||||
assert.Equal(5, len(slice1))
|
||||
assert.Equal(4, len(slice2))
|
||||
assert.Equal(3, len(slice3))
|
||||
|
||||
assert.Equal(true, reflect.DeepEqual(slice1, []int{1, 3, 5, 6, 7}))
|
||||
assert.Equal(true, reflect.DeepEqual(slice2, []float64{4.25, 1.11, 0, -2.65}))
|
||||
assert.Equal(true, reflect.DeepEqual(slice3, []Person{
|
||||
{"Jerry", 18},
|
||||
{"Tom", 20},
|
||||
{"Spike", 25},
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
// Copyright 2021 dudaodong@gmail.com. All rights reserved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
// Package datastructure contains some data structure. Stack structure contains ArrayStack and LinkedStack.
|
||||
package datastructure
|
||||
|
||||
import "errors"
|
||||
@@ -47,7 +51,7 @@ func (s *ArrayStack[T]) Pop() (*T, error) {
|
||||
return &topItem, nil
|
||||
}
|
||||
|
||||
// Peak return the top element of stack then return it
|
||||
// Peak return the top element of stack
|
||||
func (s *ArrayStack[T]) Peak() (*T, error) {
|
||||
if s.IsEmpty() {
|
||||
return nil, errors.New("stack is empty")
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
)
|
||||
|
||||
func TestArrayStack_Push(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestArrayStack_Push")
|
||||
|
||||
stack := NewArrayStack[int]()
|
||||
@@ -14,15 +16,16 @@ func TestArrayStack_Push(t *testing.T) {
|
||||
stack.Push(2)
|
||||
stack.Push(3)
|
||||
|
||||
expected := []int{3, 2, 1}
|
||||
values := stack.Data()
|
||||
length := stack.Size()
|
||||
|
||||
assert.Equal(expected, values)
|
||||
assert.Equal([]int{3, 2, 1}, values)
|
||||
assert.Equal(3, length)
|
||||
}
|
||||
|
||||
func TestArrayStack_Pop(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestArrayStack_Pop")
|
||||
|
||||
stack := NewArrayStack[int]()
|
||||
@@ -37,11 +40,12 @@ func TestArrayStack_Pop(t *testing.T) {
|
||||
assert.IsNil(err)
|
||||
assert.Equal(3, *topItem)
|
||||
|
||||
expected := []int{2, 1}
|
||||
assert.Equal(expected, stack.Data())
|
||||
assert.Equal([]int{2, 1}, stack.Data())
|
||||
}
|
||||
|
||||
func TestArrayStack_Peak(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestArrayStack_Peak")
|
||||
|
||||
stack := NewArrayStack[int]()
|
||||
@@ -56,11 +60,12 @@ func TestArrayStack_Peak(t *testing.T) {
|
||||
assert.IsNil(err)
|
||||
assert.Equal(3, *topItem)
|
||||
|
||||
expected := []int{3, 2, 1}
|
||||
assert.Equal(expected, stack.Data())
|
||||
assert.Equal([]int{3, 2, 1}, stack.Data())
|
||||
}
|
||||
|
||||
func TestArrayStack_Clear(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestArrayStack_Clear")
|
||||
|
||||
stack := NewArrayStack[int]()
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
// Copyright 2021 dudaodong@gmail.com. All rights reserved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
// Package datastructure contains some data structure. Stack structure contains ArrayStack and LinkedStack.
|
||||
package datastructure
|
||||
|
||||
import (
|
||||
@@ -20,7 +24,7 @@ func NewLinkedStack[T any]() *LinkedStack[T] {
|
||||
|
||||
// Data return stack data
|
||||
func (s *LinkedStack[T]) Data() []T {
|
||||
res := []T{}
|
||||
res := make([]T, 0, s.length)
|
||||
current := s.top
|
||||
|
||||
for current != nil {
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
)
|
||||
|
||||
func TestLinkedStack_Push(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestLinkedStack_Push")
|
||||
|
||||
stack := NewLinkedStack[int]()
|
||||
@@ -14,17 +16,16 @@ func TestLinkedStack_Push(t *testing.T) {
|
||||
stack.Push(2)
|
||||
stack.Push(3)
|
||||
|
||||
stack.Print()
|
||||
|
||||
expected := []int{3, 2, 1}
|
||||
values := stack.Data()
|
||||
size := stack.Size()
|
||||
|
||||
assert.Equal(expected, values)
|
||||
assert.Equal([]int{3, 2, 1}, values)
|
||||
assert.Equal(3, size)
|
||||
}
|
||||
|
||||
func TestLinkedStack_Pop(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestLinkedStack_Pop")
|
||||
|
||||
stack := NewLinkedStack[int]()
|
||||
@@ -39,12 +40,13 @@ func TestLinkedStack_Pop(t *testing.T) {
|
||||
assert.IsNil(err)
|
||||
assert.Equal(3, *topItem)
|
||||
|
||||
expected := []int{2, 1}
|
||||
stack.Print()
|
||||
assert.Equal(expected, stack.Data())
|
||||
assert.Equal([]int{2, 1}, stack.Data())
|
||||
}
|
||||
|
||||
func TestLinkedStack_Peak(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestLinkedStack_Peak")
|
||||
|
||||
stack := NewLinkedStack[int]()
|
||||
@@ -59,11 +61,12 @@ func TestLinkedStack_Peak(t *testing.T) {
|
||||
assert.IsNil(err)
|
||||
assert.Equal(3, *topItem)
|
||||
|
||||
expected := []int{3, 2, 1}
|
||||
assert.Equal(expected, stack.Data())
|
||||
assert.Equal([]int{3, 2, 1}, stack.Data())
|
||||
}
|
||||
|
||||
func TestLinkedStack_Empty(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestLinkedStack_Empty")
|
||||
|
||||
stack := NewLinkedStack[int]()
|
||||
|
||||
@@ -1,30 +1,34 @@
|
||||
// Copyright 2021 dudaodong@gmail.com. All rights reserved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
// Package datastructure contains some data structure. BSTree is binary search tree.
|
||||
package datastructure
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/duke-git/lancet/v2/constraints"
|
||||
"github.com/duke-git/lancet/v2/datastructure"
|
||||
"github.com/duke-git/lancet/v2/lancetconstraints"
|
||||
)
|
||||
|
||||
// BSTree is a binary search tree data structure in which each node has at most two children,
|
||||
// which are referred to as the left child and the right child.
|
||||
// In BSTree: leftNode < rootNode < rightNode
|
||||
// type T should implements Compare function in lancetconstraints.Comparator interface.
|
||||
// type T should implements Compare function in constraints.Comparator interface.
|
||||
type BSTree[T any] struct {
|
||||
root *datastructure.TreeNode[T]
|
||||
comparator lancetconstraints.Comparator
|
||||
comparator constraints.Comparator
|
||||
}
|
||||
|
||||
// NewBSTree create a BSTree pointer
|
||||
// param `comparator` is used to compare values in the tree
|
||||
func NewBSTree[T any](rootData T, comparator lancetconstraints.Comparator) *BSTree[T] {
|
||||
func NewBSTree[T any](rootData T, comparator constraints.Comparator) *BSTree[T] {
|
||||
root := datastructure.NewTreeNode(rootData)
|
||||
return &BSTree[T]{root, comparator}
|
||||
}
|
||||
|
||||
// InsertNode insert data into BSTree
|
||||
func (t *BSTree[T]) InsertNode(data T) {
|
||||
func (t *BSTree[T]) Insert(data T) {
|
||||
root := t.root
|
||||
newNode := datastructure.NewTreeNode(data)
|
||||
if root == nil {
|
||||
@@ -35,7 +39,7 @@ func (t *BSTree[T]) InsertNode(data T) {
|
||||
}
|
||||
|
||||
// DeletetNode delete data into BSTree
|
||||
func (t *BSTree[T]) DeletetNode(data T) {
|
||||
func (t *BSTree[T]) Delete(data T) {
|
||||
deleteTreeNode(t.root, data, t.comparator)
|
||||
}
|
||||
|
||||
@@ -83,21 +87,21 @@ func (t *BSTree[T]) HasSubTree(subTree *BSTree[T]) bool {
|
||||
}
|
||||
|
||||
func hasSubTree[T any](superTreeRoot, subTreeRoot *datastructure.TreeNode[T],
|
||||
comparator lancetconstraints.Comparator) bool {
|
||||
res := false
|
||||
comparator constraints.Comparator) bool {
|
||||
result := false
|
||||
|
||||
if superTreeRoot != nil && subTreeRoot != nil {
|
||||
if comparator.Compare(superTreeRoot.Value, subTreeRoot.Value) == 0 {
|
||||
res = isSubTree(superTreeRoot, subTreeRoot, comparator)
|
||||
result = isSubTree(superTreeRoot, subTreeRoot, comparator)
|
||||
}
|
||||
if !res {
|
||||
res = hasSubTree(superTreeRoot.Left, subTreeRoot, comparator)
|
||||
if !result {
|
||||
result = hasSubTree(superTreeRoot.Left, subTreeRoot, comparator)
|
||||
}
|
||||
if !res {
|
||||
res = hasSubTree(superTreeRoot.Right, subTreeRoot, comparator)
|
||||
if !result {
|
||||
result = hasSubTree(superTreeRoot.Right, subTreeRoot, comparator)
|
||||
}
|
||||
}
|
||||
return res
|
||||
return result
|
||||
}
|
||||
|
||||
// Print the bstree structure
|
||||
|
||||
@@ -20,137 +20,120 @@ func (c *intComparator) Compare(v1, v2 any) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func TestBSTree_InsertNode(t *testing.T) {
|
||||
bstree := NewBSTree(6, &intComparator{})
|
||||
|
||||
bstree.InsertNode(7)
|
||||
bstree.InsertNode(5)
|
||||
bstree.InsertNode(2)
|
||||
bstree.InsertNode(4)
|
||||
|
||||
bstree.Print()
|
||||
}
|
||||
|
||||
func TestBSTree_PreOrderTraverse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestBSTree_PreOrderTraverse")
|
||||
|
||||
bstree := NewBSTree(6, &intComparator{})
|
||||
|
||||
bstree.InsertNode(7)
|
||||
bstree.InsertNode(5)
|
||||
bstree.InsertNode(2)
|
||||
bstree.InsertNode(4)
|
||||
bstree.Insert(7)
|
||||
bstree.Insert(5)
|
||||
bstree.Insert(2)
|
||||
bstree.Insert(4)
|
||||
|
||||
acturl := bstree.PreOrderTraverse()
|
||||
t.Log(acturl)
|
||||
assert.Equal([]int{6, 5, 2, 4, 7}, acturl)
|
||||
}
|
||||
|
||||
func TestBSTree_PostOrderTraverse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestBSTree_PostOrderTraverse")
|
||||
|
||||
bstree := NewBSTree(6, &intComparator{})
|
||||
|
||||
bstree.InsertNode(7)
|
||||
bstree.InsertNode(5)
|
||||
bstree.InsertNode(2)
|
||||
bstree.InsertNode(4)
|
||||
bstree.Insert(7)
|
||||
bstree.Insert(5)
|
||||
bstree.Insert(2)
|
||||
bstree.Insert(4)
|
||||
|
||||
acturl := bstree.PostOrderTraverse()
|
||||
t.Log(acturl)
|
||||
assert.Equal([]int{5, 2, 4, 7, 6}, acturl)
|
||||
}
|
||||
|
||||
func TestBSTree_InOrderTraverse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestBSTree_InOrderTraverse")
|
||||
|
||||
bstree := NewBSTree(6, &intComparator{})
|
||||
|
||||
bstree.InsertNode(7)
|
||||
bstree.InsertNode(5)
|
||||
bstree.InsertNode(2)
|
||||
bstree.InsertNode(4)
|
||||
bstree.Insert(7)
|
||||
bstree.Insert(5)
|
||||
bstree.Insert(2)
|
||||
bstree.Insert(4)
|
||||
|
||||
acturl := bstree.InOrderTraverse()
|
||||
t.Log(acturl)
|
||||
assert.Equal([]int{2, 4, 5, 6, 7}, acturl)
|
||||
}
|
||||
|
||||
func TestBSTree_LevelOrderTraverse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestBSTree_LevelOrderTraverse")
|
||||
|
||||
bstree := NewBSTree(6, &intComparator{})
|
||||
|
||||
bstree.InsertNode(7)
|
||||
bstree.InsertNode(5)
|
||||
bstree.InsertNode(2)
|
||||
bstree.InsertNode(4)
|
||||
|
||||
bstree.Print()
|
||||
bstree.Insert(7)
|
||||
bstree.Insert(5)
|
||||
bstree.Insert(2)
|
||||
bstree.Insert(4)
|
||||
|
||||
acturl := bstree.LevelOrderTraverse()
|
||||
t.Log(acturl)
|
||||
assert.Equal([]int{6, 5, 7, 2, 4}, acturl)
|
||||
}
|
||||
|
||||
func TestBSTree_DeletetNode(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestBSTree_DeletetNode")
|
||||
func TestBSTree_Delete(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestBSTree_Delete")
|
||||
|
||||
bstree := NewBSTree(6, &intComparator{})
|
||||
|
||||
bstree.InsertNode(7)
|
||||
bstree.InsertNode(5)
|
||||
bstree.InsertNode(2)
|
||||
bstree.InsertNode(4)
|
||||
bstree.Insert(7)
|
||||
bstree.Insert(5)
|
||||
bstree.Insert(2)
|
||||
bstree.Insert(4)
|
||||
|
||||
bstree.Print()
|
||||
bstree.Delete(4)
|
||||
|
||||
bstree.DeletetNode(4)
|
||||
bstree.Print()
|
||||
acturl1 := bstree.InOrderTraverse()
|
||||
t.Log(acturl1)
|
||||
assert.Equal([]int{2, 5, 6, 7}, acturl1)
|
||||
|
||||
//todo
|
||||
// bstree.DeletetNode(6, comparator)
|
||||
// bstree.Print()
|
||||
// acturl2 := bstree.InOrderTraverse()
|
||||
// t.Log(acturl2)
|
||||
// assert.Equal([]int{2, 5, 7}, acturl2)
|
||||
}
|
||||
|
||||
func TestBSTree_Depth(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestBSTree_Depth")
|
||||
|
||||
bstree := NewBSTree(6, &intComparator{})
|
||||
|
||||
bstree.InsertNode(7)
|
||||
bstree.InsertNode(5)
|
||||
bstree.InsertNode(2)
|
||||
bstree.InsertNode(4)
|
||||
|
||||
bstree.Print()
|
||||
bstree.Insert(7)
|
||||
bstree.Insert(5)
|
||||
bstree.Insert(2)
|
||||
bstree.Insert(4)
|
||||
|
||||
assert.Equal(bstree.Depth(), 4)
|
||||
}
|
||||
|
||||
func TestBSTree_IsSubTree(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestBSTree_IsSubTree")
|
||||
|
||||
superTree := NewBSTree(8, &intComparator{})
|
||||
superTree.InsertNode(4)
|
||||
superTree.InsertNode(5)
|
||||
superTree.InsertNode(6)
|
||||
superTree.InsertNode(9)
|
||||
superTree.InsertNode(4)
|
||||
superTree.Insert(4)
|
||||
superTree.Insert(5)
|
||||
superTree.Insert(6)
|
||||
superTree.Insert(9)
|
||||
superTree.Insert(4)
|
||||
|
||||
superTree.Print()
|
||||
|
||||
subTree := NewBSTree(5, &intComparator{})
|
||||
subTree.InsertNode(4)
|
||||
subTree.InsertNode(6)
|
||||
|
||||
subTree.Print()
|
||||
subTree.Insert(4)
|
||||
subTree.Insert(6)
|
||||
|
||||
assert.Equal(true, superTree.HasSubTree(subTree))
|
||||
assert.Equal(false, subTree.HasSubTree(superTree))
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/duke-git/lancet/v2/constraints"
|
||||
"github.com/duke-git/lancet/v2/datastructure"
|
||||
"github.com/duke-git/lancet/v2/lancetconstraints"
|
||||
)
|
||||
|
||||
func preOrderTraverse[T any](node *datastructure.TreeNode[T]) []T {
|
||||
@@ -38,35 +38,35 @@ func inOrderTraverse[T any](node *datastructure.TreeNode[T]) []T {
|
||||
return data
|
||||
}
|
||||
|
||||
func preOrderPrint[T any](node *datastructure.TreeNode[T]) {
|
||||
if node == nil {
|
||||
return
|
||||
}
|
||||
// func preOrderPrint[T any](node *datastructure.TreeNode[T]) {
|
||||
// if node == nil {
|
||||
// return
|
||||
// }
|
||||
|
||||
fmt.Printf("%v, ", node.Value)
|
||||
preOrderPrint(node.Left)
|
||||
preOrderPrint(node.Right)
|
||||
}
|
||||
// fmt.Printf("%v, ", node.Value)
|
||||
// preOrderPrint(node.Left)
|
||||
// preOrderPrint(node.Right)
|
||||
// }
|
||||
|
||||
func postOrderPrint[T any](node *datastructure.TreeNode[T]) {
|
||||
if node == nil {
|
||||
return
|
||||
}
|
||||
// func postOrderPrint[T any](node *datastructure.TreeNode[T]) {
|
||||
// if node == nil {
|
||||
// return
|
||||
// }
|
||||
|
||||
preOrderPrint(node.Left)
|
||||
preOrderPrint(node.Right)
|
||||
fmt.Printf("%v, ", node.Value)
|
||||
}
|
||||
// postOrderPrint(node.Left)
|
||||
// postOrderPrint(node.Right)
|
||||
// fmt.Printf("%v, ", node.Value)
|
||||
// }
|
||||
|
||||
func inOrderPrint[T any](node *datastructure.TreeNode[T]) {
|
||||
if node == nil {
|
||||
return
|
||||
}
|
||||
// func inOrderPrint[T any](node *datastructure.TreeNode[T]) {
|
||||
// if node == nil {
|
||||
// return
|
||||
// }
|
||||
|
||||
inOrderPrint(node.Left)
|
||||
fmt.Printf("%v, ", node.Value)
|
||||
inOrderPrint(node.Right)
|
||||
}
|
||||
// inOrderPrint(node.Left)
|
||||
// fmt.Printf("%v, ", node.Value)
|
||||
// inOrderPrint(node.Right)
|
||||
// }
|
||||
|
||||
func levelOrderTraverse[T any](root *datastructure.TreeNode[T], traversal *[]T) {
|
||||
var q []*datastructure.TreeNode[T] // queue
|
||||
@@ -86,7 +86,7 @@ func levelOrderTraverse[T any](root *datastructure.TreeNode[T], traversal *[]T)
|
||||
}
|
||||
}
|
||||
|
||||
func insertTreeNode[T any](rootNode, newNode *datastructure.TreeNode[T], comparator lancetconstraints.Comparator) {
|
||||
func insertTreeNode[T any](rootNode, newNode *datastructure.TreeNode[T], comparator constraints.Comparator) {
|
||||
if comparator.Compare(newNode.Value, rootNode.Value) == -1 {
|
||||
if rootNode.Left == nil {
|
||||
rootNode.Left = newNode
|
||||
@@ -103,7 +103,7 @@ func insertTreeNode[T any](rootNode, newNode *datastructure.TreeNode[T], compara
|
||||
}
|
||||
|
||||
// todo, delete root node failed
|
||||
func deleteTreeNode[T any](node *datastructure.TreeNode[T], data T, comparator lancetconstraints.Comparator) *datastructure.TreeNode[T] {
|
||||
func deleteTreeNode[T any](node *datastructure.TreeNode[T], data T, comparator constraints.Comparator) *datastructure.TreeNode[T] {
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -147,7 +147,7 @@ func printTreeNodes[T any](nodes []*datastructure.TreeNode[T], level, maxLevel i
|
||||
|
||||
printSpaces(firstSpaces)
|
||||
|
||||
newNodes := []*datastructure.TreeNode[T]{}
|
||||
newNodes := make([]*datastructure.TreeNode[T], 0, len(nodes)*2)
|
||||
for _, node := range nodes {
|
||||
if node != nil {
|
||||
fmt.Printf("%v", node.Value)
|
||||
@@ -216,7 +216,7 @@ func calculateDepth[T any](node *datastructure.TreeNode[T], depth int) int {
|
||||
return max(calculateDepth(node.Left, depth+1), calculateDepth(node.Right, depth+1))
|
||||
}
|
||||
|
||||
func isSubTree[T any](superTreeRoot, subTreeRoot *datastructure.TreeNode[T], comparator lancetconstraints.Comparator) bool {
|
||||
func isSubTree[T any](superTreeRoot, subTreeRoot *datastructure.TreeNode[T], comparator constraints.Comparator) bool {
|
||||
if subTreeRoot == nil {
|
||||
return true
|
||||
}
|
||||
@@ -226,8 +226,8 @@ func isSubTree[T any](superTreeRoot, subTreeRoot *datastructure.TreeNode[T], com
|
||||
if comparator.Compare(superTreeRoot.Value, subTreeRoot.Value) != 0 {
|
||||
return false
|
||||
}
|
||||
res := isSubTree(superTreeRoot.Left, subTreeRoot.Left, comparator) && isSubTree(superTreeRoot.Right, subTreeRoot.Right, comparator)
|
||||
return res
|
||||
result := isSubTree(superTreeRoot.Left, subTreeRoot.Left, comparator) && isSubTree(superTreeRoot.Right, subTreeRoot.Right, comparator)
|
||||
return result
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
|
||||
@@ -9,17 +9,20 @@ type theTime struct {
|
||||
unix int64
|
||||
}
|
||||
|
||||
// NewUnixNow return unix timestamp of current time
|
||||
// NewUnixNow return unix timestamp of current time.
|
||||
// Play: https://go.dev/play/p/U4PPx-9D0oz
|
||||
func NewUnixNow() *theTime {
|
||||
return &theTime{unix: time.Now().Unix()}
|
||||
}
|
||||
|
||||
// NewUnix return unix timestamp of specified time
|
||||
// NewUnix return unix timestamp of specified time.
|
||||
// Play: https://go.dev/play/p/psoSuh_kLRt
|
||||
func NewUnix(unix int64) *theTime {
|
||||
return &theTime{unix: unix}
|
||||
}
|
||||
|
||||
// NewFormat return unix timestamp of specified time string, t should be "yyyy-mm-dd hh:mm:ss"
|
||||
// NewFormat return unix timestamp of specified time string, t should be "yyyy-mm-dd hh:mm:ss".
|
||||
// Play: https://go.dev/play/p/VkW08ZOaXPZ
|
||||
func NewFormat(t string) (*theTime, error) {
|
||||
timeLayout := "2006-01-02 15:04:05"
|
||||
loc := time.FixedZone("CST", 8*3600)
|
||||
@@ -30,7 +33,8 @@ func NewFormat(t string) (*theTime, error) {
|
||||
return &theTime{unix: tt.Unix()}, nil
|
||||
}
|
||||
|
||||
// NewISO8601 return unix timestamp of specified iso8601 time string
|
||||
// NewISO8601 return unix timestamp of specified iso8601 time string.
|
||||
// Play: https://go.dev/play/p/mkhOHQkdeA2
|
||||
func NewISO8601(iso8601 string) (*theTime, error) {
|
||||
t, err := time.ParseInLocation(time.RFC3339, iso8601, time.UTC)
|
||||
if err != nil {
|
||||
@@ -39,22 +43,26 @@ func NewISO8601(iso8601 string) (*theTime, error) {
|
||||
return &theTime{unix: t.Unix()}, nil
|
||||
}
|
||||
|
||||
// ToUnix return unix timestamp
|
||||
// ToUnix return unix timestamp.
|
||||
// Play: https://go.dev/play/p/_LUiwAdocjy
|
||||
func (t *theTime) ToUnix() int64 {
|
||||
return t.unix
|
||||
}
|
||||
|
||||
// ToFormat return the time string 'yyyy-mm-dd hh:mm:ss' of unix time
|
||||
// ToFormat return the time string 'yyyy-mm-dd hh:mm:ss' of unix time.
|
||||
// Play: https://go.dev/play/p/VkW08ZOaXPZ
|
||||
func (t *theTime) ToFormat() string {
|
||||
return time.Unix(t.unix, 0).Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
// ToFormatForTpl return the time string which format is specified tpl
|
||||
// ToFormatForTpl return the time string which format is specified tpl.
|
||||
// Play: https://go.dev/play/p/nyXxXcQJ8L5
|
||||
func (t *theTime) ToFormatForTpl(tpl string) string {
|
||||
return time.Unix(t.unix, 0).Format(tpl)
|
||||
}
|
||||
|
||||
// ToFormatForTpl return iso8601 time string
|
||||
// ToFormatForTpl return iso8601 time string.
|
||||
// Play: https://go.dev/play/p/mkhOHQkdeA2
|
||||
func (t *theTime) ToIso8601() string {
|
||||
return time.Unix(t.unix, 0).Format(time.RFC3339)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
)
|
||||
|
||||
func TestToUnix(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestToUnix")
|
||||
|
||||
tm1 := NewUnixNow()
|
||||
@@ -17,37 +19,37 @@ func TestToUnix(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestToFormat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestToFormat")
|
||||
|
||||
_, err := NewFormat("2022/03/18 17:04:05")
|
||||
assert.IsNotNil(err)
|
||||
|
||||
tm, err := NewFormat("2022-03-18 17:04:05")
|
||||
t.Log("TestToFormat", tm.ToFormat())
|
||||
assert.IsNil(err)
|
||||
|
||||
t.Log("ToFormat -> ", tm.ToFormat())
|
||||
}
|
||||
|
||||
func TestToFormatForTpl(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestToFormatForTpl")
|
||||
|
||||
_, err := NewFormat("2022/03/18 17:04:05")
|
||||
assert.IsNotNil(err)
|
||||
|
||||
tm, err := NewFormat("2022-03-18 17:04:05")
|
||||
t.Log("TestToFormatForTpl", tm.ToFormatForTpl("2006/01/02 15:04:05"))
|
||||
assert.IsNil(err)
|
||||
|
||||
t.Log("ToFormatForTpl -> ", tm.ToFormatForTpl("2006/01/02 15:04:05"))
|
||||
}
|
||||
|
||||
func TestToIso8601(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestToIso8601")
|
||||
|
||||
_, err := NewISO8601("2022-03-18 17:04:05")
|
||||
assert.IsNotNil(err)
|
||||
|
||||
tm, err := NewISO8601("2006-01-02T15:04:05.999Z")
|
||||
t.Log("TestToIso8601", tm.ToIso8601())
|
||||
assert.IsNil(err)
|
||||
|
||||
t.Log("ToIso8601 -> ", tm.ToIso8601())
|
||||
}
|
||||
|
||||
@@ -3,29 +3,35 @@
|
||||
|
||||
// Package datetime implements some functions to format date and time.
|
||||
// Note:
|
||||
// 1. `format` param in FormatTimeToStr function should be as flow:
|
||||
//"yyyy-mm-dd hh:mm:ss"
|
||||
//"yyyy-mm-dd hh:mm"
|
||||
//"yyyy-mm-dd hh"
|
||||
//"yyyy-mm-dd"
|
||||
//"yyyy-mm"
|
||||
//"mm-dd"
|
||||
//"dd-mm-yy hh:mm:ss"
|
||||
//"yyyy/mm/dd hh:mm:ss"
|
||||
//"yyyy/mm/dd hh:mm"
|
||||
//"yyyy/mm/dd hh"
|
||||
//"yyyy/mm/dd"
|
||||
//"yyyy/mm"
|
||||
//"mm/dd"
|
||||
//"dd/mm/yy hh:mm:ss"
|
||||
//"yyyy"
|
||||
//"mm"
|
||||
//"hh:mm:ss"
|
||||
//"mm:ss"
|
||||
// 1. `format` param in FormatTimeToStr function should be as flow (case no sensitive):
|
||||
// "yyyy-mm-dd hh:mm:ss"
|
||||
// "yyyy-mm-dd hh:mm"
|
||||
// "yyyy-mm-dd hh"
|
||||
// "yyyy-mm-dd"
|
||||
// "yyyy-mm"
|
||||
// "mm-dd"
|
||||
// "dd-mm-yy hh:mm:ss"
|
||||
// "yyyy/mm/dd hh:mm:ss"
|
||||
// "yyyy/mm/dd hh:mm"
|
||||
// "yyyy/mm/dd hh"
|
||||
// "yyyy/mm/dd"
|
||||
// "yyyy/mm"
|
||||
// "mm/dd"
|
||||
// "dd/mm/yy hh:mm:ss"
|
||||
// "yyyymmdd"
|
||||
// "mmddyy"
|
||||
// "yyyy"
|
||||
// "yy"
|
||||
// "mm"
|
||||
// "hh:mm:ss"
|
||||
// "hh:mm"
|
||||
// "mm:ss"
|
||||
package datetime
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -35,7 +41,7 @@ func init() {
|
||||
timeFormat = map[string]string{
|
||||
"yyyy-mm-dd hh:mm:ss": "2006-01-02 15:04:05",
|
||||
"yyyy-mm-dd hh:mm": "2006-01-02 15:04",
|
||||
"yyyy-mm-dd hh": "2006-01-02 15:04",
|
||||
"yyyy-mm-dd hh": "2006-01-02 15",
|
||||
"yyyy-mm-dd": "2006-01-02",
|
||||
"yyyy-mm": "2006-01",
|
||||
"mm-dd": "01-02",
|
||||
@@ -47,136 +53,499 @@ func init() {
|
||||
"yyyy/mm": "2006/01",
|
||||
"mm/dd": "01/02",
|
||||
"dd/mm/yy hh:mm:ss": "02/01/06 15:04:05",
|
||||
"yyyymmdd": "20060102",
|
||||
"mmddyy": "010206",
|
||||
"yyyy": "2006",
|
||||
"yy": "06",
|
||||
"mm": "01",
|
||||
"hh:mm:ss": "15:04:05",
|
||||
"hh:mm": "15:04",
|
||||
"mm:ss": "04:05",
|
||||
}
|
||||
}
|
||||
|
||||
// AddMinute add or sub minute to the time
|
||||
func AddMinute(t time.Time, minute int64) time.Time {
|
||||
return t.Add(time.Minute * time.Duration(minute))
|
||||
// AddMinute add or sub minutes to the time.
|
||||
// Play: https://go.dev/play/p/nT1heB1KUUK
|
||||
func AddMinute(t time.Time, minutes int64) time.Time {
|
||||
return t.Add(time.Minute * time.Duration(minutes))
|
||||
}
|
||||
|
||||
// AddHour add or sub hour to the time
|
||||
func AddHour(t time.Time, hour int64) time.Time {
|
||||
return t.Add(time.Hour * time.Duration(hour))
|
||||
// AddHour add or sub hours to the time.
|
||||
// Play: https://go.dev/play/p/rcMjd7OCsi5
|
||||
func AddHour(t time.Time, hours int64) time.Time {
|
||||
return t.Add(time.Hour * time.Duration(hours))
|
||||
}
|
||||
|
||||
// AddDay add or sub day to the time
|
||||
func AddDay(t time.Time, day int64) time.Time {
|
||||
return t.Add(24 * time.Hour * time.Duration(day))
|
||||
// AddDay add or sub days to the time.
|
||||
// Play: https://go.dev/play/p/dIGbs_uTdFa
|
||||
func AddDay(t time.Time, days int64) time.Time {
|
||||
return t.Add(24 * time.Hour * time.Duration(days))
|
||||
}
|
||||
|
||||
// GetNowDate return format yyyy-mm-dd of current date
|
||||
// AddWeek add or sub weeks to the time.
|
||||
// play: https://go.dev/play/p/M9TqdMiaA2p
|
||||
func AddWeek(t time.Time, weeks int64) time.Time {
|
||||
return t.Add(7 * 24 * time.Hour * time.Duration(weeks))
|
||||
}
|
||||
|
||||
// AddMonth add or sub months to the time.
|
||||
// Play: https://go.dev/play/p/DLoiOnpLvsN
|
||||
func AddMonth(t time.Time, months int64) time.Time {
|
||||
return t.AddDate(0, int(months), 0)
|
||||
}
|
||||
|
||||
// AddYear add or sub year to the time.
|
||||
// Play: https://go.dev/play/p/MqW2ujnBx10
|
||||
func AddYear(t time.Time, year int64) time.Time {
|
||||
return t.AddDate(int(year), 0, 0)
|
||||
}
|
||||
|
||||
// AddDaySafe add or sub days to the time and ensure that the returned date does not exceed the valid date of the target year and month.
|
||||
// Play: https://go.dev/play/p/JTohZFpoDJ3
|
||||
func AddDaySafe(t time.Time, days int) time.Time {
|
||||
t = t.AddDate(0, 0, days)
|
||||
year, month, day := t.Date()
|
||||
|
||||
lastDayOfMonth := time.Date(year, month+1, 0, 0, 0, 0, 0, t.Location()).Day()
|
||||
|
||||
if day > lastDayOfMonth {
|
||||
t = time.Date(year, month, lastDayOfMonth, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location())
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// AddMonthSafe add or sub months to the time and ensure that the returned date does not exceed the valid date of the target year and month.
|
||||
// Play: https://go.dev/play/p/KLw0lo6mbVW
|
||||
func AddMonthSafe(t time.Time, months int) time.Time {
|
||||
year := t.Year()
|
||||
month := int(t.Month()) + months
|
||||
|
||||
for month > 12 {
|
||||
month -= 12
|
||||
year++
|
||||
}
|
||||
for month < 1 {
|
||||
month += 12
|
||||
year--
|
||||
}
|
||||
|
||||
daysInMonth := time.Date(year, time.Month(month+1), 0, 0, 0, 0, 0, time.UTC).Day()
|
||||
|
||||
day := t.Day()
|
||||
if day > daysInMonth {
|
||||
day = daysInMonth
|
||||
}
|
||||
|
||||
return time.Date(year, time.Month(month), day, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location())
|
||||
}
|
||||
|
||||
// AddYearSafe add or sub years to the time and ensure that the returned date does not exceed the valid date of the target year and month.
|
||||
// Play: https://go.dev/play/p/KVGXWZZ54ZH
|
||||
func AddYearSafe(t time.Time, years int) time.Time {
|
||||
year, month, day := t.Date()
|
||||
year += years
|
||||
|
||||
if month == time.February && day == 29 {
|
||||
if !IsLeapYear(year) {
|
||||
day = 28
|
||||
}
|
||||
}
|
||||
|
||||
return time.Date(year, month, day, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location())
|
||||
}
|
||||
|
||||
// GetNowDate return format yyyy-mm-dd of current date.
|
||||
// Play: https://go.dev/play/p/PvfkPpcpBBf
|
||||
func GetNowDate() string {
|
||||
return time.Now().Format("2006-01-02")
|
||||
}
|
||||
|
||||
// GetNowTime return format hh-mm-ss of current time
|
||||
// GetNowTime return format hh-mm-ss of current time.
|
||||
// Play: https://go.dev/play/p/l7BNxCkTmJS
|
||||
func GetNowTime() string {
|
||||
return time.Now().Format("15:04:05")
|
||||
}
|
||||
|
||||
// GetNowDateTime return format yyyy-mm-dd hh-mm-ss of current datetime
|
||||
// GetNowDateTime return format yyyy-mm-dd hh-mm-ss of current datetime.
|
||||
// Play: https://go.dev/play/p/pI4AqngD0al
|
||||
func GetNowDateTime() string {
|
||||
return time.Now().Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
// GetZeroHourTimestamp return timestamp of zero hour (timestamp of 00:00)
|
||||
// GetTodayStartTime return the start time of today, format: yyyy-mm-dd 00:00:00.
|
||||
// Play: https://go.dev/play/p/84siyYF7t99
|
||||
func GetTodayStartTime() string {
|
||||
return time.Now().Format("2006-01-02") + " 00:00:00"
|
||||
}
|
||||
|
||||
// GetTodayEndTime return the end time of today, format: yyyy-mm-dd 23:59:59.
|
||||
// Play: https://go.dev/play/p/jjrLnfoqgn3
|
||||
func GetTodayEndTime() string {
|
||||
return time.Now().Format("2006-01-02") + " 23:59:59"
|
||||
}
|
||||
|
||||
// GetZeroHourTimestamp return timestamp of zero hour (timestamp of 00:00).
|
||||
// Play: https://go.dev/play/p/QmL2oIaGE3q
|
||||
func GetZeroHourTimestamp() int64 {
|
||||
ts := time.Now().Format("2006-01-02")
|
||||
t, _ := time.Parse("2006-01-02", ts)
|
||||
return t.UTC().Unix() - 8*3600
|
||||
}
|
||||
|
||||
// GetNightTimestamp return timestamp of zero hour (timestamp of 23:59)
|
||||
// GetNightTimestamp return timestamp of zero hour (timestamp of 23:59).
|
||||
// Play: https://go.dev/play/p/UolysR3MYP1
|
||||
func GetNightTimestamp() int64 {
|
||||
return GetZeroHourTimestamp() + 86400 - 1
|
||||
}
|
||||
|
||||
// FormatTimeToStr convert time to string
|
||||
func FormatTimeToStr(t time.Time, format string) string {
|
||||
return t.Format(timeFormat[format])
|
||||
}
|
||||
|
||||
// FormatStrToTime convert string to time
|
||||
func FormatStrToTime(str, format string) (time.Time, error) {
|
||||
v, ok := timeFormat[format]
|
||||
// FormatTimeToStr convert time to string.
|
||||
// Play: https://go.dev/play/p/_Ia7M8H_OvE
|
||||
func FormatTimeToStr(t time.Time, format string, timezone ...string) string {
|
||||
tf, ok := timeFormat[strings.ToLower(format)]
|
||||
if !ok {
|
||||
return time.Time{}, fmt.Errorf("format %s not found", format)
|
||||
return ""
|
||||
}
|
||||
|
||||
return time.Parse(v, str)
|
||||
if timezone != nil && timezone[0] != "" {
|
||||
loc, err := time.LoadLocation(timezone[0])
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return t.In(loc).Format(tf)
|
||||
}
|
||||
return t.Format(tf)
|
||||
}
|
||||
|
||||
// BeginOfMinute return beginning minute time of day
|
||||
// FormatStrToTime convert string to time.
|
||||
// Play: https://go.dev/play/p/1h9FwdU8ql4
|
||||
func FormatStrToTime(str, format string, timezone ...string) (time.Time, error) {
|
||||
tf, ok := timeFormat[strings.ToLower(format)]
|
||||
if !ok {
|
||||
return time.Time{}, fmt.Errorf("format %s not support", format)
|
||||
}
|
||||
|
||||
if timezone != nil && timezone[0] != "" {
|
||||
loc, err := time.LoadLocation(timezone[0])
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
return time.ParseInLocation(tf, str, loc)
|
||||
}
|
||||
|
||||
return time.Parse(tf, str)
|
||||
}
|
||||
|
||||
// BeginOfMinute return beginning minute time of day.
|
||||
// Play: https://go.dev/play/p/ieOLVJ9CiFT
|
||||
func BeginOfMinute(t time.Time) time.Time {
|
||||
y, m, d := t.Date()
|
||||
return time.Date(y, m, d, t.Hour(), t.Minute(), 0, 0, t.Location())
|
||||
}
|
||||
|
||||
// EndOfMinute return end minute time of day
|
||||
// EndOfMinute return end minute time of day.
|
||||
// Play: https://go.dev/play/p/yrL5wGzPj4z
|
||||
func EndOfMinute(t time.Time) time.Time {
|
||||
y, m, d := t.Date()
|
||||
return time.Date(y, m, d, t.Hour(), t.Minute(), 59, int(time.Second-time.Nanosecond), t.Location())
|
||||
}
|
||||
|
||||
// BeginOfHour return beginning hour time of day
|
||||
// BeginOfHour return beginning hour time of day.
|
||||
// Play: https://go.dev/play/p/GhdGFnDWpYs
|
||||
func BeginOfHour(t time.Time) time.Time {
|
||||
y, m, d := t.Date()
|
||||
return time.Date(y, m, d, t.Hour(), 0, 0, 0, t.Location())
|
||||
}
|
||||
|
||||
// EndOfHour return end hour time of day
|
||||
// EndOfHour return end hour time of day.
|
||||
// Play: https://go.dev/play/p/6ce3j_6cVqN
|
||||
func EndOfHour(t time.Time) time.Time {
|
||||
y, m, d := t.Date()
|
||||
return time.Date(y, m, d, t.Hour(), 59, 59, int(time.Second-time.Nanosecond), t.Location())
|
||||
}
|
||||
|
||||
// BeginOfDay return beginning hour time of day
|
||||
// BeginOfDay return beginning hour time of day.
|
||||
// Play: https://go.dev/play/p/94m_UT6cWs9
|
||||
func BeginOfDay(t time.Time) time.Time {
|
||||
y, m, d := t.Date()
|
||||
return time.Date(y, m, d, 0, 0, 0, 0, t.Location())
|
||||
}
|
||||
|
||||
// EndOfDay return end time of day
|
||||
// EndOfDay return end time of day.
|
||||
// Play: https://go.dev/play/p/eMBOvmq5Ih1
|
||||
func EndOfDay(t time.Time) time.Time {
|
||||
y, m, d := t.Date()
|
||||
return time.Date(y, m, d, 23, 59, 59, int(time.Second-time.Nanosecond), t.Location())
|
||||
}
|
||||
|
||||
// BeginOfWeek return beginning week, week begin from Sunday
|
||||
func BeginOfWeek(t time.Time) time.Time {
|
||||
y, m, d := t.AddDate(0, 0, 0-int(BeginOfDay(t).Weekday())).Date()
|
||||
return time.Date(y, m, d, 0, 0, 0, 0, t.Location())
|
||||
// BeginOfWeek return beginning week, default week begin from Sunday.
|
||||
// Play: https://go.dev/play/p/DCHdcL6gnfV
|
||||
func BeginOfWeek(t time.Time, beginFrom time.Weekday) time.Time {
|
||||
y, m, d := t.AddDate(0, 0, int(beginFrom-t.Weekday())).Date()
|
||||
beginOfWeek := time.Date(y, m, d, 0, 0, 0, 0, t.Location())
|
||||
if beginOfWeek.After(t) {
|
||||
return beginOfWeek.AddDate(0, 0, -7)
|
||||
}
|
||||
return beginOfWeek
|
||||
}
|
||||
|
||||
// EndOfWeek return end week time, week end with Saturday
|
||||
func EndOfWeek(t time.Time) time.Time {
|
||||
y, m, d := BeginOfWeek(t).AddDate(0, 0, 7).Add(-time.Nanosecond).Date()
|
||||
return time.Date(y, m, d, 23, 59, 59, int(time.Second-time.Nanosecond), t.Location())
|
||||
// EndOfWeek return end week time, default week end with Saturday.
|
||||
// Play: https://go.dev/play/p/mGSA162YgX9
|
||||
func EndOfWeek(t time.Time, endWith time.Weekday) time.Time {
|
||||
y, m, d := t.AddDate(0, 0, int(endWith-t.Weekday())).Date()
|
||||
var endWithWeek = time.Date(y, m, d, 23, 59, 59, int(time.Second-time.Nanosecond), t.Location())
|
||||
if endWithWeek.Before(t) {
|
||||
endWithWeek = endWithWeek.AddDate(0, 0, 7)
|
||||
}
|
||||
return endWithWeek
|
||||
}
|
||||
|
||||
// BeginOfMonth return beginning of month
|
||||
// BeginOfMonth return beginning of month.
|
||||
// Play: https://go.dev/play/p/bWXVFsmmzwL
|
||||
func BeginOfMonth(t time.Time) time.Time {
|
||||
y, m, _ := t.Date()
|
||||
return time.Date(y, m, 1, 0, 0, 0, 0, t.Location())
|
||||
}
|
||||
|
||||
// EndOfMonth return end of month
|
||||
// EndOfMonth return end of month.
|
||||
// Play: https://go.dev/play/p/_GWh10B3Nqi
|
||||
func EndOfMonth(t time.Time) time.Time {
|
||||
return BeginOfMonth(t).AddDate(0, 1, 0).Add(-time.Nanosecond)
|
||||
}
|
||||
|
||||
// BeginOfYear return beginning of year
|
||||
// BeginOfYear return the date time at the begin of year.
|
||||
// Play: https://go.dev/play/p/i326DSwLnV8
|
||||
func BeginOfYear(t time.Time) time.Time {
|
||||
y, _, _ := t.Date()
|
||||
return time.Date(y, time.January, 1, 0, 0, 0, 0, t.Location())
|
||||
}
|
||||
|
||||
// EndOfYear return end of year
|
||||
// EndOfYear return the date time at the end of year.
|
||||
// Play: https://go.dev/play/p/G01cKlMCvNm
|
||||
func EndOfYear(t time.Time) time.Time {
|
||||
return BeginOfYear(t).AddDate(1, 0, 0).Add(-time.Nanosecond)
|
||||
}
|
||||
|
||||
// IsLeapYear check if param year is leap year or not.
|
||||
// Play: https://go.dev/play/p/xS1eS2ejGew
|
||||
func IsLeapYear(year int) bool {
|
||||
return year%4 == 0 && (year%100 != 0 || year%400 == 0)
|
||||
}
|
||||
|
||||
// BetweenSeconds returns the number of seconds between two times.
|
||||
// Play: https://go.dev/play/p/n3YDRyfyXJu
|
||||
func BetweenSeconds(t1 time.Time, t2 time.Time) int64 {
|
||||
index := t2.Unix() - t1.Unix()
|
||||
return index
|
||||
}
|
||||
|
||||
// DayOfYear returns which day of the year the parameter date `t` is.
|
||||
// Play: https://go.dev/play/p/0hjqhTwFNlH
|
||||
func DayOfYear(t time.Time) int {
|
||||
y, m, d := t.Date()
|
||||
firstDay := time.Date(y, 1, 1, 0, 0, 0, 0, t.Location())
|
||||
nowDate := time.Date(y, m, d, 0, 0, 0, 0, t.Location())
|
||||
|
||||
return int(nowDate.Sub(firstDay).Hours() / 24)
|
||||
}
|
||||
|
||||
// IsWeekend checks if passed time is weekend or not.
|
||||
// Play: https://go.dev/play/p/cupRM5aZOIY
|
||||
// Deprecated Use '== Weekday' instead
|
||||
func IsWeekend(t time.Time) bool {
|
||||
return time.Saturday == t.Weekday() || time.Sunday == t.Weekday()
|
||||
}
|
||||
|
||||
// NowDateOrTime return current datetime with specific format and timezone.
|
||||
// Play: https://go.dev/play/p/EZ-begEjtT0
|
||||
func NowDateOrTime(format string, timezone ...string) string {
|
||||
tf, ok := timeFormat[strings.ToLower(format)]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
if timezone != nil && timezone[0] != "" {
|
||||
loc, err := time.LoadLocation(timezone[0])
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return time.Now().In(loc).Format(tf)
|
||||
}
|
||||
|
||||
return time.Now().Format(tf)
|
||||
}
|
||||
|
||||
// Timestamp return current second timestamp.
|
||||
// Play: https://go.dev/play/p/iU5b7Vvjx6x
|
||||
func Timestamp(timezone ...string) int64 {
|
||||
t := time.Now()
|
||||
|
||||
if timezone != nil && timezone[0] != "" {
|
||||
loc, err := time.LoadLocation(timezone[0])
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
t = t.In(loc)
|
||||
}
|
||||
|
||||
return t.Unix()
|
||||
}
|
||||
|
||||
// TimestampMilli return current mill second timestamp.
|
||||
// Play: https://go.dev/play/p/4gvEusOTu1T
|
||||
func TimestampMilli(timezone ...string) int64 {
|
||||
t := time.Now()
|
||||
|
||||
if timezone != nil && timezone[0] != "" {
|
||||
loc, err := time.LoadLocation(timezone[0])
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
t = t.In(loc)
|
||||
}
|
||||
|
||||
return int64(time.Nanosecond) * t.UnixNano() / int64(time.Millisecond)
|
||||
}
|
||||
|
||||
// TimestampMicro return current micro second timestamp.
|
||||
// Play: https://go.dev/play/p/2maANglKHQE
|
||||
func TimestampMicro(timezone ...string) int64 {
|
||||
t := time.Now()
|
||||
|
||||
if timezone != nil && timezone[0] != "" {
|
||||
loc, err := time.LoadLocation(timezone[0])
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
t = t.In(loc)
|
||||
}
|
||||
|
||||
return int64(time.Nanosecond) * t.UnixNano() / int64(time.Microsecond)
|
||||
}
|
||||
|
||||
// TimestampNano return current nano second timestamp.
|
||||
// Play: https://go.dev/play/p/A9Oq_COrcCF
|
||||
func TimestampNano(timezone ...string) int64 {
|
||||
t := time.Now()
|
||||
|
||||
if timezone != nil && timezone[0] != "" {
|
||||
loc, err := time.LoadLocation(timezone[0])
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
t = t.In(loc)
|
||||
}
|
||||
|
||||
return t.UnixNano()
|
||||
}
|
||||
|
||||
// TrackFuncTime track the time of function execution.
|
||||
// call it at top of the func like `defer TrackFuncTime(time.Now())()`
|
||||
// Play: https://go.dev/play/p/QBSEdfXHPTp
|
||||
func TrackFuncTime(pre time.Time) func() {
|
||||
callerName := getCallerName()
|
||||
return func() {
|
||||
elapsed := time.Since(pre)
|
||||
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: https://go.dev/play/p/qD6qGb3TbOy
|
||||
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: https://go.dev/play/p/6kHBpAxD9ZC
|
||||
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
|
||||
}
|
||||
|
||||
// Min returns the earliest time among the given times.
|
||||
// Play: https://go.dev/play/p/MCIDvHNOGGb
|
||||
func Min(t1 time.Time, times ...time.Time) time.Time {
|
||||
minTime := t1
|
||||
|
||||
for _, t := range times {
|
||||
if t.Before(minTime) {
|
||||
minTime = t
|
||||
}
|
||||
}
|
||||
|
||||
return minTime
|
||||
}
|
||||
|
||||
// Max returns the latest time among the given times.
|
||||
// Play: https://go.dev/play/p/9m6JMk1LB7-
|
||||
func Max(t1 time.Time, times ...time.Time) time.Time {
|
||||
maxTime := t1
|
||||
|
||||
for _, t := range times {
|
||||
if t.After(maxTime) {
|
||||
maxTime = t
|
||||
}
|
||||
}
|
||||
|
||||
return maxTime
|
||||
}
|
||||
|
||||
// MaxMin returns the latest and earliest time among the given times.
|
||||
// Play: https://go.dev/play/p/rbW51cDtM_2
|
||||
func MaxMin(t1 time.Time, times ...time.Time) (maxTime time.Time, minTime time.Time) {
|
||||
maxTime = t1
|
||||
minTime = t1
|
||||
|
||||
for _, t := range times {
|
||||
if t.Before(minTime) {
|
||||
minTime = t
|
||||
}
|
||||
|
||||
if t.After(maxTime) {
|
||||
maxTime = t
|
||||
}
|
||||
}
|
||||
|
||||
return maxTime, minTime
|
||||
}
|
||||
|
||||
538
datetime/datetime_example_test.go
Normal file
538
datetime/datetime_example_test.go
Normal file
@@ -0,0 +1,538 @@
|
||||
package datetime
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ExampleAddDay() {
|
||||
date, _ := time.Parse("2006-01-02 15:04:05", "2021-01-01 00:00:00")
|
||||
|
||||
after1Day := AddDay(date, 1)
|
||||
before1Day := AddDay(date, -1)
|
||||
|
||||
fmt.Println(after1Day.Format("2006-01-02 15:04:05"))
|
||||
fmt.Println(before1Day.Format("2006-01-02 15:04:05"))
|
||||
|
||||
// Output:
|
||||
// 2021-01-02 00:00:00
|
||||
// 2020-12-31 00:00:00
|
||||
}
|
||||
|
||||
func ExampleAddWeek() {
|
||||
date, _ := time.Parse("2006-01-02", "2021-01-01")
|
||||
|
||||
after2Weeks := AddWeek(date, 2)
|
||||
before2Weeks := AddWeek(date, -2)
|
||||
|
||||
fmt.Println(after2Weeks.Format("2006-01-02"))
|
||||
fmt.Println(before2Weeks.Format("2006-01-02"))
|
||||
|
||||
// Output:
|
||||
// 2021-01-15
|
||||
// 2020-12-18
|
||||
}
|
||||
|
||||
func ExampleAddMonth() {
|
||||
date, _ := time.Parse("2006-01-02", "2021-01-01")
|
||||
|
||||
after2Months := AddMonth(date, 2)
|
||||
before2Months := AddMonth(date, -2)
|
||||
|
||||
fmt.Println(after2Months.Format("2006-01-02"))
|
||||
fmt.Println(before2Months.Format("2006-01-02"))
|
||||
|
||||
// Output:
|
||||
// 2021-03-01
|
||||
// 2020-11-01
|
||||
}
|
||||
|
||||
func ExampleAddHour() {
|
||||
date, _ := time.Parse("2006-01-02 15:04:05", "2021-01-01 00:00:00")
|
||||
|
||||
after2Hours := AddHour(date, 2)
|
||||
before2Hours := AddHour(date, -2)
|
||||
|
||||
fmt.Println(after2Hours.Format("2006-01-02 15:04:05"))
|
||||
fmt.Println(before2Hours.Format("2006-01-02 15:04:05"))
|
||||
|
||||
// Output:
|
||||
// 2021-01-01 02:00:00
|
||||
// 2020-12-31 22:00:00
|
||||
}
|
||||
|
||||
func ExampleAddMinute() {
|
||||
date, _ := time.Parse("2006-01-02 15:04:05", "2021-01-01 00:00:00")
|
||||
|
||||
after2Minutes := AddMinute(date, 2)
|
||||
before2Minutes := AddMinute(date, -2)
|
||||
|
||||
fmt.Println(after2Minutes.Format("2006-01-02 15:04:05"))
|
||||
fmt.Println(before2Minutes.Format("2006-01-02 15:04:05"))
|
||||
|
||||
// Output:
|
||||
// 2021-01-01 00:02:00
|
||||
// 2020-12-31 23:58:00
|
||||
}
|
||||
|
||||
func ExampleAddYear() {
|
||||
date, _ := time.Parse("2006-01-02", "2021-01-01")
|
||||
|
||||
after2Years := AddYear(date, 2)
|
||||
before2Years := AddYear(date, -2)
|
||||
|
||||
fmt.Println(after2Years.Format("2006-01-02"))
|
||||
fmt.Println(before2Years.Format("2006-01-02"))
|
||||
|
||||
// Output:
|
||||
// 2023-01-01
|
||||
// 2019-01-01
|
||||
}
|
||||
|
||||
func ExampleAddDaySafe() {
|
||||
leapYearDate1, _ := time.Parse("2006-01-02", "2024-02-29")
|
||||
result1 := AddDaySafe(leapYearDate1, 1)
|
||||
|
||||
leapYearDate2, _ := time.Parse("2006-01-02", "2024-03-01")
|
||||
result2 := AddDaySafe(leapYearDate2, -1)
|
||||
|
||||
nonLeapYearDate1, _ := time.Parse("2006-01-02", "2025-02-28")
|
||||
result3 := AddDaySafe(nonLeapYearDate1, 1)
|
||||
|
||||
nonLeaYearDate2, _ := time.Parse("2006-01-02", "2025-03-01")
|
||||
result4 := AddDaySafe(nonLeaYearDate2, -1)
|
||||
|
||||
fmt.Println(result1.Format("2006-01-02"))
|
||||
fmt.Println(result2.Format("2006-01-02"))
|
||||
fmt.Println(result3.Format("2006-01-02"))
|
||||
fmt.Println(result4.Format("2006-01-02"))
|
||||
|
||||
// Output:
|
||||
// 2024-03-01
|
||||
// 2024-02-29
|
||||
// 2025-03-01
|
||||
// 2025-02-28
|
||||
}
|
||||
|
||||
func ExampleAddMonthSafe() {
|
||||
date1, _ := time.Parse("2006-01-02", "2025-01-31")
|
||||
result1 := AddMonthSafe(date1, 1)
|
||||
|
||||
date2, _ := time.Parse("2006-01-02", "2024-02-29")
|
||||
result2 := AddMonthSafe(date2, -1)
|
||||
|
||||
fmt.Println(result1.Format("2006-01-02"))
|
||||
fmt.Println(result2.Format("2006-01-02"))
|
||||
|
||||
// Output:
|
||||
// 2025-02-28
|
||||
// 2024-01-29
|
||||
}
|
||||
|
||||
func ExampleAddYearSafe() {
|
||||
date, _ := time.Parse("2006-01-02", "2020-02-29")
|
||||
|
||||
result1 := AddYearSafe(date, 1)
|
||||
result2 := AddYearSafe(date, -1)
|
||||
|
||||
fmt.Println(result1.Format("2006-01-02"))
|
||||
fmt.Println(result2.Format("2006-01-02"))
|
||||
|
||||
// Output:
|
||||
// 2021-02-28
|
||||
// 2019-02-28
|
||||
}
|
||||
|
||||
func ExampleGetNowDate() {
|
||||
result := GetNowDate()
|
||||
|
||||
expected := time.Now().Format("2006-01-02")
|
||||
|
||||
fmt.Println(result == expected)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
}
|
||||
|
||||
func ExampleGetNowTime() {
|
||||
result := GetNowTime()
|
||||
|
||||
expected := time.Now().Format("15:04:05")
|
||||
|
||||
fmt.Println(result == expected)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
}
|
||||
|
||||
func ExampleGetNowDateTime() {
|
||||
result := GetNowDateTime()
|
||||
|
||||
expected := time.Now().Format("2006-01-02 15:04:05")
|
||||
|
||||
fmt.Println(result == expected)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
}
|
||||
|
||||
// func ExampleGetZeroHourTimestamp() {
|
||||
// ts := GetZeroHourTimestamp()
|
||||
|
||||
// fmt.Println(ts)
|
||||
|
||||
// // Output:
|
||||
// // 1673107200
|
||||
// }
|
||||
|
||||
// func ExampleGetNightTimestamp() {
|
||||
// ts := GetNightTimestamp()
|
||||
|
||||
// fmt.Println(ts)
|
||||
|
||||
// // Output:
|
||||
// // 1673193599
|
||||
// }
|
||||
|
||||
func ExampleFormatTimeToStr() {
|
||||
datetime, _ := time.Parse("2006-01-02 15:04:05", "2021-01-02 16:04:08")
|
||||
|
||||
result1 := FormatTimeToStr(datetime, "yyyy-mm-dd hh:mm:ss")
|
||||
result2 := FormatTimeToStr(datetime, "yyyy-mm-dd")
|
||||
result3 := FormatTimeToStr(datetime, "dd-mm-yy hh:mm:ss")
|
||||
result4 := FormatTimeToStr(datetime, "yyyy-mm-dd hh")
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
|
||||
// Output:
|
||||
// 2021-01-02 16:04:08
|
||||
// 2021-01-02
|
||||
// 02-01-21 16:04:08
|
||||
// 2021-01-02 16
|
||||
}
|
||||
|
||||
func ExampleFormatStrToTime() {
|
||||
result1, _ := FormatStrToTime("2021-01-02 16:04:08", "yyyy-mm-dd hh:mm:ss")
|
||||
result2, _ := FormatStrToTime("2021-01-02", "yyyy-mm-dd")
|
||||
result3, _ := FormatStrToTime("02-01-21 16:04:08", "dd-mm-yy hh:mm:ss")
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
|
||||
// Output:
|
||||
// 2021-01-02 16:04:08 +0000 UTC
|
||||
// 2021-01-02 00:00:00 +0000 UTC
|
||||
// 2021-01-02 16:04:08 +0000 UTC
|
||||
}
|
||||
|
||||
func ExampleBeginOfMinute() {
|
||||
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
|
||||
|
||||
result := BeginOfMinute(input)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// 2023-01-08 18:50:00 +0000 UTC
|
||||
}
|
||||
|
||||
func ExampleEndOfMinute() {
|
||||
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
|
||||
|
||||
result := EndOfMinute(input)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// 2023-01-08 18:50:59.999999999 +0000 UTC
|
||||
}
|
||||
|
||||
func ExampleBeginOfHour() {
|
||||
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
|
||||
|
||||
result := BeginOfHour(input)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// 2023-01-08 18:00:00 +0000 UTC
|
||||
}
|
||||
|
||||
func ExampleEndOfHour() {
|
||||
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
|
||||
|
||||
result := EndOfHour(input)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// 2023-01-08 18:59:59.999999999 +0000 UTC
|
||||
}
|
||||
|
||||
func ExampleBeginOfDay() {
|
||||
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
|
||||
|
||||
result := BeginOfDay(input)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// 2023-01-08 00:00:00 +0000 UTC
|
||||
}
|
||||
|
||||
func ExampleEndOfDay() {
|
||||
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
|
||||
|
||||
result := EndOfDay(input)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// 2023-01-08 23:59:59.999999999 +0000 UTC
|
||||
}
|
||||
|
||||
func ExampleBeginOfWeek() {
|
||||
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
|
||||
|
||||
result := BeginOfWeek(input, time.Monday)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// 2023-01-02 00:00:00 +0000 UTC
|
||||
}
|
||||
|
||||
func ExampleEndOfWeek() {
|
||||
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
|
||||
|
||||
result := EndOfWeek(input, time.Sunday)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// 2023-01-08 23:59:59.999999999 +0000 UTC
|
||||
}
|
||||
|
||||
func ExampleBeginOfMonth() {
|
||||
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
|
||||
|
||||
result := BeginOfMonth(input)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// 2023-01-01 00:00:00 +0000 UTC
|
||||
}
|
||||
|
||||
func ExampleEndOfMonth() {
|
||||
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
|
||||
|
||||
result := EndOfMonth(input)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// 2023-01-31 23:59:59.999999999 +0000 UTC
|
||||
}
|
||||
|
||||
func ExampleBeginOfYear() {
|
||||
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
|
||||
|
||||
result := BeginOfYear(input)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// 2023-01-01 00:00:00 +0000 UTC
|
||||
}
|
||||
|
||||
func ExampleEndOfYear() {
|
||||
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
|
||||
|
||||
result := EndOfYear(input)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// 2023-12-31 23:59:59.999999999 +0000 UTC
|
||||
}
|
||||
|
||||
func ExampleNewUnix() {
|
||||
result := NewUnix(1647597438)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// &{1647597438}
|
||||
}
|
||||
|
||||
func ExampleNewUnixNow() {
|
||||
tm1 := NewUnixNow()
|
||||
|
||||
unixTimestamp := tm1.ToUnix()
|
||||
|
||||
tm2 := NewUnix(unixTimestamp)
|
||||
|
||||
fmt.Println(reflect.DeepEqual(tm1, tm2))
|
||||
|
||||
// Output:
|
||||
// true
|
||||
}
|
||||
|
||||
// func ExampleNewFormat() {
|
||||
// tm, err := NewFormat("2022-03-18 17:04:05")
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
|
||||
// result := tm.ToFormat()
|
||||
|
||||
// fmt.Println(result)
|
||||
|
||||
// // Output:
|
||||
// // 2022-03-18 17:04:05
|
||||
// }
|
||||
|
||||
// func ExampleNewISO8601() {
|
||||
// tm, err := NewISO8601("2006-01-02T15:04:05.999Z")
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
|
||||
// result := tm.ToIso8601()
|
||||
|
||||
// fmt.Println(result)
|
||||
|
||||
// // Output:
|
||||
// // 2006-01-02T23:04:05+08:00
|
||||
// }
|
||||
|
||||
func ExampleIsLeapYear() {
|
||||
result1 := IsLeapYear(2000)
|
||||
result2 := IsLeapYear(2001)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
}
|
||||
|
||||
func ExampleBetweenSeconds() {
|
||||
today := time.Now()
|
||||
tomorrow := AddDay(today, 1)
|
||||
yesterday := AddDay(today, -1)
|
||||
|
||||
result1 := BetweenSeconds(today, tomorrow)
|
||||
result2 := BetweenSeconds(today, yesterday)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
|
||||
// Output:
|
||||
// 86400
|
||||
// -86400
|
||||
}
|
||||
|
||||
func ExampleDayOfYear() {
|
||||
date1 := time.Date(2023, 02, 01, 1, 1, 1, 0, time.Local)
|
||||
result1 := DayOfYear(date1)
|
||||
|
||||
date2 := time.Date(2023, 01, 02, 1, 1, 1, 0, time.Local)
|
||||
result2 := DayOfYear(date2)
|
||||
|
||||
date3 := time.Date(2023, 01, 01, 1, 1, 1, 0, time.Local)
|
||||
result3 := DayOfYear(date3)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
|
||||
// Output:
|
||||
// 31
|
||||
// 1
|
||||
// 0
|
||||
}
|
||||
|
||||
func ExampleIsWeekend() {
|
||||
date1 := time.Date(2023, 06, 03, 0, 0, 0, 0, time.Local)
|
||||
date2 := time.Date(2023, 06, 04, 0, 0, 0, 0, time.Local)
|
||||
date3 := time.Date(2023, 06, 02, 0, 0, 0, 0, time.Local)
|
||||
|
||||
result1 := IsWeekend(date1)
|
||||
result2 := IsWeekend(date2)
|
||||
result3 := IsWeekend(date3)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// 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>
|
||||
}
|
||||
|
||||
func ExampleMin() {
|
||||
result := Min(time.Date(2024, time.September, 1, 0, 0, 0, 0, time.UTC), time.Date(2024, time.September, 2, 0, 0, 0, 0, time.UTC))
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// 2024-09-01 00:00:00 +0000 UTC
|
||||
}
|
||||
|
||||
func ExampleMax() {
|
||||
result := Max(time.Date(2024, time.September, 1, 0, 0, 0, 0, time.UTC), time.Date(2024, time.September, 2, 0, 0, 0, 0, time.UTC))
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// 2024-09-02 00:00:00 +0000 UTC
|
||||
}
|
||||
|
||||
func ExampleMaxMin() {
|
||||
max, min := MaxMin(time.Date(2024, time.September, 1, 0, 0, 0, 0, time.UTC), time.Date(2024, time.September, 2, 0, 0, 0, 0, time.UTC), time.Date(2024, time.September, 3, 0, 0, 0, 0, time.UTC))
|
||||
|
||||
fmt.Println(max)
|
||||
fmt.Println(min)
|
||||
|
||||
// Output:
|
||||
// 2024-09-03 00:00:00 +0000 UTC
|
||||
// 2024-09-01 00:00:00 +0000 UTC
|
||||
}
|
||||
@@ -7,85 +7,503 @@ import (
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
|
||||
func TestAddDay(t *testing.T) {
|
||||
func TestAddYear(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestAddDay")
|
||||
|
||||
now := time.Now()
|
||||
after2Days := AddDay(now, 2)
|
||||
diff1 := after2Days.Sub(now)
|
||||
assert.Equal(float64(48), diff1.Hours())
|
||||
tests := []struct {
|
||||
inputDate string
|
||||
years int
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
inputDate: "2021-01-01 00:00:00",
|
||||
years: 1,
|
||||
expected: "2022-01-01 00:00:00",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01 00:00:00",
|
||||
years: -1,
|
||||
expected: "2020-01-01 00:00:00",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01 00:00:00",
|
||||
years: 0,
|
||||
expected: "2021-01-01 00:00:00",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01 00:00:00",
|
||||
years: 2,
|
||||
expected: "2023-01-01 00:00:00",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01 00:00:00",
|
||||
years: 3,
|
||||
expected: "2024-01-01 00:00:00",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01 00:00:00",
|
||||
years: 4,
|
||||
expected: "2025-01-01 00:00:00",
|
||||
},
|
||||
}
|
||||
|
||||
before2Days := AddDay(now, -2)
|
||||
diff2 := before2Days.Sub(now)
|
||||
assert.Equal(float64(-48), diff2.Hours())
|
||||
for _, tt := range tests {
|
||||
date, _ := time.Parse("2006-01-02 15:04:05", tt.inputDate)
|
||||
result := AddYear(date, int64(tt.years))
|
||||
assert.Equal(tt.expected, result.Format("2006-01-02 15:04:05"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddDay(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestAddDay")
|
||||
|
||||
tests := []struct {
|
||||
inputDate string
|
||||
days int
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
inputDate: "2021-01-01 00:00:00",
|
||||
days: 1,
|
||||
expected: "2021-01-02 00:00:00",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01 00:00:00",
|
||||
days: -1,
|
||||
expected: "2020-12-31 00:00:00",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01 00:00:00",
|
||||
days: 0,
|
||||
expected: "2021-01-01 00:00:00",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01 00:00:00",
|
||||
days: 2,
|
||||
expected: "2021-01-03 00:00:00",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01 00:00:00",
|
||||
days: 3,
|
||||
expected: "2021-01-04 00:00:00",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01 00:00:00",
|
||||
days: 4,
|
||||
expected: "2021-01-05 00:00:00",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
date, _ := time.Parse("2006-01-02 15:04:05", tt.inputDate)
|
||||
result := AddDay(date, int64(tt.days))
|
||||
assert.Equal(tt.expected, result.Format("2006-01-02 15:04:05"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddHour(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestAddHour")
|
||||
|
||||
now := time.Now()
|
||||
after2Hours := AddHour(now, 2)
|
||||
diff1 := after2Hours.Sub(now)
|
||||
assert.Equal(float64(2), diff1.Hours())
|
||||
tests := []struct {
|
||||
inputDate string
|
||||
hours int
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
inputDate: "2021-01-01 00:00:00",
|
||||
hours: 1,
|
||||
expected: "2021-01-01 01:00:00",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01 00:00:00",
|
||||
hours: -1,
|
||||
expected: "2020-12-31 23:00:00",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01 00:00:00",
|
||||
hours: 0,
|
||||
expected: "2021-01-01 00:00:00",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01 00:00:00",
|
||||
hours: 24,
|
||||
expected: "2021-01-02 00:00:00",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01 00:00:00",
|
||||
hours: 25,
|
||||
expected: "2021-01-02 01:00:00",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01 00:00:00",
|
||||
hours: 48,
|
||||
expected: "2021-01-03 00:00:00",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01 00:00:00",
|
||||
hours: 49,
|
||||
expected: "2021-01-03 01:00:00",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
date, _ := time.Parse("2006-01-02 15:04:05", tt.inputDate)
|
||||
result := AddHour(date, int64(tt.hours))
|
||||
assert.Equal(tt.expected, result.Format("2006-01-02 15:04:05"))
|
||||
}
|
||||
|
||||
before2Hours := AddHour(now, -2)
|
||||
diff2 := before2Hours.Sub(now)
|
||||
assert.Equal(float64(-2), diff2.Hours())
|
||||
}
|
||||
|
||||
func TestAddMinute(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestAddMinute")
|
||||
|
||||
now := time.Now()
|
||||
after2Minutes := AddMinute(now, 2)
|
||||
diff1 := after2Minutes.Sub(now)
|
||||
assert.Equal(float64(2), diff1.Minutes())
|
||||
tests := []struct {
|
||||
inputDate string
|
||||
minutes int
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
inputDate: "2021-01-01 00:00:00",
|
||||
minutes: 1,
|
||||
expected: "2021-01-01 00:01:00",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01 00:00:00",
|
||||
minutes: -1,
|
||||
expected: "2020-12-31 23:59:00",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01 00:00:00",
|
||||
minutes: 0,
|
||||
expected: "2021-01-01 00:00:00",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01 00:00:00",
|
||||
minutes: 60,
|
||||
expected: "2021-01-01 01:00:00",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01 00:00:00",
|
||||
minutes: 61,
|
||||
expected: "2021-01-01 01:01:00",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01 00:00:00",
|
||||
minutes: 1440,
|
||||
expected: "2021-01-02 00:00:00",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01 00:00:00",
|
||||
minutes: 1441,
|
||||
expected: "2021-01-02 00:01:00",
|
||||
},
|
||||
}
|
||||
|
||||
before2Minutes := AddMinute(now, -2)
|
||||
diff2 := before2Minutes.Sub(now)
|
||||
assert.Equal(float64(-2), diff2.Minutes())
|
||||
for _, tt := range tests {
|
||||
date, _ := time.Parse("2006-01-02 15:04:05", tt.inputDate)
|
||||
result := AddMinute(date, int64(tt.minutes))
|
||||
assert.Equal(tt.expected, result.Format("2006-01-02 15:04:05"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddWeek(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestAddWeek")
|
||||
|
||||
tests := []struct {
|
||||
inputDate string
|
||||
weeks int
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
inputDate: "2021-01-01",
|
||||
weeks: 1,
|
||||
expected: "2021-01-08",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01",
|
||||
weeks: -1,
|
||||
expected: "2020-12-25",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01",
|
||||
weeks: 0,
|
||||
expected: "2021-01-01",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01",
|
||||
weeks: 52,
|
||||
expected: "2021-12-31",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01",
|
||||
weeks: 53,
|
||||
expected: "2022-01-07",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01",
|
||||
weeks: 104,
|
||||
expected: "2022-12-30",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
date, _ := time.Parse("2006-01-02", tt.inputDate)
|
||||
result := AddWeek(date, int64(tt.weeks))
|
||||
assert.Equal(tt.expected, result.Format("2006-01-02"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddMonth(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestAddMonth")
|
||||
|
||||
tests := []struct {
|
||||
inputDate string
|
||||
months int
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
inputDate: "2021-01-01",
|
||||
months: 1,
|
||||
expected: "2021-02-01",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01",
|
||||
months: -1,
|
||||
expected: "2020-12-01",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01",
|
||||
months: 0,
|
||||
expected: "2021-01-01",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01",
|
||||
months: 12,
|
||||
expected: "2022-01-01",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01",
|
||||
months: 13,
|
||||
expected: "2022-02-01",
|
||||
},
|
||||
{
|
||||
inputDate: "2021-01-01",
|
||||
months: 24,
|
||||
expected: "2023-01-01",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
date, _ := time.Parse("2006-01-02", tt.inputDate)
|
||||
result := AddMonth(date, int64(tt.months))
|
||||
assert.Equal(tt.expected, result.Format("2006-01-02"))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAddDaySafe(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestAddDaySafe")
|
||||
|
||||
tests := []struct {
|
||||
inputDate string
|
||||
days int
|
||||
expected string
|
||||
}{
|
||||
{"2025-01-31", 10, "2025-02-10"},
|
||||
{"2025-01-01", 30, "2025-01-31"},
|
||||
{"2025-01-31", 1, "2025-02-01"},
|
||||
{"2025-02-28", 1, "2025-03-01"},
|
||||
{"2024-02-29", 1, "2024-03-01"},
|
||||
{"2024-02-29", 365, "2025-02-28"},
|
||||
|
||||
{"2025-01-31", -10, "2025-01-21"},
|
||||
{"2025-01-01", -30, "2024-12-02"},
|
||||
{"2025-02-01", -1, "2025-01-31"},
|
||||
{"2025-03-01", -1, "2025-02-28"},
|
||||
{"2024-03-01", -1, "2024-02-29"},
|
||||
|
||||
{"2025-01-31", -31, "2024-12-31"},
|
||||
{"2025-12-31", 1, "2026-01-01"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
date, _ := time.Parse("2006-01-02", tt.inputDate)
|
||||
result := AddDaySafe(date, tt.days)
|
||||
assert.Equal(tt.expected, result.Format("2006-01-02"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddMonthSafe(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestAddMonthSafe")
|
||||
|
||||
tests := []struct {
|
||||
inputDate string
|
||||
months int
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
inputDate: "2025-01-31",
|
||||
months: 1,
|
||||
expected: "2025-02-28",
|
||||
},
|
||||
{
|
||||
inputDate: "2025-01-31",
|
||||
months: -1,
|
||||
expected: "2024-12-31",
|
||||
},
|
||||
{
|
||||
inputDate: "2025-12-31",
|
||||
months: 1,
|
||||
expected: "2026-01-31",
|
||||
},
|
||||
{
|
||||
inputDate: "2025-01-31",
|
||||
months: -1,
|
||||
expected: "2024-12-31",
|
||||
},
|
||||
{
|
||||
inputDate: "2024-02-29",
|
||||
months: 1,
|
||||
expected: "2024-03-29",
|
||||
},
|
||||
{
|
||||
inputDate: "2024-02-29",
|
||||
months: -1,
|
||||
expected: "2024-01-29",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
date, _ := time.Parse("2006-01-02", tt.inputDate)
|
||||
result := AddMonthSafe(date, tt.months)
|
||||
assert.Equal(tt.expected, result.Format("2006-01-02"))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAddYearSafe(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestAddYearSafe")
|
||||
|
||||
tests := []struct {
|
||||
inputDate string
|
||||
years int
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
inputDate: "2020-02-29",
|
||||
years: 1,
|
||||
expected: "2021-02-28",
|
||||
},
|
||||
{
|
||||
inputDate: "2020-02-29",
|
||||
years: 2,
|
||||
expected: "2022-02-28",
|
||||
},
|
||||
{
|
||||
inputDate: "2020-02-29",
|
||||
years: -1,
|
||||
expected: "2019-02-28",
|
||||
},
|
||||
{
|
||||
inputDate: "2020-02-29",
|
||||
years: -2,
|
||||
expected: "2018-02-28",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
date, _ := time.Parse("2006-01-02", tt.inputDate)
|
||||
result := AddYearSafe(date, tt.years)
|
||||
assert.Equal(tt.expected, result.Format("2006-01-02"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNowDate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestGetNowDate")
|
||||
expected := time.Now().Format("2006-01-02")
|
||||
assert.Equal(expected, GetNowDate())
|
||||
}
|
||||
|
||||
func TestGetNotTime(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestGetNotTime")
|
||||
func TestGetNowTime(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestGetNowTime")
|
||||
expected := time.Now().Format("15:04:05")
|
||||
assert.Equal(expected, GetNowTime())
|
||||
}
|
||||
|
||||
func TestGetNowDateTime(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestGetNowDateTime")
|
||||
expected := time.Now().Format("2006-01-02 15:04:05")
|
||||
assert.Equal(expected, GetNowDateTime())
|
||||
}
|
||||
|
||||
func TestGetTodayStartTime(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestGetTodayStartTime")
|
||||
expected := time.Now().Format("2006-01-02") + " 00:00:00"
|
||||
assert.Equal(expected, GetTodayStartTime())
|
||||
}
|
||||
|
||||
func TestGetTodayEndTime(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestGetTodayEndTime")
|
||||
expected := time.Now().Format("2006-01-02") + " 23:59:59"
|
||||
assert.Equal(expected, GetTodayEndTime())
|
||||
}
|
||||
|
||||
func TestFormatTimeToStr(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestFormatTimeToStr")
|
||||
|
||||
datetime, _ := time.Parse("2006-01-02 15:04:05", "2021-01-02 16:04:08")
|
||||
cases := []string{
|
||||
"yyyy-mm-dd hh:mm:ss", "yyyy-mm-dd",
|
||||
"dd-mm-yy hh:mm:ss", "yyyy/mm/dd hh:mm:ss",
|
||||
"hh:mm:ss", "yyyy/mm"}
|
||||
"hh:mm:ss", "yyyy/mm",
|
||||
"yyyy-mm-dd hh",
|
||||
}
|
||||
|
||||
expected := []string{
|
||||
"2021-01-02 16:04:08", "2021-01-02",
|
||||
"02-01-21 16:04:08", "2021/01/02 16:04:08",
|
||||
"16:04:08", "2021/01"}
|
||||
"16:04:08", "2021/01",
|
||||
"2021-01-02 16",
|
||||
}
|
||||
|
||||
for i := 0; i < len(cases); i++ {
|
||||
actual := FormatTimeToStr(datetime, cases[i])
|
||||
assert.Equal(expected[i], actual)
|
||||
|
||||
}
|
||||
|
||||
ds := FormatTimeToStr(datetime, "yyyy-mm-dd hh:mm:ss", "EST")
|
||||
t.Log(ds)
|
||||
}
|
||||
|
||||
func TestFormatStrToTime(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestFormatStrToTime")
|
||||
|
||||
formats := []string{
|
||||
@@ -97,22 +515,28 @@ func TestFormatStrToTime(t *testing.T) {
|
||||
"dd-mm-yy hh:mm:ss", "yyyy/mm/dd hh:mm:ss",
|
||||
"yyyy/mm"}
|
||||
|
||||
datetimeStr := []string{
|
||||
expected := []string{
|
||||
"2021-01-02 16:04:08", "2021-01-02",
|
||||
"02-01-21 16:04:08", "2021/01/02 16:04:08",
|
||||
"2021/01"}
|
||||
|
||||
for i := 0; i < len(cases); i++ {
|
||||
actual, err := FormatStrToTime(datetimeStr[i], cases[i])
|
||||
actual, err := FormatStrToTime(expected[i], cases[i])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected, _ := time.Parse(formats[i], datetimeStr[i])
|
||||
expected, _ := time.Parse(formats[i], expected[i])
|
||||
assert.Equal(expected, actual)
|
||||
}
|
||||
|
||||
estTime, err := FormatStrToTime("2021-01-02 16:04:08", "yyyy-mm-dd hh:mm:ss", "EST")
|
||||
t.Log(estTime)
|
||||
assert.IsNil(err)
|
||||
}
|
||||
|
||||
func TestBeginOfMinute(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestBeginOfMinute")
|
||||
|
||||
expected := time.Date(2022, 2, 15, 15, 48, 0, 0, time.Local)
|
||||
@@ -123,6 +547,8 @@ func TestBeginOfMinute(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEndOfMinute(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestEndOfMinute")
|
||||
|
||||
expected := time.Date(2022, 2, 15, 15, 48, 59, 999999999, time.Local)
|
||||
@@ -133,6 +559,8 @@ func TestEndOfMinute(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBeginOfHour(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestBeginOfHour")
|
||||
|
||||
expected := time.Date(2022, 2, 15, 15, 0, 0, 0, time.Local)
|
||||
@@ -143,6 +571,8 @@ func TestBeginOfHour(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEndOfHour(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestEndOfHour")
|
||||
|
||||
expected := time.Date(2022, 2, 15, 15, 59, 59, 999999999, time.Local)
|
||||
@@ -153,6 +583,8 @@ func TestEndOfHour(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBeginOfDay(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestBeginOfDay")
|
||||
|
||||
expected := time.Date(2022, 2, 15, 0, 0, 0, 0, time.Local)
|
||||
@@ -163,6 +595,8 @@ func TestBeginOfDay(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEndOfDay(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestEndOfDay")
|
||||
|
||||
expected := time.Date(2022, 2, 15, 23, 59, 59, 999999999, time.Local)
|
||||
@@ -173,26 +607,32 @@ func TestEndOfDay(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBeginOfWeek(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestBeginOfWeek")
|
||||
|
||||
expected := time.Date(2022, 2, 13, 0, 0, 0, 0, time.Local)
|
||||
expected := time.Date(2022, 2, 14, 0, 0, 0, 0, time.Local)
|
||||
td := time.Date(2022, 2, 15, 15, 48, 40, 112, time.Local)
|
||||
actual := BeginOfWeek(td)
|
||||
actual := BeginOfWeek(td, time.Monday)
|
||||
|
||||
assert.Equal(expected, actual)
|
||||
}
|
||||
|
||||
func TestEndOfWeek(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestEndOfWeek")
|
||||
|
||||
expected := time.Date(2022, 2, 19, 23, 59, 59, 999999999, time.Local)
|
||||
expected := time.Date(2022, 2, 20, 23, 59, 59, 999999999, time.Local)
|
||||
td := time.Date(2022, 2, 15, 15, 48, 40, 112, time.Local)
|
||||
actual := EndOfWeek(td)
|
||||
actual := EndOfWeek(td, time.Sunday)
|
||||
|
||||
assert.Equal(expected, actual)
|
||||
}
|
||||
|
||||
func TestBeginOfMonth(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestBeginOfMonth")
|
||||
|
||||
expected := time.Date(2022, 2, 1, 0, 0, 0, 0, time.Local)
|
||||
@@ -203,6 +643,8 @@ func TestBeginOfMonth(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEndOfMonth(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestEndOfMonth")
|
||||
|
||||
expected := time.Date(2022, 2, 28, 23, 59, 59, 999999999, time.Local)
|
||||
@@ -213,6 +655,8 @@ func TestEndOfMonth(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBeginOfYear(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestBeginOfYear")
|
||||
|
||||
expected := time.Date(2022, 1, 1, 0, 0, 0, 0, time.Local)
|
||||
@@ -223,6 +667,8 @@ func TestBeginOfYear(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEndOfYear(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestEndOfYear")
|
||||
|
||||
expected := time.Date(2022, 12, 31, 23, 59, 59, 999999999, time.Local)
|
||||
@@ -231,3 +677,269 @@ func TestEndOfYear(t *testing.T) {
|
||||
|
||||
assert.Equal(expected, actual)
|
||||
}
|
||||
|
||||
func TestIsLeapYear(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestEndOfYear")
|
||||
|
||||
result1 := IsLeapYear(2000)
|
||||
result2 := IsLeapYear(2001)
|
||||
|
||||
assert.Equal(true, result1)
|
||||
assert.Equal(false, result2)
|
||||
}
|
||||
|
||||
func TestDayOfYear(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestDayOfYear")
|
||||
date1 := time.Date(2023, 02, 01, 1, 1, 1, 0, time.Local)
|
||||
result1 := DayOfYear(date1)
|
||||
assert.Equal(31, result1)
|
||||
|
||||
date2 := time.Date(2023, 01, 02, 1, 1, 1, 0, time.Local)
|
||||
result2 := DayOfYear(date2)
|
||||
assert.Equal(1, result2)
|
||||
|
||||
date3 := time.Date(2023, 01, 01, 1, 1, 1, 0, time.Local)
|
||||
result3 := DayOfYear(date3)
|
||||
assert.Equal(0, result3)
|
||||
}
|
||||
|
||||
func TestIsWeekend(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestIsWeekend")
|
||||
|
||||
date := time.Date(2023, 06, 03, 0, 0, 0, 0, time.Local)
|
||||
result := IsWeekend(date)
|
||||
assert.Equal(true, result)
|
||||
|
||||
date1 := time.Date(2023, 06, 04, 0, 0, 0, 0, time.Local)
|
||||
result1 := IsWeekend(date1)
|
||||
assert.Equal(true, result1)
|
||||
|
||||
date2 := time.Date(2023, 06, 02, 0, 0, 0, 0, time.Local)
|
||||
result2 := IsWeekend(date2)
|
||||
assert.Equal(false, result2)
|
||||
}
|
||||
|
||||
func TestNowDateOrTime(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
formats := []string{
|
||||
"yyyy-mm-dd hh:mm:ss",
|
||||
"yyyy-mm-dd",
|
||||
"dd-mm-yy hh:mm:ss",
|
||||
"yyyy/mm/dd hh:mm:ss",
|
||||
"hh:mm:ss",
|
||||
"yyyy/mm",
|
||||
"yyyy-mm-dd hh",
|
||||
}
|
||||
|
||||
for i := 0; i < len(formats); i++ {
|
||||
result := NowDateOrTime(formats[i], "UTC")
|
||||
t.Log(result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimestamp(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ts1 := Timestamp()
|
||||
t.Log(ts1)
|
||||
|
||||
ts2 := TimestampMilli()
|
||||
t.Log(ts2)
|
||||
|
||||
ts3 := TimestampMicro()
|
||||
t.Log(ts3)
|
||||
|
||||
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")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMin(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestMin")
|
||||
|
||||
zeroTime := time.Time{}
|
||||
now := time.Now()
|
||||
oneMinuteAgo := now.Add(-time.Minute)
|
||||
oneMinuteAfter := now.Add(time.Minute)
|
||||
|
||||
assert.Equal(zeroTime, Min(zeroTime, now, oneMinuteAgo, oneMinuteAfter))
|
||||
|
||||
assert.Equal(zeroTime, Min(now, zeroTime))
|
||||
|
||||
assert.Equal(oneMinuteAgo, Min(oneMinuteAgo, now, oneMinuteAfter))
|
||||
}
|
||||
|
||||
func TestMax(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestMax")
|
||||
|
||||
zeroTime := time.Time{}
|
||||
now := time.Now()
|
||||
oneMinuteAgo := now.Add(-time.Minute)
|
||||
oneMinuteAfter := now.Add(time.Minute)
|
||||
|
||||
assert.Equal(oneMinuteAfter, Max(zeroTime, now, oneMinuteAgo, oneMinuteAfter))
|
||||
|
||||
assert.Equal(now, Max(now, zeroTime))
|
||||
|
||||
assert.Equal(oneMinuteAfter, Max(oneMinuteAgo, now, oneMinuteAfter))
|
||||
}
|
||||
|
||||
func TestMaxMin(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestMinMax")
|
||||
|
||||
zeroTime := time.Time{}
|
||||
now := time.Now()
|
||||
oneMinuteAgo := now.Add(-time.Minute)
|
||||
oneMinuteAfter := now.Add(time.Minute)
|
||||
|
||||
max, min := MaxMin(zeroTime, now, oneMinuteAgo, oneMinuteAfter)
|
||||
assert.Equal(zeroTime, min)
|
||||
assert.Equal(oneMinuteAfter, max)
|
||||
|
||||
max, min = MaxMin(now, zeroTime)
|
||||
assert.Equal(zeroTime, min)
|
||||
assert.Equal(now, max)
|
||||
|
||||
max, min = MaxMin(oneMinuteAgo, now, oneMinuteAfter)
|
||||
assert.Equal(oneMinuteAgo, min)
|
||||
assert.Equal(oneMinuteAfter, max)
|
||||
}
|
||||
|
||||
func TestBetweenSeconds(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestBetweenSeconds")
|
||||
|
||||
today := time.Now()
|
||||
tomorrow := AddDay(today, 1)
|
||||
yesterday := AddDay(today, -1)
|
||||
|
||||
result1 := BetweenSeconds(today, tomorrow)
|
||||
result2 := BetweenSeconds(today, yesterday)
|
||||
|
||||
assert.Equal(int64(86400), result1)
|
||||
assert.Equal(int64(-86400), result2)
|
||||
}
|
||||
|
||||
90
docs/.vitepress/common.ts
Normal file
90
docs/.vitepress/common.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { defineConfig, HeadConfig } from 'vitepress'
|
||||
|
||||
export const META_IMAGE = '/lancet_logo.png'
|
||||
export const isProduction = process.env.NETLIFY && process.env.CONTEXT === 'production'
|
||||
|
||||
if (process.env.NETLIFY) {
|
||||
console.log('Netlify build', process.env.CONTEXT)
|
||||
}
|
||||
|
||||
const productionHead: HeadConfig[] = [
|
||||
[
|
||||
'script',
|
||||
{
|
||||
src: 'https://unpkg.com/thesemetrics@latest',
|
||||
async: '',
|
||||
type: 'text/javascript',
|
||||
},
|
||||
],
|
||||
]
|
||||
|
||||
const rControl = /[\u0000-\u001f]/g
|
||||
const rSpecial = /[\s~`!@#$%^&*()\-_+=[\]{}|\\;:"'“”‘’<>,.?/]+/g
|
||||
const rCombining = /[\u0300-\u036F]/g
|
||||
|
||||
/**
|
||||
* Default slugification function
|
||||
*/
|
||||
export const slugify = (str: string): string =>
|
||||
str
|
||||
.normalize('NFKD')
|
||||
// Remove accents
|
||||
.replace(rCombining, '')
|
||||
// Remove control characters
|
||||
.replace(rControl, '')
|
||||
// Replace special characters
|
||||
.replace(rSpecial, '-')
|
||||
// ensure it doesn't start with a number
|
||||
.replace(/^(\d)/, '_$1')
|
||||
|
||||
export const commonConfig = defineConfig({
|
||||
title: 'Lancet',
|
||||
appearance: true,
|
||||
ignoreDeadLinks: true,
|
||||
|
||||
markdown: {
|
||||
theme: {
|
||||
dark: 'dracula-soft',
|
||||
light: 'vitesse-light',
|
||||
},
|
||||
|
||||
attrs: {
|
||||
leftDelimiter: '%{',
|
||||
rightDelimiter: '}%',
|
||||
},
|
||||
|
||||
anchor: {
|
||||
slugify,
|
||||
},
|
||||
},
|
||||
|
||||
head: [
|
||||
// ['link', { rel: 'icon', type: 'image/svg+xml', href: '/logo.svg' }],
|
||||
['link', { rel: 'icon', type: 'image/png', href: '/lancet_logo_mini.png' }],
|
||||
['meta', { name: 'theme-color', content: '#5f67ee' }],
|
||||
['meta', { name: 'og:type', content: 'website' }],
|
||||
['meta', { name: 'og:locale', content: 'zh' }],
|
||||
|
||||
...(isProduction ? productionHead : []),
|
||||
],
|
||||
|
||||
themeConfig: {
|
||||
logo: { src: '/lancet_logo_mini.png', width: 24, height: 24 },
|
||||
outline: [2, 3],
|
||||
|
||||
search: {
|
||||
provider: 'local',
|
||||
},
|
||||
socialLinks: [
|
||||
{
|
||||
icon: 'github',
|
||||
link: 'https://github.com/duke-git/lancet',
|
||||
},
|
||||
],
|
||||
|
||||
footer: {
|
||||
copyright: 'Copyright © 2023-present Duke Du',
|
||||
message: '<a href="https://beian.miit.gov.cn/" target="_blank">京ICP备2023022770号-1</a>',
|
||||
},
|
||||
},
|
||||
})
|
||||
14
docs/.vitepress/config.mts
Normal file
14
docs/.vitepress/config.mts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { defineConfig } from 'vitepress'
|
||||
import { commonConfig } from './common'
|
||||
import { zhConfig } from './zh'
|
||||
import { enConfig } from './en'
|
||||
|
||||
// https://vitepress.dev/reference/site-config
|
||||
export default defineConfig({
|
||||
...commonConfig,
|
||||
|
||||
locales: {
|
||||
root: { label: '简体中文', lang: 'zh-CN', link: '/', ...zhConfig },
|
||||
en: { label: 'English', lang: 'en-US', link: '/en/', ...enConfig },
|
||||
},
|
||||
})
|
||||
140
docs/.vitepress/en.ts
Normal file
140
docs/.vitepress/en.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import type { DefaultTheme, LocaleSpecificConfig } from 'vitepress'
|
||||
|
||||
export const META_URL = 'https://www.golancet.cn/en/'
|
||||
export const META_TITLE = 'Lancet'
|
||||
export const META_DESCRIPTION = 'A powerful util function library of Go'
|
||||
|
||||
export const enConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
||||
description: META_DESCRIPTION,
|
||||
|
||||
head: [
|
||||
['meta', { property: 'og:url', content: META_URL }],
|
||||
['meta', { property: 'og:description', content: META_DESCRIPTION }],
|
||||
],
|
||||
|
||||
themeConfig: {
|
||||
editLink: {
|
||||
pattern: 'https://github.com/duke-git/lancet/edit/v2/docs/:path',
|
||||
text: 'Suggest changes to this page',
|
||||
},
|
||||
nav: [
|
||||
{
|
||||
text: 'Home',
|
||||
link: '/en/',
|
||||
activeMatch: '^/en/',
|
||||
},
|
||||
{
|
||||
text: 'Guide',
|
||||
link: '/en/guide/introduction',
|
||||
activeMatch: '^/en/guide/',
|
||||
},
|
||||
{ text: 'API', link: '/en/api/overview', activeMatch: '^/en/api/' },
|
||||
{
|
||||
text: 'Links',
|
||||
items: [
|
||||
{
|
||||
text: 'Discussion',
|
||||
link: 'https://github.com/duke-git/lancet/discussions',
|
||||
},
|
||||
{
|
||||
text: 'Changelog',
|
||||
link: 'https://github.com/duke-git/lancet/releases',
|
||||
},
|
||||
{
|
||||
text: 'Contribution',
|
||||
link: 'https://github.com/duke-git/lancet/blob/main/CONTRIBUTION.md',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
sidebar: {
|
||||
'/en/guide/': [
|
||||
{
|
||||
text: 'Introduction',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{
|
||||
text: 'What is Lancet?',
|
||||
link: '/en/guide/introduction',
|
||||
},
|
||||
{
|
||||
text: 'Getting started',
|
||||
link: '/en/guide/getting_started',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Contribute Code',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{
|
||||
text: 'Contribution guide',
|
||||
link: '/en/guide/contribution_guide',
|
||||
},
|
||||
{
|
||||
text: 'Contributors',
|
||||
link: '/en/guide/contributors',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'API Reference',
|
||||
link: '/en/api/overview'
|
||||
},
|
||||
],
|
||||
'/en/api/': [
|
||||
{
|
||||
text: 'Overview',
|
||||
items: [{ text: 'API overview', link: '/en/api/overview' }],
|
||||
},
|
||||
{
|
||||
text: 'Packages',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: 'algorithm', link: '/en/api/packages/algorithm' },
|
||||
{ text: 'compare', link: '/en/api/packages/compare' },
|
||||
{ text: 'concurrency', link: '/en/api/packages/concurrency' },
|
||||
{ text: 'condition', link: '/en/api/packages/condition' },
|
||||
{ text: 'convertor', link: '/en/api/packages/convertor' },
|
||||
{ text: 'cryptor', link: '/en/api/packages/cryptor' },
|
||||
{
|
||||
text: 'datastructure',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: 'list', link: '/en/api/packages/datastructure/list' },
|
||||
{ text: 'safelist', link: '/en/api/packages/datastructure/copyonwritelist' },
|
||||
{ text: 'link', link: '/en/api/packages/datastructure/link' },
|
||||
{ text: 'stack', link: '/en/api/packages/datastructure/stack' },
|
||||
{ text: 'queue', link: '/en/api/packages/datastructure/queue' },
|
||||
{ text: 'heap', link: '/en/api/packages/datastructure/heap' },
|
||||
{ text: 'tree', link: '/en/api/packages/datastructure/tree' },
|
||||
{ text: 'set', link: '/en/api/packages/datastructure/set' },
|
||||
{ text: 'hashmap', link: '/en/api/packages/datastructure/hashmap' },
|
||||
],
|
||||
},
|
||||
{ text: 'datetime', link: '/en/api/packages/datetime' },
|
||||
{ text: 'eventbus', link: '/en/api/packages/eventbus' },
|
||||
{ text: 'fileutil', link: '/en/api/packages/fileutil' },
|
||||
{ text: 'formatter', link: '/en/api/packages/formatter' },
|
||||
{ text: 'function', link: '/en/api/packages/function' },
|
||||
{ text: 'mathutil', link: '/en/api/packages/mathutil' },
|
||||
{ text: 'maputil', link: '/en/api/packages/maputil' },
|
||||
{ text: 'netutil', link: '/en/api/packages/netutil' },
|
||||
{ text: 'pointer', link: '/en/api/packages/pointer' },
|
||||
{ text: 'random', link: '/en/api/packages/random' },
|
||||
{ text: 'retry', link: '/en/api/packages/retry' },
|
||||
{ text: 'slice', link: '/en/api/packages/slice' },
|
||||
{ text: 'stream', link: '/en/api/packages/stream' },
|
||||
{ text: 'struct', link: '/en/api/packages/struct' },
|
||||
{ text: 'strutil', link: '/en/api/packages/strutil' },
|
||||
{ text: 'system', link: '/en/api/packages/system' },
|
||||
{ text: 'tuple', link: '/en/api/packages/tuple' },
|
||||
{ text: 'validator', link: '/en/api/packages/validator' },
|
||||
{ text: 'xerror', link: '/en/api/packages/xerror' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
153
docs/.vitepress/zh.ts
Normal file
153
docs/.vitepress/zh.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
import type { DefaultTheme, LocaleSpecificConfig } from 'vitepress'
|
||||
|
||||
export const META_URL = 'https://www.golancet.cn'
|
||||
export const META_TITLE = 'Lancet'
|
||||
export const META_DESCRIPTION = '一个强大的Go语言工具函数库'
|
||||
|
||||
export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
||||
description: META_DESCRIPTION,
|
||||
|
||||
head: [
|
||||
['meta', { property: 'og:url', content: META_URL }],
|
||||
['meta', { property: 'og:description', content: META_DESCRIPTION }],
|
||||
],
|
||||
|
||||
themeConfig: {
|
||||
editLink: {
|
||||
pattern: 'https://github.com/duke-git/lancet/edit/v2/docs/:path',
|
||||
text: '对本页提出修改建议',
|
||||
},
|
||||
outline: {
|
||||
label: '本页内容',
|
||||
},
|
||||
|
||||
docFooter: {
|
||||
prev: '上一页',
|
||||
next: '下一页',
|
||||
},
|
||||
|
||||
nav: [
|
||||
{
|
||||
text: '首页',
|
||||
link: '/',
|
||||
activeMatch: '^/',
|
||||
},
|
||||
{
|
||||
text: '指南',
|
||||
link: '/guide/introduction',
|
||||
activeMatch: '^/guide/',
|
||||
},
|
||||
{ text: 'API', link: '/api/overview', activeMatch: '^/api/' },
|
||||
{
|
||||
text: '相关链接',
|
||||
items: [
|
||||
{
|
||||
text: '论坛',
|
||||
link: 'https://github.com/duke-git/lancet/discussions',
|
||||
},
|
||||
{
|
||||
text: '更新日志',
|
||||
link: 'https://github.com/duke-git/lancet/releases',
|
||||
},
|
||||
{
|
||||
text: '参与贡献',
|
||||
link: 'https://github.com/duke-git/lancet/blob/main/CONTRIBUTION.zh-CN.md',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
sidebar: {
|
||||
'/guide/': [
|
||||
{
|
||||
text: '介绍',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{
|
||||
text: 'Lancet是什么?',
|
||||
link: '/guide/introduction',
|
||||
},
|
||||
{
|
||||
text: '开始',
|
||||
link: '/guide/getting_started',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '贡献代码',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{
|
||||
text: '贡献指南',
|
||||
link: '/guide/contribution_guide',
|
||||
},
|
||||
{
|
||||
text: '贡献者',
|
||||
link: '/guide/contributors',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'API手册',
|
||||
link: '/api/overview'
|
||||
},
|
||||
],
|
||||
|
||||
'/api/': [
|
||||
{
|
||||
text: '概览',
|
||||
items: [{ text: 'API概述', link: '/api/overview' }],
|
||||
},
|
||||
{
|
||||
text: 'API文档',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: '算法', link: '/api/packages/algorithm' },
|
||||
{ text: '比较器', link: '/api/packages/compare' },
|
||||
{ text: '并发处理', link: '/api/packages/concurrency' },
|
||||
{ text: '条件判断', link: '/api/packages/condition' },
|
||||
{ text: '类型转换', link: '/api/packages/convertor' },
|
||||
{ text: '加密&解密', link: '/api/packages/cryptor' },
|
||||
{
|
||||
text: '数据结构',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: '线性表', link: '/api/packages/datastructure/list' },
|
||||
{
|
||||
text: '线性表(线程安全)',
|
||||
link: '/api/packages/datastructure/copyonwritelist',
|
||||
},
|
||||
{ text: '链表', link: '/api/packages/datastructure/link' },
|
||||
{ text: '栈', link: '/api/packages/datastructure/stack' },
|
||||
{ text: '队列', link: '/api/packages/datastructure/queue' },
|
||||
{ text: '堆', link: '/api/packages/datastructure/heap' },
|
||||
{ text: '树', link: '/api/packages/datastructure/tree' },
|
||||
{ text: '集合', link: '/api/packages/datastructure/set' },
|
||||
{ text: 'HashMap', link: '/api/packages/datastructure/hashmap' },
|
||||
],
|
||||
},
|
||||
{ text: '日期&时间', link: '/api/packages/datetime' },
|
||||
{ text: '事件总线', link: '/api/packages/eventbus' },
|
||||
{ text: '文件', link: '/api/packages/fileutil' },
|
||||
{ text: '格式化工具', link: '/api/packages/formatter' },
|
||||
{ text: '函数', link: '/api/packages/function' },
|
||||
{ text: '数学工具', link: '/api/packages/mathutil' },
|
||||
{ text: 'Map', link: '/api/packages/maputil' },
|
||||
{ text: '网络', link: '/api/packages/netutil' },
|
||||
{ text: '指针', link: '/api/packages/pointer' },
|
||||
{ text: '随机数', link: '/api/packages/random' },
|
||||
{ text: '重试', link: '/api/packages/retry' },
|
||||
{ text: '切片', link: '/api/packages/slice' },
|
||||
{ text: '流', link: '/api/packages/stream' },
|
||||
{ text: '结构体', link: '/api/packages/struct' },
|
||||
{ text: '字符串', link: '/api/packages/strutil' },
|
||||
{ text: '系统', link: '/api/packages/system' },
|
||||
{ text: '元组', link: '/api/packages/tuple' },
|
||||
{ text: '验证器', link: '/api/packages/validator' },
|
||||
{ text: '错误处理', link: '/api/packages/xerror' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,598 +0,0 @@
|
||||
# Algorithm
|
||||
Package algorithm implements some basic algorithm. eg. sort, search.
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## Source
|
||||
|
||||
- [https://github.com/duke-git/lancet/blob/main/algorithm/sorter.go](https://github.com/duke-git/lancet/blob/main/algorithm/sorter.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/algorithm/search.go](https://github.com/duke-git/lancet/blob/main/algorithm/search.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/algorithm/lru_cache.go](https://github.com/duke-git/lancet/blob/main/algorithm/lru_cache.go)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## Usage
|
||||
```go
|
||||
import (
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
```
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## Index
|
||||
- [Algorithm](#algorithm)
|
||||
- [Source](#source)
|
||||
- [Usage](#usage)
|
||||
- [Index](#index)
|
||||
- [Documentation](#documentation)
|
||||
- [<span id="BubbleSort">BubbleSort</span>](#bubblesort)
|
||||
- [<span id="InsertionSort">InsertionSort</span>](#insertionsort)
|
||||
- [<span id="SelectionSort">SelectionSort</span>](#selectionsort)
|
||||
- [<span id="ShellSort">ShellSort</span>](#shellsort)
|
||||
- [<span id="QuickSort">QuickSort</span>](#quicksort)
|
||||
- [<span id="HeapSort">HeapSort</span>](#heapsort)
|
||||
- [<span id="MergeSort">MergeSort</span>](#mergesort)
|
||||
- [<span id="CountSort">CountSort</span>](#countsort)
|
||||
- [<span id="BinarySearch">BinarySearch</span>](#binarysearch)
|
||||
- [<span id="BinaryIterativeSearch">BinaryIterativeSearch</span>](#binaryiterativesearch)
|
||||
- [<span id="LinearSearch">LinearSearch</span>](#linearsearch)
|
||||
- [<span id="LRUCache">LRUCache</span>](#lrucache)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## Documentation
|
||||
|
||||
|
||||
|
||||
### <span id="BubbleSort">BubbleSort</span>
|
||||
<p>Sort slice with bubble sort algorithm. Param comparator should implements lancetconstraints.Comparator.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func BubbleSort[T any](slice []T, comparator lancetconstraints.Comparator)
|
||||
```
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
val1, _ := v1.(int)
|
||||
val2, _ := v2.(int)
|
||||
|
||||
//ascending order
|
||||
if val1 < val2 {
|
||||
return -1
|
||||
} else if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
intSlice := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
algorithm.BubbleSort(intSlice, comparator)
|
||||
|
||||
fmt.Println(intSlice) //[]int{1, 2, 3, 4, 5, 6}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### <span id="InsertionSort">InsertionSort</span>
|
||||
<p>Sort slice with insertion sort algorithm. Param comparator should implements lancetconstraints.Comparator.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func InsertionSort[T any](slice []T, comparator lancetconstraints.Comparator)
|
||||
```
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type people struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
// PeopleAageComparator sort people slice by age field
|
||||
type peopleAgeComparator struct{}
|
||||
|
||||
// Compare implements github.com/duke-git/lancet/lancetconstraints/constraints.go/Comparator
|
||||
func (pc *peopleAgeComparator) Compare(v1 any, v2 any) int {
|
||||
p1, _ := v1.(people)
|
||||
p2, _ := v2.(people)
|
||||
|
||||
//ascending order
|
||||
if p1.Age < p2.Age {
|
||||
return -1
|
||||
} else if p1.Age > p2.Age {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
|
||||
//decending order
|
||||
// if p1.Age > p2.Age {
|
||||
// return -1
|
||||
// } else if p1.Age < p2.Age {
|
||||
// return 1
|
||||
// }
|
||||
}
|
||||
|
||||
var peoples = []people{
|
||||
{Name: "a", Age: 20},
|
||||
{Name: "b", Age: 10},
|
||||
{Name: "c", Age: 17},
|
||||
{Name: "d", Age: 8},
|
||||
{Name: "e", Age: 28},
|
||||
}
|
||||
comparator := &peopleAgeComparator{}
|
||||
algorithm.InsertionSort(peoples, comparator)
|
||||
|
||||
fmt.Println(peoples) //[{d 8} {b 10} {c 17} {a 20} {e 28}]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### <span id="SelectionSort">SelectionSort</span>
|
||||
<p>Sort slice with selection sort algorithm. Param comparator should implements lancetconstraints.Comparator.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func SelectionSort[T any](slice []T, comparator lancetconstraints.Comparator)
|
||||
```
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
val1, _ := v1.(int)
|
||||
val2, _ := v2.(int)
|
||||
|
||||
//ascending order
|
||||
if val1 < val2 {
|
||||
return -1
|
||||
} else if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
intSlice := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
algorithm.SelectionSort(intSlice, comparator)
|
||||
|
||||
fmt.Println(intSlice) //[]int{1, 2, 3, 4, 5, 6}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### <span id="ShellSort">ShellSort</span>
|
||||
<p>Sort slice with shell sort algorithm. Param comparator should implements lancetconstraints.Comparator.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func ShellSort[T any](slice []T, comparator lancetconstraints.Comparator)
|
||||
```
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
val1, _ := v1.(int)
|
||||
val2, _ := v2.(int)
|
||||
|
||||
//ascending order
|
||||
if val1 < val2 {
|
||||
return -1
|
||||
} else if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
intSlice := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
algorithm.ShellSort(intSlice, comparator)
|
||||
|
||||
fmt.Println(intSlice) //[]int{1, 2, 3, 4, 5, 6}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### <span id="QuickSort">QuickSort</span>
|
||||
<p>Sort slice with quick sort algorithm. Param comparator should implements lancetconstraints.Comparator.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func QuickSort[T any](slice []T, lowIndex, highIndex int, comparator lancetconstraints.Comparator)
|
||||
```
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
val1, _ := v1.(int)
|
||||
val2, _ := v2.(int)
|
||||
|
||||
//ascending order
|
||||
if val1 < val2 {
|
||||
return -1
|
||||
} else if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
intSlice := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
algorithm.QuickSort(intSlice, 0, len(intSlice)-1, comparator)
|
||||
|
||||
fmt.Println(intSlice) //[]int{1, 2, 3, 4, 5, 6}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### <span id="HeapSort">HeapSort</span>
|
||||
<p>Sort slice with heap sort algorithm. Param comparator should implements lancetconstraints.Comparator.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func HeapSort[T any](slice []T, comparator lancetconstraints.Comparator)
|
||||
```
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
val1, _ := v1.(int)
|
||||
val2, _ := v2.(int)
|
||||
|
||||
//ascending order
|
||||
if val1 < val2 {
|
||||
return -1
|
||||
} else if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
intSlice := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
algorithm.HeapSort(intSlice, comparator)
|
||||
|
||||
fmt.Println(intSlice) //[]int{1, 2, 3, 4, 5, 6}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### <span id="MergeSort">MergeSort</span>
|
||||
<p>Sort slice with merge sort algorithm. Param comparator should implements lancetconstraints.Comparator.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func MergeSort[T any](slice []T, comparator lancetconstraints.Comparator)
|
||||
```
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
val1, _ := v1.(int)
|
||||
val2, _ := v2.(int)
|
||||
|
||||
//ascending order
|
||||
if val1 < val2 {
|
||||
return -1
|
||||
} else if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
intSlice := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
algorithm.MergeSort(intSlice, comparator)
|
||||
|
||||
fmt.Println(intSlice) //[]int{1, 2, 3, 4, 5, 6}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### <span id="CountSort">CountSort</span>
|
||||
<p>Sort slice with count sort algorithm. Param comparator should implements lancetconstraints.Comparator.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func CountSort[T any](slice []T, comparator lancetconstraints.Comparator) []T
|
||||
```
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
val1, _ := v1.(int)
|
||||
val2, _ := v2.(int)
|
||||
|
||||
//ascending order
|
||||
if val1 < val2 {
|
||||
return -1
|
||||
} else if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
intSlice := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
sortedSlice := algorithm.CountSort(intSlice, comparator)
|
||||
|
||||
fmt.Println(sortedSlice) //[]int{1, 2, 3, 4, 5, 6}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### <span id="BinarySearch">BinarySearch</span>
|
||||
<p>BinarySearch search for target within a sorted slice, recursive call itself. If a target is found, the index of the target is returned. Else the function return -1.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func BinarySearch[T any](sortedSlice []T, target T, lowIndex, highIndex int, comparator lancetconstraints.Comparator) int
|
||||
```
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
val1, _ := v1.(int)
|
||||
val2, _ := v2.(int)
|
||||
|
||||
//ascending order
|
||||
if val1 < val2 {
|
||||
return -1
|
||||
} else if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
var sortedNumbers = []int{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
comparator := &intComparator{}
|
||||
foundIndex := algorithm.BinarySearch(sortedNumbers, 5, 0, len(sortedNumbers)-1, comparator)
|
||||
fmt.Println(foundIndex) //4
|
||||
|
||||
notFoundIndex := algorithm.BinarySearch(sortedNumbers, 9, 0, len(sortedNumbers)-1, comparator)
|
||||
fmt.Println(notFoundIndex) //-1
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### <span id="BinaryIterativeSearch">BinaryIterativeSearch</span>
|
||||
<p>BinaryIterativeSearch search for target within a sorted slice, recursive call itself. If a target is found, the index of the target is returned. Else the function return -1.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func BinaryIterativeSearch[T any](sortedSlice []T, target T, lowIndex, highIndex int, comparator lancetconstraints.Comparator) int
|
||||
```
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
val1, _ := v1.(int)
|
||||
val2, _ := v2.(int)
|
||||
|
||||
//ascending order
|
||||
if val1 < val2 {
|
||||
return -1
|
||||
} else if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
var sortedNumbers = []int{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
comparator := &intComparator{}
|
||||
foundIndex := algorithm.BinaryIterativeSearch(sortedNumbers, 5, 0, len(sortedNumbers)-1, comparator)
|
||||
fmt.Println(foundIndex) //4
|
||||
|
||||
notFoundIndex := algorithm.BinaryIterativeSearch(sortedNumbers, 9, 0, len(sortedNumbers)-1, comparator)
|
||||
fmt.Println(notFoundIndex) //-1
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### <span id="LinearSearch">LinearSearch</span>
|
||||
<p>LinearSearch Simple linear search algorithm that iterates over all elements of an slice. If a target is found, the index of the target is returned. Else the function return -1.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func LinearSearch[T any](slice []T, target T, comparator lancetconstraints.Comparator) int
|
||||
```
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
val1, _ := v1.(int)
|
||||
val2, _ := v2.(int)
|
||||
|
||||
//ascending order
|
||||
if val1 < val2 {
|
||||
return -1
|
||||
} else if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
intSlice := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
foundIndex := algorithm.LinearSearch(intSlice, 5, comparator)
|
||||
fmt.Println(foundIndex) //2
|
||||
|
||||
notFoundIndex := algorithm.LinearSearch(sortedNumbers, 0, comparator)
|
||||
fmt.Println(notFoundIndex) //-1
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### <span id="LRUCache">LRUCache</span>
|
||||
<p>LRUCache implements mem cache with lru.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func NewLRUCache[K comparable, V any](capacity int) *LRUCache[K, V]
|
||||
func (l *LRUCache[K, V]) Get(key K) (V, bool)
|
||||
func (l *LRUCache[K, V]) Put(key K, value V)
|
||||
```
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cache := algorithm.NewLRUCache[int, int](2)
|
||||
|
||||
cache.Put(1, 1)
|
||||
cache.Put(2, 2)
|
||||
|
||||
_, ok := cache.Get(0) // ok -> false
|
||||
|
||||
v, ok := cache.Get(1) // v->1, ok->true
|
||||
|
||||
}
|
||||
```
|
||||
@@ -1,598 +0,0 @@
|
||||
# Algorithm
|
||||
algorithm算法包实现一些基本算法,sort,search,lrucache。
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## 源码
|
||||
|
||||
- [https://github.com/duke-git/lancet/blob/main/algorithm/sorter.go](https://github.com/duke-git/lancet/blob/main/algorithm/sorter.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/algorithm/search.go](https://github.com/duke-git/lancet/blob/main/algorithm/search.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/algorithm/lru_cache.go](https://github.com/duke-git/lancet/blob/main/algorithm/lru_cache.go)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## 用法
|
||||
```go
|
||||
import (
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
```
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## 目录
|
||||
- [Algorithm](#algorithm)
|
||||
- [源码](#源码)
|
||||
- [用法](#用法)
|
||||
- [目录](#目录)
|
||||
- [文档](#文档)
|
||||
- [<span id="BubbleSort">BubbleSort</span>](#bubblesort)
|
||||
- [<span id="InsertionSort">InsertionSort</span>](#insertionsort)
|
||||
- [<span id="SelectionSort">SelectionSort</span>](#selectionsort)
|
||||
- [<span id="ShellSort">ShellSort</span>](#shellsort)
|
||||
- [<span id="QuickSort">QuickSort</span>](#quicksort)
|
||||
- [<span id="HeapSort">HeapSort</span>](#heapsort)
|
||||
- [<span id="MergeSort">MergeSort</span>](#mergesort)
|
||||
- [<span id="CountSort">CountSort</span>](#countsort)
|
||||
- [<span id="BinarySearch">BinarySearch</span>](#binarysearch)
|
||||
- [<span id="BinaryIterativeSearch">BinaryIterativeSearch</span>](#binaryiterativesearch)
|
||||
- [<span id="LinearSearch">LinearSearch</span>](#linearsearch)
|
||||
- [<span id="LRUCache">LRUCache</span>](#lrucache)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## 文档
|
||||
|
||||
|
||||
|
||||
### <span id="BubbleSort">BubbleSort</span>
|
||||
<p>冒泡排序,参数comparator需要实现包lancetconstraints.Comparator</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func BubbleSort[T any](slice []T, comparator lancetconstraints.Comparator)
|
||||
```
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
val1, _ := v1.(int)
|
||||
val2, _ := v2.(int)
|
||||
|
||||
//ascending order
|
||||
if val1 < val2 {
|
||||
return -1
|
||||
} else if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
intSlice := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
algorithm.BubbleSort(intSlice, comparator)
|
||||
|
||||
fmt.Println(intSlice) //[]int{1, 2, 3, 4, 5, 6}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### <span id="InsertionSort">InsertionSort</span>
|
||||
<p>插入排序,参数comparator需要实现包lancetconstraints.Comparator</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func InsertionSort[T any](slice []T, comparator lancetconstraints.Comparator)
|
||||
```
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type people struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
// PeopleAageComparator sort people slice by age field
|
||||
type peopleAgeComparator struct{}
|
||||
|
||||
// Compare implements github.com/duke-git/lancet/lancetconstraints/constraints.go/Comparator
|
||||
func (pc *peopleAgeComparator) Compare(v1 any, v2 any) int {
|
||||
p1, _ := v1.(people)
|
||||
p2, _ := v2.(people)
|
||||
|
||||
//ascending order
|
||||
if p1.Age < p2.Age {
|
||||
return -1
|
||||
} else if p1.Age > p2.Age {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
|
||||
//decending order
|
||||
// if p1.Age > p2.Age {
|
||||
// return -1
|
||||
// } else if p1.Age < p2.Age {
|
||||
// return 1
|
||||
// }
|
||||
}
|
||||
|
||||
var peoples = []people{
|
||||
{Name: "a", Age: 20},
|
||||
{Name: "b", Age: 10},
|
||||
{Name: "c", Age: 17},
|
||||
{Name: "d", Age: 8},
|
||||
{Name: "e", Age: 28},
|
||||
}
|
||||
comparator := &peopleAgeComparator{}
|
||||
algorithm.InsertionSort(peoples, comparator)
|
||||
|
||||
fmt.Println(intSlice) //[{d 8} {b 10} {c 17} {a 20} {e 28}]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### <span id="SelectionSort">SelectionSort</span>
|
||||
<p>选择排序,参数comparator需要实现包lancetconstraints.Comparator</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func SelectionSort[T any](slice []T, comparator lancetconstraints.Comparator)
|
||||
```
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
val1, _ := v1.(int)
|
||||
val2, _ := v2.(int)
|
||||
|
||||
//ascending order
|
||||
if val1 < val2 {
|
||||
return -1
|
||||
} else if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
intSlice := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
algorithm.SelectionSort(intSlice, comparator)
|
||||
|
||||
fmt.Println(intSlice) //[]int{1, 2, 3, 4, 5, 6}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### <span id="ShellSort">ShellSort</span>
|
||||
<p>希尔排序,参数comparator需要实现包lancetconstraints.Comparator</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func ShellSort[T any](slice []T, comparator lancetconstraints.Comparator)
|
||||
```
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
val1, _ := v1.(int)
|
||||
val2, _ := v2.(int)
|
||||
|
||||
//ascending order
|
||||
if val1 < val2 {
|
||||
return -1
|
||||
} else if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
intSlice := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
algorithm.ShellSort(intSlice, comparator)
|
||||
|
||||
fmt.Println(intSlice) //[]int{1, 2, 3, 4, 5, 6}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### <span id="QuickSort">QuickSort</span>
|
||||
<p>快速排序,参数comparator需要实现包lancetconstraints.Comparator</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func QuickSort[T any](slice []T, lowIndex, highIndex int, comparator lancetconstraints.Comparator) []T
|
||||
```
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
val1, _ := v1.(int)
|
||||
val2, _ := v2.(int)
|
||||
|
||||
//ascending order
|
||||
if val1 < val2 {
|
||||
return -1
|
||||
} else if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
intSlice := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
algorithm.QuickSort(intSlice, 0, len(intSlice)-1, comparator)
|
||||
|
||||
fmt.Println(intSlice) //[]int{1, 2, 3, 4, 5, 6}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### <span id="HeapSort">HeapSort</span>
|
||||
<p>堆排序,参数comparator需要实现包lancetconstraints.Comparator</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func HeapSort[T any](slice []T, comparator lancetconstraints.Comparator) []T
|
||||
```
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
val1, _ := v1.(int)
|
||||
val2, _ := v2.(int)
|
||||
|
||||
//ascending order
|
||||
if val1 < val2 {
|
||||
return -1
|
||||
} else if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
intSlice := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
algorithm.HeapSort(intSlice, comparator)
|
||||
|
||||
fmt.Println(intSlice) //[]int{1, 2, 3, 4, 5, 6}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### <span id="MergeSort">MergeSort</span>
|
||||
<p>归并排序,参数comparator需要实现包lancetconstraints.Comparator</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func MergeSort[T any](slice []T, comparator lancetconstraints.Comparator)
|
||||
```
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
val1, _ := v1.(int)
|
||||
val2, _ := v2.(int)
|
||||
|
||||
//ascending order
|
||||
if val1 < val2 {
|
||||
return -1
|
||||
} else if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
intSlice := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
algorithm.MergeSort(intSlice, comparator)
|
||||
|
||||
fmt.Println(intSlice) //[]int{1, 2, 3, 4, 5, 6}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### <span id="CountSort">CountSort</span>
|
||||
<p>计数排序,参数comparator需要实现包lancetconstraints.Comparator</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func CountSort[T any](slice []T, comparator lancetconstraints.Comparator) []T
|
||||
```
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
val1, _ := v1.(int)
|
||||
val2, _ := v2.(int)
|
||||
|
||||
//ascending order
|
||||
if val1 < val2 {
|
||||
return -1
|
||||
} else if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
intSlice := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
sortedSlice := algorithm.CountSort(intSlice, comparator)
|
||||
|
||||
fmt.Println(sortedSlice) //[]int{1, 2, 3, 4, 5, 6}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### <span id="BinarySearch">BinarySearch</span>
|
||||
<p>二分递归查找,返回元素索引,未找到元素返回-1,参数comparator需要实现包lancetconstraints.Comparator</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func BinarySearch[T any](sortedSlice []T, target T, lowIndex, highIndex int, comparator lancetconstraints.Comparator) int
|
||||
```
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
val1, _ := v1.(int)
|
||||
val2, _ := v2.(int)
|
||||
|
||||
//ascending order
|
||||
if val1 < val2 {
|
||||
return -1
|
||||
} else if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
var sortedNumbers = []int{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
comparator := &intComparator{}
|
||||
foundIndex := algorithm.BinarySearch(sortedNumbers, 5, 0, len(sortedNumbers)-1, comparator)
|
||||
fmt.Println(foundIndex) //4
|
||||
|
||||
notFoundIndex := algorithm.BinarySearch(sortedNumbers, 9, 0, len(sortedNumbers)-1, comparator)
|
||||
fmt.Println(notFoundIndex) //-1
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### <span id="BinaryIterativeSearch">BinaryIterativeSearch</span>
|
||||
<p>二分迭代查找,返回元素索引,未找到元素返回-1,参数comparator需要实现包lancetconstraints.Comparator</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func BinaryIterativeSearch[T any](sortedSlice []T, target T, lowIndex, highIndex int, comparator lancetconstraints.Comparator) int
|
||||
```
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
val1, _ := v1.(int)
|
||||
val2, _ := v2.(int)
|
||||
|
||||
//ascending order
|
||||
if val1 < val2 {
|
||||
return -1
|
||||
} else if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
var sortedNumbers = []int{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
comparator := &intComparator{}
|
||||
foundIndex := algorithm.BinaryIterativeSearch(sortedNumbers, 5, 0, len(sortedNumbers)-1, comparator)
|
||||
fmt.Println(foundIndex) //4
|
||||
|
||||
notFoundIndex := algorithm.BinaryIterativeSearch(sortedNumbers, 9, 0, len(sortedNumbers)-1, comparator)
|
||||
fmt.Println(notFoundIndex) //-1
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### <span id="LinearSearch">LinearSearch</span>
|
||||
<p>线性查找,返回元素索引,未找到元素返回-1,参数comparator需要实现包lancetconstraints.Comparator</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func LinearSearch[T any](slice []T, target T, comparator lancetconstraints.Comparator) int
|
||||
```
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
val1, _ := v1.(int)
|
||||
val2, _ := v2.(int)
|
||||
|
||||
//ascending order
|
||||
if val1 < val2 {
|
||||
return -1
|
||||
} else if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
intSlice := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
foundIndex := algorithm.LinearSearch(intSlice, 5, comparator)
|
||||
fmt.Println(foundIndex) //2
|
||||
|
||||
notFoundIndex := algorithm.LinearSearch(sortedNumbers, 0, comparator)
|
||||
fmt.Println(notFoundIndex) //-1
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### <span id="LRUCache">LRUCache</span>
|
||||
<p>lru实现缓存</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func NewLRUCache[K comparable, V any](capacity int) *LRUCache[K, V]
|
||||
func (l *LRUCache[K, V]) Get(key K) (V, bool)
|
||||
func (l *LRUCache[K, V]) Put(key K, value V)
|
||||
```
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cache := algorithm.NewLRUCache[int, int](2)
|
||||
|
||||
cache.Put(1, 1)
|
||||
cache.Put(2, 2)
|
||||
|
||||
_, ok := cache.Get(0) // ok -> false
|
||||
|
||||
v, ok := cache.Get(1) // v->1, ok->true
|
||||
|
||||
}
|
||||
```
|
||||
69
docs/api/overview.md
Normal file
69
docs/api/overview.md
Normal file
@@ -0,0 +1,69 @@
|
||||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# API 概述
|
||||
|
||||
<b>lancet(柳叶刀)是一个功能强大、全面、高效、可复用的 go 语言工具函数库。包含 25 个包,超过 600 个工具函数。功能涵盖字符串处理、切片处理、网络、并发、加解密、文件处理、时间/日期、流处理、迭代器等等。</b>
|
||||
|
||||
<style>
|
||||
.package-title {
|
||||
color: black;
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
.package-container {
|
||||
font-size: 16px;
|
||||
border: 1px dashed;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.package-cell {
|
||||
height: 40px;
|
||||
width: 140px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
line-height: 40px;
|
||||
background: #6cadf5;
|
||||
border: 1px solid;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 6px;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div>
|
||||
<p class="package-title">lancet功能模块</p>
|
||||
<div class="package-container">
|
||||
<div class="package-cell">algorithm</div>
|
||||
<div class="package-cell">compare</div>
|
||||
<div class="package-cell">concurrency</div>
|
||||
<div class="package-cell">condition</div>
|
||||
<div class="package-cell">convertor</div>
|
||||
<div class="package-cell">cryptor</div>
|
||||
<div class="package-cell">datastructure</div>
|
||||
<div class="package-cell">datetime</div>
|
||||
<div class="package-cell">eventbus</div>
|
||||
<div class="package-cell">fileutil</div>
|
||||
<div class="package-cell">formatter</div>
|
||||
<div class="package-cell">function</div>
|
||||
<div class="package-cell">iterator</div>
|
||||
<div class="package-cell">maputil</div>
|
||||
<div class="package-cell">mathutil</div>
|
||||
<div class="package-cell">netutil</div>
|
||||
<div class="package-cell">pointer</div>
|
||||
<div class="package-cell">random</div>
|
||||
<div class="package-cell">retry</div>
|
||||
<div class="package-cell">slice</div>
|
||||
<div class="package-cell">stream</div>
|
||||
<div class="package-cell">structs</div>
|
||||
<div class="package-cell">strutil</div>
|
||||
<div class="package-cell">system</div>
|
||||
<div class="package-cell">tuple</div>
|
||||
<div class="package-cell">validator</div>
|
||||
<div class="package-cell">xerror</div>
|
||||
</div>
|
||||
</div>
|
||||
636
docs/api/packages/algorithm.md
Normal file
636
docs/api/packages/algorithm.md
Normal file
@@ -0,0 +1,636 @@
|
||||
# Algorithm
|
||||
|
||||
algorithm 算法包实现一些基本算法,sort,search,lrucache。
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## 源码
|
||||
|
||||
- [https://github.com/duke-git/lancet/blob/main/algorithm/sort.go](https://github.com/duke-git/lancet/blob/main/algorithm/sort.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/algorithm/search.go](https://github.com/duke-git/lancet/blob/main/algorithm/search.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/algorithm/lrucache.go](https://github.com/duke-git/lancet/blob/main/algorithm/lrucache.go)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## 用法
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
```
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## 目录
|
||||
|
||||
- [BubbleSort](#BubbleSort)
|
||||
- [InsertionSort](#InsertionSort)
|
||||
- [SelectionSort](#SelectionSort)
|
||||
- [ShellSort](#ShellSort)
|
||||
- [QuickSort](#QuickSort)
|
||||
- [HeapSort](#HeapSort)
|
||||
- [MergeSort](#MergeSort)
|
||||
- [CountSort](#CountSort)
|
||||
- [BinarySearch](#BinarySearch)
|
||||
- [BinaryIterativeSearch](#BinaryIterativeSearch)
|
||||
- [LinearSearch](#LinearSearch)
|
||||
- [LRUCache](#LRUCache)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## 文档
|
||||
|
||||
### <span id="BubbleSort">BubbleSort</span>
|
||||
|
||||
<p>冒泡排序,参数comparator需要实现包constraints.Comparator。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func BubbleSort[T any](slice []T, comparator constraints.Comparator)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/GNdv7Jg2Taj)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
val1, _ := v1.(int)
|
||||
val2, _ := v2.(int)
|
||||
|
||||
//ascending order
|
||||
if val1 < val2 {
|
||||
return -1
|
||||
} else if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func main() {
|
||||
numbers := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
|
||||
algorithm.BubbleSort(numbers, comparator)
|
||||
|
||||
fmt.Println(numbers)
|
||||
|
||||
// Output:
|
||||
// [1 2 3 4 5 6]
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="InsertionSort">InsertionSort</span>
|
||||
|
||||
<p>插入排序,参数comparator需要实现包constraints.Comparator。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func InsertionSort[T any](slice []T, comparator constraints.Comparator)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/G5LJiWgJJW6)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
type people struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
// PeopleAageComparator sort people slice by age field
|
||||
type peopleAgeComparator struct{}
|
||||
|
||||
// Compare implements github.com/duke-git/lancet/constraints/constraints.go/Comparator
|
||||
func (pc *peopleAgeComparator) Compare(v1 any, v2 any) int {
|
||||
p1, _ := v1.(people)
|
||||
p2, _ := v2.(people)
|
||||
|
||||
//ascending order
|
||||
if p1.Age < p2.Age {
|
||||
return -1
|
||||
} else if p1.Age > p2.Age {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func main() {
|
||||
peoples := []people{
|
||||
{Name: "a", Age: 20},
|
||||
{Name: "b", Age: 10},
|
||||
{Name: "c", Age: 17},
|
||||
{Name: "d", Age: 8},
|
||||
{Name: "e", Age: 28},
|
||||
}
|
||||
|
||||
comparator := &peopleAgeComparator{}
|
||||
|
||||
algorithm.InsertionSort(peoples, comparator)
|
||||
|
||||
fmt.Println(peoples)
|
||||
|
||||
// Output:
|
||||
// [{d 8} {b 10} {c 17} {a 20} {e 28}]
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="SelectionSort">SelectionSort</span>
|
||||
|
||||
<p>选择排序,参数comparator需要实现包constraints.Comparator。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func SelectionSort[T any](slice []T, comparator constraints.Comparator)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/oXovbkekayS)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
val1, _ := v1.(int)
|
||||
val2, _ := v2.(int)
|
||||
|
||||
//ascending order
|
||||
if val1 < val2 {
|
||||
return -1
|
||||
} else if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func main() {
|
||||
numbers := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
|
||||
algorithm.SelectionSort(numbers, comparator)
|
||||
|
||||
fmt.Println(numbers)
|
||||
|
||||
// Output:
|
||||
// [1 2 3 4 5 6]
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="ShellSort">ShellSort</span>
|
||||
|
||||
<p>希尔排序,参数comparator需要实现包constraints.Comparator。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func ShellSort[T any](slice []T, comparator constraints.Comparator)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/3ibkszpJEu3)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
val1, _ := v1.(int)
|
||||
val2, _ := v2.(int)
|
||||
|
||||
//ascending order
|
||||
if val1 < val2 {
|
||||
return -1
|
||||
} else if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func main() {
|
||||
numbers := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
|
||||
algorithm.ShellSort(numbers, comparator)
|
||||
|
||||
fmt.Println(numbers)
|
||||
|
||||
// Output:
|
||||
// [1 2 3 4 5 6]
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="QuickSort">QuickSort</span>
|
||||
|
||||
<p>快速排序,参数comparator需要实现包constraints.Comparator。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func QuickSort[T any](slice []T comparator constraints.Comparator)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/7Y7c1Elk3ax)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
val1, _ := v1.(int)
|
||||
val2, _ := v2.(int)
|
||||
|
||||
//ascending order
|
||||
if val1 < val2 {
|
||||
return -1
|
||||
} else if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func main() {
|
||||
numbers := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
|
||||
algorithm.QuickSort(numbers, comparator)
|
||||
|
||||
fmt.Println(numbers)
|
||||
|
||||
// Output:
|
||||
// [1 2 3 4 5 6]
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="HeapSort">HeapSort</span>
|
||||
|
||||
<p>堆排序,参数comparator需要实现包constraints.Comparator。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func HeapSort[T any](slice []T, comparator constraints.Comparator)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/u6Iwa1VZS_f)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
val1, _ := v1.(int)
|
||||
val2, _ := v2.(int)
|
||||
|
||||
//ascending order
|
||||
if val1 < val2 {
|
||||
return -1
|
||||
} else if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func main() {
|
||||
numbers := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
|
||||
algorithm.HeapSort(numbers, comparator)
|
||||
|
||||
fmt.Println(numbers)
|
||||
|
||||
// Output:
|
||||
// [1 2 3 4 5 6]
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="MergeSort">MergeSort</span>
|
||||
|
||||
<p>归并排序,参数comparator需要实现包constraints.Comparator。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func MergeSort[T any](slice []T, comparator constraints.Comparator)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/ydinn9YzUJn)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
val1, _ := v1.(int)
|
||||
val2, _ := v2.(int)
|
||||
|
||||
//ascending order
|
||||
if val1 < val2 {
|
||||
return -1
|
||||
} else if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func main() {
|
||||
numbers := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
|
||||
algorithm.MergeSort(numbers, comparator)
|
||||
|
||||
fmt.Println(numbers)
|
||||
|
||||
// Output:
|
||||
// [1 2 3 4 5 6]
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="CountSort">CountSort</span>
|
||||
|
||||
<p>计数排序,参数comparator需要实现包constraints.Comparator。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func CountSort[T any](slice []T, comparator constraints.Comparator) []T
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/tB-Umgm0DrP)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
val1, _ := v1.(int)
|
||||
val2, _ := v2.(int)
|
||||
|
||||
//ascending order
|
||||
if val1 < val2 {
|
||||
return -1
|
||||
} else if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func main() {
|
||||
numbers := []int{2, 1, 5, 3, 6, 4}
|
||||
comparator := &intComparator{}
|
||||
|
||||
sortedNums := algorithm.CountSort(numbers, comparator)
|
||||
|
||||
fmt.Println(sortedNums)
|
||||
|
||||
// Output:
|
||||
// [1 2 3 4 5 6]
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="BinarySearch">BinarySearch</span>
|
||||
|
||||
<p>二分递归查找,返回元素索引,未找到元素返回-1,参数comparator需要实现包constraints.Comparator。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func BinarySearch[T any](sortedSlice []T, target T, lowIndex, highIndex int, comparator constraints.Comparator) int
|
||||
```
|
||||
|
||||
<b>示例: <span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/t6MeGiUSN47)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
val1, _ := v1.(int)
|
||||
val2, _ := v2.(int)
|
||||
|
||||
//ascending order
|
||||
if val1 < val2 {
|
||||
return -1
|
||||
} else if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func main() {
|
||||
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
comparator := &intComparator{}
|
||||
|
||||
result1 := algorithm.BinarySearch(numbers, 5, 0, len(numbers)-1, comparator)
|
||||
result2 := algorithm.BinarySearch(numbers, 9, 0, len(numbers)-1, comparator)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
|
||||
// Output:
|
||||
// 4
|
||||
// -1
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="BinaryIterativeSearch">BinaryIterativeSearch</span>
|
||||
|
||||
<p>二分迭代查找,返回元素索引,未找到元素返回-1,参数comparator需要实现包constraints.Comparator。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func BinaryIterativeSearch[T any](sortedSlice []T, target T, lowIndex, highIndex int, comparator constraints.Comparator) int
|
||||
```
|
||||
|
||||
<b>示例: <span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/Anozfr8ZLH3)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
type intComparator struct{}
|
||||
|
||||
func (c *intComparator) Compare(v1 any, v2 any) int {
|
||||
val1, _ := v1.(int)
|
||||
val2, _ := v2.(int)
|
||||
|
||||
//ascending order
|
||||
if val1 < val2 {
|
||||
return -1
|
||||
} else if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func main() {
|
||||
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
comparator := &intComparator{}
|
||||
|
||||
result1 := algorithm.BinaryIterativeSearch(numbers, 5, 0, len(numbers)-1, comparator)
|
||||
result2 := algorithm.BinaryIterativeSearch(numbers, 9, 0, len(numbers)-1, comparator)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
|
||||
// Output:
|
||||
// 4
|
||||
// -1
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="LinearSearch">LinearSearch</span>
|
||||
|
||||
<p>基于传入的相等函数线性查找元素,返回元素索引,未找到元素返回-1。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func LinearSearch[T any](slice []T, target T, equal func(a, b T) bool) int
|
||||
```
|
||||
|
||||
<b>示例: <span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/IsS7rgn5s3x)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
numbers := []int{3, 4, 5, 3, 2, 1}
|
||||
|
||||
equalFunc := func(a, b int) bool {
|
||||
return a == b
|
||||
}
|
||||
|
||||
result1 := algorithm.LinearSearch(numbers, 3, equalFunc)
|
||||
result2 := algorithm.LinearSearch(numbers, 6, equalFunc)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
|
||||
// Output:
|
||||
// 0
|
||||
// -1
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="LRUCache">LRUCache</span>
|
||||
|
||||
<p>lru算法实现缓存。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func NewLRUCache[K comparable, V any](capacity int) *LRUCache[K, V]
|
||||
func (l *LRUCache[K, V]) Get(key K) (V, bool)
|
||||
func (l *LRUCache[K, V]) Put(key K, value V)
|
||||
func (l *LRUCache[K, V]) Delete(key K) bool
|
||||
func (l *LRUCache[K, V]) Len() int
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/-EZjgOURufP)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/algorithm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cache := algorithm.NewLRUCache[int, int](2)
|
||||
|
||||
cache.Put(1, 1)
|
||||
cache.Put(2, 2)
|
||||
|
||||
result1, ok1 := cache.Get(1)
|
||||
result2, ok2 := cache.Get(2)
|
||||
result3, ok3 := cache.Get(3)
|
||||
|
||||
fmt.Println(result1, ok1)
|
||||
fmt.Println(result2, ok2)
|
||||
fmt.Println(result3, ok3)
|
||||
|
||||
fmt.Println(cache.Len())
|
||||
|
||||
ok := cache.Delete(2)
|
||||
fmt.Println(ok)
|
||||
|
||||
|
||||
// Output:
|
||||
// 1 true
|
||||
// 2 true
|
||||
// 0 false
|
||||
// 2
|
||||
// true
|
||||
}
|
||||
```
|
||||
375
docs/api/packages/compare.md
Normal file
375
docs/api/packages/compare.md
Normal file
@@ -0,0 +1,375 @@
|
||||
# Compare
|
||||
|
||||
compare包提供几个轻量级的类型比较函数。
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## 源码:
|
||||
|
||||
- [https://github.com/duke-git/lancet/blob/main/compare/compare.go](https://github.com/duke-git/lancet/blob/main/compare/compare.go)
|
||||
|
||||
- [https://github.com/duke-git/lancet/blob/main/compare/compare_internal.go](https://github.com/duke-git/lancet/blob/main/compare/compare_internal.go)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## 用法:
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/duke-git/lancet/v2/condition"
|
||||
)
|
||||
```
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## 目录
|
||||
|
||||
- [Equal](#Equal)
|
||||
- [EqualValue](#EqualValue)
|
||||
- [LessThan](#LessThan)
|
||||
- [GreaterThan](#GreaterThan)
|
||||
- [LessOrEqual](#LessOrEqual)
|
||||
- [GreaterOrEqual](#GreaterOrEqual)
|
||||
- [InDelta](#InDelta)
|
||||
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## 文档
|
||||
|
||||
### <span id="Equal">Equal</span>
|
||||
|
||||
<p>检查两个值是否相等(检查类型和值)</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func Equal(left, right any) bool
|
||||
```
|
||||
|
||||
<b>示例: <span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/wmVxR-to4lz)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/compare"
|
||||
)
|
||||
|
||||
func main() {
|
||||
result1 := compare.Equal(1, 1)
|
||||
result2 := compare.Equal("1", "1")
|
||||
result3 := compare.Equal([]int{1, 2, 3}, []int{1, 2, 3})
|
||||
result4 := compare.Equal(map[int]string{1: "a", 2: "b"}, map[int]string{1: "a", 2: "b"})
|
||||
|
||||
result5 := compare.Equal(1, "1")
|
||||
result6 := compare.Equal(1, int64(1))
|
||||
result7 := compare.Equal([]int{1, 2}, []int{1, 2, 3})
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
fmt.Println(result5)
|
||||
fmt.Println(result6)
|
||||
fmt.Println(result7)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// false
|
||||
// false
|
||||
// false
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="EqualValue">EqualValue</span>
|
||||
|
||||
<p>检查两个值是否相等(只检查值)</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func EqualValue(left, right any) bool
|
||||
```
|
||||
|
||||
<b>示例: <span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/fxnna_LLD9u)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/compare"
|
||||
)
|
||||
|
||||
func main() {
|
||||
result1 := compare.EqualValue(1, 1)
|
||||
result2 := compare.EqualValue(int(1), int64(1))
|
||||
result3 := compare.EqualValue(1, "1")
|
||||
result4 := compare.EqualValue(1, "2")
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// false
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="LessThan">LessThan</span>
|
||||
|
||||
<p>验证参数`left`的值是否小于参数`right`的值。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func LessThan(left, right any) bool
|
||||
```
|
||||
|
||||
<b>示例: <span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/cYh7FQQj0ne)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/compare"
|
||||
)
|
||||
|
||||
func main() {
|
||||
result1 := compare.LessThan(1, 2)
|
||||
result2 := compare.LessThan(1.1, 2.2)
|
||||
result3 := compare.LessThan("a", "b")
|
||||
|
||||
time1 := time.Now()
|
||||
time2 := time1.Add(time.Second)
|
||||
result4 := compare.LessThan(time1, time2)
|
||||
|
||||
result5 := compare.LessThan(2, 1)
|
||||
result6 := compare.LessThan(1, int64(2))
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
fmt.Println(result5)
|
||||
fmt.Println(result6)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// false
|
||||
// false
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="GreaterThan">GreaterThan</span>
|
||||
|
||||
<p>验证参数`left`的值是否大于参数`right`的值。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func GreaterThan(left, right any) bool
|
||||
```
|
||||
|
||||
<b>示例: <span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/9-NYDFZmIMp)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/compare"
|
||||
)
|
||||
|
||||
func main() {
|
||||
result1 := compare.GreaterThan(2, 1)
|
||||
result2 := compare.GreaterThan(2.2, 1.1)
|
||||
result3 := compare.GreaterThan("b", "a")
|
||||
|
||||
time1 := time.Now()
|
||||
time2 := time1.Add(time.Second)
|
||||
result4 := compare.GreaterThan(time2, time1)
|
||||
|
||||
result5 := compare.GreaterThan(1, 2)
|
||||
result6 := compare.GreaterThan(int64(2), 1)
|
||||
result7 := compare.GreaterThan("b", "c")
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
fmt.Println(result5)
|
||||
fmt.Println(result6)
|
||||
fmt.Println(result7)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// false
|
||||
// false
|
||||
// false
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="LessOrEqual">LessOrEqual</span>
|
||||
|
||||
<p>验证参数`left`的值是否小于或等于参数`right`的值。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func LessOrEqual(left, right any) bool
|
||||
```
|
||||
|
||||
<b>示例: <span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/e4T_scwoQzp)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/compare"
|
||||
)
|
||||
|
||||
func main() {
|
||||
result1 := compare.LessOrEqual(1, 1)
|
||||
result2 := compare.LessOrEqual(1.1, 2.2)
|
||||
result3 := compare.LessOrEqual("a", "b")
|
||||
|
||||
time1 := time.Now()
|
||||
time2 := time1.Add(time.Second)
|
||||
result4 := compare.LessOrEqual(time1, time2)
|
||||
|
||||
result5 := compare.LessOrEqual(2, 1)
|
||||
result6 := compare.LessOrEqual(1, int64(2))
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
fmt.Println(result5)
|
||||
fmt.Println(result6)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// false
|
||||
// false
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="GreaterOrEqual">GreaterOrEqual</span>
|
||||
|
||||
<p>验证参数`left`的值是否大于或参数`right`的值。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func GreaterOrEqual(left, right any) bool
|
||||
```
|
||||
|
||||
<b>示例: <span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/vx8mP0U8DFk)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/compare"
|
||||
)
|
||||
|
||||
func main() {
|
||||
result1 := compare.GreaterOrEqual(1, 1)
|
||||
result2 := compare.GreaterOrEqual(2.2, 1.1)
|
||||
result3 := compare.GreaterOrEqual("b", "b")
|
||||
|
||||
time1 := time.Now()
|
||||
time2 := time1.Add(time.Second)
|
||||
result4 := compare.GreaterOrEqual(time2, time1)
|
||||
|
||||
result5 := compare.GreaterOrEqual(1, 2)
|
||||
result6 := compare.GreaterOrEqual(int64(2), 1)
|
||||
result7 := compare.GreaterOrEqual("b", "c")
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
fmt.Println(result5)
|
||||
fmt.Println(result6)
|
||||
fmt.Println(result7)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// false
|
||||
// false
|
||||
// false
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="InDelta">InDelta</span>
|
||||
|
||||
<p>检查增量内两个值是否相等。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func InDelta[T constraints.Integer | constraints.Float](left, right T, delta float64) bool
|
||||
```
|
||||
|
||||
<b>示例: <span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/TuDdcNtMkjo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/compare"
|
||||
)
|
||||
|
||||
func main() {
|
||||
result1 := InDelta(1, 1, 0)
|
||||
result2 := InDelta(1, 2, 0)
|
||||
|
||||
result3 := InDelta(2.0/3.0, 0.66667, 0.001)
|
||||
result4 := InDelta(2.0/3.0, 0.0, 0.001)
|
||||
|
||||
result5 := InDelta(float64(74.96)-float64(20.48), 54.48, 0)
|
||||
result6 := InDelta(float64(74.96)-float64(20.48), 54.48, 1e-14)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
fmt.Println(result5)
|
||||
fmt.Println(result6)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
// true
|
||||
// false
|
||||
// false
|
||||
// true
|
||||
}
|
||||
```
|
||||
852
docs/api/packages/concurrency.md
Normal file
852
docs/api/packages/concurrency.md
Normal file
@@ -0,0 +1,852 @@
|
||||
# Concurrency
|
||||
|
||||
并发包包含一些支持并发编程的功能。例如:goroutine, channel 等。
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## 源码:
|
||||
|
||||
- [https://github.com/duke-git/lancet/blob/main/concurrency/channel.go](https://github.com/duke-git/lancet/blob/main/concurrency/channel.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/concurrency/keyed_locker.go](https://github.com/duke-git/lancet/blob/main/concurrency/keyed_locker.go)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## 用法:
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
```
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## 目录
|
||||
|
||||
### Channel
|
||||
|
||||
- [NewChannel](#NewChannel)
|
||||
- [Bridge](#Bridge)
|
||||
- [FanIn](#FanIn)
|
||||
- [Generate](#Generate)
|
||||
- [Or](#Or)
|
||||
- [OrDone](#OrDone)
|
||||
- [Repeat](#Repeat)
|
||||
- [RepeatFn](#RepeatFn)
|
||||
- [Take](#Take)
|
||||
- [Tee](#Tee)
|
||||
|
||||
### KeyedLocker
|
||||
|
||||
- [NewKeyedLocker](#NewKeyedLocker)
|
||||
- [KeyedLocker_Do](#Do)
|
||||
- [NewRWKeyedLocker](#NewRWKeyedLocker)
|
||||
- [RLock](#RLock)
|
||||
- [Lock](#Lock)
|
||||
- [NewTryKeyedLocker](#NewTryKeyedLocker)
|
||||
- [TryLock](#TryLock)
|
||||
- [Unlock](#Unlock)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## 文档
|
||||
|
||||
### Channel
|
||||
|
||||
### <span id="NewChannel">NewChannel</span>
|
||||
|
||||
<p>返回一个Channel指针实例</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
type Channel[T any] struct
|
||||
func NewChannel[T any]() *Channel[T]
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/7aB4KyMMp9A)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
c := concurrency.NewChannel[int]()
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Bridge">Bridge</span>
|
||||
|
||||
<p>将多个channel链接到一个channel,直到取消上下文。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (c *Channel[T]) Bridge(ctx context.Context, chanStream <-chan <-chan T) <-chan T
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/qmWSy1NVF-Y)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
c := concurrency.NewChannel[int]()
|
||||
genVals := func() <-chan <-chan int {
|
||||
out := make(chan (<-chan int))
|
||||
go func() {
|
||||
defer close(out)
|
||||
for i := 1; i <= 5; i++ {
|
||||
stream := make(chan int, 1)
|
||||
stream <- i
|
||||
close(stream)
|
||||
out <- stream
|
||||
}
|
||||
}()
|
||||
return out
|
||||
}
|
||||
|
||||
for v := range c.Bridge(ctx, genVals()) {
|
||||
fmt.Println(v)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
// 2
|
||||
// 3
|
||||
// 4
|
||||
// 5
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="FanIn">FanIn</span>
|
||||
|
||||
<p>将多个channel合并为一个channel,直到取消上下文。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (c *Channel[T]) FanIn(ctx context.Context, channels ...<-chan T) <-chan T
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/2VYFMexEvTm)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
c := concurrency.NewChannel[int]()
|
||||
channels := make([]<-chan int, 2)
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
channels[i] = c.Take(ctx, c.Repeat(ctx, i), 2)
|
||||
}
|
||||
|
||||
chs := c.FanIn(ctx, channels...)
|
||||
|
||||
for v := range chs {
|
||||
fmt.Println(v) //1 1 0 0 or 0 0 1 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Generate">Generate</span>
|
||||
|
||||
<p>根据传入的值,生成channel.</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (c *Channel[T]) Generate(ctx context.Context, values ...T) <-chan T
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/7aB4KyMMp9A)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
c := concurrency.NewChannel[int]()
|
||||
intStream := c.Generate(ctx, 1, 2, 3)
|
||||
|
||||
fmt.Println(<-intStream)
|
||||
fmt.Println(<-intStream)
|
||||
fmt.Println(<-intStream)
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
// 2
|
||||
// 3
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Repeat">Repeat</span>
|
||||
|
||||
<p>返回一个channel,将参数`values`重复放入channel,直到取消上下文。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (c *Channel[T]) Repeat(ctx context.Context, values ...T) <-chan T
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/k5N_ALVmYjE)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
c := concurrency.NewChannel[int]()
|
||||
intStream := c.Take(ctx, c.Repeat(ctx, 1, 2), 4)
|
||||
|
||||
for v := range intStream {
|
||||
fmt.Println(v)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
// 2
|
||||
// 1
|
||||
// 2
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="RepeatFn">RepeatFn</span>
|
||||
|
||||
<p>返回一个channel,重复执行函数fn,并将结果放入返回的channel,直到取消上下文。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (c *Channel[T]) RepeatFn(ctx context.Context, fn func() T) <-chan T
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/4J1zAWttP85)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
fn := func() string {
|
||||
return "hello"
|
||||
}
|
||||
|
||||
c := concurrency.NewChannel[string]()
|
||||
intStream := c.Take(ctx, c.RepeatFn(ctx, fn), 3)
|
||||
|
||||
for v := range intStream {
|
||||
fmt.Println(v)
|
||||
}
|
||||
// Output:
|
||||
// hello
|
||||
// hello
|
||||
// hello
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Or">Or</span>
|
||||
|
||||
<p>将一个或多个channel读取到一个channel中,当任何读取channel关闭时将结束读取。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (c *Channel[T]) Or(channels ...<-chan T) <-chan T
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/Wqz9rwioPww)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
sig := func(after time.Duration) <-chan any {
|
||||
c := make(chan any)
|
||||
go func() {
|
||||
defer close(c)
|
||||
time.Sleep(after)
|
||||
}()
|
||||
return c
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
|
||||
c := concurrency.NewChannel[any]()
|
||||
<-c.Or(
|
||||
sig(1*time.Second),
|
||||
sig(2*time.Second),
|
||||
sig(3*time.Second),
|
||||
)
|
||||
|
||||
fmt.Println("done after %v", time.Since(start)) //1.003s
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="OrDone">OrDone</span>
|
||||
|
||||
<p>将一个channel读入另一个channel,直到取消上下文。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (c *Channel[T]) OrDone(ctx context.Context, channel <-chan T) <-chan T
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/lm_GoS6aDjo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
c := concurrency.NewChannel[int]()
|
||||
intStream := c.Take(ctx, c.Repeat(ctx, 1), 3)
|
||||
|
||||
for v := range c.OrDone(ctx, intStream) {
|
||||
fmt.Println(v)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
// 1
|
||||
// 1
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Take">Take</span>
|
||||
|
||||
<p>返回一个channel,其值从另一个channel获取,直到取消上下文。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (c *Channel[T]) Take(ctx context.Context, valueStream <-chan T, number int) <-chan T
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/9Utt-1pDr2J)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
numbers := make(chan int, 5)
|
||||
numbers <- 1
|
||||
numbers <- 2
|
||||
numbers <- 3
|
||||
numbers <- 4
|
||||
numbers <- 5
|
||||
defer close(numbers)
|
||||
|
||||
c := concurrency.NewChannel[int]()
|
||||
intStream := c.Take(ctx, numbers, 3)
|
||||
|
||||
for v := range intStream {
|
||||
fmt.Println(v)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
// 2
|
||||
// 3
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Tee">Tee</span>
|
||||
|
||||
<p>将一个channel分成两个channel,直到取消上下文。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (c *Channel[T]) Tee(ctx context.Context, in <-chan T) (<-chan T, <-chan T)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/3TQPKnCirrP)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
c := concurrency.NewChannel[int]()
|
||||
intStream := c.Take(ctx, c.Repeat(ctx, 1), 2)
|
||||
|
||||
ch1, ch2 := c.Tee(ctx, intStream)
|
||||
|
||||
for v := range ch1 {
|
||||
fmt.Println(v)
|
||||
fmt.Println(<-ch2)
|
||||
}
|
||||
// Output:
|
||||
// 1
|
||||
// 1
|
||||
// 1
|
||||
// 1
|
||||
}
|
||||
```
|
||||
|
||||
### KeyedLocker
|
||||
|
||||
### <span id="NewKeyedLocker">NewKeyedLocker</span>
|
||||
|
||||
<p>NewKeyedLocker创建一个新的KeyedLocker,并为锁的过期设置指定的 TTL。KeyedLocker 是一个简单的键值锁实现,允许非阻塞的锁获取。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func NewKeyedLocker[K comparable](ttl time.Duration) *KeyedLocker[K]
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/GzeyC33T5rw)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
locker := concurrency.NewKeyedLocker[string](2 * time.Second)
|
||||
|
||||
task := func() {
|
||||
fmt.Println("Executing task...")
|
||||
time.Sleep(1 * time.Second)
|
||||
fmt.Println("Task completed.")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := locker.Do(ctx, "mykey", task); err != nil {
|
||||
log.Fatalf("Error executing task: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("Task successfully executed.")
|
||||
}
|
||||
|
||||
ctx2, cancel2 := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel2()
|
||||
|
||||
if err := locker.Do(ctx2, "mykey", task); err != nil {
|
||||
log.Fatalf("Error executing task: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("Task successfully executed.")
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Executing task...
|
||||
// Task completed.
|
||||
// Task successfully executed.
|
||||
// Executing task...
|
||||
// Task completed.
|
||||
// Task successfully executed.
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Do">Do</span>
|
||||
|
||||
<p>为指定的键获取锁并执行提供的函数。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (l *KeyedLocker[K]) Do(ctx context.Context, key K, fn func()) error
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/GzeyC33T5rw)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
locker := concurrency.NewKeyedLocker[string](2 * time.Second)
|
||||
|
||||
task := func() {
|
||||
fmt.Println("Executing task...")
|
||||
time.Sleep(1 * time.Second)
|
||||
fmt.Println("Task completed.")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := locker.Do(ctx, "mykey", task); err != nil {
|
||||
log.Fatalf("Error executing task: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("Task successfully executed.")
|
||||
}
|
||||
|
||||
ctx2, cancel2 := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel2()
|
||||
|
||||
if err := locker.Do(ctx2, "mykey", task); err != nil {
|
||||
log.Fatalf("Error executing task: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("Task successfully executed.")
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Executing task...
|
||||
// Task completed.
|
||||
// Task successfully executed.
|
||||
// Executing task...
|
||||
// Task completed.
|
||||
// Task successfully executed.
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="NewRWKeyedLocker">NewRWKeyedLocker</span>
|
||||
|
||||
<p>NewRWKeyedLocker创建一个新的RWKeyedLocker,并为锁的过期设置指定的 TTL。RWKeyedLocker 是一个简单的键值读写锁实现,允许非阻塞的锁获取。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func NewRWKeyedLocker[K comparable](ttl time.Duration) *RWKeyedLocker[K]
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/CkaJWWwZm9)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
locker := concurrency.NewRWKeyedLocker[string](2 * time.Second)
|
||||
|
||||
// Simulate a key
|
||||
key := "resource_key"
|
||||
|
||||
fn := func() {
|
||||
fmt.Println("Starting write operation...")
|
||||
// Simulate write operation, assuming it takes 2 seconds
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
fmt.Println("Write operation completed!")
|
||||
}
|
||||
|
||||
// Acquire the write lock and execute the operation
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Execute the lock operation with a 3-second timeout
|
||||
err := locker.Lock(ctx, key, fn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//output:
|
||||
//Starting write operation...
|
||||
//Write operation completed!
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="RLock">RLock</span>
|
||||
|
||||
<p>RLock为指定的键获取读锁并执行提供的函数。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (l *RWKeyedLocker[K]) RLock(ctx context.Context, key K, fn func()) error
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/ZrCr8sMo77T)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
locker := concurrency.NewRWKeyedLocker[string](2 * time.Second)
|
||||
|
||||
// Simulate a key
|
||||
key := "resource_key"
|
||||
|
||||
fn := func() {
|
||||
fmt.Println("Starting write operation...")
|
||||
// Simulate write operation, assuming it takes 2 seconds
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
fmt.Println("Write operation completed!")
|
||||
}
|
||||
|
||||
// Acquire the write lock and execute the operation
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Execute the lock operation with a 3-second timeout
|
||||
err := locker.RLock(ctx, key, fn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//output:
|
||||
//Starting write operation...
|
||||
//Write operation completed!
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Lock">Lock</span>
|
||||
|
||||
<p>Lock为指定的键获取锁并执行提供的函数。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (l *RWKeyedLocker[K]) Lock(ctx context.Context, key K, fn func()) error
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/WgAcXbOPKGk)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
locker := NewRWKeyedLocker[string](2 * time.Second)
|
||||
|
||||
// Simulate a key
|
||||
key := "resource_key"
|
||||
|
||||
fn := func() {
|
||||
fmt.Println("Starting write operation...")
|
||||
// Simulate write operation, assuming it takes 2 seconds
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
fmt.Println("Write operation completed!")
|
||||
}
|
||||
|
||||
// Acquire the write lock and execute the operation
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Execute the lock operation with a 3-second timeout
|
||||
err := locker.Lock(ctx, key, fn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//output:
|
||||
//Starting write operation...
|
||||
//Write operation completed!
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="NewTryKeyedLocker">NewTryKeyedLocker</span>
|
||||
|
||||
<p>创建一个TryKeyedLocker实例,TryKeyedLocker是KeyedLocker的非阻塞版本。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func NewTryKeyedLocker[K comparable]() *TryKeyedLocker[K]
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/VG9qLvyetE2)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
locker := concurrency.NewTryKeyedLocker[string]()
|
||||
|
||||
key := "resource_key"
|
||||
|
||||
if locker.TryLock(key) {
|
||||
fmt.Println("Lock acquired")
|
||||
time.Sleep(1 * time.Second)
|
||||
// Unlock after work is done
|
||||
locker.Unlock(key)
|
||||
fmt.Println("Lock released")
|
||||
} else {
|
||||
fmt.Println("Lock failed")
|
||||
}
|
||||
|
||||
//output:
|
||||
//Lock acquired
|
||||
//Lock released
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="TryLock">TryLock</span>
|
||||
|
||||
<p>TryLock尝试获取指定键的锁。如果锁成功获取,则返回true,否则返回false。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (l *TryKeyedLocker[K]) TryLock(key K) bool
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/VG9qLvyetE2)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
locker := concurrency.NewTryKeyedLocker[string]()
|
||||
|
||||
key := "resource_key"
|
||||
|
||||
if locker.TryLock(key) {
|
||||
fmt.Println("Lock acquired")
|
||||
time.Sleep(1 * time.Second)
|
||||
// Unlock after work is done
|
||||
locker.Unlock(key)
|
||||
fmt.Println("Lock released")
|
||||
} else {
|
||||
fmt.Println("Lock failed")
|
||||
}
|
||||
|
||||
//output:
|
||||
//Lock acquired
|
||||
//Lock released
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Unlock">Unlock</span>
|
||||
|
||||
<p>释放指定键的锁。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (l *TryKeyedLocker[K]) Unlock(key K)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/VG9qLvyetE2)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
locker := concurrency.NewTryKeyedLocker[string]()
|
||||
|
||||
key := "resource_key"
|
||||
|
||||
if locker.TryLock(key) {
|
||||
fmt.Println("Lock acquired")
|
||||
time.Sleep(1 * time.Second)
|
||||
// Unlock after work is done
|
||||
locker.Unlock(key)
|
||||
fmt.Println("Lock released")
|
||||
} else {
|
||||
fmt.Println("Lock failed")
|
||||
}
|
||||
|
||||
//output:
|
||||
//Lock acquired
|
||||
//Lock released
|
||||
}
|
||||
```
|
||||
335
docs/api/packages/condition.md
Normal file
335
docs/api/packages/condition.md
Normal file
@@ -0,0 +1,335 @@
|
||||
# Condition
|
||||
condition包含一些用于条件判断的函数。这个包的实现参考了carlmjohnson的truthy包的实现,更多有用的信息可以在[truthy](https://github.com/carlmjohnson/truthy)中找到,感谢carlmjohnson。
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## 源码:
|
||||
|
||||
- [https://github.com/duke-git/lancet/blob/main/condition/condition.go](https://github.com/duke-git/lancet/blob/main/condition/condition.go)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## 用法:
|
||||
```go
|
||||
import (
|
||||
"github.com/duke-git/lancet/v2/condition"
|
||||
)
|
||||
```
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## 目录
|
||||
|
||||
- [Bool](#Bool)
|
||||
- [And](#And)
|
||||
- [Or](#Or)
|
||||
- [Xor](#Generate)
|
||||
- [Nor](#Nor)
|
||||
- [Xnor](#Xnor)
|
||||
- [Nand](#Nand)
|
||||
- [Ternary](#Ternary)
|
||||
- [TernaryOperator<sup>deprecated</sup>](#TernaryOperator)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## 文档
|
||||
|
||||
### <span id="Bool">Bool</span>
|
||||
<p>返回传入参数的bool值.<br/>
|
||||
如果出入类型参数含有Bool方法, 会调用该方法并返回<br/>
|
||||
如果传入类型参数有IsZero方法, 返回IsZero方法返回值的取反<br/>
|
||||
slices和map的length大于0时,返回true,否则返回false<br/>
|
||||
其他类型会判断是否是零值</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func Bool[T any](value T) bool
|
||||
```
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/ETzeDJRSvhm)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/condition"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// bool
|
||||
result1 := condition.Bool(false)
|
||||
result2 := condition.Bool(true)
|
||||
fmt.Println(result1) // false
|
||||
fmt.Println(result2) // true
|
||||
|
||||
// integer
|
||||
result3 := condition.Bool(0)
|
||||
result4 := condition.Bool(1)
|
||||
fmt.Println(result3) // false
|
||||
fmt.Println(result4) // true
|
||||
|
||||
// string
|
||||
result5 := condition.Bool("")
|
||||
result6 := condition.Bool(" ")
|
||||
fmt.Println(result5) // false
|
||||
fmt.Println(result6) // true
|
||||
|
||||
// slice
|
||||
nums := []int{}
|
||||
result7 := condition.Bool(nums)
|
||||
|
||||
nums = append(nums, 1, 2)
|
||||
result8 := condition.Bool(nums)
|
||||
fmt.Println(result7) // false
|
||||
fmt.Println(result8) // true
|
||||
|
||||
// struct
|
||||
result9 = condition.Bool(struct{}{})
|
||||
fmt.Println(result8) // false
|
||||
|
||||
|
||||
// Output:
|
||||
// false
|
||||
// true
|
||||
// false
|
||||
// true
|
||||
// false
|
||||
// true
|
||||
// false
|
||||
// true
|
||||
// false
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="And">And</span>
|
||||
<p>逻辑且操作,当切仅当a和b都为true时返回true</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func And[T, U any](a T, b U) bool
|
||||
```
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/W1SSUmt6pvr)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/condition"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println(condition.And(0, 1)) // false
|
||||
fmt.Println(condition.And(0, "")) // false
|
||||
fmt.Println(condition.And(0, "0")) // false
|
||||
fmt.Println(condition.And(1, "0")) // true
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Or">Or</span>
|
||||
<p>逻辑或操作,当切仅当a和b都为false时返回false</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func Or[T, U any](a T, b U) bool
|
||||
```
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/UlQTxHaeEkq)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/condition"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println(condition.Or(0, "")) // false
|
||||
fmt.Println(condition.Or(0, 1)) // true
|
||||
fmt.Println(condition.Or(0, "0")) // true
|
||||
fmt.Println(condition.Or(1, "0")) // true
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Xor">Xor</span>
|
||||
<p>逻辑异或操作,a和b相同返回false,a和b不相同返回true</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func Xor[T, U any](a T, b U) bool
|
||||
```
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/gObZrW7ZbG8)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/condition"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println(condition.Xor(0, 0)) // false
|
||||
fmt.Println(condition.Xor(0, 1)) // true
|
||||
fmt.Println(condition.Xor(1, 0)) // true
|
||||
fmt.Println(condition.Xor(1, 1)) // false
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Nor">Nor</span>
|
||||
<p>异或的取反操作</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func Nor[T, U any](a T, b U) bool
|
||||
```
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/g2j08F_zZky)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/condition"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println(condition.Nor(0, 0)) // true
|
||||
fmt.Println(condition.Nor(0, 1)) // false
|
||||
fmt.Println(condition.Nor(1, 0)) // false
|
||||
fmt.Println(condition.Nor(1, 1)) // false
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Xnor">Xnor</span>
|
||||
<p>如果a和b都是真的或a和b均是假的,则返回true。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func Xnor[T, U any](a T, b U) bool
|
||||
```
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/OuDB9g51643)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/condition"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println(condition.Xnor(0, 0)) // true
|
||||
fmt.Println(condition.Xnor(0, 1)) // false
|
||||
fmt.Println(condition.Xnor(1, 0)) // false
|
||||
fmt.Println(condition.Xnor(1, 1)) // true
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Nand">Nand</span>
|
||||
<p>如果a和b都为真,返回false,否则返回true</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func Nand[T, U any](a T, b U) bool
|
||||
```
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/vSRMLxLIbq8)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/condition"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println(condition.Nand(0, 0)) // true
|
||||
fmt.Println(condition.Nand(0, 1)) // true
|
||||
fmt.Println(condition.Nand(1, 0)) // true
|
||||
fmt.Println(condition.Nand(1, 1)) // false
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Ternary">Ternary</span>
|
||||
<p>三元运算符。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func Ternary[T, U any](isTrue T, ifValue U, elseValue U) U
|
||||
```
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/ElllPZY0guT)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/condition"
|
||||
)
|
||||
|
||||
func main() {
|
||||
conditionTrue := 2 > 1
|
||||
result1 := condition.Ternary(conditionTrue, 0, 1)
|
||||
|
||||
conditionFalse := 2 > 3
|
||||
result2 := condition.Ternary(conditionFalse, 0, 1)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
|
||||
// Output:
|
||||
// 0
|
||||
// 1
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="TernaryOperator">TernaryOperator</span>
|
||||
<p>三元运算符</p>
|
||||
|
||||
> ⚠️ 本函数已弃用,使用`Ternary`代替。
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func TernaryOperator[T, U any](isTrue T, ifValue U, elseValue U) U
|
||||
```
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/ElllPZY0guT)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/condition"
|
||||
)
|
||||
|
||||
func main() {
|
||||
conditionTrue := 2 > 1
|
||||
result1 := condition.TernaryOperator(conditionTrue, 0, 1)
|
||||
|
||||
conditionFalse := 2 > 3
|
||||
result2 := condition.TernaryOperator(conditionFalse, 0, 1)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
|
||||
// Output:
|
||||
// 0
|
||||
// 1
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
1182
docs/api/packages/convertor.md
Normal file
1182
docs/api/packages/convertor.md
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user