mirror of
https://gitee.com/johng/gf
synced 2026-06-07 18:26:02 +08:00
Compare commits
1021 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a95624ab19 | |||
| 10e042454c | |||
| 2f17d37f7b | |||
| 5c9766fb1b | |||
| 717dae8f5d | |||
| 373dbde42c | |||
| b43e36a79d | |||
| 145fccdf6e | |||
| 5da822fdc4 | |||
| a7d30dd1d5 | |||
| fa96d881e1 | |||
| 7ad66db491 | |||
| 1dbda3872b | |||
| 3619a46f52 | |||
| 6a93d166c5 | |||
| c84e62febe | |||
| 3b0012ec30 | |||
| 62a3f1693d | |||
| ed6796dde0 | |||
| 46721d7552 | |||
| a9a5a78e02 | |||
| 7c4431ceeb | |||
| 8cfdc8f27f | |||
| 8669057681 | |||
| d0fe2d2f75 | |||
| 49ef4fd266 | |||
| 929a57ceb8 | |||
| c6f94ed95a | |||
| 949ac459fc | |||
| 82394cd70e | |||
| 4670a5d2e2 | |||
| 273d992493 | |||
| ed61c2ee22 | |||
| b29c6add47 | |||
| 8d01e565c5 | |||
| 47e0fb95d5 | |||
| 23d346b291 | |||
| bd105a188f | |||
| 09affd3981 | |||
| 8e7e18e22d | |||
| e03fd80fd4 | |||
| adc3201dcf | |||
| 3a681e5b1a | |||
| c90ed0d424 | |||
| 418cbb420b | |||
| c948f0c287 | |||
| 285b45d19d | |||
| 783f1c8457 | |||
| daa7f12994 | |||
| e63e989d41 | |||
| 31921905a9 | |||
| 5572ab858e | |||
| 961ca0879d | |||
| 5d464494b6 | |||
| 4ca2513d00 | |||
| 15d69ed950 | |||
| 0ff31012c8 | |||
| 5c7b25c960 | |||
| 6f5b5e4dc2 | |||
| 007c8a7b64 | |||
| 42214f17fc | |||
| b5f117e932 | |||
| b0f158047c | |||
| aa6110c619 | |||
| a9660fe9fa | |||
| 0801245871 | |||
| 278fd3515f | |||
| 56588f3f7f | |||
| a5d01cb547 | |||
| fabf5d1ad5 | |||
| ebae3a4929 | |||
| 8e6640ed79 | |||
| e4c42bde89 | |||
| f7515edde9 | |||
| 2b0da75412 | |||
| 04c422c3af | |||
| 339ca74ff4 | |||
| 741a13379a | |||
| da907f35bd | |||
| 133531b6a0 | |||
| 5fa0f8dd7e | |||
| 7247413a48 | |||
| 591158adc5 | |||
| 5d83383105 | |||
| 5ad809f49e | |||
| 56a62bb4c2 | |||
| afb4a233be | |||
| c0aebe023c | |||
| 055c6a668e | |||
| 735c5fc7ed | |||
| c066fff971 | |||
| 89373ebcd6 | |||
| 51cf232691 | |||
| 3bfff2347f | |||
| 7d83604540 | |||
| f25571a0a9 | |||
| 05c7ba5f29 | |||
| dcf7772589 | |||
| 3806f9db07 | |||
| 0f14002b05 | |||
| 1bf53d8b89 | |||
| 8eb10a58ad | |||
| 9985378062 | |||
| 38ad5d457b | |||
| ebed433dde | |||
| 2b02f7e210 | |||
| 1434800982 | |||
| 327e33b827 | |||
| d2a053c1d7 | |||
| abe14e049a | |||
| 4b9db0c794 | |||
| b118cd8f27 | |||
| 83669964f5 | |||
| 9248fd6b28 | |||
| bb935967ac | |||
| b7e0f1f983 | |||
| 6ee6c007c5 | |||
| 9a507b54d7 | |||
| c88f516759 | |||
| 912b743a1e | |||
| d546424f18 | |||
| fe179fff42 | |||
| adf2ddb510 | |||
| f30c9020fa | |||
| 1b3073f3f9 | |||
| 615161ac9d | |||
| 02e06e7c6e | |||
| d15268eb22 | |||
| e48415d932 | |||
| e5fa341f39 | |||
| 234d734981 | |||
| d771ed9209 | |||
| 37d72cb8b3 | |||
| 1154f9601b | |||
| 322513f99b | |||
| 6e7ac60d4d | |||
| 405840607f | |||
| 8417e7ef65 | |||
| ac2fe44d8e | |||
| 3030d7f032 | |||
| dcf7138694 | |||
| 97df154b78 | |||
| d74034e08e | |||
| 674590e247 | |||
| d41cc7c3b6 | |||
| ab48800401 | |||
| ea456c5faa | |||
| 83d1442781 | |||
| 5888b0e06a | |||
| 3120d0bd7a | |||
| a7dcc2c9c6 | |||
| 17898cc747 | |||
| 7bebf062be | |||
| 048c3d5025 | |||
| 0adcdc6b4a | |||
| 19d7ad734b | |||
| 6c657dff37 | |||
| 78ccbeac3b | |||
| 2a0eae8975 | |||
| 28d25e7812 | |||
| fe795d49f3 | |||
| 9fad898ee3 | |||
| 9a42142ce7 | |||
| 9db8ed2dfc | |||
| 619a539f2e | |||
| ae8eb4a1f1 | |||
| 9a469e64da | |||
| f7d1613d62 | |||
| 80655bce50 | |||
| b3b0ba775c | |||
| 4713739d1a | |||
| 53b5de330e | |||
| fd4843c3a1 | |||
| 97d2b9e1f9 | |||
| 5896dadaad | |||
| 0ab60cb770 | |||
| 5b71e92776 | |||
| 211678c7d3 | |||
| d25793ed03 | |||
| 98cae011a5 | |||
| 59ae6f9c10 | |||
| 474d1669d9 | |||
| 563a1408ea | |||
| 168c08a6f6 | |||
| 82783072ef | |||
| 11972ef96c | |||
| 468ba21283 | |||
| 78b0cae892 | |||
| 866482a8e8 | |||
| c342310389 | |||
| 0eb028f0b5 | |||
| 4ffe4f2262 | |||
| 677549ec15 | |||
| 68d8e25bc4 | |||
| 4f007fdd44 | |||
| 54392941f3 | |||
| ebdad47f2d | |||
| cb4e36f591 | |||
| cd30efaaa1 | |||
| 7c234e0437 | |||
| d973c5d5c7 | |||
| a7f15a4e00 | |||
| 6b5484bf55 | |||
| ab38b709b2 | |||
| 4118038198 | |||
| 142154c0df | |||
| 07ae90d64d | |||
| 8a69fd09fc | |||
| c02f502bd8 | |||
| d5d6b8c303 | |||
| 5c592afebe | |||
| 8402a2b710 | |||
| fe152dfa63 | |||
| e695983d4d | |||
| a5ab2ba332 | |||
| c6e5a52104 | |||
| 1b4a879eda | |||
| c5aa493d24 | |||
| a88363e34c | |||
| 0f1261d0e3 | |||
| c41d11df9f | |||
| 1e680c7a8b | |||
| e981143ead | |||
| 3e2d5e0bdb | |||
| 1f19ed71aa | |||
| 4de6881c89 | |||
| 5baa82da8f | |||
| 48f610a216 | |||
| e4e58791a6 | |||
| 46c42ec249 | |||
| d068c1418e | |||
| 41db3a32f4 | |||
| b3a00becf3 | |||
| d1d8cd8482 | |||
| 1228907d59 | |||
| 57b54414d6 | |||
| 548a0c47af | |||
| 4934564b7b | |||
| 7348d14fef | |||
| 9cddb7ed9a | |||
| 8c84de3f73 | |||
| 96529a4c1c | |||
| c7a729fe06 | |||
| 23d404f681 | |||
| 887aeee2d4 | |||
| 622dbfda31 | |||
| 80cf3e833b | |||
| d305d25935 | |||
| 81502cfb6d | |||
| 5950a3fcc3 | |||
| 55f5e6d7aa | |||
| e2070e785c | |||
| 905d5abed6 | |||
| 211e06d04d | |||
| 7aaf9e9228 | |||
| a901e7177c | |||
| afc2bcfb28 | |||
| 8da204fbd8 | |||
| 5aa3212fe1 | |||
| 17d49510c4 | |||
| 4af8ae1470 | |||
| 3a5c660693 | |||
| 9e65100a06 | |||
| 399e47c548 | |||
| abdf8e696c | |||
| 4a2e217625 | |||
| 52d0280137 | |||
| 5be9765eb7 | |||
| cdb9488752 | |||
| 0388113870 | |||
| c6dfb4d4f8 | |||
| 4a40b58b63 | |||
| 407068a0bf | |||
| 165330ec68 | |||
| 6e7d08fbfb | |||
| 1ae77f56e5 | |||
| fccac04980 | |||
| 469f9c7ce5 | |||
| d6d37248f6 | |||
| cb1084b770 | |||
| e6d4459992 | |||
| 1afb5a4bc5 | |||
| c124f172b2 | |||
| dbd4a7c1d4 | |||
| a4d30ef206 | |||
| 0a616173ef | |||
| 597f210f85 | |||
| 24bd83feb0 | |||
| 3855786905 | |||
| 1f670a1ab2 | |||
| d97fda794c | |||
| c034d25299 | |||
| dd6152fe8a | |||
| 485fe572ff | |||
| 15bf5d9a4d | |||
| 08aa7c4e4c | |||
| 442c658be0 | |||
| 5aa8ce1c6b | |||
| ad8ece68c6 | |||
| 74525ba8f7 | |||
| 46d46afaaf | |||
| 13eb1150a5 | |||
| f98db6d21c | |||
| c695dfd92e | |||
| ffd78d76e1 | |||
| e479c41667 | |||
| 9f8c481992 | |||
| 3320d12994 | |||
| be6f522cf3 | |||
| b0b6871bbb | |||
| 59ae6217cd | |||
| 334cd7ad51 | |||
| 501c3680d9 | |||
| ebcc81c1ee | |||
| 6814372a89 | |||
| d5d14b7efc | |||
| 3a72686774 | |||
| aa73c5ed53 | |||
| cc0a385c22 | |||
| 0b8c9713e6 | |||
| e400a94ffb | |||
| 136ad3b0b5 | |||
| f9826104d8 | |||
| c0c97b76fb | |||
| a908e4d4b1 | |||
| e5c255200c | |||
| 00db4f5ed9 | |||
| 29ead3ff3e | |||
| 055074246e | |||
| 4bbe51fb4b | |||
| 841224372b | |||
| d1f0fa1a47 | |||
| 6fecf8bb01 | |||
| 6d44f02a38 | |||
| 1458e486d7 | |||
| dc29822e69 | |||
| dae7722da1 | |||
| 16d978dc58 | |||
| 5d3c154b45 | |||
| 00a8ef63b6 | |||
| 6ac437a3a5 | |||
| 33b24eba01 | |||
| 3a99c6e5f5 | |||
| 4c5d2839bd | |||
| 85b104bafa | |||
| 74e5d03a78 | |||
| cc43324ede | |||
| e4f9e1000d | |||
| 62829b0698 | |||
| 2551e990cb | |||
| 45c34319b5 | |||
| 75dcc566b3 | |||
| 4f1047e853 | |||
| 79f765c961 | |||
| eead2fad2c | |||
| 455c9e09ab | |||
| 4665c3565c | |||
| 7456b4b4ad | |||
| 32a6454065 | |||
| c52640c672 | |||
| d62ef17290 | |||
| 216af6a662 | |||
| 35d860427e | |||
| 9206574bae | |||
| 9c6f54131f | |||
| d67b95c593 | |||
| 2bf2f1b822 | |||
| 9ad94eccad | |||
| 6e8a900f25 | |||
| d9aa9e4480 | |||
| fe74818a37 | |||
| 7335126064 | |||
| 945dd71251 | |||
| 652aa29370 | |||
| 7034e2015e | |||
| 0a890ad871 | |||
| b52bb1124e | |||
| 41f33af51b | |||
| 6b4763c7da | |||
| be07889a45 | |||
| 1b3243c09c | |||
| fee1c9eccf | |||
| 084f6c31cb | |||
| 592bf76eb0 | |||
| 6f0aee1cc5 | |||
| 3575e20137 | |||
| f69f529258 | |||
| 3e7416ca7d | |||
| cf324c5d8c | |||
| 9da883a50c | |||
| ea3e03aaba | |||
| 8d9fdfeafc | |||
| b2a8285ecb | |||
| 162e8f7e51 | |||
| 6c658813cd | |||
| 0839ea385f | |||
| 68fa235412 | |||
| 417ce4b470 | |||
| 48deaa5f57 | |||
| e9f7b8bc0c | |||
| e31861af2e | |||
| 1af482d950 | |||
| dd2436925b | |||
| b92b69564b | |||
| a4fa163333 | |||
| 635d228c86 | |||
| 75725db6fb | |||
| 5cd8475143 | |||
| 5629f37939 | |||
| 08ec04d8b6 | |||
| c0b46f364a | |||
| 303d03d43c | |||
| 8c5f74e8bb | |||
| 94832262e3 | |||
| aefbfd52e9 | |||
| f3f0689bd4 | |||
| 5198d4c5fc | |||
| 123f2d3e4e | |||
| 3c750c3c92 | |||
| 17b29cd19f | |||
| cf1077bec4 | |||
| 4e2e4e95e0 | |||
| 61d64e7ae4 | |||
| 883797c495 | |||
| 0113971877 | |||
| 664b0c06a6 | |||
| bd4c75a98e | |||
| d35840409b | |||
| abaef9ba87 | |||
| b15d8bdd2e | |||
| 718997327a | |||
| fdfefbb94d | |||
| 2b865a55ac | |||
| 8138215597 | |||
| 7cc0c7a1cc | |||
| 50f561dbd2 | |||
| 4c647aaa19 | |||
| 48b1d616c5 | |||
| 693c37d6d6 | |||
| d525c04826 | |||
| c170edbdfc | |||
| 66e40155a9 | |||
| 59ad1a9b00 | |||
| a5b536e218 | |||
| 0e6c2e790d | |||
| 5761e73061 | |||
| 34c761e9db | |||
| 87e3813636 | |||
| 361ff0315c | |||
| 2bb227d058 | |||
| 99dc69e839 | |||
| d78fde8099 | |||
| 5d0c8956d6 | |||
| c9537af062 | |||
| dfb5b3a8ce | |||
| a177e44583 | |||
| 7ae03729f3 | |||
| 898ec21a25 | |||
| 6d7d8dec02 | |||
| ea7e2ec5ec | |||
| a5b8e2aa2f | |||
| 123333d9c2 | |||
| 0c4fa1d96a | |||
| e5805e8c69 | |||
| bf2d45a012 | |||
| a7122788b1 | |||
| 237c58f2b0 | |||
| efa23e4a1d | |||
| c109cee7ef | |||
| e111d39c54 | |||
| 66306464e1 | |||
| dd34ac1722 | |||
| 34cb222b33 | |||
| a0276f7e81 | |||
| d39ef156de | |||
| f464dc7fb8 | |||
| d29b27a5df | |||
| 5346ca9046 | |||
| 6d5b552bb7 | |||
| aadc6aa504 | |||
| e8c3dfa13e | |||
| 836d62f4aa | |||
| 9eea93cc6e | |||
| 308cb55b6b | |||
| 75ada78f8f | |||
| ecd86e3a12 | |||
| c1aa5eb717 | |||
| d2fed1198b | |||
| a9f9261dbd | |||
| 161e0d6e97 | |||
| 3efe511f42 | |||
| 5d04c2e50a | |||
| 7b26b7ea4c | |||
| 9d1063c6b2 | |||
| 5ff7632d32 | |||
| e6fb41504c | |||
| a800f731dd | |||
| f1a9fbb74e | |||
| cf81a73526 | |||
| 65036fffe8 | |||
| a69934a7e3 | |||
| 9400457bf2 | |||
| 5060329721 | |||
| 07ab1d60e8 | |||
| 7377a82e19 | |||
| f82e3ac808 | |||
| 1a6cd1de04 | |||
| 90e6f685b7 | |||
| 0fc825dac1 | |||
| dbb27efe3e | |||
| 2d3d2e783e | |||
| 6ae1defa35 | |||
| d55e77fb90 | |||
| 7fb9ae71e7 | |||
| 9419555149 | |||
| ab634f8beb | |||
| 9503b80d57 | |||
| bc870226e5 | |||
| 7e106ae011 | |||
| 958e00e231 | |||
| ab187d225d | |||
| 9f5711d41d | |||
| cdb2773127 | |||
| d12532ccc1 | |||
| 53e9f05a10 | |||
| 60e7ab95bc | |||
| ae552e2b46 | |||
| 145b52f343 | |||
| 57b8fac0d5 | |||
| 16a4a5ba46 | |||
| 3d37c83532 | |||
| b02fa701b8 | |||
| 37b7c82c64 | |||
| 1deb27df06 | |||
| 84ed3d5767 | |||
| fc909a3db2 | |||
| 2f7d4cd80d | |||
| 03ccbf3613 | |||
| 1868465319 | |||
| ff473e2fcc | |||
| a3c38eec86 | |||
| 2015c847e8 | |||
| 1b583ed984 | |||
| 11191c746a | |||
| abedd3c5bf | |||
| 501ba5135b | |||
| a76c98c348 | |||
| 9dcdc1a339 | |||
| 6d1f386203 | |||
| ff9bbf0a49 | |||
| 66e24c8d40 | |||
| 34f117c631 | |||
| 21f2f16889 | |||
| f6fa7c422d | |||
| 6903b84bb6 | |||
| 0978b8fb4f | |||
| d014583e88 | |||
| a747f51b9d | |||
| 8300885ab6 | |||
| b489eed4ef | |||
| 0b57771d76 | |||
| 8a32a8271c | |||
| c3e716dafd | |||
| 119a11eb8d | |||
| e464d14842 | |||
| 905f46359a | |||
| 8e7bf1f908 | |||
| 3a1524ae6d | |||
| 5c04befea3 | |||
| ae3584cdff | |||
| a60578c82c | |||
| 74558a500c | |||
| d963d8c8c1 | |||
| 853892c24f | |||
| 641f939e3d | |||
| af77504eaf | |||
| 62649d6468 | |||
| bb914e605e | |||
| 62ee88bbfa | |||
| 99c964bb4a | |||
| 09ceaef3e9 | |||
| a82900af55 | |||
| 5ef589f31a | |||
| cd719f134d | |||
| f69eb219b5 | |||
| 0ffe17ee3d | |||
| 4ac647a215 | |||
| 872536c035 | |||
| c7a6a6fff0 | |||
| 2534655bc8 | |||
| 0cb82d70fd | |||
| acac5a2ad6 | |||
| 9ec15ad2ca | |||
| a9b7d56d0b | |||
| e9ca1eb538 | |||
| 0532800895 | |||
| ad50ca6e60 | |||
| c1ad999c25 | |||
| 4accd1264d | |||
| e1f3da3aa8 | |||
| 4bf9a7950b | |||
| 8285c31bf1 | |||
| 230be66fa9 | |||
| 428d7ec94a | |||
| 2c53f934c9 | |||
| 08785cb272 | |||
| bd0207c938 | |||
| 6fad737617 | |||
| af4148d985 | |||
| 922eaf4d42 | |||
| 4b5153950f | |||
| 78010d2bd7 | |||
| 6cc0017826 | |||
| 8a9131c3dc | |||
| 85c2ed1bf2 | |||
| aca1df634d | |||
| 053a3c1a53 | |||
| 429aa90e0d | |||
| 4f792b347d | |||
| 468c315087 | |||
| 6d8ced21b9 | |||
| b3d5fc149e | |||
| 277b7a4536 | |||
| 43886511b9 | |||
| 8b3eb5b02d | |||
| 054ef87886 | |||
| 39c65d9e9a | |||
| b97bbbfa3d | |||
| ace6ba8096 | |||
| a2b87d84e9 | |||
| b90d61b27c | |||
| 85606e3e7e | |||
| 1fc85d49bd | |||
| a5cfb4e638 | |||
| 38754bf062 | |||
| f1818ed2ff | |||
| 352ad17715 | |||
| e50b8d9632 | |||
| 2107061a46 | |||
| 61f57b4895 | |||
| d34273abff | |||
| 0aff0f0362 | |||
| 68949b69bc | |||
| c56c77d3a1 | |||
| dc82ce395a | |||
| fd63a2209b | |||
| 2a29483456 | |||
| 4f10562980 | |||
| a26ec37f59 | |||
| 779ad93bcb | |||
| 1ec0219473 | |||
| 6863928b06 | |||
| aa4dca11f0 | |||
| dc6ab820ce | |||
| be0fa4d60b | |||
| a86d2272af | |||
| 61a67892ac | |||
| 388d5954cb | |||
| 08550d413e | |||
| b89294561b | |||
| 20977558cc | |||
| 630d8fdb43 | |||
| c4c7e6caf4 | |||
| 8a8fea1257 | |||
| 0e0f297a3f | |||
| fd2c0f2b24 | |||
| ecc6e3888d | |||
| 47c073aaf3 | |||
| 07476a4349 | |||
| 817148f3a1 | |||
| bd4271cd8c | |||
| b1804fc346 | |||
| a3886c2179 | |||
| f258b5bf1c | |||
| a05361011f | |||
| 6b34a77251 | |||
| afb1adee3d | |||
| 22fa7a37f3 | |||
| 6a58bfc574 | |||
| 64124c60fc | |||
| 9a0066de62 | |||
| 22c7c7403b | |||
| 83db8e4b15 | |||
| 25f2e121e7 | |||
| 1325a145d8 | |||
| 9bc49c0b29 | |||
| 8e84e5b0f3 | |||
| a42e6b0c45 | |||
| 51bb7a9854 | |||
| 62580b5719 | |||
| 15cfd5ce5c | |||
| 11c89c4090 | |||
| 4a12cb9f27 | |||
| 32f575eddd | |||
| fbb4cb3b1c | |||
| c9d2d5e8ab | |||
| 93763192f2 | |||
| 80e0eae6b0 | |||
| 4e3d735b90 | |||
| 60e5a7da28 | |||
| 997b5ba889 | |||
| b3e7ca1963 | |||
| 5a82d695c1 | |||
| 64a0427150 | |||
| f6aafc1d6b | |||
| bca5532df8 | |||
| 72eeadd9aa | |||
| 0af55794f6 | |||
| 25a6c53533 | |||
| 9f9172c775 | |||
| 320e0db417 | |||
| cb8362d447 | |||
| 45a83fc53c | |||
| 3411bd1c1d | |||
| 6ab0a77364 | |||
| 281bae4116 | |||
| 218c692fe0 | |||
| fa69b581e1 | |||
| bd0baceeca | |||
| e71c837472 | |||
| 782aaabd07 | |||
| 9014325a7c | |||
| 8ae9276732 | |||
| 79a3aa5916 | |||
| 733c5db228 | |||
| 6171c621a7 | |||
| fc11856a28 | |||
| 802568856c | |||
| 127fb67185 | |||
| 5db039bbce | |||
| b0726b9733 | |||
| 8a3365d18e | |||
| 2ae5b1a4f8 | |||
| f9515d7126 | |||
| b56679a97c | |||
| d1b123964a | |||
| 991f7c4958 | |||
| 770619c39e | |||
| 4ad5450b80 | |||
| 49ce7fe885 | |||
| e66f63262b | |||
| 94bd5da68a | |||
| 3eee95caf2 | |||
| 5c638c630a | |||
| a6ec9d7a1c | |||
| 374c70c0e3 | |||
| 40771066d4 | |||
| 22a7ef43ce | |||
| 05f22d1cee | |||
| ebf56a86ab | |||
| 2ba59e8943 | |||
| 4e4ea25e7f | |||
| 83be1de04c | |||
| c8251ed82f | |||
| 2335ea0c4d | |||
| 5d874e9063 | |||
| f2c080d25f | |||
| 975da97b4a | |||
| 37617589a6 | |||
| 5c9f0db903 | |||
| 28abf0c175 | |||
| 13749feab4 | |||
| c1e77b7e09 | |||
| 962a5e93f7 | |||
| c3b9b8d5ae | |||
| 1ad076c522 | |||
| b01777fcd1 | |||
| 55a5532c2e | |||
| adb928941a | |||
| f92c1fc527 | |||
| 3ae7279ebc | |||
| 66287c2d0e | |||
| 5d37626981 | |||
| d0ed3b979d | |||
| fa256aec9f | |||
| 86834c5a15 | |||
| c2046157d6 | |||
| cdb2cc89c0 | |||
| 4964c09a77 | |||
| ef34b2c9ce | |||
| 9afe242293 | |||
| 136d93d373 | |||
| 3102cec5b8 | |||
| e28eb9da04 | |||
| 754ed86dfb | |||
| e352b07055 | |||
| fdea242b50 | |||
| 7058e4f2c4 | |||
| 704a5dbd73 | |||
| fbd4ce8c2e | |||
| cb3ce71cdc | |||
| 7f44f2f5e4 | |||
| 66efbe63f0 | |||
| 49a1308875 | |||
| 4332580c01 | |||
| 72ecf2d2af | |||
| 0f854e46d8 | |||
| 4564f38e1a | |||
| 7e06bf6705 | |||
| 3dd8b6ad33 | |||
| 3e0a975a88 | |||
| 6aa1c5b1eb | |||
| d780cf64c2 | |||
| 1fb5a8cd6f | |||
| 8925460718 | |||
| 9797701881 | |||
| 8a50b180c0 | |||
| 159190d187 | |||
| 989d543a1f | |||
| fcc3a1b2f6 | |||
| cfdeb87093 | |||
| 74eef34ec2 | |||
| 2e87d5322f | |||
| e89a49f39a | |||
| 5d3fd91f0b | |||
| f455d22893 | |||
| c00f528098 | |||
| 6f02ad60eb | |||
| ca6c0791ae | |||
| ee485dbfd8 | |||
| 2ecaa12647 | |||
| 580e099cb7 | |||
| 83e50f0d38 | |||
| 48c770b475 | |||
| ba67101942 | |||
| 72efde09b3 | |||
| 76c49170bd | |||
| 150aa7e2c2 | |||
| 0762fec696 | |||
| 1447496efa | |||
| 6fb9eafef0 | |||
| 16bde5e9fc | |||
| 7b85c44444 | |||
| 87d553fca2 | |||
| ba050d4c86 | |||
| 90cd7f49fd | |||
| 29b42290c7 | |||
| 6c6c64bb3b | |||
| ae7db2cf9f | |||
| 1afab62dec | |||
| 4171ca992e | |||
| e1166a8a80 | |||
| 1dc6c799e1 | |||
| ae1e075696 | |||
| 74a7f71894 | |||
| f2e149d3b3 | |||
| 92c0bf9cdc | |||
| 020b050fb1 | |||
| 08d71cede3 | |||
| be1f6cfbae | |||
| 9aa5e139cf | |||
| 1d5c3d62dd | |||
| 0e611ae94b | |||
| 104613b056 | |||
| 99577ad874 | |||
| 908a46d27d | |||
| 85677b952f | |||
| f4773ef1e4 | |||
| 5eaa6183b5 | |||
| 4036d40c58 | |||
| 0b80cbb0dc | |||
| 79cb386d82 | |||
| 09be68831b | |||
| 0ac13c2b81 | |||
| 94ef38e3cc | |||
| 76882ac01c | |||
| 0d315218dd | |||
| 59945fbe91 | |||
| c4962ec017 | |||
| 58d60bc899 | |||
| 5dd0a78423 | |||
| 141ea7cc2d | |||
| a488d1dbf7 | |||
| ec130d0763 | |||
| 30729e3f93 | |||
| 241d7402cc | |||
| 3b14aba1a2 | |||
| b02205f7cd | |||
| c27bc0023f | |||
| 9698a7c5be | |||
| 071e2f8bb4 | |||
| 726d3f7024 | |||
| 3503aa43b4 | |||
| e865b46304 | |||
| 494f96495e | |||
| 7ed2081513 | |||
| 5110313657 | |||
| 24990e26c8 | |||
| 3ca086bcec | |||
| 7d103c4ee8 | |||
| 5fed6f5681 | |||
| 616539ecb0 | |||
| 9e99e88d27 | |||
| 0e39400dd0 | |||
| 2ba796de01 | |||
| efe2535977 | |||
| c17352b8af | |||
| b1fc3ff17a | |||
| 485dafb616 | |||
| bf25a3a601 | |||
| 14fcd0b2f9 | |||
| cb24714faa | |||
| 36199334f0 | |||
| 72c7e65dfa | |||
| a4ad301b44 | |||
| 72569321fa | |||
| 1600a80124 | |||
| 80c1a02377 | |||
| 0c41909454 | |||
| 2b5d889bb9 | |||
| 2c2a71d429 | |||
| f900414e38 | |||
| 1c72766c34 | |||
| 302f3c1467 | |||
| 9415419324 | |||
| 602592a354 | |||
| 1a4cba5fa5 | |||
| 018853e976 | |||
| 651bd33b73 | |||
| f4644ce685 | |||
| 0a422e9a89 | |||
| 432c16c89f | |||
| 241706cbbf | |||
| 33dd0f9922 | |||
| ed8bb354e5 | |||
| e373392f64 | |||
| 292fd2f39e | |||
| 1c9cb8286f | |||
| 8296061b64 | |||
| bb5d84c29c | |||
| eae857bcf7 | |||
| d604d198ab | |||
| 40b5162fdf | |||
| 7934ad6904 | |||
| 858b010caa | |||
| 36791d2f48 | |||
| 08f9cffed9 | |||
| 783c0ba846 | |||
| 37cd2351e2 | |||
| be1e250a6b | |||
| f86896e5af | |||
| 7ad4f61564 | |||
| adf06a2b0d | |||
| d6aa2b2512 | |||
| a95b1f0dae | |||
| 0a8af94610 | |||
| f1c7b95b33 | |||
| 2c27c0f58a | |||
| e4a7e23c46 | |||
| 6f15adf57f | |||
| 1966b40d01 | |||
| 24ce4d098e | |||
| 98619f9bc9 | |||
| 1efeb2515d | |||
| ccf837b2bf | |||
| e558863743 | |||
| 43f21dfe92 | |||
| 4172eae87e | |||
| 26f2c61068 | |||
| f97bed2607 | |||
| 8ef7155c70 | |||
| f5b2556b70 | |||
| 2c6e8f88fb | |||
| 25068b1e83 | |||
| 1f36eb3a9a | |||
| a9ed577d05 | |||
| 782d614082 | |||
| 0629c00b07 | |||
| b90d5bb205 | |||
| cbc824c80a | |||
| 0c9be40b86 | |||
| c96abd706d | |||
| 0ae5872783 | |||
| 2cff10e0d2 | |||
| cab78f557d | |||
| 04353aa1a5 | |||
| 35121a66e9 | |||
| e726ed2c19 | |||
| 503446afc7 | |||
| 2063f662d3 | |||
| d7381399aa | |||
| d05b497cdb | |||
| ef919be587 | |||
| fff31e0f4f | |||
| cdd6fc7c1e | |||
| 74bc36a2dc | |||
| 48328ae52c | |||
| a86f4f8e23 | |||
| 0a1e048268 | |||
| 6fc5efd6ba | |||
| 2d795b593d | |||
| 20628ec75c | |||
| 10d1ccb009 | |||
| fcc37c9581 | |||
| 43cd391543 | |||
| 18d2df33f7 | |||
| a85daa5617 | |||
| 48dc4ce3e2 | |||
| d07bac89a0 | |||
| 5d32ad6bc4 | |||
| 397b0a3e7e | |||
| 259961632d | |||
| cb1d6382ec | |||
| 8714a69a13 | |||
| 3ae0ea2de7 | |||
| 1879a9f4c7 | |||
| 3938717b04 | |||
| 1208b688f1 | |||
| 0ad7ee5a32 | |||
| 7a4e68e6b9 | |||
| 71222b247f | |||
| 95db811943 | |||
| 2dbc817132 | |||
| 7a8bd96edc | |||
| c5e9686a95 | |||
| c914edf616 | |||
| 656bfcb6bd | |||
| 7434dfe6fa | |||
| e67aa63a50 | |||
| d5e46f2b42 | |||
| 09e6f10b60 |
34
.gitee/ISSUE_TEMPLATE.MD
Normal file
34
.gitee/ISSUE_TEMPLATE.MD
Normal file
@ -0,0 +1,34 @@
|
||||
<!-- 为更高效率地交流并解决问题,请按照以下模板提交issue,感谢! -->
|
||||
|
||||
### 1. 您当前使用的`Go`版本,及系统版本、系统架构?
|
||||
|
||||
<!-- 使用 `go version` 命令查看,期望的结果如:`go 1.12, linux/amd64` -->
|
||||
|
||||
|
||||
### 2. 您当前使用的`GoFrame`框架版本?
|
||||
|
||||
<!-- 框架版本可以查看自己项目下的 `go.mod`,或者框架文件 `version.go` -->
|
||||
|
||||
|
||||
### 3. 更新到最新的框架版本是否能够解决问题?
|
||||
|
||||
<!-- 务必检查是否相同问题已在新版本中已修复 -->
|
||||
|
||||
|
||||
### 4. 问题描述?
|
||||
|
||||
<!--
|
||||
请您尽可能地提供一份最短的,可复现问题的代码。
|
||||
代码尽可能地完整,最好是可以直接编译运行。
|
||||
-->
|
||||
|
||||
|
||||
|
||||
### 5. 您期望得到的结果?
|
||||
|
||||
|
||||
|
||||
### 6. 您实际得到的结果?
|
||||
|
||||
|
||||
|
||||
36
.github/ISSUE_TEMPLATE.MD
vendored
Normal file
36
.github/ISSUE_TEMPLATE.MD
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
<!-- Please answer these questions before submitting your issue. Thanks! -->
|
||||
|
||||
### 1. What version of `Go` and system type/arch are you using?
|
||||
|
||||
<!--
|
||||
Please paste the output of command `go version` from your terminal.
|
||||
What expect to see is like: `go 1.12, linux/amd64`
|
||||
-->
|
||||
|
||||
|
||||
### 2. What version of `GoFrame` are you using?
|
||||
|
||||
<!-- You can find the GF version from your `go.mod`, or from the `version.go` in `GF` -->
|
||||
|
||||
|
||||
### 3. Can this issue be reproduced with the latest release?
|
||||
|
||||
|
||||
|
||||
### 4. What did you do?
|
||||
|
||||
<!--
|
||||
If possible, provide a copy of shortest codes for reproducing the error.
|
||||
A complete runnable program is best.
|
||||
-->
|
||||
|
||||
|
||||
|
||||
### 5. What did you expect to see?
|
||||
|
||||
|
||||
|
||||
### 6. What did you see instead?
|
||||
|
||||
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,7 +7,6 @@
|
||||
.settings/
|
||||
.vscode/
|
||||
vender/
|
||||
log/
|
||||
composer.lock
|
||||
gitpush.sh
|
||||
pkg/
|
||||
|
||||
42
.travis.yml
Normal file
42
.travis.yml
Normal file
@ -0,0 +1,42 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- "1.11.x"
|
||||
- "1.12.x"
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- develop
|
||||
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
|
||||
services:
|
||||
- mysql
|
||||
- redis-server
|
||||
|
||||
addons:
|
||||
hosts:
|
||||
- local
|
||||
|
||||
before_install:
|
||||
- pwd
|
||||
|
||||
install:
|
||||
- cat /etc/hosts
|
||||
|
||||
before_script:
|
||||
- find . -name "*.go" | xargs gofmt -w
|
||||
- git diff --exit-code
|
||||
|
||||
script:
|
||||
- cd g
|
||||
- GOARCH=386 go test -v ./...
|
||||
- GOARCH=amd64 go test -v ./... -race -coverprofile=coverage.txt -covermode=atomic
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
|
||||
|
||||
25
DONATOR.MD
Normal file
25
DONATOR.MD
Normal file
@ -0,0 +1,25 @@
|
||||
# Donators
|
||||
|
||||
|
||||
| Name | Channel | Amount
|
||||
|---|---|---
|
||||
|[hailaz](https://gitee.com/hailaz)|gitee|¥20.00
|
||||
|[ireadx](https://github.com/ireadx)|alipay|¥201.00
|
||||
|[mg91](https://gitee.com/mg91)|gitee|¥10.00
|
||||
|[pibigstar](https://github.com/pibigstar)|alipay|¥10.00
|
||||
|[tiangenglan](https://gitee.com/tiangenglan)|gitee|¥30.00
|
||||
|[wxkj](https://gitee.com/wxkj)|wechat|¥10.00
|
||||
|[zhuhuan12](https://gitee.com/zhuhuan12)|gitee|¥50.00
|
||||
|[zfan_codes](https://gitee.com/zfan_codes)|gitee|¥10.00
|
||||
|[arden](https://github.com/arden)|alipay|¥10.00
|
||||
|x*z|wechat|¥20.00
|
||||
|潘兄|wechat|¥100.00
|
||||
|Fly的狐狸|wechat|¥100.00
|
||||
|土豆相公|alipay|¥66.60
|
||||
|Hades|alipay|¥66.66
|
||||
|蔡蔡|wechat|¥666.00
|
||||
|上海金保证网络科技|bank|¥2000.00
|
||||
|
||||
|
||||
|
||||
<img src="https://goframe.org/images/donate.png"/>
|
||||
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 john@johng.cn http://johng.cn
|
||||
Copyright (c) 2017 john@goframe.org https://goframe.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
278
README.MD
278
README.MD
@ -1,35 +1,52 @@
|
||||
# GoFrame
|
||||
<div align=center>
|
||||
<img src="https://gfer.me/cover.png" width="150"/>
|
||||
<img src="https://goframe.org/logo.png" width="100"/>
|
||||
|
||||
[](https://godoc.org/github.com/gogf/gf/g#pkg-subdirectories)
|
||||
[](https://travis-ci.org/gogf/gf)
|
||||
[](https://goreportcard.com/report/github.com/gogf/gf)
|
||||
[](https://codecov.io/gh/gogf/gf/branch/master)
|
||||
[](https://github.com/gogf/gf)
|
||||
[](https://github.com/gogf/gf)
|
||||
|
||||
</div>
|
||||
|
||||
`GF(GoFrame)` is a modular, lightweight, loosely coupled, high performance application development framework written in Go. Supporting graceful server, hot updates, multi-domain, multi-port, multi-service, HTTP/HTTPS, dynamic/hook routing and many more features. Providing a series of core components and dozens of practical modules.
|
||||
`GF(GoFrame)` is a modular, full-featured and production-ready application development framework of golang. Providing a series of core components and dozens of practical modules, such as: memcache, configure, validator, logging, array/queue/set/map containers, timer/timing tasks, file/memory lock, object pool, database ORM, etc. Supporting web server integrated with router, cookie, session, logger, template, https, hooks, rewrites and many more features.
|
||||
|
||||
|
||||
# Installation
|
||||
```
|
||||
go get -u gitee.com/johng/gf
|
||||
go get -u github.com/gogf/gf
|
||||
```
|
||||
or use `go.mod`:
|
||||
```
|
||||
require github.com/gogf/gf latest
|
||||
```
|
||||
|
||||
# Limitation
|
||||
```
|
||||
golang version >= 1.9.2
|
||||
golang version >= 1.10
|
||||
```
|
||||
|
||||
# Documentation
|
||||
|
||||
* [中文文档](https://gfer.me/)
|
||||
* [APIDoc](https://godoc.org/github.com/gogf/gf)
|
||||
* [中文文档](https://goframe.org)
|
||||
|
||||
# Architecture
|
||||
<div align=center>
|
||||
<img src="https://gfer.me/images/arch.png"/>
|
||||
<img src="https://goframe.org/images/arch.png"/>
|
||||
</div>
|
||||
|
||||
# Quick Start
|
||||
|
||||
## Hello World
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g"
|
||||
"gitee.com/johng/gf/g/net/ghttp"
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/net/ghttp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -40,7 +57,250 @@ func main() {
|
||||
s.Run()
|
||||
}
|
||||
```
|
||||
## Rich Router
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/net/ghttp"
|
||||
"github.com/gogf/gf/g"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/{class}-{course}/:name/*act", func(r *ghttp.Request) {
|
||||
r.Response.Writeln(r.Get("class"))
|
||||
r.Response.Writeln(r.Get("course"))
|
||||
r.Response.Writeln(r.Get("name"))
|
||||
r.Response.Writeln(r.Get("act"))
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
```
|
||||
## Group Routers
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/net/ghttp"
|
||||
)
|
||||
|
||||
type Object struct {}
|
||||
|
||||
func (o *Object) Show(r *ghttp.Request) {
|
||||
r.Response.Writeln("Object Show")
|
||||
}
|
||||
|
||||
func (o *Object) Delete(r *ghttp.Request) {
|
||||
r.Response.Writeln("Object REST Delete")
|
||||
}
|
||||
|
||||
func Handler(r *ghttp.Request) {
|
||||
r.Response.Writeln("Handler")
|
||||
}
|
||||
|
||||
func HookHandler(r *ghttp.Request) {
|
||||
r.Response.Writeln("Hook Handler")
|
||||
}
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
obj := new(Object)
|
||||
group := s.Group("/api")
|
||||
group.ALL ("*", HookHandler, ghttp.HOOK_BEFORE_SERVE)
|
||||
group.ALL ("/handler", Handler)
|
||||
group.ALL ("/obj", obj)
|
||||
group.GET ("/obj/showit", obj, "Show")
|
||||
group.REST("/obj/rest", obj)
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
```
|
||||
or
|
||||
```go
|
||||
func main() {
|
||||
s := g.Server()
|
||||
obj := new(Object)
|
||||
s.Group("/api").Bind([]ghttp.GroupItem{
|
||||
{"ALL", "*", HookHandler, ghttp.HOOK_BEFORE_SERVE},
|
||||
{"ALL", "/handler", Handler},
|
||||
{"ALL", "/obj", obj},
|
||||
{"GET", "/obj/showit", obj, "Show"},
|
||||
{"REST", "/obj/rest", obj},
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
```
|
||||
## Multi ports & domains
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/net/ghttp"
|
||||
)
|
||||
|
||||
func Hello1(r *ghttp.Request) {
|
||||
r.Response.Write("127.0.0.1: Hello1!")
|
||||
}
|
||||
|
||||
func Hello2(r *ghttp.Request) {
|
||||
r.Response.Write("localhost: Hello2!")
|
||||
}
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.Domain("127.0.0.1").BindHandler("/", Hello1)
|
||||
s.Domain("localhost").BindHandler("/", Hello2)
|
||||
s.SetPort(8100, 8200, 8300)
|
||||
s.Run()
|
||||
}
|
||||
```
|
||||
## Template Engine
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/net/ghttp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/template", func(r *ghttp.Request) {
|
||||
r.Response.WriteTpl("index.tpl", g.Map{
|
||||
"id": 123,
|
||||
"name": "john",
|
||||
})
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
```
|
||||
## File Uploading
|
||||
```go
|
||||
func Upload(r *ghttp.Request) {
|
||||
if f, h, e := r.FormFile("upload-file"); e == nil {
|
||||
defer f.Close()
|
||||
name := gfile.Basename(h.Filename)
|
||||
buffer := make([]byte, h.Size)
|
||||
f.Read(buffer)
|
||||
gfile.PutBinContents("/tmp/" + name, buffer)
|
||||
r.Response.Write(name + " uploaded successly")
|
||||
} else {
|
||||
r.Response.Write(e.Error())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## ORM Operations
|
||||
|
||||
### 1. Retrieving instance
|
||||
```go
|
||||
db := g.DB()
|
||||
db := g.DB("user-center")
|
||||
```
|
||||
### 2. Chaining Operations
|
||||
|
||||
`Where + string`
|
||||
```go
|
||||
// SELECT * FROM user WHERE uid>1 LIMIT 0,10
|
||||
r, err := db.Table("user").Where("uid > ?", 1).Limit(0, 10).Select()
|
||||
|
||||
// SELECT uid,name FROM user WHERE uid>1 LIMIT 0,10
|
||||
r, err := db.Table("user").Fileds("uid,name").Where("uid > ?", 1).Limit(0, 10).Select()
|
||||
|
||||
// SELECT * FROM user WHERE uid=1
|
||||
r, err := db.Table("user").Where("u.uid=1",).One()
|
||||
r, err := db.Table("user").Where("u.uid", 1).One()
|
||||
r, err := db.Table("user").Where("u.uid=?", 1).One()
|
||||
// SELECT * FROM user WHERE (uid=1) AND (name='john')
|
||||
r, err := db.Table("user").Where("uid", 1).Where("name", "john").One()
|
||||
r, err := db.Table("user").Where("uid=?", 1).And("name=?", "john").One()
|
||||
// SELECT * FROM user WHERE (uid=1) OR (name='john')
|
||||
r, err := db.Table("user").Where("uid=?", 1).Or("name=?", "john").One()
|
||||
```
|
||||
`Where + map`
|
||||
```go
|
||||
// SELECT * FROM user WHERE uid=1 AND name='john'
|
||||
r, err := db.Table("user").Where(g.Map{"uid" : 1, "name" : "john"}).One()
|
||||
// SELECT * FROM user WHERE uid=1 AND age>18
|
||||
r, err := db.Table("user").Where(g.Map{"uid" : 1, "age>" : 18}).One()
|
||||
```
|
||||
`Where + struct/*struct`
|
||||
```go
|
||||
type User struct {
|
||||
Id int `json:"uid"`
|
||||
UserName string `gconv:"name"`
|
||||
}
|
||||
// SELECT * FROM user WHERE uid =1 AND name='john'
|
||||
r, err := db.Table("user").Where(User{ Id : 1, UserName : "john"}).One()
|
||||
// SELECT * FROM user WHERE uid =1
|
||||
r, err := db.Table("user").Where(&User{ Id : 1}).One()
|
||||
```
|
||||
### 3. Update & Delete
|
||||
```go
|
||||
// UPDATE user SET name='john guo' WHERE name='john'
|
||||
r, err := db.Table("user").Data(gdb.Map{"name" : "john guo"}).Where("name=?", "john").Update()
|
||||
r, err := db.Table("user").Data("name='john guo'").Where("name=?", "john").Update()
|
||||
// UPDATE user SET status=1 ORDER BY login_time asc LIMIT 10
|
||||
r, err := db.Table("user").Data("status", 1).OrderBy("login_time asc").Limit(10).Update
|
||||
|
||||
// DELETE FROM user WHERE uid=10
|
||||
r, err := db.Table("user").Where("uid=?", 10).Delete()
|
||||
// DELETE FROM user ORDER BY login_time asc LIMIT 10
|
||||
r, err := db.Table("user").OrderBy("login_time asc").Limit(10).Delete()
|
||||
```
|
||||
### 4. Insert & Replace & Save
|
||||
```go
|
||||
r, err := db.Table("user").Data(g.Map{"name": "john"}).Insert()
|
||||
r, err := db.Table("user").Data(g.Map{"uid": 10000, "name": "john"}).Replace()
|
||||
r, err := db.Table("user").Data(g.Map{"uid": 10001, "name": "john"}).Save()
|
||||
```
|
||||
### 5. Transaction
|
||||
```go
|
||||
if tx, err := db.Begin(); err == nil {
|
||||
r, err := tx.Save("user", g.Map{
|
||||
"uid" : 1,
|
||||
"name" : "john",
|
||||
})
|
||||
tx.Commit()
|
||||
}
|
||||
```
|
||||
### 6. Error Handling
|
||||
```go
|
||||
func GetOrderInfo(id int) (order *Order, err error) {
|
||||
err = g.DB().Table("order").Where("id", id).Struct(&order)
|
||||
if err != nil && err == sql.ErrNoRows {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
[More Features...](https://goframe.org/start/index)
|
||||
|
||||
|
||||
# License
|
||||
|
||||
GF is licensed under the [MIT License](LICENSE), 100% free and open-source.
|
||||
`GF` is licensed under the [MIT License](LICENSE), 100% free and open-source, forever.
|
||||
|
||||
# Donators
|
||||
|
||||
We currently accept donation by Alipay/WechatPay, please note your github/gitee account in your payment bill. If you like `GF`, why not [buy developer a cup of coffee](DONATOR.MD)?
|
||||
|
||||
# Thanks
|
||||
<a href="https://www.jetbrains.com/?from=GoFrame"><img src="https://goframe.org/images/jetbrains.png" width="100" alt="JetBrains"/></a>
|
||||
|
||||
|
||||
<!--
|
||||
# Sponsor
|
||||
We appreciate any kind of sponsorship for `GF` development. If you've got some interested, please contact john@goframe.org.
|
||||
-->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
65
README_ZH.MD
65
README_ZH.MD
@ -1,40 +1,64 @@
|
||||
# GoFrame
|
||||
<div align=center>
|
||||
<img src="https://gfer.me/cover.png" width="150"/>
|
||||
<img src="https://goframe.org/logo.png" width="100"/>
|
||||
|
||||
[](https://godoc.org/github.com/gogf/gf/g#pkg-subdirectories)
|
||||
[](https://travis-ci.org/gogf/gf)
|
||||
[](https://goreportcard.com/report/github.com/gogf/gf)
|
||||
[](https://codecov.io/gh/gogf/gf/branch/master)
|
||||
[](https://github.com/gogf/gf)
|
||||
[](https://github.com/gogf/gf)
|
||||
|
||||
</div>
|
||||
|
||||
`GF(Go Frame)`是一款模块化、高性能、生产级Go应用开发框架。提供了常用的核心开发组件,如:缓存、日志、文件、时间、队列、数组、集合、字符串、定时器、命令行、文件锁、内存锁、对象池、连接池、数据校验、数据编码、文件监控、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信、
|
||||
并发安全容器等等。并提供了Web服务开发的系列核心组件,如:Router、Cookie、Session、服务注册、配置管理、模板引擎等等,支持热重启、热更新、多域名、多端口、多服务、HTTPS、Rewrite等特性。
|
||||
|
||||
`GF(Go Frame)`是一款模块化、松耦合、轻量级、高性能的Go应用开发框架。支持热重启、热更新、多域名、多端口、多服务、HTTP/HTTPS、动态路由等特性
|
||||
,并提供了Web服务开发的系列核心组件,如:Router、Cookie、Session、服务注册、配置管理、模板引擎、数据校验、分页管理、数据库ORM等等等等,
|
||||
并且提供了数十个内置核心开发模块集,如:缓存、日志、时间、命令行、二进制、文件锁、内存锁、对象池、连接池、数据编码、进程管理、进程通信、文件监控、定时任务、TCP/UDP组件、
|
||||
并发安全容器等等等等等等。
|
||||
|
||||
# 特点
|
||||
* 模块化、松耦合设计;
|
||||
* 模块丰富,开箱即用;
|
||||
* 详尽的开发文档及示例;
|
||||
* 完善的本地中文化支持;
|
||||
* 致力于项目的通用方案;
|
||||
* 更适合企业及团队使用;
|
||||
* 更多请查阅文档及源码;
|
||||
|
||||
# 安装
|
||||
```html
|
||||
go get -u gitee.com/johng/gf
|
||||
go get -u github.com/gogf/gf
|
||||
```
|
||||
或者
|
||||
`go.mod`:
|
||||
```
|
||||
require github.com/gogf/gf latest
|
||||
```
|
||||
|
||||
# 限制
|
||||
```shell
|
||||
golang版本 >= 1.9.2
|
||||
golang版本 >= 1.10
|
||||
```
|
||||
|
||||
# 架构
|
||||
<div align=center>
|
||||
<img src="https://gfer.me/images/arch.png"/>
|
||||
<img src="https://goframe.org/images/arch.png"/>
|
||||
</div>
|
||||
|
||||
|
||||
# 文档
|
||||
[https://gfer.me](https://gfer.me)
|
||||
|
||||
# 文档
|
||||
|
||||
开发文档:[https://goframe.org](https://goframe.org)
|
||||
|
||||
接口文档:[https://godoc.org/github.com/gogf/gf](https://godoc.org/github.com/gogf/gf)
|
||||
|
||||
# 使用
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g"
|
||||
"gitee.com/johng/gf/g/net/ghttp"
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/net/ghttp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -44,4 +68,19 @@ func main() {
|
||||
})
|
||||
s.Run()
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
[更多..](https://goframe.org/start/index)
|
||||
|
||||
|
||||
# 协议
|
||||
|
||||
`GF` 使用非常友好的 [MIT](LICENSE) 开源协议进行发布,永久`100%`开源免费。
|
||||
|
||||
# 捐赠
|
||||
|
||||
如果您喜欢`GF`,要不[给开发者来杯咖啡吧](DONATOR.MD)!
|
||||
请在捐赠时备注您的`github`/`gitee`账号名称。
|
||||
|
||||
# 感谢
|
||||
<a href="https://www.jetbrains.com/?from=GoFrame"><img src="https://goframe.org/images/jetbrains.png" width="100" alt="JetBrains"/></a>
|
||||
240
RELEASE.MD
240
RELEASE.MD
@ -1,19 +1,229 @@
|
||||
# `v1.7.0`
|
||||
## 新功能/改进
|
||||
1. 重构改进`glog`模块:
|
||||
- 去掉日志模块所有的锁机制,改为无锁设计,执行性能更加高效
|
||||
- 增加日志内容的异步输出特性:https://goframe.org/os/glog/async
|
||||
- 增加日志输出内容的`Json`格式支持:https://goframe.org/os/glog/json
|
||||
- 增加`Flags`额外特性支持,包括文件行号打印、自定义时间格式、异步输出等特性控制:https://goframe.org/os/glog/flags
|
||||
- 增加`Writer`接口支持,便于开发者进行自定义的日志功能扩展,或者与第三方服务/模块对接集成:https://goframe.org/os/glog/writer
|
||||
- 修改`SetStdPrint`方法名为`SetStdoutPrint`
|
||||
- 修改链式方法`StdPrint`方法名为`Stdout`
|
||||
- 标记淘汰`*fln`日志输出方法,`*f`方法支持自动的换行输出
|
||||
- 新增更多的链式方法支持:https://goframe.org/os/glog/chain
|
||||
1. 重构改进`gmap`模块:
|
||||
- 增加更多数据格式支持:`HashMap`/`ListMap`/`TreeMap`
|
||||
- 简化类型名称,如`gmap.StringInterfaceMap`简化为`gmap.StrAnyMap`
|
||||
- 改进`Map/Keys/Values`方法以提高性能
|
||||
- 修改`BatchSet`/`BatchRemove`方法名为`Sets`/`Removes`
|
||||
- 新增更多功能方法支持:https://goframe.org/container/gmap/index
|
||||
1. 改进`gtime`时间模块:
|
||||
- 增加并完善更多的类`PHP`时间格式支持
|
||||
- 新增更多功能方法,如`FormatTo`/`LayoutTo`等等
|
||||
- 详见开发文档:https://goframe.org/os/gtime/index
|
||||
1. 改进`gdb`数据库模块:
|
||||
- 增加对继承结构体的数据转换支持:https://goframe.org/database/gdb/senior
|
||||
- 新增`GetLastSql`方法,用以在调试模式下获取最近一条执行的SQL语句
|
||||
- 其他的细节处理改进
|
||||
1. 改进`gtcp`通信模块:
|
||||
- 完善处理细节,提高通信性能;
|
||||
- 增加`TLS`服务端/客户端通信支持:https://goframe.org/net/gtcp/tls
|
||||
- 增加简单协议支持,便于开发者封包/解包,并解决粘包/半包问题:https://goframe.org/net/gtcp/conn/pkg
|
||||
- TCP服务端增加`Close`方法
|
||||
- 更多细节查看开发文档:https://goframe.org/net/gtcp/index
|
||||
1. 改进`gconv`类型转换模块
|
||||
- 修改`gconv.TimeDuration`转换方法名称为`gconv.Duration`
|
||||
- 新增`gconv.StructDeep`及`gconv.MapDeep`方法,支持递归转换
|
||||
- 详见开发文档:https://goframe.org/util/gconv/struct
|
||||
1. 改进`ghttp`模块:
|
||||
- 日志输出增加`http/https`字段:https://goframe.org/net/ghttp/logs
|
||||
- 新增`ghttp.Server.SetKeepAlive`设置方法,用以开启/关闭`KeepAlive`特性
|
||||
- 增加`ghttp.Request.GetUrl`方法,用以获取当前完整的URL请求地址
|
||||
- `ghttp.Client`客户端支持开发者自定义`Transport`属性,`ghttp.Client.Post`方法支持`浏览器模式`:https://goframe.org/net/ghttp/client
|
||||
1. 新增`gtree`树形数据结构容器支持:https://goframe.org/container/gtree/index
|
||||
1. 改进`gudp`通信模块,具体请参考开发文档:https://goframe.org/net/gudp/index
|
||||
1. 改进`gcfg`配置管理模块,所有`Get*`方法增加默认值支持:https://goframe.org/os/gcfg/index
|
||||
1. `gredis`模块新增`DoVar`/`ReceiveVar`方法以便于开发者对执行结果进行灵活的数据格式转换:https://goframe.org/database/gredis/index
|
||||
1. `gcache`模块`BatchSet`/`BatchRemove`方法名修改为`Sets`/`Removes`
|
||||
1. 改进`gjson`/`gparser`模块,增加更多方法:https://goframe.org/encoding/gjson/index
|
||||
1. 改进`gfile.MainPkgPath`方法,以支持不同平台的开发环境;
|
||||
1. 改进`grpool`协程池模块,提高执行性能:https://goframe.org/os/grpool/index
|
||||
1. 改进`TryCatch`方法,当开发者不传递`Catch`参数时,默认抑制并忽略错误的处理
|
||||
1. 改进`gmlock`模块,增加`TryLockFunc`/`TryRLockFunc`方法,并且为`gmlock.Mutex`高级互斥锁对象增加`TryLockFunc`/`TryRLockFunc`方法
|
||||
1. 去除`gvar.VarRead`接口类型支持
|
||||
|
||||
## Bug Fix
|
||||
1. 解决`gdb`模块与其他第三方`ORM`模块同时使用的冲突;
|
||||
1. 修复`gcron.AddOnce`方法的细节逻辑问题;
|
||||
1. 修复内部`empty`模块的`IsEmpty`方法对结构体属性的空校验错误;
|
||||
1. 修复`gview`模板引擎的并发安全问题;
|
||||
1. 修复`ghttp.Server`的SESSION初始化过期时间问题;
|
||||
|
||||
# `v1.6.0` (2019-04-09)
|
||||
|
||||
## 新功能/改进
|
||||
1. `gcron`定时任务模块增加运行日志记录功能:https://goframe.org/os/gcron/index
|
||||
1. `gredis`增加全局分组配置功能,并增加更多的配置选项`maxIdle/maxActive/idleTimeout/maxConnLifetime`:https://goframe.org/database/gredis/index
|
||||
1. `gcfg`模块增加更多的默认配置文件检索路径,并且增加全局分组配置特性,增加`Instance`单例方法:https://goframe.org/os/gcfg/index
|
||||
1. `gview`模块增加更多的默认配置文件检索路径,并且增加`Instance`单例方法:https://goframe.org/os/gview/index
|
||||
1. `ghttp`模块新功能及改进:
|
||||
- 新增`CORS`HTTP(S)跨域请求特性: https://goframe.org/net/ghttp/cors
|
||||
- 增加`TLSConfig`配置功能;
|
||||
- 去掉路由注册方法的`error`返回值,当产生注册错误时直接终端打印错误/输出到日志文件;
|
||||
- 增加在`HTTP Code 302`跳转时的`Set-Cookie`支持;
|
||||
- 增加对`SESSION ID`的安全性检查;
|
||||
- 增加对基于`HTTPS`的`WebSocket`支持(`WSS`):https://goframe.org/net/ghttp/websocket/index
|
||||
- `Request`对象增加`Error`方法,用于输出自定义错误信息到`WebServer`错误日志中;
|
||||
- 其他一些改进;
|
||||
1. `gdb`模块新功能及改进:
|
||||
- 新增`Instance`单例管理方法;
|
||||
- 新增`Structs/Scan`链式操作方法,`gdb.DB/TX`新增`GetStructs/GetScan`方法,用于结果集`struct`/`slice`映射转换:https://goframe.org/database/gdb/chaining
|
||||
- 新增`Safe`链式操作方法(默认非并发安全),用于链式安全控制:https://goframe.org/database/gdb/chaining
|
||||
- `Where`链式操作方法改进:
|
||||
- 方法支持任意的`string/map/slice/struct/*struct`类型;
|
||||
- 逻辑调整,当链式操作中存在多个`Where`方法调用时,自动转换为`And`条件;
|
||||
- 支持`slice`条件参数,常用在`SELECT IN`查询中,例如:`Where("uid IN(?)", g.Slice{1,2,3})`;
|
||||
- 支持在`map`类型条件参数的`key`中传递条件,例如:`Where(g.Map{"uid>?", uid})`;
|
||||
1. `gconv`及`gvalid`模块改进并去掉对私有`struct`方法属性的转换/校验;
|
||||
1. `gconv.Map`转换方法新增对`json tag`: `-`, `omitempty`的支持: https://goframe.org/util/gconv/map
|
||||
1. `gstr`模块新增 `ReplaceI/ReplaceIByArray/ReplaceIByMap`大小写非敏感替换方法;
|
||||
1. `gutil`模块增加`IsEmpty`方法,用于判断给定变量是否为空(整型0, 布尔false, slice/map长度为0, 其他为nil的情况,判断为空),并增加快捷方法`g.IsEmpty`;
|
||||
1. `gutil`模块增加`Export`方法,用于导出返回格式化打印的变量内容字符串,并增加快捷方法`g.Export`;
|
||||
1. `gspath`增加缓存及非缓存检索检索方法`Search`/`SearchWithCache`;
|
||||
1. `gjson`模块增加默认的`UseNumber`功能支持;
|
||||
1. `gmap`增加`SetIfNotExistFunc/SetIfNotExistFuncLock`方法;
|
||||
1. 迁移`greuseport`模块到新的仓库:https://github.com/gogf/greuseport
|
||||
1. 大量的单元测试完善;
|
||||
|
||||
## Bug Fix
|
||||
1. 修复`gqueue`模块的资源竞争问题;
|
||||
1. 修复`gconv.GTime`转换失败问题;
|
||||
1. 修复`gconv.String`在转换`int`参数时字节溢出问题;
|
||||
1. 修复`ghttp.Request`的`HTTP Basic Auth`校验问题;
|
||||
1. 修复`gxml`针对于非`UTF-8`编码内容转换的并发安全问题;
|
||||
1. 修复`gtime`部分`Format`(`G`&`j`)格式失效问题;
|
||||
1. 修复`gudp.Conn`对象的`RemoteAddr`获取客户端连接地址方法问题;
|
||||
1. 修复`gmap/gcache`模块的`GetOrSetFuncLock`方法,增加对回调方法返回值的`nil`判断,只有非nil返回值才会被保存;
|
||||
|
||||
|
||||
|
||||
# `v1.5.8` (2019-02-28)
|
||||
|
||||
## 新特性
|
||||
1. 主库从`gitee`迁移到了`github`( https://github.com/gogf/gf ),`gitee`作为镜像站,用于国内的代码贡献及ISSUE提交,迁移说明详见:https://goframe.org/upgradeto150
|
||||
1. 对常用的`container`数组模块: `garray`做了大量改进/完善工作,新增大量常用方法,并完善单元测试用例及方法注释,详见API文档:https://godoc.org/github.com/gogf/gf/g/container/garray
|
||||
1. 对常用的`container`集合模块: `gset`做了大量改进/完善工作,新增大量常用方法,并完善单元测试用例及方法注释,详见API文档:https://godoc.org/github.com/gogf/gf/g/container/gset
|
||||
1. 对常用的`container`MAP模块: `gmap`做了大量改进/完善工作,新增大量常用方法,并完善单元测试用例及方法注释,详见API文档:https://godoc.org/github.com/gogf/gf/g/container/gmap
|
||||
1. 对常用的字符串模块: `gstr`做了大量改进/完善工作,新增大量常用方法,并完善单元测试用例及方法注释,详见API文档:https://godoc.org/github.com/gogf/gf/g/text/gstr
|
||||
1. 改进`gform`中对`struct`/`*struct`参数的支持,`*Insert/*Save/*Replace/*Update/Where/Data`方法的参数调整为`interface{}`类型,并支持任意类型的: `string/map/slice/struct/*struct`参数传递,具体请参考:https://goframe.org/database/orm/chaining
|
||||
1. 新增/完善若干模块的单元测试用例, 包括:`gvalid`/`gregex`/`garray`/`gset`/`gmap`/`gstr`/`gconv`/`ghttp`/`gdb`;
|
||||
1. 由于`gkafka`模块比较重,且不是框架核心模块,因此将该模块迁移到新的仓库中独立管理,并去掉相关依赖包:https://github.com/gogf/gkafka
|
||||
1. 新增`greuseport`模块,用以实现TCP的`REUSEPORT`特性:https://godoc.org/github.com/gogf/gf/g/net/greuseport
|
||||
|
||||
## 新功能/改进
|
||||
1. 去掉模板引擎内置变量中自动初始化`session`对象带来的内存占用问题;
|
||||
1. `ghttp.Client`改进,增加若干方法,详见:https://goframe.org/net/ghttp/client
|
||||
1. `ghttp`分组路由增加`COMMON`方法,用以注册常用的`HTTP METHOD`(`GET/PUT/POST/DELETE`)路由;
|
||||
1. 更新框架依赖的`golang.org/x/sys`模块;
|
||||
1. 改进`gform`的批量操作(`Batch*`操作)返回结果对象,可以通过该结果对象获得批量操作准确的受影响记录行数;
|
||||
1. 将`gstr`/`gregex`模块从`util`分类迁移到了`text`分类目录下;
|
||||
1. 将`gtest`模块从`util`分类迁移到了`test`分类目录下;
|
||||
1. 完善`glog`方法注释;
|
||||
|
||||
## Bug Fix
|
||||
1. 修复带点的邮件格式,用`gvalid.Check`的"`email`"规则不能匹配成功;
|
||||
1. 修复`gvalid.Check`在`regex`规则下的检查失败问题;
|
||||
1. 修复`gcron`模块定时规则中天和周不允许`?`符号的问题;
|
||||
1. 修复`ghttp.Server`在部分异常情况下仍然返回`200`状态码的问题;
|
||||
1. 修复`gfpool`模块中由于原子操作问题造成的高并发"内存泄露"问题;
|
||||
1. 修复分组路由注册对象/控制时,方法`Index`的路由仅能通过`/xxx/index`访问的问题;
|
||||
1. 修复模板引擎使用中,当不存在`config.toml`(即使没使用)配置文件时的报错问题;
|
||||
1. 其他一些修复;
|
||||
|
||||
|
||||
|
||||
# `v1.4.6` (2019-01-24)
|
||||
|
||||
## 新特性
|
||||
1. 新增并发安全的高性能任务定时器模块`gtimer`, 类似于Java的`Timer`,但是比较于Java的`Timer`更加强大,内部实现采用灵活高效的`分层时间轮`设计,被设计为可管理维护百万级别以上数量的定时任务。`gtimer`为`GF`框架的核心模块之一,单元测试覆盖率达到`93.6%`:[https://goframe.org/os/gtimer/index](https://goframe.org/os/gtimer/index)
|
||||
1. 采用任务定时器`gtimer`重构`gcron`定时任务模块,去掉第三方`github.com/robfig/cron`包的使用。`gcron`增加单例模式的定时任务:[https://goframe.org/os/gcron/index#](https://goframe.org/os/gcron/index#);
|
||||
1. `gconv`类型转换模块支持对`struct`结构体中的**指针属性**转换:[https://goframe.org/util/gconv/struct](https://goframe.org/util/gconv/struct);
|
||||
1. `gform`增加对数据库类型的自动识别特性,这一特性在需要将查询结果`json`编码返回时非常有用: [https://goframe.org/database/orm/index](https://goframe.org/database/orm/index)
|
||||
1. `Travis CI`增加对`386`架构的自动化测试支持(目前已支持`386`和`amd64`);
|
||||
|
||||
## 新功能
|
||||
1. `ghttp`模块新增`Exit`、`ExitAll`、`ExitHook`方法,用于HTTP请求处理流程控制: [https://goframe.org/net/ghttp/service/object](https://goframe.org/net/ghttp/service/object);
|
||||
1. `grand`模块增加`Meet/MeetProb`方法,用于给定概率的随机满足判断,增加别名方法`N/Str/Digits/Letters`;
|
||||
1. `gvalid`数据/表单校验模块增加`16X`及`19X`手机号的校验支持;
|
||||
|
||||
## 功能改进
|
||||
1. `gform`设置默认的数据库连接池`CONN_MAX_LIFE`参数值为`30`秒;
|
||||
1. 改进`glist`模块,提高约`20%`左右性能,并增加若干链表操作方法;
|
||||
1. 改进`gqueue`模块,提高约`50`左右性能,并增加模块对`select`语法的支持(使用`Queue.C`): [https://goframe.org/container/gqueue/index](https://goframe.org/container/gqueue/index);
|
||||
1. 改进`gmlock`内存锁模块,并完善单元测试用例:[https://goframe.org/os/gmlock/index](https://goframe.org/os/gmlock/index);
|
||||
1. 改进并发安全容器所有的模块,调整并发安全控制非必需参数`safe...bool`为`unsafe...bool`;
|
||||
1. 改进`gpool`对象复用模块,支持并发安全;
|
||||
1. 更新`gkafka`模块的第三方依赖包;
|
||||
1. 完善`ghttp`模块的单元测试用例;
|
||||
|
||||
|
||||
## Bug Fix
|
||||
1. 修复`gmd5`模块操作文件时的文件指针未关闭问题;
|
||||
1. 修复`gcache`缓存项过期删除失效问题;
|
||||
1. 其他修复;
|
||||
|
||||
# `v1.3.8` (2018-12-26)
|
||||
|
||||
## 新特性
|
||||
1. 对`gform`完成重构,以提高扩展性,并修复部分细节问题、完善单元测试用例([https://goframe.org/database/orm/index](https://goframe.org/database/orm/index));
|
||||
1. `WebServer`路由注册新增分组路由特性([https://goframe.org/net/ghttp/group](https://goframe.org/net/ghttp/group));
|
||||
1. `WebServer`新增`Rewrite`路由重写特性([https://goframe.org/net/ghttp/static](https://goframe.org/net/ghttp/static));
|
||||
1. 增加框架运行时对开发环境的自动识别;
|
||||
1. 增加了`Travis CI`自动化构建/测试;
|
||||
|
||||
## 新功能
|
||||
1. 改进`WebServer`静态文件服务功能,增加`SetStaticPath`/`AddStaticPath`方法([https://goframe.org/net/ghttp/static](https://goframe.org/net/ghttp/static));
|
||||
1. `gform`新增`Filter`链式操作方法,用于过滤参数中的非表字段键值对([https://goframe.org/database/orm/linkop](https://goframe.org/database/orm/linkop));
|
||||
1. `gcache`新增`Data`方法,用以获取所有的缓存数据项;
|
||||
1. `gredis`增加`GetConn`方法获取原生redis连接对象;
|
||||
|
||||
## 功能改进
|
||||
1. 改进`gform`的`Where`方法,支持`slice`类型的参数,并更方便地支持`in`操作查询([https://goframe.org/database/orm/linkop](https://goframe.org/database/orm/linkop));
|
||||
1. 改进`gproc`进程间通信数据结构,将`pid`字段从`16bit`扩展为`24bit`;
|
||||
1. 改进`gconv`/`gmap`/`garray`,增加若干操作方法;
|
||||
1. 改进`gview`模板引擎中的`date`内置函数,当给定的时间戳为空时打印当前的系统时间;
|
||||
1. 改进`gview`模板引擎中,当打印的变量不存在时,显示为空(标准库默认显示为`<no value>`);
|
||||
1. 改进`WebServer`,去掉`HANGUP`的信号监听,避免程序通过`nohup`运行时产生异常退出问题;
|
||||
1. 改进`gcache`性能,并完善基准测试;
|
||||
|
||||
## Bug Fix
|
||||
1. 修复`gcache`在非LRU特性开启时的缓存关闭资源竞争问题,并修复`doSetWithLockCheck`内部方法的返回值问题;
|
||||
1. 修复`grand.intn`内部方法在`x86`架构下的随机数位溢出问题;
|
||||
1. 修复`gbinary`中`Int`方法针对`[]byte`参数长度自动匹配造成的字节长度溢出问题;
|
||||
1. 修复`gjson`由于官方标准库`json`不支持`map[interface{}]*`类型造成的Go变量编码问题;
|
||||
1. 修复`garray`中部分方法的数据竞争问题,修复二分查找排序问题;
|
||||
1. 修复`ghttp.Request.GetVar`方法获取参数问题;
|
||||
1. 修复`gform`的数据库连接池不起作用的问题;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# `v1.2.11` (2018-11-26)
|
||||
## 新特性
|
||||
1. `ORM`新增对`SQLServer`及`Oracle`的支持([https://gfer.me/database/orm/database](https://gfer.me/database/orm/database));
|
||||
1. 完成`gvalid`模块校验结果的顺序特性([https://gfer.me/util/gvalid/checkmap](https://gfer.me/util/gvalid/checkmap));
|
||||
1. 改进`ghttp.Request.Exit`,使得调用该方法时立即退出业务执行,开发者无需调用`Exit`方法时再使用`return`返回([https://gfer.me/net/ghttp/service/object](https://gfer.me/net/ghttp/service/object));
|
||||
1. 模板引擎新增若干内置函数:`text/html/htmldecode/url/urldecode/date/compare/substr/strlimit/hidestr/highlight/toupper/tolower/nl2br` ([https://gfer.me/os/gview/funcs](https://gfer.me/os/gview/funcs));
|
||||
1. 模板引擎新增内置变量`Config` ([https://gfer.me/os/gview/vars](https://gfer.me/os/gview/vars));
|
||||
1. `ORM`新增对`SQLServer`及`Oracle`的支持([https://goframe.org/database/orm/database](https://goframe.org/database/orm/database));
|
||||
1. 完成`gvalid`模块校验结果的顺序特性([https://goframe.org/util/gvalid/checkmap](https://goframe.org/util/gvalid/checkmap));
|
||||
1. 改进`ghttp.Request.Exit`,使得调用该方法时立即退出业务执行,开发者无需调用`Exit`方法时再使用`return`返回([https://goframe.org/net/ghttp/service/object](https://goframe.org/net/ghttp/service/object));
|
||||
1. 模板引擎新增若干内置函数:`text/html/htmldecode/url/urldecode/date/compare/substr/strlimit/hidestr/highlight/toupper/tolower/nl2br` ([https://goframe.org/os/gview/funcs](https://goframe.org/os/gview/funcs));
|
||||
1. 模板引擎新增内置变量`Config` ([https://goframe.org/os/gview/vars](https://goframe.org/os/gview/vars));
|
||||
1. 改进`gconv.Struct`转换默认规则,支持不区分大小写的键名与属性名称匹配;
|
||||
1. `gform`配置文件支持`linkinfo`自定义数据库连接字段([https://gfer.me/database/orm/config](https://gfer.me/database/orm/config));
|
||||
1. `gfsnotify`模块增加对特定回调的取消注册功能([https://gfer.me/os/gfsnotify/index](https://gfer.me/os/gfsnotify/index));
|
||||
1. `gform`配置文件支持`linkinfo`自定义数据库连接字段([https://goframe.org/database/orm/config](https://goframe.org/database/orm/config));
|
||||
1. `gfsnotify`模块增加对特定回调的取消注册功能([https://goframe.org/os/gfsnotify/index](https://goframe.org/os/gfsnotify/index));
|
||||
|
||||
|
||||
|
||||
## 新功能
|
||||
1. 改进`ghttp.Request`,增加`SetParam/GetParam`请求流程自定义变量设置/获取方法,用于在请求流程中的回调函数共享变量([https://gfer.me/net/ghttp/request](https://gfer.me/net/ghttp/request));
|
||||
1. 改进`ghttp.Response`,增加`ServeFileDownload`方法,用于WebServer引导客户端下载文件([https://gfer.me/net/ghttp/response](https://gfer.me/net/ghttp/response));
|
||||
1. 改进`ghttp.Request`,增加`SetParam/GetParam`请求流程自定义变量设置/获取方法,用于在请求流程中的回调函数共享变量([https://goframe.org/net/ghttp/request](https://goframe.org/net/ghttp/request));
|
||||
1. 改进`ghttp.Response`,增加`ServeFileDownload`方法,用于WebServer引导客户端下载文件([https://goframe.org/net/ghttp/response](https://goframe.org/net/ghttp/response));
|
||||
1. `gvar`模块新增`gvar.VarRead`只读接口,用于控制对外只暴露数据读取功能;
|
||||
1. 增加`g.Throw`抛异常方法,`g.TryCatch`异常捕获方法封装;
|
||||
1. 改进`gcron`模块,增加自定义的Cron管理对象,增加`New/Start/Stop`方法;
|
||||
@ -21,9 +231,9 @@
|
||||
|
||||
## 功能改进
|
||||
1. WebServer添加`RouterCacheExpire`配置参数,用于设置路由检索缓存过期时间;
|
||||
1. WebServer允许同一`HOOK`事件被多次绑定注册,先注册的回调函数优先级更高([https://gfer.me/net/ghttp/service/hook](https://gfer.me/net/ghttp/service/hook));
|
||||
1. WebServer允许同一`HOOK`事件被多次绑定注册,先注册的回调函数优先级更高([https://goframe.org/net/ghttp/service/hook](https://goframe.org/net/ghttp/service/hook));
|
||||
1. 当前工作目录为系统临时目录时,`gcfg`/`gview`/`ghttp`模块默认不添加工作目录到搜索路径;
|
||||
1. 改进`WebSocket`默认支持跨域请求([https://gfer.me/net/ghttp/websocket](https://gfer.me/net/ghttp/websocket));
|
||||
1. 改进`WebSocket`默认支持跨域请求([https://goframe.org/net/ghttp/websocket](https://goframe.org/net/ghttp/websocket));
|
||||
1. 改进`gtime.Format`支持中文;
|
||||
1. 改进`gfsnotify`,支持编辑器对文件非执行标准编辑时(RENAME+CHMOD)的热更新问题;
|
||||
1. 改进`gtype.Set`方法,增加Set原子操作返回旧的变量值;
|
||||
@ -32,12 +242,12 @@
|
||||
1. `gstr`模块增加对中文截取方法;
|
||||
1. 改进`gtime.StrToTime`对常用时间格式匹配模式,新增`gtime.ParseTimeFromContent`方法;
|
||||
1. 修改配置管理、模板引擎、调试模式的环境变量名称为大写下划线标准格式;
|
||||
1. 改进`grand`模块随机数生成设计,底层使用`crypto/rand`+缓冲区实现高速的随机数生成([https://gfer.me/util/grand/index](https://gfer.me/util/grand/index));
|
||||
1. 改进`grand`模块随机数生成设计,底层使用`crypto/rand`+缓冲区实现高速的随机数生成([https://goframe.org/util/grand/index](https://goframe.org/util/grand/index));
|
||||
|
||||
## 问题修复
|
||||
1. 修复`gspath`模块在`windows`下搜索失效问题;
|
||||
1. 修复`gspath`模块Search时带有indexFiles的检索问题;
|
||||
1. bug fix INZS1([https://gitee.com/johng/gf/issues/INZS1](https://gitee.com/johng/gf/issues/INZS1));
|
||||
1. bug fix INZS1([https://github.com/gogf/gf/issues/INZS1](https://github.com/gogf/gf/issues/INZS1));
|
||||
1. 修复`gproc.ShellRun`在windows下的执行问题;
|
||||
|
||||
|
||||
@ -276,8 +486,8 @@
|
||||
12、gdb数据库ORM包增加And/Or条件链式方法,并改进Where/Data方法参数灵活性;
|
||||
13、对于新增加的模块,同时也增加了对应的开发文档,并梳理完善了现有的其他模块开发文档;
|
||||
14、修复ISSUE:
|
||||
#IISWI gitee.com/johng/gf/issues/IISWI,
|
||||
#IISMY gitee.com/johng/gf/issues/IISMY,
|
||||
#IISWI github.com/gogf/gf/issues/IISWI,
|
||||
#IISMY github.com/gogf/gf/issues/IISMY,
|
||||
反馈并跟踪完成第三方依赖mxj包的ISSUE修复(github.com/clbanning/mxj/issues/48);
|
||||
|
||||
|
||||
|
||||
60
TODO.MD
60
TODO.MD
@ -1,27 +1,15 @@
|
||||
# ON THE WAY
|
||||
1. orm增加更多数据库支持;
|
||||
1. 增加对于数据表Model的封装;
|
||||
1. 更多数据库的ORM功能支持;
|
||||
1. 考虑gdb对象管理增加二级连接池特性,提高New&Close性能;
|
||||
1. 增加图形验证码支持,至少支持数字和英文字母;
|
||||
1. 增加热编译工具,提高开发环境的开发/测试效率(媲美PHP开发效率);
|
||||
1. 增加可选择性的orm tag特性,用以数据表记录与struct对象转换的键名属性映射;
|
||||
1. ghttp.Response增加输出内容后自动退出当前请求机制,不需要用户手动return,参考beego如何实现;
|
||||
1. Cookie&Session数据池化处理;
|
||||
1. ghttp.Client增加proxy特性;
|
||||
1. gtime增加对时区转换的封装,并简化失去转换时对类似+80500时区的支持;
|
||||
1. orm增加sqlite对Save方法的支持(去掉触发器语句);
|
||||
1. ghttp.Server增加Ip访问控制功能(DenyIps&AllowIps);
|
||||
1. ghttp路由功能增加分组路由特性;
|
||||
1. ghttp增加返回数据压缩机制;
|
||||
1. gview中的template标签失效问题;
|
||||
1. gfile文件stat信息使用gfsnotify进行缓存更新改进;
|
||||
1. ghttp.Server增加proxy功能特性,本地proxy和远程proxy,本地即将路由规则映射;远程即反向代理;
|
||||
1. gjson对大json数据的解析效率问题;
|
||||
1. ghttp增加route name特性,并同时支持backend和template(提供内置函数)引用,可以通过RedirectRoute方法给定route name和路由参数跳转到指定的路由地址上;
|
||||
1. ghttp.Client自动Close机制;
|
||||
1. gvalid校验支持当第一个规则失败后便不再校验后续的规则,最好做成链式操作;
|
||||
1. 检查ghttp.Server超时问题;
|
||||
1. gvalid增加支持对[]rune的长度校验(一个中文占3个字节);
|
||||
1. ghttp.Request增加对输入参数的自动HtmlEncode机制;
|
||||
1. 常量命名风格根据golint进行修改;
|
||||
@ -40,11 +28,28 @@
|
||||
- https://github.com/Masterminds/sprig
|
||||
1. gform参考 https://gohouse.github.io/gorose/dist/index.html 进行改进
|
||||
1. gtcp提供简便的包发送/接收方法(SendPkg/RecvPkg)以解决常见的TCP通信粘包问题,并完善文档(参考:https://www.cnblogs.com/kex1n/p/6502002.html);
|
||||
1. gfile对于文件的读写强行使用了gfpool,在某些场景下不合适,需要考虑剥离开,并为开发者提供单独的指针池文件操作特性;
|
||||
1. 路由增加不区分大小写得匹配方式;
|
||||
|
||||
|
||||
|
||||
1. 改进WebServer获取POST参数处理逻辑,当提交非form数据时,例如json数据,针对某些方法可以直接解析;
|
||||
1. WebServer增加可选择的路由覆盖配置,默认情况下不覆盖;
|
||||
1. grpool性能压测结果变慢的问题;
|
||||
1. 增加jumplist的数据结构容器;
|
||||
1. DelayQueue/PriorityQueue;
|
||||
1. 权限管理模块;
|
||||
1. 从ghttp中剥离SESSION功能构成单独的模块gsession;
|
||||
1. 改进gproc进程间通信处理逻辑,提高稳定性,以应对进程间大批量的数据发送/接收;
|
||||
1. ghttp的热重启的本地进程端口监听,在不使用该特性时默认关闭掉;
|
||||
1. gtcp增加对TLS加密通信的支持;
|
||||
1. 添加Save/Replace/BatchSave/BatchReplace方法对sqlite数据库的支持;
|
||||
1. 添加sqlite数据库的单元测试用例;
|
||||
1. gredis增加cluster支持;
|
||||
1. gset.Add/Remove/Contains方法增加批量操作支持;
|
||||
1. gmlock增加手动清理机制:当内存锁不再使用时,由调用端决定是否清理内存锁;
|
||||
1. gtimer增加DelayAdd*方法返回Entry对象,以便DelayAdd*的定时任务也能进行状态控制;gcron同理需要改进;
|
||||
1. 改进gdb对pgsql/mssql/oracle的支持,使用方法覆盖的方式改进操作,而不是完全依靠正则替换的方式;
|
||||
1. gdb的Cache缓存功能增加可自定义缓存接口,以便支持外部缓存功能,缓存接口可以通过io.ReadWriter接口实现;
|
||||
1. grpool增加支持阻塞添加任务接口;
|
||||
1. gdb.Model在链式安全的对象创建中增加sync.Pool的使用;
|
||||
1. 增加g.Table快捷方法以方便操作数据表,但是得考虑后续模型操作设计,特别是脚手架的模型管理;
|
||||
|
||||
# DONE
|
||||
1. gconv完善针对不同类型的判断,例如:尽量减少sprintf("%v", xxx)来执行string类型的转换;
|
||||
@ -70,7 +75,7 @@
|
||||
21. 改进控制器及执行对象注册,更友好地支持动态路由注册,例如:注册规则为 /channel/:name,现有的控制器及执行对象注册很难友好支持这种动态形式;
|
||||
22. 当前gpage分页包的输出标签不支持li,大多数CSS框架都是li+a标签模式,需要提供可更加灵活的定制化功能实现;
|
||||
23. 平滑重启机制改进,以便于开发阶段调试;
|
||||
24. 对grpool进行优化改进,包括属性原子操作封装采用gtype实现,修正设计BUG:https://github.com/johng-cn/gf/issues/6;
|
||||
24. 对grpool进行优化改进,包括属性原子操作封装采用gtype实现,修正设计BUG:https://github.com/gogf/gf/issues/6;
|
||||
25. gredis增加redis密码支持;
|
||||
26. 改进ghttp.Server平滑重启机制,当新进程接管服务后,再使用进程间通信方式通知父进程销毁;
|
||||
27. gproc进程间通信增加分组特性,不同的进程间可以通过进程ID以及分组名称发送/获取进程消息;
|
||||
@ -100,4 +105,23 @@
|
||||
1. gcfg/gview/ghttp等模块加上对临时文件目录的自动添加监听判断(基本是开发环境下,特别是windows环境),去掉临时文件的监听,避免临时文件过大引起的运行缓慢占用内存问题;
|
||||
1. 改进gfpool在文件指针变化时的更新;
|
||||
1. ghttp hook回调使用方式在注册路由比较多的时候,优先级可能使得开发者混乱,考虑方式便于管理;
|
||||
1. gform对于MySQL字段类型为datetime类型的时区问题分析;
|
||||
1. gform对于MySQL字段类型为datetime类型的时区问题分析;
|
||||
1. 改进证书打开失败时的WebServer错误提示,前置HOOK校验后关闭后续的HOOK逻辑执行;
|
||||
1. 目前WebServer的HOOK是按照优先级执行的,需要增加覆盖特性;
|
||||
1. 更新跨域请求CORS相关功能文档;
|
||||
1. ghttp.Response增加输出内容后自动退出当前请求机制,不需要用户手动return,参考beego如何实现;
|
||||
1. gcfg包目前允许添加重复的目录路径,需要在SetPath/AddPath时判断重复性,不能添加重复的路径;
|
||||
1. gdb执行数据写入时,如果参数为struct/[]struct,自动映射与表字段对应关系,不再使用gconv标签标识;
|
||||
1. gdb的Data方法支持struct参数传入;
|
||||
1. gfcache依旧使用gcache作为缓存控制对象,不要使用gmap;
|
||||
1. 增加对ghttp路由注册的{.struct}/{.method}单元测试;
|
||||
1. gconv针对struct的转换增加json tag支持,gconv.Map默认也支持json tag, 完善开发文档;
|
||||
1. 增加SO_REUSEPORT的支持;
|
||||
1. gkafka这个包比较重,未来从框架中剥离出来;
|
||||
1. str_ireplace: http://php.net/manual/en/function.str-ireplace.php
|
||||
1. strpos/stripos/strrpos/strripos: http://php.net/manual/en/function.stripos.php
|
||||
1. gfile对于文件的读写强行使用了gfpool,在某些场景下不合适,需要考虑剥离开,并为开发者提供单独的指针池文件操作特性;
|
||||
1. ghttp.Client自动Close机制;
|
||||
1. ghttp路由功能增加分组路由特性;
|
||||
1. 增加可选择性的orm tag特性,用以数据表记录与struct对象转换的键名属性映射;
|
||||
1. gview中的template标签失效问题;
|
||||
@ -1,7 +0,0 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
package container
|
||||
@ -1,13 +1,8 @@
|
||||
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package garray provides kinds of concurrent-safe(alternative) arrays.
|
||||
// 并发安全的数组.
|
||||
// Package garray provides concurrent-safe/unsafe arrays.
|
||||
package garray
|
||||
|
||||
func New(size int, cap int, safe...bool) *Array {
|
||||
return NewArray(size, cap, safe...)
|
||||
}
|
||||
19
g/container/garray/garray_func.go
Normal file
19
g/container/garray/garray_func.go
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package garray
|
||||
|
||||
type apiSliceInterface interface {
|
||||
Slice() []interface{}
|
||||
}
|
||||
|
||||
type apiSliceInt interface {
|
||||
Slice() []int
|
||||
}
|
||||
|
||||
type apiSliceString interface {
|
||||
Slice() []string
|
||||
}
|
||||
@ -1,176 +0,0 @@
|
||||
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
package garray
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
)
|
||||
|
||||
type IntArray struct {
|
||||
mu *rwmutex.RWMutex // 互斥锁
|
||||
cap int // 初始化设置的数组容量
|
||||
size int // 初始化设置的数组大小
|
||||
array []int // 底层数组
|
||||
}
|
||||
|
||||
func NewIntArray(size int, cap int, safe...bool) *IntArray {
|
||||
a := &IntArray{
|
||||
mu : rwmutex.New(safe...),
|
||||
}
|
||||
a.size = size
|
||||
if cap > 0 {
|
||||
a.cap = cap
|
||||
a.array = make([]int, size, cap)
|
||||
} else {
|
||||
a.array = make([]int, size)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// 获取指定索引的数据项, 调用方注意判断数组边界
|
||||
func (a *IntArray) Get(index int) int {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
value := a.array[index]
|
||||
return value
|
||||
}
|
||||
|
||||
// 设置指定索引的数据项, 调用方注意判断数组边界
|
||||
func (a *IntArray) Set(index int, value int) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
a.array[index] = value
|
||||
}
|
||||
|
||||
// 在当前索引位置前插入一个数据项, 调用方注意判断数组边界
|
||||
func (a *IntArray) InsertBefore(index int, value int) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
rear := append([]int{}, a.array[index : ]...)
|
||||
a.array = append(a.array[0 : index], value)
|
||||
a.array = append(a.array, rear...)
|
||||
}
|
||||
|
||||
// 在当前索引位置后插入一个数据项, 调用方注意判断数组边界
|
||||
func (a *IntArray) InsertAfter(index int, value int) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
rear := append([]int{}, a.array[index + 1:]...)
|
||||
a.array = append(a.array[0 : index + 1], value)
|
||||
a.array = append(a.array, rear...)
|
||||
|
||||
}
|
||||
|
||||
// 删除指定索引的数据项, 调用方注意判断数组边界
|
||||
func (a *IntArray) Remove(index int) int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
// 边界删除判断,以提高删除效率
|
||||
if index == 0 {
|
||||
value := a.array[0]
|
||||
a.array = a.array[1 : ]
|
||||
return value
|
||||
} else if index == len(a.array) - 1 {
|
||||
value := a.array[index]
|
||||
a.array = a.array[: index]
|
||||
return value
|
||||
}
|
||||
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
|
||||
value := a.array[index]
|
||||
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
|
||||
return value
|
||||
}
|
||||
|
||||
// 追加数据项
|
||||
func (a *IntArray) Append(value...int) {
|
||||
a.mu.Lock()
|
||||
a.array = append(a.array, value...)
|
||||
a.mu.Unlock()
|
||||
}
|
||||
|
||||
// 数组长度
|
||||
func (a *IntArray) Len() int {
|
||||
a.mu.RLock()
|
||||
length := len(a.array)
|
||||
a.mu.RUnlock()
|
||||
return length
|
||||
}
|
||||
|
||||
// 返回原始数据数组
|
||||
func (a *IntArray) Slice() []int {
|
||||
array := ([]int)(nil)
|
||||
if a.mu.IsSafe() {
|
||||
a.mu.RLock()
|
||||
array = make([]int, len(a.array))
|
||||
for k, v := range a.array {
|
||||
array[k] = v
|
||||
}
|
||||
a.mu.RUnlock()
|
||||
} else {
|
||||
array = a.array
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
// 清空数据数组
|
||||
func (a *IntArray) Clear() {
|
||||
a.mu.Lock()
|
||||
if len(a.array) > 0 {
|
||||
if a.cap > 0 {
|
||||
a.array = make([]int, a.size, a.cap)
|
||||
} else {
|
||||
a.array = make([]int, a.size)
|
||||
}
|
||||
}
|
||||
a.mu.Unlock()
|
||||
}
|
||||
|
||||
// 查找指定数值的索引位置,返回索引位置,如果查找不到则返回-1
|
||||
func (a *IntArray) Search(value int) int {
|
||||
if len(a.array) == 0 {
|
||||
return -1
|
||||
}
|
||||
a.mu.RLock()
|
||||
result := -1
|
||||
for index, v := range a.array {
|
||||
if v == value {
|
||||
result = index
|
||||
break
|
||||
}
|
||||
}
|
||||
a.mu.RUnlock()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// 清理数组中重复的元素项
|
||||
func (a *IntArray) Unique() *IntArray {
|
||||
a.mu.Lock()
|
||||
for i := 0; i < len(a.array) - 1; i++ {
|
||||
for j := i + 1; j < len(a.array); j++ {
|
||||
if a.array[i] == a.array[j] {
|
||||
a.array = append(a.array[ : j], a.array[j + 1 : ]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
a.mu.Unlock()
|
||||
return a
|
||||
}
|
||||
|
||||
// 使用自定义方法执行加锁修改操作
|
||||
func (a *IntArray) LockFunc(f func(array []int)) {
|
||||
a.mu.Lock(true)
|
||||
defer a.mu.Unlock(true)
|
||||
f(a.array)
|
||||
}
|
||||
|
||||
// 使用自定义方法执行加锁读取操作
|
||||
func (a *IntArray) RLockFunc(f func(array []int)) {
|
||||
a.mu.RLock(true)
|
||||
defer a.mu.RUnlock(true)
|
||||
f(a.array)
|
||||
}
|
||||
@ -1,194 +0,0 @@
|
||||
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
package garray
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
)
|
||||
|
||||
type Array struct {
|
||||
mu *rwmutex.RWMutex // 互斥锁
|
||||
cap int // 初始化设置的数组容量
|
||||
size int // 初始化设置的数组大小
|
||||
array []interface{} // 底层数组
|
||||
}
|
||||
|
||||
func NewArray(size int, cap int, safe...bool) *Array {
|
||||
a := &Array{
|
||||
mu : rwmutex.New(safe...),
|
||||
}
|
||||
a.size = size
|
||||
if cap > 0 {
|
||||
a.cap = cap
|
||||
a.array = make([]interface{}, size, cap)
|
||||
} else {
|
||||
a.array = make([]interface{}, size)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// 获取指定索引的数据项, 调用方注意判断数组边界
|
||||
func (a *Array) Get(index int) interface{} {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
value := a.array[index]
|
||||
return value
|
||||
}
|
||||
|
||||
// 设置指定索引的数据项, 调用方注意判断数组边界
|
||||
func (a *Array) Set(index int, value interface{}) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
a.array[index] = value
|
||||
}
|
||||
|
||||
// 在当前索引位置前插入一个数据项, 调用方注意判断数组边界
|
||||
func (a *Array) InsertBefore(index int, value interface{}) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
rear := append([]interface{}{}, a.array[index : ]...)
|
||||
a.array = append(a.array[0 : index], value)
|
||||
a.array = append(a.array, rear...)
|
||||
}
|
||||
|
||||
// 在当前索引位置前插入一个数据项, 调用方注意判断数组边界
|
||||
func (a *Array) InsertAfter(index int, value interface{}) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
rear := append([]interface{}{}, a.array[index + 1 : ]...)
|
||||
a.array = append(a.array[0 : index + 1], value)
|
||||
a.array = append(a.array, rear...)
|
||||
}
|
||||
|
||||
// 删除指定索引的数据项, 调用方注意判断数组边界
|
||||
func (a *Array) Remove(index int) interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
// 边界删除判断,以提高删除效率
|
||||
if index == 0 {
|
||||
value := a.array[0]
|
||||
a.array = a.array[1 : ]
|
||||
return value
|
||||
} else if index == len(a.array) - 1 {
|
||||
value := a.array[index]
|
||||
a.array = a.array[: index]
|
||||
return value
|
||||
}
|
||||
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
|
||||
value := a.array[index]
|
||||
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
|
||||
return value
|
||||
}
|
||||
|
||||
// 将最左端(索引为0)的数据项移出数组,并返回该数据项
|
||||
func (a *Array) PopLeft() interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
value := a.array[0]
|
||||
a.array = a.array[1 : ]
|
||||
return value
|
||||
}
|
||||
|
||||
// 将最右端(索引为length - 1)的数据项移出数组,并返回该数据项
|
||||
func (a *Array) PopRight() interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
index := len(a.array) - 1
|
||||
value := a.array[index]
|
||||
a.array = a.array[: index]
|
||||
return value
|
||||
}
|
||||
|
||||
// 追加数据项
|
||||
func (a *Array) Append(value...interface{}) {
|
||||
a.mu.Lock()
|
||||
a.array = append(a.array, value...)
|
||||
a.mu.Unlock()
|
||||
}
|
||||
|
||||
// 数组长度
|
||||
func (a *Array) Len() int {
|
||||
a.mu.RLock()
|
||||
length := len(a.array)
|
||||
a.mu.RUnlock()
|
||||
return length
|
||||
}
|
||||
|
||||
// 返回原始数据数组
|
||||
func (a *Array) Slice() []interface{} {
|
||||
array := ([]interface{})(nil)
|
||||
if a.mu.IsSafe() {
|
||||
a.mu.RLock()
|
||||
array = make([]interface{}, len(a.array))
|
||||
for k, v := range a.array {
|
||||
array[k] = v
|
||||
}
|
||||
a.mu.RUnlock()
|
||||
} else {
|
||||
array = a.array
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
// 清空数据数组
|
||||
func (a *Array) Clear() {
|
||||
a.mu.Lock()
|
||||
if len(a.array) > 0 {
|
||||
if a.cap > 0 {
|
||||
a.array = make([]interface{}, a.size, a.cap)
|
||||
} else {
|
||||
a.array = make([]interface{}, a.size)
|
||||
}
|
||||
}
|
||||
a.mu.Unlock()
|
||||
}
|
||||
|
||||
// 查找指定数值的索引位置,返回索引位置,如果查找不到则返回-1
|
||||
func (a *Array) Search(value interface{}) int {
|
||||
if len(a.array) == 0 {
|
||||
return -1
|
||||
}
|
||||
a.mu.RLock()
|
||||
result := -1
|
||||
for index, v := range a.array {
|
||||
if v == value {
|
||||
result = index
|
||||
break
|
||||
}
|
||||
}
|
||||
a.mu.RUnlock()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// 清理数组中重复的元素项
|
||||
func (a *Array) Unique() *Array {
|
||||
a.mu.Lock()
|
||||
for i := 0; i < len(a.array) - 1; i++ {
|
||||
for j := i + 1; j < len(a.array); j++ {
|
||||
if a.array[i] == a.array[j] {
|
||||
a.array = append(a.array[ : j], a.array[j + 1 : ]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
a.mu.Unlock()
|
||||
return a
|
||||
}
|
||||
|
||||
// 使用自定义方法执行加锁修改操作
|
||||
func (a *Array) LockFunc(f func(array []interface{})) {
|
||||
a.mu.Lock(true)
|
||||
defer a.mu.Unlock(true)
|
||||
f(a.array)
|
||||
}
|
||||
|
||||
// 使用自定义方法执行加锁读取操作
|
||||
func (a *Array) RLockFunc(f func(array []interface{})) {
|
||||
a.mu.RLock(true)
|
||||
defer a.mu.RUnlock(true)
|
||||
f(a.array)
|
||||
}
|
||||
618
g/container/garray/garray_normal_int.go
Normal file
618
g/container/garray/garray_normal_int.go
Normal file
@ -0,0 +1,618 @@
|
||||
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package garray
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/gogf/gf/g/internal/rwmutex"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"github.com/gogf/gf/g/util/grand"
|
||||
)
|
||||
|
||||
type IntArray struct {
|
||||
mu *rwmutex.RWMutex
|
||||
array []int
|
||||
}
|
||||
|
||||
// NewIntArray creates and returns an empty array.
|
||||
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewIntArray(unsafe ...bool) *IntArray {
|
||||
return NewIntArraySize(0, 0, unsafe...)
|
||||
}
|
||||
|
||||
// NewIntArraySize create and returns an array with given size and cap.
|
||||
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewIntArraySize(size int, cap int, unsafe ...bool) *IntArray {
|
||||
return &IntArray{
|
||||
mu: rwmutex.New(unsafe...),
|
||||
array: make([]int, size, cap),
|
||||
}
|
||||
}
|
||||
|
||||
// NewIntArrayFrom creates and returns an array with given slice <array>.
|
||||
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewIntArrayFrom(array []int, unsafe ...bool) *IntArray {
|
||||
return &IntArray{
|
||||
mu: rwmutex.New(unsafe...),
|
||||
array: array,
|
||||
}
|
||||
}
|
||||
|
||||
// NewIntArrayFromCopy creates and returns an array from a copy of given slice <array>.
|
||||
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewIntArrayFromCopy(array []int, unsafe ...bool) *IntArray {
|
||||
newArray := make([]int, len(array))
|
||||
copy(newArray, array)
|
||||
return &IntArray{
|
||||
mu: rwmutex.New(unsafe...),
|
||||
array: newArray,
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the value of the specified index,
|
||||
// the caller should notice the boundary of the array.
|
||||
func (a *IntArray) Get(index int) int {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
value := a.array[index]
|
||||
return value
|
||||
}
|
||||
|
||||
// Set sets value to specified index.
|
||||
func (a *IntArray) Set(index int, value int) *IntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
a.array[index] = value
|
||||
return a
|
||||
}
|
||||
|
||||
// SetArray sets the underlying slice array with the given <array>.
|
||||
func (a *IntArray) SetArray(array []int) *IntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
a.array = array
|
||||
return a
|
||||
}
|
||||
|
||||
// Replace replaces the array items by given <array> from the beginning of array.
|
||||
func (a *IntArray) Replace(array []int) *IntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
max := len(array)
|
||||
if max > len(a.array) {
|
||||
max = len(a.array)
|
||||
}
|
||||
for i := 0; i < max; i++ {
|
||||
a.array[i] = array[i]
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Sum returns the sum of values in an array.
|
||||
func (a *IntArray) Sum() (sum int) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
for _, v := range a.array {
|
||||
sum += v
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Sort sorts the array in increasing order.
|
||||
// The parameter <reverse> controls whether sort
|
||||
// in increasing order(default) or decreasing order
|
||||
func (a *IntArray) Sort(reverse ...bool) *IntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if len(reverse) > 0 && reverse[0] {
|
||||
sort.Slice(a.array, func(i, j int) bool {
|
||||
if a.array[i] < a.array[j] {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
sort.Ints(a.array)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// SortFunc sorts the array by custom function <less>.
|
||||
func (a *IntArray) SortFunc(less func(v1, v2 int) bool) *IntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
sort.Slice(a.array, func(i, j int) bool {
|
||||
return less(a.array[i], a.array[j])
|
||||
})
|
||||
return a
|
||||
}
|
||||
|
||||
// InsertBefore inserts the <value> to the front of <index>.
|
||||
func (a *IntArray) InsertBefore(index int, value int) *IntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
rear := append([]int{}, a.array[index:]...)
|
||||
a.array = append(a.array[0:index], value)
|
||||
a.array = append(a.array, rear...)
|
||||
return a
|
||||
}
|
||||
|
||||
// InsertAfter inserts the <value> to the back of <index>.
|
||||
func (a *IntArray) InsertAfter(index int, value int) *IntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
rear := append([]int{}, a.array[index+1:]...)
|
||||
a.array = append(a.array[0:index+1], value)
|
||||
a.array = append(a.array, rear...)
|
||||
return a
|
||||
}
|
||||
|
||||
// Remove removes an item by index.
|
||||
func (a *IntArray) Remove(index int) int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
// Determine array boundaries when deleting to improve deletion efficiency.
|
||||
if index == 0 {
|
||||
value := a.array[0]
|
||||
a.array = a.array[1:]
|
||||
return value
|
||||
} else if index == len(a.array)-1 {
|
||||
value := a.array[index]
|
||||
a.array = a.array[:index]
|
||||
return value
|
||||
}
|
||||
// If it is a non-boundary delete,
|
||||
// it will involve the creation of an array,
|
||||
// then the deletion is less efficient.
|
||||
value := a.array[index]
|
||||
a.array = append(a.array[:index], a.array[index+1:]...)
|
||||
return value
|
||||
}
|
||||
|
||||
// PushLeft pushes one or multiple items to the beginning of array.
|
||||
func (a *IntArray) PushLeft(value ...int) *IntArray {
|
||||
a.mu.Lock()
|
||||
a.array = append(value, a.array...)
|
||||
a.mu.Unlock()
|
||||
return a
|
||||
}
|
||||
|
||||
// PushRight pushes one or multiple items to the end of array.
|
||||
// It equals to Append.
|
||||
func (a *IntArray) PushRight(value ...int) *IntArray {
|
||||
a.mu.Lock()
|
||||
a.array = append(a.array, value...)
|
||||
a.mu.Unlock()
|
||||
return a
|
||||
}
|
||||
|
||||
// PopLeft pops and returns an item from the beginning of array.
|
||||
func (a *IntArray) PopLeft() int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
value := a.array[0]
|
||||
a.array = a.array[1:]
|
||||
return value
|
||||
}
|
||||
|
||||
// PopRight pops and returns an item from the end of array.
|
||||
func (a *IntArray) PopRight() int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
index := len(a.array) - 1
|
||||
value := a.array[index]
|
||||
a.array = a.array[:index]
|
||||
return value
|
||||
}
|
||||
|
||||
// PopRand randomly pops and return an item out of array.
|
||||
func (a *IntArray) PopRand() int {
|
||||
return a.Remove(grand.Intn(len(a.array)))
|
||||
}
|
||||
|
||||
// PopRands randomly pops and returns <size> items out of array.
|
||||
func (a *IntArray) PopRands(size int) []int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if size > len(a.array) {
|
||||
size = len(a.array)
|
||||
}
|
||||
array := make([]int, size)
|
||||
for i := 0; i < size; i++ {
|
||||
index := grand.Intn(len(a.array))
|
||||
array[i] = a.array[index]
|
||||
a.array = append(a.array[:index], a.array[index+1:]...)
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
// PopLefts pops and returns <size> items from the beginning of array.
|
||||
func (a *IntArray) PopLefts(size int) []int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
length := len(a.array)
|
||||
if size > length {
|
||||
size = length
|
||||
}
|
||||
value := a.array[0:size]
|
||||
a.array = a.array[size:]
|
||||
return value
|
||||
}
|
||||
|
||||
// PopRights pops and returns <size> items from the end of array.
|
||||
func (a *IntArray) PopRights(size int) []int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
index := len(a.array) - size
|
||||
if index < 0 {
|
||||
index = 0
|
||||
}
|
||||
value := a.array[index:]
|
||||
a.array = a.array[:index]
|
||||
return value
|
||||
}
|
||||
|
||||
// Range picks and returns items by range, like array[start:end].
|
||||
// Notice, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// else a pointer to the underlying data.
|
||||
//
|
||||
// If <end> is negative, then the offset will start from the end of array.
|
||||
// If <end> is omitted, then the sequence will have everything from start up
|
||||
// until the end of the array.
|
||||
func (a *IntArray) Range(start int, end ...int) []int {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
offsetEnd := len(a.array)
|
||||
if len(end) > 0 && end[0] < offsetEnd {
|
||||
offsetEnd = end[0]
|
||||
}
|
||||
if start > offsetEnd {
|
||||
return nil
|
||||
}
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
array := ([]int)(nil)
|
||||
if a.mu.IsSafe() {
|
||||
array = make([]int, offsetEnd-start)
|
||||
copy(array, a.array[start:offsetEnd])
|
||||
} else {
|
||||
array = a.array[start:offsetEnd]
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
// SubSlice returns a slice of elements from the array as specified
|
||||
// by the <offset> and <size> parameters.
|
||||
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
|
||||
//
|
||||
// If offset is non-negative, the sequence will start at that offset in the array.
|
||||
// If offset is negative, the sequence will start that far from the end of the array.
|
||||
//
|
||||
// If length is given and is positive, then the sequence will have up to that many elements in it.
|
||||
// If the array is shorter than the length, then only the available array elements will be present.
|
||||
// If length is given and is negative then the sequence will stop that many elements from the end of the array.
|
||||
// If it is omitted, then the sequence will have everything from offset up until the end of the array.
|
||||
//
|
||||
// Any possibility crossing the left border of array, it will fail.
|
||||
func (a *IntArray) SubSlice(offset int, length ...int) []int {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
size := len(a.array)
|
||||
if len(length) > 0 {
|
||||
size = length[0]
|
||||
}
|
||||
if offset > len(a.array) {
|
||||
return nil
|
||||
}
|
||||
if offset < 0 {
|
||||
offset = len(a.array) + offset
|
||||
if offset < 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if size < 0 {
|
||||
offset += size
|
||||
size = -size
|
||||
if offset < 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
end := offset + size
|
||||
if end > len(a.array) {
|
||||
end = len(a.array)
|
||||
size = len(a.array) - offset
|
||||
}
|
||||
if a.mu.IsSafe() {
|
||||
s := make([]int, size)
|
||||
copy(s, a.array[offset:])
|
||||
return s
|
||||
} else {
|
||||
return a.array[offset:end]
|
||||
}
|
||||
}
|
||||
|
||||
// See PushRight.
|
||||
func (a *IntArray) Append(value ...int) *IntArray {
|
||||
a.mu.Lock()
|
||||
a.array = append(a.array, value...)
|
||||
a.mu.Unlock()
|
||||
return a
|
||||
}
|
||||
|
||||
// Len returns the length of array.
|
||||
func (a *IntArray) Len() int {
|
||||
a.mu.RLock()
|
||||
length := len(a.array)
|
||||
a.mu.RUnlock()
|
||||
return length
|
||||
}
|
||||
|
||||
// Slice returns the underlying data of array.
|
||||
// Notice, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// else a pointer to the underlying data.
|
||||
func (a *IntArray) Slice() []int {
|
||||
array := ([]int)(nil)
|
||||
if a.mu.IsSafe() {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
array = make([]int, len(a.array))
|
||||
copy(array, a.array)
|
||||
} else {
|
||||
array = a.array
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
// Clone returns a new array, which is a copy of current array.
|
||||
func (a *IntArray) Clone() (newArray *IntArray) {
|
||||
a.mu.RLock()
|
||||
array := make([]int, len(a.array))
|
||||
copy(array, a.array)
|
||||
a.mu.RUnlock()
|
||||
return NewIntArrayFrom(array, !a.mu.IsSafe())
|
||||
}
|
||||
|
||||
// Clear deletes all items of current array.
|
||||
func (a *IntArray) Clear() *IntArray {
|
||||
a.mu.Lock()
|
||||
if len(a.array) > 0 {
|
||||
a.array = make([]int, 0)
|
||||
}
|
||||
a.mu.Unlock()
|
||||
return a
|
||||
}
|
||||
|
||||
// Contains checks whether a value exists in the array.
|
||||
func (a *IntArray) Contains(value int) bool {
|
||||
return a.Search(value) != -1
|
||||
}
|
||||
|
||||
// Search searches array by <value>, returns the index of <value>,
|
||||
// or returns -1 if not exists.
|
||||
func (a *IntArray) Search(value int) int {
|
||||
if len(a.array) == 0 {
|
||||
return -1
|
||||
}
|
||||
a.mu.RLock()
|
||||
result := -1
|
||||
for index, v := range a.array {
|
||||
if v == value {
|
||||
result = index
|
||||
break
|
||||
}
|
||||
}
|
||||
a.mu.RUnlock()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Unique uniques the array, clear repeated items.
|
||||
func (a *IntArray) Unique() *IntArray {
|
||||
a.mu.Lock()
|
||||
for i := 0; i < len(a.array)-1; i++ {
|
||||
for j := i + 1; j < len(a.array); j++ {
|
||||
if a.array[i] == a.array[j] {
|
||||
a.array = append(a.array[:j], a.array[j+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
a.mu.Unlock()
|
||||
return a
|
||||
}
|
||||
|
||||
// LockFunc locks writing by callback function <f>.
|
||||
func (a *IntArray) LockFunc(f func(array []int)) *IntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
f(a.array)
|
||||
return a
|
||||
}
|
||||
|
||||
// RLockFunc locks reading by callback function <f>.
|
||||
func (a *IntArray) RLockFunc(f func(array []int)) *IntArray {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
f(a.array)
|
||||
return a
|
||||
}
|
||||
|
||||
// Merge merges <array> into current array.
|
||||
// The parameter <array> can be any garray or slice type.
|
||||
// The difference between Merge and Append is Append supports only specified slice type,
|
||||
// but Merge supports more parameter types.
|
||||
func (a *IntArray) Merge(array interface{}) *IntArray {
|
||||
switch v := array.(type) {
|
||||
case *Array:
|
||||
a.Append(gconv.Ints(v.Slice())...)
|
||||
case *IntArray:
|
||||
a.Append(gconv.Ints(v.Slice())...)
|
||||
case *StringArray:
|
||||
a.Append(gconv.Ints(v.Slice())...)
|
||||
case *SortedArray:
|
||||
a.Append(gconv.Ints(v.Slice())...)
|
||||
case *SortedIntArray:
|
||||
a.Append(gconv.Ints(v.Slice())...)
|
||||
case *SortedStringArray:
|
||||
a.Append(gconv.Ints(v.Slice())...)
|
||||
default:
|
||||
a.Append(gconv.Ints(array)...)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Fill fills an array with num entries of the value <value>,
|
||||
// keys starting at the <startIndex> parameter.
|
||||
func (a *IntArray) Fill(startIndex int, num int, value int) *IntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if startIndex < 0 {
|
||||
startIndex = 0
|
||||
}
|
||||
for i := startIndex; i < startIndex+num; i++ {
|
||||
if i > len(a.array)-1 {
|
||||
a.array = append(a.array, value)
|
||||
} else {
|
||||
a.array[i] = value
|
||||
}
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Chunk splits an array into multiple arrays,
|
||||
// the size of each array is determined by <size>.
|
||||
// The last chunk may contain less than size elements.
|
||||
func (a *IntArray) Chunk(size int) [][]int {
|
||||
if size < 1 {
|
||||
return nil
|
||||
}
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
length := len(a.array)
|
||||
chunks := int(math.Ceil(float64(length) / float64(size)))
|
||||
var n [][]int
|
||||
for i, end := 0, 0; chunks > 0; chunks-- {
|
||||
end = (i + 1) * size
|
||||
if end > length {
|
||||
end = length
|
||||
}
|
||||
n = append(n, a.array[i*size:end])
|
||||
i++
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Pad pads array to the specified length with <value>.
|
||||
// If size is positive then the array is padded on the right, or negative on the left.
|
||||
// If the absolute value of <size> is less than or equal to the length of the array
|
||||
// then no padding takes place.
|
||||
func (a *IntArray) Pad(size int, value int) *IntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if size == 0 || (size > 0 && size < len(a.array)) || (size < 0 && size > -len(a.array)) {
|
||||
return a
|
||||
}
|
||||
n := size
|
||||
if size < 0 {
|
||||
n = -size
|
||||
}
|
||||
n -= len(a.array)
|
||||
tmp := make([]int, n)
|
||||
for i := 0; i < n; i++ {
|
||||
tmp[i] = value
|
||||
}
|
||||
if size > 0 {
|
||||
a.array = append(a.array, tmp...)
|
||||
} else {
|
||||
a.array = append(tmp, a.array...)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Rand randomly returns one item from array(no deleting).
|
||||
func (a *IntArray) Rand() int {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return a.array[grand.Intn(len(a.array))]
|
||||
}
|
||||
|
||||
// Rands randomly returns <size> items from array(no deleting).
|
||||
func (a *IntArray) Rands(size int) []int {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
if size > len(a.array) {
|
||||
size = len(a.array)
|
||||
}
|
||||
n := make([]int, size)
|
||||
for i, v := range grand.Perm(len(a.array)) {
|
||||
n[i] = a.array[v]
|
||||
if i == size-1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Shuffle randomly shuffles the array.
|
||||
func (a *IntArray) Shuffle() *IntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
for i, v := range grand.Perm(len(a.array)) {
|
||||
a.array[i], a.array[v] = a.array[v], a.array[i]
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Reverse makes array with elements in reverse order.
|
||||
func (a *IntArray) Reverse() *IntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
for i, j := 0, len(a.array)-1; i < j; i, j = i+1, j-1 {
|
||||
a.array[i], a.array[j] = a.array[j], a.array[i]
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Join joins array elements with a string <glue>.
|
||||
func (a *IntArray) Join(glue string) string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
for k, v := range a.array {
|
||||
buffer.WriteString(gconv.String(v))
|
||||
if k != len(a.array)-1 {
|
||||
buffer.WriteString(glue)
|
||||
}
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// CountValues counts the number of occurrences of all values in the array.
|
||||
func (a *IntArray) CountValues() map[int]int {
|
||||
m := make(map[int]int)
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
for _, v := range a.array {
|
||||
m[v]++
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// String returns current array as a string.
|
||||
func (a *IntArray) String() string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return fmt.Sprint(a.array)
|
||||
}
|
||||
612
g/container/garray/garray_normal_interface.go
Normal file
612
g/container/garray/garray_normal_interface.go
Normal file
@ -0,0 +1,612 @@
|
||||
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package garray
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/gogf/gf/g/internal/rwmutex"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"github.com/gogf/gf/g/util/grand"
|
||||
)
|
||||
|
||||
type Array struct {
|
||||
mu *rwmutex.RWMutex
|
||||
array []interface{}
|
||||
}
|
||||
|
||||
// New creates and returns an empty array.
|
||||
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func New(unsafe ...bool) *Array {
|
||||
return NewArraySize(0, 0, unsafe...)
|
||||
}
|
||||
|
||||
// See New.
|
||||
func NewArray(unsafe ...bool) *Array {
|
||||
return NewArraySize(0, 0, unsafe...)
|
||||
}
|
||||
|
||||
// NewArraySize create and returns an array with given size and cap.
|
||||
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewArraySize(size int, cap int, unsafe ...bool) *Array {
|
||||
return &Array{
|
||||
mu: rwmutex.New(unsafe...),
|
||||
array: make([]interface{}, size, cap),
|
||||
}
|
||||
}
|
||||
|
||||
// See NewArrayFrom.
|
||||
func NewFrom(array []interface{}, unsafe ...bool) *Array {
|
||||
return NewArrayFrom(array, unsafe...)
|
||||
}
|
||||
|
||||
// See NewArrayFromCopy.
|
||||
func NewFromCopy(array []interface{}, unsafe ...bool) *Array {
|
||||
return NewArrayFromCopy(array, unsafe...)
|
||||
}
|
||||
|
||||
// NewArrayFrom creates and returns an array with given slice <array>.
|
||||
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewArrayFrom(array []interface{}, unsafe ...bool) *Array {
|
||||
return &Array{
|
||||
mu: rwmutex.New(unsafe...),
|
||||
array: array,
|
||||
}
|
||||
}
|
||||
|
||||
// NewArrayFromCopy creates and returns an array from a copy of given slice <array>.
|
||||
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewArrayFromCopy(array []interface{}, unsafe ...bool) *Array {
|
||||
newArray := make([]interface{}, len(array))
|
||||
copy(newArray, array)
|
||||
return &Array{
|
||||
mu: rwmutex.New(unsafe...),
|
||||
array: newArray,
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the value of the specified index,
|
||||
// the caller should notice the boundary of the array.
|
||||
func (a *Array) Get(index int) interface{} {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
value := a.array[index]
|
||||
return value
|
||||
}
|
||||
|
||||
// Set sets value to specified index.
|
||||
func (a *Array) Set(index int, value interface{}) *Array {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
a.array[index] = value
|
||||
return a
|
||||
}
|
||||
|
||||
// SetArray sets the underlying slice array with the given <array>.
|
||||
func (a *Array) SetArray(array []interface{}) *Array {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
a.array = array
|
||||
return a
|
||||
}
|
||||
|
||||
// Replace replaces the array items by given <array> from the beginning of array.
|
||||
func (a *Array) Replace(array []interface{}) *Array {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
max := len(array)
|
||||
if max > len(a.array) {
|
||||
max = len(a.array)
|
||||
}
|
||||
for i := 0; i < max; i++ {
|
||||
a.array[i] = array[i]
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Sum returns the sum of values in an array.
|
||||
func (a *Array) Sum() (sum int) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
for _, v := range a.array {
|
||||
sum += gconv.Int(v)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SortFunc sorts the array by custom function <less>.
|
||||
func (a *Array) SortFunc(less func(v1, v2 interface{}) bool) *Array {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
sort.Slice(a.array, func(i, j int) bool {
|
||||
return less(a.array[i], a.array[j])
|
||||
})
|
||||
return a
|
||||
}
|
||||
|
||||
// InsertBefore inserts the <value> to the front of <index>.
|
||||
func (a *Array) InsertBefore(index int, value interface{}) *Array {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
rear := append([]interface{}{}, a.array[index:]...)
|
||||
a.array = append(a.array[0:index], value)
|
||||
a.array = append(a.array, rear...)
|
||||
return a
|
||||
}
|
||||
|
||||
// InsertAfter inserts the <value> to the back of <index>.
|
||||
func (a *Array) InsertAfter(index int, value interface{}) *Array {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
rear := append([]interface{}{}, a.array[index+1:]...)
|
||||
a.array = append(a.array[0:index+1], value)
|
||||
a.array = append(a.array, rear...)
|
||||
return a
|
||||
}
|
||||
|
||||
// Remove removes an item by index.
|
||||
func (a *Array) Remove(index int) interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
// Determine array boundaries when deleting to improve deletion efficiency。
|
||||
if index == 0 {
|
||||
value := a.array[0]
|
||||
a.array = a.array[1:]
|
||||
return value
|
||||
} else if index == len(a.array)-1 {
|
||||
value := a.array[index]
|
||||
a.array = a.array[:index]
|
||||
return value
|
||||
}
|
||||
// If it is a non-boundary delete,
|
||||
// it will involve the creation of an array,
|
||||
// then the deletion is less efficient.
|
||||
value := a.array[index]
|
||||
a.array = append(a.array[:index], a.array[index+1:]...)
|
||||
return value
|
||||
}
|
||||
|
||||
// PushLeft pushes one or multiple items to the beginning of array.
|
||||
func (a *Array) PushLeft(value ...interface{}) *Array {
|
||||
a.mu.Lock()
|
||||
a.array = append(value, a.array...)
|
||||
a.mu.Unlock()
|
||||
return a
|
||||
}
|
||||
|
||||
// PushRight pushes one or multiple items to the end of array.
|
||||
// It equals to Append.
|
||||
func (a *Array) PushRight(value ...interface{}) *Array {
|
||||
a.mu.Lock()
|
||||
a.array = append(a.array, value...)
|
||||
a.mu.Unlock()
|
||||
return a
|
||||
}
|
||||
|
||||
// PopRand randomly pops and return an item out of array.
|
||||
func (a *Array) PopRand() interface{} {
|
||||
return a.Remove(grand.Intn(len(a.array)))
|
||||
}
|
||||
|
||||
// PopRands randomly pops and returns <size> items out of array.
|
||||
func (a *Array) PopRands(size int) []interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if size > len(a.array) {
|
||||
size = len(a.array)
|
||||
}
|
||||
array := make([]interface{}, size)
|
||||
for i := 0; i < size; i++ {
|
||||
index := grand.Intn(len(a.array))
|
||||
array[i] = a.array[index]
|
||||
a.array = append(a.array[:index], a.array[index+1:]...)
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
// PopLeft pops and returns an item from the beginning of array.
|
||||
func (a *Array) PopLeft() interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
value := a.array[0]
|
||||
a.array = a.array[1:]
|
||||
return value
|
||||
}
|
||||
|
||||
// PopRight pops and returns an item from the end of array.
|
||||
func (a *Array) PopRight() interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
index := len(a.array) - 1
|
||||
value := a.array[index]
|
||||
a.array = a.array[:index]
|
||||
return value
|
||||
}
|
||||
|
||||
// PopLefts pops and returns <size> items from the beginning of array.
|
||||
func (a *Array) PopLefts(size int) []interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
length := len(a.array)
|
||||
if size > length {
|
||||
size = length
|
||||
}
|
||||
value := a.array[0:size]
|
||||
a.array = a.array[size:]
|
||||
return value
|
||||
}
|
||||
|
||||
// PopRights pops and returns <size> items from the end of array.
|
||||
func (a *Array) PopRights(size int) []interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
index := len(a.array) - size
|
||||
if index < 0 {
|
||||
index = 0
|
||||
}
|
||||
value := a.array[index:]
|
||||
a.array = a.array[:index]
|
||||
return value
|
||||
}
|
||||
|
||||
// Range picks and returns items by range, like array[start:end].
|
||||
// Notice, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// else a pointer to the underlying data.
|
||||
//
|
||||
// If <end> is negative, then the offset will start from the end of array.
|
||||
// If <end> is omitted, then the sequence will have everything from start up
|
||||
// until the end of the array.
|
||||
func (a *Array) Range(start int, end ...int) []interface{} {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
offsetEnd := len(a.array)
|
||||
if len(end) > 0 && end[0] < offsetEnd {
|
||||
offsetEnd = end[0]
|
||||
}
|
||||
if start > offsetEnd {
|
||||
return nil
|
||||
}
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
array := ([]interface{})(nil)
|
||||
if a.mu.IsSafe() {
|
||||
array = make([]interface{}, offsetEnd-start)
|
||||
copy(array, a.array[start:offsetEnd])
|
||||
} else {
|
||||
array = a.array[start:offsetEnd]
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
// SubSlice returns a slice of elements from the array as specified
|
||||
// by the <offset> and <size> parameters.
|
||||
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
|
||||
//
|
||||
// If offset is non-negative, the sequence will start at that offset in the array.
|
||||
// If offset is negative, the sequence will start that far from the end of the array.
|
||||
//
|
||||
// If length is given and is positive, then the sequence will have up to that many elements in it.
|
||||
// If the array is shorter than the length, then only the available array elements will be present.
|
||||
// If length is given and is negative then the sequence will stop that many elements from the end of the array.
|
||||
// If it is omitted, then the sequence will have everything from offset up until the end of the array.
|
||||
//
|
||||
// Any possibility crossing the left border of array, it will fail.
|
||||
func (a *Array) SubSlice(offset int, length ...int) []interface{} {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
size := len(a.array)
|
||||
if len(length) > 0 {
|
||||
size = length[0]
|
||||
}
|
||||
if offset > len(a.array) {
|
||||
return nil
|
||||
}
|
||||
if offset < 0 {
|
||||
offset = len(a.array) + offset
|
||||
if offset < 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if size < 0 {
|
||||
offset += size
|
||||
size = -size
|
||||
if offset < 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
end := offset + size
|
||||
if end > len(a.array) {
|
||||
end = len(a.array)
|
||||
size = len(a.array) - offset
|
||||
}
|
||||
if a.mu.IsSafe() {
|
||||
s := make([]interface{}, size)
|
||||
copy(s, a.array[offset:])
|
||||
return s
|
||||
} else {
|
||||
return a.array[offset:end]
|
||||
}
|
||||
}
|
||||
|
||||
// See PushRight.
|
||||
func (a *Array) Append(value ...interface{}) *Array {
|
||||
a.PushRight(value...)
|
||||
return a
|
||||
}
|
||||
|
||||
// Len returns the length of array.
|
||||
func (a *Array) Len() int {
|
||||
a.mu.RLock()
|
||||
length := len(a.array)
|
||||
a.mu.RUnlock()
|
||||
return length
|
||||
}
|
||||
|
||||
// Slice returns the underlying data of array.
|
||||
// Notice, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// else a pointer to the underlying data.
|
||||
func (a *Array) Slice() []interface{} {
|
||||
array := ([]interface{})(nil)
|
||||
if a.mu.IsSafe() {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
array = make([]interface{}, len(a.array))
|
||||
copy(array, a.array)
|
||||
} else {
|
||||
array = a.array
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
// Clone returns a new array, which is a copy of current array.
|
||||
func (a *Array) Clone() (newArray *Array) {
|
||||
a.mu.RLock()
|
||||
array := make([]interface{}, len(a.array))
|
||||
copy(array, a.array)
|
||||
a.mu.RUnlock()
|
||||
return NewArrayFrom(array, !a.mu.IsSafe())
|
||||
}
|
||||
|
||||
// Clear deletes all items of current array.
|
||||
func (a *Array) Clear() *Array {
|
||||
a.mu.Lock()
|
||||
if len(a.array) > 0 {
|
||||
a.array = make([]interface{}, 0)
|
||||
}
|
||||
a.mu.Unlock()
|
||||
return a
|
||||
}
|
||||
|
||||
// Contains checks whether a value exists in the array.
|
||||
func (a *Array) Contains(value interface{}) bool {
|
||||
return a.Search(value) != -1
|
||||
}
|
||||
|
||||
// Search searches array by <value>, returns the index of <value>,
|
||||
// or returns -1 if not exists.
|
||||
func (a *Array) Search(value interface{}) int {
|
||||
if len(a.array) == 0 {
|
||||
return -1
|
||||
}
|
||||
a.mu.RLock()
|
||||
result := -1
|
||||
for index, v := range a.array {
|
||||
if v == value {
|
||||
result = index
|
||||
break
|
||||
}
|
||||
}
|
||||
a.mu.RUnlock()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Unique uniques the array, clear repeated items.
|
||||
func (a *Array) Unique() *Array {
|
||||
a.mu.Lock()
|
||||
for i := 0; i < len(a.array)-1; i++ {
|
||||
for j := i + 1; j < len(a.array); j++ {
|
||||
if a.array[i] == a.array[j] {
|
||||
a.array = append(a.array[:j], a.array[j+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
a.mu.Unlock()
|
||||
return a
|
||||
}
|
||||
|
||||
// LockFunc locks writing by callback function <f>.
|
||||
func (a *Array) LockFunc(f func(array []interface{})) *Array {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
f(a.array)
|
||||
return a
|
||||
}
|
||||
|
||||
// RLockFunc locks reading by callback function <f>.
|
||||
func (a *Array) RLockFunc(f func(array []interface{})) *Array {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
f(a.array)
|
||||
return a
|
||||
}
|
||||
|
||||
// Merge merges <array> into current array.
|
||||
// The parameter <array> can be any garray or slice type.
|
||||
// The difference between Merge and Append is Append supports only specified slice type,
|
||||
// but Merge supports more parameter types.
|
||||
func (a *Array) Merge(array interface{}) *Array {
|
||||
switch v := array.(type) {
|
||||
case *Array:
|
||||
a.Append(gconv.Interfaces(v.Slice())...)
|
||||
case *IntArray:
|
||||
a.Append(gconv.Interfaces(v.Slice())...)
|
||||
case *StringArray:
|
||||
a.Append(gconv.Interfaces(v.Slice())...)
|
||||
case *SortedArray:
|
||||
a.Append(gconv.Interfaces(v.Slice())...)
|
||||
case *SortedIntArray:
|
||||
a.Append(gconv.Interfaces(v.Slice())...)
|
||||
case *SortedStringArray:
|
||||
a.Append(gconv.Interfaces(v.Slice())...)
|
||||
default:
|
||||
a.Append(gconv.Interfaces(array)...)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Fill fills an array with num entries of the value <value>,
|
||||
// keys starting at the <startIndex> parameter.
|
||||
func (a *Array) Fill(startIndex int, num int, value interface{}) *Array {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if startIndex < 0 {
|
||||
startIndex = 0
|
||||
}
|
||||
for i := startIndex; i < startIndex+num; i++ {
|
||||
if i > len(a.array)-1 {
|
||||
a.array = append(a.array, value)
|
||||
} else {
|
||||
a.array[i] = value
|
||||
}
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Chunk splits an array into multiple arrays,
|
||||
// the size of each array is determined by <size>.
|
||||
// The last chunk may contain less than size elements.
|
||||
func (a *Array) Chunk(size int) [][]interface{} {
|
||||
if size < 1 {
|
||||
return nil
|
||||
}
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
length := len(a.array)
|
||||
chunks := int(math.Ceil(float64(length) / float64(size)))
|
||||
var n [][]interface{}
|
||||
for i, end := 0, 0; chunks > 0; chunks-- {
|
||||
end = (i + 1) * size
|
||||
if end > length {
|
||||
end = length
|
||||
}
|
||||
n = append(n, a.array[i*size:end])
|
||||
i++
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Pad pads array to the specified length with <value>.
|
||||
// If size is positive then the array is padded on the right, or negative on the left.
|
||||
// If the absolute value of <size> is less than or equal to the length of the array
|
||||
// then no padding takes place.
|
||||
func (a *Array) Pad(size int, val interface{}) *Array {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if size == 0 || (size > 0 && size < len(a.array)) || (size < 0 && size > -len(a.array)) {
|
||||
return a
|
||||
}
|
||||
n := size
|
||||
if size < 0 {
|
||||
n = -size
|
||||
}
|
||||
n -= len(a.array)
|
||||
tmp := make([]interface{}, n)
|
||||
for i := 0; i < n; i++ {
|
||||
tmp[i] = val
|
||||
}
|
||||
if size > 0 {
|
||||
a.array = append(a.array, tmp...)
|
||||
} else {
|
||||
a.array = append(tmp, a.array...)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Rand randomly returns one item from array(no deleting).
|
||||
func (a *Array) Rand() interface{} {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return a.array[grand.Intn(len(a.array))]
|
||||
}
|
||||
|
||||
// Rands randomly returns <size> items from array(no deleting).
|
||||
func (a *Array) Rands(size int) []interface{} {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
if size > len(a.array) {
|
||||
size = len(a.array)
|
||||
}
|
||||
n := make([]interface{}, size)
|
||||
for i, v := range grand.Perm(len(a.array)) {
|
||||
n[i] = a.array[v]
|
||||
if i == size-1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Shuffle randomly shuffles the array.
|
||||
func (a *Array) Shuffle() *Array {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
for i, v := range grand.Perm(len(a.array)) {
|
||||
a.array[i], a.array[v] = a.array[v], a.array[i]
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Reverse makes array with elements in reverse order.
|
||||
func (a *Array) Reverse() *Array {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
for i, j := 0, len(a.array)-1; i < j; i, j = i+1, j-1 {
|
||||
a.array[i], a.array[j] = a.array[j], a.array[i]
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Join joins array elements with a string <glue>.
|
||||
func (a *Array) Join(glue string) string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
for k, v := range a.array {
|
||||
buffer.WriteString(gconv.String(v))
|
||||
if k != len(a.array)-1 {
|
||||
buffer.WriteString(glue)
|
||||
}
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// CountValues counts the number of occurrences of all values in the array.
|
||||
func (a *Array) CountValues() map[interface{}]int {
|
||||
m := make(map[interface{}]int)
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
for _, v := range a.array {
|
||||
m[v]++
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// String returns current array as a string.
|
||||
func (a *Array) String() string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return fmt.Sprint(a.array)
|
||||
}
|
||||
618
g/container/garray/garray_normal_string.go
Normal file
618
g/container/garray/garray_normal_string.go
Normal file
@ -0,0 +1,618 @@
|
||||
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package garray
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/g/internal/rwmutex"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"github.com/gogf/gf/g/util/grand"
|
||||
)
|
||||
|
||||
type StringArray struct {
|
||||
mu *rwmutex.RWMutex
|
||||
array []string
|
||||
}
|
||||
|
||||
// NewStringArray creates and returns an empty array.
|
||||
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewStringArray(unsafe ...bool) *StringArray {
|
||||
return NewStringArraySize(0, 0, unsafe...)
|
||||
}
|
||||
|
||||
// NewStringArraySize create and returns an array with given size and cap.
|
||||
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewStringArraySize(size int, cap int, unsafe ...bool) *StringArray {
|
||||
return &StringArray{
|
||||
mu: rwmutex.New(unsafe...),
|
||||
array: make([]string, size, cap),
|
||||
}
|
||||
}
|
||||
|
||||
// NewStringArrayFrom creates and returns an array with given slice <array>.
|
||||
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewStringArrayFrom(array []string, unsafe ...bool) *StringArray {
|
||||
return &StringArray{
|
||||
mu: rwmutex.New(unsafe...),
|
||||
array: array,
|
||||
}
|
||||
}
|
||||
|
||||
// NewStringArrayFromCopy creates and returns an array from a copy of given slice <array>.
|
||||
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewStringArrayFromCopy(array []string, unsafe ...bool) *StringArray {
|
||||
newArray := make([]string, len(array))
|
||||
copy(newArray, array)
|
||||
return &StringArray{
|
||||
mu: rwmutex.New(unsafe...),
|
||||
array: newArray,
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the value of the specified index,
|
||||
// the caller should notice the boundary of the array.
|
||||
func (a *StringArray) Get(index int) string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
value := a.array[index]
|
||||
return value
|
||||
}
|
||||
|
||||
// Set sets value to specified index.
|
||||
func (a *StringArray) Set(index int, value string) *StringArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
a.array[index] = value
|
||||
return a
|
||||
}
|
||||
|
||||
// SetArray sets the underlying slice array with the given <array>.
|
||||
func (a *StringArray) SetArray(array []string) *StringArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
a.array = array
|
||||
return a
|
||||
}
|
||||
|
||||
// Replace replaces the array items by given <array> from the beginning of array.
|
||||
func (a *StringArray) Replace(array []string) *StringArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
max := len(array)
|
||||
if max > len(a.array) {
|
||||
max = len(a.array)
|
||||
}
|
||||
for i := 0; i < max; i++ {
|
||||
a.array[i] = array[i]
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Sum returns the sum of values in an array.
|
||||
func (a *StringArray) Sum() (sum int) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
for _, v := range a.array {
|
||||
sum += gconv.Int(v)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Sort sorts the array in increasing order.
|
||||
// The parameter <reverse> controls whether sort
|
||||
// in increasing order(default) or decreasing order
|
||||
func (a *StringArray) Sort(reverse ...bool) *StringArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if len(reverse) > 0 && reverse[0] {
|
||||
sort.Slice(a.array, func(i, j int) bool {
|
||||
if strings.Compare(a.array[i], a.array[j]) < 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
sort.Strings(a.array)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// SortFunc sorts the array by custom function <less>.
|
||||
func (a *StringArray) SortFunc(less func(v1, v2 string) bool) *StringArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
sort.Slice(a.array, func(i, j int) bool {
|
||||
return less(a.array[i], a.array[j])
|
||||
})
|
||||
return a
|
||||
}
|
||||
|
||||
// InsertBefore inserts the <value> to the front of <index>.
|
||||
func (a *StringArray) InsertBefore(index int, value string) *StringArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
rear := append([]string{}, a.array[index:]...)
|
||||
a.array = append(a.array[0:index], value)
|
||||
a.array = append(a.array, rear...)
|
||||
return a
|
||||
}
|
||||
|
||||
// InsertAfter inserts the <value> to the back of <index>.
|
||||
func (a *StringArray) InsertAfter(index int, value string) *StringArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
rear := append([]string{}, a.array[index+1:]...)
|
||||
a.array = append(a.array[0:index+1], value)
|
||||
a.array = append(a.array, rear...)
|
||||
return a
|
||||
}
|
||||
|
||||
// Remove removes an item by index.
|
||||
func (a *StringArray) Remove(index int) string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
// Determine array boundaries when deleting to improve deletion efficiency。
|
||||
if index == 0 {
|
||||
value := a.array[0]
|
||||
a.array = a.array[1:]
|
||||
return value
|
||||
} else if index == len(a.array)-1 {
|
||||
value := a.array[index]
|
||||
a.array = a.array[:index]
|
||||
return value
|
||||
}
|
||||
// If it is a non-boundary delete,
|
||||
// it will involve the creation of an array,
|
||||
// then the deletion is less efficient.
|
||||
value := a.array[index]
|
||||
a.array = append(a.array[:index], a.array[index+1:]...)
|
||||
return value
|
||||
}
|
||||
|
||||
// PushLeft pushes one or multiple items to the beginning of array.
|
||||
func (a *StringArray) PushLeft(value ...string) *StringArray {
|
||||
a.mu.Lock()
|
||||
a.array = append(value, a.array...)
|
||||
a.mu.Unlock()
|
||||
return a
|
||||
}
|
||||
|
||||
// PushRight pushes one or multiple items to the end of array.
|
||||
// It equals to Append.
|
||||
func (a *StringArray) PushRight(value ...string) *StringArray {
|
||||
a.mu.Lock()
|
||||
a.array = append(a.array, value...)
|
||||
a.mu.Unlock()
|
||||
return a
|
||||
}
|
||||
|
||||
// PopLeft pops and returns an item from the beginning of array.
|
||||
func (a *StringArray) PopLeft() string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
value := a.array[0]
|
||||
a.array = a.array[1:]
|
||||
return value
|
||||
}
|
||||
|
||||
// PopRight pops and returns an item from the end of array.
|
||||
func (a *StringArray) PopRight() string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
index := len(a.array) - 1
|
||||
value := a.array[index]
|
||||
a.array = a.array[:index]
|
||||
return value
|
||||
}
|
||||
|
||||
// PopRand randomly pops and return an item out of array.
|
||||
func (a *StringArray) PopRand() string {
|
||||
return a.Remove(grand.Intn(len(a.array)))
|
||||
}
|
||||
|
||||
// PopRands randomly pops and returns <size> items out of array.
|
||||
func (a *StringArray) PopRands(size int) []string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if size > len(a.array) {
|
||||
size = len(a.array)
|
||||
}
|
||||
array := make([]string, size)
|
||||
for i := 0; i < size; i++ {
|
||||
index := grand.Intn(len(a.array))
|
||||
array[i] = a.array[index]
|
||||
a.array = append(a.array[:index], a.array[index+1:]...)
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
// PopLefts pops and returns <size> items from the beginning of array.
|
||||
func (a *StringArray) PopLefts(size int) []string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
length := len(a.array)
|
||||
if size > length {
|
||||
size = length
|
||||
}
|
||||
value := a.array[0:size]
|
||||
a.array = a.array[size:]
|
||||
return value
|
||||
}
|
||||
|
||||
// PopRights pops and returns <size> items from the end of array.
|
||||
func (a *StringArray) PopRights(size int) []string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
index := len(a.array) - size
|
||||
if index < 0 {
|
||||
index = 0
|
||||
}
|
||||
value := a.array[index:]
|
||||
a.array = a.array[:index]
|
||||
return value
|
||||
}
|
||||
|
||||
// Range picks and returns items by range, like array[start:end].
|
||||
// Notice, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// else a pointer to the underlying data.
|
||||
//
|
||||
// If <end> is negative, then the offset will start from the end of array.
|
||||
// If <end> is omitted, then the sequence will have everything from start up
|
||||
// until the end of the array.
|
||||
func (a *StringArray) Range(start int, end ...int) []string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
offsetEnd := len(a.array)
|
||||
if len(end) > 0 && end[0] < offsetEnd {
|
||||
offsetEnd = end[0]
|
||||
}
|
||||
if start > offsetEnd {
|
||||
return nil
|
||||
}
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
array := ([]string)(nil)
|
||||
if a.mu.IsSafe() {
|
||||
array = make([]string, offsetEnd-start)
|
||||
copy(array, a.array[start:offsetEnd])
|
||||
} else {
|
||||
array = a.array[start:offsetEnd]
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
// SubSlice returns a slice of elements from the array as specified
|
||||
// by the <offset> and <size> parameters.
|
||||
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
|
||||
//
|
||||
// If offset is non-negative, the sequence will start at that offset in the array.
|
||||
// If offset is negative, the sequence will start that far from the end of the array.
|
||||
//
|
||||
// If length is given and is positive, then the sequence will have up to that many elements in it.
|
||||
// If the array is shorter than the length, then only the available array elements will be present.
|
||||
// If length is given and is negative then the sequence will stop that many elements from the end of the array.
|
||||
// If it is omitted, then the sequence will have everything from offset up until the end of the array.
|
||||
//
|
||||
// Any possibility crossing the left border of array, it will fail.
|
||||
func (a *StringArray) SubSlice(offset int, length ...int) []string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
size := len(a.array)
|
||||
if len(length) > 0 {
|
||||
size = length[0]
|
||||
}
|
||||
if offset > len(a.array) {
|
||||
return nil
|
||||
}
|
||||
if offset < 0 {
|
||||
offset = len(a.array) + offset
|
||||
if offset < 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if size < 0 {
|
||||
offset += size
|
||||
size = -size
|
||||
if offset < 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
end := offset + size
|
||||
if end > len(a.array) {
|
||||
end = len(a.array)
|
||||
size = len(a.array) - offset
|
||||
}
|
||||
if a.mu.IsSafe() {
|
||||
s := make([]string, size)
|
||||
copy(s, a.array[offset:])
|
||||
return s
|
||||
} else {
|
||||
return a.array[offset:end]
|
||||
}
|
||||
}
|
||||
|
||||
// See PushRight.
|
||||
func (a *StringArray) Append(value ...string) *StringArray {
|
||||
a.mu.Lock()
|
||||
a.array = append(a.array, value...)
|
||||
a.mu.Unlock()
|
||||
return a
|
||||
}
|
||||
|
||||
// Len returns the length of array.
|
||||
func (a *StringArray) Len() int {
|
||||
a.mu.RLock()
|
||||
length := len(a.array)
|
||||
a.mu.RUnlock()
|
||||
return length
|
||||
}
|
||||
|
||||
// Slice returns the underlying data of array.
|
||||
// Notice, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// else a pointer to the underlying data.
|
||||
func (a *StringArray) Slice() []string {
|
||||
array := ([]string)(nil)
|
||||
if a.mu.IsSafe() {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
array = make([]string, len(a.array))
|
||||
copy(array, a.array)
|
||||
} else {
|
||||
array = a.array
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
// Clone returns a new array, which is a copy of current array.
|
||||
func (a *StringArray) Clone() (newArray *StringArray) {
|
||||
a.mu.RLock()
|
||||
array := make([]string, len(a.array))
|
||||
copy(array, a.array)
|
||||
a.mu.RUnlock()
|
||||
return NewStringArrayFrom(array, !a.mu.IsSafe())
|
||||
}
|
||||
|
||||
// Clear deletes all items of current array.
|
||||
func (a *StringArray) Clear() *StringArray {
|
||||
a.mu.Lock()
|
||||
if len(a.array) > 0 {
|
||||
a.array = make([]string, 0)
|
||||
}
|
||||
a.mu.Unlock()
|
||||
return a
|
||||
}
|
||||
|
||||
// Contains checks whether a value exists in the array.
|
||||
func (a *StringArray) Contains(value string) bool {
|
||||
return a.Search(value) != -1
|
||||
}
|
||||
|
||||
// Search searches array by <value>, returns the index of <value>,
|
||||
// or returns -1 if not exists.
|
||||
func (a *StringArray) Search(value string) int {
|
||||
if len(a.array) == 0 {
|
||||
return -1
|
||||
}
|
||||
a.mu.RLock()
|
||||
result := -1
|
||||
for index, v := range a.array {
|
||||
if strings.Compare(v, value) == 0 {
|
||||
result = index
|
||||
break
|
||||
}
|
||||
}
|
||||
a.mu.RUnlock()
|
||||
return result
|
||||
}
|
||||
|
||||
// Unique uniques the array, clear repeated items.
|
||||
func (a *StringArray) Unique() *StringArray {
|
||||
a.mu.Lock()
|
||||
for i := 0; i < len(a.array)-1; i++ {
|
||||
for j := i + 1; j < len(a.array); j++ {
|
||||
if a.array[i] == a.array[j] {
|
||||
a.array = append(a.array[:j], a.array[j+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
a.mu.Unlock()
|
||||
return a
|
||||
}
|
||||
|
||||
// LockFunc locks writing by callback function <f>.
|
||||
func (a *StringArray) LockFunc(f func(array []string)) *StringArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
f(a.array)
|
||||
return a
|
||||
}
|
||||
|
||||
// RLockFunc locks reading by callback function <f>.
|
||||
func (a *StringArray) RLockFunc(f func(array []string)) *StringArray {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
f(a.array)
|
||||
return a
|
||||
}
|
||||
|
||||
// Merge merges <array> into current array.
|
||||
// The parameter <array> can be any garray or slice type.
|
||||
// The difference between Merge and Append is Append supports only specified slice type,
|
||||
// but Merge supports more parameter types.
|
||||
func (a *StringArray) Merge(array interface{}) *StringArray {
|
||||
switch v := array.(type) {
|
||||
case *Array:
|
||||
a.Append(gconv.Strings(v.Slice())...)
|
||||
case *IntArray:
|
||||
a.Append(gconv.Strings(v.Slice())...)
|
||||
case *StringArray:
|
||||
a.Append(gconv.Strings(v.Slice())...)
|
||||
case *SortedArray:
|
||||
a.Append(gconv.Strings(v.Slice())...)
|
||||
case *SortedIntArray:
|
||||
a.Append(gconv.Strings(v.Slice())...)
|
||||
case *SortedStringArray:
|
||||
a.Append(gconv.Strings(v.Slice())...)
|
||||
default:
|
||||
a.Append(gconv.Strings(array)...)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Fill fills an array with num entries of the value <value>,
|
||||
// keys starting at the <startIndex> parameter.
|
||||
func (a *StringArray) Fill(startIndex int, num int, value string) *StringArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if startIndex < 0 {
|
||||
startIndex = 0
|
||||
}
|
||||
for i := startIndex; i < startIndex+num; i++ {
|
||||
if i > len(a.array)-1 {
|
||||
a.array = append(a.array, value)
|
||||
} else {
|
||||
a.array[i] = value
|
||||
}
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Chunk splits an array into multiple arrays,
|
||||
// the size of each array is determined by <size>.
|
||||
// The last chunk may contain less than size elements.
|
||||
func (a *StringArray) Chunk(size int) [][]string {
|
||||
if size < 1 {
|
||||
return nil
|
||||
}
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
length := len(a.array)
|
||||
chunks := int(math.Ceil(float64(length) / float64(size)))
|
||||
var n [][]string
|
||||
for i, end := 0, 0; chunks > 0; chunks-- {
|
||||
end = (i + 1) * size
|
||||
if end > length {
|
||||
end = length
|
||||
}
|
||||
n = append(n, a.array[i*size:end])
|
||||
i++
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Pad pads array to the specified length with <value>.
|
||||
// If size is positive then the array is padded on the right, or negative on the left.
|
||||
// If the absolute value of <size> is less than or equal to the length of the array
|
||||
// then no padding takes place.
|
||||
func (a *StringArray) Pad(size int, value string) *StringArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if size == 0 || (size > 0 && size < len(a.array)) || (size < 0 && size > -len(a.array)) {
|
||||
return a
|
||||
}
|
||||
n := size
|
||||
if size < 0 {
|
||||
n = -size
|
||||
}
|
||||
n -= len(a.array)
|
||||
tmp := make([]string, n)
|
||||
for i := 0; i < n; i++ {
|
||||
tmp[i] = value
|
||||
}
|
||||
if size > 0 {
|
||||
a.array = append(a.array, tmp...)
|
||||
} else {
|
||||
a.array = append(tmp, a.array...)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Rand randomly returns one item from array(no deleting).
|
||||
func (a *StringArray) Rand() string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return a.array[grand.Intn(len(a.array))]
|
||||
}
|
||||
|
||||
// Rands randomly returns <size> items from array(no deleting).
|
||||
func (a *StringArray) Rands(size int) []string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
if size > len(a.array) {
|
||||
size = len(a.array)
|
||||
}
|
||||
n := make([]string, size)
|
||||
for i, v := range grand.Perm(len(a.array)) {
|
||||
n[i] = a.array[v]
|
||||
if i == size-1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Shuffle randomly shuffles the array.
|
||||
func (a *StringArray) Shuffle() *StringArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
for i, v := range grand.Perm(len(a.array)) {
|
||||
a.array[i], a.array[v] = a.array[v], a.array[i]
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Reverse makes array with elements in reverse order.
|
||||
func (a *StringArray) Reverse() *StringArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
for i, j := 0, len(a.array)-1; i < j; i, j = i+1, j-1 {
|
||||
a.array[i], a.array[j] = a.array[j], a.array[i]
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Join joins array elements with a string <glue>.
|
||||
func (a *StringArray) Join(glue string) string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
for k, v := range a.array {
|
||||
buffer.WriteString(gconv.String(v))
|
||||
if k != len(a.array)-1 {
|
||||
buffer.WriteString(glue)
|
||||
}
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// CountValues counts the number of occurrences of all values in the array.
|
||||
func (a *StringArray) CountValues() map[string]int {
|
||||
m := make(map[string]int)
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
for _, v := range a.array {
|
||||
m[v]++
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// String returns current array as a string.
|
||||
func (a *StringArray) String() string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return fmt.Sprint(a.array)
|
||||
}
|
||||
@ -1,218 +1,543 @@
|
||||
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package garray
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/gtype"
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/gogf/gf/g/container/gtype"
|
||||
"github.com/gogf/gf/g/internal/rwmutex"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"github.com/gogf/gf/g/util/grand"
|
||||
)
|
||||
|
||||
// 默认按照从低到高进行排序
|
||||
// It's using increasing order in default.
|
||||
type SortedIntArray struct {
|
||||
mu *rwmutex.RWMutex // 互斥锁
|
||||
cap int // 初始化设置的数组容量
|
||||
array []int // 底层数组
|
||||
unique *gtype.Bool // 是否要求不能重复(默认false)
|
||||
compareFunc func(v1, v2 int) int // 比较函数,返回值 -1: v1 < v2;0: v1 == v2;1: v1 > v2
|
||||
mu *rwmutex.RWMutex
|
||||
array []int
|
||||
unique *gtype.Bool // Whether enable unique feature(false)
|
||||
comparator func(v1, v2 int) int // Comparison function(it returns -1: v1 < v2; 0: v1 == v2; 1: v1 > v2)
|
||||
}
|
||||
|
||||
// 创建一个排序的int数组
|
||||
func NewSortedIntArray(cap int, safe...bool) *SortedIntArray {
|
||||
return &SortedIntArray {
|
||||
mu : rwmutex.New(safe...),
|
||||
array : make([]int, 0, cap),
|
||||
unique : gtype.NewBool(),
|
||||
compareFunc : func(v1, v2 int) int {
|
||||
if v1 < v2 {
|
||||
return -1
|
||||
}
|
||||
if v1 > v2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
},
|
||||
}
|
||||
// NewSortedIntArray creates and returns an empty sorted array.
|
||||
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewSortedIntArray(unsafe ...bool) *SortedIntArray {
|
||||
return NewSortedIntArraySize(0, unsafe...)
|
||||
}
|
||||
|
||||
// 添加加数据项
|
||||
func (a *SortedIntArray) Add(values...int) {
|
||||
if len(values) == 0 {
|
||||
return
|
||||
}
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
for _, value := range values {
|
||||
index, cmp := a.binSearch(value, false)
|
||||
if a.unique.Val() && cmp == 0 {
|
||||
continue
|
||||
}
|
||||
if index < 0 {
|
||||
a.array = append(a.array, value)
|
||||
continue
|
||||
}
|
||||
// 加到指定索引后面
|
||||
if cmp > 0 {
|
||||
index++
|
||||
}
|
||||
rear := append([]int{}, a.array[index : ]...)
|
||||
a.array = append(a.array[0 : index], value)
|
||||
a.array = append(a.array, rear...)
|
||||
}
|
||||
// NewSortedIntArraySize create and returns an sorted array with given size and cap.
|
||||
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewSortedIntArraySize(cap int, unsafe ...bool) *SortedIntArray {
|
||||
return &SortedIntArray{
|
||||
mu: rwmutex.New(unsafe...),
|
||||
array: make([]int, 0, cap),
|
||||
unique: gtype.NewBool(),
|
||||
comparator: func(v1, v2 int) int {
|
||||
if v1 < v2 {
|
||||
return -1
|
||||
}
|
||||
if v1 > v2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 获取指定索引的数据项, 调用方注意判断数组边界
|
||||
// NewIntArrayFrom creates and returns an sorted array with given slice <array>.
|
||||
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewSortedIntArrayFrom(array []int, unsafe ...bool) *SortedIntArray {
|
||||
a := NewSortedIntArraySize(0, unsafe...)
|
||||
a.array = array
|
||||
sort.Ints(a.array)
|
||||
return a
|
||||
}
|
||||
|
||||
// NewSortedIntArrayFromCopy creates and returns an sorted array from a copy of given slice <array>.
|
||||
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewSortedIntArrayFromCopy(array []int, unsafe ...bool) *SortedIntArray {
|
||||
newArray := make([]int, len(array))
|
||||
copy(newArray, array)
|
||||
return NewSortedIntArrayFrom(newArray, unsafe...)
|
||||
}
|
||||
|
||||
// SetArray sets the underlying slice array with the given <array>.
|
||||
func (a *SortedIntArray) SetArray(array []int) *SortedIntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
a.array = array
|
||||
sort.Ints(a.array)
|
||||
return a
|
||||
}
|
||||
|
||||
// Sort sorts the array in increasing order.
|
||||
// The parameter <reverse> controls whether sort
|
||||
// in increasing order(default) or decreasing order.
|
||||
func (a *SortedIntArray) Sort() *SortedIntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
sort.Ints(a.array)
|
||||
return a
|
||||
}
|
||||
|
||||
// Add adds one or multiple values to sorted array, the array always keeps sorted.
|
||||
func (a *SortedIntArray) Add(values ...int) *SortedIntArray {
|
||||
if len(values) == 0 {
|
||||
return a
|
||||
}
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
for _, value := range values {
|
||||
index, cmp := a.binSearch(value, false)
|
||||
if a.unique.Val() && cmp == 0 {
|
||||
continue
|
||||
}
|
||||
if index < 0 {
|
||||
a.array = append(a.array, value)
|
||||
continue
|
||||
}
|
||||
if cmp > 0 {
|
||||
index++
|
||||
}
|
||||
rear := append([]int{}, a.array[index:]...)
|
||||
a.array = append(a.array[0:index], value)
|
||||
a.array = append(a.array, rear...)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Get returns the value of the specified index,
|
||||
// the caller should notice the boundary of the array.
|
||||
func (a *SortedIntArray) Get(index int) int {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
value := a.array[index]
|
||||
return value
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
value := a.array[index]
|
||||
return value
|
||||
}
|
||||
|
||||
// 删除指定索引的数据项, 调用方注意判断数组边界
|
||||
// Remove removes an item by index.
|
||||
func (a *SortedIntArray) Remove(index int) int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
// 边界删除判断,以提高删除效率
|
||||
if index == 0 {
|
||||
value := a.array[0]
|
||||
a.array = a.array[1 : ]
|
||||
return value
|
||||
} else if index == len(a.array) - 1 {
|
||||
value := a.array[index]
|
||||
a.array = a.array[: index]
|
||||
return value
|
||||
}
|
||||
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
|
||||
value := a.array[index]
|
||||
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
|
||||
return value
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
// Determine array boundaries when deleting to improve deletion efficiency.
|
||||
if index == 0 {
|
||||
value := a.array[0]
|
||||
a.array = a.array[1:]
|
||||
return value
|
||||
} else if index == len(a.array)-1 {
|
||||
value := a.array[index]
|
||||
a.array = a.array[:index]
|
||||
return value
|
||||
}
|
||||
// If it is a non-boundary delete,
|
||||
// it will involve the creation of an array,
|
||||
// then the deletion is less efficient.
|
||||
value := a.array[index]
|
||||
a.array = append(a.array[:index], a.array[index+1:]...)
|
||||
return value
|
||||
}
|
||||
|
||||
// 将最左端(索引为0)的数据项移出数组,并返回该数据项
|
||||
// PopLeft pops and returns an item from the beginning of array.
|
||||
func (a *SortedIntArray) PopLeft() int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
value := a.array[0]
|
||||
a.array = a.array[1 : ]
|
||||
return value
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
value := a.array[0]
|
||||
a.array = a.array[1:]
|
||||
return value
|
||||
}
|
||||
|
||||
// 将最右端(索引为length - 1)的数据项移出数组,并返回该数据项
|
||||
// PopRight pops and returns an item from the end of array.
|
||||
func (a *SortedIntArray) PopRight() int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
index := len(a.array) - 1
|
||||
value := a.array[index]
|
||||
a.array = a.array[: index]
|
||||
return value
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
index := len(a.array) - 1
|
||||
value := a.array[index]
|
||||
a.array = a.array[:index]
|
||||
return value
|
||||
}
|
||||
|
||||
// 数组长度
|
||||
// PopRand randomly pops and return an item out of array.
|
||||
func (a *SortedIntArray) PopRand() int {
|
||||
return a.Remove(grand.Intn(len(a.array)))
|
||||
}
|
||||
|
||||
// PopRands randomly pops and returns <size> items out of array.
|
||||
func (a *SortedIntArray) PopRands(size int) []int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if size > len(a.array) {
|
||||
size = len(a.array)
|
||||
}
|
||||
array := make([]int, size)
|
||||
for i := 0; i < size; i++ {
|
||||
index := grand.Intn(len(a.array))
|
||||
array[i] = a.array[index]
|
||||
a.array = append(a.array[:index], a.array[index+1:]...)
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
// PopLefts pops and returns <size> items from the beginning of array.
|
||||
func (a *SortedIntArray) PopLefts(size int) []int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
length := len(a.array)
|
||||
if size > length {
|
||||
size = length
|
||||
}
|
||||
value := a.array[0:size]
|
||||
a.array = a.array[size:]
|
||||
return value
|
||||
}
|
||||
|
||||
// PopRights pops and returns <size> items from the end of array.
|
||||
func (a *SortedIntArray) PopRights(size int) []int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
index := len(a.array) - size
|
||||
if index < 0 {
|
||||
index = 0
|
||||
}
|
||||
value := a.array[index:]
|
||||
a.array = a.array[:index]
|
||||
return value
|
||||
}
|
||||
|
||||
// Range picks and returns items by range, like array[start:end].
|
||||
// Notice, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// else a pointer to the underlying data.
|
||||
//
|
||||
// If <end> is negative, then the offset will start from the end of array.
|
||||
// If <end> is omitted, then the sequence will have everything from start up
|
||||
// until the end of the array.
|
||||
func (a *SortedIntArray) Range(start int, end ...int) []int {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
offsetEnd := len(a.array)
|
||||
if len(end) > 0 && end[0] < offsetEnd {
|
||||
offsetEnd = end[0]
|
||||
}
|
||||
if start > offsetEnd {
|
||||
return nil
|
||||
}
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
array := ([]int)(nil)
|
||||
if a.mu.IsSafe() {
|
||||
array = make([]int, offsetEnd-start)
|
||||
copy(array, a.array[start:offsetEnd])
|
||||
} else {
|
||||
array = a.array[start:offsetEnd]
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
// SubSlice returns a slice of elements from the array as specified
|
||||
// by the <offset> and <size> parameters.
|
||||
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
|
||||
//
|
||||
// If offset is non-negative, the sequence will start at that offset in the array.
|
||||
// If offset is negative, the sequence will start that far from the end of the array.
|
||||
//
|
||||
// If length is given and is positive, then the sequence will have up to that many elements in it.
|
||||
// If the array is shorter than the length, then only the available array elements will be present.
|
||||
// If length is given and is negative then the sequence will stop that many elements from the end of the array.
|
||||
// If it is omitted, then the sequence will have everything from offset up until the end of the array.
|
||||
//
|
||||
// Any possibility crossing the left border of array, it will fail.
|
||||
func (a *SortedIntArray) SubSlice(offset int, length ...int) []int {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
size := len(a.array)
|
||||
if len(length) > 0 {
|
||||
size = length[0]
|
||||
}
|
||||
if offset > len(a.array) {
|
||||
return nil
|
||||
}
|
||||
if offset < 0 {
|
||||
offset = len(a.array) + offset
|
||||
if offset < 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if size < 0 {
|
||||
offset += size
|
||||
size = -size
|
||||
if offset < 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
end := offset + size
|
||||
if end > len(a.array) {
|
||||
end = len(a.array)
|
||||
size = len(a.array) - offset
|
||||
}
|
||||
if a.mu.IsSafe() {
|
||||
s := make([]int, size)
|
||||
copy(s, a.array[offset:])
|
||||
return s
|
||||
} else {
|
||||
return a.array[offset:end]
|
||||
}
|
||||
}
|
||||
|
||||
// Len returns the length of array.
|
||||
func (a *SortedIntArray) Len() int {
|
||||
a.mu.RLock()
|
||||
length := len(a.array)
|
||||
a.mu.RUnlock()
|
||||
return length
|
||||
a.mu.RLock()
|
||||
length := len(a.array)
|
||||
a.mu.RUnlock()
|
||||
return length
|
||||
}
|
||||
|
||||
// 返回原始数据数组
|
||||
// Sum returns the sum of values in an array.
|
||||
func (a *SortedIntArray) Sum() (sum int) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
for _, v := range a.array {
|
||||
sum += v
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Slice returns the underlying data of array.
|
||||
// Notice, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// else a pointer to the underlying data.
|
||||
func (a *SortedIntArray) Slice() []int {
|
||||
array := ([]int)(nil)
|
||||
if a.mu.IsSafe() {
|
||||
a.mu.RLock()
|
||||
array = make([]int, len(a.array))
|
||||
for k, v := range a.array {
|
||||
array[k] = v
|
||||
}
|
||||
a.mu.RUnlock()
|
||||
} else {
|
||||
array = a.array
|
||||
}
|
||||
return array
|
||||
array := ([]int)(nil)
|
||||
if a.mu.IsSafe() {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
array = make([]int, len(a.array))
|
||||
copy(array, a.array)
|
||||
} else {
|
||||
array = a.array
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
// 查找指定数值的索引位置,返回索引位置(具体匹配位置或者最后对比位置)及查找结果
|
||||
// 返回值: 最后比较位置, 比较结果
|
||||
func (a *SortedIntArray) Search(value int) (index int, result int) {
|
||||
return a.binSearch(value, true)
|
||||
// Contains checks whether a value exists in the array.
|
||||
func (a *SortedIntArray) Contains(value int) bool {
|
||||
return a.Search(value) != -1
|
||||
}
|
||||
|
||||
// Search searches array by <value>, returns the index of <value>,
|
||||
// or returns -1 if not exists.
|
||||
func (a *SortedIntArray) Search(value int) (index int) {
|
||||
if i, r := a.binSearch(value, true); r == 0 {
|
||||
return i
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Binary search.
|
||||
// It returns the last compared index and the result.
|
||||
// If <result> equals to 0, it means the value at <index> is equals to <value>.
|
||||
// If <result> lesser than 0, it means the value at <index> is lesser than <value>.
|
||||
// If <result> greater than 0, it means the value at <index> is greater than <value>.
|
||||
func (a *SortedIntArray) binSearch(value int, lock bool) (index int, result int) {
|
||||
if len(a.array) == 0 {
|
||||
return -1, -2
|
||||
}
|
||||
if lock {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
}
|
||||
min := 0
|
||||
max := len(a.array) - 1
|
||||
mid := 0
|
||||
cmp := -2
|
||||
for {
|
||||
mid = int((min + max) / 2)
|
||||
cmp = a.compareFunc(value, a.array[mid])
|
||||
switch cmp {
|
||||
case -1 : max = mid - 1
|
||||
case 0 :
|
||||
case 1 : min = mid + 1
|
||||
}
|
||||
if cmp == 0 || min >= max {
|
||||
break
|
||||
}
|
||||
}
|
||||
return mid, cmp
|
||||
if len(a.array) == 0 {
|
||||
return -1, -2
|
||||
}
|
||||
if lock {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
}
|
||||
min := 0
|
||||
max := len(a.array) - 1
|
||||
mid := 0
|
||||
cmp := -2
|
||||
for min <= max {
|
||||
mid = int((min + max) / 2)
|
||||
cmp = a.comparator(value, a.array[mid])
|
||||
switch {
|
||||
case cmp < 0:
|
||||
max = mid - 1
|
||||
case cmp > 0:
|
||||
min = mid + 1
|
||||
default:
|
||||
return mid, cmp
|
||||
}
|
||||
}
|
||||
return mid, cmp
|
||||
}
|
||||
|
||||
// 设置是否允许数组唯一
|
||||
func (a *SortedIntArray) SetUnique(unique bool) {
|
||||
oldUnique := a.unique.Val()
|
||||
a.unique.Set(unique)
|
||||
if unique && oldUnique != unique {
|
||||
a.doUnique()
|
||||
}
|
||||
// SetUnique sets unique mark to the array,
|
||||
// which means it does not contain any repeated items.
|
||||
// It also do unique check, remove all repeated items.
|
||||
func (a *SortedIntArray) SetUnique(unique bool) *SortedIntArray {
|
||||
oldUnique := a.unique.Val()
|
||||
a.unique.Set(unique)
|
||||
if unique && oldUnique != unique {
|
||||
a.Unique()
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// 清理数组中重复的元素项
|
||||
func (a *SortedIntArray) doUnique() {
|
||||
a.mu.Lock()
|
||||
i := 0
|
||||
for {
|
||||
if i == len(a.array) - 1 {
|
||||
break
|
||||
}
|
||||
if a.compareFunc(a.array[i], a.array[i + 1]) == 0 {
|
||||
a.array = append(a.array[ : i + 1], a.array[i + 1 + 1 : ]...)
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
}
|
||||
a.mu.Unlock()
|
||||
// Unique uniques the array, clear repeated items.
|
||||
func (a *SortedIntArray) Unique() *SortedIntArray {
|
||||
a.mu.Lock()
|
||||
i := 0
|
||||
for {
|
||||
if i == len(a.array)-1 {
|
||||
break
|
||||
}
|
||||
if a.comparator(a.array[i], a.array[i+1]) == 0 {
|
||||
a.array = append(a.array[:i+1], a.array[i+1+1:]...)
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
}
|
||||
a.mu.Unlock()
|
||||
return a
|
||||
}
|
||||
|
||||
// 清空数据数组
|
||||
func (a *SortedIntArray) Clear() {
|
||||
a.mu.Lock()
|
||||
if len(a.array) > 0 {
|
||||
a.array = make([]int, 0, a.cap)
|
||||
}
|
||||
a.mu.Unlock()
|
||||
// Clone returns a new array, which is a copy of current array.
|
||||
func (a *SortedIntArray) Clone() (newArray *SortedIntArray) {
|
||||
a.mu.RLock()
|
||||
array := make([]int, len(a.array))
|
||||
copy(array, a.array)
|
||||
a.mu.RUnlock()
|
||||
return NewSortedIntArrayFrom(array, !a.mu.IsSafe())
|
||||
}
|
||||
|
||||
// 使用自定义方法执行加锁修改操作
|
||||
func (a *SortedIntArray) LockFunc(f func(array []int)) {
|
||||
a.mu.Lock(true)
|
||||
defer a.mu.Unlock(true)
|
||||
f(a.array)
|
||||
// Clear deletes all items of current array.
|
||||
func (a *SortedIntArray) Clear() *SortedIntArray {
|
||||
a.mu.Lock()
|
||||
if len(a.array) > 0 {
|
||||
a.array = make([]int, 0)
|
||||
}
|
||||
a.mu.Unlock()
|
||||
return a
|
||||
}
|
||||
|
||||
// 使用自定义方法执行加锁读取操作
|
||||
func (a *SortedIntArray) RLockFunc(f func(array []int)) {
|
||||
a.mu.RLock(true)
|
||||
defer a.mu.RUnlock(true)
|
||||
f(a.array)
|
||||
}
|
||||
// LockFunc locks writing by callback function <f>.
|
||||
func (a *SortedIntArray) LockFunc(f func(array []int)) *SortedIntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
f(a.array)
|
||||
return a
|
||||
}
|
||||
|
||||
// RLockFunc locks reading by callback function <f>.
|
||||
func (a *SortedIntArray) RLockFunc(f func(array []int)) *SortedIntArray {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
f(a.array)
|
||||
return a
|
||||
}
|
||||
|
||||
// Merge merges <array> into current array.
|
||||
// The parameter <array> can be any garray or slice type.
|
||||
// The difference between Merge and Append is Append supports only specified slice type,
|
||||
// but Merge supports more parameter types.
|
||||
func (a *SortedIntArray) Merge(array interface{}) *SortedIntArray {
|
||||
switch v := array.(type) {
|
||||
case *Array:
|
||||
a.Add(gconv.Ints(v.Slice())...)
|
||||
case *IntArray:
|
||||
a.Add(gconv.Ints(v.Slice())...)
|
||||
case *StringArray:
|
||||
a.Add(gconv.Ints(v.Slice())...)
|
||||
case *SortedArray:
|
||||
a.Add(gconv.Ints(v.Slice())...)
|
||||
case *SortedIntArray:
|
||||
a.Add(gconv.Ints(v.Slice())...)
|
||||
case *SortedStringArray:
|
||||
a.Add(gconv.Ints(v.Slice())...)
|
||||
default:
|
||||
a.Add(gconv.Ints(array)...)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Chunk splits an array into multiple arrays,
|
||||
// the size of each array is determined by <size>.
|
||||
// The last chunk may contain less than size elements.
|
||||
func (a *SortedIntArray) Chunk(size int) [][]int {
|
||||
if size < 1 {
|
||||
return nil
|
||||
}
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
length := len(a.array)
|
||||
chunks := int(math.Ceil(float64(length) / float64(size)))
|
||||
var n [][]int
|
||||
for i, end := 0, 0; chunks > 0; chunks-- {
|
||||
end = (i + 1) * size
|
||||
if end > length {
|
||||
end = length
|
||||
}
|
||||
n = append(n, a.array[i*size:end])
|
||||
i++
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Rand randomly returns one item from array(no deleting).
|
||||
func (a *SortedIntArray) Rand() int {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return a.array[grand.Intn(len(a.array))]
|
||||
}
|
||||
|
||||
// Rands randomly returns <size> items from array(no deleting).
|
||||
func (a *SortedIntArray) Rands(size int) []int {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
if size > len(a.array) {
|
||||
size = len(a.array)
|
||||
}
|
||||
n := make([]int, size)
|
||||
for i, v := range grand.Perm(len(a.array)) {
|
||||
n[i] = a.array[v]
|
||||
if i == size-1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Join joins array elements with a string <glue>.
|
||||
func (a *SortedIntArray) Join(glue string) string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
for k, v := range a.array {
|
||||
buffer.WriteString(gconv.String(v))
|
||||
if k != len(a.array)-1 {
|
||||
buffer.WriteString(glue)
|
||||
}
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// CountValues counts the number of occurrences of all values in the array.
|
||||
func (a *SortedIntArray) CountValues() map[int]int {
|
||||
m := make(map[int]int)
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
for _, v := range a.array {
|
||||
m[v]++
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// String returns current array as a string.
|
||||
func (a *SortedIntArray) String() string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return fmt.Sprint(a.array)
|
||||
}
|
||||
|
||||
@ -1,211 +1,544 @@
|
||||
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package garray
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/gtype"
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/gogf/gf/g/container/gtype"
|
||||
"github.com/gogf/gf/g/internal/rwmutex"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"github.com/gogf/gf/g/util/grand"
|
||||
)
|
||||
|
||||
// 默认按照从低到高进行排序
|
||||
// It's using increasing order in default.
|
||||
type SortedArray struct {
|
||||
mu *rwmutex.RWMutex // 互斥锁
|
||||
cap int // 初始化设置的数组容量
|
||||
array []interface{} // 底层数组
|
||||
unique *gtype.Bool // 是否要求不能重复
|
||||
compareFunc func(v1, v2 interface{}) int // 比较函数,返回值 -1: v1 < v2;0: v1 == v2;1: v1 > v2
|
||||
mu *rwmutex.RWMutex
|
||||
array []interface{}
|
||||
unique *gtype.Bool // Whether enable unique feature(false)
|
||||
comparator func(v1, v2 interface{}) int // Comparison function(it returns -1: v1 < v2; 0: v1 == v2; 1: v1 > v2)
|
||||
}
|
||||
|
||||
func NewSortedArray(cap int, compareFunc func(v1, v2 interface{}) int, safe...bool) *SortedArray {
|
||||
return &SortedArray{
|
||||
mu : rwmutex.New(safe...),
|
||||
unique : gtype.NewBool(),
|
||||
array : make([]interface{}, 0, cap),
|
||||
compareFunc : compareFunc,
|
||||
}
|
||||
// NewSortedArray creates and returns an empty sorted array.
|
||||
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety, which is false in default.
|
||||
// The parameter <comparator> used to compare values to sort in array,
|
||||
// if it returns value < 0, means v1 < v2;
|
||||
// if it returns value = 0, means v1 = v2;
|
||||
// if it returns value > 0, means v1 > v2;
|
||||
func NewSortedArray(comparator func(v1, v2 interface{}) int, unsafe ...bool) *SortedArray {
|
||||
return NewSortedArraySize(0, comparator, unsafe...)
|
||||
}
|
||||
|
||||
// 添加加数据项
|
||||
func (a *SortedArray) Add(values...interface{}) {
|
||||
if len(values) == 0 {
|
||||
return
|
||||
}
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
for _, value := range values {
|
||||
index, cmp := a.binSearch(value, false)
|
||||
if a.unique.Val() && cmp == 0 {
|
||||
continue
|
||||
}
|
||||
if index < 0 {
|
||||
a.array = append(a.array, value)
|
||||
continue
|
||||
}
|
||||
// 加到指定索引后面
|
||||
if cmp > 0 {
|
||||
index++
|
||||
}
|
||||
rear := append([]interface{}{}, a.array[index : ]...)
|
||||
a.array = append(a.array[0 : index], value)
|
||||
a.array = append(a.array, rear...)
|
||||
}
|
||||
// NewSortedArraySize create and returns an sorted array with given size and cap.
|
||||
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewSortedArraySize(cap int, comparator func(v1, v2 interface{}) int, unsafe ...bool) *SortedArray {
|
||||
return &SortedArray{
|
||||
mu: rwmutex.New(unsafe...),
|
||||
unique: gtype.NewBool(),
|
||||
array: make([]interface{}, 0, cap),
|
||||
comparator: comparator,
|
||||
}
|
||||
}
|
||||
|
||||
// 获取指定索引的数据项, 调用方注意判断数组边界
|
||||
// NewSortedArrayFrom creates and returns an sorted array with given slice <array>.
|
||||
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewSortedArrayFrom(array []interface{}, comparator func(v1, v2 interface{}) int, unsafe ...bool) *SortedArray {
|
||||
a := NewSortedArraySize(0, comparator, unsafe...)
|
||||
a.array = array
|
||||
sort.Slice(a.array, func(i, j int) bool {
|
||||
return a.comparator(a.array[i], a.array[j]) < 0
|
||||
})
|
||||
return a
|
||||
}
|
||||
|
||||
// NewSortedArrayFromCopy creates and returns an sorted array from a copy of given slice <array>.
|
||||
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewSortedArrayFromCopy(array []interface{}, comparator func(v1, v2 interface{}) int, unsafe ...bool) *SortedArray {
|
||||
newArray := make([]interface{}, len(array))
|
||||
copy(newArray, array)
|
||||
return NewSortedArrayFrom(newArray, comparator, unsafe...)
|
||||
}
|
||||
|
||||
// SetArray sets the underlying slice array with the given <array>.
|
||||
func (a *SortedArray) SetArray(array []interface{}) *SortedArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
a.array = array
|
||||
sort.Slice(a.array, func(i, j int) bool {
|
||||
return a.comparator(a.array[i], a.array[j]) < 0
|
||||
})
|
||||
return a
|
||||
}
|
||||
|
||||
// Sort sorts the array in increasing order.
|
||||
// The parameter <reverse> controls whether sort
|
||||
// in increasing order(default) or decreasing order
|
||||
func (a *SortedArray) Sort() *SortedArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
sort.Slice(a.array, func(i, j int) bool {
|
||||
return a.comparator(a.array[i], a.array[j]) < 0
|
||||
})
|
||||
return a
|
||||
}
|
||||
|
||||
// Add adds one or multiple values to sorted array, the array always keeps sorted.
|
||||
func (a *SortedArray) Add(values ...interface{}) *SortedArray {
|
||||
if len(values) == 0 {
|
||||
return a
|
||||
}
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
for _, value := range values {
|
||||
index, cmp := a.binSearch(value, false)
|
||||
if a.unique.Val() && cmp == 0 {
|
||||
continue
|
||||
}
|
||||
if index < 0 {
|
||||
a.array = append(a.array, value)
|
||||
continue
|
||||
}
|
||||
if cmp > 0 {
|
||||
index++
|
||||
}
|
||||
rear := append([]interface{}{}, a.array[index:]...)
|
||||
a.array = append(a.array[0:index], value)
|
||||
a.array = append(a.array, rear...)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Get returns the value of the specified index,
|
||||
// the caller should notice the boundary of the array.
|
||||
func (a *SortedArray) Get(index int) interface{} {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
value := a.array[index]
|
||||
return value
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
value := a.array[index]
|
||||
return value
|
||||
}
|
||||
|
||||
// 删除指定索引的数据项, 调用方注意判断数组边界
|
||||
// Remove removes an item by index.
|
||||
func (a *SortedArray) Remove(index int) interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
// 边界删除判断,以提高删除效率
|
||||
if index == 0 {
|
||||
value := a.array[0]
|
||||
a.array = a.array[1 : ]
|
||||
return value
|
||||
} else if index == len(a.array) - 1 {
|
||||
value := a.array[index]
|
||||
a.array = a.array[: index]
|
||||
return value
|
||||
}
|
||||
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
|
||||
value := a.array[index]
|
||||
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
|
||||
return value
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
// Determine array boundaries when deleting to improve deletion efficiency.
|
||||
if index == 0 {
|
||||
value := a.array[0]
|
||||
a.array = a.array[1:]
|
||||
return value
|
||||
} else if index == len(a.array)-1 {
|
||||
value := a.array[index]
|
||||
a.array = a.array[:index]
|
||||
return value
|
||||
}
|
||||
// If it is a non-boundary delete,
|
||||
// it will involve the creation of an array,
|
||||
// then the deletion is less efficient.
|
||||
value := a.array[index]
|
||||
a.array = append(a.array[:index], a.array[index+1:]...)
|
||||
return value
|
||||
}
|
||||
|
||||
// 将最左端(索引为0)的数据项移出数组,并返回该数据项
|
||||
// PopLeft pops and returns an item from the beginning of array.
|
||||
func (a *SortedArray) PopLeft() interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
value := a.array[0]
|
||||
a.array = a.array[1 : ]
|
||||
return value
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
value := a.array[0]
|
||||
a.array = a.array[1:]
|
||||
return value
|
||||
}
|
||||
|
||||
// 将最右端(索引为length - 1)的数据项移出数组,并返回该数据项
|
||||
// PopRight pops and returns an item from the end of array.
|
||||
func (a *SortedArray) PopRight() interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
index := len(a.array) - 1
|
||||
value := a.array[index]
|
||||
a.array = a.array[: index]
|
||||
return value
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
index := len(a.array) - 1
|
||||
value := a.array[index]
|
||||
a.array = a.array[:index]
|
||||
return value
|
||||
}
|
||||
|
||||
// 数组长度
|
||||
// PopRand randomly pops and return an item out of array.
|
||||
func (a *SortedArray) PopRand() interface{} {
|
||||
return a.Remove(grand.Intn(len(a.array)))
|
||||
}
|
||||
|
||||
// PopRands randomly pops and returns <size> items out of array.
|
||||
func (a *SortedArray) PopRands(size int) []interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if size > len(a.array) {
|
||||
size = len(a.array)
|
||||
}
|
||||
array := make([]interface{}, size)
|
||||
for i := 0; i < size; i++ {
|
||||
index := grand.Intn(len(a.array))
|
||||
array[i] = a.array[index]
|
||||
a.array = append(a.array[:index], a.array[index+1:]...)
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
// PopLefts pops and returns <size> items from the beginning of array.
|
||||
func (a *SortedArray) PopLefts(size int) []interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
length := len(a.array)
|
||||
if size > length {
|
||||
size = length
|
||||
}
|
||||
value := a.array[0:size]
|
||||
a.array = a.array[size:]
|
||||
return value
|
||||
}
|
||||
|
||||
// PopRights pops and returns <size> items from the end of array.
|
||||
func (a *SortedArray) PopRights(size int) []interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
index := len(a.array) - size
|
||||
if index < 0 {
|
||||
index = 0
|
||||
}
|
||||
value := a.array[index:]
|
||||
a.array = a.array[:index]
|
||||
return value
|
||||
}
|
||||
|
||||
// Range picks and returns items by range, like array[start:end].
|
||||
// Notice, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// else a pointer to the underlying data.
|
||||
//
|
||||
// If <end> is negative, then the offset will start from the end of array.
|
||||
// If <end> is omitted, then the sequence will have everything from start up
|
||||
// until the end of the array.
|
||||
func (a *SortedArray) Range(start int, end ...int) []interface{} {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
offsetEnd := len(a.array)
|
||||
if len(end) > 0 && end[0] < offsetEnd {
|
||||
offsetEnd = end[0]
|
||||
}
|
||||
if start > offsetEnd {
|
||||
return nil
|
||||
}
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
array := ([]interface{})(nil)
|
||||
if a.mu.IsSafe() {
|
||||
array = make([]interface{}, offsetEnd-start)
|
||||
copy(array, a.array[start:offsetEnd])
|
||||
} else {
|
||||
array = a.array[start:offsetEnd]
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
// SubSlice returns a slice of elements from the array as specified
|
||||
// by the <offset> and <size> parameters.
|
||||
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
|
||||
//
|
||||
// If offset is non-negative, the sequence will start at that offset in the array.
|
||||
// If offset is negative, the sequence will start that far from the end of the array.
|
||||
//
|
||||
// If length is given and is positive, then the sequence will have up to that many elements in it.
|
||||
// If the array is shorter than the length, then only the available array elements will be present.
|
||||
// If length is given and is negative then the sequence will stop that many elements from the end of the array.
|
||||
// If it is omitted, then the sequence will have everything from offset up until the end of the array.
|
||||
//
|
||||
// Any possibility crossing the left border of array, it will fail.
|
||||
func (a *SortedArray) SubSlice(offset int, length ...int) []interface{} {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
size := len(a.array)
|
||||
if len(length) > 0 {
|
||||
size = length[0]
|
||||
}
|
||||
if offset > len(a.array) {
|
||||
return nil
|
||||
}
|
||||
if offset < 0 {
|
||||
offset = len(a.array) + offset
|
||||
if offset < 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if size < 0 {
|
||||
offset += size
|
||||
size = -size
|
||||
if offset < 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
end := offset + size
|
||||
if end > len(a.array) {
|
||||
end = len(a.array)
|
||||
size = len(a.array) - offset
|
||||
}
|
||||
if a.mu.IsSafe() {
|
||||
s := make([]interface{}, size)
|
||||
copy(s, a.array[offset:])
|
||||
return s
|
||||
} else {
|
||||
return a.array[offset:end]
|
||||
}
|
||||
}
|
||||
|
||||
// Sum returns the sum of values in an array.
|
||||
func (a *SortedArray) Sum() (sum int) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
for _, v := range a.array {
|
||||
sum += gconv.Int(v)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Len returns the length of array.
|
||||
func (a *SortedArray) Len() int {
|
||||
a.mu.RLock()
|
||||
length := len(a.array)
|
||||
a.mu.RUnlock()
|
||||
return length
|
||||
a.mu.RLock()
|
||||
length := len(a.array)
|
||||
a.mu.RUnlock()
|
||||
return length
|
||||
}
|
||||
|
||||
// 返回原始数据数组
|
||||
// Slice returns the underlying data of array.
|
||||
// Notice, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// else a pointer to the underlying data.
|
||||
func (a *SortedArray) Slice() []interface{} {
|
||||
array := ([]interface{})(nil)
|
||||
if a.mu.IsSafe() {
|
||||
a.mu.RLock()
|
||||
array = make([]interface{}, len(a.array))
|
||||
for k, v := range a.array {
|
||||
array[k] = v
|
||||
}
|
||||
a.mu.RUnlock()
|
||||
} else {
|
||||
array = a.array
|
||||
}
|
||||
return array
|
||||
array := ([]interface{})(nil)
|
||||
if a.mu.IsSafe() {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
array = make([]interface{}, len(a.array))
|
||||
copy(array, a.array)
|
||||
} else {
|
||||
array = a.array
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
// 查找指定数值的索引位置,返回索引位置(具体匹配位置或者最后对比位置)及查找结果
|
||||
// 返回值: 最后比较位置, 比较结果
|
||||
func (a *SortedArray) Search(value interface{}) (index int, result int) {
|
||||
return a.binSearch(value, true)
|
||||
// Contains checks whether a value exists in the array.
|
||||
func (a *SortedArray) Contains(value interface{}) bool {
|
||||
return a.Search(value) != -1
|
||||
}
|
||||
|
||||
// 查找指定数值的索引位置,返回索引位置(具体匹配位置或者最后对比位置)及查找结果
|
||||
// 返回值: 最后比较位置, 比较结果
|
||||
func (a *SortedArray) binSearch(value interface{}, lock bool)(index int, result int) {
|
||||
if len(a.array) == 0 {
|
||||
return -1, -2
|
||||
}
|
||||
if lock {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
}
|
||||
min := 0
|
||||
max := len(a.array) - 1
|
||||
mid := 0
|
||||
cmp := -2
|
||||
for {
|
||||
mid = int((min + max) / 2)
|
||||
cmp = a.compareFunc(value, a.array[mid])
|
||||
switch cmp {
|
||||
case -1 : max = mid - 1
|
||||
case 0 :
|
||||
case 1 : min = mid + 1
|
||||
}
|
||||
if cmp == 0 || min >= max {
|
||||
break
|
||||
}
|
||||
}
|
||||
return mid, cmp
|
||||
// Search searches array by <value>, returns the index of <value>,
|
||||
// or returns -1 if not exists.
|
||||
func (a *SortedArray) Search(value interface{}) (index int) {
|
||||
if i, r := a.binSearch(value, true); r == 0 {
|
||||
return i
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// 设置是否允许数组唯一
|
||||
func (a *SortedArray) SetUnique(unique bool) {
|
||||
oldUnique := a.unique.Val()
|
||||
a.unique.Set(unique)
|
||||
if unique && oldUnique != unique {
|
||||
a.doUnique()
|
||||
}
|
||||
// Binary search.
|
||||
// It returns the last compared index and the result.
|
||||
// If <result> equals to 0, it means the value at <index> is equals to <value>.
|
||||
// If <result> lesser than 0, it means the value at <index> is lesser than <value>.
|
||||
// If <result> greater than 0, it means the value at <index> is greater than <value>.
|
||||
func (a *SortedArray) binSearch(value interface{}, lock bool) (index int, result int) {
|
||||
if len(a.array) == 0 {
|
||||
return -1, -2
|
||||
}
|
||||
if lock {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
}
|
||||
min := 0
|
||||
max := len(a.array) - 1
|
||||
mid := 0
|
||||
cmp := -2
|
||||
for min <= max {
|
||||
mid = int((min + max) / 2)
|
||||
cmp = a.comparator(value, a.array[mid])
|
||||
switch {
|
||||
case cmp < 0:
|
||||
max = mid - 1
|
||||
case cmp > 0:
|
||||
min = mid + 1
|
||||
default:
|
||||
return mid, cmp
|
||||
}
|
||||
}
|
||||
return mid, cmp
|
||||
}
|
||||
|
||||
// 清理数组中重复的元素项
|
||||
func (a *SortedArray) doUnique() {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
i := 0
|
||||
for {
|
||||
if i == len(a.array) - 1 {
|
||||
break
|
||||
}
|
||||
if a.compareFunc(a.array[i], a.array[i + 1]) == 0 {
|
||||
a.array = append(a.array[ : i + 1], a.array[i + 1 + 1 : ]...)
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
}
|
||||
// SetUnique sets unique mark to the array,
|
||||
// which means it does not contain any repeated items.
|
||||
// It also do unique check, remove all repeated items.
|
||||
func (a *SortedArray) SetUnique(unique bool) *SortedArray {
|
||||
oldUnique := a.unique.Val()
|
||||
a.unique.Set(unique)
|
||||
if unique && oldUnique != unique {
|
||||
a.Unique()
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// 清空数据数组
|
||||
func (a *SortedArray) Clear() {
|
||||
a.mu.Lock()
|
||||
if len(a.array) > 0 {
|
||||
a.array = make([]interface{}, 0, a.cap)
|
||||
}
|
||||
a.mu.Unlock()
|
||||
// Unique uniques the array, clear repeated items.
|
||||
func (a *SortedArray) Unique() *SortedArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
i := 0
|
||||
for {
|
||||
if i == len(a.array)-1 {
|
||||
break
|
||||
}
|
||||
if a.comparator(a.array[i], a.array[i+1]) == 0 {
|
||||
a.array = append(a.array[:i+1], a.array[i+1+1:]...)
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// 使用自定义方法执行加锁修改操作
|
||||
func (a *SortedArray) LockFunc(f func(array []interface{})) {
|
||||
a.mu.Lock(true)
|
||||
defer a.mu.Unlock(true)
|
||||
f(a.array)
|
||||
// Clone returns a new array, which is a copy of current array.
|
||||
func (a *SortedArray) Clone() (newArray *SortedArray) {
|
||||
a.mu.RLock()
|
||||
array := make([]interface{}, len(a.array))
|
||||
copy(array, a.array)
|
||||
a.mu.RUnlock()
|
||||
return NewSortedArrayFrom(array, a.comparator, !a.mu.IsSafe())
|
||||
}
|
||||
|
||||
// 使用自定义方法执行加锁读取操作
|
||||
func (a *SortedArray) RLockFunc(f func(array []interface{})) {
|
||||
a.mu.RLock(true)
|
||||
defer a.mu.RUnlock(true)
|
||||
f(a.array)
|
||||
}
|
||||
// Clear deletes all items of current array.
|
||||
func (a *SortedArray) Clear() *SortedArray {
|
||||
a.mu.Lock()
|
||||
if len(a.array) > 0 {
|
||||
a.array = make([]interface{}, 0)
|
||||
}
|
||||
a.mu.Unlock()
|
||||
return a
|
||||
}
|
||||
|
||||
// LockFunc locks writing by callback function <f>.
|
||||
func (a *SortedArray) LockFunc(f func(array []interface{})) *SortedArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
f(a.array)
|
||||
return a
|
||||
}
|
||||
|
||||
// RLockFunc locks reading by callback function <f>.
|
||||
func (a *SortedArray) RLockFunc(f func(array []interface{})) *SortedArray {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
f(a.array)
|
||||
return a
|
||||
}
|
||||
|
||||
// Merge merges <array> into current array.
|
||||
// The parameter <array> can be any garray or slice type.
|
||||
// The difference between Merge and Append is Append supports only specified slice type,
|
||||
// but Merge supports more parameter types.
|
||||
func (a *SortedArray) Merge(array interface{}) *SortedArray {
|
||||
switch v := array.(type) {
|
||||
case *Array:
|
||||
a.Add(gconv.Interfaces(v.Slice())...)
|
||||
case *IntArray:
|
||||
a.Add(gconv.Interfaces(v.Slice())...)
|
||||
case *StringArray:
|
||||
a.Add(gconv.Interfaces(v.Slice())...)
|
||||
case *SortedArray:
|
||||
a.Add(gconv.Interfaces(v.Slice())...)
|
||||
case *SortedIntArray:
|
||||
a.Add(gconv.Interfaces(v.Slice())...)
|
||||
case *SortedStringArray:
|
||||
a.Add(gconv.Interfaces(v.Slice())...)
|
||||
default:
|
||||
a.Add(gconv.Interfaces(array)...)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Chunk splits an array into multiple arrays,
|
||||
// the size of each array is determined by <size>.
|
||||
// The last chunk may contain less than size elements.
|
||||
func (a *SortedArray) Chunk(size int) [][]interface{} {
|
||||
if size < 1 {
|
||||
return nil
|
||||
}
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
length := len(a.array)
|
||||
chunks := int(math.Ceil(float64(length) / float64(size)))
|
||||
var n [][]interface{}
|
||||
for i, end := 0, 0; chunks > 0; chunks-- {
|
||||
end = (i + 1) * size
|
||||
if end > length {
|
||||
end = length
|
||||
}
|
||||
n = append(n, a.array[i*size:end])
|
||||
i++
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Rand randomly returns one item from array(no deleting).
|
||||
func (a *SortedArray) Rand() interface{} {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return a.array[grand.Intn(len(a.array))]
|
||||
}
|
||||
|
||||
// Rands randomly returns <size> items from array(no deleting).
|
||||
func (a *SortedArray) Rands(size int) []interface{} {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
if size > len(a.array) {
|
||||
size = len(a.array)
|
||||
}
|
||||
n := make([]interface{}, size)
|
||||
for i, v := range grand.Perm(len(a.array)) {
|
||||
n[i] = a.array[v]
|
||||
if i == size-1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Join joins array elements with a string <glue>.
|
||||
func (a *SortedArray) Join(glue string) string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
for k, v := range a.array {
|
||||
buffer.WriteString(gconv.String(v))
|
||||
if k != len(a.array)-1 {
|
||||
buffer.WriteString(glue)
|
||||
}
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// CountValues counts the number of occurrences of all values in the array.
|
||||
func (a *SortedArray) CountValues() map[interface{}]int {
|
||||
m := make(map[interface{}]int)
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
for _, v := range a.array {
|
||||
m[v]++
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// String returns current array as a string.
|
||||
func (a *SortedArray) String() string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return fmt.Sprint(a.array)
|
||||
}
|
||||
|
||||
@ -1,212 +1,538 @@
|
||||
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package garray
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/gtype"
|
||||
"strings"
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/g/container/gtype"
|
||||
"github.com/gogf/gf/g/internal/rwmutex"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"github.com/gogf/gf/g/util/grand"
|
||||
)
|
||||
|
||||
// 默认按照从低到高进行排序
|
||||
// It's using increasing order in default.
|
||||
type SortedStringArray struct {
|
||||
mu *rwmutex.RWMutex // 互斥锁
|
||||
cap int // 初始化设置的数组容量
|
||||
array []string // 底层数组
|
||||
unique *gtype.Bool // 是否要求不能重复
|
||||
compareFunc func(v1, v2 string) int // 比较函数,返回值 -1: v1 < v2;0: v1 == v2;1: v1 > v2
|
||||
mu *rwmutex.RWMutex
|
||||
array []string
|
||||
unique *gtype.Bool // Whether enable unique feature(false)
|
||||
comparator func(v1, v2 string) int // Comparison function(it returns -1: v1 < v2; 0: v1 == v2; 1: v1 > v2)
|
||||
}
|
||||
|
||||
func NewSortedStringArray(cap int, safe...bool) *SortedStringArray {
|
||||
return &SortedStringArray {
|
||||
mu : rwmutex.New(safe...),
|
||||
array : make([]string, 0, cap),
|
||||
unique : gtype.NewBool(),
|
||||
compareFunc : func(v1, v2 string) int {
|
||||
return strings.Compare(v1, v2)
|
||||
},
|
||||
}
|
||||
// NewSortedStringArray creates and returns an empty sorted array.
|
||||
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewSortedStringArray(unsafe ...bool) *SortedStringArray {
|
||||
return NewSortedStringArraySize(0, unsafe...)
|
||||
}
|
||||
|
||||
// 添加加数据项
|
||||
func (a *SortedStringArray) Add(values...string) {
|
||||
if len(values) == 0 {
|
||||
return
|
||||
}
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
for _, value := range values {
|
||||
index, cmp := a.binSearch(value, false)
|
||||
if a.unique.Val() && cmp == 0 {
|
||||
continue
|
||||
}
|
||||
if index < 0 {
|
||||
a.array = append(a.array, value)
|
||||
continue
|
||||
}
|
||||
// 加到指定索引后面
|
||||
if cmp > 0 {
|
||||
index++
|
||||
}
|
||||
rear := append([]string{}, a.array[index : ]...)
|
||||
a.array = append(a.array[0 : index], value)
|
||||
a.array = append(a.array, rear...)
|
||||
}
|
||||
// NewSortedStringArraySize create and returns an sorted array with given size and cap.
|
||||
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewSortedStringArraySize(cap int, unsafe ...bool) *SortedStringArray {
|
||||
return &SortedStringArray{
|
||||
mu: rwmutex.New(unsafe...),
|
||||
array: make([]string, 0, cap),
|
||||
unique: gtype.NewBool(),
|
||||
comparator: func(v1, v2 string) int {
|
||||
return strings.Compare(v1, v2)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 获取指定索引的数据项, 调用方注意判断数组边界
|
||||
// NewSortedStringArrayFrom creates and returns an sorted array with given slice <array>.
|
||||
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewSortedStringArrayFrom(array []string, unsafe ...bool) *SortedStringArray {
|
||||
a := NewSortedStringArraySize(0, unsafe...)
|
||||
a.array = array
|
||||
sort.Strings(a.array)
|
||||
return a
|
||||
}
|
||||
|
||||
// NewSortedStringArrayFromCopy creates and returns an sorted array from a copy of given slice <array>.
|
||||
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewSortedStringArrayFromCopy(array []string, unsafe ...bool) *SortedStringArray {
|
||||
newArray := make([]string, len(array))
|
||||
copy(newArray, array)
|
||||
return NewSortedStringArrayFrom(newArray, unsafe...)
|
||||
}
|
||||
|
||||
// SetArray sets the underlying slice array with the given <array>.
|
||||
func (a *SortedStringArray) SetArray(array []string) *SortedStringArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
a.array = array
|
||||
sort.Strings(a.array)
|
||||
return a
|
||||
}
|
||||
|
||||
// Sort sorts the array in increasing order.
|
||||
// The parameter <reverse> controls whether sort
|
||||
// in increasing order(default) or decreasing order.
|
||||
func (a *SortedStringArray) Sort() *SortedStringArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
sort.Strings(a.array)
|
||||
return a
|
||||
}
|
||||
|
||||
// Add adds one or multiple values to sorted array, the array always keeps sorted.
|
||||
func (a *SortedStringArray) Add(values ...string) *SortedStringArray {
|
||||
if len(values) == 0 {
|
||||
return a
|
||||
}
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
for _, value := range values {
|
||||
index, cmp := a.binSearch(value, false)
|
||||
if a.unique.Val() && cmp == 0 {
|
||||
continue
|
||||
}
|
||||
if index < 0 {
|
||||
a.array = append(a.array, value)
|
||||
continue
|
||||
}
|
||||
if cmp > 0 {
|
||||
index++
|
||||
}
|
||||
rear := append([]string{}, a.array[index:]...)
|
||||
a.array = append(a.array[0:index], value)
|
||||
a.array = append(a.array, rear...)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Get returns the value of the specified index,
|
||||
// the caller should notice the boundary of the array.
|
||||
func (a *SortedStringArray) Get(index int) string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
value := a.array[index]
|
||||
return value
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
value := a.array[index]
|
||||
return value
|
||||
}
|
||||
|
||||
// 删除指定索引的数据项, 调用方注意判断数组边界
|
||||
// Remove removes an item by index.
|
||||
func (a *SortedStringArray) Remove(index int) string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
// 边界删除判断,以提高删除效率
|
||||
if index == 0 {
|
||||
value := a.array[0]
|
||||
a.array = a.array[1 : ]
|
||||
return value
|
||||
} else if index == len(a.array) - 1 {
|
||||
value := a.array[index]
|
||||
a.array = a.array[: index]
|
||||
return value
|
||||
}
|
||||
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
|
||||
value := a.array[index]
|
||||
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
|
||||
return value
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
// Determine array boundaries when deleting to improve deletion efficiency.
|
||||
if index == 0 {
|
||||
value := a.array[0]
|
||||
a.array = a.array[1:]
|
||||
return value
|
||||
} else if index == len(a.array)-1 {
|
||||
value := a.array[index]
|
||||
a.array = a.array[:index]
|
||||
return value
|
||||
}
|
||||
// If it is a non-boundary delete,
|
||||
// it will involve the creation of an array,
|
||||
// then the deletion is less efficient.
|
||||
value := a.array[index]
|
||||
a.array = append(a.array[:index], a.array[index+1:]...)
|
||||
return value
|
||||
}
|
||||
|
||||
// 将最左端(索引为0)的数据项移出数组,并返回该数据项
|
||||
// PopLeft pops and returns an item from the beginning of array.
|
||||
func (a *SortedStringArray) PopLeft() string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
value := a.array[0]
|
||||
a.array = a.array[1 : ]
|
||||
return value
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
value := a.array[0]
|
||||
a.array = a.array[1:]
|
||||
return value
|
||||
}
|
||||
|
||||
// 将最右端(索引为length - 1)的数据项移出数组,并返回该数据项
|
||||
// PopRight pops and returns an item from the end of array.
|
||||
func (a *SortedStringArray) PopRight() string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
index := len(a.array) - 1
|
||||
value := a.array[index]
|
||||
a.array = a.array[: index]
|
||||
return value
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
index := len(a.array) - 1
|
||||
value := a.array[index]
|
||||
a.array = a.array[:index]
|
||||
return value
|
||||
}
|
||||
|
||||
// 数组长度
|
||||
// PopRand randomly pops and return an item out of array.
|
||||
func (a *SortedStringArray) PopRand() string {
|
||||
return a.Remove(grand.Intn(len(a.array)))
|
||||
}
|
||||
|
||||
// PopRands randomly pops and returns <size> items out of array.
|
||||
func (a *SortedStringArray) PopRands(size int) []string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if size > len(a.array) {
|
||||
size = len(a.array)
|
||||
}
|
||||
array := make([]string, size)
|
||||
for i := 0; i < size; i++ {
|
||||
index := grand.Intn(len(a.array))
|
||||
array[i] = a.array[index]
|
||||
a.array = append(a.array[:index], a.array[index+1:]...)
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
// PopLefts pops and returns <size> items from the beginning of array.
|
||||
func (a *SortedStringArray) PopLefts(size int) []string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
length := len(a.array)
|
||||
if size > length {
|
||||
size = length
|
||||
}
|
||||
value := a.array[0:size]
|
||||
a.array = a.array[size:]
|
||||
return value
|
||||
}
|
||||
|
||||
// PopRights pops and returns <size> items from the end of array.
|
||||
func (a *SortedStringArray) PopRights(size int) []string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
index := len(a.array) - size
|
||||
if index < 0 {
|
||||
index = 0
|
||||
}
|
||||
value := a.array[index:]
|
||||
a.array = a.array[:index]
|
||||
return value
|
||||
}
|
||||
|
||||
// Range picks and returns items by range, like array[start:end].
|
||||
// Notice, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// else a pointer to the underlying data.
|
||||
//
|
||||
// If <end> is negative, then the offset will start from the end of array.
|
||||
// If <end> is omitted, then the sequence will have everything from start up
|
||||
// until the end of the array.
|
||||
func (a *SortedStringArray) Range(start int, end ...int) []string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
offsetEnd := len(a.array)
|
||||
if len(end) > 0 && end[0] < offsetEnd {
|
||||
offsetEnd = end[0]
|
||||
}
|
||||
if start > offsetEnd {
|
||||
return nil
|
||||
}
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
array := ([]string)(nil)
|
||||
if a.mu.IsSafe() {
|
||||
array = make([]string, offsetEnd-start)
|
||||
copy(array, a.array[start:offsetEnd])
|
||||
} else {
|
||||
array = a.array[start:offsetEnd]
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
// SubSlice returns a slice of elements from the array as specified
|
||||
// by the <offset> and <size> parameters.
|
||||
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
|
||||
//
|
||||
// If offset is non-negative, the sequence will start at that offset in the array.
|
||||
// If offset is negative, the sequence will start that far from the end of the array.
|
||||
//
|
||||
// If length is given and is positive, then the sequence will have up to that many elements in it.
|
||||
// If the array is shorter than the length, then only the available array elements will be present.
|
||||
// If length is given and is negative then the sequence will stop that many elements from the end of the array.
|
||||
// If it is omitted, then the sequence will have everything from offset up until the end of the array.
|
||||
//
|
||||
// Any possibility crossing the left border of array, it will fail.
|
||||
func (a *SortedStringArray) SubSlice(offset int, length ...int) []string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
size := len(a.array)
|
||||
if len(length) > 0 {
|
||||
size = length[0]
|
||||
}
|
||||
if offset > len(a.array) {
|
||||
return nil
|
||||
}
|
||||
if offset < 0 {
|
||||
offset = len(a.array) + offset
|
||||
if offset < 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if size < 0 {
|
||||
offset += size
|
||||
size = -size
|
||||
if offset < 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
end := offset + size
|
||||
if end > len(a.array) {
|
||||
end = len(a.array)
|
||||
size = len(a.array) - offset
|
||||
}
|
||||
if a.mu.IsSafe() {
|
||||
s := make([]string, size)
|
||||
copy(s, a.array[offset:])
|
||||
return s
|
||||
} else {
|
||||
return a.array[offset:end]
|
||||
}
|
||||
}
|
||||
|
||||
// Sum returns the sum of values in an array.
|
||||
func (a *SortedStringArray) Sum() (sum int) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
for _, v := range a.array {
|
||||
sum += gconv.Int(v)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Len returns the length of array.
|
||||
func (a *SortedStringArray) Len() int {
|
||||
a.mu.RLock()
|
||||
length := len(a.array)
|
||||
a.mu.RUnlock()
|
||||
return length
|
||||
a.mu.RLock()
|
||||
length := len(a.array)
|
||||
a.mu.RUnlock()
|
||||
return length
|
||||
}
|
||||
|
||||
// 返回原始数据数组
|
||||
// Slice returns the underlying data of array.
|
||||
// Notice, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// else a pointer to the underlying data.
|
||||
func (a *SortedStringArray) Slice() []string {
|
||||
array := ([]string)(nil)
|
||||
if a.mu.IsSafe() {
|
||||
a.mu.RLock()
|
||||
array = make([]string, len(a.array))
|
||||
for k, v := range a.array {
|
||||
array[k] = v
|
||||
}
|
||||
a.mu.RUnlock()
|
||||
} else {
|
||||
array = a.array
|
||||
}
|
||||
return array
|
||||
array := ([]string)(nil)
|
||||
if a.mu.IsSafe() {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
array = make([]string, len(a.array))
|
||||
copy(array, a.array)
|
||||
} else {
|
||||
array = a.array
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
// 查找指定数值的索引位置,返回索引位置(具体匹配位置或者最后对比位置)及查找结果
|
||||
// 返回值: 最后比较位置, 比较结果
|
||||
func (a *SortedStringArray) Search(value string) (index int, result int) {
|
||||
return a.binSearch(value, true)
|
||||
// Contains checks whether a value exists in the array.
|
||||
func (a *SortedStringArray) Contains(value string) bool {
|
||||
return a.Search(value) != -1
|
||||
}
|
||||
|
||||
// Search searches array by <value>, returns the index of <value>,
|
||||
// or returns -1 if not exists.
|
||||
func (a *SortedStringArray) Search(value string) (index int) {
|
||||
if i, r := a.binSearch(value, true); r == 0 {
|
||||
return i
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Binary search.
|
||||
// It returns the last compared index and the result.
|
||||
// If <result> equals to 0, it means the value at <index> is equals to <value>.
|
||||
// If <result> lesser than 0, it means the value at <index> is lesser than <value>.
|
||||
// If <result> greater than 0, it means the value at <index> is greater than <value>.
|
||||
func (a *SortedStringArray) binSearch(value string, lock bool) (index int, result int) {
|
||||
if len(a.array) == 0 {
|
||||
return -1, -2
|
||||
}
|
||||
if lock {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
}
|
||||
min := 0
|
||||
max := len(a.array) - 1
|
||||
mid := 0
|
||||
cmp := -2
|
||||
for {
|
||||
mid = int((min + max) / 2)
|
||||
cmp = a.compareFunc(value, a.array[mid])
|
||||
switch cmp {
|
||||
case -1 : max = mid - 1
|
||||
case 0 :
|
||||
case 1 : min = mid + 1
|
||||
}
|
||||
if cmp == 0 || min >= max {
|
||||
break
|
||||
}
|
||||
}
|
||||
return mid, cmp
|
||||
if len(a.array) == 0 {
|
||||
return -1, -2
|
||||
}
|
||||
if lock {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
}
|
||||
min := 0
|
||||
max := len(a.array) - 1
|
||||
mid := 0
|
||||
cmp := -2
|
||||
for min <= max {
|
||||
mid = int((min + max) / 2)
|
||||
cmp = a.comparator(value, a.array[mid])
|
||||
switch {
|
||||
case cmp < 0:
|
||||
max = mid - 1
|
||||
case cmp > 0:
|
||||
min = mid + 1
|
||||
default:
|
||||
return mid, cmp
|
||||
}
|
||||
}
|
||||
return mid, cmp
|
||||
}
|
||||
|
||||
// 设置是否允许数组唯一
|
||||
func (a *SortedStringArray) SetUnique(unique bool) {
|
||||
oldUnique := a.unique.Val()
|
||||
a.unique.Set(unique)
|
||||
if unique && oldUnique != unique {
|
||||
a.doUnique()
|
||||
}
|
||||
// SetUnique sets unique mark to the array,
|
||||
// which means it does not contain any repeated items.
|
||||
// It also do unique check, remove all repeated items.
|
||||
func (a *SortedStringArray) SetUnique(unique bool) *SortedStringArray {
|
||||
oldUnique := a.unique.Val()
|
||||
a.unique.Set(unique)
|
||||
if unique && oldUnique != unique {
|
||||
a.Unique()
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// 清理数组中重复的元素项
|
||||
func (a *SortedStringArray) doUnique() {
|
||||
a.mu.Lock()
|
||||
i := 0
|
||||
for {
|
||||
if i == len(a.array) - 1 {
|
||||
break
|
||||
}
|
||||
if a.compareFunc(a.array[i], a.array[i + 1]) == 0 {
|
||||
a.array = append(a.array[ : i + 1], a.array[i + 1 + 1 : ]...)
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
}
|
||||
a.mu.Unlock()
|
||||
// Unique uniques the array, clear repeated items.
|
||||
func (a *SortedStringArray) Unique() *SortedStringArray {
|
||||
a.mu.Lock()
|
||||
i := 0
|
||||
for {
|
||||
if i == len(a.array)-1 {
|
||||
break
|
||||
}
|
||||
if a.comparator(a.array[i], a.array[i+1]) == 0 {
|
||||
a.array = append(a.array[:i+1], a.array[i+1+1:]...)
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
}
|
||||
a.mu.Unlock()
|
||||
return a
|
||||
}
|
||||
|
||||
// 清空数据数组
|
||||
func (a *SortedStringArray) Clear() {
|
||||
a.mu.Lock()
|
||||
if len(a.array) > 0 {
|
||||
a.array = make([]string, 0, a.cap)
|
||||
}
|
||||
a.mu.Unlock()
|
||||
// Clone returns a new array, which is a copy of current array.
|
||||
func (a *SortedStringArray) Clone() (newArray *SortedStringArray) {
|
||||
a.mu.RLock()
|
||||
array := make([]string, len(a.array))
|
||||
copy(array, a.array)
|
||||
a.mu.RUnlock()
|
||||
return NewSortedStringArrayFrom(array, !a.mu.IsSafe())
|
||||
}
|
||||
|
||||
// 使用自定义方法执行加锁修改操作
|
||||
func (a *SortedStringArray) LockFunc(f func(array []string)) {
|
||||
a.mu.Lock(true)
|
||||
defer a.mu.Unlock(true)
|
||||
f(a.array)
|
||||
// Clear deletes all items of current array.
|
||||
func (a *SortedStringArray) Clear() *SortedStringArray {
|
||||
a.mu.Lock()
|
||||
if len(a.array) > 0 {
|
||||
a.array = make([]string, 0)
|
||||
}
|
||||
a.mu.Unlock()
|
||||
return a
|
||||
}
|
||||
|
||||
// 使用自定义方法执行加锁读取操作
|
||||
func (a *SortedStringArray) RLockFunc(f func(array []string)) {
|
||||
a.mu.RLock(true)
|
||||
defer a.mu.RUnlock(true)
|
||||
f(a.array)
|
||||
}
|
||||
// LockFunc locks writing by callback function <f>.
|
||||
func (a *SortedStringArray) LockFunc(f func(array []string)) *SortedStringArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
f(a.array)
|
||||
return a
|
||||
}
|
||||
|
||||
// RLockFunc locks reading by callback function <f>.
|
||||
func (a *SortedStringArray) RLockFunc(f func(array []string)) *SortedStringArray {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
f(a.array)
|
||||
return a
|
||||
}
|
||||
|
||||
// Merge merges <array> into current array.
|
||||
// The parameter <array> can be any garray or slice type.
|
||||
// The difference between Merge and Append is Append supports only specified slice type,
|
||||
// but Merge supports more parameter types.
|
||||
func (a *SortedStringArray) Merge(array interface{}) *SortedStringArray {
|
||||
switch v := array.(type) {
|
||||
case *Array:
|
||||
a.Add(gconv.Strings(v.Slice())...)
|
||||
case *IntArray:
|
||||
a.Add(gconv.Strings(v.Slice())...)
|
||||
case *StringArray:
|
||||
a.Add(gconv.Strings(v.Slice())...)
|
||||
case *SortedArray:
|
||||
a.Add(gconv.Strings(v.Slice())...)
|
||||
case *SortedIntArray:
|
||||
a.Add(gconv.Strings(v.Slice())...)
|
||||
case *SortedStringArray:
|
||||
a.Add(gconv.Strings(v.Slice())...)
|
||||
default:
|
||||
a.Add(gconv.Strings(array)...)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Chunk splits an array into multiple arrays,
|
||||
// the size of each array is determined by <size>.
|
||||
// The last chunk may contain less than size elements.
|
||||
func (a *SortedStringArray) Chunk(size int) [][]string {
|
||||
if size < 1 {
|
||||
return nil
|
||||
}
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
length := len(a.array)
|
||||
chunks := int(math.Ceil(float64(length) / float64(size)))
|
||||
var n [][]string
|
||||
for i, end := 0, 0; chunks > 0; chunks-- {
|
||||
end = (i + 1) * size
|
||||
if end > length {
|
||||
end = length
|
||||
}
|
||||
n = append(n, a.array[i*size:end])
|
||||
i++
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Rand randomly returns one item from array(no deleting).
|
||||
func (a *SortedStringArray) Rand() string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return a.array[grand.Intn(len(a.array))]
|
||||
}
|
||||
|
||||
// Rands randomly returns <size> items from array(no deleting).
|
||||
func (a *SortedStringArray) Rands(size int) []string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
if size > len(a.array) {
|
||||
size = len(a.array)
|
||||
}
|
||||
n := make([]string, size)
|
||||
for i, v := range grand.Perm(len(a.array)) {
|
||||
n[i] = a.array[v]
|
||||
if i == size-1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Join joins array elements with a string <glue>.
|
||||
func (a *SortedStringArray) Join(glue string) string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
for k, v := range a.array {
|
||||
buffer.WriteString(gconv.String(v))
|
||||
if k != len(a.array)-1 {
|
||||
buffer.WriteString(glue)
|
||||
}
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// CountValues counts the number of occurrences of all values in the array.
|
||||
func (a *SortedStringArray) CountValues() map[string]int {
|
||||
m := make(map[string]int)
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
for _, v := range a.array {
|
||||
m[v]++
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// String returns current array as a string.
|
||||
func (a *SortedStringArray) String() string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return fmt.Sprint(a.array)
|
||||
}
|
||||
|
||||
@ -1,175 +0,0 @@
|
||||
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
package garray
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
)
|
||||
|
||||
type StringArray struct {
|
||||
mu *rwmutex.RWMutex // 互斥锁
|
||||
cap int // 初始化设置的数组容量
|
||||
size int // 初始化设置的数组大小
|
||||
array []string // 底层数组
|
||||
}
|
||||
|
||||
func NewStringArray(size int, cap int, safe...bool) *StringArray {
|
||||
a := &StringArray{
|
||||
mu : rwmutex.New(safe...),
|
||||
}
|
||||
a.size = size
|
||||
if cap > 0 {
|
||||
a.cap = cap
|
||||
a.array = make([]string, size, cap)
|
||||
} else {
|
||||
a.array = make([]string, size)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// 获取指定索引的数据项, 调用方注意判断数组边界
|
||||
func (a *StringArray) Get(index int) string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
value := a.array[index]
|
||||
return value
|
||||
}
|
||||
|
||||
// 设置指定索引的数据项, 调用方注意判断数组边界
|
||||
func (a *StringArray) Set(index int, value string) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
a.array[index] = value
|
||||
}
|
||||
|
||||
// 在当前索引位置前插入一个数据项, 调用方注意判断数组边界
|
||||
func (a *StringArray) InsertBefore(index int, value string) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
rear := append([]string{}, a.array[index : ]...)
|
||||
a.array = append(a.array[0 : index], value)
|
||||
a.array = append(a.array, rear...)
|
||||
}
|
||||
|
||||
// 在当前索引位置后插入一个数据项, 调用方注意判断数组边界
|
||||
func (a *StringArray) InsertAfter(index int, value string) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
rear := append([]string{}, a.array[index + 1:]...)
|
||||
a.array = append(a.array[ 0: index + 1], value)
|
||||
a.array = append(a.array, rear...)
|
||||
}
|
||||
|
||||
// 删除指定索引的数据项, 调用方注意判断数组边界
|
||||
func (a *StringArray) Remove(index int) string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
// 边界删除判断,以提高删除效率
|
||||
if index == 0 {
|
||||
value := a.array[0]
|
||||
a.array = a.array[1 : ]
|
||||
return value
|
||||
} else if index == len(a.array) - 1 {
|
||||
value := a.array[index]
|
||||
a.array = a.array[: index]
|
||||
return value
|
||||
}
|
||||
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
|
||||
value := a.array[index]
|
||||
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
|
||||
return value
|
||||
}
|
||||
|
||||
// 追加数据项
|
||||
func (a *StringArray) Append(value...string) {
|
||||
a.mu.Lock()
|
||||
a.array = append(a.array, value...)
|
||||
a.mu.Unlock()
|
||||
}
|
||||
|
||||
// 数组长度
|
||||
func (a *StringArray) Len() int {
|
||||
a.mu.RLock()
|
||||
length := len(a.array)
|
||||
a.mu.RUnlock()
|
||||
return length
|
||||
}
|
||||
|
||||
// 返回原始数据数组
|
||||
func (a *StringArray) Slice() []string {
|
||||
array := ([]string)(nil)
|
||||
if a.mu.IsSafe() {
|
||||
a.mu.RLock()
|
||||
array = make([]string, len(a.array))
|
||||
for k, v := range a.array {
|
||||
array[k] = v
|
||||
}
|
||||
a.mu.RUnlock()
|
||||
} else {
|
||||
array = a.array
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
// 清空数据数组
|
||||
func (a *StringArray) Clear() {
|
||||
a.mu.Lock()
|
||||
if len(a.array) > 0 {
|
||||
if a.cap > 0 {
|
||||
a.array = make([]string, a.size, a.cap)
|
||||
} else {
|
||||
a.array = make([]string, a.size)
|
||||
}
|
||||
}
|
||||
a.mu.Unlock()
|
||||
}
|
||||
|
||||
// 查找指定数值的索引位置,返回索引位置,如果查找不到则返回-1
|
||||
func (a *StringArray) Search(value string) int {
|
||||
if len(a.array) == 0 {
|
||||
return -1
|
||||
}
|
||||
a.mu.RLock()
|
||||
result := -1
|
||||
for index, v := range a.array {
|
||||
if strings.Compare(v, value) == 0 {
|
||||
result = index
|
||||
break
|
||||
}
|
||||
}
|
||||
a.mu.RUnlock()
|
||||
return result
|
||||
}
|
||||
|
||||
// 清理数组中重复的元素项
|
||||
func (a *StringArray) Unique() *StringArray {
|
||||
a.mu.Lock()
|
||||
for i := 0; i < len(a.array) - 1; i++ {
|
||||
for j := i + 1; j < len(a.array); j++ {
|
||||
if a.array[i] == a.array[j] {
|
||||
a.array = append(a.array[ : j], a.array[j + 1 : ]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
a.mu.Unlock()
|
||||
return a
|
||||
}
|
||||
|
||||
// 使用自定义方法执行加锁修改操作
|
||||
func (a *StringArray) LockFunc(f func(array []string)) {
|
||||
a.mu.Lock(true)
|
||||
defer a.mu.Unlock(true)
|
||||
f(a.array)
|
||||
}
|
||||
|
||||
// 使用自定义方法执行加锁读取操作
|
||||
func (a *StringArray) RLockFunc(f func(array []string)) {
|
||||
a.mu.RLock(true)
|
||||
defer a.mu.RUnlock(true)
|
||||
f(a.array)
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// go test *.go
|
||||
|
||||
package garray_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/container/garray"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
func TestArray_Unique(t *testing.T) {
|
||||
expect := []int{1, 2, 3, 4, 5, 6}
|
||||
array := garray.NewIntArray(0, 0)
|
||||
array.Append(1, 1, 2, 3, 3, 4, 4, 5, 5, 6, 6)
|
||||
array.Unique()
|
||||
if fmt.Sprint(array.Slice()) != fmt.Sprint(expect) {
|
||||
t.Errorf("get: %v, expect: %v\n", array.Slice(), expect)
|
||||
}
|
||||
}
|
||||
43
g/container/garray/garray_z_bench_test.go
Normal file
43
g/container/garray/garray_z_bench_test.go
Normal file
@ -0,0 +1,43 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// go test *.go -bench=".*" -benchmem
|
||||
|
||||
package garray_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/garray"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
sortedIntArray = garray.NewSortedIntArray()
|
||||
)
|
||||
|
||||
func BenchmarkSortedIntArray_Add(b *testing.B) {
|
||||
b.N = 1000
|
||||
for i := 0; i < b.N; i++ {
|
||||
sortedIntArray.Add(i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSortedIntArray_Search(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
sortedIntArray.Search(i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSortedIntArray_PopLeft(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
sortedIntArray.PopLeft()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSortedIntArray_PopRight(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
sortedIntArray.PopLeft()
|
||||
}
|
||||
}
|
||||
111
g/container/garray/garray_z_example_test.go
Normal file
111
g/container/garray/garray_z_example_test.go
Normal file
@ -0,0 +1,111 @@
|
||||
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package garray_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/container/garray"
|
||||
)
|
||||
|
||||
func Example_basic() {
|
||||
// 创建普通的数组,默认并发安全(带锁)
|
||||
a := garray.New()
|
||||
|
||||
// 添加数据项
|
||||
for i := 0; i < 10; i++ {
|
||||
a.Append(i)
|
||||
}
|
||||
|
||||
// 获取当前数组长度
|
||||
fmt.Println(a.Len())
|
||||
|
||||
// 获取当前数据项列表
|
||||
fmt.Println(a.Slice())
|
||||
|
||||
// 获取指定索引项
|
||||
fmt.Println(a.Get(6))
|
||||
|
||||
// 查找指定数据项是否存在
|
||||
fmt.Println(a.Contains(6))
|
||||
fmt.Println(a.Contains(100))
|
||||
|
||||
// 在指定索引前插入数据项
|
||||
a.InsertAfter(9, 11)
|
||||
// 在指定索引后插入数据项
|
||||
a.InsertBefore(10, 10)
|
||||
|
||||
fmt.Println(a.Slice())
|
||||
|
||||
// 修改指定索引的数据项
|
||||
a.Set(0, 100)
|
||||
fmt.Println(a.Slice())
|
||||
|
||||
// 搜索数据项,返回搜索到的索引位置
|
||||
fmt.Println(a.Search(5))
|
||||
|
||||
// 删除指定索引的数据项
|
||||
a.Remove(0)
|
||||
fmt.Println(a.Slice())
|
||||
|
||||
// 清空数组
|
||||
fmt.Println(a.Slice())
|
||||
a.Clear()
|
||||
fmt.Println(a.Slice())
|
||||
|
||||
// Output:
|
||||
// 10
|
||||
// [0 1 2 3 4 5 6 7 8 9]
|
||||
// 6
|
||||
// true
|
||||
// false
|
||||
// [0 1 2 3 4 5 6 7 8 9 10 11]
|
||||
// [100 1 2 3 4 5 6 7 8 9 10 11]
|
||||
// 5
|
||||
// [1 2 3 4 5 6 7 8 9 10 11]
|
||||
// [1 2 3 4 5 6 7 8 9 10 11]
|
||||
// []
|
||||
}
|
||||
|
||||
func Example_rand() {
|
||||
array := garray.NewFrom([]interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9})
|
||||
// 随机返回两个数据项(不删除)
|
||||
fmt.Println(array.Rands(2))
|
||||
fmt.Println(array.PopRand())
|
||||
}
|
||||
|
||||
func Example_pop() {
|
||||
array := garray.NewFrom([]interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9})
|
||||
fmt.Println(array.PopLeft())
|
||||
fmt.Println(array.PopLefts(2))
|
||||
fmt.Println(array.PopRight())
|
||||
fmt.Println(array.PopRights(2))
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
// [2 3]
|
||||
// 9
|
||||
// [7 8]
|
||||
}
|
||||
|
||||
func Example_merge() {
|
||||
array1 := garray.NewFrom([]interface{}{1, 2})
|
||||
array2 := garray.NewFrom([]interface{}{3, 4})
|
||||
slice1 := []interface{}{5, 6}
|
||||
slice2 := []int{7, 8}
|
||||
slice3 := []string{"9", "0"}
|
||||
fmt.Println(array1.Slice())
|
||||
array1.Merge(array1)
|
||||
array1.Merge(array2)
|
||||
array1.Merge(slice1)
|
||||
array1.Merge(slice2)
|
||||
array1.Merge(slice3)
|
||||
fmt.Println(array1.Slice())
|
||||
|
||||
// Output:
|
||||
// [1 2]
|
||||
// [1 2 1 2 3 4 5 6 7 8 9 0]
|
||||
}
|
||||
105
g/container/garray/garray_z_unit_basic_test.go
Normal file
105
g/container/garray/garray_z_unit_basic_test.go
Normal file
@ -0,0 +1,105 @@
|
||||
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// go test *.go
|
||||
|
||||
package garray_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/garray"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_IntArray_Unique(t *testing.T) {
|
||||
expect := []int{1, 2, 3, 4, 5, 6}
|
||||
array := garray.NewIntArray()
|
||||
array.Append(1, 1, 2, 3, 3, 4, 4, 5, 5, 6, 6)
|
||||
array.Unique()
|
||||
gtest.Assert(array.Slice(), expect)
|
||||
}
|
||||
|
||||
func Test_SortedIntArray1(t *testing.T) {
|
||||
expect := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
|
||||
array := garray.NewSortedIntArray()
|
||||
for i := 10; i > -1; i-- {
|
||||
array.Add(i)
|
||||
}
|
||||
gtest.Assert(array.Slice(), expect)
|
||||
gtest.Assert(array.Add().Slice(), expect)
|
||||
}
|
||||
|
||||
func Test_SortedIntArray2(t *testing.T) {
|
||||
expect := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
|
||||
array := garray.NewSortedIntArray()
|
||||
for i := 0; i <= 10; i++ {
|
||||
array.Add(i)
|
||||
}
|
||||
gtest.Assert(array.Slice(), expect)
|
||||
}
|
||||
|
||||
func Test_SortedStringArray1(t *testing.T) {
|
||||
expect := []string{"0", "1", "10", "2", "3", "4", "5", "6", "7", "8", "9"}
|
||||
array1 := garray.NewSortedStringArray()
|
||||
array2 := garray.NewSortedStringArray(true)
|
||||
for i := 10; i > -1; i-- {
|
||||
array1.Add(gconv.String(i))
|
||||
array2.Add(gconv.String(i))
|
||||
}
|
||||
gtest.Assert(array1.Slice(), expect)
|
||||
gtest.Assert(array2.Slice(), expect)
|
||||
|
||||
}
|
||||
|
||||
func Test_SortedStringArray2(t *testing.T) {
|
||||
expect := []string{"0", "1", "10", "2", "3", "4", "5", "6", "7", "8", "9"}
|
||||
array := garray.NewSortedStringArray()
|
||||
for i := 0; i <= 10; i++ {
|
||||
array.Add(gconv.String(i))
|
||||
}
|
||||
gtest.Assert(array.Slice(), expect)
|
||||
array.Add()
|
||||
gtest.Assert(array.Slice(), expect)
|
||||
}
|
||||
|
||||
func Test_SortedArray1(t *testing.T) {
|
||||
expect := []string{"0", "1", "10", "2", "3", "4", "5", "6", "7", "8", "9"}
|
||||
array := garray.NewSortedArray(func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
})
|
||||
for i := 10; i > -1; i-- {
|
||||
array.Add(gconv.String(i))
|
||||
}
|
||||
gtest.Assert(array.Slice(), expect)
|
||||
}
|
||||
|
||||
func Test_SortedArray2(t *testing.T) {
|
||||
expect := []string{"0", "1", "10", "2", "3", "4", "5", "6", "7", "8", "9"}
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
}
|
||||
array := garray.NewSortedArray(func1)
|
||||
array2 := garray.NewSortedArray(func1, true)
|
||||
for i := 0; i <= 10; i++ {
|
||||
array.Add(gconv.String(i))
|
||||
array2.Add(gconv.String(i))
|
||||
}
|
||||
gtest.Assert(array.Slice(), expect)
|
||||
gtest.Assert(array.Add().Slice(), expect)
|
||||
gtest.Assert(array2.Slice(), expect)
|
||||
}
|
||||
|
||||
func TestNewFromCopy(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{"100", "200", "300", "400", "500", "600"}
|
||||
array1 := garray.NewFromCopy(a1)
|
||||
gtest.AssertIN(array1.PopRands(2), a1)
|
||||
gtest.Assert(len(array1.PopRands(1)), 1)
|
||||
gtest.Assert(len(array1.PopRands(9)), 3)
|
||||
})
|
||||
}
|
||||
825
g/container/garray/garray_z_unit_int_test.go
Normal file
825
g/container/garray/garray_z_unit_int_test.go
Normal file
@ -0,0 +1,825 @@
|
||||
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// go test *.go
|
||||
|
||||
package garray_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/g/container/garray"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
)
|
||||
|
||||
func Test_IntArray_Basic(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
expect := []int{0, 1, 2, 3}
|
||||
expect2 := []int{}
|
||||
array := garray.NewIntArrayFrom(expect)
|
||||
array2 := garray.NewIntArrayFrom(expect2)
|
||||
gtest.Assert(array.Slice(), expect)
|
||||
array.Set(0, 100)
|
||||
gtest.Assert(array.Get(0), 100)
|
||||
gtest.Assert(array.Get(1), 1)
|
||||
gtest.Assert(array.Search(100), 0)
|
||||
gtest.Assert(array2.Search(100), -1)
|
||||
gtest.Assert(array.Contains(100), true)
|
||||
gtest.Assert(array.Remove(0), 100)
|
||||
gtest.Assert(array.Contains(100), false)
|
||||
array.Append(4)
|
||||
gtest.Assert(array.Len(), 4)
|
||||
array.InsertBefore(0, 100)
|
||||
array.InsertAfter(0, 200)
|
||||
gtest.Assert(array.Slice(), []int{100, 200, 1, 2, 3, 4})
|
||||
array.InsertBefore(5, 300)
|
||||
array.InsertAfter(6, 400)
|
||||
gtest.Assert(array.Slice(), []int{100, 200, 1, 2, 3, 300, 4, 400})
|
||||
gtest.Assert(array.Clear().Len(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_Sort(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
expect1 := []int{0, 1, 2, 3}
|
||||
expect2 := []int{3, 2, 1, 0}
|
||||
array := garray.NewIntArray()
|
||||
array2 := garray.NewIntArray(true)
|
||||
for i := 3; i >= 0; i-- {
|
||||
array.Append(i)
|
||||
array2.Append(i)
|
||||
}
|
||||
array.Sort()
|
||||
gtest.Assert(array.Slice(), expect1)
|
||||
array.Sort(true)
|
||||
gtest.Assert(array.Slice(), expect2)
|
||||
gtest.Assert(array2.Slice(), expect2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_Unique(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
expect := []int{1, 1, 2, 3}
|
||||
array := garray.NewIntArrayFrom(expect)
|
||||
gtest.Assert(array.Unique().Slice(), []int{1, 2, 3})
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_PushAndPop(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
expect := []int{0, 1, 2, 3}
|
||||
array := garray.NewIntArrayFrom(expect)
|
||||
gtest.Assert(array.Slice(), expect)
|
||||
gtest.Assert(array.PopLeft(), 0)
|
||||
gtest.Assert(array.PopRight(), 3)
|
||||
gtest.AssertIN(array.PopRand(), []int{1, 2})
|
||||
gtest.AssertIN(array.PopRand(), []int{1, 2})
|
||||
gtest.Assert(array.Len(), 0)
|
||||
array.PushLeft(1).PushRight(2)
|
||||
gtest.Assert(array.Slice(), []int{1, 2})
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_PopLeftsAndPopRights(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
value1 := []int{0, 1, 2, 3, 4, 5, 6}
|
||||
value2 := []int{0, 1, 2, 3, 4, 5, 6}
|
||||
array1 := garray.NewIntArrayFrom(value1)
|
||||
array2 := garray.NewIntArrayFrom(value2)
|
||||
gtest.Assert(array1.PopLefts(2), []int{0, 1})
|
||||
gtest.Assert(array1.Slice(), []int{2, 3, 4, 5, 6})
|
||||
gtest.Assert(array1.PopRights(2), []int{5, 6})
|
||||
gtest.Assert(array1.Slice(), []int{2, 3, 4})
|
||||
gtest.Assert(array1.PopRights(20), []int{2, 3, 4})
|
||||
gtest.Assert(array1.Slice(), []int{})
|
||||
gtest.Assert(array2.PopLefts(20), []int{0, 1, 2, 3, 4, 5, 6})
|
||||
gtest.Assert(array2.Slice(), []int{})
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_Range(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
value1 := []int{0, 1, 2, 3, 4, 5, 6}
|
||||
array1 := garray.NewIntArrayFrom(value1)
|
||||
array2 := garray.NewIntArrayFrom(value1, true)
|
||||
gtest.Assert(array1.Range(0, 1), []int{0})
|
||||
gtest.Assert(array1.Range(1, 2), []int{1})
|
||||
gtest.Assert(array1.Range(0, 2), []int{0, 1})
|
||||
gtest.Assert(array1.Range(10, 2), nil)
|
||||
gtest.Assert(array1.Range(-1, 10), value1)
|
||||
gtest.Assert(array2.Range(1, 2), []int{1})
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_Merge(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
if gconv.Int(v1) < gconv.Int(v2) {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
n1 := []int{0, 1, 2, 3}
|
||||
n2 := []int{4, 5, 6, 7}
|
||||
i1 := []interface{}{"1", "2"}
|
||||
s1 := []string{"a", "b", "c"}
|
||||
s2 := []string{"e", "f"}
|
||||
a1 := garray.NewIntArrayFrom(n1)
|
||||
a2 := garray.NewIntArrayFrom(n2)
|
||||
a3 := garray.NewArrayFrom(i1)
|
||||
a4 := garray.NewStringArrayFrom(s1)
|
||||
|
||||
a5 := garray.NewSortedStringArrayFrom(s2)
|
||||
a6 := garray.NewSortedIntArrayFrom([]int{1, 2, 3})
|
||||
|
||||
a7 := garray.NewSortedStringArrayFrom(s1)
|
||||
a8 := garray.NewSortedArrayFrom([]interface{}{4, 5}, func1)
|
||||
|
||||
gtest.Assert(a1.Merge(a2).Slice(), []int{0, 1, 2, 3, 4, 5, 6, 7})
|
||||
gtest.Assert(a1.Merge(a3).Len(), 10)
|
||||
gtest.Assert(a1.Merge(a4).Len(), 13)
|
||||
gtest.Assert(a1.Merge(a5).Len(), 15)
|
||||
gtest.Assert(a1.Merge(a6).Len(), 18)
|
||||
gtest.Assert(a1.Merge(a7).Len(), 21)
|
||||
gtest.Assert(a1.Merge(a8).Len(), 23)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_Fill(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{0}
|
||||
a2 := []int{0}
|
||||
array1 := garray.NewIntArrayFrom(a1)
|
||||
array2 := garray.NewIntArrayFrom(a2)
|
||||
gtest.Assert(array1.Fill(1, 2, 100).Slice(), []int{0, 100, 100})
|
||||
gtest.Assert(array2.Fill(0, 2, 100).Slice(), []int{100, 100})
|
||||
gtest.Assert(array2.Fill(-1, 2, 100).Slice(), []int{100, 100})
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_Chunk(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{1, 2, 3, 4, 5}
|
||||
array1 := garray.NewIntArrayFrom(a1)
|
||||
chunks := array1.Chunk(2)
|
||||
gtest.Assert(len(chunks), 3)
|
||||
gtest.Assert(chunks[0], []int{1, 2})
|
||||
gtest.Assert(chunks[1], []int{3, 4})
|
||||
gtest.Assert(chunks[2], []int{5})
|
||||
gtest.Assert(array1.Chunk(0), nil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_Pad(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{0}
|
||||
array1 := garray.NewIntArrayFrom(a1)
|
||||
gtest.Assert(array1.Pad(3, 1).Slice(), []int{0, 1, 1})
|
||||
gtest.Assert(array1.Pad(-4, 1).Slice(), []int{1, 0, 1, 1})
|
||||
gtest.Assert(array1.Pad(3, 1).Slice(), []int{1, 0, 1, 1})
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_SubSlice(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{0, 1, 2, 3, 4, 5, 6}
|
||||
array1 := garray.NewIntArrayFrom(a1)
|
||||
array2 := garray.NewIntArrayFrom(a1, true)
|
||||
gtest.Assert(array1.SubSlice(6), []int{6})
|
||||
gtest.Assert(array1.SubSlice(5), []int{5, 6})
|
||||
gtest.Assert(array1.SubSlice(8), nil)
|
||||
gtest.Assert(array1.SubSlice(0, 2), []int{0, 1})
|
||||
gtest.Assert(array1.SubSlice(2, 2), []int{2, 3})
|
||||
gtest.Assert(array1.SubSlice(5, 8), []int{5, 6})
|
||||
gtest.Assert(array1.SubSlice(-1, 1), []int{6})
|
||||
gtest.Assert(array1.SubSlice(-1, 9), []int{6})
|
||||
gtest.Assert(array1.SubSlice(-2, 3), []int{5, 6})
|
||||
gtest.Assert(array1.SubSlice(-7, 3), []int{0, 1, 2})
|
||||
gtest.Assert(array1.SubSlice(-8, 3), nil)
|
||||
gtest.Assert(array1.SubSlice(-1, -3), []int{3, 4, 5})
|
||||
gtest.Assert(array1.SubSlice(-9, 3), nil)
|
||||
gtest.Assert(array1.SubSlice(1, -1), []int{0})
|
||||
gtest.Assert(array1.SubSlice(1, -3), nil)
|
||||
gtest.Assert(array2.SubSlice(0, 2), []int{0, 1})
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_Rand(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{0, 1, 2, 3, 4, 5, 6}
|
||||
array1 := garray.NewIntArrayFrom(a1)
|
||||
gtest.Assert(len(array1.Rands(2)), 2)
|
||||
gtest.Assert(len(array1.Rands(10)), 7)
|
||||
gtest.AssertIN(array1.Rands(1)[0], a1)
|
||||
gtest.AssertIN(array1.Rand(), a1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_PopRands(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{100, 200, 300, 400, 500, 600}
|
||||
array := garray.NewIntArrayFrom(a1)
|
||||
ns1 := array.PopRands(2)
|
||||
gtest.AssertIN(ns1, []int{100, 200, 300, 400, 500, 600})
|
||||
gtest.AssertIN(len(ns1), 2)
|
||||
|
||||
ns2 := array.PopRands(7)
|
||||
gtest.AssertIN(len(ns2), 6)
|
||||
gtest.AssertIN(ns2, []int{100, 200, 300, 400, 500, 600})
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_Shuffle(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{0, 1, 2, 3, 4, 5, 6}
|
||||
array1 := garray.NewIntArrayFrom(a1)
|
||||
gtest.Assert(array1.Shuffle().Len(), 7)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_Reverse(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{0, 1, 2, 3, 4, 5, 6}
|
||||
array1 := garray.NewIntArrayFrom(a1)
|
||||
gtest.Assert(array1.Reverse().Slice(), []int{6, 5, 4, 3, 2, 1, 0})
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_Join(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{0, 1, 2, 3, 4, 5, 6}
|
||||
array1 := garray.NewIntArrayFrom(a1)
|
||||
gtest.Assert(array1.Join("."), "0.1.2.3.4.5.6")
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewSortedIntArrayFrom(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{0, 3, 2, 1, 4, 5, 6}
|
||||
array1 := garray.NewSortedIntArrayFrom(a1, true)
|
||||
gtest.Assert(array1.Join("."), "0.1.2.3.4.5.6")
|
||||
gtest.Assert(array1.Slice(), a1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewSortedIntArrayFromCopy(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{0, 5, 2, 1, 4, 3, 6}
|
||||
array1 := garray.NewSortedIntArrayFromCopy(a1, false)
|
||||
gtest.Assert(array1.Join("."), "0.1.2.3.4.5.6")
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedIntArray_SetArray(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{0, 1, 2, 3}
|
||||
a2 := []int{4, 5, 6}
|
||||
array1 := garray.NewSortedIntArrayFrom(a1)
|
||||
array2 := array1.SetArray(a2)
|
||||
|
||||
gtest.Assert(array2.Len(), 3)
|
||||
gtest.Assert(array2.Search(3), -1)
|
||||
gtest.Assert(array2.Search(5), 1)
|
||||
gtest.Assert(array2.Search(6), 2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedIntArray_Sort(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{0, 3, 2, 1}
|
||||
|
||||
array1 := garray.NewSortedIntArrayFrom(a1)
|
||||
array2 := array1.Sort()
|
||||
|
||||
gtest.Assert(array2.Len(), 4)
|
||||
gtest.Assert(array2, []int{0, 1, 2, 3})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedIntArray_Get(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{1, 3, 5, 0}
|
||||
array1 := garray.NewSortedIntArrayFrom(a1)
|
||||
gtest.Assert(array1.Get(0), 0)
|
||||
gtest.Assert(array1.Get(1), 1)
|
||||
gtest.Assert(array1.Get(3), 5)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedIntArray_Remove(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{1, 3, 5, 0}
|
||||
array1 := garray.NewSortedIntArrayFrom(a1)
|
||||
i1 := array1.Remove(2)
|
||||
gtest.Assert(i1, 3)
|
||||
gtest.Assert(array1.Search(5), 2)
|
||||
|
||||
// 再次删除剩下的数组中的第一个
|
||||
i2 := array1.Remove(0)
|
||||
gtest.Assert(i2, 0)
|
||||
gtest.Assert(array1.Search(5), 1)
|
||||
|
||||
a2 := []int{1, 3, 4}
|
||||
array2 := garray.NewSortedIntArrayFrom(a2)
|
||||
i3 := array2.Remove(1)
|
||||
gtest.Assert(array2.Search(1), 0)
|
||||
gtest.Assert(i3, 3)
|
||||
i3 = array2.Remove(1)
|
||||
gtest.Assert(array2.Search(4), -1)
|
||||
gtest.Assert(i3, 4)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedIntArray_PopLeft(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{1, 3, 5, 2}
|
||||
array1 := garray.NewSortedIntArrayFrom(a1)
|
||||
i1 := array1.PopLeft()
|
||||
gtest.Assert(i1, 1)
|
||||
gtest.Assert(array1.Len(), 3)
|
||||
gtest.Assert(array1.Search(1), -1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedIntArray_PopRight(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{1, 3, 5, 2}
|
||||
array1 := garray.NewSortedIntArrayFrom(a1)
|
||||
i1 := array1.PopRight()
|
||||
gtest.Assert(i1, 5)
|
||||
gtest.Assert(array1.Len(), 3)
|
||||
gtest.Assert(array1.Search(5), -1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedIntArray_PopRand(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{1, 3, 5, 2}
|
||||
array1 := garray.NewSortedIntArrayFrom(a1)
|
||||
i1 := array1.PopRand()
|
||||
gtest.Assert(array1.Len(), 3)
|
||||
gtest.Assert(array1.Search(i1), -1)
|
||||
gtest.AssertIN(i1, []int{1, 3, 5, 2})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedIntArray_PopRands(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{1, 3, 5, 2}
|
||||
array1 := garray.NewSortedIntArrayFrom(a1)
|
||||
ns1 := array1.PopRands(2)
|
||||
gtest.Assert(array1.Len(), 2)
|
||||
gtest.AssertIN(ns1, []int{1, 3, 5, 2})
|
||||
|
||||
a2 := []int{1, 3, 5, 2}
|
||||
array2 := garray.NewSortedIntArrayFrom(a2)
|
||||
ns2 := array2.PopRands(5)
|
||||
gtest.Assert(array2.Len(), 0)
|
||||
gtest.Assert(len(ns2), 4)
|
||||
gtest.AssertIN(ns2, []int{1, 3, 5, 2})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedIntArray_PopLefts(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{1, 3, 5, 2}
|
||||
array1 := garray.NewSortedIntArrayFrom(a1)
|
||||
ns1 := array1.PopLefts(2)
|
||||
gtest.Assert(array1.Len(), 2)
|
||||
gtest.Assert(ns1, []int{1, 2})
|
||||
|
||||
a2 := []int{1, 3, 5, 2}
|
||||
array2 := garray.NewSortedIntArrayFrom(a2)
|
||||
ns2 := array2.PopLefts(5)
|
||||
gtest.Assert(array2.Len(), 0)
|
||||
gtest.AssertIN(ns2, []int{1, 3, 5, 2})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedIntArray_PopRights(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{1, 3, 5, 2}
|
||||
array1 := garray.NewSortedIntArrayFrom(a1)
|
||||
ns1 := array1.PopRights(2)
|
||||
gtest.Assert(array1.Len(), 2)
|
||||
gtest.Assert(ns1, []int{3, 5})
|
||||
|
||||
a2 := []int{1, 3, 5, 2}
|
||||
array2 := garray.NewSortedIntArrayFrom(a2)
|
||||
ns2 := array2.PopRights(5)
|
||||
gtest.Assert(array2.Len(), 0)
|
||||
gtest.AssertIN(ns2, []int{1, 3, 5, 2})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedIntArray_Range(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{1, 3, 5, 2, 6, 7}
|
||||
array1 := garray.NewSortedIntArrayFrom(a1)
|
||||
array2 := garray.NewSortedIntArrayFrom(a1, true)
|
||||
ns1 := array1.Range(1, 4)
|
||||
gtest.Assert(len(ns1), 3)
|
||||
gtest.Assert(ns1, []int{2, 3, 5})
|
||||
|
||||
ns2 := array1.Range(5, 4)
|
||||
gtest.Assert(len(ns2), 0)
|
||||
|
||||
ns3 := array1.Range(-1, 4)
|
||||
gtest.Assert(len(ns3), 4)
|
||||
|
||||
nsl := array1.Range(5, 8)
|
||||
gtest.Assert(len(nsl), 1)
|
||||
gtest.Assert(array2.Range(1, 2), []int{2})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedIntArray_Sum(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{1, 3, 5}
|
||||
array1 := garray.NewSortedIntArrayFrom(a1)
|
||||
n1 := array1.Sum()
|
||||
gtest.Assert(n1, 9)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedIntArray_Contains(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{1, 3, 5}
|
||||
array1 := garray.NewSortedIntArrayFrom(a1)
|
||||
gtest.Assert(array1.Contains(4), false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedIntArray_Clone(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{1, 3, 5}
|
||||
array1 := garray.NewSortedIntArrayFrom(a1)
|
||||
array2 := array1.Clone()
|
||||
gtest.Assert(array2.Len(), 3)
|
||||
gtest.Assert(array2, array1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedIntArray_Clear(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{1, 3, 5}
|
||||
array1 := garray.NewSortedIntArrayFrom(a1)
|
||||
array1.Clear()
|
||||
gtest.Assert(array1.Len(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedIntArray_Chunk(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{1, 2, 3, 4, 5}
|
||||
array1 := garray.NewSortedIntArrayFrom(a1)
|
||||
ns1 := array1.Chunk(2) //按每几个元素切成一个数组
|
||||
ns2 := array1.Chunk(-1)
|
||||
gtest.Assert(len(ns1), 3)
|
||||
gtest.Assert(ns1[0], []int{1, 2})
|
||||
gtest.Assert(ns1[2], []int{5})
|
||||
gtest.Assert(len(ns2), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedIntArray_SubSlice(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{1, 2, 3, 4, 5}
|
||||
array1 := garray.NewSortedIntArrayFrom(a1)
|
||||
array2 := garray.NewSortedIntArrayFrom(a1, true)
|
||||
ns1 := array1.SubSlice(1, 2)
|
||||
gtest.Assert(len(ns1), 2)
|
||||
gtest.Assert(ns1, []int{2, 3})
|
||||
|
||||
ns2 := array1.SubSlice(7, 2)
|
||||
gtest.Assert(len(ns2), 0)
|
||||
|
||||
ns3 := array1.SubSlice(3, 5)
|
||||
gtest.Assert(len(ns3), 2)
|
||||
gtest.Assert(ns3, []int{4, 5})
|
||||
|
||||
ns4 := array1.SubSlice(3, 1)
|
||||
gtest.Assert(len(ns4), 1)
|
||||
gtest.Assert(ns4, []int{4})
|
||||
gtest.Assert(array1.SubSlice(-1, 1), []int{5})
|
||||
gtest.Assert(array1.SubSlice(-9, 1), nil)
|
||||
gtest.Assert(array1.SubSlice(1, -9), nil)
|
||||
gtest.Assert(array2.SubSlice(1, 2), []int{2, 3})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedIntArray_Rand(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{1, 2, 3, 4, 5}
|
||||
array1 := garray.NewSortedIntArrayFrom(a1)
|
||||
ns1 := array1.Rand() //按每几个元素切成一个数组
|
||||
gtest.AssertIN(ns1, a1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedIntArray_Rands(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{1, 2, 3, 4, 5}
|
||||
array1 := garray.NewSortedIntArrayFrom(a1)
|
||||
ns1 := array1.Rands(2) //按每几个元素切成一个数组
|
||||
gtest.AssertIN(ns1, a1)
|
||||
gtest.Assert(len(ns1), 2)
|
||||
|
||||
ns2 := array1.Rands(6) //按每几个元素切成一个数组
|
||||
gtest.AssertIN(ns2, a1)
|
||||
gtest.Assert(len(ns2), 5)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedIntArray_CountValues(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{1, 2, 3, 4, 5, 3}
|
||||
array1 := garray.NewSortedIntArrayFrom(a1)
|
||||
ns1 := array1.CountValues() //按每几个元素切成一个数组
|
||||
gtest.Assert(len(ns1), 5)
|
||||
gtest.Assert(ns1[2], 1)
|
||||
gtest.Assert(ns1[3], 2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedIntArray_SetUnique(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{1, 2, 3, 4, 5, 3}
|
||||
array1 := garray.NewSortedIntArrayFrom(a1)
|
||||
array1.SetUnique(true)
|
||||
gtest.Assert(array1.Len(), 5)
|
||||
gtest.Assert(array1, []int{1, 2, 3, 4, 5})
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_SetArray(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{1, 2, 3, 5}
|
||||
a2 := []int{6, 7}
|
||||
array1 := garray.NewIntArrayFrom(a1)
|
||||
array1.SetArray(a2)
|
||||
gtest.Assert(array1.Len(), 2)
|
||||
gtest.Assert(array1, []int{6, 7})
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_Replace(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{1, 2, 3, 5}
|
||||
a2 := []int{6, 7}
|
||||
a3 := []int{9, 10, 11, 12, 13}
|
||||
array1 := garray.NewIntArrayFrom(a1)
|
||||
array1.Replace(a2)
|
||||
gtest.Assert(array1, []int{6, 7, 3, 5})
|
||||
|
||||
array1.Replace(a3)
|
||||
gtest.Assert(array1, []int{9, 10, 11, 12})
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_Clear(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{1, 2, 3, 5}
|
||||
array1 := garray.NewIntArrayFrom(a1)
|
||||
array1.Clear()
|
||||
gtest.Assert(array1.Len(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_Clone(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{1, 2, 3, 5}
|
||||
array1 := garray.NewIntArrayFrom(a1)
|
||||
array2 := array1.Clone()
|
||||
gtest.Assert(array1, array2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestArray_Get(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{1, 2, 3, 5}
|
||||
array1 := garray.NewIntArrayFrom(a1)
|
||||
gtest.Assert(array1.Get(2), 3)
|
||||
gtest.Assert(array1.Len(), 4)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_Sum(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{1, 2, 3, 5}
|
||||
array1 := garray.NewIntArrayFrom(a1)
|
||||
gtest.Assert(array1.Sum(), 11)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_CountValues(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{1, 2, 3, 5, 3}
|
||||
array1 := garray.NewIntArrayFrom(a1)
|
||||
m1 := array1.CountValues()
|
||||
gtest.Assert(len(m1), 4)
|
||||
gtest.Assert(m1[1], 1)
|
||||
gtest.Assert(m1[3], 2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewIntArrayFromCopy(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{1, 2, 3, 5, 3}
|
||||
array1 := garray.NewIntArrayFromCopy(a1)
|
||||
gtest.Assert(array1.Len(), 5)
|
||||
gtest.Assert(array1, a1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_Remove(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []int{1, 2, 3, 5, 4}
|
||||
array1 := garray.NewIntArrayFrom(a1)
|
||||
n1 := array1.Remove(1)
|
||||
gtest.Assert(n1, 2)
|
||||
gtest.Assert(array1.Len(), 4)
|
||||
|
||||
n1 = array1.Remove(0)
|
||||
gtest.Assert(n1, 1)
|
||||
gtest.Assert(array1.Len(), 3)
|
||||
|
||||
n1 = array1.Remove(2)
|
||||
gtest.Assert(n1, 4)
|
||||
gtest.Assert(array1.Len(), 2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_LockFunc(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := []int{1, 2, 3, 4}
|
||||
a1 := garray.NewIntArrayFrom(s1)
|
||||
|
||||
ch1 := make(chan int64, 3)
|
||||
ch2 := make(chan int64, 3)
|
||||
//go1
|
||||
go a1.LockFunc(func(n1 []int) { //读写锁
|
||||
time.Sleep(2 * time.Second) //暂停2秒
|
||||
n1[2] = 6
|
||||
ch2 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
})
|
||||
|
||||
//go2
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond) //故意暂停0.01秒,等go1执行锁后,再开始执行.
|
||||
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
a1.Len()
|
||||
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
}()
|
||||
|
||||
t1 := <-ch1
|
||||
t2 := <-ch1
|
||||
<-ch2 //等待go1完成
|
||||
|
||||
// 防止ci抖动,以豪秒为单位
|
||||
gtest.AssertGT(t2-t1, 20) //go1加的读写互斥锁,所go2读的时候被阻塞。
|
||||
gtest.Assert(a1.Contains(6), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_SortFunc(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := []int{1, 4, 3, 2}
|
||||
a1 := garray.NewIntArrayFrom(s1)
|
||||
func1 := func(v1, v2 int) bool {
|
||||
return v1 < v2
|
||||
}
|
||||
a11 := a1.SortFunc(func1)
|
||||
gtest.Assert(a11, []int{1, 2, 3, 4})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntArray_RLockFunc(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := []int{1, 2, 3, 4}
|
||||
a1 := garray.NewIntArrayFrom(s1)
|
||||
|
||||
ch1 := make(chan int64, 3)
|
||||
ch2 := make(chan int64, 1)
|
||||
//go1
|
||||
go a1.RLockFunc(func(n1 []int) { //读锁
|
||||
time.Sleep(2 * time.Second) //暂停1秒
|
||||
n1[2] = 6
|
||||
ch2 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
})
|
||||
|
||||
//go2
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond) //故意暂停0.01秒,等go1执行锁后,再开始执行.
|
||||
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
a1.Len()
|
||||
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
}()
|
||||
|
||||
t1 := <-ch1
|
||||
t2 := <-ch1
|
||||
<-ch2 //等待go1完成
|
||||
|
||||
// 防止ci抖动,以豪秒为单位
|
||||
gtest.AssertLT(t2-t1, 20) //go1加的读锁,所go2读的时候,并没有阻塞。
|
||||
gtest.Assert(a1.Contains(6), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedIntArray_LockFunc(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := []int{1, 2, 3, 4}
|
||||
a1 := garray.NewSortedIntArrayFrom(s1)
|
||||
ch1 := make(chan int64, 3)
|
||||
ch2 := make(chan int64, 3)
|
||||
//go1
|
||||
go a1.LockFunc(func(n1 []int) { //读写锁
|
||||
time.Sleep(2 * time.Second) //暂停2秒
|
||||
n1[2] = 6
|
||||
ch2 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
})
|
||||
|
||||
//go2
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond) //故意暂停0.01秒,等go1执行锁后,再开始执行.
|
||||
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
a1.Len()
|
||||
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
}()
|
||||
|
||||
t1 := <-ch1
|
||||
t2 := <-ch1
|
||||
<-ch2 //等待go1完成
|
||||
|
||||
// 防止ci抖动,以豪秒为单位
|
||||
gtest.AssertGT(t2-t1, 20) //go1加的读写互斥锁,所go2读的时候被阻塞。
|
||||
gtest.Assert(a1.Contains(6), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedIntArray_RLockFunc(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := []int{1, 2, 3, 4}
|
||||
a1 := garray.NewSortedIntArrayFrom(s1)
|
||||
|
||||
ch1 := make(chan int64, 3)
|
||||
ch2 := make(chan int64, 1)
|
||||
//go1
|
||||
go a1.RLockFunc(func(n1 []int) { //读锁
|
||||
time.Sleep(2 * time.Second) //暂停1秒
|
||||
n1[2] = 6
|
||||
ch2 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
})
|
||||
|
||||
//go2
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond) //故意暂停0.01秒,等go1执行锁后,再开始执行.
|
||||
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
a1.Len()
|
||||
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
}()
|
||||
|
||||
t1 := <-ch1
|
||||
t2 := <-ch1
|
||||
<-ch2 //等待go1完成
|
||||
|
||||
// 防止ci抖动,以豪秒为单位
|
||||
gtest.AssertLT(t2-t1, 20) //go1加的读锁,所go2读的时候,并没有阻塞。
|
||||
gtest.Assert(a1.Contains(6), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedIntArray_Merge(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
if gconv.Int(v1) < gconv.Int(v2) {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
i0 := []int{1, 2, 3, 4}
|
||||
s2 := []string{"e", "f"}
|
||||
i1 := garray.NewIntArrayFrom([]int{1, 2, 3})
|
||||
i2 := garray.NewArrayFrom([]interface{}{3})
|
||||
s3 := garray.NewStringArrayFrom([]string{"g", "h"})
|
||||
s4 := garray.NewSortedArrayFrom([]interface{}{4, 5}, func1)
|
||||
s5 := garray.NewSortedStringArrayFrom(s2)
|
||||
s6 := garray.NewSortedIntArrayFrom([]int{1, 2, 3})
|
||||
a1 := garray.NewSortedIntArrayFrom(i0)
|
||||
|
||||
gtest.Assert(a1.Merge(s2).Len(), 6)
|
||||
gtest.Assert(a1.Merge(i1).Len(), 9)
|
||||
gtest.Assert(a1.Merge(i2).Len(), 10)
|
||||
gtest.Assert(a1.Merge(s3).Len(), 12)
|
||||
gtest.Assert(a1.Merge(s4).Len(), 14)
|
||||
gtest.Assert(a1.Merge(s5).Len(), 16)
|
||||
gtest.Assert(a1.Merge(s6).Len(), 19)
|
||||
})
|
||||
}
|
||||
906
g/container/garray/garray_z_unit_interface_test.go
Normal file
906
g/container/garray/garray_z_unit_interface_test.go
Normal file
@ -0,0 +1,906 @@
|
||||
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// go test *.go
|
||||
|
||||
package garray_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/garray"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Test_Array_Basic(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
expect := []interface{}{0, 1, 2, 3}
|
||||
array := garray.NewArrayFrom(expect)
|
||||
array2 := garray.NewArrayFrom(expect)
|
||||
array3 := garray.NewArrayFrom([]interface{}{})
|
||||
gtest.Assert(array.Slice(), expect)
|
||||
array.Set(0, 100)
|
||||
gtest.Assert(array.Get(0), 100)
|
||||
gtest.Assert(array.Get(1), 1)
|
||||
gtest.Assert(array.Search(100), 0)
|
||||
gtest.Assert(array3.Search(100), -1)
|
||||
gtest.Assert(array.Contains(100), true)
|
||||
gtest.Assert(array.Remove(0), 100)
|
||||
|
||||
gtest.Assert(array2.Remove(3), 3)
|
||||
gtest.Assert(array2.Remove(1), 1)
|
||||
|
||||
gtest.Assert(array.Contains(100), false)
|
||||
array.Append(4)
|
||||
gtest.Assert(array.Len(), 4)
|
||||
array.InsertBefore(0, 100)
|
||||
array.InsertAfter(0, 200)
|
||||
gtest.Assert(array.Slice(), []interface{}{100, 200, 2, 2, 3, 4})
|
||||
array.InsertBefore(5, 300)
|
||||
array.InsertAfter(6, 400)
|
||||
gtest.Assert(array.Slice(), []interface{}{100, 200, 2, 2, 3, 300, 4, 400})
|
||||
gtest.Assert(array.Clear().Len(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestArray_Sort(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
expect1 := []interface{}{0, 1, 2, 3}
|
||||
expect2 := []interface{}{3, 2, 1, 0}
|
||||
array := garray.NewArray()
|
||||
for i := 3; i >= 0; i-- {
|
||||
array.Append(i)
|
||||
}
|
||||
array.SortFunc(func(v1, v2 interface{}) bool {
|
||||
return v1.(int) < v2.(int)
|
||||
})
|
||||
gtest.Assert(array.Slice(), expect1)
|
||||
array.SortFunc(func(v1, v2 interface{}) bool {
|
||||
return v1.(int) > v2.(int)
|
||||
})
|
||||
gtest.Assert(array.Slice(), expect2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestArray_Unique(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
expect := []interface{}{1, 1, 2, 3}
|
||||
array := garray.NewArrayFrom(expect)
|
||||
gtest.Assert(array.Unique().Slice(), []interface{}{1, 2, 3})
|
||||
})
|
||||
}
|
||||
|
||||
func TestArray_PushAndPop(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
expect := []interface{}{0, 1, 2, 3}
|
||||
array := garray.NewArrayFrom(expect)
|
||||
gtest.Assert(array.Slice(), expect)
|
||||
gtest.Assert(array.PopLeft(), 0)
|
||||
gtest.Assert(array.PopRight(), 3)
|
||||
gtest.AssertIN(array.PopRand(), []interface{}{1, 2})
|
||||
gtest.AssertIN(array.PopRand(), []interface{}{1, 2})
|
||||
gtest.Assert(array.Len(), 0)
|
||||
array.PushLeft(1).PushRight(2)
|
||||
gtest.Assert(array.Slice(), []interface{}{1, 2})
|
||||
})
|
||||
}
|
||||
|
||||
func TestArray_PopRands(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{100, 200, 300, 400, 500, 600}
|
||||
array := garray.NewFromCopy(a1)
|
||||
gtest.AssertIN(array.PopRands(2), a1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestArray_PopLeftsAndPopRights(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
value1 := []interface{}{0, 1, 2, 3, 4, 5, 6}
|
||||
value2 := []interface{}{0, 1, 2, 3, 4, 5, 6}
|
||||
array1 := garray.NewArrayFrom(value1)
|
||||
array2 := garray.NewArrayFrom(value2)
|
||||
gtest.Assert(array1.PopLefts(2), []interface{}{0, 1})
|
||||
gtest.Assert(array1.Slice(), []interface{}{2, 3, 4, 5, 6})
|
||||
gtest.Assert(array1.PopRights(2), []interface{}{5, 6})
|
||||
gtest.Assert(array1.Slice(), []interface{}{2, 3, 4})
|
||||
gtest.Assert(array1.PopRights(20), []interface{}{2, 3, 4})
|
||||
gtest.Assert(array1.Slice(), []interface{}{})
|
||||
gtest.Assert(array2.PopLefts(20), []interface{}{0, 1, 2, 3, 4, 5, 6})
|
||||
gtest.Assert(array2.Slice(), []interface{}{})
|
||||
})
|
||||
}
|
||||
|
||||
func TestArray_Range(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
value1 := []interface{}{0, 1, 2, 3, 4, 5, 6}
|
||||
array1 := garray.NewArrayFrom(value1)
|
||||
array2 := garray.NewArrayFrom(value1, true)
|
||||
gtest.Assert(array1.Range(0, 1), []interface{}{0})
|
||||
gtest.Assert(array1.Range(1, 2), []interface{}{1})
|
||||
gtest.Assert(array1.Range(0, 2), []interface{}{0, 1})
|
||||
gtest.Assert(array1.Range(-1, 10), value1)
|
||||
gtest.Assert(array1.Range(10, 2), nil)
|
||||
gtest.Assert(array2.Range(1, 3), []interface{}{1, 2})
|
||||
})
|
||||
}
|
||||
|
||||
func TestArray_Merge(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
if gconv.Int(v1) < gconv.Int(v2) {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
i1 := []interface{}{0, 1, 2, 3}
|
||||
i2 := []interface{}{4, 5, 6, 7}
|
||||
array1 := garray.NewArrayFrom(i1)
|
||||
array2 := garray.NewArrayFrom(i2)
|
||||
gtest.Assert(array1.Merge(array2).Slice(), []interface{}{0, 1, 2, 3, 4, 5, 6, 7})
|
||||
|
||||
//s1 := []string{"a", "b", "c", "d"}
|
||||
s2 := []string{"e", "f"}
|
||||
i3 := garray.NewIntArrayFrom([]int{1, 2, 3})
|
||||
i4 := garray.NewArrayFrom([]interface{}{3})
|
||||
s3 := garray.NewStringArrayFrom([]string{"g", "h"})
|
||||
s4 := garray.NewSortedArrayFrom([]interface{}{4, 5}, func1)
|
||||
s5 := garray.NewSortedStringArrayFrom(s2)
|
||||
s6 := garray.NewSortedIntArrayFrom([]int{1, 2, 3})
|
||||
a1 := garray.NewArrayFrom(i1)
|
||||
|
||||
gtest.Assert(a1.Merge(s2).Len(), 6)
|
||||
gtest.Assert(a1.Merge(i3).Len(), 9)
|
||||
gtest.Assert(a1.Merge(i4).Len(), 10)
|
||||
gtest.Assert(a1.Merge(s3).Len(), 12)
|
||||
gtest.Assert(a1.Merge(s4).Len(), 14)
|
||||
gtest.Assert(a1.Merge(s5).Len(), 16)
|
||||
gtest.Assert(a1.Merge(s6).Len(), 19)
|
||||
})
|
||||
}
|
||||
|
||||
func TestArray_Fill(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{0}
|
||||
a2 := []interface{}{0}
|
||||
array1 := garray.NewArrayFrom(a1)
|
||||
array2 := garray.NewArrayFrom(a2, true)
|
||||
gtest.Assert(array1.Fill(1, 2, 100).Slice(), []interface{}{0, 100, 100})
|
||||
gtest.Assert(array2.Fill(0, 2, 100).Slice(), []interface{}{100, 100})
|
||||
gtest.Assert(array2.Fill(-1, 2, 100).Slice(), []interface{}{100, 100})
|
||||
})
|
||||
}
|
||||
|
||||
func TestArray_Chunk(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{1, 2, 3, 4, 5}
|
||||
array1 := garray.NewArrayFrom(a1)
|
||||
chunks := array1.Chunk(2)
|
||||
gtest.Assert(len(chunks), 3)
|
||||
gtest.Assert(chunks[0], []interface{}{1, 2})
|
||||
gtest.Assert(chunks[1], []interface{}{3, 4})
|
||||
gtest.Assert(chunks[2], []interface{}{5})
|
||||
gtest.Assert(array1.Chunk(0), nil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestArray_Pad(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{0}
|
||||
array1 := garray.NewArrayFrom(a1)
|
||||
gtest.Assert(array1.Pad(3, 1).Slice(), []interface{}{0, 1, 1})
|
||||
gtest.Assert(array1.Pad(-4, 1).Slice(), []interface{}{1, 0, 1, 1})
|
||||
gtest.Assert(array1.Pad(3, 1).Slice(), []interface{}{1, 0, 1, 1})
|
||||
})
|
||||
}
|
||||
|
||||
func TestArray_SubSlice(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{0, 1, 2, 3, 4, 5, 6}
|
||||
array1 := garray.NewArrayFrom(a1)
|
||||
array2 := garray.NewArrayFrom(a1, true)
|
||||
gtest.Assert(array1.SubSlice(0, 2), []interface{}{0, 1})
|
||||
gtest.Assert(array1.SubSlice(2, 2), []interface{}{2, 3})
|
||||
gtest.Assert(array1.SubSlice(5, 8), []interface{}{5, 6})
|
||||
gtest.Assert(array1.SubSlice(9, 1), nil)
|
||||
gtest.Assert(array1.SubSlice(-2, 2), []interface{}{5, 6})
|
||||
gtest.Assert(array1.SubSlice(-9, 2), nil)
|
||||
gtest.Assert(array1.SubSlice(1, -2), nil)
|
||||
gtest.Assert(array2.SubSlice(0, 2), []interface{}{0, 1})
|
||||
})
|
||||
}
|
||||
|
||||
func TestArray_Rand(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{0, 1, 2, 3, 4, 5, 6}
|
||||
array1 := garray.NewArrayFrom(a1)
|
||||
gtest.Assert(len(array1.Rands(2)), 2)
|
||||
gtest.Assert(len(array1.Rands(10)), 7)
|
||||
gtest.AssertIN(array1.Rands(1)[0], a1)
|
||||
})
|
||||
|
||||
gtest.Case(t, func() {
|
||||
s1 := []interface{}{"a", "b", "c", "d"}
|
||||
a1 := garray.NewArrayFrom(s1)
|
||||
i1 := a1.Rand()
|
||||
gtest.Assert(a1.Contains(i1), true)
|
||||
gtest.Assert(a1.Len(), 4)
|
||||
})
|
||||
}
|
||||
|
||||
func TestArray_Shuffle(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{0, 1, 2, 3, 4, 5, 6}
|
||||
array1 := garray.NewArrayFrom(a1)
|
||||
gtest.Assert(array1.Shuffle().Len(), 7)
|
||||
})
|
||||
}
|
||||
|
||||
func TestArray_Reverse(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{0, 1, 2, 3, 4, 5, 6}
|
||||
array1 := garray.NewArrayFrom(a1)
|
||||
gtest.Assert(array1.Reverse().Slice(), []interface{}{6, 5, 4, 3, 2, 1, 0})
|
||||
})
|
||||
}
|
||||
|
||||
func TestArray_Join(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{0, 1, 2, 3, 4, 5, 6}
|
||||
array1 := garray.NewArrayFrom(a1)
|
||||
gtest.Assert(array1.Join("."), "0.1.2.3.4.5.6")
|
||||
})
|
||||
}
|
||||
|
||||
func TestArray_Replace(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{0, 1, 2, 3, 4, 5, 6}
|
||||
a2 := []interface{}{"a", "b", "c"}
|
||||
a3 := []interface{}{"m", "n", "p", "z", "x", "y", "d", "u"}
|
||||
array1 := garray.NewArrayFrom(a1)
|
||||
array2 := array1.Replace(a2)
|
||||
gtest.Assert(array2.Len(), 7)
|
||||
gtest.Assert(array2.Contains("b"), true)
|
||||
gtest.Assert(array2.Contains(4), true)
|
||||
gtest.Assert(array2.Contains("v"), false)
|
||||
array3 := array1.Replace(a3)
|
||||
gtest.Assert(array3.Len(), 7)
|
||||
gtest.Assert(array3.Contains(4), false)
|
||||
gtest.Assert(array3.Contains("p"), true)
|
||||
gtest.Assert(array3.Contains("u"), false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestArray_SetArray(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{0, 1, 2, 3, 4, 5, 6}
|
||||
a2 := []interface{}{"a", "b", "c"}
|
||||
|
||||
array1 := garray.NewArrayFrom(a1)
|
||||
array1 = array1.SetArray(a2)
|
||||
gtest.Assert(array1.Len(), 3)
|
||||
gtest.Assert(array1.Contains("b"), true)
|
||||
gtest.Assert(array1.Contains("5"), false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestArray_Sum(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{0, 1, 2, 3}
|
||||
a2 := []interface{}{"a", "b", "c"}
|
||||
a3 := []interface{}{"a", "1", "2"}
|
||||
|
||||
array1 := garray.NewArrayFrom(a1)
|
||||
array2 := garray.NewArrayFrom(a2)
|
||||
array3 := garray.NewArrayFrom(a3)
|
||||
|
||||
gtest.Assert(array1.Sum(), 6)
|
||||
gtest.Assert(array2.Sum(), 0)
|
||||
gtest.Assert(array3.Sum(), 3)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestArray_Clone(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{0, 1, 2, 3}
|
||||
array1 := garray.NewArrayFrom(a1)
|
||||
array2 := array1.Clone()
|
||||
|
||||
gtest.Assert(array1.Len(), 4)
|
||||
gtest.Assert(array2.Sum(), 6)
|
||||
gtest.AssertEQ(array1, array2)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestArray_CountValues(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{"a", "b", "c", "d", "e", "d"}
|
||||
array1 := garray.NewArrayFrom(a1)
|
||||
array2 := array1.CountValues()
|
||||
gtest.Assert(len(array2), 5)
|
||||
gtest.Assert(array2["b"], 1)
|
||||
gtest.Assert(array2["d"], 2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedArray_NewSortedArrayFrom(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{"a", "f", "c"}
|
||||
a2 := []interface{}{"h", "j", "i", "k"}
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
}
|
||||
func2 := func(v1, v2 interface{}) int {
|
||||
return -1
|
||||
}
|
||||
array1 := garray.NewSortedArrayFrom(a1, func1)
|
||||
array2 := garray.NewSortedArrayFrom(a2, func2)
|
||||
|
||||
gtest.Assert(array1.Len(), 3)
|
||||
gtest.Assert(array1, []interface{}{"a", "c", "f"})
|
||||
|
||||
gtest.Assert(array2.Len(), 4)
|
||||
gtest.Assert(array2, []interface{}{"k", "i", "j", "h"})
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewSortedArrayFromCopy(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{"a", "f", "c"}
|
||||
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
}
|
||||
func2 := func(v1, v2 interface{}) int {
|
||||
return -1
|
||||
}
|
||||
array1 := garray.NewSortedArrayFromCopy(a1, func1)
|
||||
array2 := garray.NewSortedArrayFromCopy(a1, func2)
|
||||
gtest.Assert(array1.Len(), 3)
|
||||
gtest.Assert(array1, []interface{}{"a", "c", "f"})
|
||||
gtest.Assert(array1.Len(), 3)
|
||||
gtest.Assert(array2, []interface{}{"c", "f", "a"})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedArray_SetArray(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{"a", "f", "c"}
|
||||
a2 := []interface{}{"e", "h", "g", "k"}
|
||||
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
}
|
||||
|
||||
array1 := garray.NewSortedArrayFrom(a1, func1)
|
||||
array1.SetArray(a2)
|
||||
gtest.Assert(array1.Len(), 4)
|
||||
gtest.Assert(array1, []interface{}{"e", "g", "h", "k"})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestSortedArray_Sort(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{"a", "f", "c"}
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
}
|
||||
array1 := garray.NewSortedArrayFrom(a1, func1)
|
||||
array1.Sort()
|
||||
gtest.Assert(array1.Len(), 3)
|
||||
gtest.Assert(array1, []interface{}{"a", "c", "f"})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestSortedArray_Get(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{"a", "f", "c"}
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
}
|
||||
array1 := garray.NewSortedArrayFrom(a1, func1)
|
||||
gtest.Assert(array1.Get(2), "f")
|
||||
gtest.Assert(array1.Get(1), "c")
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestSortedArray_Remove(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{"a", "d", "c", "b"}
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
}
|
||||
array1 := garray.NewSortedArrayFrom(a1, func1)
|
||||
i1 := array1.Remove(1)
|
||||
gtest.Assert(gconv.String(i1), "b")
|
||||
gtest.Assert(array1.Len(), 3)
|
||||
gtest.Assert(array1.Contains("b"), false)
|
||||
|
||||
i2 := array1.Remove(0)
|
||||
gtest.Assert(gconv.String(i2), "a")
|
||||
gtest.Assert(array1.Len(), 2)
|
||||
gtest.Assert(array1.Contains("a"), false)
|
||||
|
||||
i3 := array1.Remove(1)
|
||||
gtest.Assert(gconv.String(i3), "d")
|
||||
gtest.Assert(array1.Len(), 1)
|
||||
gtest.Assert(array1.Contains("d"), false)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestSortedArray_PopLeft(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{"a", "d", "c", "b"}
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
}
|
||||
array1 := garray.NewSortedArrayFrom(a1, func1)
|
||||
i1 := array1.PopLeft()
|
||||
gtest.Assert(gconv.String(i1), "a")
|
||||
gtest.Assert(array1.Len(), 3)
|
||||
gtest.Assert(array1, []interface{}{"b", "c", "d"})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestSortedArray_PopRight(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{"a", "d", "c", "b"}
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
}
|
||||
array1 := garray.NewSortedArrayFrom(a1, func1)
|
||||
i1 := array1.PopRight()
|
||||
gtest.Assert(gconv.String(i1), "d")
|
||||
gtest.Assert(array1.Len(), 3)
|
||||
gtest.Assert(array1, []interface{}{"a", "b", "c"})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestSortedArray_PopRand(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{"a", "d", "c", "b"}
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
}
|
||||
array1 := garray.NewSortedArrayFrom(a1, func1)
|
||||
i1 := array1.PopRand()
|
||||
gtest.AssertIN(i1, []interface{}{"a", "d", "c", "b"})
|
||||
gtest.Assert(array1.Len(), 3)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedArray_PopRands(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{"a", "d", "c", "b"}
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
}
|
||||
array1 := garray.NewSortedArrayFrom(a1, func1)
|
||||
i1 := array1.PopRands(2)
|
||||
gtest.Assert(len(i1), 2)
|
||||
gtest.AssertIN(i1, []interface{}{"a", "d", "c", "b"})
|
||||
gtest.Assert(array1.Len(), 2)
|
||||
|
||||
i2 := array1.PopRands(3)
|
||||
gtest.Assert(len(i1), 2)
|
||||
gtest.AssertIN(i2, []interface{}{"a", "d", "c", "b"})
|
||||
gtest.Assert(array1.Len(), 0)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedArray_PopLefts(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{"a", "d", "c", "b", "e", "f"}
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
}
|
||||
array1 := garray.NewSortedArrayFrom(a1, func1)
|
||||
i1 := array1.PopLefts(2)
|
||||
gtest.Assert(len(i1), 2)
|
||||
gtest.AssertIN(i1, []interface{}{"a", "d", "c", "b", "e", "f"})
|
||||
gtest.Assert(array1.Len(), 4)
|
||||
|
||||
i2 := array1.PopLefts(5)
|
||||
gtest.Assert(len(i2), 4)
|
||||
gtest.AssertIN(i1, []interface{}{"a", "d", "c", "b", "e", "f"})
|
||||
gtest.Assert(array1.Len(), 0)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedArray_PopRights(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{"a", "d", "c", "b", "e", "f"}
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
}
|
||||
array1 := garray.NewSortedArrayFrom(a1, func1)
|
||||
i1 := array1.PopRights(2)
|
||||
gtest.Assert(len(i1), 2)
|
||||
gtest.Assert(i1, []interface{}{"e", "f"})
|
||||
gtest.Assert(array1.Len(), 4)
|
||||
|
||||
i2 := array1.PopRights(10)
|
||||
gtest.Assert(len(i2), 4)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedArray_Range(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{"a", "d", "c", "b", "e", "f"}
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
}
|
||||
array1 := garray.NewSortedArrayFrom(a1, func1)
|
||||
array2 := garray.NewSortedArrayFrom(a1, func1, true)
|
||||
i1 := array1.Range(2, 5)
|
||||
gtest.Assert(i1, []interface{}{"c", "d", "e"})
|
||||
gtest.Assert(array1.Len(), 6)
|
||||
|
||||
i2 := array1.Range(7, 5)
|
||||
gtest.Assert(len(i2), 0)
|
||||
i2 = array1.Range(-1, 2)
|
||||
gtest.Assert(i2, []interface{}{"a", "b"})
|
||||
|
||||
i2 = array1.Range(4, 10)
|
||||
gtest.Assert(len(i2), 2)
|
||||
gtest.Assert(i2, []interface{}{"e", "f"})
|
||||
|
||||
gtest.Assert(array2.Range(1, 3), []interface{}{"b", "c"})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedArray_Sum(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{"a", "d", "c", "b", "e", "f"}
|
||||
a2 := []interface{}{"1", "2", "3", "b", "e", "f"}
|
||||
a3 := []interface{}{"4", "5", "6"}
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
}
|
||||
array1 := garray.NewSortedArrayFrom(a1, func1)
|
||||
array2 := garray.NewSortedArrayFrom(a2, func1)
|
||||
array3 := garray.NewSortedArrayFrom(a3, func1)
|
||||
gtest.Assert(array1.Sum(), 0)
|
||||
gtest.Assert(array2.Sum(), 6)
|
||||
gtest.Assert(array3.Sum(), 15)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedArray_Clone(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{"a", "d", "c", "b", "e", "f"}
|
||||
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
}
|
||||
array1 := garray.NewSortedArrayFrom(a1, func1)
|
||||
array2 := array1.Clone()
|
||||
gtest.Assert(array1, array2)
|
||||
array1.Remove(1)
|
||||
gtest.AssertNE(array1, array2)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedArray_Clear(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{"a", "d", "c", "b", "e", "f"}
|
||||
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
}
|
||||
array1 := garray.NewSortedArrayFrom(a1, func1)
|
||||
gtest.Assert(array1.Len(), 6)
|
||||
array1.Clear()
|
||||
gtest.Assert(array1.Len(), 0)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedArray_Chunk(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{"a", "d", "c", "b", "e"}
|
||||
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
}
|
||||
array1 := garray.NewSortedArrayFrom(a1, func1)
|
||||
i1 := array1.Chunk(2)
|
||||
gtest.Assert(len(i1), 3)
|
||||
gtest.Assert(i1[0], []interface{}{"a", "b"})
|
||||
gtest.Assert(i1[2], []interface{}{"e"})
|
||||
|
||||
i1 = array1.Chunk(0)
|
||||
gtest.Assert(len(i1), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedArray_SubSlice(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{"a", "d", "c", "b", "e"}
|
||||
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
}
|
||||
array1 := garray.NewSortedArrayFrom(a1, func1)
|
||||
array2 := garray.NewSortedArrayFrom(a1, func1, true)
|
||||
i1 := array1.SubSlice(2, 3)
|
||||
gtest.Assert(len(i1), 3)
|
||||
gtest.Assert(i1, []interface{}{"c", "d", "e"})
|
||||
|
||||
i1 = array1.SubSlice(2, 6)
|
||||
gtest.Assert(len(i1), 3)
|
||||
gtest.Assert(i1, []interface{}{"c", "d", "e"})
|
||||
|
||||
i1 = array1.SubSlice(7, 2)
|
||||
gtest.Assert(len(i1), 0)
|
||||
|
||||
s1 := array1.SubSlice(1, -2)
|
||||
gtest.Assert(s1, nil)
|
||||
|
||||
s1 = array1.SubSlice(-9, 2)
|
||||
gtest.Assert(s1, nil)
|
||||
gtest.Assert(array2.SubSlice(1, 3), []interface{}{"b", "c", "d"})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedArray_Rand(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{"a", "d", "c"}
|
||||
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
}
|
||||
array1 := garray.NewSortedArrayFrom(a1, func1)
|
||||
i1 := array1.Rand()
|
||||
gtest.AssertIN(i1, []interface{}{"a", "d", "c"})
|
||||
gtest.Assert(array1.Len(), 3)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedArray_Rands(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{"a", "d", "c"}
|
||||
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
}
|
||||
array1 := garray.NewSortedArrayFrom(a1, func1)
|
||||
i1 := array1.Rands(2)
|
||||
gtest.AssertIN(i1, []interface{}{"a", "d", "c"})
|
||||
gtest.Assert(len(i1), 2)
|
||||
gtest.Assert(array1.Len(), 3)
|
||||
|
||||
i1 = array1.Rands(4)
|
||||
gtest.Assert(len(i1), 3)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedArray_Join(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{"a", "d", "c"}
|
||||
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
}
|
||||
array1 := garray.NewSortedArrayFrom(a1, func1)
|
||||
gtest.Assert(array1.Join(","), "a,c,d")
|
||||
gtest.Assert(array1.Join("."), "a.c.d")
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedArray_CountValues(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{"a", "d", "c", "c"}
|
||||
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
}
|
||||
array1 := garray.NewSortedArrayFrom(a1, func1)
|
||||
m1 := array1.CountValues()
|
||||
gtest.Assert(len(m1), 3)
|
||||
gtest.Assert(m1["c"], 2)
|
||||
gtest.Assert(m1["a"], 1)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedArray_SetUnique(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []interface{}{"a", "d", "c", "c"}
|
||||
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
}
|
||||
array1 := garray.NewSortedArrayFrom(a1, func1)
|
||||
array1.SetUnique(true)
|
||||
gtest.Assert(array1.Len(), 3)
|
||||
gtest.Assert(array1, []interface{}{"a", "c", "d"})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedArray_LockFunc(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
}
|
||||
s1 := []interface{}{"a", "b", "c", "d"}
|
||||
a1 := garray.NewSortedArrayFrom(s1, func1)
|
||||
|
||||
ch1 := make(chan int64, 3)
|
||||
ch2 := make(chan int64, 3)
|
||||
//go1
|
||||
go a1.LockFunc(func(n1 []interface{}) { //读写锁
|
||||
time.Sleep(2 * time.Second) //暂停2秒
|
||||
n1[2] = "g"
|
||||
ch2 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
})
|
||||
|
||||
//go2
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond) //故意暂停0.01秒,等go1执行锁后,再开始执行.
|
||||
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
a1.Len()
|
||||
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
}()
|
||||
|
||||
t1 := <-ch1
|
||||
t2 := <-ch1
|
||||
<-ch2 //等待go1完成
|
||||
|
||||
// 防止ci抖动,以豪秒为单位
|
||||
gtest.AssertGT(t2-t1, 20) //go1加的读写互斥锁,所go2读的时候被阻塞。
|
||||
gtest.Assert(a1.Contains("g"), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedArray_RLockFunc(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
}
|
||||
s1 := []interface{}{"a", "b", "c", "d"}
|
||||
a1 := garray.NewSortedArrayFrom(s1, func1)
|
||||
|
||||
ch1 := make(chan int64, 3)
|
||||
ch2 := make(chan int64, 3)
|
||||
//go1
|
||||
go a1.RLockFunc(func(n1 []interface{}) { //读写锁
|
||||
time.Sleep(2 * time.Second) //暂停2秒
|
||||
n1[2] = "g"
|
||||
ch2 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
})
|
||||
|
||||
//go2
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond) //故意暂停0.01秒,等go1执行锁后,再开始执行.
|
||||
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
a1.Len()
|
||||
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
}()
|
||||
|
||||
t1 := <-ch1
|
||||
t2 := <-ch1
|
||||
<-ch2 //等待go1完成
|
||||
|
||||
// 防止ci抖动,以豪秒为单位
|
||||
gtest.AssertLT(t2-t1, 20) //go1加的读锁,所go2读的时候不会被阻塞。
|
||||
gtest.Assert(a1.Contains("g"), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedArray_Merge(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
if gconv.Int(v1) < gconv.Int(v2) {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
s1 := []interface{}{"a", "b", "c", "d"}
|
||||
s2 := []string{"e", "f"}
|
||||
i1 := garray.NewIntArrayFrom([]int{1, 2, 3})
|
||||
i2 := garray.NewArrayFrom([]interface{}{3})
|
||||
s3 := garray.NewStringArrayFrom([]string{"g", "h"})
|
||||
s4 := garray.NewSortedArrayFrom([]interface{}{4, 5}, func1)
|
||||
s5 := garray.NewSortedStringArrayFrom(s2)
|
||||
s6 := garray.NewSortedIntArrayFrom([]int{1, 2, 3})
|
||||
|
||||
a1 := garray.NewSortedArrayFrom(s1, func1)
|
||||
|
||||
gtest.Assert(a1.Merge(s2).Len(), 6)
|
||||
gtest.Assert(a1.Merge(i1).Len(), 9)
|
||||
gtest.Assert(a1.Merge(i2).Len(), 10)
|
||||
gtest.Assert(a1.Merge(s3).Len(), 12)
|
||||
gtest.Assert(a1.Merge(s4).Len(), 14)
|
||||
gtest.Assert(a1.Merge(s5).Len(), 16)
|
||||
gtest.Assert(a1.Merge(s6).Len(), 19)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestArray_LockFunc(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := []interface{}{"a", "b", "c", "d"}
|
||||
a1 := garray.NewArrayFrom(s1)
|
||||
|
||||
ch1 := make(chan int64, 3)
|
||||
ch2 := make(chan int64, 3)
|
||||
//go1
|
||||
go a1.LockFunc(func(n1 []interface{}) { //读写锁
|
||||
time.Sleep(2 * time.Second) //暂停2秒
|
||||
n1[2] = "g"
|
||||
ch2 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
})
|
||||
|
||||
//go2
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond) //故意暂停0.01秒,等go1执行锁后,再开始执行.
|
||||
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
a1.Len()
|
||||
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
}()
|
||||
|
||||
t1 := <-ch1
|
||||
t2 := <-ch1
|
||||
<-ch2 //等待go1完成
|
||||
|
||||
// 防止ci抖动,以豪秒为单位
|
||||
gtest.AssertGT(t2-t1, 20) //go1加的读写互斥锁,所go2读的时候被阻塞。
|
||||
gtest.Assert(a1.Contains("g"), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestArray_RLockFunc(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := []interface{}{"a", "b", "c", "d"}
|
||||
a1 := garray.NewArrayFrom(s1)
|
||||
|
||||
ch1 := make(chan int64, 3)
|
||||
ch2 := make(chan int64, 1)
|
||||
//go1
|
||||
go a1.RLockFunc(func(n1 []interface{}) { //读锁
|
||||
time.Sleep(2 * time.Second) //暂停1秒
|
||||
n1[2] = "g"
|
||||
ch2 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
})
|
||||
|
||||
//go2
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond) //故意暂停0.01秒,等go1执行锁后,再开始执行.
|
||||
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
a1.Len()
|
||||
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
}()
|
||||
|
||||
t1 := <-ch1
|
||||
t2 := <-ch1
|
||||
<-ch2 //等待go1完成
|
||||
|
||||
// 防止ci抖动,以豪秒为单位
|
||||
gtest.AssertLT(t2-t1, 20) //go1加的读锁,所go2读的时候,并没有阻塞。
|
||||
gtest.Assert(a1.Contains("g"), true)
|
||||
})
|
||||
}
|
||||
835
g/container/garray/garray_z_unit_string_test.go
Normal file
835
g/container/garray/garray_z_unit_string_test.go
Normal file
@ -0,0 +1,835 @@
|
||||
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// go test *.go
|
||||
|
||||
package garray_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/garray"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Test_StringArray_Basic(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
expect := []string{"0", "1", "2", "3"}
|
||||
array := garray.NewStringArrayFrom(expect)
|
||||
array2 := garray.NewStringArrayFrom(expect, true)
|
||||
array3 := garray.NewStringArrayFrom([]string{})
|
||||
gtest.Assert(array.Slice(), expect)
|
||||
array.Set(0, "100")
|
||||
gtest.Assert(array.Get(0), 100)
|
||||
gtest.Assert(array.Get(1), 1)
|
||||
gtest.Assert(array.Search("100"), 0)
|
||||
gtest.Assert(array.Contains("100"), true)
|
||||
gtest.Assert(array.Remove(0), 100)
|
||||
gtest.Assert(array.Contains("100"), false)
|
||||
array.Append("4")
|
||||
gtest.Assert(array.Len(), 4)
|
||||
array.InsertBefore(0, "100")
|
||||
array.InsertAfter(0, "200")
|
||||
gtest.Assert(array.Slice(), []string{"100", "200", "1", "2", "3", "4"})
|
||||
array.InsertBefore(5, "300")
|
||||
array.InsertAfter(6, "400")
|
||||
gtest.Assert(array.Slice(), []string{"100", "200", "1", "2", "3", "300", "4", "400"})
|
||||
gtest.Assert(array.Clear().Len(), 0)
|
||||
gtest.Assert(array2.Slice(), expect)
|
||||
gtest.Assert(array3.Search("100"), -1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringArray_Sort(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
expect1 := []string{"0", "1", "2", "3"}
|
||||
expect2 := []string{"3", "2", "1", "0"}
|
||||
array := garray.NewStringArray()
|
||||
for i := 3; i >= 0; i-- {
|
||||
array.Append(gconv.String(i))
|
||||
}
|
||||
array.Sort()
|
||||
gtest.Assert(array.Slice(), expect1)
|
||||
array.Sort(true)
|
||||
gtest.Assert(array.Slice(), expect2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringArray_Unique(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
expect := []string{"1", "1", "2", "3"}
|
||||
array := garray.NewStringArrayFrom(expect)
|
||||
gtest.Assert(array.Unique().Slice(), []string{"1", "2", "3"})
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringArray_PushAndPop(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
expect := []string{"0", "1", "2", "3"}
|
||||
array := garray.NewStringArrayFrom(expect)
|
||||
gtest.Assert(array.Slice(), expect)
|
||||
gtest.Assert(array.PopLeft(), "0")
|
||||
gtest.Assert(array.PopRight(), "3")
|
||||
gtest.AssertIN(array.PopRand(), []string{"1", "2"})
|
||||
gtest.AssertIN(array.PopRand(), []string{"1", "2"})
|
||||
gtest.Assert(array.Len(), 0)
|
||||
array.PushLeft("1").PushRight("2")
|
||||
gtest.Assert(array.Slice(), []string{"1", "2"})
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringArray_PopLeftsAndPopRights(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
value1 := []string{"0", "1", "2", "3", "4", "5", "6"}
|
||||
value2 := []string{"0", "1", "2", "3", "4", "5", "6"}
|
||||
array1 := garray.NewStringArrayFrom(value1)
|
||||
array2 := garray.NewStringArrayFrom(value2)
|
||||
gtest.Assert(array1.PopLefts(2), []interface{}{"0", "1"})
|
||||
gtest.Assert(array1.Slice(), []interface{}{"2", "3", "4", "5", "6"})
|
||||
gtest.Assert(array1.PopRights(2), []interface{}{"5", "6"})
|
||||
gtest.Assert(array1.Slice(), []interface{}{"2", "3", "4"})
|
||||
gtest.Assert(array1.PopRights(20), []interface{}{"2", "3", "4"})
|
||||
gtest.Assert(array1.Slice(), []interface{}{})
|
||||
gtest.Assert(array2.PopLefts(20), []interface{}{"0", "1", "2", "3", "4", "5", "6"})
|
||||
gtest.Assert(array2.Slice(), []interface{}{})
|
||||
})
|
||||
}
|
||||
|
||||
func TestString_Range(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
value1 := []string{"0", "1", "2", "3", "4", "5", "6"}
|
||||
array1 := garray.NewStringArrayFrom(value1)
|
||||
array2 := garray.NewStringArrayFrom(value1, true)
|
||||
gtest.Assert(array1.Range(0, 1), []interface{}{"0"})
|
||||
gtest.Assert(array1.Range(1, 2), []interface{}{"1"})
|
||||
gtest.Assert(array1.Range(0, 2), []interface{}{"0", "1"})
|
||||
gtest.Assert(array1.Range(-1, 10), value1)
|
||||
gtest.Assert(array1.Range(10, 1), nil)
|
||||
gtest.Assert(array2.Range(0, 1), []interface{}{"0"})
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringArray_Merge(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a11 := []string{"0", "1", "2", "3"}
|
||||
a21 := []string{"4", "5", "6", "7"}
|
||||
array1 := garray.NewStringArrayFrom(a11)
|
||||
array2 := garray.NewStringArrayFrom(a21)
|
||||
gtest.Assert(array1.Merge(array2).Slice(), []string{"0", "1", "2", "3", "4", "5", "6", "7"})
|
||||
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
if gconv.Int(v1) < gconv.Int(v2) {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
s1 := []string{"a", "b", "c", "d"}
|
||||
s2 := []string{"e", "f"}
|
||||
i1 := garray.NewIntArrayFrom([]int{1, 2, 3})
|
||||
i2 := garray.NewArrayFrom([]interface{}{3})
|
||||
s3 := garray.NewStringArrayFrom([]string{"g", "h"})
|
||||
s4 := garray.NewSortedArrayFrom([]interface{}{4, 5}, func1)
|
||||
s5 := garray.NewSortedStringArrayFrom(s2)
|
||||
s6 := garray.NewSortedIntArrayFrom([]int{1, 2, 3})
|
||||
a1 := garray.NewStringArrayFrom(s1)
|
||||
|
||||
gtest.Assert(a1.Merge(s2).Len(), 6)
|
||||
gtest.Assert(a1.Merge(i1).Len(), 9)
|
||||
gtest.Assert(a1.Merge(i2).Len(), 10)
|
||||
gtest.Assert(a1.Merge(s3).Len(), 12)
|
||||
gtest.Assert(a1.Merge(s4).Len(), 14)
|
||||
gtest.Assert(a1.Merge(s5).Len(), 16)
|
||||
gtest.Assert(a1.Merge(s6).Len(), 19)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringArray_Fill(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"0"}
|
||||
a2 := []string{"0"}
|
||||
array1 := garray.NewStringArrayFrom(a1)
|
||||
array2 := garray.NewStringArrayFrom(a2)
|
||||
gtest.Assert(array1.Fill(1, 2, "100").Slice(), []string{"0", "100", "100"})
|
||||
gtest.Assert(array2.Fill(0, 2, "100").Slice(), []string{"100", "100"})
|
||||
s1 := array2.Fill(-1, 2, "100")
|
||||
gtest.Assert(s1.Len(), 2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringArray_Chunk(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"1", "2", "3", "4", "5"}
|
||||
array1 := garray.NewStringArrayFrom(a1)
|
||||
chunks := array1.Chunk(2)
|
||||
gtest.Assert(len(chunks), 3)
|
||||
gtest.Assert(chunks[0], []string{"1", "2"})
|
||||
gtest.Assert(chunks[1], []string{"3", "4"})
|
||||
gtest.Assert(chunks[2], []string{"5"})
|
||||
gtest.Assert(len(array1.Chunk(0)), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringArray_Pad(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"0"}
|
||||
array1 := garray.NewStringArrayFrom(a1)
|
||||
gtest.Assert(array1.Pad(3, "1").Slice(), []string{"0", "1", "1"})
|
||||
gtest.Assert(array1.Pad(-4, "1").Slice(), []string{"1", "0", "1", "1"})
|
||||
gtest.Assert(array1.Pad(3, "1").Slice(), []string{"1", "0", "1", "1"})
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringArray_SubSlice(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"0", "1", "2", "3", "4", "5", "6"}
|
||||
array1 := garray.NewStringArrayFrom(a1)
|
||||
array2 := garray.NewStringArrayFrom(a1, true)
|
||||
gtest.Assert(array1.SubSlice(0, 2), []string{"0", "1"})
|
||||
gtest.Assert(array1.SubSlice(2, 2), []string{"2", "3"})
|
||||
gtest.Assert(array1.SubSlice(5, 8), []string{"5", "6"})
|
||||
gtest.Assert(array1.SubSlice(8, 2), nil)
|
||||
gtest.Assert(array1.SubSlice(1, -2), nil)
|
||||
gtest.Assert(array1.SubSlice(-5, 2), []string{"2", "3"})
|
||||
gtest.Assert(array1.SubSlice(-10, 1), nil)
|
||||
gtest.Assert(array2.SubSlice(0, 2), []string{"0", "1"})
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringArray_Rand(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"0", "1", "2", "3", "4", "5", "6"}
|
||||
array1 := garray.NewStringArrayFrom(a1)
|
||||
gtest.Assert(len(array1.Rands(2)), "2")
|
||||
gtest.Assert(len(array1.Rands(10)), "7")
|
||||
gtest.AssertIN(array1.Rands(1)[0], a1)
|
||||
gtest.Assert(len(array1.Rand()), 1)
|
||||
gtest.AssertIN(array1.Rand(), a1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringArray_PopRands(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"a", "b", "c", "d", "e", "f", "g"}
|
||||
a2 := []string{"1", "2", "3", "4", "5", "6", "7"}
|
||||
array1 := garray.NewStringArrayFrom(a1)
|
||||
gtest.AssertIN(array1.PopRands(1), strings.Join(a1, ","))
|
||||
gtest.AssertNI(array1.PopRands(1), strings.Join(a2, ","))
|
||||
gtest.Assert(len(array1.PopRands(10)), 5)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringArray_Shuffle(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"0", "1", "2", "3", "4", "5", "6"}
|
||||
array1 := garray.NewStringArrayFrom(a1)
|
||||
gtest.Assert(array1.Shuffle().Len(), 7)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringArray_Reverse(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"0", "1", "2", "3", "4", "5", "6"}
|
||||
array1 := garray.NewStringArrayFrom(a1)
|
||||
gtest.Assert(array1.Reverse().Slice(), []string{"6", "5", "4", "3", "2", "1", "0"})
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringArray_Join(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"0", "1", "2", "3", "4", "5", "6"}
|
||||
array1 := garray.NewStringArrayFrom(a1)
|
||||
gtest.Assert(array1.Join("."), "0.1.2.3.4.5.6")
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewStringArrayFromCopy(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"0", "1", "2", "3", "4", "5", "6"}
|
||||
a2 := garray.NewStringArrayFromCopy(a1)
|
||||
a3 := garray.NewStringArrayFromCopy(a1, true)
|
||||
gtest.Assert(a2.Contains("1"), true)
|
||||
gtest.Assert(a2.Len(), 7)
|
||||
gtest.Assert(a2, a3)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringArray_SetArray(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"0", "1", "2", "3", "4", "5", "6"}
|
||||
a2 := []string{"a", "b", "c", "d"}
|
||||
array1 := garray.NewStringArrayFrom(a1)
|
||||
gtest.Assert(array1.Contains("2"), true)
|
||||
gtest.Assert(array1.Len(), 7)
|
||||
|
||||
array1 = array1.SetArray(a2)
|
||||
gtest.Assert(array1.Contains("2"), false)
|
||||
gtest.Assert(array1.Contains("c"), true)
|
||||
gtest.Assert(array1.Len(), 4)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringArray_Replace(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"0", "1", "2", "3", "4", "5", "6"}
|
||||
a2 := []string{"a", "b", "c", "d"}
|
||||
a3 := []string{"o", "p", "q", "x", "y", "z", "w", "r", "v"}
|
||||
array1 := garray.NewStringArrayFrom(a1)
|
||||
gtest.Assert(array1.Contains("2"), true)
|
||||
gtest.Assert(array1.Len(), 7)
|
||||
|
||||
array1 = array1.Replace(a2)
|
||||
gtest.Assert(array1.Contains("2"), false)
|
||||
gtest.Assert(array1.Contains("c"), true)
|
||||
gtest.Assert(array1.Contains("5"), true)
|
||||
gtest.Assert(array1.Len(), 7)
|
||||
|
||||
array1 = array1.Replace(a3)
|
||||
gtest.Assert(array1.Contains("2"), false)
|
||||
gtest.Assert(array1.Contains("c"), false)
|
||||
gtest.Assert(array1.Contains("5"), false)
|
||||
gtest.Assert(array1.Contains("p"), true)
|
||||
gtest.Assert(array1.Contains("r"), false)
|
||||
gtest.Assert(array1.Len(), 7)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringArray_Sum(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"0", "1", "2", "3", "4", "5", "6"}
|
||||
a2 := []string{"0", "a", "3", "4", "5", "6"}
|
||||
array1 := garray.NewStringArrayFrom(a1)
|
||||
array2 := garray.NewStringArrayFrom(a2)
|
||||
gtest.Assert(array1.Sum(), 21)
|
||||
gtest.Assert(array2.Sum(), 18)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringArray_PopRand(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"0", "1", "2", "3", "4", "5", "6"}
|
||||
array1 := garray.NewStringArrayFrom(a1)
|
||||
str1 := array1.PopRand()
|
||||
gtest.Assert(strings.Contains("0,1,2,3,4,5,6", str1), true)
|
||||
gtest.Assert(array1.Len(), 6)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringArray_Clone(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"0", "1", "2", "3", "4", "5", "6"}
|
||||
array1 := garray.NewStringArrayFrom(a1)
|
||||
array2 := array1.Clone()
|
||||
gtest.Assert(array2, array1)
|
||||
gtest.Assert(array2.Len(), 7)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringArray_CountValues(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"0", "1", "2", "3", "4", "4", "6"}
|
||||
array1 := garray.NewStringArrayFrom(a1)
|
||||
|
||||
m1 := array1.CountValues()
|
||||
gtest.Assert(len(m1), 6)
|
||||
gtest.Assert(m1["2"], 1)
|
||||
gtest.Assert(m1["4"], 2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewSortedStringArrayFrom(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"a", "d", "c", "b"}
|
||||
s1 := garray.NewSortedStringArrayFrom(a1, true)
|
||||
gtest.Assert(s1, []string{"a", "b", "c", "d"})
|
||||
s2 := garray.NewSortedStringArrayFrom(a1, false)
|
||||
gtest.Assert(s2, []string{"a", "b", "c", "d"})
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewSortedStringArrayFromCopy(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"a", "d", "c", "b"}
|
||||
s1 := garray.NewSortedStringArrayFromCopy(a1, true)
|
||||
gtest.Assert(s1.Len(), 4)
|
||||
gtest.Assert(s1, []string{"a", "b", "c", "d"})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedStringArray_SetArray(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"a", "d", "c", "b"}
|
||||
a2 := []string{"f", "g", "h"}
|
||||
array1 := garray.NewSortedStringArrayFrom(a1)
|
||||
array1.SetArray(a2)
|
||||
gtest.Assert(array1.Len(), 3)
|
||||
gtest.Assert(array1.Contains("d"), false)
|
||||
gtest.Assert(array1.Contains("b"), false)
|
||||
gtest.Assert(array1.Contains("g"), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedStringArray_Sort(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"a", "d", "c", "b"}
|
||||
array1 := garray.NewSortedStringArrayFrom(a1)
|
||||
|
||||
gtest.Assert(array1, []string{"a", "b", "c", "d"})
|
||||
array1.Sort()
|
||||
gtest.Assert(array1.Len(), 4)
|
||||
gtest.Assert(array1.Contains("c"), true)
|
||||
gtest.Assert(array1, []string{"a", "b", "c", "d"})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedStringArray_Get(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"a", "d", "c", "b"}
|
||||
array1 := garray.NewSortedStringArrayFrom(a1)
|
||||
gtest.Assert(array1.Get(2), "c")
|
||||
gtest.Assert(array1.Get(0), "a")
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedStringArray_Remove(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"a", "d", "c", "b"}
|
||||
array1 := garray.NewSortedStringArrayFrom(a1)
|
||||
gtest.Assert(array1.Remove(2), "c")
|
||||
gtest.Assert(array1.Get(2), "d")
|
||||
gtest.Assert(array1.Len(), 3)
|
||||
gtest.Assert(array1.Contains("c"), false)
|
||||
|
||||
gtest.Assert(array1.Remove(0), "a")
|
||||
gtest.Assert(array1.Len(), 2)
|
||||
gtest.Assert(array1.Contains("a"), false)
|
||||
|
||||
// 此时array1里的元素只剩下2个
|
||||
gtest.Assert(array1.Remove(1), "d")
|
||||
gtest.Assert(array1.Len(), 1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedStringArray_PopLeft(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"e", "a", "d", "c", "b"}
|
||||
array1 := garray.NewSortedStringArrayFrom(a1)
|
||||
s1 := array1.PopLeft()
|
||||
gtest.Assert(s1, "a")
|
||||
gtest.Assert(array1.Len(), 4)
|
||||
gtest.Assert(array1.Contains("a"), false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedStringArray_PopRight(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"e", "a", "d", "c", "b"}
|
||||
array1 := garray.NewSortedStringArrayFrom(a1)
|
||||
s1 := array1.PopRight()
|
||||
gtest.Assert(s1, "e")
|
||||
gtest.Assert(array1.Len(), 4)
|
||||
gtest.Assert(array1.Contains("e"), false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedStringArray_PopRand(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"e", "a", "d", "c", "b"}
|
||||
array1 := garray.NewSortedStringArrayFrom(a1)
|
||||
s1 := array1.PopRand()
|
||||
gtest.AssertIN(s1, []string{"e", "a", "d", "c", "b"})
|
||||
gtest.Assert(array1.Len(), 4)
|
||||
gtest.Assert(array1.Contains(s1), false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedStringArray_PopRands(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"e", "a", "d", "c", "b"}
|
||||
array1 := garray.NewSortedStringArrayFrom(a1)
|
||||
s1 := array1.PopRands(2)
|
||||
gtest.AssertIN(s1, []string{"e", "a", "d", "c", "b"})
|
||||
gtest.Assert(array1.Len(), 3)
|
||||
gtest.Assert(len(s1), 2)
|
||||
|
||||
s1 = array1.PopRands(4)
|
||||
gtest.Assert(len(s1), 3)
|
||||
gtest.AssertIN(s1, []string{"e", "a", "d", "c", "b"})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedStringArray_PopLefts(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"e", "a", "d", "c", "b"}
|
||||
array1 := garray.NewSortedStringArrayFrom(a1)
|
||||
s1 := array1.PopLefts(2)
|
||||
gtest.Assert(s1, []string{"a", "b"})
|
||||
gtest.Assert(array1.Len(), 3)
|
||||
gtest.Assert(len(s1), 2)
|
||||
|
||||
s1 = array1.PopLefts(4)
|
||||
gtest.Assert(len(s1), 3)
|
||||
gtest.Assert(s1, []string{"c", "d", "e"})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedStringArray_PopRights(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"e", "a", "d", "c", "b", "f", "g"}
|
||||
array1 := garray.NewSortedStringArrayFrom(a1)
|
||||
s1 := array1.PopRights(2)
|
||||
gtest.Assert(s1, []string{"f", "g"})
|
||||
gtest.Assert(array1.Len(), 5)
|
||||
gtest.Assert(len(s1), 2)
|
||||
s1 = array1.PopRights(6)
|
||||
gtest.Assert(len(s1), 5)
|
||||
gtest.Assert(s1, []string{"a", "b", "c", "d", "e"})
|
||||
gtest.Assert(array1.Len(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedStringArray_Range(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"e", "a", "d", "c", "b", "f", "g"}
|
||||
array1 := garray.NewSortedStringArrayFrom(a1)
|
||||
array2 := garray.NewSortedStringArrayFrom(a1, true)
|
||||
s1 := array1.Range(2, 4)
|
||||
gtest.Assert(len(s1), 2)
|
||||
gtest.Assert(s1, []string{"c", "d"})
|
||||
|
||||
s1 = array1.Range(-1, 2)
|
||||
gtest.Assert(len(s1), 2)
|
||||
gtest.Assert(s1, []string{"a", "b"})
|
||||
|
||||
s1 = array1.Range(4, 8)
|
||||
gtest.Assert(len(s1), 3)
|
||||
gtest.Assert(s1, []string{"e", "f", "g"})
|
||||
gtest.Assert(array1.Range(10, 2), nil)
|
||||
|
||||
s2 := array2.Range(2, 4)
|
||||
gtest.Assert(s2, []string{"c", "d"})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedStringArray_Sum(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"e", "a", "d", "c", "b", "f", "g"}
|
||||
a2 := []string{"1", "2", "3", "4", "a"}
|
||||
array1 := garray.NewSortedStringArrayFrom(a1)
|
||||
array2 := garray.NewSortedStringArrayFrom(a2)
|
||||
gtest.Assert(array1.Sum(), 0)
|
||||
gtest.Assert(array2.Sum(), 10)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedStringArray_Clone(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"e", "a", "d", "c", "b", "f", "g"}
|
||||
array1 := garray.NewSortedStringArrayFrom(a1)
|
||||
array2 := array1.Clone()
|
||||
gtest.Assert(array1, array2)
|
||||
array1.Remove(1)
|
||||
gtest.Assert(array2.Len(), 7)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedStringArray_Clear(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"e", "a", "d", "c", "b", "f", "g"}
|
||||
array1 := garray.NewSortedStringArrayFrom(a1)
|
||||
array1.Clear()
|
||||
gtest.Assert(array1.Len(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedStringArray_SubSlice(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"e", "a", "d", "c", "b", "f", "g"}
|
||||
array1 := garray.NewSortedStringArrayFrom(a1)
|
||||
array2 := garray.NewSortedStringArrayFrom(a1, true)
|
||||
s1 := array1.SubSlice(1, 3)
|
||||
gtest.Assert(len(s1), 3)
|
||||
gtest.Assert(s1, []string{"b", "c", "d"})
|
||||
gtest.Assert(array1.Len(), 7)
|
||||
|
||||
s2 := array1.SubSlice(1, 10)
|
||||
gtest.Assert(len(s2), 6)
|
||||
|
||||
s3 := array1.SubSlice(10, 2)
|
||||
gtest.Assert(len(s3), 0)
|
||||
|
||||
s3 = array1.SubSlice(-5, 2)
|
||||
gtest.Assert(s3, []string{"c", "d"})
|
||||
|
||||
s3 = array1.SubSlice(-10, 2)
|
||||
gtest.Assert(s3, nil)
|
||||
|
||||
s3 = array1.SubSlice(1, -2)
|
||||
gtest.Assert(s3, nil)
|
||||
|
||||
gtest.Assert(array2.SubSlice(1, 3), []string{"b", "c", "d"})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedStringArray_Len(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"e", "a", "d", "c", "b", "f", "g"}
|
||||
array1 := garray.NewSortedStringArrayFrom(a1)
|
||||
gtest.Assert(array1.Len(), 7)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedStringArray_Rand(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"e", "a", "d"}
|
||||
array1 := garray.NewSortedStringArrayFrom(a1)
|
||||
gtest.AssertIN(array1.Rand(), []string{"e", "a", "d"})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedStringArray_Rands(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"e", "a", "d"}
|
||||
array1 := garray.NewSortedStringArrayFrom(a1)
|
||||
s1 := array1.Rands(2)
|
||||
|
||||
gtest.AssertIN(s1, []string{"e", "a", "d"})
|
||||
gtest.Assert(len(s1), 2)
|
||||
|
||||
s1 = array1.Rands(4)
|
||||
gtest.AssertIN(s1, []string{"e", "a", "d"})
|
||||
gtest.Assert(len(s1), 3)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedStringArray_Join(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"e", "a", "d"}
|
||||
array1 := garray.NewSortedStringArrayFrom(a1)
|
||||
gtest.Assert(array1.Join(","), "a,d,e")
|
||||
gtest.Assert(array1.Join("."), "a.d.e")
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedStringArray_CountValues(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"e", "a", "d", "a", "c"}
|
||||
array1 := garray.NewSortedStringArrayFrom(a1)
|
||||
m1 := array1.CountValues()
|
||||
gtest.Assert(m1["a"], 2)
|
||||
gtest.Assert(m1["d"], 1)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedStringArray_Chunk(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"e", "a", "d", "a", "c"}
|
||||
array1 := garray.NewSortedStringArrayFrom(a1)
|
||||
array2 := array1.Chunk(2)
|
||||
gtest.Assert(len(array2), 3)
|
||||
gtest.Assert(len(array2[0]), 2)
|
||||
gtest.Assert(array2[1], []string{"c", "d"})
|
||||
gtest.Assert(array1.Chunk(0), nil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedStringArray_SetUnique(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"e", "a", "d", "a", "c"}
|
||||
array1 := garray.NewSortedStringArrayFrom(a1)
|
||||
array2 := array1.SetUnique(true)
|
||||
gtest.Assert(array2.Len(), 4)
|
||||
gtest.Assert(array2, []string{"a", "c", "d", "e"})
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringArray_Remove(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
a1 := []string{"e", "a", "d", "a", "c"}
|
||||
array1 := garray.NewStringArrayFrom(a1)
|
||||
s1 := array1.Remove(1)
|
||||
gtest.Assert(s1, "a")
|
||||
gtest.Assert(array1.Len(), 4)
|
||||
s1 = array1.Remove(3)
|
||||
gtest.Assert(s1, "c")
|
||||
gtest.Assert(array1.Len(), 3)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringArray_RLockFunc(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := []string{"a", "b", "c", "d"}
|
||||
a1 := garray.NewStringArrayFrom(s1, true)
|
||||
|
||||
ch1 := make(chan int64, 3)
|
||||
ch2 := make(chan int64, 1)
|
||||
//go1
|
||||
go a1.RLockFunc(func(n1 []string) { //读锁
|
||||
time.Sleep(2 * time.Second) //暂停1秒
|
||||
n1[2] = "g"
|
||||
ch2 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
})
|
||||
|
||||
//go2
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond) //故意暂停0.01秒,等go1执行锁后,再开始执行.
|
||||
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
a1.Len()
|
||||
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
}()
|
||||
|
||||
t1 := <-ch1
|
||||
t2 := <-ch1
|
||||
<-ch2 //等待go1完成
|
||||
|
||||
// 防止ci抖动,以豪秒为单位
|
||||
gtest.AssertLT(t2-t1, 20) //go1加的读锁,所go2读的时候,并没有阻塞。
|
||||
gtest.Assert(a1.Contains("g"), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedStringArray_LockFunc(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := []string{"a", "b", "c", "d"}
|
||||
a1 := garray.NewSortedStringArrayFrom(s1)
|
||||
|
||||
ch1 := make(chan int64, 3)
|
||||
ch2 := make(chan int64, 3)
|
||||
//go1
|
||||
go a1.LockFunc(func(n1 []string) { //读写锁
|
||||
time.Sleep(2 * time.Second) //暂停2秒
|
||||
n1[2] = "g"
|
||||
ch2 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
})
|
||||
|
||||
//go2
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond) //故意暂停0.01秒,等go1执行锁后,再开始执行.
|
||||
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
a1.Len()
|
||||
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
}()
|
||||
|
||||
t1 := <-ch1
|
||||
t2 := <-ch1
|
||||
<-ch2 //等待go1完成
|
||||
|
||||
// 防止ci抖动,以豪秒为单位
|
||||
gtest.AssertGT(t2-t1, 20) //go1加的读写互斥锁,所go2读的时候被阻塞。
|
||||
gtest.Assert(a1.Contains("g"), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedStringArray_RLockFunc(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := []string{"a", "b", "c", "d"}
|
||||
a1 := garray.NewSortedStringArrayFrom(s1)
|
||||
|
||||
ch1 := make(chan int64, 3)
|
||||
ch2 := make(chan int64, 1)
|
||||
//go1
|
||||
go a1.RLockFunc(func(n1 []string) { //读锁
|
||||
time.Sleep(2 * time.Second) //暂停1秒
|
||||
n1[2] = "g"
|
||||
ch2 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
})
|
||||
|
||||
//go2
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond) //故意暂停0.01秒,等go1执行锁后,再开始执行.
|
||||
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
a1.Len()
|
||||
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
}()
|
||||
|
||||
t1 := <-ch1
|
||||
t2 := <-ch1
|
||||
<-ch2 //等待go1完成
|
||||
|
||||
// 防止ci抖动,以豪秒为单位
|
||||
gtest.AssertLT(t2-t1, 20) //go1加的读锁,所go2读的时候,并没有阻塞。
|
||||
gtest.Assert(a1.Contains("g"), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedStringArray_Merge(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
if gconv.Int(v1) < gconv.Int(v2) {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
s1 := []string{"a", "b", "c", "d"}
|
||||
s2 := []string{"e", "f"}
|
||||
i1 := garray.NewIntArrayFrom([]int{1, 2, 3})
|
||||
i2 := garray.NewArrayFrom([]interface{}{3})
|
||||
s3 := garray.NewStringArrayFrom([]string{"g", "h"})
|
||||
s4 := garray.NewSortedArrayFrom([]interface{}{4, 5}, func1)
|
||||
s5 := garray.NewSortedStringArrayFrom(s2)
|
||||
s6 := garray.NewSortedIntArrayFrom([]int{1, 2, 3})
|
||||
a1 := garray.NewSortedStringArrayFrom(s1)
|
||||
|
||||
gtest.Assert(a1.Merge(s2).Len(), 6)
|
||||
gtest.Assert(a1.Merge(i1).Len(), 9)
|
||||
gtest.Assert(a1.Merge(i2).Len(), 10)
|
||||
gtest.Assert(a1.Merge(s3).Len(), 12)
|
||||
gtest.Assert(a1.Merge(s4).Len(), 14)
|
||||
gtest.Assert(a1.Merge(s5).Len(), 16)
|
||||
gtest.Assert(a1.Merge(s6).Len(), 19)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringArray_SortFunc(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := []string{"a", "d", "c", "b"}
|
||||
a1 := garray.NewStringArrayFrom(s1)
|
||||
func1 := func(v1, v2 string) bool {
|
||||
return v1 < v2
|
||||
}
|
||||
a11 := a1.SortFunc(func1)
|
||||
gtest.Assert(a11, []string{"a", "b", "c", "d"})
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringArray_LockFunc(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := []string{"a", "b", "c", "d"}
|
||||
a1 := garray.NewStringArrayFrom(s1)
|
||||
|
||||
ch1 := make(chan int64, 3)
|
||||
ch2 := make(chan int64, 3)
|
||||
//go1
|
||||
go a1.LockFunc(func(n1 []string) { //读写锁
|
||||
time.Sleep(2 * time.Second) //暂停2秒
|
||||
n1[2] = "g"
|
||||
ch2 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
})
|
||||
|
||||
//go2
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond) //故意暂停0.01秒,等go1执行锁后,再开始执行.
|
||||
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
a1.Len()
|
||||
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
|
||||
}()
|
||||
|
||||
t1 := <-ch1
|
||||
t2 := <-ch1
|
||||
<-ch2 //等待go1完成
|
||||
|
||||
// 防止ci抖动,以豪秒为单位
|
||||
gtest.AssertGT(t2-t1, 20) //go1加的读写互斥锁,所go2读的时候被阻塞。
|
||||
gtest.Assert(a1.Contains("g"), true)
|
||||
})
|
||||
}
|
||||
@ -1,52 +1,70 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package gchan provides graceful operations for channel.
|
||||
// 优雅的Channel操作.
|
||||
// Package gchan provides graceful channel for no panic operations.
|
||||
//
|
||||
// It's safe to call Chan.Push/Close functions repeatedly.
|
||||
package gchan
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"gitee.com/johng/gf/g/container/gtype"
|
||||
"errors"
|
||||
|
||||
"github.com/gogf/gf/g/container/gtype"
|
||||
)
|
||||
|
||||
// Graceful channel.
|
||||
type Chan struct {
|
||||
list chan interface{}
|
||||
closed *gtype.Bool
|
||||
channel chan interface{}
|
||||
closed *gtype.Bool
|
||||
}
|
||||
|
||||
// New creates a graceful channel with given <limit>.
|
||||
func New(limit int) *Chan {
|
||||
return &Chan {
|
||||
list : make(chan interface{}, limit),
|
||||
closed : gtype.NewBool(),
|
||||
}
|
||||
return &Chan{
|
||||
channel: make(chan interface{}, limit),
|
||||
closed: gtype.NewBool(),
|
||||
}
|
||||
}
|
||||
|
||||
// 将数据压入队列
|
||||
func (q *Chan) Push(v interface{}) error {
|
||||
if q.closed.Val() {
|
||||
return errors.New("closed")
|
||||
}
|
||||
q.list <- v
|
||||
return nil
|
||||
// Push pushes <value> to channel.
|
||||
// It is safe to be called repeatedly.
|
||||
func (c *Chan) Push(value interface{}) error {
|
||||
if c.closed.Val() {
|
||||
return errors.New("channel is closed")
|
||||
}
|
||||
c.channel <- value
|
||||
return nil
|
||||
}
|
||||
|
||||
// 先进先出地从队列取出一项数据,当没有数据可获取时,阻塞等待
|
||||
func (q *Chan) Pop() interface{} {
|
||||
return <- q.list
|
||||
// Pop pops value from channel.
|
||||
// If there's no value in channel, it would block to wait.
|
||||
// If the channel is closed, it will return a nil value immediately.
|
||||
func (c *Chan) Pop() interface{} {
|
||||
return <-c.channel
|
||||
}
|
||||
|
||||
// 关闭队列(通知所有通过Pop阻塞的协程退出)
|
||||
func (q *Chan) Close() {
|
||||
if !q.closed.Set(true) {
|
||||
close(q.list)
|
||||
}
|
||||
// Close closes the channel.
|
||||
// It is safe to be called repeatedly.
|
||||
func (c *Chan) Close() {
|
||||
if !c.closed.Set(true) {
|
||||
close(c.channel)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前队列大小
|
||||
func (q *Chan) Size() int {
|
||||
return len(q.list)
|
||||
}
|
||||
// See Len.
|
||||
func (c *Chan) Size() int {
|
||||
return c.Len()
|
||||
}
|
||||
|
||||
// Len returns the length of the channel.
|
||||
func (c *Chan) Len() int {
|
||||
return len(c.channel)
|
||||
}
|
||||
|
||||
// Cap returns the capacity of the channel.
|
||||
func (c *Chan) Cap() int {
|
||||
return cap(c.channel)
|
||||
}
|
||||
|
||||
29
g/container/gchan/gchan_example_test.go
Normal file
29
g/container/gchan/gchan_example_test.go
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gchan_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/container/gchan"
|
||||
)
|
||||
|
||||
func Example_basic() {
|
||||
n := 10
|
||||
c := gchan.New(n)
|
||||
for i := 0; i < n; i++ {
|
||||
c.Push(i)
|
||||
}
|
||||
fmt.Println(c.Len(), c.Cap())
|
||||
for i := 0; i < n; i++ {
|
||||
fmt.Print(c.Pop())
|
||||
}
|
||||
c.Close()
|
||||
|
||||
// Output:
|
||||
//10 10
|
||||
//0123456789
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// go test *.go -bench=".*"
|
||||
|
||||
package gchan_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"gitee.com/johng/gf/g/container/gchan"
|
||||
)
|
||||
|
||||
var length = 10000000
|
||||
var q1 = gchan.New(length)
|
||||
var q2 = make(chan int, length)
|
||||
|
||||
func BenchmarkGchanPushAndPop(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
q1.Push(i)
|
||||
q1.Pop()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkChannelPushAndPop(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
q2 <- i
|
||||
<- q2
|
||||
}
|
||||
}
|
||||
47
g/container/gchan/gchan_z_unit_test.go
Normal file
47
g/container/gchan/gchan_z_unit_test.go
Normal file
@ -0,0 +1,47 @@
|
||||
package gchan_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/g/container/gchan"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
)
|
||||
|
||||
func Test_Gchan(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
ch := gchan.New(10)
|
||||
|
||||
gtest.Assert(ch.Cap(), 10)
|
||||
gtest.Assert(ch.Push(1), nil)
|
||||
gtest.Assert(ch.Len(), 1)
|
||||
gtest.Assert(ch.Size(), 1)
|
||||
ch.Pop()
|
||||
gtest.Assert(ch.Len(), 0)
|
||||
gtest.Assert(ch.Size(), 0)
|
||||
ch.Close()
|
||||
gtest.Assert(ch.Push(1), errors.New("channel is closed"))
|
||||
|
||||
ch = gchan.New(0)
|
||||
ch1 := gchan.New(0)
|
||||
go func() {
|
||||
var i = 0
|
||||
for {
|
||||
v := ch.Pop()
|
||||
if v == nil {
|
||||
ch1.Push(i)
|
||||
break
|
||||
}
|
||||
gtest.Assert(v, i)
|
||||
i++
|
||||
}
|
||||
}()
|
||||
|
||||
for index := 0; index < 10; index++ {
|
||||
ch.Push(index)
|
||||
}
|
||||
ch.Close()
|
||||
gtest.Assert(ch1.Pop(), 10)
|
||||
ch1.Close()
|
||||
})
|
||||
}
|
||||
@ -1,264 +1,372 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
// If a copy of the MIT was not distributed with l file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
//
|
||||
|
||||
// Package glist provides a concurrent-safe(alternative) doubly linked list.
|
||||
// 并发安全的双向链表.
|
||||
// Package glist provides a concurrent-safe/unsafe doubly linked list.
|
||||
package glist
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
"github.com/gogf/gf/g/internal/rwmutex"
|
||||
)
|
||||
|
||||
// 变长双向链表
|
||||
type List struct {
|
||||
mu *rwmutex.RWMutex
|
||||
list *list.List
|
||||
}
|
||||
|
||||
// 获得一个变长链表指针
|
||||
func New(safe...bool) *List {
|
||||
return &List {
|
||||
mu : rwmutex.New(safe...),
|
||||
list : list.New(),
|
||||
}
|
||||
}
|
||||
|
||||
// 往链表头入栈数据项
|
||||
func (this *List) PushFront(v interface{}) *list.Element {
|
||||
this.mu.Lock()
|
||||
e := this.list.PushFront(v)
|
||||
this.mu.Unlock()
|
||||
return e
|
||||
}
|
||||
|
||||
// 往链表尾入栈数据项
|
||||
func (this *List) PushBack(v interface{}) *list.Element {
|
||||
this.mu.Lock()
|
||||
r := this.list.PushBack(v)
|
||||
this.mu.Unlock()
|
||||
return r
|
||||
}
|
||||
|
||||
// 在list 中元素mark之后插入一个值为v的元素,并返回该元素,如果mark不是list中元素,则list不改变。
|
||||
func (this *List) InsertAfter(v interface{}, mark *list.Element) *list.Element {
|
||||
this.mu.Lock()
|
||||
r := this.list.InsertAfter(v, mark)
|
||||
this.mu.Unlock()
|
||||
return r
|
||||
}
|
||||
|
||||
// 在list 中元素mark之前插入一个值为v的元素,并返回该元素,如果mark不是list中元素,则list不改变。
|
||||
func (this *List) InsertBefore(v interface{}, mark *list.Element) *list.Element {
|
||||
this.mu.Lock()
|
||||
r := this.list.InsertBefore(v, mark)
|
||||
this.mu.Unlock()
|
||||
return r
|
||||
}
|
||||
|
||||
|
||||
// 批量往链表头入栈数据项
|
||||
func (this *List) BatchPushFront(vs []interface{}) {
|
||||
this.mu.Lock()
|
||||
for _, item := range vs {
|
||||
this.list.PushFront(item)
|
||||
}
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 从链表尾端出栈数据项(删除)
|
||||
func (this *List) PopBack() interface{} {
|
||||
this.mu.Lock()
|
||||
if elem := this.list.Back(); elem != nil {
|
||||
item := this.list.Remove(elem)
|
||||
this.mu.Unlock()
|
||||
return item
|
||||
}
|
||||
this.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// 从链表头端出栈数据项(删除)
|
||||
func (this *List) PopFront() interface{} {
|
||||
this.mu.Lock()
|
||||
if elem := this.list.Front(); elem != nil {
|
||||
item := this.list.Remove(elem)
|
||||
this.mu.Unlock()
|
||||
return item
|
||||
}
|
||||
this.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// 批量从链表尾端出栈数据项(删除)
|
||||
func (this *List) BatchPopBack(max int) []interface{} {
|
||||
this.mu.Lock()
|
||||
count := this.list.Len()
|
||||
if count == 0 {
|
||||
this.mu.Unlock()
|
||||
return []interface{}{}
|
||||
type (
|
||||
List struct {
|
||||
mu *rwmutex.RWMutex
|
||||
list *list.List
|
||||
}
|
||||
|
||||
if count > max {
|
||||
count = max
|
||||
Element = list.Element
|
||||
)
|
||||
|
||||
// New creates and returns a new empty doubly linked list.
|
||||
func New(unsafe ...bool) *List {
|
||||
return &List{
|
||||
mu: rwmutex.New(unsafe...),
|
||||
list: list.New(),
|
||||
}
|
||||
items := make([]interface{}, count)
|
||||
for i := 0; i < count; i++ {
|
||||
items[i] = this.list.Remove(this.list.Back())
|
||||
}
|
||||
|
||||
// PushFront inserts a new element <e> with value <v> at the front of list <l> and returns <e>.
|
||||
func (l *List) PushFront(v interface{}) (e *Element) {
|
||||
l.mu.Lock()
|
||||
e = l.list.PushFront(v)
|
||||
l.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// PushBack inserts a new element <e> with value <v> at the back of list <l> and returns <e>.
|
||||
func (l *List) PushBack(v interface{}) (e *Element) {
|
||||
l.mu.Lock()
|
||||
e = l.list.PushBack(v)
|
||||
l.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// PushFronts inserts multiple new elements with values <values> at the front of list <l>.
|
||||
func (l *List) PushFronts(values []interface{}) {
|
||||
l.mu.Lock()
|
||||
for _, v := range values {
|
||||
l.list.PushFront(v)
|
||||
}
|
||||
this.mu.Unlock()
|
||||
return items
|
||||
l.mu.Unlock()
|
||||
}
|
||||
|
||||
// 批量从链表头端出栈数据项(删除)
|
||||
func (this *List) BatchPopFront(max int) []interface{} {
|
||||
this.mu.Lock()
|
||||
count := this.list.Len()
|
||||
if count == 0 {
|
||||
this.mu.Unlock()
|
||||
return []interface{}{}
|
||||
// PushBacks inserts multiple new elements with values <values> at the back of list <l>.
|
||||
func (l *List) PushBacks(values []interface{}) {
|
||||
l.mu.Lock()
|
||||
for _, v := range values {
|
||||
l.list.PushBack(v)
|
||||
}
|
||||
l.mu.Unlock()
|
||||
}
|
||||
|
||||
if count > max {
|
||||
count = max
|
||||
// PopBack removes the element from back of <l> and returns the value of the element.
|
||||
func (l *List) PopBack() (value interface{}) {
|
||||
l.mu.Lock()
|
||||
if e := l.list.Back(); e != nil {
|
||||
value = l.list.Remove(e)
|
||||
}
|
||||
items := make([]interface{}, count)
|
||||
for i := 0; i < count; i++ {
|
||||
items[i] = this.list.Remove(this.list.Front())
|
||||
l.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// PopFront removes the element from front of <l> and returns the value of the element.
|
||||
func (l *List) PopFront() (value interface{}) {
|
||||
l.mu.Lock()
|
||||
if e := l.list.Front(); e != nil {
|
||||
value = l.list.Remove(e)
|
||||
}
|
||||
this.mu.Unlock()
|
||||
return items
|
||||
l.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// 批量从链表尾端依次获取所有数据(删除)
|
||||
func (this *List) PopBackAll() []interface{} {
|
||||
this.mu.Lock()
|
||||
count := this.list.Len()
|
||||
if count == 0 {
|
||||
this.mu.Unlock()
|
||||
return []interface{}{}
|
||||
// PopBacks removes <max> elements from back of <l>
|
||||
// and returns values of the removed elements as slice.
|
||||
func (l *List) PopBacks(max int) (values []interface{}) {
|
||||
l.mu.Lock()
|
||||
length := l.list.Len()
|
||||
if length > 0 {
|
||||
if max > 0 && max < length {
|
||||
length = max
|
||||
}
|
||||
values = make([]interface{}, length)
|
||||
for i := 0; i < length; i++ {
|
||||
values[i] = l.list.Remove(l.list.Back())
|
||||
}
|
||||
}
|
||||
items := make([]interface{}, count)
|
||||
for i := 0; i < count; i++ {
|
||||
items[i] = this.list.Remove(this.list.Back())
|
||||
l.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// PopFronts removes <max> elements from front of <l>
|
||||
// and returns values of the removed elements as slice.
|
||||
func (l *List) PopFronts(max int) (values []interface{}) {
|
||||
l.mu.Lock()
|
||||
length := l.list.Len()
|
||||
if length > 0 {
|
||||
if max > 0 && max < length {
|
||||
length = max
|
||||
}
|
||||
values = make([]interface{}, length)
|
||||
for i := 0; i < length; i++ {
|
||||
values[i] = l.list.Remove(l.list.Front())
|
||||
}
|
||||
}
|
||||
this.mu.Unlock()
|
||||
return items
|
||||
l.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// 批量从链表头端依次获取所有数据(删除)
|
||||
func (this *List) PopFrontAll() []interface{} {
|
||||
this.mu.Lock()
|
||||
count := this.list.Len()
|
||||
if count == 0 {
|
||||
this.mu.Unlock()
|
||||
return []interface{}{}
|
||||
}
|
||||
items := make([]interface{}, count)
|
||||
for i := 0; i < count; i++ {
|
||||
items[i] = this.list.Remove(this.list.Front())
|
||||
}
|
||||
this.mu.Unlock()
|
||||
return items
|
||||
// PopBackAll removes all elements from back of <l>
|
||||
// and returns values of the removed elements as slice.
|
||||
func (l *List) PopBackAll() []interface{} {
|
||||
return l.PopBacks(-1)
|
||||
}
|
||||
|
||||
// 删除数据项
|
||||
func (this *List) Remove(e *list.Element) interface{} {
|
||||
this.mu.Lock()
|
||||
r := this.list.Remove(e)
|
||||
this.mu.Unlock()
|
||||
return r
|
||||
// PopFrontAll removes all elements from front of <l>
|
||||
// and returns values of the removed elements as slice.
|
||||
func (l *List) PopFrontAll() []interface{} {
|
||||
return l.PopFronts(-1)
|
||||
}
|
||||
|
||||
// 删除所有数据项
|
||||
func (this *List) RemoveAll() {
|
||||
this.mu.Lock()
|
||||
this.list = list.New()
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 从链表头获取所有数据(不删除)
|
||||
func (this *List) FrontAll() []interface{} {
|
||||
this.mu.RLock()
|
||||
count := this.list.Len()
|
||||
if count == 0 {
|
||||
this.mu.RUnlock()
|
||||
return []interface{}{}
|
||||
// FrontAll copies and returns values of all elements from front of <l> as slice.
|
||||
func (l *List) FrontAll() (values []interface{}) {
|
||||
l.mu.RLock()
|
||||
length := l.list.Len()
|
||||
if length > 0 {
|
||||
values = make([]interface{}, length)
|
||||
for i, e := 0, l.list.Front(); i < length; i, e = i+1, e.Next() {
|
||||
values[i] = e.Value
|
||||
}
|
||||
}
|
||||
l.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
items := make([]interface{}, 0, count)
|
||||
for e := this.list.Front(); e != nil; e = e.Next() {
|
||||
items = append(items, e.Value)
|
||||
// BackAll copies and returns values of all elements from back of <l> as slice.
|
||||
func (l *List) BackAll() (values []interface{}) {
|
||||
l.mu.RLock()
|
||||
length := l.list.Len()
|
||||
if length > 0 {
|
||||
values = make([]interface{}, length)
|
||||
for i, e := 0, l.list.Back(); i < length; i, e = i+1, e.Prev() {
|
||||
values[i] = e.Value
|
||||
}
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
return items
|
||||
l.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// 从链表尾获取所有数据(不删除)
|
||||
func (this *List) BackAll() []interface{} {
|
||||
this.mu.RLock()
|
||||
count := this.list.Len()
|
||||
if count == 0 {
|
||||
this.mu.RUnlock()
|
||||
return []interface{}{}
|
||||
// FrontValue returns value of the first element of <l> or nil if the list is empty.
|
||||
func (l *List) FrontValue() (value interface{}) {
|
||||
l.mu.RLock()
|
||||
if e := l.list.Front(); e != nil {
|
||||
value = e.Value
|
||||
}
|
||||
l.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
items := make([]interface{}, 0, count)
|
||||
for e := this.list.Back(); e != nil; e = e.Prev() {
|
||||
items = append(items, e.Value)
|
||||
// BackValue returns value of the last element of <l> or nil if the list is empty.
|
||||
func (l *List) BackValue() (value interface{}) {
|
||||
l.mu.RLock()
|
||||
if e := l.list.Back(); e != nil {
|
||||
value = e.Value
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
return items
|
||||
l.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// 获取链表头值(不删除)
|
||||
func (this *List) FrontItem() interface{} {
|
||||
this.mu.RLock()
|
||||
if f := this.list.Front(); f != nil {
|
||||
this.mu.RUnlock()
|
||||
return f.Value
|
||||
// Front returns the first element of list <l> or nil if the list is empty.
|
||||
func (l *List) Front() (e *Element) {
|
||||
l.mu.RLock()
|
||||
e = l.list.Front()
|
||||
l.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Back returns the last element of list <l> or nil if the list is empty.
|
||||
func (l *List) Back() (e *Element) {
|
||||
l.mu.RLock()
|
||||
e = l.list.Back()
|
||||
l.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Len returns the number of elements of list <l>.
|
||||
// The complexity is O(1).
|
||||
func (l *List) Len() (length int) {
|
||||
l.mu.RLock()
|
||||
length = l.list.Len()
|
||||
l.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Size is alias of Len.
|
||||
func (l *List) Size() int {
|
||||
return l.Len()
|
||||
}
|
||||
|
||||
// MoveBefore moves element <e> to its new position before <p>.
|
||||
// If <e> or <p> is not an element of <l>, or <e> == <p>, the list is not modified.
|
||||
// The element and <p> must not be nil.
|
||||
func (l *List) MoveBefore(e, p *Element) {
|
||||
l.mu.Lock()
|
||||
l.list.MoveBefore(e, p)
|
||||
l.mu.Unlock()
|
||||
}
|
||||
|
||||
// MoveAfter moves element <e> to its new position after <p>.
|
||||
// If <e> or <p> is not an element of <l>, or <e> == <p>, the list is not modified.
|
||||
// The element and <p> must not be nil.
|
||||
func (l *List) MoveAfter(e, p *Element) {
|
||||
l.mu.Lock()
|
||||
l.list.MoveAfter(e, p)
|
||||
l.mu.Unlock()
|
||||
}
|
||||
|
||||
// MoveToFront moves element <e> to the front of list <l>.
|
||||
// If <e> is not an element of <l>, the list is not modified.
|
||||
// The element must not be nil.
|
||||
func (l *List) MoveToFront(e *Element) {
|
||||
l.mu.Lock()
|
||||
l.list.MoveToFront(e)
|
||||
l.mu.Unlock()
|
||||
}
|
||||
|
||||
// MoveToBack moves element <e> to the back of list <l>.
|
||||
// If <e> is not an element of <l>, the list is not modified.
|
||||
// The element must not be nil.
|
||||
func (l *List) MoveToBack(e *Element) {
|
||||
l.mu.Lock()
|
||||
l.list.MoveToBack(e)
|
||||
l.mu.Unlock()
|
||||
}
|
||||
|
||||
// PushBackList inserts a copy of an other list at the back of list <l>.
|
||||
// The lists <l> and <other> may be the same, but they must not be nil.
|
||||
func (l *List) PushBackList(other *List) {
|
||||
if l != other {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
|
||||
this.mu.RUnlock()
|
||||
return nil
|
||||
l.mu.Lock()
|
||||
l.list.PushBackList(other.list)
|
||||
l.mu.Unlock()
|
||||
}
|
||||
|
||||
// 获取链表尾值(不删除)
|
||||
func (this *List) BackItem() interface{} {
|
||||
this.mu.RLock()
|
||||
if f := this.list.Back(); f != nil {
|
||||
this.mu.RUnlock()
|
||||
return f.Value
|
||||
}
|
||||
|
||||
this.mu.RUnlock()
|
||||
return nil
|
||||
// PushFrontList inserts a copy of an other list at the front of list <l>.
|
||||
// The lists <l> and <other> may be the same, but they must not be nil.
|
||||
func (l *List) PushFrontList(other *List) {
|
||||
if l != other {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
l.mu.Lock()
|
||||
l.list.PushFrontList(other.list)
|
||||
l.mu.Unlock()
|
||||
}
|
||||
|
||||
// 获取表头指针
|
||||
func (this *List) Front() *list.Element {
|
||||
this.mu.RLock()
|
||||
r := this.list.Front()
|
||||
this.mu.RUnlock()
|
||||
return r
|
||||
// InsertAfter inserts a new element <e> with value <v> immediately after <p> and returns <e>.
|
||||
// If <p> is not an element of <l>, the list is not modified.
|
||||
// The <p> must not be nil.
|
||||
func (l *List) InsertAfter(v interface{}, p *Element) (e *Element) {
|
||||
l.mu.Lock()
|
||||
e = l.list.InsertAfter(v, p)
|
||||
l.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// 获取表位指针
|
||||
func (this *List) Back() *list.Element {
|
||||
this.mu.RLock()
|
||||
r := this.list.Back()
|
||||
this.mu.RUnlock()
|
||||
return r
|
||||
// InsertBefore inserts a new element <e> with value <v> immediately before <p> and returns <e>.
|
||||
// If <p> is not an element of <l>, the list is not modified.
|
||||
// The <p> must not be nil.
|
||||
func (l *List) InsertBefore(v interface{}, p *Element) (e *Element) {
|
||||
l.mu.Lock()
|
||||
e = l.list.InsertBefore(v, p)
|
||||
l.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// 获取链表长度
|
||||
func (this *List) Len() int {
|
||||
this.mu.RLock()
|
||||
length := this.list.Len()
|
||||
this.mu.RUnlock()
|
||||
return length
|
||||
// Remove removes <e> from <l> if <e> is an element of list <l>.
|
||||
// It returns the element value e.Value.
|
||||
// The element must not be nil.
|
||||
func (l *List) Remove(e *Element) (value interface{}) {
|
||||
l.mu.Lock()
|
||||
value = l.list.Remove(e)
|
||||
l.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Removes removes multiple elements <es> from <l> if <es> are elements of list <l>.
|
||||
func (l *List) Removes(es []*Element) {
|
||||
l.mu.Lock()
|
||||
for _, e := range es {
|
||||
l.list.Remove(e)
|
||||
}
|
||||
l.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// RemoveAll removes all elements from list <l>.
|
||||
func (l *List) RemoveAll() {
|
||||
l.mu.Lock()
|
||||
l.list = list.New()
|
||||
l.mu.Unlock()
|
||||
}
|
||||
|
||||
// See RemoveAll().
|
||||
func (l *List) Clear() {
|
||||
l.RemoveAll()
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with given callback function <f> within RWMutex.RLock.
|
||||
func (l *List) RLockFunc(f func(list *list.List)) {
|
||||
l.mu.RLock()
|
||||
defer l.mu.RUnlock()
|
||||
f(l.list)
|
||||
}
|
||||
|
||||
// LockFunc locks writing with given callback function <f> within RWMutex.Lock.
|
||||
func (l *List) LockFunc(f func(list *list.List)) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
f(l.list)
|
||||
}
|
||||
|
||||
// Iterator is alias of IteratorAsc.
|
||||
func (l *List) Iterator(f func(e *Element) bool) {
|
||||
l.IteratorAsc(f)
|
||||
}
|
||||
|
||||
// IteratorAsc iterates the list in ascending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (l *List) IteratorAsc(f func(e *Element) bool) {
|
||||
l.mu.RLock()
|
||||
length := l.list.Len()
|
||||
if length > 0 {
|
||||
for i, e := 0, l.list.Front(); i < length; i, e = i+1, e.Next() {
|
||||
if !f(e) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
l.mu.RUnlock()
|
||||
}
|
||||
|
||||
// IteratorDesc iterates the list in descending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (l *List) IteratorDesc(f func(e *Element) bool) {
|
||||
l.mu.RLock()
|
||||
length := l.list.Len()
|
||||
if length > 0 {
|
||||
for i, e := 0, l.list.Back(); i < length; i, e = i+1, e.Prev() {
|
||||
if !f(e) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
l.mu.RUnlock()
|
||||
}
|
||||
|
||||
36
g/container/glist/glist_example_test.go
Normal file
36
g/container/glist/glist_example_test.go
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package glist_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/container/glist"
|
||||
)
|
||||
|
||||
func Example_basic() {
|
||||
n := 10
|
||||
l := glist.New()
|
||||
for i := 0; i < n; i++ {
|
||||
l.PushBack(i)
|
||||
}
|
||||
fmt.Println(l.Len())
|
||||
fmt.Println(l.FrontAll())
|
||||
fmt.Println(l.BackAll())
|
||||
for i := 0; i < n; i++ {
|
||||
fmt.Print(l.PopFront())
|
||||
}
|
||||
l.Clear()
|
||||
fmt.Println()
|
||||
fmt.Println(l.Len())
|
||||
|
||||
// Output:
|
||||
//10
|
||||
//[0 1 2 3 4 5 6 7 8 9]
|
||||
//[9 8 7 6 5 4 3 2 1 0]
|
||||
//0123456789
|
||||
//0
|
||||
}
|
||||
@ -1,46 +0,0 @@
|
||||
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// go test *.go -bench=".*" -benchmem
|
||||
|
||||
package glist
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var l = New()
|
||||
|
||||
func Benchmark_PushBack(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
l.PushBack(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_PopFront(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
l.PopFront()
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_PushFront(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
l.PushFront(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_PopBack(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
l.PopBack()
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Len(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
l.Len()
|
||||
}
|
||||
}
|
||||
|
||||
53
g/container/glist/glist_z_bench_test.go
Normal file
53
g/container/glist/glist_z_bench_test.go
Normal file
@ -0,0 +1,53 @@
|
||||
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// go test *.go -bench=".*" -benchmem
|
||||
|
||||
package glist
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
l = New()
|
||||
bn = 20000000
|
||||
)
|
||||
|
||||
func Benchmark_PushBack(b *testing.B) {
|
||||
b.N = bn
|
||||
for i := 0; i < b.N; i++ {
|
||||
l.PushBack(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_PushFront(b *testing.B) {
|
||||
b.N = bn
|
||||
for i := 0; i < b.N; i++ {
|
||||
l.PushFront(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Len(b *testing.B) {
|
||||
b.N = bn
|
||||
for i := 0; i < b.N; i++ {
|
||||
l.Len()
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_PopFront(b *testing.B) {
|
||||
b.N = bn
|
||||
for i := 0; i < b.N; i++ {
|
||||
l.PopFront()
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_PopBack(b *testing.B) {
|
||||
b.N = bn
|
||||
for i := 0; i < b.N; i++ {
|
||||
l.PopBack()
|
||||
}
|
||||
}
|
||||
583
g/container/glist/glist_z_unit_test.go
Normal file
583
g/container/glist/glist_z_unit_test.go
Normal file
@ -0,0 +1,583 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package glist
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
// 检查链表长度
|
||||
func checkListLen(t *testing.T, l *List, len int) bool {
|
||||
if n := l.Len(); n != len {
|
||||
t.Errorf("l.Len() = %d, want %d", n, len)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 检查指针地址
|
||||
func checkListPointers(t *testing.T, l *List, es []*Element) {
|
||||
if !checkListLen(t, l, len(es)) {
|
||||
return
|
||||
}
|
||||
l.RLockFunc(func(list *list.List) {
|
||||
for i, e := 0, l.list.Front(); i < list.Len(); i, e = i+1, e.Next() {
|
||||
if e.Prev() != es[i].Prev() {
|
||||
t.Errorf("list[%d].Prev = %p, want %p", i, e.Prev(), es[i].Prev())
|
||||
}
|
||||
if e.Next() != es[i].Next() {
|
||||
t.Errorf("list[%d].Next = %p, want %p", i, e.Next(), es[i].Next())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestBasic(t *testing.T) {
|
||||
l := New()
|
||||
l.PushFront(1)
|
||||
l.PushFront(2)
|
||||
if v := l.PopBack(); v != 1 {
|
||||
t.Errorf("EXPECT %v, GOT %v", 1, v)
|
||||
} else {
|
||||
//fmt.Println(v)
|
||||
}
|
||||
if v := l.PopBack(); v != 2 {
|
||||
t.Errorf("EXPECT %v, GOT %v", 2, v)
|
||||
} else {
|
||||
//fmt.Println(v)
|
||||
}
|
||||
if v := l.PopBack(); v != nil {
|
||||
t.Errorf("EXPECT %v, GOT %v", nil, v)
|
||||
} else {
|
||||
//fmt.Println(v)
|
||||
}
|
||||
l.PushBack(1)
|
||||
l.PushBack(2)
|
||||
if v := l.PopFront(); v != 1 {
|
||||
t.Errorf("EXPECT %v, GOT %v", 1, v)
|
||||
} else {
|
||||
//fmt.Println(v)
|
||||
}
|
||||
if v := l.PopFront(); v != 2 {
|
||||
t.Errorf("EXPECT %v, GOT %v", 2, v)
|
||||
} else {
|
||||
//fmt.Println(v)
|
||||
}
|
||||
if v := l.PopFront(); v != nil {
|
||||
t.Errorf("EXPECT %v, GOT %v", nil, v)
|
||||
} else {
|
||||
//fmt.Println(v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
l := New()
|
||||
checkListPointers(t, l, []*Element{})
|
||||
|
||||
// Single element list
|
||||
e := l.PushFront("a")
|
||||
checkListPointers(t, l, []*Element{e})
|
||||
l.MoveToFront(e)
|
||||
checkListPointers(t, l, []*Element{e})
|
||||
l.MoveToBack(e)
|
||||
checkListPointers(t, l, []*Element{e})
|
||||
l.Remove(e)
|
||||
checkListPointers(t, l, []*Element{})
|
||||
|
||||
// Bigger list
|
||||
e2 := l.PushFront(2)
|
||||
e1 := l.PushFront(1)
|
||||
e3 := l.PushBack(3)
|
||||
e4 := l.PushBack("banana")
|
||||
checkListPointers(t, l, []*Element{e1, e2, e3, e4})
|
||||
|
||||
l.Remove(e2)
|
||||
checkListPointers(t, l, []*Element{e1, e3, e4})
|
||||
|
||||
l.MoveToFront(e3) // move from middle
|
||||
checkListPointers(t, l, []*Element{e3, e1, e4})
|
||||
|
||||
l.MoveToFront(e1)
|
||||
l.MoveToBack(e3) // move from middle
|
||||
checkListPointers(t, l, []*Element{e1, e4, e3})
|
||||
|
||||
l.MoveToFront(e3) // move from back
|
||||
checkListPointers(t, l, []*Element{e3, e1, e4})
|
||||
l.MoveToFront(e3) // should be no-op
|
||||
checkListPointers(t, l, []*Element{e3, e1, e4})
|
||||
|
||||
l.MoveToBack(e3) // move from front
|
||||
checkListPointers(t, l, []*Element{e1, e4, e3})
|
||||
l.MoveToBack(e3) // should be no-op
|
||||
checkListPointers(t, l, []*Element{e1, e4, e3})
|
||||
|
||||
e2 = l.InsertBefore(2, e1) // insert before front
|
||||
checkListPointers(t, l, []*Element{e2, e1, e4, e3})
|
||||
l.Remove(e2)
|
||||
e2 = l.InsertBefore(2, e4) // insert before middle
|
||||
checkListPointers(t, l, []*Element{e1, e2, e4, e3})
|
||||
l.Remove(e2)
|
||||
e2 = l.InsertBefore(2, e3) // insert before back
|
||||
checkListPointers(t, l, []*Element{e1, e4, e2, e3})
|
||||
l.Remove(e2)
|
||||
|
||||
e2 = l.InsertAfter(2, e1) // insert after front
|
||||
checkListPointers(t, l, []*Element{e1, e2, e4, e3})
|
||||
l.Remove(e2)
|
||||
e2 = l.InsertAfter(2, e4) // insert after middle
|
||||
checkListPointers(t, l, []*Element{e1, e4, e2, e3})
|
||||
l.Remove(e2)
|
||||
e2 = l.InsertAfter(2, e3) // insert after back
|
||||
checkListPointers(t, l, []*Element{e1, e4, e3, e2})
|
||||
l.Remove(e2)
|
||||
|
||||
// Check standard iteration.
|
||||
sum := 0
|
||||
for e := l.Front(); e != nil; e = e.Next() {
|
||||
if i, ok := e.Value.(int); ok {
|
||||
sum += i
|
||||
}
|
||||
}
|
||||
if sum != 4 {
|
||||
t.Errorf("sum over l = %d, want 4", sum)
|
||||
}
|
||||
|
||||
// Clear all elements by iterating
|
||||
var next *Element
|
||||
for e := l.Front(); e != nil; e = next {
|
||||
next = e.Next()
|
||||
l.Remove(e)
|
||||
}
|
||||
checkListPointers(t, l, []*Element{})
|
||||
}
|
||||
|
||||
func checkList(t *testing.T, l *List, es []interface{}) {
|
||||
if !checkListLen(t, l, len(es)) {
|
||||
return
|
||||
}
|
||||
|
||||
i := 0
|
||||
for e := l.Front(); e != nil; e = e.Next() {
|
||||
|
||||
switch e.Value.(type) {
|
||||
case int:
|
||||
if le := e.Value.(int); le != es[i] {
|
||||
t.Errorf("elt[%d].Value() = %v, want %v", i, le, es[i])
|
||||
}
|
||||
// default string
|
||||
default:
|
||||
if le := e.Value.(string); le != es[i] {
|
||||
t.Errorf("elt[%v].Value() = %v, want %v", i, le, es[i])
|
||||
}
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
//for e := l.Front(); e != nil; e = e.Next() {
|
||||
// le := e.Value.(int)
|
||||
// if le != es[i] {
|
||||
// t.Errorf("elt[%d].Value() = %v, want %v", i, le, es[i])
|
||||
// }
|
||||
// i++
|
||||
//}
|
||||
}
|
||||
|
||||
func TestExtending(t *testing.T) {
|
||||
l1 := New()
|
||||
l2 := New()
|
||||
|
||||
l1.PushBack(1)
|
||||
l1.PushBack(2)
|
||||
l1.PushBack(3)
|
||||
|
||||
l2.PushBack(4)
|
||||
l2.PushBack(5)
|
||||
|
||||
l3 := New()
|
||||
l3.PushBackList(l1)
|
||||
checkList(t, l3, []interface{}{1, 2, 3})
|
||||
l3.PushBackList(l2)
|
||||
checkList(t, l3, []interface{}{1, 2, 3, 4, 5})
|
||||
|
||||
l3 = New()
|
||||
l3.PushFrontList(l2)
|
||||
checkList(t, l3, []interface{}{4, 5})
|
||||
l3.PushFrontList(l1)
|
||||
checkList(t, l3, []interface{}{1, 2, 3, 4, 5})
|
||||
|
||||
checkList(t, l1, []interface{}{1, 2, 3})
|
||||
checkList(t, l2, []interface{}{4, 5})
|
||||
|
||||
l3 = New()
|
||||
l3.PushBackList(l1)
|
||||
checkList(t, l3, []interface{}{1, 2, 3})
|
||||
l3.PushBackList(l3)
|
||||
checkList(t, l3, []interface{}{1, 2, 3, 1, 2, 3})
|
||||
|
||||
l3 = New()
|
||||
l3.PushFrontList(l1)
|
||||
checkList(t, l3, []interface{}{1, 2, 3})
|
||||
l3.PushFrontList(l3)
|
||||
checkList(t, l3, []interface{}{1, 2, 3, 1, 2, 3})
|
||||
|
||||
l3 = New()
|
||||
l1.PushBackList(l3)
|
||||
checkList(t, l1, []interface{}{1, 2, 3})
|
||||
l1.PushFrontList(l3)
|
||||
checkList(t, l1, []interface{}{1, 2, 3})
|
||||
}
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
l := New()
|
||||
e1 := l.PushBack(1)
|
||||
e2 := l.PushBack(2)
|
||||
checkListPointers(t, l, []*Element{e1, e2})
|
||||
//e := l.Front()
|
||||
//l.Remove(e)
|
||||
//checkListPointers(t, l, []*Element{e2})
|
||||
//l.Remove(e)
|
||||
//checkListPointers(t, l, []*Element{e2})
|
||||
}
|
||||
|
||||
func TestIssue4103(t *testing.T) {
|
||||
l1 := New()
|
||||
l1.PushBack(1)
|
||||
l1.PushBack(2)
|
||||
|
||||
l2 := New()
|
||||
l2.PushBack(3)
|
||||
l2.PushBack(4)
|
||||
|
||||
e := l1.Front()
|
||||
l2.Remove(e) // l2 should not change because e is not an element of l2
|
||||
if n := l2.Len(); n != 2 {
|
||||
t.Errorf("l2.Len() = %d, want 2", n)
|
||||
}
|
||||
|
||||
l1.InsertBefore(8, e)
|
||||
if n := l1.Len(); n != 3 {
|
||||
t.Errorf("l1.Len() = %d, want 3", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue6349(t *testing.T) {
|
||||
l := New()
|
||||
l.PushBack(1)
|
||||
l.PushBack(2)
|
||||
|
||||
e := l.Front()
|
||||
l.Remove(e)
|
||||
if e.Value != 1 {
|
||||
t.Errorf("e.value = %d, want 1", e.Value)
|
||||
}
|
||||
//if e.Next() != nil {
|
||||
// t.Errorf("e.Next() != nil")
|
||||
//}
|
||||
//if e.Prev() != nil {
|
||||
// t.Errorf("e.Prev() != nil")
|
||||
//}
|
||||
}
|
||||
|
||||
func TestMove(t *testing.T) {
|
||||
l := New()
|
||||
e1 := l.PushBack(1)
|
||||
e2 := l.PushBack(2)
|
||||
e3 := l.PushBack(3)
|
||||
e4 := l.PushBack(4)
|
||||
|
||||
l.MoveAfter(e3, e3)
|
||||
checkListPointers(t, l, []*Element{e1, e2, e3, e4})
|
||||
l.MoveBefore(e2, e2)
|
||||
checkListPointers(t, l, []*Element{e1, e2, e3, e4})
|
||||
|
||||
l.MoveAfter(e3, e2)
|
||||
checkListPointers(t, l, []*Element{e1, e2, e3, e4})
|
||||
l.MoveBefore(e2, e3)
|
||||
checkListPointers(t, l, []*Element{e1, e2, e3, e4})
|
||||
|
||||
l.MoveBefore(e2, e4)
|
||||
checkListPointers(t, l, []*Element{e1, e3, e2, e4})
|
||||
e2, e3 = e3, e2
|
||||
|
||||
l.MoveBefore(e4, e1)
|
||||
checkListPointers(t, l, []*Element{e4, e1, e2, e3})
|
||||
e1, e2, e3, e4 = e4, e1, e2, e3
|
||||
|
||||
l.MoveAfter(e4, e1)
|
||||
checkListPointers(t, l, []*Element{e1, e4, e2, e3})
|
||||
e2, e3, e4 = e4, e2, e3
|
||||
|
||||
l.MoveAfter(e2, e3)
|
||||
checkListPointers(t, l, []*Element{e1, e3, e2, e4})
|
||||
e2, e3 = e3, e2
|
||||
}
|
||||
|
||||
// Test PushFront, PushBack, PushFrontList, PushBackList with uninitialized List
|
||||
func TestZeroList(t *testing.T) {
|
||||
var l1 = New()
|
||||
l1.PushFront(1)
|
||||
checkList(t, l1, []interface{}{1})
|
||||
|
||||
var l2 = New()
|
||||
l2.PushBack(1)
|
||||
checkList(t, l2, []interface{}{1})
|
||||
|
||||
var l3 = New()
|
||||
l3.PushFrontList(l1)
|
||||
checkList(t, l3, []interface{}{1})
|
||||
|
||||
var l4 = New()
|
||||
l4.PushBackList(l2)
|
||||
checkList(t, l4, []interface{}{1})
|
||||
}
|
||||
|
||||
// Test that a list l is not modified when calling InsertBefore with a mark that is not an element of l.
|
||||
func TestInsertBeforeUnknownMark(t *testing.T) {
|
||||
l := New()
|
||||
l.PushBack(1)
|
||||
l.PushBack(2)
|
||||
l.PushBack(3)
|
||||
l.InsertBefore(1, new(Element))
|
||||
checkList(t, l, []interface{}{1, 2, 3})
|
||||
}
|
||||
|
||||
// Test that a list l is not modified when calling InsertAfter with a mark that is not an element of l.
|
||||
func TestInsertAfterUnknownMark(t *testing.T) {
|
||||
l := New()
|
||||
l.PushBack(1)
|
||||
l.PushBack(2)
|
||||
l.PushBack(3)
|
||||
l.InsertAfter(1, new(Element))
|
||||
checkList(t, l, []interface{}{1, 2, 3})
|
||||
}
|
||||
|
||||
// Test that a list l is not modified when calling MoveAfter or MoveBefore with a mark that is not an element of l.
|
||||
func TestMoveUnknownMark(t *testing.T) {
|
||||
l1 := New()
|
||||
e1 := l1.PushBack(1)
|
||||
|
||||
l2 := New()
|
||||
e2 := l2.PushBack(2)
|
||||
|
||||
l1.MoveAfter(e1, e2)
|
||||
checkList(t, l1, []interface{}{1})
|
||||
checkList(t, l2, []interface{}{2})
|
||||
|
||||
l1.MoveBefore(e1, e2)
|
||||
checkList(t, l1, []interface{}{1})
|
||||
checkList(t, l2, []interface{}{2})
|
||||
}
|
||||
|
||||
func TestList_RemoveAll(t *testing.T) {
|
||||
l := New()
|
||||
l.PushBack(1)
|
||||
l.RemoveAll()
|
||||
checkList(t, l, []interface{}{})
|
||||
l.PushBack(2)
|
||||
checkList(t, l, []interface{}{2})
|
||||
}
|
||||
|
||||
func TestList_PushFronts(t *testing.T) {
|
||||
l := New()
|
||||
a1 := []interface{}{1, 2}
|
||||
l.PushFronts(a1)
|
||||
checkList(t, l, []interface{}{2, 1})
|
||||
a1 = []interface{}{3, 4, 5}
|
||||
l.PushFronts(a1)
|
||||
checkList(t, l, []interface{}{5, 4, 3, 2, 1})
|
||||
}
|
||||
|
||||
func TestList_PushBacks(t *testing.T) {
|
||||
l := New()
|
||||
a1 := []interface{}{1, 2}
|
||||
l.PushBacks(a1)
|
||||
checkList(t, l, []interface{}{1, 2})
|
||||
a1 = []interface{}{3, 4, 5}
|
||||
l.PushBacks(a1)
|
||||
checkList(t, l, []interface{}{1, 2, 3, 4, 5})
|
||||
}
|
||||
|
||||
func TestList_PopBacks(t *testing.T) {
|
||||
l := New()
|
||||
a1 := []interface{}{1, 2, 3, 4}
|
||||
a2 := []interface{}{"a", "c", "b", "e"}
|
||||
l.PushFronts(a1)
|
||||
i1 := l.PopBacks(2)
|
||||
gtest.Assert(i1, []interface{}{"1", "2"})
|
||||
|
||||
l.PushBacks(a2) //4.3,a,c,b,e
|
||||
i1 = l.PopBacks(3)
|
||||
gtest.Assert(i1, []interface{}{"e", "b", "c"})
|
||||
|
||||
}
|
||||
|
||||
func TestList_PopFronts(t *testing.T) {
|
||||
l := New()
|
||||
a1 := []interface{}{1, 2, 3, 4}
|
||||
l.PushFronts(a1)
|
||||
i1 := l.PopFronts(2)
|
||||
gtest.Assert(i1, []interface{}{"4", "3"})
|
||||
gtest.Assert(l.Len(), 2)
|
||||
}
|
||||
|
||||
func TestList_PopBackAll(t *testing.T) {
|
||||
l := New()
|
||||
a1 := []interface{}{1, 2, 3, 4}
|
||||
l.PushFronts(a1)
|
||||
i1 := l.PopBackAll()
|
||||
gtest.Assert(i1, []interface{}{1, 2, 3, 4})
|
||||
gtest.Assert(l.Len(), 0)
|
||||
|
||||
}
|
||||
|
||||
func TestList_PopFrontAll(t *testing.T) {
|
||||
l := New()
|
||||
a1 := []interface{}{1, 2, 3, 4}
|
||||
l.PushFronts(a1)
|
||||
i1 := l.PopFrontAll()
|
||||
gtest.Assert(i1, []interface{}{4, 3, 2, 1})
|
||||
gtest.Assert(l.Len(), 0)
|
||||
}
|
||||
|
||||
func TestList_FrontAll(t *testing.T) {
|
||||
l := New()
|
||||
a1 := []interface{}{1, 2, 3, 4}
|
||||
l.PushFronts(a1)
|
||||
i1 := l.FrontAll()
|
||||
gtest.Assert(i1, []interface{}{4, 3, 2, 1})
|
||||
gtest.Assert(l.Len(), 4)
|
||||
}
|
||||
|
||||
func TestList_BackAll(t *testing.T) {
|
||||
l := New()
|
||||
a1 := []interface{}{1, 2, 3, 4}
|
||||
l.PushFronts(a1)
|
||||
i1 := l.BackAll()
|
||||
gtest.Assert(i1, []interface{}{1, 2, 3, 4})
|
||||
gtest.Assert(l.Len(), 4)
|
||||
}
|
||||
|
||||
func TestList_FrontValue(t *testing.T) {
|
||||
l := New()
|
||||
l2 := New()
|
||||
a1 := []interface{}{1, 2, 3, 4}
|
||||
l.PushFronts(a1)
|
||||
i1 := l.FrontValue()
|
||||
gtest.Assert(gconv.Int(i1), 4)
|
||||
gtest.Assert(l.Len(), 4)
|
||||
|
||||
i1 = l2.FrontValue()
|
||||
gtest.Assert(i1, nil)
|
||||
}
|
||||
|
||||
func TestList_BackValue(t *testing.T) {
|
||||
l := New()
|
||||
l2 := New()
|
||||
a1 := []interface{}{1, 2, 3, 4}
|
||||
l.PushFronts(a1)
|
||||
i1 := l.BackValue()
|
||||
gtest.Assert(gconv.Int(i1), 1)
|
||||
gtest.Assert(l.Len(), 4)
|
||||
|
||||
i1 = l2.FrontValue()
|
||||
gtest.Assert(i1, nil)
|
||||
|
||||
}
|
||||
|
||||
func TestList_Back(t *testing.T) {
|
||||
l := New()
|
||||
a1 := []interface{}{1, 2, 3, 4}
|
||||
l.PushFronts(a1)
|
||||
e1 := l.Back()
|
||||
gtest.Assert(e1.Value, 1)
|
||||
gtest.Assert(l.Len(), 4)
|
||||
}
|
||||
|
||||
func TestList_Size(t *testing.T) {
|
||||
l := New()
|
||||
a1 := []interface{}{1, 2, 3, 4}
|
||||
l.PushFronts(a1)
|
||||
gtest.Assert(l.Size(), 4)
|
||||
l.PopFront()
|
||||
gtest.Assert(l.Size(), 3)
|
||||
}
|
||||
|
||||
func TestList_Removes(t *testing.T) {
|
||||
l := New()
|
||||
a1 := []interface{}{1, 2, 3, 4}
|
||||
l.PushFronts(a1)
|
||||
e1 := l.Back()
|
||||
l.Removes([]*Element{e1})
|
||||
gtest.Assert(l.Len(), 3)
|
||||
|
||||
e2 := l.Back()
|
||||
l.Removes([]*Element{e2})
|
||||
gtest.Assert(l.Len(), 2)
|
||||
checkList(t, l, []interface{}{4, 3})
|
||||
|
||||
}
|
||||
|
||||
func TestList_Clear(t *testing.T) {
|
||||
l := New()
|
||||
a1 := []interface{}{1, 2, 3, 4}
|
||||
l.PushFronts(a1)
|
||||
l.Clear()
|
||||
gtest.Assert(l.Len(), 0)
|
||||
}
|
||||
|
||||
func TestList_IteratorAsc(t *testing.T) {
|
||||
l := New()
|
||||
a1 := []interface{}{1, 2, 5, 6, 3, 4}
|
||||
l.PushFronts(a1)
|
||||
e1 := l.Back()
|
||||
fun1 := func(e *Element) bool {
|
||||
if gconv.Int(e1.Value) > 2 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
checkList(t, l, []interface{}{4, 3, 6, 5, 2, 1})
|
||||
l.IteratorAsc(fun1)
|
||||
checkList(t, l, []interface{}{4, 3, 6, 5, 2, 1})
|
||||
}
|
||||
|
||||
func TestList_IteratorDesc(t *testing.T) {
|
||||
l := New()
|
||||
a1 := []interface{}{1, 2, 3, 4}
|
||||
l.PushFronts(a1)
|
||||
e1 := l.Back()
|
||||
fun1 := func(e *Element) bool {
|
||||
if gconv.Int(e1.Value) > 6 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
l.IteratorDesc(fun1)
|
||||
gtest.Assert(l.Len(), 4)
|
||||
checkList(t, l, []interface{}{4, 3, 2, 1})
|
||||
}
|
||||
|
||||
func TestList_Iterator(t *testing.T) {
|
||||
l := New()
|
||||
a1 := []interface{}{"a", "b", "c", "d", "e"}
|
||||
l.PushFronts(a1)
|
||||
e1 := l.Back()
|
||||
fun1 := func(e *Element) bool {
|
||||
if gconv.String(e1.Value) > "c" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
checkList(t, l, []interface{}{"e", "d", "c", "b", "a"})
|
||||
l.Iterator(fun1)
|
||||
checkList(t, l, []interface{}{"e", "d", "c", "b", "a"})
|
||||
}
|
||||
@ -1,20 +1,44 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
// If a copy of the MIT was not distributed with gm file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package gmap provides kinds of concurrent-safe(alternative) maps.
|
||||
// 并发安全的哈希MAP.
|
||||
// Package gmap provides concurrent-safe/unsafe map containers.
|
||||
package gmap
|
||||
|
||||
// 默认的Map对象其实就是InterfaceInterfaceMap的别名。
|
||||
// 注意
|
||||
// 1、这个Map是所有并发安全Map中效率最低的,如果对效率要求比较高的场合,请合理选择对应数据类型的Map;
|
||||
// 2、这个Map的优点是使用简便,由于键值都是interface{}类型,因此对键值的数据类型要求不高;
|
||||
// 3、底层实现比较类似于sync.Map;
|
||||
type Map = InterfaceInterfaceMap
|
||||
// Map based on hash table, alias of AnyAnyMap.
|
||||
type Map = AnyAnyMap
|
||||
type HashMap = AnyAnyMap
|
||||
|
||||
func New(safe...bool) *Map {
|
||||
return NewInterfaceInterfaceMap(safe...)
|
||||
}
|
||||
// New returns an empty hash map.
|
||||
// The parameter <unsafe> used to specify whether using map in un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe.
|
||||
func New(unsafe ...bool) *Map {
|
||||
return NewAnyAnyMap(unsafe...)
|
||||
}
|
||||
|
||||
// NewFrom returns a hash map from given map <data>.
|
||||
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
|
||||
// there might be some concurrent-safe issues when changing the map outside.
|
||||
// The parameter <unsafe> used to specify whether using tree in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewFrom(data map[interface{}]interface{}, unsafe ...bool) *Map {
|
||||
return NewAnyAnyMapFrom(data, unsafe...)
|
||||
}
|
||||
|
||||
// NewHashMap returns an empty hash map.
|
||||
// The parameter <unsafe> used to specify whether using map in un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe.
|
||||
func NewHashMap(unsafe ...bool) *Map {
|
||||
return NewAnyAnyMap(unsafe...)
|
||||
}
|
||||
|
||||
// NewHashMapFrom returns a hash map from given map <data>.
|
||||
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
|
||||
// there might be some concurrent-safe issues when changing the map outside.
|
||||
// The parameter <unsafe> used to specify whether using tree in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewHashMapFrom(data map[interface{}]interface{}, unsafe ...bool) *Map {
|
||||
return NewAnyAnyMapFrom(data, unsafe...)
|
||||
}
|
||||
|
||||
@ -1,139 +0,0 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// go test *.go -bench=".*" -benchmem
|
||||
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
|
||||
var ibm = NewIntBoolMap()
|
||||
var iim = NewIntIntMap()
|
||||
var iifm = NewIntInterfaceMap()
|
||||
var ism = NewIntStringMap()
|
||||
var ififm = NewInterfaceInterfaceMap()
|
||||
var sbm = NewStringBoolMap()
|
||||
var sim = NewStringIntMap()
|
||||
var sifm = NewStringInterfaceMap()
|
||||
var ssm = NewStringStringMap()
|
||||
|
||||
// 写入性能测试
|
||||
|
||||
func Benchmark_IntBoolMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ibm.Set(i, true)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_IntIntMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
iim.Set(i, i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_IntInterfaceMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
iifm.Set(i, i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_IntStringMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ism.Set(i, strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_InterfaceInterfaceMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ififm.Set(i, i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_StringBoolMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
sbm.Set(strconv.Itoa(i), true)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_StringIntMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
sim.Set(strconv.Itoa(i), i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_StringInterfaceMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
sifm.Set(strconv.Itoa(i), i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_StringStringMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ssm.Set(strconv.Itoa(i), strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 读取性能测试
|
||||
|
||||
func Benchmark_IntBoolMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ibm.Get(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_IntIntMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
iim.Get(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_IntInterfaceMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
iifm.Get(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_IntStringMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ism.Get(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_InterfaceInterfaceMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ififm.Get(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_StringBoolMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
sbm.Get(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_StringIntMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
sim.Get(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_StringInterfaceMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
sifm.Get(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_StringStringMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ssm.Get(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,56 +0,0 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// go test *.go -bench=".*" -benchmem
|
||||
|
||||
package gmap_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"gitee.com/johng/gf/g/container/gmap"
|
||||
"sync"
|
||||
)
|
||||
|
||||
|
||||
var m1 = gmap.NewIntIntMap()
|
||||
var m2 = sync.Map{}
|
||||
|
||||
func BenchmarkGmapSet(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
m1.Set(i, i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSyncmapSet(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
m2.Store(i, i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGmapGet(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
m1.Get(i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSyncmapGet(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
m2.Load(i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGmapRemove(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
m1.Remove(i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSyncmapRmove(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
m2.Delete(i)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,139 +0,0 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// go test *.go -bench=".*" -benchmem
|
||||
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
|
||||
var ibmUnsafe = NewIntBoolMap(false)
|
||||
var iimUnsafe = NewIntIntMap(false)
|
||||
var iifmUnsafe = NewIntInterfaceMap(false)
|
||||
var ismUnsafe = NewIntStringMap(false)
|
||||
var ififmUnsafe = NewInterfaceInterfaceMap(false)
|
||||
var sbmUnsafe = NewStringBoolMap(false)
|
||||
var simUnsafe = NewStringIntMap(false)
|
||||
var sifmUnsafe = NewStringInterfaceMap(false)
|
||||
var ssmUnsafe = NewStringStringMap(false)
|
||||
|
||||
// 写入性能测试
|
||||
|
||||
func Benchmark_Unsafe_IntBoolMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ibmUnsafe.Set(i, true)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_IntIntMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
iimUnsafe.Set(i, i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_IntInterfaceMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
iifmUnsafe.Set(i, i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_IntStringMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ismUnsafe.Set(i, strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_InterfaceInterfaceMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ififmUnsafe.Set(i, i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_StringBoolMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
sbmUnsafe.Set(strconv.Itoa(i), true)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_StringIntMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
simUnsafe.Set(strconv.Itoa(i), i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_StringInterfaceMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
sifmUnsafe.Set(strconv.Itoa(i), i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_StringStringMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ssmUnsafe.Set(strconv.Itoa(i), strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 读取性能测试
|
||||
|
||||
func Benchmark_Unsafe_IntBoolMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ibmUnsafe.Get(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_IntIntMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
iimUnsafe.Get(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_IntInterfaceMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
iifmUnsafe.Get(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_IntStringMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ismUnsafe.Get(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_InterfaceInterfaceMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ififmUnsafe.Get(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_StringBoolMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
sbmUnsafe.Get(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_StringIntMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
simUnsafe.Get(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_StringInterfaceMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
sifmUnsafe.Get(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_StringStringMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ssmUnsafe.Get(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
330
g/container/gmap/gmap_hash_any_any_map.go
Normal file
330
g/container/gmap/gmap_hash_any_any_map.go
Normal file
@ -0,0 +1,330 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with gm file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/gvar"
|
||||
"github.com/gogf/gf/g/internal/rwmutex"
|
||||
)
|
||||
|
||||
type AnyAnyMap struct {
|
||||
mu *rwmutex.RWMutex
|
||||
data map[interface{}]interface{}
|
||||
}
|
||||
|
||||
// NewAnyAnyMap returns an empty hash map.
|
||||
// The parameter <unsafe> used to specify whether using map in un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe.
|
||||
func NewAnyAnyMap(unsafe ...bool) *AnyAnyMap {
|
||||
return &AnyAnyMap{
|
||||
mu: rwmutex.New(unsafe...),
|
||||
data: make(map[interface{}]interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
// NewAnyAnyMapFrom returns a hash map from given map <data>.
|
||||
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
|
||||
// there might be some concurrent-safe issues when changing the map outside.
|
||||
func NewAnyAnyMapFrom(data map[interface{}]interface{}, unsafe ...bool) *AnyAnyMap {
|
||||
return &AnyAnyMap{
|
||||
mu: rwmutex.New(unsafe...),
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// Iterator iterates the hash map with custom callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (m *AnyAnyMap) Iterator(f func(k interface{}, v interface{}) bool) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
for k, v := range m.data {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clone returns a new hash map with copy of current map data.
|
||||
func (m *AnyAnyMap) Clone(unsafe ...bool) *AnyAnyMap {
|
||||
return NewFrom(m.Map(), unsafe...)
|
||||
}
|
||||
|
||||
// Map returns a copy of the data of the hash map.
|
||||
func (m *AnyAnyMap) Map() map[interface{}]interface{} {
|
||||
m.mu.RLock()
|
||||
data := make(map[interface{}]interface{}, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return data
|
||||
}
|
||||
|
||||
// Set sets key-value to the hash map.
|
||||
func (m *AnyAnyMap) Set(key interface{}, val interface{}) {
|
||||
m.mu.Lock()
|
||||
m.data[key] = val
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the hash map.
|
||||
func (m *AnyAnyMap) Sets(data map[interface{}]interface{}) {
|
||||
m.mu.Lock()
|
||||
for k, v := range data {
|
||||
m.data[k] = v
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Search searches the map with given <key>.
|
||||
// Second return parameter <found> is true if key was found, otherwise false.
|
||||
func (m *AnyAnyMap) Search(key interface{}) (value interface{}, found bool) {
|
||||
m.mu.RLock()
|
||||
value, found = m.data[key]
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Get returns the value by given <key>.
|
||||
func (m *AnyAnyMap) Get(key interface{}) interface{} {
|
||||
m.mu.RLock()
|
||||
val, _ := m.data[key]
|
||||
m.mu.RUnlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given <key>,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// When setting value, if <value> is type of <func() interface {}>,
|
||||
// it will be executed with mutex.Lock of the hash map,
|
||||
// and its return value will be set to the map with <key>.
|
||||
//
|
||||
// It returns value with given <key>.
|
||||
func (m *AnyAnyMap) doSetWithLockCheck(key interface{}, value interface{}) interface{} {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if v, ok := m.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
if f, ok := value.(func() interface{}); ok {
|
||||
value = f()
|
||||
}
|
||||
m.data[key] = value
|
||||
return value
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or set value with given <value> if not exist and returns this value.
|
||||
func (m *AnyAnyMap) GetOrSet(key interface{}, value interface{}) interface{} {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with return value of callback function <f> if not exist
|
||||
// and returns this value.
|
||||
func (m *AnyAnyMap) GetOrSetFunc(key interface{}, f func() interface{}) interface{} {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
// or sets value with return value of callback function <f> if not exist
|
||||
// and returns this value.
|
||||
//
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
|
||||
// with mutex.Lock of the hash map.
|
||||
func (m *AnyAnyMap) GetOrSetFuncLock(key interface{}, f func() interface{}) interface{} {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetVar returns a gvar.Var with the value by given <key>.
|
||||
// The returned gvar.Var is un-concurrent safe.
|
||||
func (m *AnyAnyMap) GetVar(key interface{}) *gvar.Var {
|
||||
return gvar.New(m.Get(key), true)
|
||||
}
|
||||
|
||||
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
|
||||
// The returned gvar.Var is un-concurrent safe.
|
||||
func (m *AnyAnyMap) GetVarOrSet(key interface{}, value interface{}) *gvar.Var {
|
||||
return gvar.New(m.GetOrSet(key, value), true)
|
||||
}
|
||||
|
||||
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
|
||||
// The returned gvar.Var is un-concurrent safe.
|
||||
func (m *AnyAnyMap) GetVarOrSetFunc(key interface{}, f func() interface{}) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFunc(key, f), true)
|
||||
}
|
||||
|
||||
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
|
||||
// The returned gvar.Var is un-concurrent safe.
|
||||
func (m *AnyAnyMap) GetVarOrSetFuncLock(key interface{}, f func() interface{}) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFuncLock(key, f), true)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
func (m *AnyAnyMap) SetIfNotExist(key interface{}, value interface{}) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
func (m *AnyAnyMap) SetIfNotExistFunc(key interface{}, f func() interface{}) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
//
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function <f> with mutex.Lock of the hash map.
|
||||
func (m *AnyAnyMap) SetIfNotExistFuncLock(key interface{}, f func() interface{}) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Remove deletes value from map by given <key>, and return this deleted value.
|
||||
func (m *AnyAnyMap) Remove(key interface{}) interface{} {
|
||||
m.mu.Lock()
|
||||
val, exists := m.data[key]
|
||||
if exists {
|
||||
delete(m.data, key)
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the map by keys.
|
||||
func (m *AnyAnyMap) Removes(keys []interface{}) {
|
||||
m.mu.Lock()
|
||||
for _, key := range keys {
|
||||
delete(m.data, key)
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Keys returns all keys of the map as a slice.
|
||||
func (m *AnyAnyMap) Keys() []interface{} {
|
||||
m.mu.RLock()
|
||||
keys := make([]interface{}, len(m.data))
|
||||
index := 0
|
||||
for key := range m.data {
|
||||
keys[index] = key
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return keys
|
||||
}
|
||||
|
||||
// Values returns all values of the map as a slice.
|
||||
func (m *AnyAnyMap) Values() []interface{} {
|
||||
m.mu.RLock()
|
||||
values := make([]interface{}, len(m.data))
|
||||
index := 0
|
||||
for _, value := range m.data {
|
||||
values[index] = value
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return values
|
||||
}
|
||||
|
||||
// Contains checks whether a key exists.
|
||||
// It returns true if the <key> exists, or else false.
|
||||
func (m *AnyAnyMap) Contains(key interface{}) bool {
|
||||
m.mu.RLock()
|
||||
_, exists := m.data[key]
|
||||
m.mu.RUnlock()
|
||||
return exists
|
||||
}
|
||||
|
||||
// Size returns the size of the map.
|
||||
func (m *AnyAnyMap) Size() int {
|
||||
m.mu.RLock()
|
||||
length := len(m.data)
|
||||
m.mu.RUnlock()
|
||||
return length
|
||||
}
|
||||
|
||||
// IsEmpty checks whether the map is empty.
|
||||
// It returns true if map is empty, or else false.
|
||||
func (m *AnyAnyMap) IsEmpty() bool {
|
||||
m.mu.RLock()
|
||||
empty := len(m.data) == 0
|
||||
m.mu.RUnlock()
|
||||
return empty
|
||||
}
|
||||
|
||||
// Clear deletes all data of the map, it will remake a new underlying data map.
|
||||
func (m *AnyAnyMap) Clear() {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[interface{}]interface{})
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// LockFunc locks writing with given callback function <f> within RWMutex.Lock.
|
||||
func (m *AnyAnyMap) LockFunc(f func(m map[interface{}]interface{})) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
f(m.data)
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with given callback function <f> within RWMutex.RLock.
|
||||
func (m *AnyAnyMap) RLockFunc(f func(m map[interface{}]interface{})) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
f(m.data)
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the map to value-key.
|
||||
func (m *AnyAnyMap) Flip() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
n := make(map[interface{}]interface{}, len(m.data))
|
||||
for k, v := range m.data {
|
||||
n[v] = k
|
||||
}
|
||||
m.data = n
|
||||
}
|
||||
|
||||
// Merge merges two hash maps.
|
||||
// The <other> map will be merged into the map <m>.
|
||||
func (m *AnyAnyMap) Merge(other *AnyAnyMap) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if other != m {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
for k, v := range other.data {
|
||||
m.data[k] = v
|
||||
}
|
||||
}
|
||||
332
g/container/gmap/gmap_hash_int_any_map.go
Normal file
332
g/container/gmap/gmap_hash_int_any_map.go
Normal file
@ -0,0 +1,332 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with gm file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
//
|
||||
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/gvar"
|
||||
"github.com/gogf/gf/g/internal/rwmutex"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
)
|
||||
|
||||
type IntAnyMap struct {
|
||||
mu *rwmutex.RWMutex
|
||||
data map[int]interface{}
|
||||
}
|
||||
|
||||
// NewIntAnyMap returns an empty IntAnyMap object.
|
||||
// The parameter <unsafe> used to specify whether using map in un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe.
|
||||
func NewIntAnyMap(unsafe ...bool) *IntAnyMap {
|
||||
return &IntAnyMap{
|
||||
mu: rwmutex.New(unsafe...),
|
||||
data: make(map[int]interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
// NewIntAnyMapFrom returns a hash map from given map <data>.
|
||||
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
|
||||
// there might be some concurrent-safe issues when changing the map outside.
|
||||
func NewIntAnyMapFrom(data map[int]interface{}, unsafe ...bool) *IntAnyMap {
|
||||
return &IntAnyMap{
|
||||
mu: rwmutex.New(unsafe...),
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// Iterator iterates the hash map with custom callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (m *IntAnyMap) Iterator(f func(k int, v interface{}) bool) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
for k, v := range m.data {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clone returns a new hash map with copy of current map data.
|
||||
func (m *IntAnyMap) Clone() *IntAnyMap {
|
||||
return NewIntAnyMapFrom(m.Map(), !m.mu.IsSafe())
|
||||
}
|
||||
|
||||
// Map returns a copy of the data of the hash map.
|
||||
func (m *IntAnyMap) Map() map[int]interface{} {
|
||||
m.mu.RLock()
|
||||
data := make(map[int]interface{}, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return data
|
||||
}
|
||||
|
||||
// Set sets key-value to the hash map.
|
||||
func (m *IntAnyMap) Set(key int, val interface{}) {
|
||||
m.mu.Lock()
|
||||
m.data[key] = val
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the hash map.
|
||||
func (m *IntAnyMap) Sets(data map[int]interface{}) {
|
||||
m.mu.Lock()
|
||||
for k, v := range data {
|
||||
m.data[k] = v
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Search searches the map with given <key>.
|
||||
// Second return parameter <found> is true if key was found, otherwise false.
|
||||
func (m *IntAnyMap) Search(key int) (value interface{}, found bool) {
|
||||
m.mu.RLock()
|
||||
value, found = m.data[key]
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Get returns the value by given <key>.
|
||||
func (m *IntAnyMap) Get(key int) interface{} {
|
||||
m.mu.RLock()
|
||||
val, _ := m.data[key]
|
||||
m.mu.RUnlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given <key>,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// When setting value, if <value> is type of <func() interface {}>,
|
||||
// it will be executed with mutex.Lock of the hash map,
|
||||
// and its return value will be set to the map with <key>.
|
||||
//
|
||||
// It returns value with given <key>.
|
||||
func (m *IntAnyMap) doSetWithLockCheck(key int, value interface{}) interface{} {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if v, ok := m.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
if f, ok := value.(func() interface{}); ok {
|
||||
value = f()
|
||||
}
|
||||
if value != nil {
|
||||
m.data[key] = value
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or set value with given <value> if not exist and returns this value.
|
||||
func (m *IntAnyMap) GetOrSet(key int, value interface{}) interface{} {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with return value of callback function <f> if not exist and returns this value.
|
||||
func (m *IntAnyMap) GetOrSetFunc(key int, f func() interface{}) interface{} {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
// or sets value with return value of callback function <f> if not exist and returns this value.
|
||||
//
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
|
||||
// with mutex.Lock of the hash map.
|
||||
func (m *IntAnyMap) GetOrSetFuncLock(key int, f func() interface{}) interface{} {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetVar returns a gvar.Var with the value by given <key>.
|
||||
// The returned gvar.Var is un-concurrent safe.
|
||||
func (m *IntAnyMap) GetVar(key int) *gvar.Var {
|
||||
return gvar.New(m.Get(key), true)
|
||||
}
|
||||
|
||||
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
|
||||
// The returned gvar.Var is un-concurrent safe.
|
||||
func (m *IntAnyMap) GetVarOrSet(key int, value interface{}) *gvar.Var {
|
||||
return gvar.New(m.GetOrSet(key, value), true)
|
||||
}
|
||||
|
||||
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
|
||||
// The returned gvar.Var is un-concurrent safe.
|
||||
func (m *IntAnyMap) GetVarOrSetFunc(key int, f func() interface{}) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFunc(key, f), true)
|
||||
}
|
||||
|
||||
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
|
||||
// The returned gvar.Var is un-concurrent safe.
|
||||
func (m *IntAnyMap) GetVarOrSetFuncLock(key int, f func() interface{}) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFuncLock(key, f), true)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
func (m *IntAnyMap) SetIfNotExist(key int, value interface{}) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
func (m *IntAnyMap) SetIfNotExistFunc(key int, f func() interface{}) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
//
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function <f> with mutex.Lock of the hash map.
|
||||
func (m *IntAnyMap) SetIfNotExistFuncLock(key int, f func() interface{}) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the map by keys.
|
||||
func (m *IntAnyMap) Removes(keys []int) {
|
||||
m.mu.Lock()
|
||||
for _, key := range keys {
|
||||
delete(m.data, key)
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Remove deletes value from map by given <key>, and return this deleted value.
|
||||
func (m *IntAnyMap) Remove(key int) interface{} {
|
||||
m.mu.Lock()
|
||||
val, exists := m.data[key]
|
||||
if exists {
|
||||
delete(m.data, key)
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// Keys returns all keys of the map as a slice.
|
||||
func (m *IntAnyMap) Keys() []int {
|
||||
m.mu.RLock()
|
||||
keys := make([]int, len(m.data))
|
||||
index := 0
|
||||
for key := range m.data {
|
||||
keys[index] = key
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return keys
|
||||
}
|
||||
|
||||
// Values returns all values of the map as a slice.
|
||||
func (m *IntAnyMap) Values() []interface{} {
|
||||
m.mu.RLock()
|
||||
values := make([]interface{}, len(m.data))
|
||||
index := 0
|
||||
for _, value := range m.data {
|
||||
values[index] = value
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return values
|
||||
}
|
||||
|
||||
// Contains checks whether a key exists.
|
||||
// It returns true if the <key> exists, or else false.
|
||||
func (m *IntAnyMap) Contains(key int) bool {
|
||||
m.mu.RLock()
|
||||
_, exists := m.data[key]
|
||||
m.mu.RUnlock()
|
||||
return exists
|
||||
}
|
||||
|
||||
// Size returns the size of the map.
|
||||
func (m *IntAnyMap) Size() int {
|
||||
m.mu.RLock()
|
||||
length := len(m.data)
|
||||
m.mu.RUnlock()
|
||||
return length
|
||||
}
|
||||
|
||||
// IsEmpty checks whether the map is empty.
|
||||
// It returns true if map is empty, or else false.
|
||||
func (m *IntAnyMap) IsEmpty() bool {
|
||||
m.mu.RLock()
|
||||
empty := len(m.data) == 0
|
||||
m.mu.RUnlock()
|
||||
return empty
|
||||
}
|
||||
|
||||
// Clear deletes all data of the map, it will remake a new underlying data map.
|
||||
func (m *IntAnyMap) Clear() {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[int]interface{})
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// LockFunc locks writing with given callback function <f> within RWMutex.Lock.
|
||||
func (m *IntAnyMap) LockFunc(f func(m map[int]interface{})) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
f(m.data)
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with given callback function <f> within RWMutex.RLock.
|
||||
func (m *IntAnyMap) RLockFunc(f func(m map[int]interface{})) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
f(m.data)
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the map to value-key.
|
||||
func (m *IntAnyMap) Flip() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
n := make(map[int]interface{}, len(m.data))
|
||||
for k, v := range m.data {
|
||||
n[gconv.Int(v)] = k
|
||||
}
|
||||
m.data = n
|
||||
}
|
||||
|
||||
// Merge merges two hash maps.
|
||||
// The <other> map will be merged into the map <m>.
|
||||
func (m *IntAnyMap) Merge(other *IntAnyMap) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if other != m {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
for k, v := range other.data {
|
||||
m.data[k] = v
|
||||
}
|
||||
}
|
||||
308
g/container/gmap/gmap_hash_int_int_map.go
Normal file
308
g/container/gmap/gmap_hash_int_int_map.go
Normal file
@ -0,0 +1,308 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with gm file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/internal/rwmutex"
|
||||
)
|
||||
|
||||
type IntIntMap struct {
|
||||
mu *rwmutex.RWMutex
|
||||
data map[int]int
|
||||
}
|
||||
|
||||
// NewIntIntMap returns an empty IntIntMap object.
|
||||
// The parameter <unsafe> used to specify whether using map in un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe.
|
||||
func NewIntIntMap(unsafe ...bool) *IntIntMap {
|
||||
return &IntIntMap{
|
||||
mu: rwmutex.New(unsafe...),
|
||||
data: make(map[int]int),
|
||||
}
|
||||
}
|
||||
|
||||
// NewIntIntMapFrom returns a hash map from given map <data>.
|
||||
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
|
||||
// there might be some concurrent-safe issues when changing the map outside.
|
||||
func NewIntIntMapFrom(data map[int]int, unsafe ...bool) *IntIntMap {
|
||||
return &IntIntMap{
|
||||
mu: rwmutex.New(unsafe...),
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// Iterator iterates the hash map with custom callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (m *IntIntMap) Iterator(f func(k int, v int) bool) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
for k, v := range m.data {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clone returns a new hash map with copy of current map data.
|
||||
func (m *IntIntMap) Clone() *IntIntMap {
|
||||
return NewIntIntMapFrom(m.Map(), !m.mu.IsSafe())
|
||||
}
|
||||
|
||||
// Map returns a copy of the data of the hash map.
|
||||
func (m *IntIntMap) Map() map[int]int {
|
||||
m.mu.RLock()
|
||||
data := make(map[int]int, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return data
|
||||
}
|
||||
|
||||
// Set sets key-value to the hash map.
|
||||
func (m *IntIntMap) Set(key int, val int) {
|
||||
m.mu.Lock()
|
||||
m.data[key] = val
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the hash map.
|
||||
func (m *IntIntMap) Sets(data map[int]int) {
|
||||
m.mu.Lock()
|
||||
for k, v := range data {
|
||||
m.data[k] = v
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Search searches the map with given <key>.
|
||||
// Second return parameter <found> is true if key was found, otherwise false.
|
||||
func (m *IntIntMap) Search(key int) (value int, found bool) {
|
||||
m.mu.RLock()
|
||||
value, found = m.data[key]
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Get returns the value by given <key>.
|
||||
func (m *IntIntMap) Get(key int) int {
|
||||
m.mu.RLock()
|
||||
val, _ := m.data[key]
|
||||
m.mu.RUnlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given <key>,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// It returns value with given <key>.
|
||||
func (m *IntIntMap) doSetWithLockCheck(key int, value int) int {
|
||||
m.mu.Lock()
|
||||
if v, ok := m.data[key]; ok {
|
||||
m.mu.Unlock()
|
||||
return v
|
||||
}
|
||||
m.data[key] = value
|
||||
m.mu.Unlock()
|
||||
return value
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or set value with given <value> if not exist and returns this value.
|
||||
func (m *IntIntMap) GetOrSet(key int, value int) int {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with return value of callback function <f> if not exist and returns this value.
|
||||
func (m *IntIntMap) GetOrSetFunc(key int, f func() int) int {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
// or sets value with return value of callback function <f> if not exist and returns this value.
|
||||
//
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
|
||||
// with mutex.Lock of the hash map.
|
||||
func (m *IntIntMap) GetOrSetFuncLock(key int, f func() int) int {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if v, ok = m.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
v = f()
|
||||
m.data[key] = v
|
||||
return v
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
func (m *IntIntMap) SetIfNotExist(key int, value int) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
func (m *IntIntMap) SetIfNotExistFunc(key int, f func() int) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
//
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function <f> with mutex.Lock of the hash map.
|
||||
func (m *IntIntMap) SetIfNotExistFuncLock(key int, f func() int) bool {
|
||||
if !m.Contains(key) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if _, ok := m.data[key]; !ok {
|
||||
m.data[key] = f()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the map by keys.
|
||||
func (m *IntIntMap) Removes(keys []int) {
|
||||
m.mu.Lock()
|
||||
for _, key := range keys {
|
||||
delete(m.data, key)
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Remove deletes value from map by given <key>, and return this deleted value.
|
||||
func (m *IntIntMap) Remove(key int) int {
|
||||
m.mu.Lock()
|
||||
val, exists := m.data[key]
|
||||
if exists {
|
||||
delete(m.data, key)
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// Keys returns all keys of the map as a slice.
|
||||
func (m *IntIntMap) Keys() []int {
|
||||
m.mu.RLock()
|
||||
keys := make([]int, len(m.data))
|
||||
index := 0
|
||||
for key := range m.data {
|
||||
keys[index] = key
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return keys
|
||||
}
|
||||
|
||||
// Values returns all values of the map as a slice.
|
||||
func (m *IntIntMap) Values() []int {
|
||||
m.mu.RLock()
|
||||
values := make([]int, len(m.data))
|
||||
index := 0
|
||||
for _, value := range m.data {
|
||||
values[index] = value
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return values
|
||||
}
|
||||
|
||||
// Contains checks whether a key exists.
|
||||
// It returns true if the <key> exists, or else false.
|
||||
func (m *IntIntMap) Contains(key int) bool {
|
||||
m.mu.RLock()
|
||||
_, exists := m.data[key]
|
||||
m.mu.RUnlock()
|
||||
return exists
|
||||
}
|
||||
|
||||
// Size returns the size of the map.
|
||||
func (m *IntIntMap) Size() int {
|
||||
m.mu.RLock()
|
||||
length := len(m.data)
|
||||
m.mu.RUnlock()
|
||||
return length
|
||||
}
|
||||
|
||||
// IsEmpty checks whether the map is empty.
|
||||
// It returns true if map is empty, or else false.
|
||||
func (m *IntIntMap) IsEmpty() bool {
|
||||
m.mu.RLock()
|
||||
empty := len(m.data) == 0
|
||||
m.mu.RUnlock()
|
||||
return empty
|
||||
}
|
||||
|
||||
// Clear deletes all data of the map, it will remake a new underlying data map.
|
||||
func (m *IntIntMap) Clear() {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[int]int)
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// LockFunc locks writing with given callback function <f> within RWMutex.Lock.
|
||||
func (m *IntIntMap) LockFunc(f func(m map[int]int)) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
f(m.data)
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with given callback function <f> within RWMutex.RLock.
|
||||
func (m *IntIntMap) RLockFunc(f func(m map[int]int)) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
f(m.data)
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the map to value-key.
|
||||
func (m *IntIntMap) Flip() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
n := make(map[int]int, len(m.data))
|
||||
for k, v := range m.data {
|
||||
n[v] = k
|
||||
}
|
||||
m.data = n
|
||||
}
|
||||
|
||||
// Merge merges two hash maps.
|
||||
// The <other> map will be merged into the map <m>.
|
||||
func (m *IntIntMap) Merge(other *IntIntMap) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if other != m {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
for k, v := range other.data {
|
||||
m.data[k] = v
|
||||
}
|
||||
}
|
||||
309
g/container/gmap/gmap_hash_int_str_map.go
Normal file
309
g/container/gmap/gmap_hash_int_str_map.go
Normal file
@ -0,0 +1,309 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with gm file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/internal/rwmutex"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
)
|
||||
|
||||
type IntStrMap struct {
|
||||
mu *rwmutex.RWMutex
|
||||
data map[int]string
|
||||
}
|
||||
|
||||
// NewIntStrMap returns an empty IntStrMap object.
|
||||
// The parameter <unsafe> used to specify whether using map in un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe.
|
||||
func NewIntStrMap(unsafe ...bool) *IntStrMap {
|
||||
return &IntStrMap{
|
||||
mu: rwmutex.New(unsafe...),
|
||||
data: make(map[int]string),
|
||||
}
|
||||
}
|
||||
|
||||
// NewIntStrMapFrom returns a hash map from given map <data>.
|
||||
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
|
||||
// there might be some concurrent-safe issues when changing the map outside.
|
||||
func NewIntStrMapFrom(data map[int]string, unsafe ...bool) *IntStrMap {
|
||||
return &IntStrMap{
|
||||
mu: rwmutex.New(unsafe...),
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// Iterator iterates the hash map with custom callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (m *IntStrMap) Iterator(f func(k int, v string) bool) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
for k, v := range m.data {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clone returns a new hash map with copy of current map data.
|
||||
func (m *IntStrMap) Clone() *IntStrMap {
|
||||
return NewIntStrMapFrom(m.Map(), !m.mu.IsSafe())
|
||||
}
|
||||
|
||||
// Map returns a copy of the data of the hash map.
|
||||
func (m *IntStrMap) Map() map[int]string {
|
||||
m.mu.RLock()
|
||||
data := make(map[int]string, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return data
|
||||
}
|
||||
|
||||
// Set sets key-value to the hash map.
|
||||
func (m *IntStrMap) Set(key int, val string) {
|
||||
m.mu.Lock()
|
||||
m.data[key] = val
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the hash map.
|
||||
func (m *IntStrMap) Sets(data map[int]string) {
|
||||
m.mu.Lock()
|
||||
for k, v := range data {
|
||||
m.data[k] = v
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Search searches the map with given <key>.
|
||||
// Second return parameter <found> is true if key was found, otherwise false.
|
||||
func (m *IntStrMap) Search(key int) (value string, found bool) {
|
||||
m.mu.RLock()
|
||||
value, found = m.data[key]
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Get returns the value by given <key>.
|
||||
func (m *IntStrMap) Get(key int) string {
|
||||
m.mu.RLock()
|
||||
val, _ := m.data[key]
|
||||
m.mu.RUnlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given <key>,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// It returns value with given <key>.
|
||||
func (m *IntStrMap) doSetWithLockCheck(key int, value string) string {
|
||||
m.mu.Lock()
|
||||
if v, ok := m.data[key]; ok {
|
||||
m.mu.Unlock()
|
||||
return v
|
||||
}
|
||||
m.data[key] = value
|
||||
m.mu.Unlock()
|
||||
return value
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or set value with given <value> if not exist and returns this value.
|
||||
func (m *IntStrMap) GetOrSet(key int, value string) string {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with return value of callback function <f> if not exist and returns this value.
|
||||
func (m *IntStrMap) GetOrSetFunc(key int, f func() string) string {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
// or sets value with return value of callback function <f> if not exist and returns this value.
|
||||
//
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
|
||||
// with mutex.Lock of the hash map.
|
||||
func (m *IntStrMap) GetOrSetFuncLock(key int, f func() string) string {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if v, ok = m.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
v = f()
|
||||
m.data[key] = v
|
||||
return v
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
func (m *IntStrMap) SetIfNotExist(key int, value string) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
func (m *IntStrMap) SetIfNotExistFunc(key int, f func() string) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
//
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function <f> with mutex.Lock of the hash map.
|
||||
func (m *IntStrMap) SetIfNotExistFuncLock(key int, f func() string) bool {
|
||||
if !m.Contains(key) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if _, ok := m.data[key]; !ok {
|
||||
m.data[key] = f()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the map by keys.
|
||||
func (m *IntStrMap) Removes(keys []int) {
|
||||
m.mu.Lock()
|
||||
for _, key := range keys {
|
||||
delete(m.data, key)
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Remove deletes value from map by given <key>, and return this deleted value.
|
||||
func (m *IntStrMap) Remove(key int) string {
|
||||
m.mu.Lock()
|
||||
val, exists := m.data[key]
|
||||
if exists {
|
||||
delete(m.data, key)
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// Keys returns all keys of the map as a slice.
|
||||
func (m *IntStrMap) Keys() []int {
|
||||
m.mu.RLock()
|
||||
keys := make([]int, len(m.data))
|
||||
index := 0
|
||||
for key := range m.data {
|
||||
keys[index] = key
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return keys
|
||||
}
|
||||
|
||||
// Values returns all values of the map as a slice.
|
||||
func (m *IntStrMap) Values() []string {
|
||||
m.mu.RLock()
|
||||
values := make([]string, len(m.data))
|
||||
index := 0
|
||||
for _, value := range m.data {
|
||||
values[index] = value
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return values
|
||||
}
|
||||
|
||||
// Contains checks whether a key exists.
|
||||
// It returns true if the <key> exists, or else false.
|
||||
func (m *IntStrMap) Contains(key int) bool {
|
||||
m.mu.RLock()
|
||||
_, exists := m.data[key]
|
||||
m.mu.RUnlock()
|
||||
return exists
|
||||
}
|
||||
|
||||
// Size returns the size of the map.
|
||||
func (m *IntStrMap) Size() int {
|
||||
m.mu.RLock()
|
||||
length := len(m.data)
|
||||
m.mu.RUnlock()
|
||||
return length
|
||||
}
|
||||
|
||||
// IsEmpty checks whether the map is empty.
|
||||
// It returns true if map is empty, or else false.
|
||||
func (m *IntStrMap) IsEmpty() bool {
|
||||
m.mu.RLock()
|
||||
empty := len(m.data) == 0
|
||||
m.mu.RUnlock()
|
||||
return empty
|
||||
}
|
||||
|
||||
// Clear deletes all data of the map, it will remake a new underlying data map.
|
||||
func (m *IntStrMap) Clear() {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[int]string)
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// LockFunc locks writing with given callback function <f> within RWMutex.Lock.
|
||||
func (m *IntStrMap) LockFunc(f func(m map[int]string)) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
f(m.data)
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with given callback function <f> within RWMutex.RLock.
|
||||
func (m *IntStrMap) RLockFunc(f func(m map[int]string)) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
f(m.data)
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the map to value-key.
|
||||
func (m *IntStrMap) Flip() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
n := make(map[int]string, len(m.data))
|
||||
for k, v := range m.data {
|
||||
n[gconv.Int(v)] = gconv.String(k)
|
||||
}
|
||||
m.data = n
|
||||
}
|
||||
|
||||
// Merge merges two hash maps.
|
||||
// The <other> map will be merged into the map <m>.
|
||||
func (m *IntStrMap) Merge(other *IntStrMap) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if other != m {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
for k, v := range other.data {
|
||||
m.data[k] = v
|
||||
}
|
||||
}
|
||||
334
g/container/gmap/gmap_hash_str_any_map.go
Normal file
334
g/container/gmap/gmap_hash_str_any_map.go
Normal file
@ -0,0 +1,334 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with gm file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
//
|
||||
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/gvar"
|
||||
"github.com/gogf/gf/g/internal/rwmutex"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
)
|
||||
|
||||
type StrAnyMap struct {
|
||||
mu *rwmutex.RWMutex
|
||||
data map[string]interface{}
|
||||
}
|
||||
|
||||
// NewStrAnyMap returns an empty StrAnyMap object.
|
||||
// The parameter <unsafe> used to specify whether using map in un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe.
|
||||
func NewStrAnyMap(unsafe ...bool) *StrAnyMap {
|
||||
return &StrAnyMap{
|
||||
mu: rwmutex.New(unsafe...),
|
||||
data: make(map[string]interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
// NewStrAnyMapFrom returns a hash map from given map <data>.
|
||||
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
|
||||
// there might be some concurrent-safe issues when changing the map outside.
|
||||
func NewStrAnyMapFrom(data map[string]interface{}, unsafe ...bool) *StrAnyMap {
|
||||
return &StrAnyMap{
|
||||
mu: rwmutex.New(unsafe...),
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// Iterator iterates the hash map with custom callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (m *StrAnyMap) Iterator(f func(k string, v interface{}) bool) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
for k, v := range m.data {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clone returns a new hash map with copy of current map data.
|
||||
func (m *StrAnyMap) Clone() *StrAnyMap {
|
||||
return NewStrAnyMapFrom(m.Map(), !m.mu.IsSafe())
|
||||
}
|
||||
|
||||
// Map returns a copy of the data of the hash map.
|
||||
func (m *StrAnyMap) Map() map[string]interface{} {
|
||||
m.mu.RLock()
|
||||
data := make(map[string]interface{}, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return data
|
||||
}
|
||||
|
||||
// Set sets key-value to the hash map.
|
||||
func (m *StrAnyMap) Set(key string, val interface{}) {
|
||||
m.mu.Lock()
|
||||
m.data[key] = val
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the hash map.
|
||||
func (m *StrAnyMap) Sets(data map[string]interface{}) {
|
||||
m.mu.Lock()
|
||||
for k, v := range data {
|
||||
m.data[k] = v
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Search searches the map with given <key>.
|
||||
// Second return parameter <found> is true if key was found, otherwise false.
|
||||
func (m *StrAnyMap) Search(key string) (value interface{}, found bool) {
|
||||
m.mu.RLock()
|
||||
value, found = m.data[key]
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Get returns the value by given <key>.
|
||||
func (m *StrAnyMap) Get(key string) interface{} {
|
||||
m.mu.RLock()
|
||||
val, _ := m.data[key]
|
||||
m.mu.RUnlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given <key>,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// When setting value, if <value> is type of <func() interface {}>,
|
||||
// it will be executed with mutex.Lock of the hash map,
|
||||
// and its return value will be set to the map with <key>.
|
||||
//
|
||||
// It returns value with given <key>.
|
||||
func (m *StrAnyMap) doSetWithLockCheck(key string, value interface{}) interface{} {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if v, ok := m.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
if f, ok := value.(func() interface{}); ok {
|
||||
value = f()
|
||||
}
|
||||
if value != nil {
|
||||
m.data[key] = value
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or set value with given <value> if not exist and returns this value.
|
||||
func (m *StrAnyMap) GetOrSet(key string, value interface{}) interface{} {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with return value of callback function <f> if not exist
|
||||
// and returns this value.
|
||||
func (m *StrAnyMap) GetOrSetFunc(key string, f func() interface{}) interface{} {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
// or sets value with return value of callback function <f> if not exist
|
||||
// and returns this value.
|
||||
//
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
|
||||
// with mutex.Lock of the hash map.
|
||||
func (m *StrAnyMap) GetOrSetFuncLock(key string, f func() interface{}) interface{} {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetVar returns a gvar.Var with the value by given <key>.
|
||||
// The returned gvar.Var is un-concurrent safe.
|
||||
func (m *StrAnyMap) GetVar(key string) *gvar.Var {
|
||||
return gvar.New(m.Get(key), true)
|
||||
}
|
||||
|
||||
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
|
||||
// The returned gvar.Var is un-concurrent safe.
|
||||
func (m *StrAnyMap) GetVarOrSet(key string, value interface{}) *gvar.Var {
|
||||
return gvar.New(m.GetOrSet(key, value), true)
|
||||
}
|
||||
|
||||
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
|
||||
// The returned gvar.Var is un-concurrent safe.
|
||||
func (m *StrAnyMap) GetVarOrSetFunc(key string, f func() interface{}) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFunc(key, f), true)
|
||||
}
|
||||
|
||||
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
|
||||
// The returned gvar.Var is un-concurrent safe.
|
||||
func (m *StrAnyMap) GetVarOrSetFuncLock(key string, f func() interface{}) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFuncLock(key, f), true)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
func (m *StrAnyMap) SetIfNotExist(key string, value interface{}) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
func (m *StrAnyMap) SetIfNotExistFunc(key string, f func() interface{}) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
//
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function <f> with mutex.Lock of the hash map.
|
||||
func (m *StrAnyMap) SetIfNotExistFuncLock(key string, f func() interface{}) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the map by keys.
|
||||
func (m *StrAnyMap) Removes(keys []string) {
|
||||
m.mu.Lock()
|
||||
for _, key := range keys {
|
||||
delete(m.data, key)
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Remove deletes value from map by given <key>, and return this deleted value.
|
||||
func (m *StrAnyMap) Remove(key string) interface{} {
|
||||
m.mu.Lock()
|
||||
val, exists := m.data[key]
|
||||
if exists {
|
||||
delete(m.data, key)
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// Keys returns all keys of the map as a slice.
|
||||
func (m *StrAnyMap) Keys() []string {
|
||||
m.mu.RLock()
|
||||
keys := make([]string, len(m.data))
|
||||
index := 0
|
||||
for key := range m.data {
|
||||
keys[index] = key
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return keys
|
||||
}
|
||||
|
||||
// Values returns all values of the map as a slice.
|
||||
func (m *StrAnyMap) Values() []interface{} {
|
||||
m.mu.RLock()
|
||||
values := make([]interface{}, len(m.data))
|
||||
index := 0
|
||||
for _, value := range m.data {
|
||||
values[index] = value
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return values
|
||||
}
|
||||
|
||||
// Contains checks whether a key exists.
|
||||
// It returns true if the <key> exists, or else false.
|
||||
func (m *StrAnyMap) Contains(key string) bool {
|
||||
m.mu.RLock()
|
||||
_, exists := m.data[key]
|
||||
m.mu.RUnlock()
|
||||
return exists
|
||||
}
|
||||
|
||||
// Size returns the size of the map.
|
||||
func (m *StrAnyMap) Size() int {
|
||||
m.mu.RLock()
|
||||
length := len(m.data)
|
||||
m.mu.RUnlock()
|
||||
return length
|
||||
}
|
||||
|
||||
// IsEmpty checks whether the map is empty.
|
||||
// It returns true if map is empty, or else false.
|
||||
func (m *StrAnyMap) IsEmpty() bool {
|
||||
m.mu.RLock()
|
||||
empty := len(m.data) == 0
|
||||
m.mu.RUnlock()
|
||||
return empty
|
||||
}
|
||||
|
||||
// Clear deletes all data of the map, it will remake a new underlying data map.
|
||||
func (m *StrAnyMap) Clear() {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[string]interface{})
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// LockFunc locks writing with given callback function <f> within RWMutex.Lock.
|
||||
func (m *StrAnyMap) LockFunc(f func(m map[string]interface{})) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
f(m.data)
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with given callback function <f> within RWMutex.RLock.
|
||||
func (m *StrAnyMap) RLockFunc(f func(m map[string]interface{})) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
f(m.data)
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the map to value-key.
|
||||
func (m *StrAnyMap) Flip() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
n := make(map[string]interface{}, len(m.data))
|
||||
for k, v := range m.data {
|
||||
n[gconv.String(v)] = k
|
||||
}
|
||||
m.data = n
|
||||
}
|
||||
|
||||
// Merge merges two hash maps.
|
||||
// The <other> map will be merged into the map <m>.
|
||||
func (m *StrAnyMap) Merge(other *StrAnyMap) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if other != m {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
for k, v := range other.data {
|
||||
m.data[k] = v
|
||||
}
|
||||
}
|
||||
312
g/container/gmap/gmap_hash_str_int_map.go
Normal file
312
g/container/gmap/gmap_hash_str_int_map.go
Normal file
@ -0,0 +1,312 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with gm file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
//
|
||||
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/internal/rwmutex"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
)
|
||||
|
||||
type StrIntMap struct {
|
||||
mu *rwmutex.RWMutex
|
||||
data map[string]int
|
||||
}
|
||||
|
||||
// NewStrIntMap returns an empty StrIntMap object.
|
||||
// The parameter <unsafe> used to specify whether using map in un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe.
|
||||
func NewStrIntMap(unsafe ...bool) *StrIntMap {
|
||||
return &StrIntMap{
|
||||
mu: rwmutex.New(unsafe...),
|
||||
data: make(map[string]int),
|
||||
}
|
||||
}
|
||||
|
||||
// NewStrIntMapFrom returns a hash map from given map <data>.
|
||||
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
|
||||
// there might be some concurrent-safe issues when changing the map outside.
|
||||
func NewStrIntMapFrom(data map[string]int, unsafe ...bool) *StrIntMap {
|
||||
return &StrIntMap{
|
||||
mu: rwmutex.New(unsafe...),
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// Iterator iterates the hash map with custom callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (m *StrIntMap) Iterator(f func(k string, v int) bool) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
for k, v := range m.data {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clone returns a new hash map with copy of current map data.
|
||||
func (m *StrIntMap) Clone() *StrIntMap {
|
||||
return NewStrIntMapFrom(m.Map(), !m.mu.IsSafe())
|
||||
}
|
||||
|
||||
// Map returns a copy of the data of the hash map.
|
||||
func (m *StrIntMap) Map() map[string]int {
|
||||
m.mu.RLock()
|
||||
data := make(map[string]int, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return data
|
||||
}
|
||||
|
||||
// Set sets key-value to the hash map.
|
||||
func (m *StrIntMap) Set(key string, val int) {
|
||||
m.mu.Lock()
|
||||
m.data[key] = val
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the hash map.
|
||||
func (m *StrIntMap) Sets(data map[string]int) {
|
||||
m.mu.Lock()
|
||||
for k, v := range data {
|
||||
m.data[k] = v
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Search searches the map with given <key>.
|
||||
// Second return parameter <found> is true if key was found, otherwise false.
|
||||
func (m *StrIntMap) Search(key string) (value int, found bool) {
|
||||
m.mu.RLock()
|
||||
value, found = m.data[key]
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Get returns the value by given <key>.
|
||||
func (m *StrIntMap) Get(key string) int {
|
||||
m.mu.RLock()
|
||||
val, _ := m.data[key]
|
||||
m.mu.RUnlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given <key>,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// It returns value with given <key>.
|
||||
func (m *StrIntMap) doSetWithLockCheck(key string, value int) int {
|
||||
m.mu.Lock()
|
||||
if v, ok := m.data[key]; ok {
|
||||
m.mu.Unlock()
|
||||
return v
|
||||
}
|
||||
m.data[key] = value
|
||||
m.mu.Unlock()
|
||||
return value
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or set value with given <value> if not exist and returns this value.
|
||||
func (m *StrIntMap) GetOrSet(key string, value int) int {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with return value of callback function <f> if not exist
|
||||
// and returns this value.
|
||||
func (m *StrIntMap) GetOrSetFunc(key string, f func() int) int {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
// or sets value with return value of callback function <f> if not exist
|
||||
// and returns this value.
|
||||
//
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
|
||||
// with mutex.Lock of the hash map.
|
||||
func (m *StrIntMap) GetOrSetFuncLock(key string, f func() int) int {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if v, ok = m.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
v = f()
|
||||
m.data[key] = v
|
||||
return v
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
func (m *StrIntMap) SetIfNotExist(key string, value int) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
func (m *StrIntMap) SetIfNotExistFunc(key string, f func() int) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
//
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function <f> with mutex.Lock of the hash map.
|
||||
func (m *StrIntMap) SetIfNotExistFuncLock(key string, f func() int) bool {
|
||||
if !m.Contains(key) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if _, ok := m.data[key]; !ok {
|
||||
m.data[key] = f()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the map by keys.
|
||||
func (m *StrIntMap) Removes(keys []string) {
|
||||
m.mu.Lock()
|
||||
for _, key := range keys {
|
||||
delete(m.data, key)
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Remove deletes value from map by given <key>, and return this deleted value.
|
||||
func (m *StrIntMap) Remove(key string) int {
|
||||
m.mu.Lock()
|
||||
val, exists := m.data[key]
|
||||
if exists {
|
||||
delete(m.data, key)
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// Keys returns all keys of the map as a slice.
|
||||
func (m *StrIntMap) Keys() []string {
|
||||
m.mu.RLock()
|
||||
keys := make([]string, len(m.data))
|
||||
index := 0
|
||||
for key := range m.data {
|
||||
keys[index] = key
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return keys
|
||||
}
|
||||
|
||||
// Values returns all values of the map as a slice.
|
||||
func (m *StrIntMap) Values() []int {
|
||||
m.mu.RLock()
|
||||
values := make([]int, len(m.data))
|
||||
index := 0
|
||||
for _, value := range m.data {
|
||||
values[index] = value
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return values
|
||||
}
|
||||
|
||||
// Contains checks whether a key exists.
|
||||
// It returns true if the <key> exists, or else false.
|
||||
func (m *StrIntMap) Contains(key string) bool {
|
||||
m.mu.RLock()
|
||||
_, exists := m.data[key]
|
||||
m.mu.RUnlock()
|
||||
return exists
|
||||
}
|
||||
|
||||
// Size returns the size of the map.
|
||||
func (m *StrIntMap) Size() int {
|
||||
m.mu.RLock()
|
||||
length := len(m.data)
|
||||
m.mu.RUnlock()
|
||||
return length
|
||||
}
|
||||
|
||||
// IsEmpty checks whether the map is empty.
|
||||
// It returns true if map is empty, or else false.
|
||||
func (m *StrIntMap) IsEmpty() bool {
|
||||
m.mu.RLock()
|
||||
empty := len(m.data) == 0
|
||||
m.mu.RUnlock()
|
||||
return empty
|
||||
}
|
||||
|
||||
// Clear deletes all data of the map, it will remake a new underlying data map.
|
||||
func (m *StrIntMap) Clear() {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[string]int)
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// LockFunc locks writing with given callback function <f> within RWMutex.Lock.
|
||||
func (m *StrIntMap) LockFunc(f func(m map[string]int)) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
f(m.data)
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with given callback function <f> within RWMutex.RLock.
|
||||
func (m *StrIntMap) RLockFunc(f func(m map[string]int)) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
f(m.data)
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the map to value-key.
|
||||
func (m *StrIntMap) Flip() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
n := make(map[string]int, len(m.data))
|
||||
for k, v := range m.data {
|
||||
n[gconv.String(v)] = gconv.Int(k)
|
||||
}
|
||||
m.data = n
|
||||
}
|
||||
|
||||
// Merge merges two hash maps.
|
||||
// The <other> map will be merged into the map <m>.
|
||||
func (m *StrIntMap) Merge(other *StrIntMap) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if other != m {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
for k, v := range other.data {
|
||||
m.data[k] = v
|
||||
}
|
||||
}
|
||||
311
g/container/gmap/gmap_hash_str_str_map.go
Normal file
311
g/container/gmap/gmap_hash_str_str_map.go
Normal file
@ -0,0 +1,311 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with gm file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
//
|
||||
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/internal/rwmutex"
|
||||
)
|
||||
|
||||
type StrStrMap struct {
|
||||
mu *rwmutex.RWMutex
|
||||
data map[string]string
|
||||
}
|
||||
|
||||
// NewStrStrMap returns an empty StrStrMap object.
|
||||
// The parameter <unsafe> used to specify whether using map in un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe.
|
||||
func NewStrStrMap(unsafe ...bool) *StrStrMap {
|
||||
return &StrStrMap{
|
||||
data: make(map[string]string),
|
||||
mu: rwmutex.New(unsafe...),
|
||||
}
|
||||
}
|
||||
|
||||
// NewStrStrMapFrom returns a hash map from given map <data>.
|
||||
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
|
||||
// there might be some concurrent-safe issues when changing the map outside.
|
||||
func NewStrStrMapFrom(data map[string]string, unsafe ...bool) *StrStrMap {
|
||||
return &StrStrMap{
|
||||
mu: rwmutex.New(unsafe...),
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// Iterator iterates the hash map with custom callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (m *StrStrMap) Iterator(f func(k string, v string) bool) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
for k, v := range m.data {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clone returns a new hash map with copy of current map data.
|
||||
func (m *StrStrMap) Clone() *StrStrMap {
|
||||
return NewStrStrMapFrom(m.Map(), !m.mu.IsSafe())
|
||||
}
|
||||
|
||||
// Map returns a copy of the data of the hash map.
|
||||
func (m *StrStrMap) Map() map[string]string {
|
||||
m.mu.RLock()
|
||||
data := make(map[string]string, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return data
|
||||
}
|
||||
|
||||
// Set sets key-value to the hash map.
|
||||
func (m *StrStrMap) Set(key string, val string) {
|
||||
m.mu.Lock()
|
||||
m.data[key] = val
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the hash map.
|
||||
func (m *StrStrMap) Sets(data map[string]string) {
|
||||
m.mu.Lock()
|
||||
for k, v := range data {
|
||||
m.data[k] = v
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Search searches the map with given <key>.
|
||||
// Second return parameter <found> is true if key was found, otherwise false.
|
||||
func (m *StrStrMap) Search(key string) (value string, found bool) {
|
||||
m.mu.RLock()
|
||||
value, found = m.data[key]
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Get returns the value by given <key>.
|
||||
func (m *StrStrMap) Get(key string) string {
|
||||
m.mu.RLock()
|
||||
val, _ := m.data[key]
|
||||
m.mu.RUnlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given <key>,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// It returns value with given <key>.
|
||||
func (m *StrStrMap) doSetWithLockCheck(key string, value string) string {
|
||||
m.mu.Lock()
|
||||
if v, ok := m.data[key]; ok {
|
||||
m.mu.Unlock()
|
||||
return v
|
||||
}
|
||||
m.data[key] = value
|
||||
m.mu.Unlock()
|
||||
return value
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or set value with given <value> if not exist and returns this value.
|
||||
func (m *StrStrMap) GetOrSet(key string, value string) string {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with return value of callback function <f> if not exist
|
||||
// and returns this value.
|
||||
func (m *StrStrMap) GetOrSetFunc(key string, f func() string) string {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
// or sets value with return value of callback function <f> if not exist
|
||||
// and returns this value.
|
||||
//
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
|
||||
// with mutex.Lock of the hash map.
|
||||
func (m *StrStrMap) GetOrSetFuncLock(key string, f func() string) string {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if v, ok = m.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
v = f()
|
||||
m.data[key] = v
|
||||
return v
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
func (m *StrStrMap) SetIfNotExist(key string, value string) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
func (m *StrStrMap) SetIfNotExistFunc(key string, f func() string) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
//
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function <f> with mutex.Lock of the hash map.
|
||||
func (m *StrStrMap) SetIfNotExistFuncLock(key string, f func() string) bool {
|
||||
if !m.Contains(key) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if _, ok := m.data[key]; !ok {
|
||||
m.data[key] = f()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the map by keys.
|
||||
func (m *StrStrMap) Removes(keys []string) {
|
||||
m.mu.Lock()
|
||||
for _, key := range keys {
|
||||
delete(m.data, key)
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Remove deletes value from map by given <key>, and return this deleted value.
|
||||
func (m *StrStrMap) Remove(key string) string {
|
||||
m.mu.Lock()
|
||||
val, exists := m.data[key]
|
||||
if exists {
|
||||
delete(m.data, key)
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// Keys returns all keys of the map as a slice.
|
||||
func (m *StrStrMap) Keys() []string {
|
||||
m.mu.RLock()
|
||||
keys := make([]string, len(m.data))
|
||||
index := 0
|
||||
for key := range m.data {
|
||||
keys[index] = key
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return keys
|
||||
}
|
||||
|
||||
// Values returns all values of the map as a slice.
|
||||
func (m *StrStrMap) Values() []string {
|
||||
m.mu.RLock()
|
||||
values := make([]string, len(m.data))
|
||||
index := 0
|
||||
for _, value := range m.data {
|
||||
values[index] = value
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return values
|
||||
}
|
||||
|
||||
// Contains checks whether a key exists.
|
||||
// It returns true if the <key> exists, or else false.
|
||||
func (m *StrStrMap) Contains(key string) bool {
|
||||
m.mu.RLock()
|
||||
_, exists := m.data[key]
|
||||
m.mu.RUnlock()
|
||||
return exists
|
||||
}
|
||||
|
||||
// Size returns the size of the map.
|
||||
func (m *StrStrMap) Size() int {
|
||||
m.mu.RLock()
|
||||
length := len(m.data)
|
||||
m.mu.RUnlock()
|
||||
return length
|
||||
}
|
||||
|
||||
// IsEmpty checks whether the map is empty.
|
||||
// It returns true if map is empty, or else false.
|
||||
func (m *StrStrMap) IsEmpty() bool {
|
||||
m.mu.RLock()
|
||||
empty := len(m.data) == 0
|
||||
m.mu.RUnlock()
|
||||
return empty
|
||||
}
|
||||
|
||||
// Clear deletes all data of the map, it will remake a new underlying data map.
|
||||
func (m *StrStrMap) Clear() {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[string]string)
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// LockFunc locks writing with given callback function <f> within RWMutex.Lock.
|
||||
func (m *StrStrMap) LockFunc(f func(m map[string]string)) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
f(m.data)
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with given callback function <f> within RWMutex.RLock.
|
||||
func (m *StrStrMap) RLockFunc(f func(m map[string]string)) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
f(m.data)
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the map to value-key.
|
||||
func (m *StrStrMap) Flip() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
n := make(map[string]string, len(m.data))
|
||||
for k, v := range m.data {
|
||||
n[v] = k
|
||||
}
|
||||
m.data = n
|
||||
}
|
||||
|
||||
// Merge merges two hash maps.
|
||||
// The <other> map will be merged into the map <m>.
|
||||
func (m *StrStrMap) Merge(other *StrStrMap) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if other != m {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
for k, v := range other.data {
|
||||
m.data[k] = v
|
||||
}
|
||||
}
|
||||
@ -1,223 +0,0 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
//
|
||||
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
)
|
||||
|
||||
type IntBoolMap struct {
|
||||
m map[int]bool
|
||||
mu *rwmutex.RWMutex
|
||||
}
|
||||
|
||||
func NewIntBoolMap(safe...bool) *IntBoolMap {
|
||||
return &IntBoolMap{
|
||||
m : make(map[int]bool),
|
||||
mu : rwmutex.New(safe...),
|
||||
}
|
||||
}
|
||||
|
||||
// 哈希表克隆
|
||||
func (this *IntBoolMap) Clone() map[int]bool {
|
||||
m := make(map[int]bool)
|
||||
this.mu.RLock()
|
||||
for k, v := range this.m {
|
||||
m[k] = v
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
return m
|
||||
}
|
||||
|
||||
// 给定回调函数对原始内容进行遍历,回调函数返回true表示继续遍历,否则停止遍历
|
||||
func (this *IntBoolMap) Iterator(f func (k int, v bool) bool) {
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
for k, v := range this.m {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 设置键值对
|
||||
func (this *IntBoolMap) Set(key int, val bool) {
|
||||
this.mu.Lock()
|
||||
this.m[key] = val
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 批量设置键值对
|
||||
func (this *IntBoolMap) BatchSet(m map[int]bool) {
|
||||
this.mu.Lock()
|
||||
for k, v := range m {
|
||||
this.m[k] = v
|
||||
}
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 获取键值
|
||||
func (this *IntBoolMap) Get(key int) bool {
|
||||
this.mu.RLock()
|
||||
val, _ := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// 设置kv缓存键值对,内部会对键名的存在性使用写锁进行二次检索确认,如果存在则不再写入;返回键名对应的键值。
|
||||
// 在高并发下有用,防止数据写入的并发逻辑错误。
|
||||
func (this *IntBoolMap) doSetWithLockCheck(key int, value bool) bool {
|
||||
this.mu.Lock()
|
||||
if v, ok := this.m[key]; ok {
|
||||
this.mu.Unlock()
|
||||
return v
|
||||
}
|
||||
this.m[key] = value
|
||||
this.mu.Unlock()
|
||||
return value
|
||||
}
|
||||
|
||||
// 当键名存在时返回其键值,否则写入指定的键值
|
||||
func (this *IntBoolMap) GetOrSet(key int, value bool) bool {
|
||||
this.mu.RLock()
|
||||
v, ok := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
if !ok {
|
||||
return this.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// 当键名存在时返回其键值,否则写入指定的键值,键值由指定的函数生成
|
||||
func (this *IntBoolMap) GetOrSetFunc(key int, f func() bool) bool {
|
||||
this.mu.RLock()
|
||||
v, ok := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
if !ok {
|
||||
return this.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// 与GetOrSetFunc不同的是,f是在写锁机制内执行
|
||||
func (this *IntBoolMap) GetOrSetFuncLock(key int, f func() bool) bool {
|
||||
this.mu.RLock()
|
||||
val, ok := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
if !ok {
|
||||
this.mu.Lock()
|
||||
defer this.mu.Unlock()
|
||||
if v, ok := this.m[key]; ok {
|
||||
this.mu.Unlock()
|
||||
return v
|
||||
}
|
||||
val = f()
|
||||
this.m[key] = val
|
||||
return val
|
||||
} else {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
// 当键名不存在时写入,并返回true;否则返回false。
|
||||
func (this *IntBoolMap) SetIfNotExist(key int, value bool) bool {
|
||||
if !this.Contains(key) {
|
||||
this.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 批量删除键值对
|
||||
func (this *IntBoolMap) BatchRemove(keys []int) {
|
||||
this.mu.Lock()
|
||||
for _, key := range keys {
|
||||
delete(this.m, key)
|
||||
}
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 返回对应的键值,并删除该键值
|
||||
func (this *IntBoolMap) Remove(key int) bool {
|
||||
this.mu.Lock()
|
||||
val, exists := this.m[key]
|
||||
if exists {
|
||||
delete(this.m, key)
|
||||
}
|
||||
this.mu.Unlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// 返回键列表
|
||||
func (this *IntBoolMap) Keys() []int {
|
||||
this.mu.RLock()
|
||||
keys := make([]int, 0)
|
||||
for key, _ := range this.m {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
return keys
|
||||
}
|
||||
|
||||
// 返回值列表(注意是随机排序)
|
||||
//func (this *IntBoolMap) Values() []bool {
|
||||
// this.mu.RLock()
|
||||
// vals := make([]bool, 0)
|
||||
// for _, val := range this.m {
|
||||
// vals = append(vals, val)
|
||||
// }
|
||||
// this.mu.RUnlock()
|
||||
// return vals
|
||||
//}
|
||||
|
||||
// 是否存在某个键
|
||||
func (this *IntBoolMap) Contains(key int) bool {
|
||||
this.mu.RLock()
|
||||
_, exists := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
return exists
|
||||
}
|
||||
|
||||
// 哈希表大小
|
||||
func (this *IntBoolMap) Size() int {
|
||||
this.mu.RLock()
|
||||
length := len(this.m)
|
||||
this.mu.RUnlock()
|
||||
return length
|
||||
}
|
||||
|
||||
// 哈希表是否为空
|
||||
func (this *IntBoolMap) IsEmpty() bool {
|
||||
this.mu.RLock()
|
||||
empty := len(this.m) == 0
|
||||
this.mu.RUnlock()
|
||||
return empty
|
||||
}
|
||||
|
||||
// 清空哈希表
|
||||
func (this *IntBoolMap) Clear() {
|
||||
this.mu.Lock()
|
||||
this.m = make(map[int]bool)
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 并发安全锁操作,使用自定义方法执行加锁修改操作
|
||||
func (this *IntBoolMap) LockFunc(f func(m map[int]bool)) {
|
||||
this.mu.Lock(true)
|
||||
defer this.mu.Unlock(true)
|
||||
f(this.m)
|
||||
}
|
||||
|
||||
// 并发安全锁操作,使用自定义方法执行加锁读取操作
|
||||
func (this *IntBoolMap) RLockFunc(f func(m map[int]bool)) {
|
||||
this.mu.RLock(true)
|
||||
defer this.mu.RUnlock(true)
|
||||
f(this.m)
|
||||
}
|
||||
@ -1,223 +0,0 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
)
|
||||
|
||||
type IntIntMap struct {
|
||||
mu *rwmutex.RWMutex
|
||||
m map[int]int
|
||||
}
|
||||
|
||||
func NewIntIntMap(safe...bool) *IntIntMap {
|
||||
return &IntIntMap{
|
||||
m : make(map[int]int),
|
||||
mu : rwmutex.New(safe...),
|
||||
}
|
||||
}
|
||||
|
||||
// 给定回调函数对原始内容进行遍历,回调函数返回true表示继续遍历,否则停止遍历
|
||||
func (this *IntIntMap) Iterator(f func (k int, v int) bool) {
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
for k, v := range this.m {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 哈希表克隆
|
||||
func (this *IntIntMap) Clone() map[int]int {
|
||||
m := make(map[int]int)
|
||||
this.mu.RLock()
|
||||
for k, v := range this.m {
|
||||
m[k] = v
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
return m
|
||||
}
|
||||
|
||||
// 设置键值对
|
||||
func (this *IntIntMap) Set(key int, val int) {
|
||||
this.mu.Lock()
|
||||
this.m[key] = val
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 批量设置键值对
|
||||
func (this *IntIntMap) BatchSet(m map[int]int) {
|
||||
this.mu.Lock()
|
||||
for k, v := range m {
|
||||
this.m[k] = v
|
||||
}
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 获取键值
|
||||
func (this *IntIntMap) Get(key int) (int) {
|
||||
this.mu.RLock()
|
||||
val, _ := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// 设置kv缓存键值对,内部会对键名的存在性使用写锁进行二次检索确认,如果存在则不再写入;返回键名对应的键值。
|
||||
// 在高并发下有用,防止数据写入的并发逻辑错误。
|
||||
func (this *IntIntMap) doSetWithLockCheck(key int, value int) int {
|
||||
this.mu.Lock()
|
||||
if v, ok := this.m[key]; ok {
|
||||
this.mu.Unlock()
|
||||
return v
|
||||
}
|
||||
this.m[key] = value
|
||||
this.mu.Unlock()
|
||||
return value
|
||||
}
|
||||
|
||||
// 当键名存在时返回其键值,否则写入指定的键值
|
||||
func (this *IntIntMap) GetOrSet(key int, value int) int {
|
||||
this.mu.RLock()
|
||||
v, ok := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
if !ok {
|
||||
return this.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// 当键名存在时返回其键值,否则写入指定的键值,键值由指定的函数生成
|
||||
func (this *IntIntMap) GetOrSetFunc(key int, f func() int) int {
|
||||
this.mu.RLock()
|
||||
v, ok := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
if !ok {
|
||||
return this.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// 与GetOrSetFunc不同的是,f是在写锁机制内执行
|
||||
func (this *IntIntMap) GetOrSetFuncLock(key int, f func() int) int {
|
||||
this.mu.RLock()
|
||||
val, ok := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
if !ok {
|
||||
this.mu.Lock()
|
||||
defer this.mu.Unlock()
|
||||
if v, ok := this.m[key]; ok {
|
||||
this.mu.Unlock()
|
||||
return v
|
||||
}
|
||||
val = f()
|
||||
this.m[key] = val
|
||||
return val
|
||||
} else {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
// 当键名不存在时写入,并返回true;否则返回false。
|
||||
func (this *IntIntMap) SetIfNotExist(key int, value int) bool {
|
||||
if !this.Contains(key) {
|
||||
this.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 批量删除键值对
|
||||
func (this *IntIntMap) BatchRemove(keys []int) {
|
||||
this.mu.Lock()
|
||||
for _, key := range keys {
|
||||
delete(this.m, key)
|
||||
}
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 返回对应的键值,并删除该键值
|
||||
func (this *IntIntMap) Remove(key int) int {
|
||||
this.mu.Lock()
|
||||
val, exists := this.m[key]
|
||||
if exists {
|
||||
delete(this.m, key)
|
||||
}
|
||||
this.mu.Unlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// 返回键列表
|
||||
func (this *IntIntMap) Keys() []int {
|
||||
this.mu.RLock()
|
||||
keys := make([]int, 0)
|
||||
for key, _ := range this.m {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
return keys
|
||||
}
|
||||
|
||||
// 返回值列表(注意是随机排序)
|
||||
func (this *IntIntMap) Values() []int {
|
||||
this.mu.RLock()
|
||||
vals := make([]int, 0)
|
||||
for _, val := range this.m {
|
||||
vals = append(vals, val)
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
return vals
|
||||
}
|
||||
|
||||
// 是否存在某个键
|
||||
func (this *IntIntMap) Contains(key int) bool {
|
||||
this.mu.RLock()
|
||||
_, exists := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
return exists
|
||||
}
|
||||
|
||||
// 哈希表大小
|
||||
func (this *IntIntMap) Size() int {
|
||||
this.mu.RLock()
|
||||
length := len(this.m)
|
||||
this.mu.RUnlock()
|
||||
return length
|
||||
}
|
||||
|
||||
// 哈希表是否为空
|
||||
func (this *IntIntMap) IsEmpty() bool {
|
||||
this.mu.RLock()
|
||||
empty := len(this.m) == 0
|
||||
this.mu.RUnlock()
|
||||
return empty
|
||||
}
|
||||
|
||||
// 清空哈希表
|
||||
func (this *IntIntMap) Clear() {
|
||||
this.mu.Lock()
|
||||
this.m = make(map[int]int)
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 并发安全锁操作,使用自定义方法执行加锁修改操作
|
||||
func (this *IntIntMap) LockFunc(f func(m map[int]int)) {
|
||||
this.mu.Lock(true)
|
||||
defer this.mu.Unlock(true)
|
||||
f(this.m)
|
||||
}
|
||||
|
||||
// 并发安全锁操作,使用自定义方法执行加锁读取操作
|
||||
func (this *IntIntMap) RLockFunc(f func(m map[int]int)) {
|
||||
this.mu.RLock(true)
|
||||
defer this.mu.RUnlock(true)
|
||||
f(this.m)
|
||||
}
|
||||
@ -1,206 +0,0 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
//
|
||||
|
||||
package gmap
|
||||
|
||||
import "gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
|
||||
type IntInterfaceMap struct {
|
||||
mu *rwmutex.RWMutex
|
||||
m map[int]interface{}
|
||||
}
|
||||
|
||||
func NewIntInterfaceMap(safe...bool) *IntInterfaceMap {
|
||||
return &IntInterfaceMap{
|
||||
m : make(map[int]interface{}),
|
||||
mu : rwmutex.New(safe...),
|
||||
}
|
||||
}
|
||||
|
||||
// 给定回调函数对原始内容进行遍历,回调函数返回true表示继续遍历,否则停止遍历
|
||||
func (this *IntInterfaceMap) Iterator(f func (k int, v interface{}) bool) {
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
for k, v := range this.m {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 哈希表克隆
|
||||
func (this *IntInterfaceMap) Clone() map[int]interface{} {
|
||||
m := make(map[int]interface{})
|
||||
this.mu.RLock()
|
||||
for k, v := range this.m {
|
||||
m[k] = v
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
return m
|
||||
}
|
||||
|
||||
// 设置键值对
|
||||
func (this *IntInterfaceMap) Set(key int, val interface{}) {
|
||||
this.mu.Lock()
|
||||
this.m[key] = val
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 批量设置键值对
|
||||
func (this *IntInterfaceMap) BatchSet(m map[int]interface{}) {
|
||||
this.mu.Lock()
|
||||
for k, v := range m {
|
||||
this.m[k] = v
|
||||
}
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 获取键值
|
||||
func (this *IntInterfaceMap) Get(key int) (interface{}) {
|
||||
this.mu.RLock()
|
||||
val, _ := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// 设置kv缓存键值对,内部会对键名的存在性使用写锁进行二次检索确认,如果存在则不再写入;返回键名对应的键值。
|
||||
// 在高并发下有用,防止数据写入的并发逻辑错误。
|
||||
func (this *IntInterfaceMap) doSetWithLockCheck(key int, value interface{}) interface{} {
|
||||
this.mu.Lock()
|
||||
defer this.mu.Unlock()
|
||||
if v, ok := this.m[key]; ok {
|
||||
return v
|
||||
}
|
||||
if f, ok := value.(func() interface {}); ok {
|
||||
value = f()
|
||||
}
|
||||
this.m[key] = value
|
||||
return value
|
||||
}
|
||||
|
||||
// 当键名存在时返回其键值,否则写入指定的键值
|
||||
func (this *IntInterfaceMap) GetOrSet(key int, value interface{}) interface{} {
|
||||
if v := this.Get(key); v == nil {
|
||||
return this.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// 当键名存在时返回其键值,否则写入指定的键值,键值由指定的函数生成
|
||||
func (this *IntInterfaceMap) GetOrSetFunc(key int, f func() interface{}) interface{} {
|
||||
if v := this.Get(key); v == nil {
|
||||
return this.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// 与GetOrSetFunc不同的是,f是在写锁机制内执行
|
||||
func (this *IntInterfaceMap) GetOrSetFuncLock(key int, f func() interface{}) interface{} {
|
||||
if v := this.Get(key); v == nil {
|
||||
return this.doSetWithLockCheck(key, f)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// 当键名不存在时写入,并返回true;否则返回false。
|
||||
func (this *IntInterfaceMap) SetIfNotExist(key int, value interface{}) bool {
|
||||
if !this.Contains(key) {
|
||||
this.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 批量删除键值对
|
||||
func (this *IntInterfaceMap) BatchRemove(keys []int) {
|
||||
this.mu.Lock()
|
||||
for _, key := range keys {
|
||||
delete(this.m, key)
|
||||
}
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 返回对应的键值,并删除该键值
|
||||
func (this *IntInterfaceMap) Remove(key int) interface{} {
|
||||
this.mu.Lock()
|
||||
val, exists := this.m[key]
|
||||
if exists {
|
||||
delete(this.m, key)
|
||||
}
|
||||
this.mu.Unlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// 返回键列表
|
||||
func (this *IntInterfaceMap) Keys() []int {
|
||||
this.mu.RLock()
|
||||
keys := make([]int, 0)
|
||||
for key, _ := range this.m {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
return keys
|
||||
}
|
||||
|
||||
// 返回值列表(注意是随机排序)
|
||||
func (this *IntInterfaceMap) Values() []interface{} {
|
||||
this.mu.RLock()
|
||||
vals := make([]interface{}, 0)
|
||||
for _, val := range this.m {
|
||||
vals = append(vals, val)
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
return vals
|
||||
}
|
||||
|
||||
// 是否存在某个键
|
||||
func (this *IntInterfaceMap) Contains(key int) bool {
|
||||
this.mu.RLock()
|
||||
_, exists := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
return exists
|
||||
}
|
||||
|
||||
// 哈希表大小
|
||||
func (this *IntInterfaceMap) Size() int {
|
||||
this.mu.RLock()
|
||||
length := len(this.m)
|
||||
this.mu.RUnlock()
|
||||
return length
|
||||
}
|
||||
|
||||
// 哈希表是否为空
|
||||
func (this *IntInterfaceMap) IsEmpty() bool {
|
||||
this.mu.RLock()
|
||||
empty := len(this.m) == 0
|
||||
this.mu.RUnlock()
|
||||
return empty
|
||||
}
|
||||
|
||||
// 清空哈希表
|
||||
func (this *IntInterfaceMap) Clear() {
|
||||
this.mu.Lock()
|
||||
this.m = make(map[int]interface{})
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 并发安全锁操作,使用自定义方法执行加锁修改操作
|
||||
func (this *IntInterfaceMap) LockFunc(f func(m map[int]interface{})) {
|
||||
this.mu.Lock()
|
||||
defer this.mu.Unlock()
|
||||
f(this.m)
|
||||
}
|
||||
|
||||
// 并发安全锁操作,使用自定义方法执行加锁读取操作
|
||||
func (this *IntInterfaceMap) RLockFunc(f func(m map[int]interface{})) {
|
||||
this.mu.RLock(true)
|
||||
defer this.mu.RUnlock(true)
|
||||
f(this.m)
|
||||
}
|
||||
@ -1,223 +0,0 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
)
|
||||
|
||||
type IntStringMap struct {
|
||||
mu *rwmutex.RWMutex
|
||||
m map[int]string
|
||||
}
|
||||
|
||||
func NewIntStringMap(safe...bool) *IntStringMap {
|
||||
return &IntStringMap{
|
||||
m : make(map[int]string),
|
||||
mu : rwmutex.New(safe...),
|
||||
}
|
||||
}
|
||||
|
||||
// 给定回调函数对原始内容进行遍历,回调函数返回true表示继续遍历,否则停止遍历
|
||||
func (this *IntStringMap) Iterator(f func (k int, v string) bool) {
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
for k, v := range this.m {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 哈希表克隆
|
||||
func (this *IntStringMap) Clone() map[int]string {
|
||||
m := make(map[int]string)
|
||||
this.mu.RLock()
|
||||
for k, v := range this.m {
|
||||
m[k] = v
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
return m
|
||||
}
|
||||
|
||||
// 设置键值对
|
||||
func (this *IntStringMap) Set(key int, val string) {
|
||||
this.mu.Lock()
|
||||
this.m[key] = val
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 批量设置键值对
|
||||
func (this *IntStringMap) BatchSet(m map[int]string) {
|
||||
this.mu.Lock()
|
||||
for k, v := range m {
|
||||
this.m[k] = v
|
||||
}
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 获取键值
|
||||
func (this *IntStringMap) Get(key int) string {
|
||||
this.mu.RLock()
|
||||
val, _ := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// 设置kv缓存键值对,内部会对键名的存在性使用写锁进行二次检索确认,如果存在则不再写入;返回键名对应的键值。
|
||||
// 在高并发下有用,防止数据写入的并发逻辑错误。
|
||||
func (this *IntStringMap) doSetWithLockCheck(key int, value string) string {
|
||||
this.mu.Lock()
|
||||
if v, ok := this.m[key]; ok {
|
||||
this.mu.Unlock()
|
||||
return v
|
||||
}
|
||||
this.m[key] = value
|
||||
this.mu.Unlock()
|
||||
return value
|
||||
}
|
||||
|
||||
// 当键名存在时返回其键值,否则写入指定的键值
|
||||
func (this *IntStringMap) GetOrSet(key int, value string) string {
|
||||
this.mu.RLock()
|
||||
v, ok := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
if !ok {
|
||||
return this.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// 当键名存在时返回其键值,否则写入指定的键值,键值由指定的函数生成
|
||||
func (this *IntStringMap) GetOrSetFunc(key int, f func() string) string {
|
||||
this.mu.RLock()
|
||||
v, ok := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
if !ok {
|
||||
return this.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// 与GetOrSetFunc不同的是,f是在写锁机制内执行
|
||||
func (this *IntStringMap) GetOrSetFuncLock(key int, f func() string) string {
|
||||
this.mu.RLock()
|
||||
val, ok := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
if !ok {
|
||||
this.mu.Lock()
|
||||
defer this.mu.Unlock()
|
||||
if v, ok := this.m[key]; ok {
|
||||
this.mu.Unlock()
|
||||
return v
|
||||
}
|
||||
val = f()
|
||||
this.m[key] = val
|
||||
return val
|
||||
} else {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
// 当键名不存在时写入,并返回true;否则返回false。
|
||||
func (this *IntStringMap) SetIfNotExist(key int, value string) bool {
|
||||
if !this.Contains(key) {
|
||||
this.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 批量删除键值对
|
||||
func (this *IntStringMap) BatchRemove(keys []int) {
|
||||
this.mu.Lock()
|
||||
for _, key := range keys {
|
||||
delete(this.m, key)
|
||||
}
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 返回对应的键值,并删除该键值
|
||||
func (this *IntStringMap) Remove(key int) string {
|
||||
this.mu.Lock()
|
||||
val, exists := this.m[key]
|
||||
if exists {
|
||||
delete(this.m, key)
|
||||
}
|
||||
this.mu.Unlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// 返回键列表
|
||||
func (this *IntStringMap) Keys() []int {
|
||||
this.mu.RLock()
|
||||
keys := make([]int, 0)
|
||||
for key, _ := range this.m {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
return keys
|
||||
}
|
||||
|
||||
// 返回值列表(注意是随机排序)
|
||||
func (this *IntStringMap) Values() []string {
|
||||
this.mu.RLock()
|
||||
vals := make([]string, 0)
|
||||
for _, val := range this.m {
|
||||
vals = append(vals, val)
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
return vals
|
||||
}
|
||||
|
||||
// 是否存在某个键
|
||||
func (this *IntStringMap) Contains(key int) bool {
|
||||
this.mu.RLock()
|
||||
_, exists := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
return exists
|
||||
}
|
||||
|
||||
// 哈希表大小
|
||||
func (this *IntStringMap) Size() int {
|
||||
this.mu.RLock()
|
||||
length := len(this.m)
|
||||
this.mu.RUnlock()
|
||||
return length
|
||||
}
|
||||
|
||||
// 哈希表是否为空
|
||||
func (this *IntStringMap) IsEmpty() bool {
|
||||
this.mu.RLock()
|
||||
empty := len(this.m) == 0
|
||||
this.mu.RUnlock()
|
||||
return empty
|
||||
}
|
||||
|
||||
// 清空哈希表
|
||||
func (this *IntStringMap) Clear() {
|
||||
this.mu.Lock()
|
||||
this.m = make(map[int]string)
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 并发安全锁操作,使用自定义方法执行加锁修改操作
|
||||
func (this *IntStringMap) LockFunc(f func(m map[int]string)) {
|
||||
this.mu.Lock(true)
|
||||
defer this.mu.Unlock(true)
|
||||
f(this.m)
|
||||
}
|
||||
|
||||
// 并发安全锁操作,使用自定义方法执行加锁读取操作
|
||||
func (this *IntStringMap) RLockFunc(f func(m map[int]string)) {
|
||||
this.mu.RLock(true)
|
||||
defer this.mu.RUnlock(true)
|
||||
f(this.m)
|
||||
}
|
||||
@ -1,208 +0,0 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
//
|
||||
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
)
|
||||
|
||||
type InterfaceInterfaceMap struct {
|
||||
mu *rwmutex.RWMutex
|
||||
m map[interface{}]interface{}
|
||||
}
|
||||
|
||||
func NewInterfaceInterfaceMap(safe...bool) *InterfaceInterfaceMap {
|
||||
return &InterfaceInterfaceMap{
|
||||
m : make(map[interface{}]interface{}),
|
||||
mu : rwmutex.New(safe...),
|
||||
}
|
||||
}
|
||||
|
||||
// 给定回调函数对原始内容进行遍历,回调函数返回true表示继续遍历,否则停止遍历
|
||||
func (this *InterfaceInterfaceMap) Iterator(f func (k interface{}, v interface{}) bool) {
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
for k, v := range this.m {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 哈希表克隆
|
||||
func (this *InterfaceInterfaceMap) Clone() map[interface{}]interface{} {
|
||||
m := make(map[interface{}]interface{})
|
||||
this.mu.RLock()
|
||||
for k, v := range this.m {
|
||||
m[k] = v
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
return m
|
||||
}
|
||||
|
||||
// 设置键值对
|
||||
func (this *InterfaceInterfaceMap) Set(key interface{}, val interface{}) {
|
||||
this.mu.Lock()
|
||||
this.m[key] = val
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 批量设置键值对
|
||||
func (this *InterfaceInterfaceMap) BatchSet(m map[interface{}]interface{}) {
|
||||
this.mu.Lock()
|
||||
for k, v := range m {
|
||||
this.m[k] = v
|
||||
}
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 获取键值
|
||||
func (this *InterfaceInterfaceMap) Get(key interface{}) interface{} {
|
||||
this.mu.RLock()
|
||||
val, _ := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// 设置kv缓存键值对,内部会对键名的存在性使用写锁进行二次检索确认,如果存在则不再写入;返回键名对应的键值。
|
||||
// 在高并发下有用,防止数据写入的并发逻辑错误。
|
||||
func (this *InterfaceInterfaceMap) doSetWithLockCheck(key interface{}, value interface{}) interface{} {
|
||||
this.mu.Lock()
|
||||
defer this.mu.Unlock()
|
||||
if v, ok := this.m[key]; ok {
|
||||
return v
|
||||
}
|
||||
if f, ok := value.(func() interface {}); ok {
|
||||
value = f()
|
||||
}
|
||||
this.m[key] = value
|
||||
return value
|
||||
}
|
||||
|
||||
// 当键名存在时返回其键值,否则写入指定的键值
|
||||
func (this *InterfaceInterfaceMap) GetOrSet(key interface{}, value interface{}) interface{} {
|
||||
if v := this.Get(key); v == nil {
|
||||
return this.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// 当键名存在时返回其键值,否则写入指定的键值,键值由指定的函数生成
|
||||
func (this *InterfaceInterfaceMap) GetOrSetFunc(key interface{}, f func() interface{}) interface{} {
|
||||
if v := this.Get(key); v == nil {
|
||||
return this.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// 与GetOrSetFunc不同的是,f是在写锁机制内执行
|
||||
func (this *InterfaceInterfaceMap) GetOrSetFuncLock(key interface{}, f func() interface{}) interface{} {
|
||||
if v := this.Get(key); v == nil {
|
||||
return this.doSetWithLockCheck(key, f)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// 当键名不存在时写入,并返回true;否则返回false。
|
||||
func (this *InterfaceInterfaceMap) SetIfNotExist(key interface{}, value interface{}) bool {
|
||||
if !this.Contains(key) {
|
||||
this.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 批量删除键值对
|
||||
func (this *InterfaceInterfaceMap) BatchRemove(keys []interface{}) {
|
||||
this.mu.Lock()
|
||||
for _, key := range keys {
|
||||
delete(this.m, key)
|
||||
}
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 返回对应的键值,并删除该键值
|
||||
func (this *InterfaceInterfaceMap) Remove(key interface{}) interface{} {
|
||||
this.mu.Lock()
|
||||
val, exists := this.m[key]
|
||||
if exists {
|
||||
delete(this.m, key)
|
||||
}
|
||||
this.mu.Unlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// 返回键列表
|
||||
func (this *InterfaceInterfaceMap) Keys() []interface{} {
|
||||
this.mu.RLock()
|
||||
keys := make([]interface{}, 0)
|
||||
for key, _ := range this.m {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
return keys
|
||||
}
|
||||
|
||||
// 返回值列表(注意是随机排序)
|
||||
func (this *InterfaceInterfaceMap) Values() []interface{} {
|
||||
this.mu.RLock()
|
||||
vals := make([]interface{}, 0)
|
||||
for _, val := range this.m {
|
||||
vals = append(vals, val)
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
return vals
|
||||
}
|
||||
|
||||
// 是否存在某个键
|
||||
func (this *InterfaceInterfaceMap) Contains(key interface{}) bool {
|
||||
this.mu.RLock()
|
||||
_, exists := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
return exists
|
||||
}
|
||||
|
||||
// 哈希表大小
|
||||
func (this *InterfaceInterfaceMap) Size() int {
|
||||
this.mu.RLock()
|
||||
length := len(this.m)
|
||||
this.mu.RUnlock()
|
||||
return length
|
||||
}
|
||||
|
||||
// 哈希表是否为空
|
||||
func (this *InterfaceInterfaceMap) IsEmpty() bool {
|
||||
this.mu.RLock()
|
||||
empty := len(this.m) == 0
|
||||
this.mu.RUnlock()
|
||||
return empty
|
||||
}
|
||||
|
||||
// 清空哈希表
|
||||
func (this *InterfaceInterfaceMap) Clear() {
|
||||
this.mu.Lock()
|
||||
this.m = make(map[interface{}]interface{})
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 并发安全锁操作,使用自定义方法执行加锁修改操作
|
||||
func (this *InterfaceInterfaceMap) LockFunc(f func(m map[interface{}]interface{})) {
|
||||
this.mu.Lock(true)
|
||||
defer this.mu.Unlock(true)
|
||||
f(this.m)
|
||||
}
|
||||
|
||||
// 并发安全锁操作,使用自定义方法执行加锁读取操作
|
||||
func (this *InterfaceInterfaceMap) RLockFunc(f func(m map[interface{}]interface{})) {
|
||||
this.mu.RLock(true)
|
||||
defer this.mu.RUnlock(true)
|
||||
f(this.m)
|
||||
}
|
||||
366
g/container/gmap/gmap_link_map.go
Normal file
366
g/container/gmap/gmap_link_map.go
Normal file
@ -0,0 +1,366 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with gm file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/glist"
|
||||
"github.com/gogf/gf/g/container/gvar"
|
||||
"github.com/gogf/gf/g/internal/rwmutex"
|
||||
)
|
||||
|
||||
type ListMap struct {
|
||||
mu *rwmutex.RWMutex
|
||||
data map[interface{}]*glist.Element
|
||||
list *glist.List
|
||||
}
|
||||
|
||||
type gListMapNode struct {
|
||||
key interface{}
|
||||
value interface{}
|
||||
}
|
||||
|
||||
// NewListMap returns an empty link map.
|
||||
// ListMap is backed by a hash table to store values and doubly-linked list to store ordering.
|
||||
// The parameter <unsafe> used to specify whether using map in un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe.
|
||||
func NewListMap(unsafe ...bool) *ListMap {
|
||||
return &ListMap{
|
||||
mu: rwmutex.New(unsafe...),
|
||||
data: make(map[interface{}]*glist.Element),
|
||||
list: glist.New(true),
|
||||
}
|
||||
}
|
||||
|
||||
// NewListMapFrom returns a link map from given map <data>.
|
||||
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
|
||||
// there might be some concurrent-safe issues when changing the map outside.
|
||||
func NewListMapFrom(data map[interface{}]interface{}, unsafe ...bool) *ListMap {
|
||||
m := NewListMap(unsafe...)
|
||||
m.Sets(data)
|
||||
return m
|
||||
}
|
||||
|
||||
// Iterator is alias of IteratorAsc.
|
||||
func (m *ListMap) Iterator(f func(key, value interface{}) bool) {
|
||||
m.IteratorAsc(f)
|
||||
}
|
||||
|
||||
// IteratorAsc iterates the map in ascending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (m *ListMap) IteratorAsc(f func(key interface{}, value interface{}) bool) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
node := (*gListMapNode)(nil)
|
||||
m.list.IteratorAsc(func(e *glist.Element) bool {
|
||||
node = e.Value.(*gListMapNode)
|
||||
return f(node.key, node.value)
|
||||
})
|
||||
}
|
||||
|
||||
// IteratorDesc iterates the map in descending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (m *ListMap) IteratorDesc(f func(key interface{}, value interface{}) bool) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
node := (*gListMapNode)(nil)
|
||||
m.list.IteratorDesc(func(e *glist.Element) bool {
|
||||
node = e.Value.(*gListMapNode)
|
||||
return f(node.key, node.value)
|
||||
})
|
||||
}
|
||||
|
||||
// Clone returns a new link map with copy of current map data.
|
||||
func (m *ListMap) Clone(unsafe ...bool) *ListMap {
|
||||
return NewListMapFrom(m.Map(), unsafe...)
|
||||
}
|
||||
|
||||
// Clear deletes all data of the map, it will remake a new underlying data map.
|
||||
func (m *ListMap) Clear() {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[interface{}]*glist.Element)
|
||||
m.list = glist.New(true)
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Map returns a copy of the data of the map.
|
||||
func (m *ListMap) Map() map[interface{}]interface{} {
|
||||
m.mu.RLock()
|
||||
node := (*gListMapNode)(nil)
|
||||
data := make(map[interface{}]interface{}, len(m.data))
|
||||
m.list.IteratorAsc(func(e *glist.Element) bool {
|
||||
node = e.Value.(*gListMapNode)
|
||||
data[node.key] = node.value
|
||||
return true
|
||||
})
|
||||
m.mu.RUnlock()
|
||||
return data
|
||||
}
|
||||
|
||||
// Set sets key-value to the map.
|
||||
func (m *ListMap) Set(key interface{}, value interface{}) {
|
||||
m.mu.Lock()
|
||||
if e, ok := m.data[key]; !ok {
|
||||
m.data[key] = m.list.PushBack(&gListMapNode{key, value})
|
||||
} else {
|
||||
e.Value = &gListMapNode{key, value}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the map.
|
||||
func (m *ListMap) Sets(data map[interface{}]interface{}) {
|
||||
m.mu.Lock()
|
||||
for key, value := range data {
|
||||
if e, ok := m.data[key]; !ok {
|
||||
m.data[key] = m.list.PushBack(&gListMapNode{key, value})
|
||||
} else {
|
||||
e.Value = &gListMapNode{key, value}
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Search searches the map with given <key>.
|
||||
// Second return parameter <found> is true if key was found, otherwise false.
|
||||
func (m *ListMap) Search(key interface{}) (value interface{}, found bool) {
|
||||
m.mu.RLock()
|
||||
if e, ok := m.data[key]; ok {
|
||||
value = e.Value.(*gListMapNode).value
|
||||
found = ok
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Get returns the value by given <key>.
|
||||
func (m *ListMap) Get(key interface{}) (value interface{}) {
|
||||
m.mu.RLock()
|
||||
if e, ok := m.data[key]; ok {
|
||||
value = e.Value.(*gListMapNode).value
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given <key>,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// When setting value, if <value> is type of <func() interface {}>,
|
||||
// it will be executed with mutex.Lock of the map,
|
||||
// and its return value will be set to the map with <key>.
|
||||
//
|
||||
// It returns value with given <key>.
|
||||
func (m *ListMap) doSetWithLockCheck(key interface{}, value interface{}) interface{} {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if e, ok := m.data[key]; ok {
|
||||
return e.Value.(*gListMapNode).value
|
||||
}
|
||||
if f, ok := value.(func() interface{}); ok {
|
||||
value = f()
|
||||
}
|
||||
m.data[key] = m.list.PushBack(&gListMapNode{key, value})
|
||||
return value
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or set value with given <value> if not exist and returns this value.
|
||||
func (m *ListMap) GetOrSet(key interface{}, value interface{}) interface{} {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with return value of callback function <f> if not exist
|
||||
// and returns this value.
|
||||
func (m *ListMap) GetOrSetFunc(key interface{}, f func() interface{}) interface{} {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
// or sets value with return value of callback function <f> if not exist
|
||||
// and returns this value.
|
||||
//
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
|
||||
// with mutex.Lock of the map.
|
||||
func (m *ListMap) GetOrSetFuncLock(key interface{}, f func() interface{}) interface{} {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetVar returns a gvar.Var with the value by given <key>.
|
||||
// The returned gvar.Var is un-concurrent safe.
|
||||
func (m *ListMap) GetVar(key interface{}) *gvar.Var {
|
||||
return gvar.New(m.Get(key), true)
|
||||
}
|
||||
|
||||
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
|
||||
// The returned gvar.Var is un-concurrent safe.
|
||||
func (m *ListMap) GetVarOrSet(key interface{}, value interface{}) *gvar.Var {
|
||||
return gvar.New(m.GetOrSet(key, value), true)
|
||||
}
|
||||
|
||||
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
|
||||
// The returned gvar.Var is un-concurrent safe.
|
||||
func (m *ListMap) GetVarOrSetFunc(key interface{}, f func() interface{}) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFunc(key, f), true)
|
||||
}
|
||||
|
||||
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
|
||||
// The returned gvar.Var is un-concurrent safe.
|
||||
func (m *ListMap) GetVarOrSetFuncLock(key interface{}, f func() interface{}) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFuncLock(key, f), true)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
func (m *ListMap) SetIfNotExist(key interface{}, value interface{}) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
func (m *ListMap) SetIfNotExistFunc(key interface{}, f func() interface{}) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
//
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function <f> with mutex.Lock of the map.
|
||||
func (m *ListMap) SetIfNotExistFuncLock(key interface{}, f func() interface{}) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Remove deletes value from map by given <key>, and return this deleted value.
|
||||
func (m *ListMap) Remove(key interface{}) (value interface{}) {
|
||||
m.mu.Lock()
|
||||
if e, ok := m.data[key]; ok {
|
||||
value = e.Value.(*gListMapNode).value
|
||||
delete(m.data, key)
|
||||
m.list.Remove(e)
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the map by keys.
|
||||
func (m *ListMap) Removes(keys []interface{}) {
|
||||
m.mu.Lock()
|
||||
for _, key := range keys {
|
||||
if e, ok := m.data[key]; ok {
|
||||
delete(m.data, key)
|
||||
m.list.Remove(e)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Keys returns all keys of the map as a slice in ascending order.
|
||||
func (m *ListMap) Keys() []interface{} {
|
||||
m.mu.RLock()
|
||||
keys := make([]interface{}, m.list.Len())
|
||||
index := 0
|
||||
m.list.IteratorAsc(func(e *glist.Element) bool {
|
||||
keys[index] = e.Value.(*gListMapNode).key
|
||||
index++
|
||||
return true
|
||||
})
|
||||
m.mu.RUnlock()
|
||||
return keys
|
||||
}
|
||||
|
||||
// Values returns all values of the map as a slice.
|
||||
func (m *ListMap) Values() []interface{} {
|
||||
m.mu.RLock()
|
||||
values := make([]interface{}, m.list.Len())
|
||||
index := 0
|
||||
m.list.IteratorAsc(func(e *glist.Element) bool {
|
||||
values[index] = e.Value.(*gListMapNode).value
|
||||
index++
|
||||
return true
|
||||
})
|
||||
m.mu.RUnlock()
|
||||
return values
|
||||
}
|
||||
|
||||
// Contains checks whether a key exists.
|
||||
// It returns true if the <key> exists, or else false.
|
||||
func (m *ListMap) Contains(key interface{}) (ok bool) {
|
||||
m.mu.RLock()
|
||||
_, ok = m.data[key]
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Size returns the size of the map.
|
||||
func (m *ListMap) Size() (size int) {
|
||||
m.mu.RLock()
|
||||
size = len(m.data)
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// IsEmpty checks whether the map is empty.
|
||||
// It returns true if map is empty, or else false.
|
||||
func (m *ListMap) IsEmpty() bool {
|
||||
return m.Size() == 0
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the map to value-key.
|
||||
func (m *ListMap) Flip() {
|
||||
data := m.Map()
|
||||
m.Clear()
|
||||
for key, value := range data {
|
||||
m.Set(value, key)
|
||||
}
|
||||
}
|
||||
|
||||
// Merge merges two link maps.
|
||||
// The <other> map will be merged into the map <m>.
|
||||
func (m *ListMap) Merge(other *ListMap) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if other != m {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
node := (*gListMapNode)(nil)
|
||||
other.list.IteratorAsc(func(e *glist.Element) bool {
|
||||
node = e.Value.(*gListMapNode)
|
||||
if e, ok := m.data[node.key]; !ok {
|
||||
m.data[node.key] = m.list.PushBack(&gListMapNode{node.key, node.value})
|
||||
} else {
|
||||
e.Value = &gListMapNode{node.key, node.value}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
@ -1,223 +0,0 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
//
|
||||
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
)
|
||||
|
||||
type StringBoolMap struct {
|
||||
mu *rwmutex.RWMutex
|
||||
m map[string]bool
|
||||
}
|
||||
|
||||
func NewStringBoolMap(safe...bool) *StringBoolMap {
|
||||
return &StringBoolMap{
|
||||
m : make(map[string]bool),
|
||||
mu : rwmutex.New(safe...),
|
||||
}
|
||||
}
|
||||
|
||||
// 给定回调函数对原始内容进行遍历,回调函数返回true表示继续遍历,否则停止遍历
|
||||
func (this *StringBoolMap) Iterator(f func (k string, v bool) bool) {
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
for k, v := range this.m {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 哈希表克隆
|
||||
func (this *StringBoolMap) Clone() map[string]bool {
|
||||
m := make(map[string]bool)
|
||||
this.mu.RLock()
|
||||
for k, v := range this.m {
|
||||
m[k] = v
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
return m
|
||||
}
|
||||
|
||||
// 设置键值对
|
||||
func (this *StringBoolMap) Set(key string, val bool) {
|
||||
this.mu.Lock()
|
||||
this.m[key] = val
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 批量设置键值对
|
||||
func (this *StringBoolMap) BatchSet(m map[string]bool) {
|
||||
this.mu.Lock()
|
||||
for k, v := range m {
|
||||
this.m[k] = v
|
||||
}
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 获取键值
|
||||
func (this *StringBoolMap) Get(key string) bool {
|
||||
this.mu.RLock()
|
||||
val, _ := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// 设置kv缓存键值对,内部会对键名的存在性使用写锁进行二次检索确认,如果存在则不再写入;返回键名对应的键值。
|
||||
// 在高并发下有用,防止数据写入的并发逻辑错误。
|
||||
func (this *StringBoolMap) doSetWithLockCheck(key string, value bool) bool {
|
||||
this.mu.Lock()
|
||||
if v, ok := this.m[key]; ok {
|
||||
this.mu.Unlock()
|
||||
return v
|
||||
}
|
||||
this.m[key] = value
|
||||
this.mu.Unlock()
|
||||
return value
|
||||
}
|
||||
|
||||
// 当键名存在时返回其键值,否则写入指定的键值
|
||||
func (this *StringBoolMap) GetOrSet(key string, value bool) bool {
|
||||
this.mu.RLock()
|
||||
v, ok := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
if !ok {
|
||||
return this.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// 当键名存在时返回其键值,否则写入指定的键值,键值由指定的函数生成
|
||||
func (this *StringBoolMap) GetOrSetFunc(key string, f func() bool) bool {
|
||||
this.mu.RLock()
|
||||
v, ok := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
if !ok {
|
||||
return this.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// 与GetOrSetFunc不同的是,f是在写锁机制内执行
|
||||
func (this *StringBoolMap) GetOrSetFuncLock(key string, f func() bool) bool {
|
||||
this.mu.RLock()
|
||||
val, ok := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
if !ok {
|
||||
this.mu.Lock()
|
||||
defer this.mu.Unlock()
|
||||
if v, ok := this.m[key]; ok {
|
||||
this.mu.Unlock()
|
||||
return v
|
||||
}
|
||||
val = f()
|
||||
this.m[key] = val
|
||||
return val
|
||||
} else {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
// 当键名不存在时写入,并返回true;否则返回false。
|
||||
func (this *StringBoolMap) SetIfNotExist(key string, value bool) bool {
|
||||
if !this.Contains(key) {
|
||||
this.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 批量删除键值对
|
||||
func (this *StringBoolMap) BatchRemove(keys []string) {
|
||||
this.mu.Lock()
|
||||
for _, key := range keys {
|
||||
delete(this.m, key)
|
||||
}
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 返回对应的键值,并删除该键值
|
||||
func (this *StringBoolMap) Remove(key string) bool {
|
||||
this.mu.Lock()
|
||||
val, exists := this.m[key]
|
||||
if exists {
|
||||
delete(this.m, key)
|
||||
}
|
||||
this.mu.Unlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// 返回键列表
|
||||
func (this *StringBoolMap) Keys() []string {
|
||||
this.mu.RLock()
|
||||
keys := make([]string, 0)
|
||||
for key, _ := range this.m {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
return keys
|
||||
}
|
||||
|
||||
// 返回值列表(注意是随机排序)
|
||||
//func (this *StringBoolMap) Values() []bool {
|
||||
// this.mu.RLock()
|
||||
// vals := make([]bool, 0)
|
||||
// for _, val := range this.m {
|
||||
// vals = append(vals, val)
|
||||
// }
|
||||
// this.mu.RUnlock()
|
||||
// return vals
|
||||
//}
|
||||
|
||||
// 是否存在某个键
|
||||
func (this *StringBoolMap) Contains(key string) bool {
|
||||
this.mu.RLock()
|
||||
_, exists := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
return exists
|
||||
}
|
||||
|
||||
// 哈希表大小
|
||||
func (this *StringBoolMap) Size() int {
|
||||
this.mu.RLock()
|
||||
length := len(this.m)
|
||||
this.mu.RUnlock()
|
||||
return length
|
||||
}
|
||||
|
||||
// 哈希表是否为空
|
||||
func (this *StringBoolMap) IsEmpty() bool {
|
||||
this.mu.RLock()
|
||||
empty := len(this.m) == 0
|
||||
this.mu.RUnlock()
|
||||
return empty
|
||||
}
|
||||
|
||||
// 清空哈希表
|
||||
func (this *StringBoolMap) Clear() {
|
||||
this.mu.Lock()
|
||||
this.m = make(map[string]bool)
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 并发安全锁操作,使用自定义方法执行加锁修改操作
|
||||
func (this *StringBoolMap) LockFunc(f func(m map[string]bool)) {
|
||||
this.mu.Lock(true)
|
||||
defer this.mu.Unlock(true)
|
||||
f(this.m)
|
||||
}
|
||||
|
||||
// 并发安全锁操作,使用自定义方法执行加锁读取操作
|
||||
func (this *StringBoolMap) RLockFunc(f func(m map[string]bool)) {
|
||||
this.mu.RLock(true)
|
||||
defer this.mu.RUnlock(true)
|
||||
f(this.m)
|
||||
}
|
||||
@ -1,221 +0,0 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
//
|
||||
|
||||
package gmap
|
||||
|
||||
import "gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
|
||||
type StringIntMap struct {
|
||||
mu *rwmutex.RWMutex
|
||||
m map[string]int
|
||||
}
|
||||
|
||||
func NewStringIntMap(safe...bool) *StringIntMap {
|
||||
return &StringIntMap{
|
||||
m : make(map[string]int),
|
||||
mu : rwmutex.New(safe...),
|
||||
}
|
||||
}
|
||||
|
||||
// 给定回调函数对原始内容进行遍历,回调函数返回true表示继续遍历,否则停止遍历
|
||||
func (this *StringIntMap) Iterator(f func (k string, v int) bool) {
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
for k, v := range this.m {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 哈希表克隆
|
||||
func (this *StringIntMap) Clone() map[string]int {
|
||||
m := make(map[string]int)
|
||||
this.mu.RLock()
|
||||
for k, v := range this.m {
|
||||
m[k] = v
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
return m
|
||||
}
|
||||
|
||||
// 设置键值对
|
||||
func (this *StringIntMap) Set(key string, val int) {
|
||||
this.mu.Lock()
|
||||
this.m[key] = val
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 批量设置键值对
|
||||
func (this *StringIntMap) BatchSet(m map[string]int) {
|
||||
this.mu.Lock()
|
||||
for k, v := range m {
|
||||
this.m[k] = v
|
||||
}
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 获取键值
|
||||
func (this *StringIntMap) Get(key string) int {
|
||||
this.mu.RLock()
|
||||
val, _ := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// 设置kv缓存键值对,内部会对键名的存在性使用写锁进行二次检索确认,如果存在则不再写入;返回键名对应的键值。
|
||||
// 在高并发下有用,防止数据写入的并发逻辑错误。
|
||||
func (this *StringIntMap) doSetWithLockCheck(key string, value int) int {
|
||||
this.mu.Lock()
|
||||
if v, ok := this.m[key]; ok {
|
||||
this.mu.Unlock()
|
||||
return v
|
||||
}
|
||||
this.m[key] = value
|
||||
this.mu.Unlock()
|
||||
return value
|
||||
}
|
||||
|
||||
// 当键名存在时返回其键值,否则写入指定的键值
|
||||
func (this *StringIntMap) GetOrSet(key string, value int) int {
|
||||
this.mu.RLock()
|
||||
v, ok := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
if !ok {
|
||||
return this.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// 当键名存在时返回其键值,否则写入指定的键值,键值由指定的函数生成
|
||||
func (this *StringIntMap) GetOrSetFunc(key string, f func() int) int {
|
||||
this.mu.RLock()
|
||||
v, ok := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
if !ok {
|
||||
return this.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// 与GetOrSetFunc不同的是,f是在写锁机制内执行
|
||||
func (this *StringIntMap) GetOrSetFuncLock(key string, f func() int) int {
|
||||
this.mu.RLock()
|
||||
val, ok := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
if !ok {
|
||||
this.mu.Lock()
|
||||
defer this.mu.Unlock()
|
||||
if v, ok := this.m[key]; ok {
|
||||
this.mu.Unlock()
|
||||
return v
|
||||
}
|
||||
val = f()
|
||||
this.m[key] = val
|
||||
return val
|
||||
} else {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
// 当键名不存在时写入,并返回true;否则返回false。
|
||||
func (this *StringIntMap) SetIfNotExist(key string, value int) bool {
|
||||
if !this.Contains(key) {
|
||||
this.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 批量删除键值对
|
||||
func (this *StringIntMap) BatchRemove(keys []string) {
|
||||
this.mu.Lock()
|
||||
for _, key := range keys {
|
||||
delete(this.m, key)
|
||||
}
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 返回对应的键值,并删除该键值
|
||||
func (this *StringIntMap) Remove(key string) int {
|
||||
this.mu.Lock()
|
||||
val, exists := this.m[key]
|
||||
if exists {
|
||||
delete(this.m, key)
|
||||
}
|
||||
this.mu.Unlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// 返回键列表
|
||||
func (this *StringIntMap) Keys() []string {
|
||||
this.mu.RLock()
|
||||
keys := make([]string, 0)
|
||||
for key, _ := range this.m {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
return keys
|
||||
}
|
||||
|
||||
// 返回值列表(注意是随机排序)
|
||||
func (this *StringIntMap) Values() []int {
|
||||
this.mu.RLock()
|
||||
vals := make([]int, 0)
|
||||
for _, val := range this.m {
|
||||
vals = append(vals, val)
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
return vals
|
||||
}
|
||||
|
||||
// 是否存在某个键
|
||||
func (this *StringIntMap) Contains(key string) bool {
|
||||
this.mu.RLock()
|
||||
_, exists := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
return exists
|
||||
}
|
||||
|
||||
// 哈希表大小
|
||||
func (this *StringIntMap) Size() int {
|
||||
this.mu.RLock()
|
||||
length := len(this.m)
|
||||
this.mu.RUnlock()
|
||||
return length
|
||||
}
|
||||
|
||||
// 哈希表是否为空
|
||||
func (this *StringIntMap) IsEmpty() bool {
|
||||
this.mu.RLock()
|
||||
empty := len(this.m) == 0
|
||||
this.mu.RUnlock()
|
||||
return empty
|
||||
}
|
||||
|
||||
// 清空哈希表
|
||||
func (this *StringIntMap) Clear() {
|
||||
this.mu.Lock()
|
||||
this.m = make(map[string]int)
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 并发安全写锁操作,使用自定义方法执行加锁修改操作
|
||||
func (this *StringIntMap) LockFunc(f func(m map[string]int)) {
|
||||
this.mu.Lock(true)
|
||||
defer this.mu.Unlock(true)
|
||||
f(this.m)
|
||||
}
|
||||
|
||||
// 并发安全读锁操作,使用自定义方法执行加锁读取操作
|
||||
func (this *StringIntMap) RLockFunc(f func(m map[string]int)) {
|
||||
this.mu.RLock(true)
|
||||
defer this.mu.RUnlock(true)
|
||||
f(this.m)
|
||||
}
|
||||
@ -1,208 +0,0 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
//
|
||||
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
)
|
||||
|
||||
type StringInterfaceMap struct {
|
||||
mu *rwmutex.RWMutex
|
||||
m map[string]interface{}
|
||||
}
|
||||
|
||||
func NewStringInterfaceMap(safe...bool) *StringInterfaceMap {
|
||||
return &StringInterfaceMap{
|
||||
m : make(map[string]interface{}),
|
||||
mu : rwmutex.New(safe...),
|
||||
}
|
||||
}
|
||||
|
||||
// 给定回调函数对原始内容进行遍历,回调函数返回true表示继续遍历,否则停止遍历
|
||||
func (this *StringInterfaceMap) Iterator(f func (k string, v interface{}) bool) {
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
for k, v := range this.m {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 哈希表克隆
|
||||
func (this *StringInterfaceMap) Clone() map[string]interface{} {
|
||||
m := make(map[string]interface{})
|
||||
this.mu.RLock()
|
||||
for k, v := range this.m {
|
||||
m[k] = v
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
return m
|
||||
}
|
||||
|
||||
// 设置键值对
|
||||
func (this *StringInterfaceMap) Set(key string, val interface{}) {
|
||||
this.mu.Lock()
|
||||
this.m[key] = val
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 批量设置键值对
|
||||
func (this *StringInterfaceMap) BatchSet(m map[string]interface{}) {
|
||||
this.mu.Lock()
|
||||
for k, v := range m {
|
||||
this.m[k] = v
|
||||
}
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 获取键值
|
||||
func (this *StringInterfaceMap) Get(key string) interface{} {
|
||||
this.mu.RLock()
|
||||
val, _ := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// 设置kv缓存键值对,内部会对键名的存在性使用写锁进行二次检索确认,如果存在则不再写入;返回键名对应的键值。
|
||||
// 在高并发下有用,防止数据写入的并发逻辑错误。
|
||||
func (this *StringInterfaceMap) doSetWithLockCheck(key string, value interface{}) interface{} {
|
||||
this.mu.Lock()
|
||||
defer this.mu.Unlock()
|
||||
if v, ok := this.m[key]; ok {
|
||||
return v
|
||||
}
|
||||
if f, ok := value.(func() interface {}); ok {
|
||||
value = f()
|
||||
}
|
||||
this.m[key] = value
|
||||
return value
|
||||
}
|
||||
|
||||
// 当键名存在时返回其键值,否则写入指定的键值
|
||||
func (this *StringInterfaceMap) GetOrSet(key string, value interface{}) interface{} {
|
||||
if v := this.Get(key); v == nil {
|
||||
return this.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// 当键名存在时返回其键值,否则写入指定的键值,键值由指定的函数生成
|
||||
func (this *StringInterfaceMap) GetOrSetFunc(key string, f func() interface{}) interface{} {
|
||||
if v := this.Get(key); v == nil {
|
||||
return this.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// 与GetOrSetFunc不同的是,f是在写锁机制内执行
|
||||
func (this *StringInterfaceMap) GetOrSetFuncLock(key string, f func() interface{}) interface{} {
|
||||
if v := this.Get(key); v == nil {
|
||||
return this.doSetWithLockCheck(key, f)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// 当键名不存在时写入,并返回true;否则返回false。
|
||||
func (this *StringInterfaceMap) SetIfNotExist(key string, value interface{}) bool {
|
||||
if !this.Contains(key) {
|
||||
this.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 批量删除键值对
|
||||
func (this *StringInterfaceMap) BatchRemove(keys []string) {
|
||||
this.mu.Lock()
|
||||
for _, key := range keys {
|
||||
delete(this.m, key)
|
||||
}
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 返回对应的键值,并删除该键值
|
||||
func (this *StringInterfaceMap) Remove(key string) interface{} {
|
||||
this.mu.Lock()
|
||||
val, exists := this.m[key]
|
||||
if exists {
|
||||
delete(this.m, key)
|
||||
}
|
||||
this.mu.Unlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// 返回键列表
|
||||
func (this *StringInterfaceMap) Keys() []string {
|
||||
this.mu.RLock()
|
||||
keys := make([]string, 0)
|
||||
for key, _ := range this.m {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
return keys
|
||||
}
|
||||
|
||||
// 返回值列表(注意是随机排序)
|
||||
func (this *StringInterfaceMap) Values() []interface{} {
|
||||
this.mu.RLock()
|
||||
vals := make([]interface{}, 0)
|
||||
for _, val := range this.m {
|
||||
vals = append(vals, val)
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
return vals
|
||||
}
|
||||
|
||||
// 是否存在某个键
|
||||
func (this *StringInterfaceMap) Contains(key string) bool {
|
||||
this.mu.RLock()
|
||||
_, exists := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
return exists
|
||||
}
|
||||
|
||||
// 哈希表大小
|
||||
func (this *StringInterfaceMap) Size() int {
|
||||
this.mu.RLock()
|
||||
length := len(this.m)
|
||||
this.mu.RUnlock()
|
||||
return length
|
||||
}
|
||||
|
||||
// 哈希表是否为空
|
||||
func (this *StringInterfaceMap) IsEmpty() bool {
|
||||
this.mu.RLock()
|
||||
empty := len(this.m) == 0
|
||||
this.mu.RUnlock()
|
||||
return empty
|
||||
}
|
||||
|
||||
// 清空哈希表
|
||||
func (this *StringInterfaceMap) Clear() {
|
||||
this.mu.Lock()
|
||||
this.m = make(map[string]interface{})
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 并发安全写锁操作,使用自定义方法执行加锁修改操作
|
||||
func (this *StringInterfaceMap) LockFunc(f func(m map[string]interface{})) {
|
||||
this.mu.Lock(true)
|
||||
defer this.mu.Unlock(true)
|
||||
f(this.m)
|
||||
}
|
||||
|
||||
// 并发安全读锁操作,使用自定义方法执行加锁读取操作
|
||||
func (this *StringInterfaceMap) RLockFunc(f func(m map[string]interface{})) {
|
||||
this.mu.RLock(true)
|
||||
defer this.mu.RUnlock(true)
|
||||
f(this.m)
|
||||
}
|
||||
@ -1,221 +0,0 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
//
|
||||
|
||||
package gmap
|
||||
|
||||
import "gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
|
||||
type StringStringMap struct {
|
||||
mu *rwmutex.RWMutex
|
||||
m map[string]string
|
||||
}
|
||||
|
||||
func NewStringStringMap(safe...bool) *StringStringMap {
|
||||
return &StringStringMap{
|
||||
m : make(map[string]string),
|
||||
mu : rwmutex.New(safe...),
|
||||
}
|
||||
}
|
||||
|
||||
// 给定回调函数对原始内容进行遍历,回调函数返回true表示继续遍历,否则停止遍历
|
||||
func (this *StringStringMap) Iterator(f func (k string, v string) bool) {
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
for k, v := range this.m {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 哈希表克隆
|
||||
func (this *StringStringMap) Clone() map[string]string {
|
||||
m := make(map[string]string)
|
||||
this.mu.RLock()
|
||||
for k, v := range this.m {
|
||||
m[k] = v
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
return m
|
||||
}
|
||||
|
||||
// 设置键值对
|
||||
func (this *StringStringMap) Set(key string, val string) {
|
||||
this.mu.Lock()
|
||||
this.m[key] = val
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 批量设置键值对
|
||||
func (this *StringStringMap) BatchSet(m map[string]string) {
|
||||
this.mu.Lock()
|
||||
for k, v := range m {
|
||||
this.m[k] = v
|
||||
}
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 获取键值
|
||||
func (this *StringStringMap) Get(key string) string {
|
||||
this.mu.RLock()
|
||||
val, _ := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// 设置kv缓存键值对,内部会对键名的存在性使用写锁进行二次检索确认,如果存在则不再写入;返回键名对应的键值。
|
||||
// 在高并发下有用,防止数据写入的并发逻辑错误。
|
||||
func (this *StringStringMap) doSetWithLockCheck(key string, value string) string {
|
||||
this.mu.Lock()
|
||||
if v, ok := this.m[key]; ok {
|
||||
this.mu.Unlock()
|
||||
return v
|
||||
}
|
||||
this.m[key] = value
|
||||
this.mu.Unlock()
|
||||
return value
|
||||
}
|
||||
|
||||
// 当键名存在时返回其键值,否则写入指定的键值
|
||||
func (this *StringStringMap) GetOrSet(key string, value string) string {
|
||||
this.mu.RLock()
|
||||
v, ok := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
if !ok {
|
||||
return this.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// 当键名存在时返回其键值,否则写入指定的键值,键值由指定的函数生成
|
||||
func (this *StringStringMap) GetOrSetFunc(key string, f func() string) string {
|
||||
this.mu.RLock()
|
||||
v, ok := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
if !ok {
|
||||
return this.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// 与GetOrSetFunc不同的是,f是在写锁机制内执行
|
||||
func (this *StringStringMap) GetOrSetFuncLock(key string, f func() string) string {
|
||||
this.mu.RLock()
|
||||
val, ok := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
if !ok {
|
||||
this.mu.Lock()
|
||||
defer this.mu.Unlock()
|
||||
if v, ok := this.m[key]; ok {
|
||||
this.mu.Unlock()
|
||||
return v
|
||||
}
|
||||
val = f()
|
||||
this.m[key] = val
|
||||
return val
|
||||
} else {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
// 当键名不存在时写入,并返回true;否则返回false。
|
||||
func (this *StringStringMap) SetIfNotExist(key string, value string) bool {
|
||||
if !this.Contains(key) {
|
||||
this.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 批量删除键值对
|
||||
func (this *StringStringMap) BatchRemove(keys []string) {
|
||||
this.mu.Lock()
|
||||
for _, key := range keys {
|
||||
delete(this.m, key)
|
||||
}
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 返回对应的键值,并删除该键值
|
||||
func (this *StringStringMap) Remove(key string) string {
|
||||
this.mu.Lock()
|
||||
val, exists := this.m[key]
|
||||
if exists {
|
||||
delete(this.m, key)
|
||||
}
|
||||
this.mu.Unlock()
|
||||
return val
|
||||
}
|
||||
|
||||
// 返回键列表
|
||||
func (this *StringStringMap) Keys() []string {
|
||||
this.mu.RLock()
|
||||
keys := make([]string, 0)
|
||||
for key, _ := range this.m {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
return keys
|
||||
}
|
||||
|
||||
// 返回值列表(注意是随机排序)
|
||||
func (this *StringStringMap) Values() []string {
|
||||
this.mu.RLock()
|
||||
vals := make([]string, 0)
|
||||
for _, val := range this.m {
|
||||
vals = append(vals, val)
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
return vals
|
||||
}
|
||||
|
||||
// 是否存在某个键
|
||||
func (this *StringStringMap) Contains(key string) bool {
|
||||
this.mu.RLock()
|
||||
_, exists := this.m[key]
|
||||
this.mu.RUnlock()
|
||||
return exists
|
||||
}
|
||||
|
||||
// 哈希表大小
|
||||
func (this *StringStringMap) Size() int {
|
||||
this.mu.RLock()
|
||||
length := len(this.m)
|
||||
this.mu.RUnlock()
|
||||
return length
|
||||
}
|
||||
|
||||
// 哈希表是否为空
|
||||
func (this *StringStringMap) IsEmpty() bool {
|
||||
this.mu.RLock()
|
||||
empty := len(this.m) == 0
|
||||
this.mu.RUnlock()
|
||||
return empty
|
||||
}
|
||||
|
||||
// 清空哈希表
|
||||
func (this *StringStringMap) Clear() {
|
||||
this.mu.Lock()
|
||||
this.m = make(map[string]string)
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 并发安全写锁操作,使用自定义方法执行加锁修改操作
|
||||
func (this *StringStringMap) LockFunc(f func(m map[string]string)) {
|
||||
this.mu.Lock(true)
|
||||
defer this.mu.Unlock(true)
|
||||
f(this.m)
|
||||
}
|
||||
|
||||
// 并发安全读锁操作,使用自定义方法执行加锁读取操作
|
||||
func (this *StringStringMap) RLockFunc(f func(m map[string]string)) {
|
||||
this.mu.RLock(true)
|
||||
defer this.mu.RUnlock(true)
|
||||
f(this.m)
|
||||
}
|
||||
30
g/container/gmap/gmap_tree_map.go
Normal file
30
g/container/gmap/gmap_tree_map.go
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with gm file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/gtree"
|
||||
)
|
||||
|
||||
// Map based on red-black tree, alias of RedBlackTree.
|
||||
type TreeMap = gtree.RedBlackTree
|
||||
|
||||
// NewTreeMap instantiates a tree map with the custom comparator.
|
||||
// The parameter <unsafe> used to specify whether using tree in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewTreeMap(comparator func(v1, v2 interface{}) int, unsafe ...bool) *TreeMap {
|
||||
return gtree.NewRedBlackTree(comparator, unsafe...)
|
||||
}
|
||||
|
||||
// NewTreeMapFrom instantiates a tree map with the custom comparator and <data> map.
|
||||
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
|
||||
// there might be some concurrent-safe issues when changing the map outside.
|
||||
// The parameter <unsafe> used to specify whether using tree in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewTreeMapFrom(comparator func(v1, v2 interface{}) int, data map[interface{}]interface{}, unsafe ...bool) *TreeMap {
|
||||
return gtree.NewRedBlackTreeFrom(comparator, data, unsafe...)
|
||||
}
|
||||
125
g/container/gmap/gmap_z_basic_test.go
Normal file
125
g/container/gmap/gmap_z_basic_test.go
Normal file
@ -0,0 +1,125 @@
|
||||
// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with gm file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gmap_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/gmap"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func getValue() interface{} {
|
||||
return 3
|
||||
}
|
||||
|
||||
func Test_Map_Basic(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
m := gmap.New()
|
||||
m.Set("key1", "val1")
|
||||
gtest.Assert(m.Keys(), []interface{}{"key1"})
|
||||
|
||||
gtest.Assert(m.Get("key1"), "val1")
|
||||
gtest.Assert(m.Size(), 1)
|
||||
gtest.Assert(m.IsEmpty(), false)
|
||||
|
||||
gtest.Assert(m.GetOrSet("key2", "val2"), "val2")
|
||||
gtest.Assert(m.SetIfNotExist("key2", "val2"), false)
|
||||
|
||||
gtest.Assert(m.SetIfNotExist("key3", "val3"), true)
|
||||
|
||||
gtest.Assert(m.Remove("key2"), "val2")
|
||||
gtest.Assert(m.Contains("key2"), false)
|
||||
|
||||
gtest.AssertIN("key3", m.Keys())
|
||||
gtest.AssertIN("key1", m.Keys())
|
||||
gtest.AssertIN("val3", m.Values())
|
||||
gtest.AssertIN("val1", m.Values())
|
||||
|
||||
m.Flip()
|
||||
gtest.Assert(m.Map(), map[interface{}]interface{}{"val3": "key3", "val1": "key1"})
|
||||
|
||||
m.Clear()
|
||||
gtest.Assert(m.Size(), 0)
|
||||
gtest.Assert(m.IsEmpty(), true)
|
||||
|
||||
m2 := gmap.NewFrom(map[interface{}]interface{}{1: 1, "key1": "val1"})
|
||||
gtest.Assert(m2.Map(), map[interface{}]interface{}{1: 1, "key1": "val1"})
|
||||
})
|
||||
}
|
||||
func Test_Map_Set_Fun(t *testing.T) {
|
||||
m := gmap.New()
|
||||
m.GetOrSetFunc("fun", getValue)
|
||||
m.GetOrSetFuncLock("funlock", getValue)
|
||||
gtest.Assert(m.Get("funlock"), 3)
|
||||
gtest.Assert(m.Get("fun"), 3)
|
||||
m.GetOrSetFunc("fun", getValue)
|
||||
gtest.Assert(m.SetIfNotExistFunc("fun", getValue), false)
|
||||
gtest.Assert(m.SetIfNotExistFuncLock("funlock", getValue), false)
|
||||
}
|
||||
|
||||
func Test_Map_Batch(t *testing.T) {
|
||||
m := gmap.New()
|
||||
m.Sets(map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
|
||||
gtest.Assert(m.Map(), map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
|
||||
m.Removes([]interface{}{"key1", 1})
|
||||
gtest.Assert(m.Map(), map[interface{}]interface{}{"key2": "val2", "key3": "val3"})
|
||||
}
|
||||
func Test_Map_Iterator(t *testing.T) {
|
||||
expect := map[interface{}]interface{}{1: 1, "key1": "val1"}
|
||||
|
||||
m := gmap.NewFrom(expect)
|
||||
m.Iterator(func(k interface{}, v interface{}) bool {
|
||||
gtest.Assert(expect[k], v)
|
||||
return true
|
||||
})
|
||||
// 断言返回值对遍历控制
|
||||
i := 0
|
||||
j := 0
|
||||
m.Iterator(func(k interface{}, v interface{}) bool {
|
||||
i++
|
||||
return true
|
||||
})
|
||||
m.Iterator(func(k interface{}, v interface{}) bool {
|
||||
j++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(i, 2)
|
||||
gtest.Assert(j, 1)
|
||||
}
|
||||
|
||||
func Test_Map_Lock(t *testing.T) {
|
||||
expect := map[interface{}]interface{}{1: 1, "key1": "val1"}
|
||||
|
||||
m := gmap.NewFrom(expect)
|
||||
m.LockFunc(func(m map[interface{}]interface{}) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
m.RLockFunc(func(m map[interface{}]interface{}) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Map_Clone(t *testing.T) {
|
||||
//clone 方法是深克隆
|
||||
m := gmap.NewFrom(map[interface{}]interface{}{1: 1, "key1": "val1"})
|
||||
m_clone := m.Clone()
|
||||
m.Remove(1)
|
||||
//修改原 map,clone 后的 map 不影响
|
||||
gtest.AssertIN(1, m_clone.Keys())
|
||||
|
||||
m_clone.Remove("key1")
|
||||
//修改clone map,原 map 不影响
|
||||
gtest.AssertIN("key1", m.Keys())
|
||||
}
|
||||
func Test_Map_Basic_Merge(t *testing.T) {
|
||||
m1 := gmap.New()
|
||||
m2 := gmap.New()
|
||||
m1.Set("key1", "val1")
|
||||
m2.Set("key2", "val2")
|
||||
m1.Merge(m2)
|
||||
gtest.Assert(m1.Map(), map[interface{}]interface{}{"key1": "val1", "key2": "val2"})
|
||||
}
|
||||
55
g/container/gmap/gmap_z_bench_maps_test.go
Normal file
55
g/container/gmap/gmap_z_bench_maps_test.go
Normal file
@ -0,0 +1,55 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with gm file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// go test *.go -bench=".*" -benchmem
|
||||
|
||||
package gmap_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/gmap"
|
||||
"github.com/gogf/gf/g/util/gutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var hashMap = gmap.New()
|
||||
var listMap = gmap.NewListMap()
|
||||
var treeMap = gmap.NewTreeMap(gutil.ComparatorInt)
|
||||
|
||||
func Benchmark_HashMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
hashMap.Set(i, i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_ListMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
listMap.Set(i, i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_TreeMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
treeMap.Set(i, i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_HashMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
hashMap.Get(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_ListMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
listMap.Get(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_TreeMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
treeMap.Get(i)
|
||||
}
|
||||
}
|
||||
107
g/container/gmap/gmap_z_bench_safe_test.go
Normal file
107
g/container/gmap/gmap_z_bench_safe_test.go
Normal file
@ -0,0 +1,107 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with gm file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// go test *.go -bench=".*" -benchmem
|
||||
|
||||
package gmap_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/gmap"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var ififm = gmap.New()
|
||||
var iim = gmap.NewIntIntMap()
|
||||
var iifm = gmap.NewIntAnyMap()
|
||||
var ism = gmap.NewIntStrMap()
|
||||
var sim = gmap.NewStrIntMap()
|
||||
var sifm = gmap.NewStrAnyMap()
|
||||
var ssm = gmap.NewStrStrMap()
|
||||
|
||||
func Benchmark_IntIntMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
iim.Set(i, i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_IntAnyMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
iifm.Set(i, i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_IntStrMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ism.Set(i, strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_AnyAnyMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ififm.Set(i, i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_StrIntMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
sim.Set(strconv.Itoa(i), i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_StrAnyMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
sifm.Set(strconv.Itoa(i), i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_StrStrMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ssm.Set(strconv.Itoa(i), strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_IntIntMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
iim.Get(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_IntAnyMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
iifm.Get(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_IntStrMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ism.Get(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_AnyAnyMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ififm.Get(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_StrIntMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
sim.Get(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_StrAnyMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
sifm.Get(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_StrStrMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ssm.Get(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
54
g/container/gmap/gmap_z_bench_syncmap_test.go
Normal file
54
g/container/gmap/gmap_z_bench_syncmap_test.go
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with gm file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// go test *.go -bench=".*" -benchmem
|
||||
|
||||
package gmap_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/gmap"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var m1 = gmap.NewIntIntMap()
|
||||
var m2 = sync.Map{}
|
||||
|
||||
func BenchmarkGmapSet(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
m1.Set(i, i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSyncmapSet(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
m2.Store(i, i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGmapGet(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
m1.Get(i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSyncmapGet(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
m2.Load(i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGmapRemove(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
m1.Remove(i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSyncmapRmove(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
m2.Delete(i)
|
||||
}
|
||||
}
|
||||
111
g/container/gmap/gmap_z_bench_unsafe_test.go
Normal file
111
g/container/gmap/gmap_z_bench_unsafe_test.go
Normal file
@ -0,0 +1,111 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with gm file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// go test *.go -bench=".*" -benchmem
|
||||
|
||||
package gmap_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/gmap"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var ififmUnsafe = gmap.New(true)
|
||||
var iimUnsafe = gmap.NewIntIntMap(true)
|
||||
var iifmUnsafe = gmap.NewIntAnyMap(true)
|
||||
var ismUnsafe = gmap.NewIntStrMap(true)
|
||||
var simUnsafe = gmap.NewStrIntMap(true)
|
||||
var sifmUnsafe = gmap.NewStrAnyMap(true)
|
||||
var ssmUnsafe = gmap.NewStrStrMap(true)
|
||||
|
||||
// 写入性能测试
|
||||
|
||||
func Benchmark_Unsafe_IntIntMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
iimUnsafe.Set(i, i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_IntAnyMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
iifmUnsafe.Set(i, i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_IntStrMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ismUnsafe.Set(i, strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_AnyAnyMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ififmUnsafe.Set(i, i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_StrIntMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
simUnsafe.Set(strconv.Itoa(i), i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_StrAnyMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
sifmUnsafe.Set(strconv.Itoa(i), i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_StrStrMap_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ssmUnsafe.Set(strconv.Itoa(i), strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
// 读取性能测试
|
||||
|
||||
func Benchmark_Unsafe_IntIntMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
iimUnsafe.Get(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_IntAnyMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
iifmUnsafe.Get(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_IntStrMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ismUnsafe.Get(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_AnyAnyMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ififmUnsafe.Get(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_StrIntMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
simUnsafe.Get(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_StrAnyMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
sifmUnsafe.Get(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_StrStrMap_Get(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ssmUnsafe.Get(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
70
g/container/gmap/gmap_z_example_test.go
Normal file
70
g/container/gmap/gmap_z_example_test.go
Normal file
@ -0,0 +1,70 @@
|
||||
package gmap_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/container/gmap"
|
||||
)
|
||||
|
||||
func Example_Normal_Basic() {
|
||||
m := gmap.New()
|
||||
|
||||
//Add data
|
||||
m.Set("key1", "val1")
|
||||
|
||||
//Print size
|
||||
fmt.Println(m.Size())
|
||||
//output 1
|
||||
|
||||
add_map := make(map[interface{}]interface{})
|
||||
add_map["key2"] = "val2"
|
||||
add_map["key3"] = "val3"
|
||||
add_map[1] = 1
|
||||
|
||||
fmt.Println(m.Values())
|
||||
|
||||
//Batch add data
|
||||
m.Sets(add_map)
|
||||
|
||||
//Gets the value of the corresponding key
|
||||
key3_val := m.Get("key3")
|
||||
fmt.Println(key3_val)
|
||||
|
||||
//Get the value by key, or set it with given key-value if not exist.
|
||||
get_or_set_val := m.GetOrSet("key4", "val4")
|
||||
fmt.Println(get_or_set_val)
|
||||
|
||||
// Set key-value if the key does not exist, then return true; or else return false.
|
||||
is_set := m.SetIfNotExist("key3", "val3")
|
||||
fmt.Println(is_set)
|
||||
|
||||
//Remove key
|
||||
m.Remove("key2")
|
||||
fmt.Println(m.Keys())
|
||||
|
||||
//Batch remove keys
|
||||
remove_keys := []interface{}{"key1", 1}
|
||||
m.Removes(remove_keys)
|
||||
fmt.Println(m.Keys())
|
||||
|
||||
//Contains checks whether a key exists.
|
||||
is_contain := m.Contains("key3")
|
||||
fmt.Println(is_contain)
|
||||
|
||||
//Flip exchanges key-value of the map, it will change key-value to value-key.
|
||||
m.Flip()
|
||||
fmt.Println(m.Map())
|
||||
|
||||
// Clear deletes all data of the map,
|
||||
m.Clear()
|
||||
|
||||
fmt.Println(m.Size())
|
||||
|
||||
}
|
||||
func Example_Normal_Merge() {
|
||||
m1 := gmap.New()
|
||||
m2 := gmap.New()
|
||||
m1.Set("key1", "val1")
|
||||
m2.Set("key2", "val2")
|
||||
m1.Merge(m2)
|
||||
fmt.Println(m1.Map())
|
||||
}
|
||||
130
g/container/gmap/gmap_z_int_any_test.go
Normal file
130
g/container/gmap/gmap_z_int_any_test.go
Normal file
@ -0,0 +1,130 @@
|
||||
// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with gm file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gmap_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/gmap"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func getAny() interface{} {
|
||||
return 123
|
||||
}
|
||||
func intAnyCallBack(int, interface{}) bool {
|
||||
return true
|
||||
}
|
||||
func Test_IntAnyMap_Basic(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
m := gmap.NewIntAnyMap()
|
||||
m.Set(1, 1)
|
||||
|
||||
gtest.Assert(m.Get(1), 1)
|
||||
gtest.Assert(m.Size(), 1)
|
||||
gtest.Assert(m.IsEmpty(), false)
|
||||
|
||||
gtest.Assert(m.GetOrSet(2, "2"), "2")
|
||||
gtest.Assert(m.SetIfNotExist(2, "2"), false)
|
||||
|
||||
gtest.Assert(m.SetIfNotExist(3, 3), true)
|
||||
|
||||
gtest.Assert(m.Remove(2), "2")
|
||||
gtest.Assert(m.Contains(2), false)
|
||||
|
||||
gtest.AssertIN(3, m.Keys())
|
||||
gtest.AssertIN(1, m.Keys())
|
||||
gtest.AssertIN(3, m.Values())
|
||||
gtest.AssertIN(1, m.Values())
|
||||
m.Flip()
|
||||
gtest.Assert(m.Map(), map[interface{}]int{1: 1, 3: 3})
|
||||
|
||||
m.Clear()
|
||||
gtest.Assert(m.Size(), 0)
|
||||
gtest.Assert(m.IsEmpty(), true)
|
||||
|
||||
m2 := gmap.NewIntAnyMapFrom(map[int]interface{}{1: 1, 2: "2"})
|
||||
gtest.Assert(m2.Map(), map[int]interface{}{1: 1, 2: "2"})
|
||||
})
|
||||
}
|
||||
func Test_IntAnyMap_Set_Fun(t *testing.T) {
|
||||
m := gmap.NewIntAnyMap()
|
||||
|
||||
m.GetOrSetFunc(1, getAny)
|
||||
m.GetOrSetFuncLock(2, getAny)
|
||||
gtest.Assert(m.Get(1), 123)
|
||||
gtest.Assert(m.Get(2), 123)
|
||||
|
||||
gtest.Assert(m.SetIfNotExistFunc(1, getAny), false)
|
||||
gtest.Assert(m.SetIfNotExistFunc(3, getAny), true)
|
||||
|
||||
gtest.Assert(m.SetIfNotExistFuncLock(2, getAny), false)
|
||||
gtest.Assert(m.SetIfNotExistFuncLock(4, getAny), true)
|
||||
|
||||
}
|
||||
|
||||
func Test_IntAnyMap_Batch(t *testing.T) {
|
||||
m := gmap.NewIntAnyMap()
|
||||
|
||||
m.Sets(map[int]interface{}{1: 1, 2: "2", 3: 3})
|
||||
gtest.Assert(m.Map(), map[int]interface{}{1: 1, 2: "2", 3: 3})
|
||||
m.Removes([]int{1, 2})
|
||||
gtest.Assert(m.Map(), map[int]interface{}{3: 3})
|
||||
}
|
||||
func Test_IntAnyMap_Iterator(t *testing.T) {
|
||||
expect := map[int]interface{}{1: 1, 2: "2"}
|
||||
m := gmap.NewIntAnyMapFrom(expect)
|
||||
m.Iterator(func(k int, v interface{}) bool {
|
||||
gtest.Assert(expect[k], v)
|
||||
return true
|
||||
})
|
||||
// 断言返回值对遍历控制
|
||||
i := 0
|
||||
j := 0
|
||||
m.Iterator(func(k int, v interface{}) bool {
|
||||
i++
|
||||
return true
|
||||
})
|
||||
m.Iterator(func(k int, v interface{}) bool {
|
||||
j++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(i, "2")
|
||||
gtest.Assert(j, 1)
|
||||
|
||||
}
|
||||
|
||||
func Test_IntAnyMap_Lock(t *testing.T) {
|
||||
expect := map[int]interface{}{1: 1, 2: "2"}
|
||||
m := gmap.NewIntAnyMapFrom(expect)
|
||||
m.LockFunc(func(m map[int]interface{}) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
m.RLockFunc(func(m map[int]interface{}) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
}
|
||||
func Test_IntAnyMap_Clone(t *testing.T) {
|
||||
//clone 方法是深克隆
|
||||
m := gmap.NewIntAnyMapFrom(map[int]interface{}{1: 1, 2: "2"})
|
||||
|
||||
m_clone := m.Clone()
|
||||
m.Remove(1)
|
||||
//修改原 map,clone 后的 map 不影响
|
||||
gtest.AssertIN(1, m_clone.Keys())
|
||||
|
||||
m_clone.Remove(2)
|
||||
//修改clone map,原 map 不影响
|
||||
gtest.AssertIN(2, m.Keys())
|
||||
}
|
||||
func Test_IntAnyMap_Merge(t *testing.T) {
|
||||
m1 := gmap.NewIntAnyMap()
|
||||
m2 := gmap.NewIntAnyMap()
|
||||
m1.Set(1, 1)
|
||||
m2.Set(2, "2")
|
||||
m1.Merge(m2)
|
||||
gtest.Assert(m1.Map(), map[int]interface{}{1: 1, 2: "2"})
|
||||
}
|
||||
131
g/container/gmap/gmap_z_int_int_test.go
Normal file
131
g/container/gmap/gmap_z_int_int_test.go
Normal file
@ -0,0 +1,131 @@
|
||||
// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with gm file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gmap_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/gmap"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func getInt() int {
|
||||
return 123
|
||||
}
|
||||
func intIntCallBack(int, int) bool {
|
||||
return true
|
||||
}
|
||||
func Test_IntIntMap_Basic(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
m := gmap.NewIntIntMap()
|
||||
m.Set(1, 1)
|
||||
|
||||
gtest.Assert(m.Get(1), 1)
|
||||
gtest.Assert(m.Size(), 1)
|
||||
gtest.Assert(m.IsEmpty(), false)
|
||||
|
||||
gtest.Assert(m.GetOrSet(2, 2), 2)
|
||||
gtest.Assert(m.SetIfNotExist(2, 2), false)
|
||||
|
||||
gtest.Assert(m.SetIfNotExist(3, 3), true)
|
||||
|
||||
gtest.Assert(m.Remove(2), 2)
|
||||
gtest.Assert(m.Contains(2), false)
|
||||
|
||||
gtest.AssertIN(3, m.Keys())
|
||||
gtest.AssertIN(1, m.Keys())
|
||||
gtest.AssertIN(3, m.Values())
|
||||
gtest.AssertIN(1, m.Values())
|
||||
m.Flip()
|
||||
gtest.Assert(m.Map(), map[int]int{1: 1, 3: 3})
|
||||
|
||||
m.Clear()
|
||||
gtest.Assert(m.Size(), 0)
|
||||
gtest.Assert(m.IsEmpty(), true)
|
||||
|
||||
m2 := gmap.NewIntIntMapFrom(map[int]int{1: 1, 2: 2})
|
||||
gtest.Assert(m2.Map(), map[int]int{1: 1, 2: 2})
|
||||
})
|
||||
}
|
||||
func Test_IntIntMap_Set_Fun(t *testing.T) {
|
||||
m := gmap.NewIntIntMap()
|
||||
|
||||
m.GetOrSetFunc(1, getInt)
|
||||
m.GetOrSetFuncLock(2, getInt)
|
||||
gtest.Assert(m.Get(1), 123)
|
||||
gtest.Assert(m.Get(2), 123)
|
||||
gtest.Assert(m.SetIfNotExistFunc(1, getInt), false)
|
||||
gtest.Assert(m.SetIfNotExistFunc(3, getInt), true)
|
||||
|
||||
gtest.Assert(m.SetIfNotExistFuncLock(2, getInt), false)
|
||||
gtest.Assert(m.SetIfNotExistFuncLock(4, getInt), true)
|
||||
|
||||
}
|
||||
|
||||
func Test_IntIntMap_Batch(t *testing.T) {
|
||||
m := gmap.NewIntIntMap()
|
||||
|
||||
m.Sets(map[int]int{1: 1, 2: 2, 3: 3})
|
||||
m.Iterator(intIntCallBack)
|
||||
gtest.Assert(m.Map(), map[int]int{1: 1, 2: 2, 3: 3})
|
||||
m.Removes([]int{1, 2})
|
||||
gtest.Assert(m.Map(), map[int]int{3: 3})
|
||||
}
|
||||
|
||||
func Test_IntIntMap_Iterator(t *testing.T) {
|
||||
expect := map[int]int{1: 1, 2: 2}
|
||||
m := gmap.NewIntIntMapFrom(expect)
|
||||
m.Iterator(func(k int, v int) bool {
|
||||
gtest.Assert(expect[k], v)
|
||||
return true
|
||||
})
|
||||
// 断言返回值对遍历控制
|
||||
i := 0
|
||||
j := 0
|
||||
m.Iterator(func(k int, v int) bool {
|
||||
i++
|
||||
return true
|
||||
})
|
||||
m.Iterator(func(k int, v int) bool {
|
||||
j++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(i, 2)
|
||||
gtest.Assert(j, 1)
|
||||
}
|
||||
|
||||
func Test_IntIntMap_Lock(t *testing.T) {
|
||||
expect := map[int]int{1: 1, 2: 2}
|
||||
m := gmap.NewIntIntMapFrom(expect)
|
||||
m.LockFunc(func(m map[int]int) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
m.RLockFunc(func(m map[int]int) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
|
||||
}
|
||||
func Test_IntIntMap_Clone(t *testing.T) {
|
||||
//clone 方法是深克隆
|
||||
m := gmap.NewIntIntMapFrom(map[int]int{1: 1, 2: 2})
|
||||
|
||||
m_clone := m.Clone()
|
||||
m.Remove(1)
|
||||
//修改原 map,clone 后的 map 不影响
|
||||
gtest.AssertIN(1, m_clone.Keys())
|
||||
|
||||
m_clone.Remove(2)
|
||||
//修改clone map,原 map 不影响
|
||||
gtest.AssertIN(2, m.Keys())
|
||||
}
|
||||
func Test_IntIntMap_Merge(t *testing.T) {
|
||||
m1 := gmap.NewIntIntMap()
|
||||
m2 := gmap.NewIntIntMap()
|
||||
m1.Set(1, 1)
|
||||
m2.Set(2, 2)
|
||||
m1.Merge(m2)
|
||||
gtest.Assert(m1.Map(), map[int]int{1: 1, 2: 2})
|
||||
}
|
||||
135
g/container/gmap/gmap_z_int_str_test.go
Normal file
135
g/container/gmap/gmap_z_int_str_test.go
Normal file
@ -0,0 +1,135 @@
|
||||
// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with gm file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gmap_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/gmap"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func getStr() string {
|
||||
return "z"
|
||||
}
|
||||
func intStrCallBack(int, string) bool {
|
||||
return true
|
||||
}
|
||||
func Test_IntStrMap_Basic(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
m := gmap.NewIntStrMap()
|
||||
m.Set(1, "a")
|
||||
|
||||
gtest.Assert(m.Get(1), "a")
|
||||
gtest.Assert(m.Size(), 1)
|
||||
gtest.Assert(m.IsEmpty(), false)
|
||||
|
||||
gtest.Assert(m.GetOrSet(2, "b"), "b")
|
||||
gtest.Assert(m.SetIfNotExist(2, "b"), false)
|
||||
|
||||
gtest.Assert(m.SetIfNotExist(3, "c"), true)
|
||||
|
||||
gtest.Assert(m.Remove(2), "b")
|
||||
gtest.Assert(m.Contains(2), false)
|
||||
|
||||
gtest.AssertIN(3, m.Keys())
|
||||
gtest.AssertIN(1, m.Keys())
|
||||
gtest.AssertIN("a", m.Values())
|
||||
gtest.AssertIN("c", m.Values())
|
||||
|
||||
//反转之后不成为以下 map,flip 操作只是翻转原 map
|
||||
//gtest.Assert(m.Map(), map[string]int{"a": 1, "c": 3})
|
||||
m_f := gmap.NewIntStrMap()
|
||||
m_f.Set(1, "2")
|
||||
m_f.Flip()
|
||||
gtest.Assert(m_f.Map(), map[int]string{2: "1"})
|
||||
|
||||
m.Clear()
|
||||
gtest.Assert(m.Size(), 0)
|
||||
gtest.Assert(m.IsEmpty(), true)
|
||||
|
||||
m2 := gmap.NewIntStrMapFrom(map[int]string{1: "a", 2: "b"})
|
||||
gtest.Assert(m2.Map(), map[int]string{1: "a", 2: "b"})
|
||||
})
|
||||
}
|
||||
func Test_IntStrMap_Set_Fun(t *testing.T) {
|
||||
m := gmap.NewIntStrMap()
|
||||
|
||||
m.GetOrSetFunc(1, getStr)
|
||||
m.GetOrSetFuncLock(2, getStr)
|
||||
gtest.Assert(m.Get(1), "z")
|
||||
gtest.Assert(m.Get(2), "z")
|
||||
gtest.Assert(m.SetIfNotExistFunc(1, getStr), false)
|
||||
gtest.Assert(m.SetIfNotExistFunc(3, getStr), true)
|
||||
|
||||
gtest.Assert(m.SetIfNotExistFuncLock(2, getStr), false)
|
||||
gtest.Assert(m.SetIfNotExistFuncLock(4, getStr), true)
|
||||
|
||||
}
|
||||
|
||||
func Test_IntStrMap_Batch(t *testing.T) {
|
||||
m := gmap.NewIntStrMap()
|
||||
|
||||
m.Sets(map[int]string{1: "a", 2: "b", 3: "c"})
|
||||
gtest.Assert(m.Map(), map[int]string{1: "a", 2: "b", 3: "c"})
|
||||
m.Removes([]int{1, 2})
|
||||
gtest.Assert(m.Map(), map[int]interface{}{3: "c"})
|
||||
}
|
||||
func Test_IntStrMap_Iterator(t *testing.T) {
|
||||
expect := map[int]string{1: "a", 2: "b"}
|
||||
m := gmap.NewIntStrMapFrom(expect)
|
||||
m.Iterator(func(k int, v string) bool {
|
||||
gtest.Assert(expect[k], v)
|
||||
return true
|
||||
})
|
||||
// 断言返回值对遍历控制
|
||||
i := 0
|
||||
j := 0
|
||||
m.Iterator(func(k int, v string) bool {
|
||||
i++
|
||||
return true
|
||||
})
|
||||
m.Iterator(func(k int, v string) bool {
|
||||
j++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(i, 2)
|
||||
gtest.Assert(j, 1)
|
||||
}
|
||||
|
||||
func Test_IntStrMap_Lock(t *testing.T) {
|
||||
|
||||
expect := map[int]string{1: "a", 2: "b", 3: "c"}
|
||||
m := gmap.NewIntStrMapFrom(expect)
|
||||
m.LockFunc(func(m map[int]string) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
m.RLockFunc(func(m map[int]string) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
|
||||
}
|
||||
func Test_IntStrMap_Clone(t *testing.T) {
|
||||
//clone 方法是深克隆
|
||||
m := gmap.NewIntStrMapFrom(map[int]string{1: "a", 2: "b", 3: "c"})
|
||||
|
||||
m_clone := m.Clone()
|
||||
m.Remove(1)
|
||||
//修改原 map,clone 后的 map 不影响
|
||||
gtest.AssertIN(1, m_clone.Keys())
|
||||
|
||||
m_clone.Remove(2)
|
||||
//修改clone map,原 map 不影响
|
||||
gtest.AssertIN(2, m.Keys())
|
||||
}
|
||||
func Test_IntStrMap_Merge(t *testing.T) {
|
||||
m1 := gmap.NewIntStrMap()
|
||||
m2 := gmap.NewIntStrMap()
|
||||
m1.Set(1, "a")
|
||||
m2.Set(2, "b")
|
||||
m1.Merge(m2)
|
||||
gtest.Assert(m1.Map(), map[int]string{1: "a", 2: "b"})
|
||||
}
|
||||
120
g/container/gmap/gmap_z_link_map_test.go
Normal file
120
g/container/gmap/gmap_z_link_map_test.go
Normal file
@ -0,0 +1,120 @@
|
||||
// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with gm file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gmap_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/container/gmap"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_List_Map_Basic(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
m := gmap.NewListMap()
|
||||
m.Set("key1", "val1")
|
||||
gtest.Assert(m.Keys(), []interface{}{"key1"})
|
||||
|
||||
gtest.Assert(m.Get("key1"), "val1")
|
||||
gtest.Assert(m.Size(), 1)
|
||||
gtest.Assert(m.IsEmpty(), false)
|
||||
|
||||
gtest.Assert(m.GetOrSet("key2", "val2"), "val2")
|
||||
gtest.Assert(m.SetIfNotExist("key2", "val2"), false)
|
||||
|
||||
gtest.Assert(m.SetIfNotExist("key3", "val3"), true)
|
||||
gtest.Assert(m.Remove("key2"), "val2")
|
||||
gtest.Assert(m.Contains("key2"), false)
|
||||
|
||||
gtest.AssertIN("key3", m.Keys())
|
||||
gtest.AssertIN("key1", m.Keys())
|
||||
gtest.AssertIN("val3", m.Values())
|
||||
gtest.AssertIN("val1", m.Values())
|
||||
|
||||
m.Flip()
|
||||
|
||||
gtest.Assert(m.Map(), map[interface{}]interface{}{"val3": "key3", "val1": "key1"})
|
||||
|
||||
m.Clear()
|
||||
gtest.Assert(m.Size(), 0)
|
||||
gtest.Assert(m.IsEmpty(), true)
|
||||
|
||||
m2 := gmap.NewListMapFrom(map[interface{}]interface{}{1: 1, "key1": "val1"})
|
||||
gtest.Assert(m2.Map(), map[interface{}]interface{}{1: 1, "key1": "val1"})
|
||||
})
|
||||
}
|
||||
func Test_List_Map_Set_Fun(t *testing.T) {
|
||||
m := gmap.NewListMap()
|
||||
m.GetOrSetFunc("fun", getValue)
|
||||
m.GetOrSetFuncLock("funlock", getValue)
|
||||
gtest.Assert(m.Get("funlock"), 3)
|
||||
gtest.Assert(m.Get("fun"), 3)
|
||||
m.GetOrSetFunc("fun", getValue)
|
||||
gtest.Assert(m.SetIfNotExistFunc("fun", getValue), false)
|
||||
gtest.Assert(m.SetIfNotExistFuncLock("funlock", getValue), false)
|
||||
}
|
||||
|
||||
func Test_List_Map_Batch(t *testing.T) {
|
||||
m := gmap.NewListMap()
|
||||
m.Sets(map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
|
||||
gtest.Assert(m.Map(), map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
|
||||
m.Removes([]interface{}{"key1", 1})
|
||||
gtest.Assert(m.Map(), map[interface{}]interface{}{"key2": "val2", "key3": "val3"})
|
||||
}
|
||||
func Test_List_Map_Iterator(t *testing.T) {
|
||||
expect := map[interface{}]interface{}{1: 1, "key1": "val1"}
|
||||
|
||||
m := gmap.NewListMapFrom(expect)
|
||||
m.Iterator(func(k interface{}, v interface{}) bool {
|
||||
gtest.Assert(expect[k], v)
|
||||
return true
|
||||
})
|
||||
// 断言返回值对遍历控制
|
||||
i := 0
|
||||
j := 0
|
||||
m.Iterator(func(k interface{}, v interface{}) bool {
|
||||
i++
|
||||
return true
|
||||
})
|
||||
m.Iterator(func(k interface{}, v interface{}) bool {
|
||||
j++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(i, 2)
|
||||
gtest.Assert(j, 1)
|
||||
}
|
||||
|
||||
func Test_List_Map_Clone(t *testing.T) {
|
||||
//clone 方法是深克隆
|
||||
m := gmap.NewListMapFrom(map[interface{}]interface{}{1: 1, "key1": "val1"})
|
||||
m_clone := m.Clone()
|
||||
m.Remove(1)
|
||||
//修改原 map,clone 后的 map 不影响
|
||||
gtest.AssertIN(1, m_clone.Keys())
|
||||
|
||||
m_clone.Remove("key1")
|
||||
//修改clone map,原 map 不影响
|
||||
gtest.AssertIN("key1", m.Keys())
|
||||
}
|
||||
|
||||
func Test_List_Map_Basic_Merge(t *testing.T) {
|
||||
m1 := gmap.NewListMap()
|
||||
m2 := gmap.NewListMap()
|
||||
m1.Set("key1", "val1")
|
||||
m2.Set("key2", "val2")
|
||||
m1.Merge(m2)
|
||||
gtest.Assert(m1.Map(), map[interface{}]interface{}{"key1": "val1", "key2": "val2"})
|
||||
}
|
||||
|
||||
func Test_List_Map_Order(t *testing.T) {
|
||||
m := gmap.NewListMap()
|
||||
m.Set("k1", "v1")
|
||||
m.Set("k2", "v2")
|
||||
m.Set("k3", "v3")
|
||||
gtest.Assert(m.Keys(), g.Slice{"k1", "k2", "k3"})
|
||||
gtest.Assert(m.Values(), g.Slice{"v1", "v2", "v3"})
|
||||
}
|
||||
128
g/container/gmap/gmap_z_str_any_test.go
Normal file
128
g/container/gmap/gmap_z_str_any_test.go
Normal file
@ -0,0 +1,128 @@
|
||||
// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with gm file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gmap_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/gmap"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func stringAnyCallBack(string, interface{}) bool {
|
||||
return true
|
||||
}
|
||||
func Test_StrAnyMap_Basic(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
m := gmap.NewStrAnyMap()
|
||||
m.Set("a", 1)
|
||||
|
||||
gtest.Assert(m.Get("a"), 1)
|
||||
gtest.Assert(m.Size(), 1)
|
||||
gtest.Assert(m.IsEmpty(), false)
|
||||
|
||||
gtest.Assert(m.GetOrSet("b", "2"), "2")
|
||||
gtest.Assert(m.SetIfNotExist("b", "2"), false)
|
||||
|
||||
gtest.Assert(m.SetIfNotExist("c", 3), true)
|
||||
|
||||
gtest.Assert(m.Remove("b"), "2")
|
||||
gtest.Assert(m.Contains("b"), false)
|
||||
|
||||
gtest.AssertIN("c", m.Keys())
|
||||
gtest.AssertIN("a", m.Keys())
|
||||
gtest.AssertIN(3, m.Values())
|
||||
gtest.AssertIN(1, m.Values())
|
||||
|
||||
m.Flip()
|
||||
gtest.Assert(m.Map(), map[string]interface{}{"1": "a", "3": "c"})
|
||||
|
||||
m.Clear()
|
||||
gtest.Assert(m.Size(), 0)
|
||||
gtest.Assert(m.IsEmpty(), true)
|
||||
|
||||
m2 := gmap.NewStrAnyMapFrom(map[string]interface{}{"a": 1, "b": "2"})
|
||||
gtest.Assert(m2.Map(), map[string]interface{}{"a": 1, "b": "2"})
|
||||
})
|
||||
}
|
||||
func Test_StrAnyMap_Set_Fun(t *testing.T) {
|
||||
m := gmap.NewStrAnyMap()
|
||||
|
||||
m.GetOrSetFunc("a", getAny)
|
||||
m.GetOrSetFuncLock("b", getAny)
|
||||
gtest.Assert(m.Get("a"), 123)
|
||||
gtest.Assert(m.Get("b"), 123)
|
||||
gtest.Assert(m.SetIfNotExistFunc("a", getAny), false)
|
||||
gtest.Assert(m.SetIfNotExistFunc("c", getAny), true)
|
||||
|
||||
gtest.Assert(m.SetIfNotExistFuncLock("b", getAny), false)
|
||||
gtest.Assert(m.SetIfNotExistFuncLock("d", getAny), true)
|
||||
|
||||
}
|
||||
|
||||
func Test_StrAnyMap_Batch(t *testing.T) {
|
||||
m := gmap.NewStrAnyMap()
|
||||
|
||||
m.Sets(map[string]interface{}{"a": 1, "b": "2", "c": 3})
|
||||
gtest.Assert(m.Map(), map[string]interface{}{"a": 1, "b": "2", "c": 3})
|
||||
m.Removes([]string{"a", "b"})
|
||||
gtest.Assert(m.Map(), map[string]interface{}{"c": 3})
|
||||
}
|
||||
|
||||
func Test_StrAnyMap_Iterator(t *testing.T) {
|
||||
expect := map[string]interface{}{"a": true, "b": false}
|
||||
m := gmap.NewStrAnyMapFrom(expect)
|
||||
m.Iterator(func(k string, v interface{}) bool {
|
||||
gtest.Assert(expect[k], v)
|
||||
return true
|
||||
})
|
||||
// 断言返回值对遍历控制
|
||||
i := 0
|
||||
j := 0
|
||||
m.Iterator(func(k string, v interface{}) bool {
|
||||
i++
|
||||
return true
|
||||
})
|
||||
m.Iterator(func(k string, v interface{}) bool {
|
||||
j++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(i, 2)
|
||||
gtest.Assert(j, 1)
|
||||
}
|
||||
|
||||
func Test_StrAnyMap_Lock(t *testing.T) {
|
||||
expect := map[string]interface{}{"a": true, "b": false}
|
||||
|
||||
m := gmap.NewStrAnyMapFrom(expect)
|
||||
m.LockFunc(func(m map[string]interface{}) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
m.RLockFunc(func(m map[string]interface{}) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
}
|
||||
func Test_StrAnyMap_Clone(t *testing.T) {
|
||||
//clone 方法是深克隆
|
||||
m := gmap.NewStrAnyMapFrom(map[string]interface{}{"a": 1, "b": "2"})
|
||||
|
||||
m_clone := m.Clone()
|
||||
m.Remove("a")
|
||||
//修改原 map,clone 后的 map 不影响
|
||||
gtest.AssertIN("a", m_clone.Keys())
|
||||
|
||||
m_clone.Remove("b")
|
||||
//修改clone map,原 map 不影响
|
||||
gtest.AssertIN("b", m.Keys())
|
||||
}
|
||||
func Test_StrAnyMap_Merge(t *testing.T) {
|
||||
m1 := gmap.NewStrAnyMap()
|
||||
m2 := gmap.NewStrAnyMap()
|
||||
m1.Set("a", 1)
|
||||
m2.Set("b", "2")
|
||||
m1.Merge(m2)
|
||||
gtest.Assert(m1.Map(), map[string]interface{}{"a": 1, "b": "2"})
|
||||
}
|
||||
131
g/container/gmap/gmap_z_str_int_test.go
Normal file
131
g/container/gmap/gmap_z_str_int_test.go
Normal file
@ -0,0 +1,131 @@
|
||||
// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with gm file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gmap_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/gmap"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func stringIntCallBack(string, int) bool {
|
||||
return true
|
||||
}
|
||||
func Test_StrIntMap_Basic(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
m := gmap.NewStrIntMap()
|
||||
m.Set("a", 1)
|
||||
|
||||
gtest.Assert(m.Get("a"), 1)
|
||||
gtest.Assert(m.Size(), 1)
|
||||
gtest.Assert(m.IsEmpty(), false)
|
||||
|
||||
gtest.Assert(m.GetOrSet("b", 2), 2)
|
||||
gtest.Assert(m.SetIfNotExist("b", 2), false)
|
||||
|
||||
gtest.Assert(m.SetIfNotExist("c", 3), true)
|
||||
|
||||
gtest.Assert(m.Remove("b"), 2)
|
||||
gtest.Assert(m.Contains("b"), false)
|
||||
|
||||
gtest.AssertIN("c", m.Keys())
|
||||
gtest.AssertIN("a", m.Keys())
|
||||
gtest.AssertIN(3, m.Values())
|
||||
gtest.AssertIN(1, m.Values())
|
||||
|
||||
m_f := gmap.NewStrIntMap()
|
||||
m_f.Set("1", 2)
|
||||
m_f.Flip()
|
||||
gtest.Assert(m_f.Map(), map[string]int{"2": 1})
|
||||
|
||||
m.Clear()
|
||||
gtest.Assert(m.Size(), 0)
|
||||
gtest.Assert(m.IsEmpty(), true)
|
||||
|
||||
m2 := gmap.NewStrIntMapFrom(map[string]int{"a": 1, "b": 2})
|
||||
gtest.Assert(m2.Map(), map[string]int{"a": 1, "b": 2})
|
||||
})
|
||||
}
|
||||
func Test_StrIntMap_Set_Fun(t *testing.T) {
|
||||
m := gmap.NewStrIntMap()
|
||||
|
||||
m.GetOrSetFunc("a", getInt)
|
||||
m.GetOrSetFuncLock("b", getInt)
|
||||
gtest.Assert(m.Get("a"), 123)
|
||||
gtest.Assert(m.Get("b"), 123)
|
||||
gtest.Assert(m.SetIfNotExistFunc("a", getInt), false)
|
||||
gtest.Assert(m.SetIfNotExistFunc("c", getInt), true)
|
||||
|
||||
gtest.Assert(m.SetIfNotExistFuncLock("b", getInt), false)
|
||||
gtest.Assert(m.SetIfNotExistFuncLock("d", getInt), true)
|
||||
|
||||
}
|
||||
|
||||
func Test_StrIntMap_Batch(t *testing.T) {
|
||||
m := gmap.NewStrIntMap()
|
||||
|
||||
m.Sets(map[string]int{"a": 1, "b": 2, "c": 3})
|
||||
gtest.Assert(m.Map(), map[string]int{"a": 1, "b": 2, "c": 3})
|
||||
m.Removes([]string{"a", "b"})
|
||||
gtest.Assert(m.Map(), map[string]int{"c": 3})
|
||||
}
|
||||
func Test_StrIntMap_Iterator(t *testing.T) {
|
||||
expect := map[string]int{"a": 1, "b": 2}
|
||||
m := gmap.NewStrIntMapFrom(expect)
|
||||
m.Iterator(func(k string, v int) bool {
|
||||
gtest.Assert(expect[k], v)
|
||||
return true
|
||||
})
|
||||
// 断言返回值对遍历控制
|
||||
i := 0
|
||||
j := 0
|
||||
m.Iterator(func(k string, v int) bool {
|
||||
i++
|
||||
return true
|
||||
})
|
||||
m.Iterator(func(k string, v int) bool {
|
||||
j++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(i, 2)
|
||||
gtest.Assert(j, 1)
|
||||
|
||||
}
|
||||
|
||||
func Test_StrIntMap_Lock(t *testing.T) {
|
||||
expect := map[string]int{"a": 1, "b": 2}
|
||||
|
||||
m := gmap.NewStrIntMapFrom(expect)
|
||||
m.LockFunc(func(m map[string]int) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
m.RLockFunc(func(m map[string]int) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_StrIntMap_Clone(t *testing.T) {
|
||||
//clone 方法是深克隆
|
||||
m := gmap.NewStrIntMapFrom(map[string]int{"a": 1, "b": 2, "c": 3})
|
||||
|
||||
m_clone := m.Clone()
|
||||
m.Remove("a")
|
||||
//修改原 map,clone 后的 map 不影响
|
||||
gtest.AssertIN("a", m_clone.Keys())
|
||||
|
||||
m_clone.Remove("b")
|
||||
//修改clone map,原 map 不影响
|
||||
gtest.AssertIN("b", m.Keys())
|
||||
}
|
||||
func Test_StrIntMap_Merge(t *testing.T) {
|
||||
m1 := gmap.NewStrIntMap()
|
||||
m2 := gmap.NewStrIntMap()
|
||||
m1.Set("a", 1)
|
||||
m2.Set("b", 2)
|
||||
m1.Merge(m2)
|
||||
gtest.Assert(m1.Map(), map[string]int{"a": 1, "b": 2})
|
||||
}
|
||||
128
g/container/gmap/gmap_z_str_str_test.go
Normal file
128
g/container/gmap/gmap_z_str_str_test.go
Normal file
@ -0,0 +1,128 @@
|
||||
// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with gm file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gmap_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/gmap"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func stringStrCallBack(string, string) bool {
|
||||
return true
|
||||
}
|
||||
func Test_StrStrMap_Basic(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
m := gmap.NewStrStrMap()
|
||||
m.Set("a", "a")
|
||||
|
||||
gtest.Assert(m.Get("a"), "a")
|
||||
gtest.Assert(m.Size(), 1)
|
||||
gtest.Assert(m.IsEmpty(), false)
|
||||
|
||||
gtest.Assert(m.GetOrSet("b", "b"), "b")
|
||||
gtest.Assert(m.SetIfNotExist("b", "b"), false)
|
||||
|
||||
gtest.Assert(m.SetIfNotExist("c", "c"), true)
|
||||
|
||||
gtest.Assert(m.Remove("b"), "b")
|
||||
gtest.Assert(m.Contains("b"), false)
|
||||
|
||||
gtest.AssertIN("c", m.Keys())
|
||||
gtest.AssertIN("a", m.Keys())
|
||||
gtest.AssertIN("a", m.Values())
|
||||
gtest.AssertIN("c", m.Values())
|
||||
|
||||
m.Flip()
|
||||
|
||||
gtest.Assert(m.Map(), map[string]string{"a": "a", "c": "c"})
|
||||
|
||||
m.Clear()
|
||||
gtest.Assert(m.Size(), 0)
|
||||
gtest.Assert(m.IsEmpty(), true)
|
||||
|
||||
m2 := gmap.NewStrStrMapFrom(map[string]string{"a": "a", "b": "b"})
|
||||
gtest.Assert(m2.Map(), map[string]string{"a": "a", "b": "b"})
|
||||
})
|
||||
}
|
||||
func Test_StrStrMap_Set_Fun(t *testing.T) {
|
||||
m := gmap.NewStrStrMap()
|
||||
|
||||
m.GetOrSetFunc("a", getStr)
|
||||
m.GetOrSetFuncLock("b", getStr)
|
||||
gtest.Assert(m.Get("a"), "z")
|
||||
gtest.Assert(m.Get("b"), "z")
|
||||
gtest.Assert(m.SetIfNotExistFunc("a", getStr), false)
|
||||
gtest.Assert(m.SetIfNotExistFunc("c", getStr), true)
|
||||
|
||||
gtest.Assert(m.SetIfNotExistFuncLock("b", getStr), false)
|
||||
gtest.Assert(m.SetIfNotExistFuncLock("d", getStr), true)
|
||||
|
||||
}
|
||||
|
||||
func Test_StrStrMap_Batch(t *testing.T) {
|
||||
m := gmap.NewStrStrMap()
|
||||
|
||||
m.Sets(map[string]string{"a": "a", "b": "b", "c": "c"})
|
||||
gtest.Assert(m.Map(), map[string]string{"a": "a", "b": "b", "c": "c"})
|
||||
m.Removes([]string{"a", "b"})
|
||||
gtest.Assert(m.Map(), map[string]string{"c": "c"})
|
||||
}
|
||||
func Test_StrStrMap_Iterator(t *testing.T) {
|
||||
expect := map[string]string{"a": "a", "b": "b"}
|
||||
m := gmap.NewStrStrMapFrom(expect)
|
||||
m.Iterator(func(k string, v string) bool {
|
||||
gtest.Assert(expect[k], v)
|
||||
return true
|
||||
})
|
||||
// 断言返回值对遍历控制
|
||||
i := 0
|
||||
j := 0
|
||||
m.Iterator(func(k string, v string) bool {
|
||||
i++
|
||||
return true
|
||||
})
|
||||
m.Iterator(func(k string, v string) bool {
|
||||
j++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(i, 2)
|
||||
gtest.Assert(j, 1)
|
||||
}
|
||||
|
||||
func Test_StrStrMap_Lock(t *testing.T) {
|
||||
expect := map[string]string{"a": "a", "b": "b"}
|
||||
|
||||
m := gmap.NewStrStrMapFrom(expect)
|
||||
m.LockFunc(func(m map[string]string) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
m.RLockFunc(func(m map[string]string) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
}
|
||||
func Test_StrStrMap_Clone(t *testing.T) {
|
||||
//clone 方法是深克隆
|
||||
m := gmap.NewStrStrMapFrom(map[string]string{"a": "a", "b": "b", "c": "c"})
|
||||
|
||||
m_clone := m.Clone()
|
||||
m.Remove("a")
|
||||
//修改原 map,clone 后的 map 不影响
|
||||
gtest.AssertIN("a", m_clone.Keys())
|
||||
|
||||
m_clone.Remove("b")
|
||||
//修改clone map,原 map 不影响
|
||||
gtest.AssertIN("b", m.Keys())
|
||||
}
|
||||
func Test_StrStrMap_Merge(t *testing.T) {
|
||||
m1 := gmap.NewStrStrMap()
|
||||
m2 := gmap.NewStrStrMap()
|
||||
m1.Set("a", "a")
|
||||
m2.Set("b", "b")
|
||||
m1.Merge(m2)
|
||||
gtest.Assert(m1.Map(), map[string]string{"a": "a", "b": "b"})
|
||||
}
|
||||
102
g/container/gmap/gmap_z_tree_map_test.go
Normal file
102
g/container/gmap/gmap_z_tree_map_test.go
Normal file
@ -0,0 +1,102 @@
|
||||
// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with gm file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gmap_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/gmap"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"github.com/gogf/gf/g/util/gutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_Tree_Map_Basic(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
m := gmap.NewTreeMap(gutil.ComparatorString)
|
||||
m.Set("key1", "val1")
|
||||
gtest.Assert(m.Keys(), []interface{}{"key1"})
|
||||
|
||||
gtest.Assert(m.Get("key1"), "val1")
|
||||
gtest.Assert(m.Size(), 1)
|
||||
gtest.Assert(m.IsEmpty(), false)
|
||||
|
||||
gtest.Assert(m.GetOrSet("key2", "val2"), "val2")
|
||||
gtest.Assert(m.SetIfNotExist("key2", "val2"), false)
|
||||
|
||||
gtest.Assert(m.SetIfNotExist("key3", "val3"), true)
|
||||
|
||||
gtest.Assert(m.Remove("key2"), "val2")
|
||||
gtest.Assert(m.Contains("key2"), false)
|
||||
|
||||
gtest.AssertIN("key3", m.Keys())
|
||||
gtest.AssertIN("key1", m.Keys())
|
||||
gtest.AssertIN("val3", m.Values())
|
||||
gtest.AssertIN("val1", m.Values())
|
||||
|
||||
m.Flip()
|
||||
gtest.Assert(m.Map(), map[interface{}]interface{}{"val3": "key3", "val1": "key1"})
|
||||
|
||||
m.Clear()
|
||||
gtest.Assert(m.Size(), 0)
|
||||
gtest.Assert(m.IsEmpty(), true)
|
||||
|
||||
m2 := gmap.NewTreeMapFrom(gutil.ComparatorString, map[interface{}]interface{}{1: 1, "key1": "val1"})
|
||||
gtest.Assert(m2.Map(), map[interface{}]interface{}{1: 1, "key1": "val1"})
|
||||
})
|
||||
}
|
||||
func Test_Tree_Map_Set_Fun(t *testing.T) {
|
||||
m := gmap.NewTreeMap(gutil.ComparatorString)
|
||||
m.GetOrSetFunc("fun", getValue)
|
||||
m.GetOrSetFuncLock("funlock", getValue)
|
||||
gtest.Assert(m.Get("funlock"), 3)
|
||||
gtest.Assert(m.Get("fun"), 3)
|
||||
m.GetOrSetFunc("fun", getValue)
|
||||
gtest.Assert(m.SetIfNotExistFunc("fun", getValue), false)
|
||||
gtest.Assert(m.SetIfNotExistFuncLock("funlock", getValue), false)
|
||||
}
|
||||
|
||||
func Test_Tree_Map_Batch(t *testing.T) {
|
||||
m := gmap.NewTreeMap(gutil.ComparatorString)
|
||||
m.Sets(map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
|
||||
gtest.Assert(m.Map(), map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
|
||||
m.Removes([]interface{}{"key1", 1})
|
||||
gtest.Assert(m.Map(), map[interface{}]interface{}{"key2": "val2", "key3": "val3"})
|
||||
}
|
||||
func Test_Tree_Map_Iterator(t *testing.T) {
|
||||
expect := map[interface{}]interface{}{1: 1, "key1": "val1"}
|
||||
|
||||
m := gmap.NewTreeMapFrom(gutil.ComparatorString, expect)
|
||||
m.Iterator(func(k interface{}, v interface{}) bool {
|
||||
gtest.Assert(expect[k], v)
|
||||
return true
|
||||
})
|
||||
// 断言返回值对遍历控制
|
||||
i := 0
|
||||
j := 0
|
||||
m.Iterator(func(k interface{}, v interface{}) bool {
|
||||
i++
|
||||
return true
|
||||
})
|
||||
m.Iterator(func(k interface{}, v interface{}) bool {
|
||||
j++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(i, 2)
|
||||
gtest.Assert(j, 1)
|
||||
}
|
||||
|
||||
func Test_Tree_Map_Clone(t *testing.T) {
|
||||
//clone 方法是深克隆
|
||||
m := gmap.NewTreeMapFrom(gutil.ComparatorString, map[interface{}]interface{}{1: 1, "key1": "val1"})
|
||||
m_clone := m.Clone()
|
||||
m.Remove(1)
|
||||
//修改原 map,clone 后的 map 不影响
|
||||
gtest.AssertIN(1, m_clone.Keys())
|
||||
|
||||
m_clone.Remove("key1")
|
||||
//修改clone map,原 map 不影响
|
||||
gtest.AssertIN("key1", m.Keys())
|
||||
}
|
||||
@ -1,121 +1,144 @@
|
||||
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package gpool provides a object-reusable concurrent-safe pool.
|
||||
// 对象复用池.
|
||||
// Package gpool provides object-reusable concurrent-safe pool.
|
||||
package gpool
|
||||
|
||||
import (
|
||||
"time"
|
||||
"errors"
|
||||
"gitee.com/johng/gf/g/os/gtime"
|
||||
"gitee.com/johng/gf/g/container/glist"
|
||||
"gitee.com/johng/gf/g/container/gtype"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/g/container/glist"
|
||||
"github.com/gogf/gf/g/container/gtype"
|
||||
"github.com/gogf/gf/g/os/gtime"
|
||||
"github.com/gogf/gf/g/os/gtimer"
|
||||
)
|
||||
|
||||
// 对象池
|
||||
// Object-Reusable Pool.
|
||||
type Pool struct {
|
||||
list *glist.List // 可用/闲置的文件指针链表
|
||||
closed *gtype.Bool // 连接池是否已关闭
|
||||
Expire int64 // (毫秒)闲置最大时间,超过该时间则被系统回收
|
||||
NewFunc func()(interface{}, error) // 创建对象的方法定义
|
||||
ExpireFunc func(interface{}) // 对象的过期销毁方法(当池对象销毁需要执行额外的销毁操作时,需要定义该方法)
|
||||
// 例如: net.Conn, os.File等对象都需要执行额外关闭操作
|
||||
list *glist.List // Available/idle list.
|
||||
closed *gtype.Bool // Whether the pool is closed.
|
||||
Expire int64 // Max idle time(ms), after which it is recycled.
|
||||
NewFunc func() (interface{}, error) // Callback function to create item.
|
||||
ExpireFunc func(interface{}) // Expired destruction function for objects.
|
||||
// This function needs to be defined when the pool object
|
||||
// needs to perform additional destruction operations.
|
||||
// Eg: net.Conn, os.File, etc.
|
||||
}
|
||||
|
||||
// 对象池数据项
|
||||
// Pool item.
|
||||
type poolItem struct {
|
||||
expire int64 // (毫秒)过期时间
|
||||
value interface{} // 对象值
|
||||
expire int64 // Expire time(millisecond).
|
||||
value interface{} // Value.
|
||||
}
|
||||
|
||||
// 创建一个对象池,为保证执行效率,过期时间一旦设定之后无法修改
|
||||
// expire = 0表示不过期,expire < 0表示使用完立即回收,expire > 0表示超时回收
|
||||
// 注意过期时间单位为**毫秒**
|
||||
func New(expire int, newFunc...func() (interface{}, error)) *Pool {
|
||||
r := &Pool {
|
||||
list : glist.New(),
|
||||
closed : gtype.NewBool(),
|
||||
Expire : int64(expire),
|
||||
}
|
||||
if len(newFunc) > 0 {
|
||||
r.NewFunc = newFunc[0]
|
||||
}
|
||||
go r.expireCheckingLoop()
|
||||
return r
|
||||
// Creation function for object.
|
||||
type NewFunc func() (interface{}, error)
|
||||
|
||||
// Destruction function for object.
|
||||
type ExpireFunc func(interface{})
|
||||
|
||||
// New returns a new object pool.
|
||||
// To ensure execution efficiency, the expiration time cannot be modified once it is set.
|
||||
//
|
||||
// Expiration logistics:
|
||||
// expire = 0 : not expired;
|
||||
// expire < 0 : immediate expired after use;
|
||||
// expire > 0 : timeout expired;
|
||||
// Note that the expiration time unit is ** milliseconds **.
|
||||
func New(expire int, newFunc NewFunc, expireFunc ...ExpireFunc) *Pool {
|
||||
r := &Pool{
|
||||
list: glist.New(),
|
||||
closed: gtype.NewBool(),
|
||||
Expire: int64(expire),
|
||||
NewFunc: newFunc,
|
||||
}
|
||||
if len(expireFunc) > 0 {
|
||||
r.ExpireFunc = expireFunc[0]
|
||||
}
|
||||
gtimer.AddSingleton(time.Second, r.checkExpire)
|
||||
return r
|
||||
}
|
||||
|
||||
// 设置对象过期销毁时的关闭方法
|
||||
func (p *Pool) SetExpireFunc(expireFunc func(interface{})) {
|
||||
p.ExpireFunc = expireFunc
|
||||
}
|
||||
|
||||
// 放一个临时对象到池中
|
||||
// Put puts an item to pool.
|
||||
func (p *Pool) Put(value interface{}) {
|
||||
item := &poolItem {
|
||||
value : value,
|
||||
}
|
||||
if p.Expire == 0 {
|
||||
item.expire = 0
|
||||
} else {
|
||||
item.expire = gtime.Millisecond() + p.Expire
|
||||
}
|
||||
p.list.PushBack(item)
|
||||
item := &poolItem{
|
||||
value: value,
|
||||
}
|
||||
if p.Expire == 0 {
|
||||
item.expire = 0
|
||||
} else {
|
||||
item.expire = gtime.Millisecond() + p.Expire
|
||||
}
|
||||
p.list.PushBack(item)
|
||||
}
|
||||
|
||||
// 清空对象池
|
||||
// Clear clears pool, which means it will remove all items from pool.
|
||||
func (p *Pool) Clear() {
|
||||
p.list.RemoveAll()
|
||||
p.list.RemoveAll()
|
||||
}
|
||||
|
||||
// 从池中获得一个临时对象
|
||||
// Get picks an item from pool.
|
||||
func (p *Pool) Get() (interface{}, error) {
|
||||
for !p.closed.Val() {
|
||||
if r := p.list.PopFront(); r != nil {
|
||||
f := r.(*poolItem)
|
||||
if f.expire == 0 || f.expire > gtime.Millisecond() {
|
||||
return f.value, nil
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if p.NewFunc != nil {
|
||||
return p.NewFunc()
|
||||
}
|
||||
return nil, errors.New("pool is empty")
|
||||
for !p.closed.Val() {
|
||||
if r := p.list.PopFront(); r != nil {
|
||||
f := r.(*poolItem)
|
||||
if f.expire == 0 || f.expire > gtime.Millisecond() {
|
||||
return f.value, nil
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if p.NewFunc != nil {
|
||||
return p.NewFunc()
|
||||
}
|
||||
return nil, errors.New("pool is empty")
|
||||
}
|
||||
|
||||
// 查询当前池中的对象数量
|
||||
// Size returns the count of available items of pool.
|
||||
func (p *Pool) Size() int {
|
||||
return p.list.Len()
|
||||
return p.list.Len()
|
||||
}
|
||||
|
||||
// 关闭池
|
||||
// Close closes the pool. If <p> has ExpireFunc,
|
||||
// then it automatically closes all items using this function before it's closed.
|
||||
func (p *Pool) Close() {
|
||||
p.closed.Set(true)
|
||||
p.closed.Set(true)
|
||||
}
|
||||
|
||||
// 超时检测循环
|
||||
func (p *Pool) expireCheckingLoop() {
|
||||
for !p.closed.Val() {
|
||||
for {
|
||||
if r := p.list.PopFront(); r != nil {
|
||||
item := r.(*poolItem)
|
||||
if item.expire == 0 || item.expire > gtime.Millisecond() {
|
||||
p.list.PushFront(item)
|
||||
break
|
||||
}
|
||||
if p.ExpireFunc != nil {
|
||||
p.ExpireFunc(item.value)
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
// checkExpire removes expired items from pool every second.
|
||||
func (p *Pool) checkExpire() {
|
||||
if p.closed.Val() {
|
||||
// If p has ExpireFunc,
|
||||
// then it must close all items using this function.
|
||||
if p.ExpireFunc != nil {
|
||||
for {
|
||||
if r := p.list.PopFront(); r != nil {
|
||||
p.ExpireFunc(r.(*poolItem).value)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
gtimer.Exit()
|
||||
}
|
||||
for {
|
||||
// TODO Do not use Pop and Push mechanism, which is not graceful.
|
||||
if r := p.list.PopFront(); r != nil {
|
||||
item := r.(*poolItem)
|
||||
if item.expire == 0 || item.expire > gtime.Millisecond() {
|
||||
p.list.PushFront(item)
|
||||
break
|
||||
}
|
||||
if p.ExpireFunc != nil {
|
||||
p.ExpireFunc(item.value)
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
42
g/container/gpool/gpool_bench_test.go
Normal file
42
g/container/gpool/gpool_bench_test.go
Normal file
@ -0,0 +1,42 @@
|
||||
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// go test *.go -bench=".*"
|
||||
|
||||
package gpool_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/gpool"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var pool = gpool.New(99999999, nil)
|
||||
var syncp = sync.Pool{}
|
||||
|
||||
func BenchmarkGPoolPut(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
pool.Put(i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGPoolGet(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
pool.Get()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSyncPoolPut(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
syncp.Put(i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGpoolGet(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
syncp.Get()
|
||||
}
|
||||
}
|
||||
@ -1,41 +0,0 @@
|
||||
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// go test *.go -bench=".*"
|
||||
|
||||
package gpool
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var pool = New(99999999)
|
||||
var syncp = sync.Pool{}
|
||||
|
||||
func BenchmarkGPoolPut(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
pool.Put(i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGPoolGet(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
pool.Get()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSyncPoolPut(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
syncp.Put(i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGpoolGet(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
syncp.Get()
|
||||
}
|
||||
}
|
||||
98
g/container/gpool/gpool_z_unit_test.go
Normal file
98
g/container/gpool/gpool_z_unit_test.go
Normal file
@ -0,0 +1,98 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gpool_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gogf/gf/g"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/g/container/gpool"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
)
|
||||
|
||||
var nf gpool.NewFunc = func() (i interface{}, e error) {
|
||||
return "hello", nil
|
||||
}
|
||||
|
||||
var assertIndex int = 0
|
||||
var ef gpool.ExpireFunc = func(i interface{}) {
|
||||
assertIndex++
|
||||
gtest.Assert(i, assertIndex)
|
||||
}
|
||||
|
||||
func Test_Gpool(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
//
|
||||
//expire = 0
|
||||
p1 := gpool.New(0, nf)
|
||||
p1.Put(1)
|
||||
p1.Put(2)
|
||||
time.Sleep(1 * time.Second)
|
||||
//test won't be timeout
|
||||
v1, err1 := p1.Get()
|
||||
gtest.Assert(err1, nil)
|
||||
gtest.AssertIN(v1, g.Slice{1, 2})
|
||||
//test clear
|
||||
p1.Clear()
|
||||
gtest.Assert(p1.Size(), 0)
|
||||
//test newFunc
|
||||
v1, err1 = p1.Get()
|
||||
gtest.Assert(err1, nil)
|
||||
gtest.Assert(v1, "hello")
|
||||
//put data again
|
||||
p1.Put(3)
|
||||
p1.Put(4)
|
||||
v1, err1 = p1.Get()
|
||||
gtest.Assert(err1, nil)
|
||||
gtest.AssertIN(v1, g.Slice{3, 4})
|
||||
//test close
|
||||
p1.Close()
|
||||
v1, err1 = p1.Get()
|
||||
gtest.Assert(err1, nil)
|
||||
gtest.Assert(v1, "hello")
|
||||
})
|
||||
|
||||
gtest.Case(t, func() {
|
||||
//
|
||||
//expire > 0
|
||||
p2 := gpool.New(2000, nil, ef)
|
||||
for index := 0; index < 10; index++ {
|
||||
p2.Put(index)
|
||||
}
|
||||
gtest.Assert(p2.Size(), 10)
|
||||
v2, err2 := p2.Get()
|
||||
gtest.Assert(err2, nil)
|
||||
gtest.Assert(v2, 0)
|
||||
//test timeout expireFunc
|
||||
time.Sleep(3 * time.Second)
|
||||
v2, err2 = p2.Get()
|
||||
gtest.Assert(err2, errors.New("pool is empty"))
|
||||
gtest.Assert(v2, nil)
|
||||
//test close expireFunc
|
||||
for index := 0; index < 10; index++ {
|
||||
p2.Put(index)
|
||||
}
|
||||
gtest.Assert(p2.Size(), 10)
|
||||
v2, err2 = p2.Get()
|
||||
gtest.Assert(err2, nil)
|
||||
gtest.Assert(v2, 0)
|
||||
assertIndex = 0
|
||||
p2.Close()
|
||||
time.Sleep(3 * time.Second)
|
||||
})
|
||||
|
||||
gtest.Case(t, func() {
|
||||
//
|
||||
//expire < 0
|
||||
p3 := gpool.New(-1, nil)
|
||||
v3, err3 := p3.Get()
|
||||
gtest.Assert(err3, errors.New("pool is empty"))
|
||||
gtest.Assert(v3, nil)
|
||||
})
|
||||
}
|
||||
@ -1,103 +1,139 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package gqueue provides a dynamic/static concurrent-safe(alternative) queue.
|
||||
// 并发安全的动态队列.
|
||||
// 特点:
|
||||
// 1、动态队列初始化速度快;
|
||||
// 2、动态的队列大小(不限大小);
|
||||
// 3、取数据时如果队列为空那么会阻塞等待;
|
||||
// Package gqueue provides a dynamic/static concurrent-safe queue.
|
||||
//
|
||||
// Features:
|
||||
//
|
||||
// 1. FIFO queue(data -> list -> chan);
|
||||
//
|
||||
// 2. Fast creation and initialization;
|
||||
//
|
||||
// 3. Support dynamic queue size(unlimited queue size);
|
||||
//
|
||||
// 4. Blocking when reading data from queue;
|
||||
//
|
||||
package gqueue
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/glist"
|
||||
"math"
|
||||
"github.com/gogf/gf/g/container/glist"
|
||||
"github.com/gogf/gf/g/container/gtype"
|
||||
"math"
|
||||
)
|
||||
|
||||
// 0、这是一个先进先出的队列(chan <-- list);
|
||||
// 1、当创建Queue对象时限定大小,那么等同于一个同步的chan并发安全队列;
|
||||
// 2、不限制大小时,list链表用以存储数据,临时chan负责为客户端读取数据,当从chan获取数据时,list往chan中不停补充数据;
|
||||
// 3、由于功能主体是chan,那么操作仍然像chan那样具有阻塞效果;
|
||||
type Queue struct {
|
||||
limit int // 队列限制大小
|
||||
queue chan interface{} // 用于队列写入限制
|
||||
list *glist.List // 数据链表
|
||||
events chan struct{} // 通知chan,当不限制队列大小时的写入事件通知
|
||||
closeChan chan struct{} // 关闭channel
|
||||
limit int // Limit for queue size.
|
||||
list *glist.List // Underlying list structure for data maintaining.
|
||||
closed *gtype.Bool // Whether queue is closed.
|
||||
events chan struct{} // Events for data writing.
|
||||
C chan interface{} // Underlying channel for data reading.
|
||||
}
|
||||
|
||||
const (
|
||||
// 动态队列缓冲区大小
|
||||
gQUEUE_SIZE = 10000
|
||||
// Size for queue buffer.
|
||||
gDEFAULT_QUEUE_SIZE = 10000
|
||||
// Max batch size per-fetching from list.
|
||||
gDEFAULT_MAX_BATCH_SIZE = 10
|
||||
)
|
||||
|
||||
// 队列大小为非必须参数,默认不限制
|
||||
func New(limit...int) *Queue {
|
||||
q := &Queue {
|
||||
closeChan : make(chan struct{}, 0),
|
||||
}
|
||||
if len(limit) > 0 {
|
||||
q.limit = limit[0]
|
||||
q.queue = make(chan interface{}, limit[0])
|
||||
} else {
|
||||
q.list = glist.New()
|
||||
q.queue = make(chan interface{}, gQUEUE_SIZE)
|
||||
q.events = make(chan struct{}, math.MaxInt32)
|
||||
go q.startAsyncLoop()
|
||||
}
|
||||
return q
|
||||
// New returns an empty queue object.
|
||||
// Optional parameter <limit> is used to limit the size of the queue, which is unlimited in default.
|
||||
// When <limit> is given, the queue will be static and high performance which is comparable with stdlib channel.
|
||||
func New(limit ...int) *Queue {
|
||||
q := &Queue{
|
||||
closed: gtype.NewBool(),
|
||||
}
|
||||
if len(limit) > 0 {
|
||||
q.limit = limit[0]
|
||||
q.C = make(chan interface{}, limit[0])
|
||||
} else {
|
||||
q.list = glist.New()
|
||||
q.events = make(chan struct{}, math.MaxInt32)
|
||||
q.C = make(chan interface{}, gDEFAULT_QUEUE_SIZE)
|
||||
go q.startAsyncLoop()
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
// 异步list->chan同步队列
|
||||
// startAsyncLoop starts an asynchronous goroutine,
|
||||
// which handles the data synchronization from list <q.list> to channel <q.C>.
|
||||
func (q *Queue) startAsyncLoop() {
|
||||
for {
|
||||
select {
|
||||
case <- q.closeChan:
|
||||
return
|
||||
case <- q.events:
|
||||
// 循环读取链表,直到为空才跳出
|
||||
for {
|
||||
if v := q.list.PopFront(); v != nil {
|
||||
q.queue <- v
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
if q.closed.Val() {
|
||||
_ = recover()
|
||||
}
|
||||
}()
|
||||
for !q.closed.Val() {
|
||||
<-q.events
|
||||
for !q.closed.Val() {
|
||||
if length := q.list.Len(); length > 0 {
|
||||
if length > gDEFAULT_MAX_BATCH_SIZE {
|
||||
length = gDEFAULT_MAX_BATCH_SIZE
|
||||
}
|
||||
for _, v := range q.list.PopFronts(length) {
|
||||
// When q.C is closed, it will panic here, especially q.C is being blocked for writing.
|
||||
// If any error occurs here, it will be caught by recover and be ignored.
|
||||
q.C <- v
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
// Clear q.events to remain just one event to do the next synchronization check.
|
||||
for i := 0; i < len(q.events)-1; i++ {
|
||||
<-q.events
|
||||
}
|
||||
}
|
||||
// It should be here to close q.C.
|
||||
// It's the sender's responsibility to close channel when it should be closed.
|
||||
close(q.C)
|
||||
}
|
||||
|
||||
// 将数据压入队列, 队头
|
||||
// Push pushes the data <v> into the queue.
|
||||
// Note that it would panics if Push is called after the queue is closed.
|
||||
func (q *Queue) Push(v interface{}) {
|
||||
if q.limit > 0 {
|
||||
q.queue <- v
|
||||
} else {
|
||||
q.list.PushBack(v)
|
||||
if len(q.events) == 0 {
|
||||
q.events <- struct{}{}
|
||||
}
|
||||
}
|
||||
if q.limit > 0 {
|
||||
q.C <- v
|
||||
} else {
|
||||
q.list.PushBack(v)
|
||||
if len(q.events) < gDEFAULT_QUEUE_SIZE {
|
||||
q.events <- struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 从队头先进先出地从队列取出一项数据
|
||||
// Pop pops an item from the queue in FIFO way.
|
||||
// Note that it would return nil immediately if Pop is called after the queue is closed.
|
||||
func (q *Queue) Pop() interface{} {
|
||||
return <- q.queue
|
||||
return <-q.C
|
||||
}
|
||||
|
||||
// 关闭队列(通知所有通过Pop*阻塞的协程退出)
|
||||
// Close closes the queue.
|
||||
// Notice: It would notify all goroutines return immediately,
|
||||
// which are being blocked reading using Pop method.
|
||||
func (q *Queue) Close() {
|
||||
q.list.RemoveAll()
|
||||
close(q.queue)
|
||||
close(q.events)
|
||||
close(q.closeChan)
|
||||
q.closed.Set(true)
|
||||
if q.events != nil {
|
||||
close(q.events)
|
||||
}
|
||||
for i := 0; i < gDEFAULT_MAX_BATCH_SIZE; i++ {
|
||||
q.Pop()
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前队列大小
|
||||
// Len returns the length of the queue.
|
||||
func (q *Queue) Len() (length int) {
|
||||
if q.list != nil {
|
||||
length += q.list.Len()
|
||||
}
|
||||
length += len(q.C)
|
||||
return
|
||||
}
|
||||
|
||||
// Size is alias of Len.
|
||||
func (q *Queue) Size() int {
|
||||
return len(q.queue) + q.list.Len()
|
||||
return q.Len()
|
||||
}
|
||||
|
||||
|
||||
|
||||
50
g/container/gqueue/gqueue_bench_test.go
Normal file
50
g/container/gqueue/gqueue_bench_test.go
Normal file
@ -0,0 +1,50 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// go test *.go -bench=".*" -benchmem
|
||||
|
||||
package gqueue_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/gqueue"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var bn = 20000000
|
||||
var length = 1000000
|
||||
var qstatic = gqueue.New(length)
|
||||
var qdynamic = gqueue.New()
|
||||
var cany = make(chan interface{}, length)
|
||||
|
||||
func Benchmark_Gqueue_StaticPushAndPop(b *testing.B) {
|
||||
b.N = bn
|
||||
for i := 0; i < b.N; i++ {
|
||||
qstatic.Push(i)
|
||||
qstatic.Pop()
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Gqueue_DynamicPush(b *testing.B) {
|
||||
b.N = bn
|
||||
for i := 0; i < b.N; i++ {
|
||||
qdynamic.Push(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Gqueue_DynamicPop(b *testing.B) {
|
||||
b.N = bn
|
||||
for i := 0; i < b.N; i++ {
|
||||
qdynamic.Pop()
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Channel_PushAndPop(b *testing.B) {
|
||||
b.N = bn
|
||||
for i := 0; i < b.N; i++ {
|
||||
cany <- i
|
||||
<-cany
|
||||
}
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// go test *.go -bench=".*" -benchmem
|
||||
|
||||
package gqueue_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"gitee.com/johng/gf/g/container/gqueue"
|
||||
)
|
||||
|
||||
var bn = 20000000
|
||||
var length = 1000000
|
||||
var qstatic = gqueue.New(length)
|
||||
var qdynamic = gqueue.New()
|
||||
var cany = make(chan interface{}, length)
|
||||
|
||||
func Benchmark_Gqueue_StaticPushAndPop(b *testing.B) {
|
||||
b.N = bn
|
||||
for i := 0; i < b.N; i++ {
|
||||
qstatic.Push(i)
|
||||
qstatic.Pop()
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Gqueue_DynamicPush(b *testing.B) {
|
||||
b.N = bn
|
||||
for i := 0; i < b.N; i++ {
|
||||
qdynamic.Push(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Gqueue_DynamicPop(b *testing.B) {
|
||||
b.N = bn
|
||||
for i := 0; i < b.N; i++ {
|
||||
qdynamic.Pop()
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Channel_PushAndPop(b *testing.B) {
|
||||
b.N = bn
|
||||
for i := 0; i < b.N; i++ {
|
||||
cany <- i
|
||||
<- cany
|
||||
}
|
||||
}
|
||||
54
g/container/gqueue/gqueue_unit_test.go
Normal file
54
g/container/gqueue/gqueue_unit_test.go
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// go test *.go -bench=".*" -benchmem
|
||||
|
||||
package gqueue_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/gqueue"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestQueue_Len(t *testing.T) {
|
||||
max := 100
|
||||
for n := 10; n < max; n++ {
|
||||
q1 := gqueue.New(max)
|
||||
for i := 0; i < max; i++ {
|
||||
q1.Push(i)
|
||||
}
|
||||
gtest.Assert(q1.Len(), max)
|
||||
gtest.Assert(q1.Size(), max)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueue_Basic(t *testing.T) {
|
||||
q := gqueue.New()
|
||||
for i := 0; i < 100; i++ {
|
||||
q.Push(i)
|
||||
}
|
||||
gtest.Assert(q.Pop(), 0)
|
||||
gtest.Assert(q.Pop(), 1)
|
||||
}
|
||||
|
||||
func TestQueue_Pop(t *testing.T) {
|
||||
q1 := gqueue.New()
|
||||
q1.Push(1)
|
||||
q1.Push(2)
|
||||
q1.Push(3)
|
||||
q1.Push(4)
|
||||
i1 := q1.Pop()
|
||||
gtest.Assert(i1, 1)
|
||||
}
|
||||
|
||||
func TestQueue_Close(t *testing.T) {
|
||||
q1 := gqueue.New()
|
||||
q1.Push(1)
|
||||
q1.Push(2)
|
||||
gtest.Assert(q1.Len(), 2)
|
||||
q1.Close()
|
||||
}
|
||||
@ -1,235 +1,258 @@
|
||||
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package gring provides a concurrent-safe(alternative) ring(circular lists).
|
||||
// 并发安全的环.
|
||||
// Package gring provides a concurrent-safe/unsafe ring(circular lists).
|
||||
package gring
|
||||
|
||||
import (
|
||||
"container/ring"
|
||||
"gitee.com/johng/gf/g/container/gtype"
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
"container/ring"
|
||||
"github.com/gogf/gf/g/container/gtype"
|
||||
"github.com/gogf/gf/g/internal/rwmutex"
|
||||
)
|
||||
|
||||
type Ring struct {
|
||||
mu *rwmutex.RWMutex // 互斥锁
|
||||
ring *ring.Ring // 底层环形数据结构
|
||||
len *gtype.Int // 数据大小(已使用的大小)
|
||||
cap *gtype.Int // 总长度(分配的环大小,包括未使用的数据项数量)
|
||||
dirty *gtype.Bool // 标记环是否脏了(需要重新计算大小,当环大小发生改变时做标记)
|
||||
mu *rwmutex.RWMutex
|
||||
ring *ring.Ring // Underlying ring.
|
||||
len *gtype.Int // Length(already used size).
|
||||
cap *gtype.Int // Capability(>=len).
|
||||
dirty *gtype.Bool // Dirty, which means the len and cap should be recalculated.
|
||||
// It's marked dirty when the size of ring changes.
|
||||
}
|
||||
|
||||
func New(cap int, safe...bool) *Ring {
|
||||
return &Ring {
|
||||
mu : rwmutex.New(safe...),
|
||||
ring : ring.New(cap),
|
||||
len : gtype.NewInt(),
|
||||
cap : gtype.NewInt(cap),
|
||||
dirty : gtype.NewBool(),
|
||||
}
|
||||
func New(cap int, unsafe ...bool) *Ring {
|
||||
return &Ring{
|
||||
mu: rwmutex.New(unsafe...),
|
||||
ring: ring.New(cap),
|
||||
len: gtype.NewInt(),
|
||||
cap: gtype.NewInt(cap),
|
||||
dirty: gtype.NewBool(),
|
||||
}
|
||||
}
|
||||
|
||||
// 返回当前环指向的数据项值
|
||||
// Val returns the item's value of current position.
|
||||
func (r *Ring) Val() interface{} {
|
||||
r.mu.RLock()
|
||||
v := r.ring.Value
|
||||
r.mu.RUnlock()
|
||||
return v
|
||||
r.mu.RLock()
|
||||
v := r.ring.Value
|
||||
r.mu.RUnlock()
|
||||
return v
|
||||
}
|
||||
|
||||
// 返回当前环已有数据项大小
|
||||
// Len returns the size of ring.
|
||||
func (r *Ring) Len() int {
|
||||
r.checkAndUpdateLenAndCap()
|
||||
return r.len.Val()
|
||||
r.checkAndUpdateLenAndCap()
|
||||
return r.len.Val()
|
||||
}
|
||||
|
||||
// 返回当前环总大小(包含未使用长度)
|
||||
// Cap returns the capacity of ring.
|
||||
func (r *Ring) Cap() int {
|
||||
r.checkAndUpdateLenAndCap()
|
||||
return r.cap.Val()
|
||||
r.checkAndUpdateLenAndCap()
|
||||
return r.cap.Val()
|
||||
}
|
||||
|
||||
// 检测并执行len和cap的更新(两者必须一起更新)
|
||||
func (r *Ring) checkAndUpdateLenAndCap() {
|
||||
if !r.dirty.Val() {
|
||||
return
|
||||
}
|
||||
totalLen := 0
|
||||
emptyLen := 0
|
||||
if r.ring != nil {
|
||||
r.mu.RLock()
|
||||
for p := r.ring.Next(); p != r.ring; p = p.Next() {
|
||||
if p.Value == nil {
|
||||
emptyLen++
|
||||
}
|
||||
totalLen++
|
||||
}
|
||||
r.mu.RUnlock()
|
||||
}
|
||||
r.cap.Set(totalLen)
|
||||
r.len.Set(totalLen - emptyLen)
|
||||
r.dirty.Set(false)
|
||||
// Checks and updates the len and cap of ring when ring is dirty.
|
||||
func (r *Ring) checkAndUpdateLenAndCap() {
|
||||
if !r.dirty.Val() {
|
||||
return
|
||||
}
|
||||
totalLen := 0
|
||||
emptyLen := 0
|
||||
if r.ring != nil {
|
||||
r.mu.RLock()
|
||||
for p := r.ring.Next(); p != r.ring; p = p.Next() {
|
||||
if p.Value == nil {
|
||||
emptyLen++
|
||||
}
|
||||
totalLen++
|
||||
}
|
||||
r.mu.RUnlock()
|
||||
}
|
||||
r.cap.Set(totalLen)
|
||||
r.len.Set(totalLen - emptyLen)
|
||||
r.dirty.Set(false)
|
||||
}
|
||||
|
||||
// 当前位置设置数据项值
|
||||
// Set sets value to the item of current position.
|
||||
func (r *Ring) Set(value interface{}) *Ring {
|
||||
r.mu.Lock()
|
||||
if r.ring.Value == nil {
|
||||
r.len.Add(1)
|
||||
}
|
||||
r.ring.Value = value
|
||||
r.mu.Unlock()
|
||||
return r
|
||||
r.mu.Lock()
|
||||
if r.ring.Value == nil {
|
||||
r.len.Add(1)
|
||||
}
|
||||
r.ring.Value = value
|
||||
r.mu.Unlock()
|
||||
return r
|
||||
}
|
||||
|
||||
// Set & Next
|
||||
// Put sets <value> to current item of ring and moves position to next item.
|
||||
func (r *Ring) Put(value interface{}) *Ring {
|
||||
r.mu.Lock()
|
||||
if r.ring.Value == nil {
|
||||
r.len.Add(1)
|
||||
}
|
||||
r.ring.Value = value
|
||||
r.ring = r.ring.Next()
|
||||
r.mu.Unlock()
|
||||
return r
|
||||
r.mu.Lock()
|
||||
if r.ring.Value == nil {
|
||||
r.len.Add(1)
|
||||
}
|
||||
r.ring.Value = value
|
||||
r.ring = r.ring.Next()
|
||||
r.mu.Unlock()
|
||||
return r
|
||||
}
|
||||
|
||||
// 环往后(n > 0)或者往前(n < 0)移动n个元素
|
||||
// Move moves n % r.Len() elements backward (n < 0) or forward (n >= 0)
|
||||
// in the ring and returns that ring element. r must not be empty.
|
||||
func (r *Ring) Move(n int) *Ring {
|
||||
r.mu.Lock()
|
||||
r.ring = r.ring.Move(n)
|
||||
r.mu.Unlock()
|
||||
return r
|
||||
r.mu.Lock()
|
||||
r.ring = r.ring.Move(n)
|
||||
r.mu.Unlock()
|
||||
return r
|
||||
}
|
||||
|
||||
// 环往前移动1个元素
|
||||
// Prev returns the previous ring element. r must not be empty.
|
||||
func (r *Ring) Prev() *Ring {
|
||||
r.mu.Lock()
|
||||
r.ring = r.ring.Prev()
|
||||
r.mu.Unlock()
|
||||
return r
|
||||
r.mu.Lock()
|
||||
r.ring = r.ring.Prev()
|
||||
r.mu.Unlock()
|
||||
return r
|
||||
}
|
||||
|
||||
// 环往后移动1个元素
|
||||
// Next returns the next ring element. r must not be empty.
|
||||
func (r *Ring) Next() *Ring {
|
||||
r.mu.Lock()
|
||||
r.ring = r.ring.Next()
|
||||
r.mu.Unlock()
|
||||
return r
|
||||
r.mu.Lock()
|
||||
r.ring = r.ring.Next()
|
||||
r.mu.Unlock()
|
||||
return r
|
||||
}
|
||||
|
||||
// 连接两个环,两个环的大小和位置都有可能会发生改变。
|
||||
// 1、链接将环r与环s连接,使得r.Next()成为s并返回r.Next()的原始值。r一定不能为空。
|
||||
// 2、如果r和s指向同一个环,则链接它们会从环中移除r和s之间的元素。
|
||||
// 删除的元素形成子环,结果是对该子环的引用(如果没有删除元素,结果仍然是r.Next()的原始值,而不是nil)。
|
||||
// 3、如果r和s指向不同的环,则链接它们会创建一个单独的环,并在r之后插入s的元素。 结果指向插入后s的最后一个元素后面的元素。
|
||||
// Link connects ring r with ring s such that r.Next()
|
||||
// becomes s and returns the original value for r.Next().
|
||||
// r must not be empty.
|
||||
//
|
||||
// If r and s point to the same ring, linking
|
||||
// them removes the elements between r and s from the ring.
|
||||
// The removed elements form a subring and the result is a
|
||||
// reference to that subring (if no elements were removed,
|
||||
// the result is still the original value for r.Next(),
|
||||
// and not nil).
|
||||
//
|
||||
// If r and s point to different rings, linking
|
||||
// them creates a single ring with the elements of s inserted
|
||||
// after r. The result points to the element following the
|
||||
// last element of s after insertion.
|
||||
//
|
||||
func (r *Ring) Link(s *Ring) *Ring {
|
||||
r.mu.Lock()
|
||||
s.mu.Lock()
|
||||
r.ring.Link(s.ring)
|
||||
s.mu.Unlock()
|
||||
r.mu.Unlock()
|
||||
r.dirty.Set(true)
|
||||
s.dirty.Set(true)
|
||||
return r
|
||||
r.mu.Lock()
|
||||
s.mu.Lock()
|
||||
r.ring.Link(s.ring)
|
||||
s.mu.Unlock()
|
||||
r.mu.Unlock()
|
||||
r.dirty.Set(true)
|
||||
s.dirty.Set(true)
|
||||
return r
|
||||
}
|
||||
|
||||
// 删除环中当前位置往后的n个数据项
|
||||
// Unlink removes n % r.Len() elements from the ring r, starting
|
||||
// at r.Next(). If n % r.Len() == 0, r remains unchanged.
|
||||
// The result is the removed subring. r must not be empty.
|
||||
//
|
||||
func (r *Ring) Unlink(n int) *Ring {
|
||||
r.mu.Lock()
|
||||
r.ring = r.ring.Unlink(n)
|
||||
r.dirty.Set(true)
|
||||
r.mu.Unlock()
|
||||
return r
|
||||
r.mu.Lock()
|
||||
r.ring = r.ring.Unlink(n)
|
||||
r.dirty.Set(true)
|
||||
r.mu.Unlock()
|
||||
return r
|
||||
}
|
||||
|
||||
// 读锁遍历,往后只读遍历,回调函数返回true表示继续遍历,否则退出遍历
|
||||
// RLockIteratorNext iterates and locks reading forward
|
||||
// with given callback function <f> within RWMutex.RLock.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (r *Ring) RLockIteratorNext(f func(value interface{}) bool) {
|
||||
r.mu.RLock(true)
|
||||
defer r.mu.RUnlock(true)
|
||||
if !f(r.ring.Value) {
|
||||
return
|
||||
}
|
||||
for p := r.ring.Next(); p != r.ring; p = p.Next() {
|
||||
if !f(p.Value) {
|
||||
break
|
||||
}
|
||||
}
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
if !f(r.ring.Value) {
|
||||
return
|
||||
}
|
||||
for p := r.ring.Next(); p != r.ring; p = p.Next() {
|
||||
if !f(p.Value) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 读锁遍历,往前只读遍历,回调函数返回true表示继续遍历,否则退出遍历
|
||||
// RLockIteratorPrev iterates and locks reading backward
|
||||
// with given callback function <f> within RWMutex.RLock.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (r *Ring) RLockIteratorPrev(f func(value interface{}) bool) {
|
||||
r.mu.RLock(true)
|
||||
defer r.mu.RUnlock(true)
|
||||
if !f(r.ring.Value) {
|
||||
return
|
||||
}
|
||||
for p := r.ring.Prev(); p != r.ring; p = p.Prev() {
|
||||
if !f(p.Value) {
|
||||
break
|
||||
}
|
||||
}
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
if !f(r.ring.Value) {
|
||||
return
|
||||
}
|
||||
for p := r.ring.Prev(); p != r.ring; p = p.Prev() {
|
||||
if !f(p.Value) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 写锁遍历,往后写遍历,回调函数返回true表示继续遍历,否则退出遍历
|
||||
// LockIteratorNext iterates and locks writing forward
|
||||
// with given callback function <f> within RWMutex.RLock.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (r *Ring) LockIteratorNext(f func(item *ring.Ring) bool) {
|
||||
r.mu.RLock(true)
|
||||
defer r.mu.RUnlock(true)
|
||||
if !f(r.ring) {
|
||||
return
|
||||
}
|
||||
for p := r.ring.Next(); p != r.ring; p = p.Next() {
|
||||
if !f(p) {
|
||||
break
|
||||
}
|
||||
}
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
if !f(r.ring) {
|
||||
return
|
||||
}
|
||||
for p := r.ring.Next(); p != r.ring; p = p.Next() {
|
||||
if !f(p) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 写锁遍历,往前写遍历,回调函数返回true表示继续遍历,否则退出遍历
|
||||
// LockIteratorPrev iterates and locks writing backward
|
||||
// with given callback function <f> within RWMutex.RLock.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (r *Ring) LockIteratorPrev(f func(item *ring.Ring) bool) {
|
||||
r.mu.RLock(true)
|
||||
defer r.mu.RUnlock(true)
|
||||
if !f(r.ring) {
|
||||
return
|
||||
}
|
||||
for p := r.ring.Prev(); p != r.ring; p = p.Prev() {
|
||||
if !f(p) {
|
||||
break
|
||||
}
|
||||
}
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
if !f(r.ring) {
|
||||
return
|
||||
}
|
||||
for p := r.ring.Prev(); p != r.ring; p = p.Prev() {
|
||||
if !f(p) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 从当前位置,往后只读完整遍历,返回非空数据项值构成的数组
|
||||
// SliceNext returns a copy of all item values as slice forward from current position.
|
||||
func (r *Ring) SliceNext() []interface{} {
|
||||
s := make([]interface{}, 0)
|
||||
r.mu.RLock()
|
||||
if r.ring.Value != nil {
|
||||
s = append(s, r.ring.Value)
|
||||
}
|
||||
for p := r.ring.Next(); p != r.ring; p = p.Next() {
|
||||
if p.Value != nil {
|
||||
s = append(s, p.Value)
|
||||
}
|
||||
}
|
||||
r.mu.RUnlock()
|
||||
return s
|
||||
s := make([]interface{}, 0)
|
||||
r.mu.RLock()
|
||||
if r.ring.Value != nil {
|
||||
s = append(s, r.ring.Value)
|
||||
}
|
||||
for p := r.ring.Next(); p != r.ring; p = p.Next() {
|
||||
if p.Value != nil {
|
||||
s = append(s, p.Value)
|
||||
}
|
||||
}
|
||||
r.mu.RUnlock()
|
||||
return s
|
||||
}
|
||||
|
||||
// 从当前位置,往前只读完整遍历,返回非空数据项值构成的数组
|
||||
// SlicePrev returns a copy of all item values as slice backward from current position.
|
||||
func (r *Ring) SlicePrev() []interface{} {
|
||||
s := make([]interface{}, 0)
|
||||
r.mu.RLock()
|
||||
if r.ring.Value != nil {
|
||||
s = append(s, r.ring.Value)
|
||||
}
|
||||
for p := r.ring.Prev(); p != r.ring; p = p.Prev() {
|
||||
if p.Value != nil {
|
||||
s = append(s, p.Value)
|
||||
}
|
||||
}
|
||||
r.mu.RUnlock()
|
||||
return s
|
||||
}
|
||||
s := make([]interface{}, 0)
|
||||
r.mu.RLock()
|
||||
if r.ring.Value != nil {
|
||||
s = append(s, r.ring.Value)
|
||||
}
|
||||
for p := r.ring.Prev(); p != r.ring; p = p.Prev() {
|
||||
if p.Value != nil {
|
||||
s = append(s, p.Value)
|
||||
}
|
||||
}
|
||||
r.mu.RUnlock()
|
||||
return s
|
||||
}
|
||||
|
||||
@ -1,46 +1,46 @@
|
||||
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// go test *.go -bench=".*"
|
||||
|
||||
package gring
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var length = 10000
|
||||
var r1 = New(length)
|
||||
var r1 = New(length)
|
||||
|
||||
func BenchmarkRing_Put(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
r1.Put(i)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
r1.Put(i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRing_Next(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
r1.Next()
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
r1.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRing_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
r1.Set(i)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
r1.Set(i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRing_Len(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
r1.Len()
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
r1.Len()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRing_Cap(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
r1.Cap()
|
||||
}
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
r1.Cap()
|
||||
}
|
||||
}
|
||||
|
||||
229
g/container/gring/gring_unit_test.go
Normal file
229
g/container/gring/gring_unit_test.go
Normal file
@ -0,0 +1,229 @@
|
||||
package gring_test
|
||||
|
||||
import (
|
||||
"container/ring"
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/container/gring"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type Student struct {
|
||||
position int
|
||||
name string
|
||||
upgrade bool
|
||||
}
|
||||
|
||||
func TestRing_Val(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
//定义cap 为3的ring类型数据
|
||||
r := gring.New(3, true)
|
||||
//分别给3个元素初始化赋值
|
||||
r.Put(&Student{1, "jimmy", true})
|
||||
r.Put(&Student{2, "tom", true})
|
||||
r.Put(&Student{3, "alon", false})
|
||||
|
||||
//元素取值并判断和预设值是否相等
|
||||
gtest.Assert(r.Val().(*Student).name, "jimmy")
|
||||
//从当前位置往后移两个元素
|
||||
r.Move(2)
|
||||
gtest.Assert(r.Val().(*Student).name, "alon")
|
||||
//更新元素值
|
||||
//测试 value == nil
|
||||
r.Set(nil)
|
||||
gtest.Assert(r.Val(), nil)
|
||||
//测试value != nil
|
||||
r.Set(&Student{3, "jack", true})
|
||||
})
|
||||
}
|
||||
func TestRing_CapLen(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
r := gring.New(10)
|
||||
r.Put("goframe")
|
||||
//cap长度 10
|
||||
gtest.Assert(r.Cap(), 10)
|
||||
//已有数据项 1
|
||||
gtest.Assert(r.Len(), 1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRing_Position(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
r := gring.New(2)
|
||||
r.Put(1)
|
||||
r.Put(2)
|
||||
//往后移动1个元素
|
||||
r.Next()
|
||||
gtest.Assert(r.Val(), 2)
|
||||
//往前移动1个元素
|
||||
r.Prev()
|
||||
gtest.Assert(r.Val(), 1)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestRing_Link(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
r := gring.New(3)
|
||||
r.Put(1)
|
||||
r.Put(2)
|
||||
r.Put(3)
|
||||
s := gring.New(2)
|
||||
s.Put("a")
|
||||
s.Put("b")
|
||||
|
||||
rs := r.Link(s)
|
||||
gtest.Assert(rs.Move(2).Val(), "b")
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestRing_Unlink(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
r := gring.New(5)
|
||||
for i := 0; i < 5; i++ {
|
||||
r.Put(i + 1)
|
||||
}
|
||||
// 1 2 3 4
|
||||
// 删除当前位置往后的2个数据,返回被删除的数据
|
||||
// 重新计算s len
|
||||
s := r.Unlink(2) // 2 3
|
||||
gtest.Assert(s.Val(), 2)
|
||||
gtest.Assert(s.Len(), 1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRing_Slice(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
ringLen := 5
|
||||
r := gring.New(ringLen)
|
||||
for i := 0; i < ringLen; i++ {
|
||||
r.Put(i + 1)
|
||||
}
|
||||
r.Move(2) // 3
|
||||
array := r.SliceNext() // [3 4 5 1 2]
|
||||
gtest.Assert(array[0], 3)
|
||||
gtest.Assert(len(array), 5)
|
||||
|
||||
//判断array是否等于[3 4 5 1 2]
|
||||
ra := []int{3, 4, 5, 1, 2}
|
||||
gtest.Assert(ra, array)
|
||||
|
||||
//第3个元素设为nil
|
||||
r.Set(nil)
|
||||
array2 := r.SliceNext() //[4 5 1 2]
|
||||
//返回当前位置往后不为空的元素数组,长度为4
|
||||
gtest.Assert(array2, g.Slice{4, 5, 1, 2})
|
||||
|
||||
array3 := r.SlicePrev() //[2 1 5 4]
|
||||
gtest.Assert(array3, g.Slice{2, 1, 5, 4})
|
||||
|
||||
s := gring.New(ringLen)
|
||||
for i := 0; i < ringLen; i++ {
|
||||
s.Put(i + 1)
|
||||
}
|
||||
array4 := s.SlicePrev() // []
|
||||
gtest.Assert(array4, g.Slice{1, 5, 4, 3, 2})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestRing_RLockIterator(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
ringLen := 5
|
||||
r := gring.New(ringLen)
|
||||
|
||||
//ring不存在有值元素
|
||||
r.RLockIteratorNext(func(v interface{}) bool {
|
||||
gtest.Assert(v, nil)
|
||||
return false
|
||||
})
|
||||
r.RLockIteratorNext(func(v interface{}) bool {
|
||||
gtest.Assert(v, nil)
|
||||
return true
|
||||
})
|
||||
|
||||
r.RLockIteratorPrev(func(v interface{}) bool {
|
||||
gtest.Assert(v, nil)
|
||||
return true
|
||||
})
|
||||
|
||||
for i := 0; i < ringLen; i++ {
|
||||
r.Put(i + 1)
|
||||
}
|
||||
|
||||
//回调函数返回true,RLockIteratorNext遍历5次,期望值分别是1、2、3、4、5
|
||||
i := 0
|
||||
r.RLockIteratorNext(func(v interface{}) bool {
|
||||
gtest.Assert(v, i+1)
|
||||
i++
|
||||
return true
|
||||
})
|
||||
|
||||
//RLockIteratorPrev遍历1次返回 false,退出遍历
|
||||
r.RLockIteratorPrev(func(v interface{}) bool {
|
||||
gtest.Assert(v, 1)
|
||||
return false
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestRing_LockIterator(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
ringLen := 5
|
||||
r := gring.New(ringLen)
|
||||
|
||||
//不存在有值元素
|
||||
r.LockIteratorNext(func(item *ring.Ring) bool {
|
||||
gtest.Assert(item.Value, nil)
|
||||
return false
|
||||
})
|
||||
r.LockIteratorNext(func(item *ring.Ring) bool {
|
||||
gtest.Assert(item.Value, nil)
|
||||
return false
|
||||
})
|
||||
r.LockIteratorNext(func(item *ring.Ring) bool {
|
||||
gtest.Assert(item.Value, nil)
|
||||
return true
|
||||
})
|
||||
|
||||
r.LockIteratorPrev(func(item *ring.Ring) bool {
|
||||
gtest.Assert(item.Value, nil)
|
||||
return false
|
||||
})
|
||||
r.LockIteratorPrev(func(item *ring.Ring) bool {
|
||||
gtest.Assert(item.Value, nil)
|
||||
return true
|
||||
})
|
||||
|
||||
//ring初始化元素值
|
||||
for i := 0; i < ringLen; i++ {
|
||||
r.Put(i + 1)
|
||||
}
|
||||
|
||||
//往后遍历组成数据 [1,2,3,4,5]
|
||||
array1 := g.Slice{1, 2, 3, 4, 5}
|
||||
ii := 0
|
||||
r.LockIteratorNext(func(item *ring.Ring) bool {
|
||||
//校验每一次遍历取值是否是期望值
|
||||
gtest.Assert(item.Value, array1[ii])
|
||||
ii++
|
||||
return true
|
||||
})
|
||||
|
||||
//往后取3个元素组成数组
|
||||
//获得 [1,5,4]
|
||||
i := 0
|
||||
a := g.Slice{1, 5, 4}
|
||||
r.LockIteratorPrev(func(item *ring.Ring) bool {
|
||||
if i > 2 {
|
||||
return false
|
||||
}
|
||||
gtest.Assert(item.Value, a[i])
|
||||
i++
|
||||
return true
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
@ -1,16 +1,325 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package gset provides kinds of concurrent-safe(alternative) sets.
|
||||
// 并发安全的集合SET.
|
||||
// Package gset provides kinds of concurrent-safe/unsafe sets.
|
||||
package gset
|
||||
|
||||
type Set = InterfaceSet
|
||||
import (
|
||||
"github.com/gogf/gf/g/internal/rwmutex"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 默认Set类型
|
||||
func New(safe...bool) *Set {
|
||||
return NewInterfaceSet(safe...)
|
||||
}
|
||||
type Set struct {
|
||||
mu *rwmutex.RWMutex
|
||||
m map[interface{}]struct{}
|
||||
}
|
||||
|
||||
// New create and returns a new set, which contains un-repeated items.
|
||||
// The parameter <unsafe> used to specify whether using set in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func New(unsafe ...bool) *Set {
|
||||
return NewSet(unsafe...)
|
||||
}
|
||||
|
||||
// See New.
|
||||
func NewSet(unsafe ...bool) *Set {
|
||||
return &Set{
|
||||
m: make(map[interface{}]struct{}),
|
||||
mu: rwmutex.New(unsafe...),
|
||||
}
|
||||
}
|
||||
|
||||
// NewFrom returns a new set from <items>.
|
||||
// Parameter <items> can be either a variable of any type, or a slice.
|
||||
func NewFrom(items interface{}, unsafe ...bool) *Set {
|
||||
m := make(map[interface{}]struct{})
|
||||
for _, v := range gconv.Interfaces(items) {
|
||||
m[v] = struct{}{}
|
||||
}
|
||||
return &Set{
|
||||
m: m,
|
||||
mu: rwmutex.New(unsafe...),
|
||||
}
|
||||
}
|
||||
|
||||
// Iterator iterates the set with given callback function <f>,
|
||||
// if <f> returns true then continue iterating; or false to stop.
|
||||
func (set *Set) Iterator(f func(v interface{}) bool) *Set {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for k, _ := range set.m {
|
||||
if !f(k) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
// Add adds one or multiple items to the set.
|
||||
func (set *Set) Add(item ...interface{}) *Set {
|
||||
set.mu.Lock()
|
||||
for _, v := range item {
|
||||
set.m[v] = struct{}{}
|
||||
}
|
||||
set.mu.Unlock()
|
||||
return set
|
||||
}
|
||||
|
||||
// Contains checks whether the set contains <item>.
|
||||
func (set *Set) Contains(item interface{}) bool {
|
||||
set.mu.RLock()
|
||||
_, exists := set.m[item]
|
||||
set.mu.RUnlock()
|
||||
return exists
|
||||
}
|
||||
|
||||
// Remove deletes <item> from set.
|
||||
func (set *Set) Remove(item interface{}) *Set {
|
||||
set.mu.Lock()
|
||||
delete(set.m, item)
|
||||
set.mu.Unlock()
|
||||
return set
|
||||
}
|
||||
|
||||
// Size returns the size of the set.
|
||||
func (set *Set) Size() int {
|
||||
set.mu.RLock()
|
||||
l := len(set.m)
|
||||
set.mu.RUnlock()
|
||||
return l
|
||||
}
|
||||
|
||||
// Clear deletes all items of the set.
|
||||
func (set *Set) Clear() *Set {
|
||||
set.mu.Lock()
|
||||
set.m = make(map[interface{}]struct{})
|
||||
set.mu.Unlock()
|
||||
return set
|
||||
}
|
||||
|
||||
// Slice returns the a of items of the set as slice.
|
||||
func (set *Set) Slice() []interface{} {
|
||||
set.mu.RLock()
|
||||
i := 0
|
||||
ret := make([]interface{}, len(set.m))
|
||||
for item := range set.m {
|
||||
ret[i] = item
|
||||
i++
|
||||
}
|
||||
set.mu.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
// Join joins items with a string <glue>.
|
||||
func (set *Set) Join(glue string) string {
|
||||
return strings.Join(gconv.Strings(set.Slice()), ",")
|
||||
}
|
||||
|
||||
// String returns items as a string, which are joined by char ','.
|
||||
func (set *Set) String() string {
|
||||
return set.Join(",")
|
||||
}
|
||||
|
||||
// LockFunc locks writing with callback function <f>.
|
||||
func (set *Set) LockFunc(f func(m map[interface{}]struct{})) {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
f(set.m)
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with callback function <f>.
|
||||
func (set *Set) RLockFunc(f func(m map[interface{}]struct{})) {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
f(set.m)
|
||||
}
|
||||
|
||||
// Equal checks whether the two sets equal.
|
||||
func (set *Set) Equal(other *Set) bool {
|
||||
if set == other {
|
||||
return true
|
||||
}
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
if len(set.m) != len(other.m) {
|
||||
return false
|
||||
}
|
||||
for key := range set.m {
|
||||
if _, ok := other.m[key]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsSubsetOf checks whether the current set is a sub-set of <other>.
|
||||
func (set *Set) IsSubsetOf(other *Set) bool {
|
||||
if set == other {
|
||||
return true
|
||||
}
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
for key := range set.m {
|
||||
if _, ok := other.m[key]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Union returns a new set which is the union of <set> and <others>.
|
||||
// Which means, all the items in <newSet> are in <set> or in <others>.
|
||||
func (set *Set) Union(others ...*Set) (newSet *Set) {
|
||||
newSet = NewSet(true)
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for _, other := range others {
|
||||
if set != other {
|
||||
other.mu.RLock()
|
||||
}
|
||||
for k, v := range set.m {
|
||||
newSet.m[k] = v
|
||||
}
|
||||
if set != other {
|
||||
for k, v := range other.m {
|
||||
newSet.m[k] = v
|
||||
}
|
||||
}
|
||||
if set != other {
|
||||
other.mu.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Diff returns a new set which is the difference set from <set> to <others>.
|
||||
// Which means, all the items in <newSet> are in <set> but not in <others>.
|
||||
func (set *Set) Diff(others ...*Set) (newSet *Set) {
|
||||
newSet = NewSet(true)
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for _, other := range others {
|
||||
if set == other {
|
||||
continue
|
||||
}
|
||||
other.mu.RLock()
|
||||
for k, v := range set.m {
|
||||
if _, ok := other.m[k]; !ok {
|
||||
newSet.m[k] = v
|
||||
}
|
||||
}
|
||||
other.mu.RUnlock()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Intersect returns a new set which is the intersection from <set> to <others>.
|
||||
// Which means, all the items in <newSet> are in <set> and also in <others>.
|
||||
func (set *Set) Intersect(others ...*Set) (newSet *Set) {
|
||||
newSet = NewSet(true)
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for _, other := range others {
|
||||
if set != other {
|
||||
other.mu.RLock()
|
||||
}
|
||||
for k, v := range set.m {
|
||||
if _, ok := other.m[k]; ok {
|
||||
newSet.m[k] = v
|
||||
}
|
||||
}
|
||||
if set != other {
|
||||
other.mu.RUnlock()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Complement returns a new set which is the complement from <set> to <full>.
|
||||
// Which means, all the items in <newSet> are in <full> and not in <set>.
|
||||
//
|
||||
// It returns the difference between <full> and <set>
|
||||
// if the given set <full> is not the full set of <set>.
|
||||
func (set *Set) Complement(full *Set) (newSet *Set) {
|
||||
newSet = NewSet(true)
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
if set != full {
|
||||
full.mu.RLock()
|
||||
defer full.mu.RUnlock()
|
||||
}
|
||||
for k, v := range full.m {
|
||||
if _, ok := set.m[k]; !ok {
|
||||
newSet.m[k] = v
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Merge adds items from <others> sets into <set>.
|
||||
func (set *Set) Merge(others ...*Set) *Set {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
for _, other := range others {
|
||||
if set != other {
|
||||
other.mu.RLock()
|
||||
}
|
||||
for k, v := range other.m {
|
||||
set.m[k] = v
|
||||
}
|
||||
if set != other {
|
||||
other.mu.RUnlock()
|
||||
}
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
// Sum sums items.
|
||||
// Note: The items should be converted to int type,
|
||||
// or you'd get a result that you unexpected.
|
||||
func (set *Set) Sum() (sum int) {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for k, _ := range set.m {
|
||||
sum += gconv.Int(k)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Pops randomly pops an item from set.
|
||||
func (set *Set) Pop(size int) interface{} {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for k, _ := range set.m {
|
||||
return k
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Pops randomly pops <size> items from set.
|
||||
func (set *Set) Pops(size int) []interface{} {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
if size > len(set.m) {
|
||||
size = len(set.m)
|
||||
}
|
||||
index := 0
|
||||
array := make([]interface{}, size)
|
||||
for k, _ := range set.m {
|
||||
array[index] = k
|
||||
index++
|
||||
if index == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
//
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
//
|
||||
|
||||
package gset
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
"github.com/gogf/gf/g/internal/rwmutex"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type IntSet struct {
|
||||
@ -18,99 +18,302 @@ type IntSet struct {
|
||||
m map[int]struct{}
|
||||
}
|
||||
|
||||
func NewIntSet(safe...bool) *IntSet {
|
||||
// New create and returns a new set, which contains un-repeated items.
|
||||
// The parameter <unsafe> used to specify whether using set in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewIntSet(unsafe ...bool) *IntSet {
|
||||
return &IntSet{
|
||||
m : make(map[int]struct{}),
|
||||
mu : rwmutex.New(safe...),
|
||||
m: make(map[int]struct{}),
|
||||
mu: rwmutex.New(unsafe...),
|
||||
}
|
||||
}
|
||||
|
||||
// 给定回调函数对原始内容进行遍历,回调函数返回true表示继续遍历,否则停止遍历
|
||||
func (this *IntSet) Iterator(f func (v int) bool) {
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
for k, _ := range this.m {
|
||||
// NewIntSetFrom returns a new set from <items>.
|
||||
func NewIntSetFrom(items []int, unsafe ...bool) *IntSet {
|
||||
m := make(map[int]struct{})
|
||||
for _, v := range items {
|
||||
m[v] = struct{}{}
|
||||
}
|
||||
return &IntSet{
|
||||
m: m,
|
||||
mu: rwmutex.New(unsafe...),
|
||||
}
|
||||
}
|
||||
|
||||
// Iterator iterates the set with given callback function <f>,
|
||||
// if <f> returns true then continue iterating; or false to stop.
|
||||
func (set *IntSet) Iterator(f func(v int) bool) *IntSet {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for k, _ := range set.m {
|
||||
if !f(k) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
// 设置键
|
||||
func (this *IntSet) Add(item int) *IntSet {
|
||||
this.mu.Lock()
|
||||
this.m[item] = struct{}{}
|
||||
this.mu.Unlock()
|
||||
return this
|
||||
}
|
||||
|
||||
// 批量添加设置键
|
||||
func (this *IntSet) BatchAdd(items []int) *IntSet {
|
||||
this.mu.Lock()
|
||||
for _, item := range items {
|
||||
this.m[item] = struct{}{}
|
||||
// Add adds one or multiple items to the set.
|
||||
func (set *IntSet) Add(item ...int) *IntSet {
|
||||
set.mu.Lock()
|
||||
for _, v := range item {
|
||||
set.m[v] = struct{}{}
|
||||
}
|
||||
this.mu.Unlock()
|
||||
return this
|
||||
set.mu.Unlock()
|
||||
return set
|
||||
}
|
||||
|
||||
// 键是否存在
|
||||
func (this *IntSet) Contains(item int) bool {
|
||||
this.mu.RLock()
|
||||
_, exists := this.m[item]
|
||||
this.mu.RUnlock()
|
||||
// Contains checks whether the set contains <item>.
|
||||
func (set *IntSet) Contains(item int) bool {
|
||||
set.mu.RLock()
|
||||
_, exists := set.m[item]
|
||||
set.mu.RUnlock()
|
||||
return exists
|
||||
}
|
||||
|
||||
// 删除键值对
|
||||
func (this *IntSet) Remove(key int) {
|
||||
this.mu.Lock()
|
||||
delete(this.m, key)
|
||||
this.mu.Unlock()
|
||||
// Remove deletes <item> from set.
|
||||
func (set *IntSet) Remove(item int) *IntSet {
|
||||
set.mu.Lock()
|
||||
delete(set.m, item)
|
||||
set.mu.Unlock()
|
||||
return set
|
||||
}
|
||||
|
||||
// 大小
|
||||
func (this *IntSet) Size() int {
|
||||
this.mu.RLock()
|
||||
l := len(this.m)
|
||||
this.mu.RUnlock()
|
||||
// Size returns the size of the set.
|
||||
func (set *IntSet) Size() int {
|
||||
set.mu.RLock()
|
||||
l := len(set.m)
|
||||
set.mu.RUnlock()
|
||||
return l
|
||||
}
|
||||
|
||||
// 清空set
|
||||
func (this *IntSet) Clear() {
|
||||
this.mu.Lock()
|
||||
this.m = make(map[int]struct{})
|
||||
this.mu.Unlock()
|
||||
// Clear deletes all items of the set.
|
||||
func (set *IntSet) Clear() *IntSet {
|
||||
set.mu.Lock()
|
||||
set.m = make(map[int]struct{})
|
||||
set.mu.Unlock()
|
||||
return set
|
||||
}
|
||||
|
||||
// 转换为数组
|
||||
func (this *IntSet) Slice() []int {
|
||||
this.mu.RLock()
|
||||
ret := make([]int, len(this.m))
|
||||
// Slice returns the a of items of the set as slice.
|
||||
func (set *IntSet) Slice() []int {
|
||||
set.mu.RLock()
|
||||
ret := make([]int, len(set.m))
|
||||
i := 0
|
||||
for item := range this.m {
|
||||
ret[i] = item
|
||||
for k, _ := range set.m {
|
||||
ret[i] = k
|
||||
i++
|
||||
}
|
||||
|
||||
this.mu.RUnlock()
|
||||
set.mu.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
// 转换为字符串
|
||||
func (this *IntSet) String() string {
|
||||
return fmt.Sprint(this.Slice())
|
||||
// Join joins items with a string <glue>.
|
||||
func (set *IntSet) Join(glue string) string {
|
||||
return strings.Join(gconv.Strings(set.Slice()), ",")
|
||||
}
|
||||
|
||||
func (this *IntSet) LockFunc(f func(m map[int]struct{})) {
|
||||
this.mu.Lock(true)
|
||||
defer this.mu.Unlock(true)
|
||||
f(this.m)
|
||||
// String returns items as a string, which are joined by char ','.
|
||||
func (set *IntSet) String() string {
|
||||
return set.Join(",")
|
||||
}
|
||||
|
||||
func (this *IntSet) RLockFunc(f func(m map[int]struct{})) {
|
||||
this.mu.RLock(true)
|
||||
defer this.mu.RUnlock(true)
|
||||
f(this.m)
|
||||
}
|
||||
// LockFunc locks writing with callback function <f>.
|
||||
func (set *IntSet) LockFunc(f func(m map[int]struct{})) {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
f(set.m)
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with callback function <f>.
|
||||
func (set *IntSet) RLockFunc(f func(m map[int]struct{})) {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
f(set.m)
|
||||
}
|
||||
|
||||
// Equal checks whether the two sets equal.
|
||||
func (set *IntSet) Equal(other *IntSet) bool {
|
||||
if set == other {
|
||||
return true
|
||||
}
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
if len(set.m) != len(other.m) {
|
||||
return false
|
||||
}
|
||||
for key := range set.m {
|
||||
if _, ok := other.m[key]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsSubsetOf checks whether the current set is a sub-set of <other>.
|
||||
func (set *IntSet) IsSubsetOf(other *IntSet) bool {
|
||||
if set == other {
|
||||
return true
|
||||
}
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
for key := range set.m {
|
||||
if _, ok := other.m[key]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Union returns a new set which is the union of <set> and <other>.
|
||||
// Which means, all the items in <newSet> are in <set> or in <other>.
|
||||
func (set *IntSet) Union(others ...*IntSet) (newSet *IntSet) {
|
||||
newSet = NewIntSet(true)
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for _, other := range others {
|
||||
if set != other {
|
||||
other.mu.RLock()
|
||||
}
|
||||
for k, v := range set.m {
|
||||
newSet.m[k] = v
|
||||
}
|
||||
if set != other {
|
||||
for k, v := range other.m {
|
||||
newSet.m[k] = v
|
||||
}
|
||||
}
|
||||
if set != other {
|
||||
other.mu.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Diff returns a new set which is the difference set from <set> to <other>.
|
||||
// Which means, all the items in <newSet> are in <set> but not in <other>.
|
||||
func (set *IntSet) Diff(others ...*IntSet) (newSet *IntSet) {
|
||||
newSet = NewIntSet(true)
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for _, other := range others {
|
||||
if set == other {
|
||||
continue
|
||||
}
|
||||
other.mu.RLock()
|
||||
for k, v := range set.m {
|
||||
if _, ok := other.m[k]; !ok {
|
||||
newSet.m[k] = v
|
||||
}
|
||||
}
|
||||
other.mu.RUnlock()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Intersect returns a new set which is the intersection from <set> to <other>.
|
||||
// Which means, all the items in <newSet> are in <set> and also in <other>.
|
||||
func (set *IntSet) Intersect(others ...*IntSet) (newSet *IntSet) {
|
||||
newSet = NewIntSet(true)
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for _, other := range others {
|
||||
if set != other {
|
||||
other.mu.RLock()
|
||||
}
|
||||
for k, v := range set.m {
|
||||
if _, ok := other.m[k]; ok {
|
||||
newSet.m[k] = v
|
||||
}
|
||||
}
|
||||
if set != other {
|
||||
other.mu.RUnlock()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Complement returns a new set which is the complement from <set> to <full>.
|
||||
// Which means, all the items in <newSet> are in <full> and not in <set>.
|
||||
//
|
||||
// It returns the difference between <full> and <set>
|
||||
// if the given set <full> is not the full set of <set>.
|
||||
func (set *IntSet) Complement(full *IntSet) (newSet *IntSet) {
|
||||
newSet = NewIntSet(true)
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
if set != full {
|
||||
full.mu.RLock()
|
||||
defer full.mu.RUnlock()
|
||||
}
|
||||
for k, v := range full.m {
|
||||
if _, ok := set.m[k]; !ok {
|
||||
newSet.m[k] = v
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Merge adds items from <others> sets into <set>.
|
||||
func (set *IntSet) Merge(others ...*IntSet) *IntSet {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
for _, other := range others {
|
||||
if set != other {
|
||||
other.mu.RLock()
|
||||
}
|
||||
for k, v := range other.m {
|
||||
set.m[k] = v
|
||||
}
|
||||
if set != other {
|
||||
other.mu.RUnlock()
|
||||
}
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
// Sum sums items.
|
||||
// Note: The items should be converted to int type,
|
||||
// or you'd get a result that you unexpected.
|
||||
func (set *IntSet) Sum() (sum int) {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for k, _ := range set.m {
|
||||
sum += k
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Pops randomly pops an item from set.
|
||||
func (set *IntSet) Pop(size int) int {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for k, _ := range set.m {
|
||||
return k
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Pops randomly pops <size> items from set.
|
||||
func (set *IntSet) Pops(size int) []int {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
if size > len(set.m) {
|
||||
size = len(set.m)
|
||||
}
|
||||
index := 0
|
||||
array := make([]int, size)
|
||||
for k, _ := range set.m {
|
||||
array[index] = k
|
||||
index++
|
||||
if index == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
@ -1,114 +0,0 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
//
|
||||
|
||||
package gset
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
)
|
||||
|
||||
type InterfaceSet struct {
|
||||
mu *rwmutex.RWMutex
|
||||
m map[interface{}]struct{}
|
||||
}
|
||||
|
||||
func NewInterfaceSet(safe...bool) *InterfaceSet {
|
||||
return &InterfaceSet{
|
||||
m : make(map[interface{}]struct{}),
|
||||
mu : rwmutex.New(safe...),
|
||||
}
|
||||
}
|
||||
|
||||
// 给定回调函数对原始内容进行遍历,回调函数返回true表示继续遍历,否则停止遍历
|
||||
func (this *InterfaceSet) Iterator(f func (v interface{}) bool) {
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
for k, _ := range this.m {
|
||||
if !f(k) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加
|
||||
func (this *InterfaceSet) Add(item interface{}) *InterfaceSet {
|
||||
this.mu.Lock()
|
||||
this.m[item] = struct{}{}
|
||||
this.mu.Unlock()
|
||||
return this
|
||||
}
|
||||
|
||||
// 批量添加
|
||||
func (this *InterfaceSet) BatchAdd(items []interface{}) *InterfaceSet {
|
||||
this.mu.Lock()
|
||||
for _, item := range items {
|
||||
this.m[item] = struct{}{}
|
||||
}
|
||||
this.mu.Unlock()
|
||||
return this
|
||||
}
|
||||
|
||||
// 键是否存在
|
||||
func (this *InterfaceSet) Contains(item interface{}) bool {
|
||||
this.mu.RLock()
|
||||
_, exists := this.m[item]
|
||||
this.mu.RUnlock()
|
||||
return exists
|
||||
}
|
||||
|
||||
// 删除键值对
|
||||
func (this *InterfaceSet) Remove(key interface{}) {
|
||||
this.mu.Lock()
|
||||
delete(this.m, key)
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 大小
|
||||
func (this *InterfaceSet) Size() int {
|
||||
this.mu.RLock()
|
||||
l := len(this.m)
|
||||
this.mu.RUnlock()
|
||||
return l
|
||||
}
|
||||
|
||||
// 清空set
|
||||
func (this *InterfaceSet) Clear() {
|
||||
this.mu.Lock()
|
||||
this.m = make(map[interface{}]struct{})
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 转换为数组
|
||||
func (this *InterfaceSet) Slice() []interface{} {
|
||||
this.mu.RLock()
|
||||
i := 0
|
||||
ret := make([]interface{}, len(this.m))
|
||||
for item := range this.m {
|
||||
ret[i] = item
|
||||
i++
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
// 转换为字符串
|
||||
func (this *InterfaceSet) String() string {
|
||||
return fmt.Sprint(this.Slice())
|
||||
}
|
||||
|
||||
func (this *InterfaceSet) LockFunc(f func(m map[interface{}]struct{})) {
|
||||
this.mu.Lock(true)
|
||||
defer this.mu.Unlock(true)
|
||||
f(this.m)
|
||||
}
|
||||
|
||||
func (this *InterfaceSet) RLockFunc(f func(m map[interface{}]struct{})) {
|
||||
this.mu.RLock(true)
|
||||
defer this.mu.RUnlock(true)
|
||||
f(this.m)
|
||||
}
|
||||
@ -1,15 +1,16 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
//
|
||||
|
||||
package gset
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
"github.com/gogf/gf/g/internal/rwmutex"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type StringSet struct {
|
||||
@ -17,99 +18,303 @@ type StringSet struct {
|
||||
m map[string]struct{}
|
||||
}
|
||||
|
||||
func NewStringSet(safe...bool) *StringSet {
|
||||
// New create and returns a new set, which contains un-repeated items.
|
||||
// The parameter <unsafe> used to specify whether using set in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewStringSet(unsafe ...bool) *StringSet {
|
||||
return &StringSet{
|
||||
m : make(map[string]struct{}),
|
||||
mu : rwmutex.New(safe...),
|
||||
m: make(map[string]struct{}),
|
||||
mu: rwmutex.New(unsafe...),
|
||||
}
|
||||
}
|
||||
|
||||
// 给定回调函数对原始内容进行遍历,回调函数返回true表示继续遍历,否则停止遍历
|
||||
func (this *StringSet) Iterator(f func (v string) bool) {
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
for k, _ := range this.m {
|
||||
// NewStringSetFrom returns a new set from <items>.
|
||||
func NewStringSetFrom(items []string, unsafe ...bool) *StringSet {
|
||||
m := make(map[string]struct{})
|
||||
for _, v := range items {
|
||||
m[v] = struct{}{}
|
||||
}
|
||||
return &StringSet{
|
||||
m: m,
|
||||
mu: rwmutex.New(unsafe...),
|
||||
}
|
||||
}
|
||||
|
||||
// Iterator iterates the set with given callback function <f>,
|
||||
// if <f> returns true then continue iterating; or false to stop.
|
||||
func (set *StringSet) Iterator(f func(v string) bool) *StringSet {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for k, _ := range set.m {
|
||||
if !f(k) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
// 设置键
|
||||
func (this *StringSet) Add(item string) *StringSet {
|
||||
this.mu.Lock()
|
||||
this.m[item] = struct{}{}
|
||||
this.mu.Unlock()
|
||||
return this
|
||||
// Add adds one or multiple items to the set.
|
||||
func (set *StringSet) Add(item ...string) *StringSet {
|
||||
set.mu.Lock()
|
||||
for _, v := range item {
|
||||
set.m[v] = struct{}{}
|
||||
}
|
||||
set.mu.Unlock()
|
||||
return set
|
||||
}
|
||||
|
||||
// 批量添加设置键
|
||||
func (this *StringSet) BatchAdd(items []string) *StringSet {
|
||||
this.mu.Lock()
|
||||
for _, item := range items {
|
||||
this.m[item] = struct{}{}
|
||||
}
|
||||
this.mu.Unlock()
|
||||
return this
|
||||
}
|
||||
|
||||
// 键是否存在
|
||||
func (this *StringSet) Contains(item string) bool {
|
||||
this.mu.RLock()
|
||||
_, exists := this.m[item]
|
||||
this.mu.RUnlock()
|
||||
// Contains checks whether the set contains <item>.
|
||||
func (set *StringSet) Contains(item string) bool {
|
||||
set.mu.RLock()
|
||||
_, exists := set.m[item]
|
||||
set.mu.RUnlock()
|
||||
return exists
|
||||
}
|
||||
|
||||
// 删除键值对
|
||||
func (this *StringSet) Remove(key string) {
|
||||
this.mu.Lock()
|
||||
delete(this.m, key)
|
||||
this.mu.Unlock()
|
||||
// Remove deletes <item> from set.
|
||||
func (set *StringSet) Remove(item string) *StringSet {
|
||||
set.mu.Lock()
|
||||
delete(set.m, item)
|
||||
set.mu.Unlock()
|
||||
return set
|
||||
}
|
||||
|
||||
// 大小
|
||||
func (this *StringSet) Size() int {
|
||||
this.mu.RLock()
|
||||
l := len(this.m)
|
||||
this.mu.RUnlock()
|
||||
// Size returns the size of the set.
|
||||
func (set *StringSet) Size() int {
|
||||
set.mu.RLock()
|
||||
l := len(set.m)
|
||||
set.mu.RUnlock()
|
||||
return l
|
||||
}
|
||||
|
||||
// 清空set
|
||||
func (this *StringSet) Clear() {
|
||||
this.mu.Lock()
|
||||
this.m = make(map[string]struct{})
|
||||
this.mu.Unlock()
|
||||
// Clear deletes all items of the set.
|
||||
func (set *StringSet) Clear() *StringSet {
|
||||
set.mu.Lock()
|
||||
set.m = make(map[string]struct{})
|
||||
set.mu.Unlock()
|
||||
return set
|
||||
}
|
||||
|
||||
// 转换为数组
|
||||
func (this *StringSet) Slice() []string {
|
||||
this.mu.RLock()
|
||||
ret := make([]string, len(this.m))
|
||||
// Slice returns the a of items of the set as slice.
|
||||
func (set *StringSet) Slice() []string {
|
||||
set.mu.RLock()
|
||||
ret := make([]string, len(set.m))
|
||||
i := 0
|
||||
for item := range this.m {
|
||||
for item := range set.m {
|
||||
ret[i] = item
|
||||
i++
|
||||
}
|
||||
|
||||
this.mu.RUnlock()
|
||||
set.mu.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
// 转换为字符串
|
||||
func (this *StringSet) String() string {
|
||||
return fmt.Sprint(this.Slice())
|
||||
// Join joins items with a string <glue>.
|
||||
func (set *StringSet) Join(glue string) string {
|
||||
return strings.Join(set.Slice(), ",")
|
||||
}
|
||||
|
||||
func (this *StringSet) LockFunc(f func(m map[string]struct{})) {
|
||||
this.mu.Lock(true)
|
||||
defer this.mu.Unlock(true)
|
||||
f(this.m)
|
||||
// String returns items as a string, which are joined by char ','.
|
||||
func (set *StringSet) String() string {
|
||||
return set.Join(",")
|
||||
}
|
||||
|
||||
func (this *StringSet) RLockFunc(f func(m map[string]struct{})) {
|
||||
this.mu.RLock(true)
|
||||
defer this.mu.RUnlock(true)
|
||||
f(this.m)
|
||||
// LockFunc locks writing with callback function <f>.
|
||||
func (set *StringSet) LockFunc(f func(m map[string]struct{})) {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
f(set.m)
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with callback function <f>.
|
||||
func (set *StringSet) RLockFunc(f func(m map[string]struct{})) {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
f(set.m)
|
||||
}
|
||||
|
||||
// Equal checks whether the two sets equal.
|
||||
func (set *StringSet) Equal(other *StringSet) bool {
|
||||
if set == other {
|
||||
return true
|
||||
}
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
if len(set.m) != len(other.m) {
|
||||
return false
|
||||
}
|
||||
for key := range set.m {
|
||||
if _, ok := other.m[key]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsSubsetOf checks whether the current set is a sub-set of <other>.
|
||||
func (set *StringSet) IsSubsetOf(other *StringSet) bool {
|
||||
if set == other {
|
||||
return true
|
||||
}
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
for key := range set.m {
|
||||
if _, ok := other.m[key]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Union returns a new set which is the union of <set> and <other>.
|
||||
// Which means, all the items in <newSet> are in <set> or in <other>.
|
||||
func (set *StringSet) Union(others ...*StringSet) (newSet *StringSet) {
|
||||
newSet = NewStringSet(true)
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for _, other := range others {
|
||||
if set != other {
|
||||
other.mu.RLock()
|
||||
}
|
||||
for k, v := range set.m {
|
||||
newSet.m[k] = v
|
||||
}
|
||||
if set != other {
|
||||
for k, v := range other.m {
|
||||
newSet.m[k] = v
|
||||
}
|
||||
}
|
||||
if set != other {
|
||||
other.mu.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Diff returns a new set which is the difference set from <set> to <other>.
|
||||
// Which means, all the items in <newSet> are in <set> but not in <other>.
|
||||
func (set *StringSet) Diff(others ...*StringSet) (newSet *StringSet) {
|
||||
newSet = NewStringSet(true)
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for _, other := range others {
|
||||
if set == other {
|
||||
continue
|
||||
}
|
||||
other.mu.RLock()
|
||||
for k, v := range set.m {
|
||||
if _, ok := other.m[k]; !ok {
|
||||
newSet.m[k] = v
|
||||
}
|
||||
}
|
||||
other.mu.RUnlock()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Intersect returns a new set which is the intersection from <set> to <other>.
|
||||
// Which means, all the items in <newSet> are in <set> and also in <other>.
|
||||
func (set *StringSet) Intersect(others ...*StringSet) (newSet *StringSet) {
|
||||
newSet = NewStringSet(true)
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for _, other := range others {
|
||||
if set != other {
|
||||
other.mu.RLock()
|
||||
}
|
||||
for k, v := range set.m {
|
||||
if _, ok := other.m[k]; ok {
|
||||
newSet.m[k] = v
|
||||
}
|
||||
}
|
||||
if set != other {
|
||||
other.mu.RUnlock()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Complement returns a new set which is the complement from <set> to <full>.
|
||||
// Which means, all the items in <newSet> are in <full> and not in <set>.
|
||||
//
|
||||
// It returns the difference between <full> and <set>
|
||||
// if the given set <full> is not the full set of <set>.
|
||||
func (set *StringSet) Complement(full *StringSet) (newSet *StringSet) {
|
||||
newSet = NewStringSet(true)
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
if set != full {
|
||||
full.mu.RLock()
|
||||
defer full.mu.RUnlock()
|
||||
}
|
||||
for k, v := range full.m {
|
||||
if _, ok := set.m[k]; !ok {
|
||||
newSet.m[k] = v
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Merge adds items from <others> sets into <set>.
|
||||
func (set *StringSet) Merge(others ...*StringSet) *StringSet {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
for _, other := range others {
|
||||
if set != other {
|
||||
other.mu.RLock()
|
||||
}
|
||||
for k, v := range other.m {
|
||||
set.m[k] = v
|
||||
}
|
||||
if set != other {
|
||||
other.mu.RUnlock()
|
||||
}
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
// Sum sums items.
|
||||
// Note: The items should be converted to int type,
|
||||
// or you'd get a result that you unexpected.
|
||||
func (set *StringSet) Sum() (sum int) {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for k, _ := range set.m {
|
||||
sum += gconv.Int(k)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Pops randomly pops an item from set.
|
||||
func (set *StringSet) Pop(size int) string {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for k, _ := range set.m {
|
||||
return k
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Pops randomly pops <size> items from set.
|
||||
func (set *StringSet) Pops(size int) []string {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
if size > len(set.m) {
|
||||
size = len(set.m)
|
||||
}
|
||||
index := 0
|
||||
array := make([]string, size)
|
||||
for k, _ := range set.m {
|
||||
array[index] = k
|
||||
index++
|
||||
if index == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
@ -1,73 +0,0 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// go test *.go -bench=".*"
|
||||
|
||||
package gset_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"strconv"
|
||||
"gitee.com/johng/gf/g/container/gset"
|
||||
)
|
||||
|
||||
var ints = gset.NewIntSet()
|
||||
var itfs = gset.NewInterfaceSet()
|
||||
var strs = gset.NewStringSet()
|
||||
|
||||
func Benchmark_IntSet_Add(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ints.Add(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_IntSet_Contains(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ints.Contains(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_IntSet_Remove(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ints.Remove(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_InterfaceSet_Add(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
itfs.Add(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_InterfaceSet_Contains(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
itfs.Contains(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_InterfaceSet_Remove(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
itfs.Remove(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_StringSet_Add(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
strs.Add(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_StringSet_Contains(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
strs.Contains(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_StringSet_Remove(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
strs.Remove(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
@ -1,73 +0,0 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// go test *.go -bench=".*"
|
||||
|
||||
package gset_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"strconv"
|
||||
"gitee.com/johng/gf/g/container/gset"
|
||||
)
|
||||
|
||||
var intsUnsafe = gset.NewIntSet(false)
|
||||
var itfsUnsafe = gset.NewInterfaceSet(false)
|
||||
var strsUnsafe = gset.NewStringSet(false)
|
||||
|
||||
func Benchmark_Unsafe_IntSet_Add(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
intsUnsafe.Add(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_IntSet_Contains(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
intsUnsafe.Contains(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_IntSet_Remove(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
intsUnsafe.Remove(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_InterfaceSet_Add(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
itfsUnsafe.Add(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_InterfaceSet_Contains(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
itfsUnsafe.Contains(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_InterfaceSet_Remove(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
itfsUnsafe.Remove(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_StringSet_Add(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
strsUnsafe.Add(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_StringSet_Contains(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
strsUnsafe.Contains(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_StringSet_Remove(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
strsUnsafe.Remove(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
130
g/container/gset/gset_z_bench_test.go
Normal file
130
g/container/gset/gset_z_bench_test.go
Normal file
@ -0,0 +1,130 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// go test *.go -bench=".*"
|
||||
|
||||
package gset_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/gset"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var ints = gset.NewIntSet()
|
||||
var itfs = gset.NewSet()
|
||||
var strs = gset.NewStringSet()
|
||||
var intsUnsafe = gset.NewIntSet(true)
|
||||
var itfsUnsafe = gset.NewSet(true)
|
||||
var strsUnsafe = gset.NewStringSet(true)
|
||||
|
||||
func Benchmark_IntSet_Add(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ints.Add(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_IntSet_Contains(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ints.Contains(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_IntSet_Remove(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ints.Remove(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Set_Add(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
itfs.Add(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Set_Contains(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
itfs.Contains(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Set_Remove(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
itfs.Remove(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_StringSet_Add(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
strs.Add(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_StringSet_Contains(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
strs.Contains(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_StringSet_Remove(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
strs.Remove(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_IntSet_Add(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
intsUnsafe.Add(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_IntSet_Contains(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
intsUnsafe.Contains(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_IntSet_Remove(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
intsUnsafe.Remove(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_Set_Add(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
itfsUnsafe.Add(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_Set_Contains(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
itfsUnsafe.Contains(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_Set_Remove(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
itfsUnsafe.Remove(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_StringSet_Add(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
strsUnsafe.Add(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_StringSet_Contains(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
strsUnsafe.Contains(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_StringSet_Remove(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
strsUnsafe.Remove(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
216
g/container/gset/gset_z_unit_int_test.go
Normal file
216
g/container/gset/gset_z_unit_int_test.go
Normal file
@ -0,0 +1,216 @@
|
||||
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// go test *.go
|
||||
|
||||
package gset_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/garray"
|
||||
"github.com/gogf/gf/g/container/gset"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIntSet_Basic(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s := gset.NewIntSet()
|
||||
s.Add(1).Add(1).Add(2)
|
||||
s.Add([]int{3, 4}...)
|
||||
gtest.Assert(s.Size(), 4)
|
||||
gtest.AssertIN(1, s.Slice())
|
||||
gtest.AssertIN(2, s.Slice())
|
||||
gtest.AssertIN(3, s.Slice())
|
||||
gtest.AssertIN(4, s.Slice())
|
||||
gtest.AssertNI(0, s.Slice())
|
||||
gtest.Assert(s.Contains(4), true)
|
||||
gtest.Assert(s.Contains(5), false)
|
||||
s.Remove(1)
|
||||
gtest.Assert(s.Size(), 3)
|
||||
s.Clear()
|
||||
gtest.Assert(s.Size(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntSet_Iterator(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s := gset.NewIntSet()
|
||||
s.Add(1).Add(2).Add(3)
|
||||
gtest.Assert(s.Size(), 3)
|
||||
|
||||
a1 := garray.New()
|
||||
a2 := garray.New()
|
||||
s.Iterator(func(v int) bool {
|
||||
a1.Append(1)
|
||||
return false
|
||||
})
|
||||
s.Iterator(func(v int) bool {
|
||||
a2.Append(1)
|
||||
return true
|
||||
})
|
||||
gtest.Assert(a1.Len(), 1)
|
||||
gtest.Assert(a2.Len(), 3)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntSet_LockFunc(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s := gset.NewIntSet()
|
||||
s.Add(1).Add(2).Add(3)
|
||||
gtest.Assert(s.Size(), 3)
|
||||
s.LockFunc(func(m map[int]struct{}) {
|
||||
delete(m, 1)
|
||||
})
|
||||
gtest.Assert(s.Size(), 2)
|
||||
s.RLockFunc(func(m map[int]struct{}) {
|
||||
gtest.Assert(m, map[int]struct{}{
|
||||
3: struct{}{},
|
||||
2: struct{}{},
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntSet_Equal(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewIntSet()
|
||||
s2 := gset.NewIntSet()
|
||||
s3 := gset.NewIntSet()
|
||||
s1.Add(1).Add(2).Add(3)
|
||||
s2.Add(1).Add(2).Add(3)
|
||||
s3.Add(1).Add(2).Add(3).Add(4)
|
||||
gtest.Assert(s1.Equal(s2), true)
|
||||
gtest.Assert(s1.Equal(s3), false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntSet_IsSubsetOf(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewIntSet()
|
||||
s2 := gset.NewIntSet()
|
||||
s3 := gset.NewIntSet()
|
||||
s1.Add(1).Add(2)
|
||||
s2.Add(1).Add(2).Add(3)
|
||||
s3.Add(1).Add(2).Add(3).Add(4)
|
||||
gtest.Assert(s1.IsSubsetOf(s2), true)
|
||||
gtest.Assert(s2.IsSubsetOf(s3), true)
|
||||
gtest.Assert(s1.IsSubsetOf(s3), true)
|
||||
gtest.Assert(s2.IsSubsetOf(s1), false)
|
||||
gtest.Assert(s3.IsSubsetOf(s2), false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntSet_Union(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewIntSet()
|
||||
s2 := gset.NewIntSet()
|
||||
s1.Add(1).Add(2)
|
||||
s2.Add(3).Add(4)
|
||||
s3 := s1.Union(s2)
|
||||
gtest.Assert(s3.Contains(1), true)
|
||||
gtest.Assert(s3.Contains(2), true)
|
||||
gtest.Assert(s3.Contains(3), true)
|
||||
gtest.Assert(s3.Contains(4), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntSet_Diff(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewIntSet()
|
||||
s2 := gset.NewIntSet()
|
||||
s1.Add(1).Add(2).Add(3)
|
||||
s2.Add(3).Add(4).Add(5)
|
||||
s3 := s1.Diff(s2)
|
||||
gtest.Assert(s3.Contains(1), true)
|
||||
gtest.Assert(s3.Contains(2), true)
|
||||
gtest.Assert(s3.Contains(3), false)
|
||||
gtest.Assert(s3.Contains(4), false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntSet_Intersect(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewIntSet()
|
||||
s2 := gset.NewIntSet()
|
||||
s1.Add(1).Add(2).Add(3)
|
||||
s2.Add(3).Add(4).Add(5)
|
||||
s3 := s1.Intersect(s2)
|
||||
gtest.Assert(s3.Contains(1), false)
|
||||
gtest.Assert(s3.Contains(2), false)
|
||||
gtest.Assert(s3.Contains(3), true)
|
||||
gtest.Assert(s3.Contains(4), false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntSet_Complement(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewIntSet()
|
||||
s2 := gset.NewIntSet()
|
||||
s1.Add(1).Add(2).Add(3)
|
||||
s2.Add(3).Add(4).Add(5)
|
||||
s3 := s1.Complement(s2)
|
||||
gtest.Assert(s3.Contains(1), false)
|
||||
gtest.Assert(s3.Contains(2), false)
|
||||
gtest.Assert(s3.Contains(4), true)
|
||||
gtest.Assert(s3.Contains(5), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntSet_Size(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewIntSet(true)
|
||||
s1.Add(1).Add(2).Add(3)
|
||||
gtest.Assert(s1.Size(), 3)
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestIntSet_Merge(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewIntSet()
|
||||
s2 := gset.NewIntSet()
|
||||
s1.Add(1).Add(2).Add(3)
|
||||
s2.Add(3).Add(4).Add(5)
|
||||
s3 := s1.Merge(s2)
|
||||
gtest.Assert(s3.Contains(1), true)
|
||||
gtest.Assert(s3.Contains(5), true)
|
||||
gtest.Assert(s3.Contains(6), false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntSet_Join(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewIntSet()
|
||||
s1.Add(1).Add(2).Add(3)
|
||||
s3 := s1.Join(",")
|
||||
gtest.Assert(strings.Contains(s3, "3"), true)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntSet_Sum(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewIntSet()
|
||||
s1.Add(1).Add(2).Add(3)
|
||||
s2 := gset.NewIntSet()
|
||||
s2.Add(5).Add(6).Add(7)
|
||||
gtest.Assert(s2.Sum(), 18)
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestIntSet_Pop(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewIntSet()
|
||||
s1.Add(4).Add(2).Add(3)
|
||||
gtest.AssertIN(s1.Pop(1), []int{4, 2, 3})
|
||||
gtest.AssertIN(s1.Pop(5), []int{4, 2, 3})
|
||||
gtest.Assert(s1.Size(), 3)
|
||||
})
|
||||
}
|
||||
256
g/container/gset/gset_z_unit_string_test.go
Normal file
256
g/container/gset/gset_z_unit_string_test.go
Normal file
@ -0,0 +1,256 @@
|
||||
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// go test *.go
|
||||
|
||||
package gset_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/garray"
|
||||
"github.com/gogf/gf/g/container/gset"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStringSet_Basic(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s := gset.NewStringSet()
|
||||
s.Add("1").Add("1").Add("2")
|
||||
s.Add([]string{"3", "4"}...)
|
||||
gtest.Assert(s.Size(), 4)
|
||||
gtest.AssertIN("1", s.Slice())
|
||||
gtest.AssertIN("2", s.Slice())
|
||||
gtest.AssertIN("3", s.Slice())
|
||||
gtest.AssertIN("4", s.Slice())
|
||||
gtest.AssertNI("0", s.Slice())
|
||||
gtest.Assert(s.Contains("4"), true)
|
||||
gtest.Assert(s.Contains("5"), false)
|
||||
s.Remove("1")
|
||||
gtest.Assert(s.Size(), 3)
|
||||
s.Clear()
|
||||
gtest.Assert(s.Size(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringSet_Iterator(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s := gset.NewStringSet()
|
||||
s.Add("1").Add("2").Add("3")
|
||||
gtest.Assert(s.Size(), 3)
|
||||
|
||||
a1 := garray.New()
|
||||
a2 := garray.New()
|
||||
s.Iterator(func(v string) bool {
|
||||
a1.Append("1")
|
||||
return false
|
||||
})
|
||||
s.Iterator(func(v string) bool {
|
||||
a2.Append("1")
|
||||
return true
|
||||
})
|
||||
gtest.Assert(a1.Len(), 1)
|
||||
gtest.Assert(a2.Len(), 3)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringSet_LockFunc(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s := gset.NewStringSet()
|
||||
s.Add("1").Add("2").Add("3")
|
||||
gtest.Assert(s.Size(), 3)
|
||||
s.LockFunc(func(m map[string]struct{}) {
|
||||
delete(m, "1")
|
||||
})
|
||||
gtest.Assert(s.Size(), 2)
|
||||
s.RLockFunc(func(m map[string]struct{}) {
|
||||
gtest.Assert(m, map[string]struct{}{
|
||||
"3": struct{}{},
|
||||
"2": struct{}{},
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringSet_Equal(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewStringSet()
|
||||
s2 := gset.NewStringSet()
|
||||
s3 := gset.NewStringSet()
|
||||
s1.Add("1").Add("2").Add("3")
|
||||
s2.Add("1").Add("2").Add("3")
|
||||
s3.Add("1").Add("2").Add("3").Add("4")
|
||||
gtest.Assert(s1.Equal(s2), true)
|
||||
gtest.Assert(s1.Equal(s3), false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringSet_IsSubsetOf(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewStringSet()
|
||||
s2 := gset.NewStringSet()
|
||||
s3 := gset.NewStringSet()
|
||||
s1.Add("1").Add("2")
|
||||
s2.Add("1").Add("2").Add("3")
|
||||
s3.Add("1").Add("2").Add("3").Add("4")
|
||||
gtest.Assert(s1.IsSubsetOf(s2), true)
|
||||
gtest.Assert(s2.IsSubsetOf(s3), true)
|
||||
gtest.Assert(s1.IsSubsetOf(s3), true)
|
||||
gtest.Assert(s2.IsSubsetOf(s1), false)
|
||||
gtest.Assert(s3.IsSubsetOf(s2), false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringSet_Union(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewStringSet()
|
||||
s2 := gset.NewStringSet()
|
||||
s1.Add("1").Add("2")
|
||||
s2.Add("3").Add("4")
|
||||
s3 := s1.Union(s2)
|
||||
gtest.Assert(s3.Contains("1"), true)
|
||||
gtest.Assert(s3.Contains("2"), true)
|
||||
gtest.Assert(s3.Contains("3"), true)
|
||||
gtest.Assert(s3.Contains("4"), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringSet_Diff(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewStringSet()
|
||||
s2 := gset.NewStringSet()
|
||||
s1.Add("1").Add("2").Add("3")
|
||||
s2.Add("3").Add("4").Add("5")
|
||||
s3 := s1.Diff(s2)
|
||||
gtest.Assert(s3.Contains("1"), true)
|
||||
gtest.Assert(s3.Contains("2"), true)
|
||||
gtest.Assert(s3.Contains("3"), false)
|
||||
gtest.Assert(s3.Contains("4"), false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringSet_Intersect(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewStringSet()
|
||||
s2 := gset.NewStringSet()
|
||||
s1.Add("1").Add("2").Add("3")
|
||||
s2.Add("3").Add("4").Add("5")
|
||||
s3 := s1.Intersect(s2)
|
||||
gtest.Assert(s3.Contains("1"), false)
|
||||
gtest.Assert(s3.Contains("2"), false)
|
||||
gtest.Assert(s3.Contains("3"), true)
|
||||
gtest.Assert(s3.Contains("4"), false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringSet_Complement(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewStringSet()
|
||||
s2 := gset.NewStringSet()
|
||||
s1.Add("1").Add("2").Add("3")
|
||||
s2.Add("3").Add("4").Add("5")
|
||||
s3 := s1.Complement(s2)
|
||||
gtest.Assert(s3.Contains("1"), false)
|
||||
gtest.Assert(s3.Contains("2"), false)
|
||||
gtest.Assert(s3.Contains("4"), true)
|
||||
gtest.Assert(s3.Contains("5"), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewIntSetFrom(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewIntSetFrom([]int{1, 2, 3, 4})
|
||||
s2 := gset.NewIntSetFrom([]int{5, 6, 7, 8})
|
||||
gtest.Assert(s1.Contains(3), true)
|
||||
gtest.Assert(s1.Contains(5), false)
|
||||
gtest.Assert(s2.Contains(3), false)
|
||||
gtest.Assert(s2.Contains(5), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringSet_Merge(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewStringSet()
|
||||
s2 := gset.NewStringSet()
|
||||
s1.Add("1").Add("2").Add("3")
|
||||
s2.Add("3").Add("4").Add("5")
|
||||
s3 := s1.Merge(s2)
|
||||
gtest.Assert(s3.Contains("1"), true)
|
||||
gtest.Assert(s3.Contains("6"), false)
|
||||
gtest.Assert(s3.Contains("4"), true)
|
||||
gtest.Assert(s3.Contains("5"), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewStringSetFrom(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewStringSetFrom([]string{"a", "b", "c"}, true)
|
||||
gtest.Assert(s1.Contains("b"), true)
|
||||
gtest.Assert(s1.Contains("d"), false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringSet_Join(t *testing.T) {
|
||||
s1 := gset.NewStringSetFrom([]string{"a", "b", "c"}, true)
|
||||
str1 := s1.Join(",")
|
||||
gtest.Assert(strings.Contains(str1, "b"), true)
|
||||
gtest.Assert(strings.Contains(str1, "d"), false)
|
||||
}
|
||||
|
||||
func TestStringSet_String(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewStringSetFrom([]string{"a", "b", "c"}, true)
|
||||
str1 := s1.String()
|
||||
gtest.Assert(strings.Contains(str1, "b"), true)
|
||||
gtest.Assert(strings.Contains(str1, "d"), false)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestStringSet_Sum(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewStringSetFrom([]string{"a", "b", "c"}, true)
|
||||
s2 := gset.NewIntSetFrom([]int{2, 3, 4}, true)
|
||||
gtest.Assert(s1.Sum(), 0)
|
||||
gtest.Assert(s2.Sum(), 9)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringSet_Size(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewStringSetFrom([]string{"a", "b", "c"}, true)
|
||||
gtest.Assert(s1.Size(), 3)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringSet_Remove(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewStringSetFrom([]string{"a", "b", "c"}, true)
|
||||
s1 = s1.Remove("b")
|
||||
gtest.Assert(s1.Contains("b"), false)
|
||||
gtest.Assert(s1.Contains("c"), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringSet_Pop(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewStringSetFrom([]string{"a", "b", "c"}, true)
|
||||
str1 := s1.Pop(1)
|
||||
gtest.Assert(strings.Contains("a,b,c", str1), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringSet_Pops(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewStringSetFrom([]string{"a", "b", "c"}, true)
|
||||
strs1 := s1.Pops(2)
|
||||
gtest.AssertIN(strs1, []string{"a", "b", "c"})
|
||||
gtest.Assert(len(strs1), 2)
|
||||
str2 := s1.Pops(7)
|
||||
gtest.AssertIN(str2, []string{"a", "b", "c"})
|
||||
})
|
||||
}
|
||||
268
g/container/gset/gset_z_unit_test.go
Normal file
268
g/container/gset/gset_z_unit_test.go
Normal file
@ -0,0 +1,268 @@
|
||||
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// go test *.go
|
||||
|
||||
package gset_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/garray"
|
||||
"github.com/gogf/gf/g/container/gset"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"strings"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSet_New(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s := gset.New()
|
||||
s.Add(1).Add(1).Add(2)
|
||||
s.Add([]interface{}{3, 4}...)
|
||||
gtest.Assert(s.Size(), 4)
|
||||
gtest.AssertIN(1, s.Slice())
|
||||
gtest.AssertIN(2, s.Slice())
|
||||
gtest.AssertIN(3, s.Slice())
|
||||
gtest.AssertIN(4, s.Slice())
|
||||
gtest.AssertNI(0, s.Slice())
|
||||
gtest.Assert(s.Contains(4), true)
|
||||
gtest.Assert(s.Contains(5), false)
|
||||
s.Remove(1)
|
||||
gtest.Assert(s.Size(), 3)
|
||||
s.Clear()
|
||||
gtest.Assert(s.Size(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSet_Basic(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s := gset.NewSet()
|
||||
s.Add(1).Add(1).Add(2)
|
||||
s.Add([]interface{}{3, 4}...)
|
||||
gtest.Assert(s.Size(), 4)
|
||||
gtest.AssertIN(1, s.Slice())
|
||||
gtest.AssertIN(2, s.Slice())
|
||||
gtest.AssertIN(3, s.Slice())
|
||||
gtest.AssertIN(4, s.Slice())
|
||||
gtest.AssertNI(0, s.Slice())
|
||||
gtest.Assert(s.Contains(4), true)
|
||||
gtest.Assert(s.Contains(5), false)
|
||||
s.Remove(1)
|
||||
gtest.Assert(s.Size(), 3)
|
||||
s.Clear()
|
||||
gtest.Assert(s.Size(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSet_Iterator(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s := gset.NewSet()
|
||||
s.Add(1).Add(2).Add(3)
|
||||
gtest.Assert(s.Size(), 3)
|
||||
|
||||
a1 := garray.New()
|
||||
a2 := garray.New()
|
||||
s.Iterator(func(v interface{}) bool {
|
||||
a1.Append(1)
|
||||
return false
|
||||
})
|
||||
s.Iterator(func(v interface{}) bool {
|
||||
a2.Append(1)
|
||||
return true
|
||||
})
|
||||
gtest.Assert(a1.Len(), 1)
|
||||
gtest.Assert(a2.Len(), 3)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSet_LockFunc(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s := gset.NewSet()
|
||||
s.Add(1).Add(2).Add(3)
|
||||
gtest.Assert(s.Size(), 3)
|
||||
s.LockFunc(func(m map[interface{}]struct{}) {
|
||||
delete(m, 1)
|
||||
})
|
||||
gtest.Assert(s.Size(), 2)
|
||||
s.RLockFunc(func(m map[interface{}]struct{}) {
|
||||
gtest.Assert(m, map[interface{}]struct{}{
|
||||
3: struct{}{},
|
||||
2: struct{}{},
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSet_Equal(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewSet()
|
||||
s2 := gset.NewSet()
|
||||
s3 := gset.NewSet()
|
||||
s1.Add(1).Add(2).Add(3)
|
||||
s2.Add(1).Add(2).Add(3)
|
||||
s3.Add(1).Add(2).Add(3).Add(4)
|
||||
gtest.Assert(s1.Equal(s2), true)
|
||||
gtest.Assert(s1.Equal(s3), false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSet_IsSubsetOf(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewSet()
|
||||
s2 := gset.NewSet()
|
||||
s3 := gset.NewSet()
|
||||
s1.Add(1).Add(2)
|
||||
s2.Add(1).Add(2).Add(3)
|
||||
s3.Add(1).Add(2).Add(3).Add(4)
|
||||
gtest.Assert(s1.IsSubsetOf(s2), true)
|
||||
gtest.Assert(s2.IsSubsetOf(s3), true)
|
||||
gtest.Assert(s1.IsSubsetOf(s3), true)
|
||||
gtest.Assert(s2.IsSubsetOf(s1), false)
|
||||
gtest.Assert(s3.IsSubsetOf(s2), false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSet_Union(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewSet()
|
||||
s2 := gset.NewSet()
|
||||
s1.Add(1).Add(2)
|
||||
s2.Add(3).Add(4)
|
||||
s3 := s1.Union(s2)
|
||||
gtest.Assert(s3.Contains(1), true)
|
||||
gtest.Assert(s3.Contains(2), true)
|
||||
gtest.Assert(s3.Contains(3), true)
|
||||
gtest.Assert(s3.Contains(4), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSet_Diff(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewSet()
|
||||
s2 := gset.NewSet()
|
||||
s1.Add(1).Add(2).Add(3)
|
||||
s2.Add(3).Add(4).Add(5)
|
||||
s3 := s1.Diff(s2)
|
||||
gtest.Assert(s3.Contains(1), true)
|
||||
gtest.Assert(s3.Contains(2), true)
|
||||
gtest.Assert(s3.Contains(3), false)
|
||||
gtest.Assert(s3.Contains(4), false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSet_Intersect(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewSet()
|
||||
s2 := gset.NewSet()
|
||||
s1.Add(1).Add(2).Add(3)
|
||||
s2.Add(3).Add(4).Add(5)
|
||||
s3 := s1.Intersect(s2)
|
||||
gtest.Assert(s3.Contains(1), false)
|
||||
gtest.Assert(s3.Contains(2), false)
|
||||
gtest.Assert(s3.Contains(3), true)
|
||||
gtest.Assert(s3.Contains(4), false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSet_Complement(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewSet()
|
||||
s2 := gset.NewSet()
|
||||
s1.Add(1).Add(2).Add(3)
|
||||
s2.Add(3).Add(4).Add(5)
|
||||
s3 := s1.Complement(s2)
|
||||
gtest.Assert(s3.Contains(1), false)
|
||||
gtest.Assert(s3.Contains(2), false)
|
||||
gtest.Assert(s3.Contains(4), true)
|
||||
gtest.Assert(s3.Contains(5), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewFrom(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.NewFrom("a")
|
||||
s2 := gset.NewFrom("b", false)
|
||||
s3 := gset.NewFrom(3, true)
|
||||
s4 := gset.NewFrom([]string{"s1", "s2"}, true)
|
||||
gtest.Assert(s1.Contains("a"), true)
|
||||
gtest.Assert(s2.Contains("b"), true)
|
||||
gtest.Assert(s3.Contains(3), true)
|
||||
gtest.Assert(s4.Contains("s1"), true)
|
||||
gtest.Assert(s4.Contains("s3"), false)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.New()
|
||||
s1.Add("a").Add(2)
|
||||
s2 := gset.New(true)
|
||||
s2.Add("b").Add(3)
|
||||
gtest.Assert(s1.Contains("a"), true)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestSet_Join(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.New(true)
|
||||
s1.Add("a").Add("a1").Add("b").Add("c")
|
||||
str1 := s1.Join(",")
|
||||
gtest.Assert(strings.Contains(str1, "a1"), true)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestSet_String(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.New(true)
|
||||
s1.Add("a").Add("a2").Add("b").Add("c")
|
||||
str1 := s1.String()
|
||||
gtest.Assert(strings.Contains(str1, "a2"), true)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestSet_Merge(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.New(true)
|
||||
s2 := gset.New(true)
|
||||
s1.Add("a").Add("a2").Add("b").Add("c")
|
||||
s2.Add("b").Add("b1").Add("e").Add("f")
|
||||
ss := s1.Merge(s2)
|
||||
gtest.Assert(ss.Contains("a2"), true)
|
||||
gtest.Assert(ss.Contains("b1"), true)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestSet_Sum(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.New(true)
|
||||
s1.Add(1).Add(2).Add(3).Add(4)
|
||||
gtest.Assert(s1.Sum(), int(10))
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestSet_Pop(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.New(true)
|
||||
s1.Add(1).Add(2).Add(3).Add(4)
|
||||
gtest.AssertIN(s1.Pop(1), []int{1, 2, 3, 4})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSet_Pops(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s1 := gset.New(true)
|
||||
s1.Add(1).Add(2).Add(3).Add(4)
|
||||
gtest.AssertIN(s1.Pops(1), []int{1, 2, 3, 4})
|
||||
gtest.AssertIN(s1.Pops(6), []int{1, 2, 3, 4})
|
||||
gtest.Assert(len(s1.Pops(2)), 2)
|
||||
})
|
||||
}
|
||||
8
g/container/gtree/gtree.go
Normal file
8
g/container/gtree/gtree.go
Normal file
@ -0,0 +1,8 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package gtree provides concurrent-safe/unsafe tree containers.
|
||||
package gtree
|
||||
706
g/container/gtree/gtree_avltree.go
Normal file
706
g/container/gtree/gtree_avltree.go
Normal file
@ -0,0 +1,706 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gtree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/container/gvar"
|
||||
"github.com/gogf/gf/g/internal/rwmutex"
|
||||
)
|
||||
|
||||
// AVLTree holds elements of the AVL tree.
|
||||
type AVLTree struct {
|
||||
mu *rwmutex.RWMutex
|
||||
root *AVLTreeNode
|
||||
comparator func(v1, v2 interface{}) int
|
||||
size int
|
||||
}
|
||||
|
||||
// AVLTreeNode is a single element within the tree.
|
||||
type AVLTreeNode struct {
|
||||
Key interface{}
|
||||
Value interface{}
|
||||
parent *AVLTreeNode
|
||||
children [2]*AVLTreeNode
|
||||
b int8
|
||||
}
|
||||
|
||||
// NewAVLTree instantiates an AVL tree with the custom key comparator.
|
||||
// The parameter <unsafe> used to specify whether using tree in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewAVLTree(comparator func(v1, v2 interface{}) int, unsafe ...bool) *AVLTree {
|
||||
return &AVLTree{
|
||||
mu: rwmutex.New(unsafe...),
|
||||
comparator: comparator,
|
||||
}
|
||||
}
|
||||
|
||||
// NewAVLTreeFrom instantiates an AVL tree with the custom key comparator and data map.
|
||||
// The parameter <unsafe> used to specify whether using tree in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewAVLTreeFrom(comparator func(v1, v2 interface{}) int, data map[interface{}]interface{}, unsafe ...bool) *AVLTree {
|
||||
tree := NewAVLTree(comparator, unsafe...)
|
||||
for k, v := range data {
|
||||
tree.put(k, v, nil, &tree.root)
|
||||
}
|
||||
return tree
|
||||
}
|
||||
|
||||
// Clone returns a new tree with a copy of current tree.
|
||||
func (tree *AVLTree) Clone(unsafe ...bool) *AVLTree {
|
||||
newTree := NewAVLTree(tree.comparator, !tree.mu.IsSafe())
|
||||
newTree.Sets(tree.Map())
|
||||
return newTree
|
||||
}
|
||||
|
||||
// Set inserts node into the tree.
|
||||
func (tree *AVLTree) Set(key interface{}, value interface{}) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
tree.put(key, value, nil, &tree.root)
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the tree.
|
||||
func (tree *AVLTree) Sets(data map[interface{}]interface{}) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
for key, value := range data {
|
||||
tree.put(key, value, nil, &tree.root)
|
||||
}
|
||||
}
|
||||
|
||||
// Search searches the tree with given <key>.
|
||||
// Second return parameter <found> is true if key was found, otherwise false.
|
||||
func (tree *AVLTree) Search(key interface{}) (value interface{}, found bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.doSearch(key)
|
||||
}
|
||||
|
||||
// doSearch searches the tree with given <key>.
|
||||
// Second return parameter <found> is true if key was found, otherwise false.
|
||||
func (tree *AVLTree) doSearch(key interface{}) (value interface{}, found bool) {
|
||||
n := tree.root
|
||||
for n != nil {
|
||||
cmp := tree.comparator(key, n.Key)
|
||||
switch {
|
||||
case cmp == 0:
|
||||
return n.Value, true
|
||||
case cmp < 0:
|
||||
n = n.children[0]
|
||||
case cmp > 0:
|
||||
n = n.children[1]
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Get searches the node in the tree by <key> and returns its value or nil if key is not found in tree.
|
||||
func (tree *AVLTree) Get(key interface{}) (value interface{}) {
|
||||
value, _ = tree.Search(key)
|
||||
return
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given <key>,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// When setting value, if <value> is type of <func() interface {}>,
|
||||
// it will be executed with mutex.Lock of the hash map,
|
||||
// and its return value will be set to the map with <key>.
|
||||
//
|
||||
// It returns value with given <key>.
|
||||
func (tree *AVLTree) doSetWithLockCheck(key interface{}, value interface{}) interface{} {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if v, ok := tree.doSearch(key); ok {
|
||||
return v
|
||||
}
|
||||
if f, ok := value.(func() interface{}); ok {
|
||||
value = f()
|
||||
}
|
||||
tree.put(key, value, nil, &tree.root)
|
||||
return value
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or set value with given <value> if not exist and returns this value.
|
||||
func (tree *AVLTree) GetOrSet(key interface{}, value interface{}) interface{} {
|
||||
if v, ok := tree.Search(key); !ok {
|
||||
return tree.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with return value of callback function <f> if not exist
|
||||
// and returns this value.
|
||||
func (tree *AVLTree) GetOrSetFunc(key interface{}, f func() interface{}) interface{} {
|
||||
if v, ok := tree.Search(key); !ok {
|
||||
return tree.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
// or sets value with return value of callback function <f> if not exist
|
||||
// and returns this value.
|
||||
//
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
|
||||
// with mutex.Lock of the hash map.
|
||||
func (tree *AVLTree) GetOrSetFuncLock(key interface{}, f func() interface{}) interface{} {
|
||||
if v, ok := tree.Search(key); !ok {
|
||||
return tree.doSetWithLockCheck(key, f)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetVar returns a gvar.Var with the value by given <key>.
|
||||
// The returned gvar.Var is un-concurrent safe.
|
||||
func (tree *AVLTree) GetVar(key interface{}) *gvar.Var {
|
||||
return gvar.New(tree.Get(key), true)
|
||||
}
|
||||
|
||||
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
|
||||
// The returned gvar.Var is un-concurrent safe.
|
||||
func (tree *AVLTree) GetVarOrSet(key interface{}, value interface{}) *gvar.Var {
|
||||
return gvar.New(tree.GetOrSet(key, value), true)
|
||||
}
|
||||
|
||||
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
|
||||
// The returned gvar.Var is un-concurrent safe.
|
||||
func (tree *AVLTree) GetVarOrSetFunc(key interface{}, f func() interface{}) *gvar.Var {
|
||||
return gvar.New(tree.GetOrSetFunc(key, f), true)
|
||||
}
|
||||
|
||||
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
|
||||
// The returned gvar.Var is un-concurrent safe.
|
||||
func (tree *AVLTree) GetVarOrSetFuncLock(key interface{}, f func() interface{}) *gvar.Var {
|
||||
return gvar.New(tree.GetOrSetFuncLock(key, f), true)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
func (tree *AVLTree) SetIfNotExist(key interface{}, value interface{}) bool {
|
||||
if !tree.Contains(key) {
|
||||
tree.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
func (tree *AVLTree) SetIfNotExistFunc(key interface{}, f func() interface{}) bool {
|
||||
if !tree.Contains(key) {
|
||||
tree.doSetWithLockCheck(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
//
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function <f> with mutex.Lock of the hash map.
|
||||
func (tree *AVLTree) SetIfNotExistFuncLock(key interface{}, f func() interface{}) bool {
|
||||
if !tree.Contains(key) {
|
||||
tree.doSetWithLockCheck(key, f)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Contains checks whether <key> exists in the tree.
|
||||
func (tree *AVLTree) Contains(key interface{}) bool {
|
||||
_, ok := tree.Search(key)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Remove removes the node from the tree by key.
|
||||
// Key should adhere to the comparator's type assertion, otherwise method panics.
|
||||
func (tree *AVLTree) Remove(key interface{}) (value interface{}) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
value, _ = tree.remove(key, &tree.root)
|
||||
return
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the tree by <keys>.
|
||||
func (tree *AVLTree) Removes(keys []interface{}) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
for _, key := range keys {
|
||||
tree.remove(key, &tree.root)
|
||||
}
|
||||
}
|
||||
|
||||
// IsEmpty returns true if tree does not contain any nodes.
|
||||
func (tree *AVLTree) IsEmpty() bool {
|
||||
return tree.Size() == 0
|
||||
}
|
||||
|
||||
// Size returns number of nodes in the tree.
|
||||
func (tree *AVLTree) Size() int {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.size
|
||||
}
|
||||
|
||||
// Keys returns all keys in asc order.
|
||||
func (tree *AVLTree) Keys() []interface{} {
|
||||
keys := make([]interface{}, tree.Size())
|
||||
index := 0
|
||||
tree.IteratorAsc(func(key, value interface{}) bool {
|
||||
keys[index] = key
|
||||
index++
|
||||
return true
|
||||
})
|
||||
return keys
|
||||
}
|
||||
|
||||
// Values returns all values in asc order based on the key.
|
||||
func (tree *AVLTree) Values() []interface{} {
|
||||
values := make([]interface{}, tree.Size())
|
||||
index := 0
|
||||
tree.IteratorAsc(func(key, value interface{}) bool {
|
||||
values[index] = value
|
||||
index++
|
||||
return true
|
||||
})
|
||||
return values
|
||||
}
|
||||
|
||||
// Left returns the minimum element of the AVL tree
|
||||
// or nil if the tree is empty.
|
||||
func (tree *AVLTree) Left() *AVLTreeNode {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node := tree.bottom(0)
|
||||
if tree.mu.IsSafe() {
|
||||
return &AVLTreeNode{
|
||||
Key: node.Key,
|
||||
Value: node.Value,
|
||||
}
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// Right returns the maximum element of the AVL tree
|
||||
// or nil if the tree is empty.
|
||||
func (tree *AVLTree) Right() *AVLTreeNode {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node := tree.bottom(1)
|
||||
if tree.mu.IsSafe() {
|
||||
return &AVLTreeNode{
|
||||
Key: node.Key,
|
||||
Value: node.Value,
|
||||
}
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// Floor Finds floor node of the input key, return the floor node or nil if no floor node is found.
|
||||
// Second return parameter is true if floor was found, otherwise false.
|
||||
//
|
||||
// Floor node is defined as the largest node that is smaller than or equal to the given node.
|
||||
// A floor node may not be found, either because the tree is empty, or because
|
||||
// all nodes in the tree is larger than the given node.
|
||||
//
|
||||
// Key should adhere to the comparator's type assertion, otherwise method panics.
|
||||
func (tree *AVLTree) Floor(key interface{}) (floor *AVLTreeNode, found bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
n := tree.root
|
||||
for n != nil {
|
||||
c := tree.comparator(key, n.Key)
|
||||
switch {
|
||||
case c == 0:
|
||||
return n, true
|
||||
case c < 0:
|
||||
n = n.children[0]
|
||||
case c > 0:
|
||||
floor, found = n, true
|
||||
n = n.children[1]
|
||||
}
|
||||
}
|
||||
if found {
|
||||
return
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Ceiling finds ceiling node of the input key, return the ceiling node or nil if no ceiling node is found.
|
||||
// Second return parameter is true if ceiling was found, otherwise false.
|
||||
//
|
||||
// Ceiling node is defined as the smallest node that is larger than or equal to the given node.
|
||||
// A ceiling node may not be found, either because the tree is empty, or because
|
||||
// all nodes in the tree is smaller than the given node.
|
||||
//
|
||||
// Key should adhere to the comparator's type assertion, otherwise method panics.
|
||||
func (tree *AVLTree) Ceiling(key interface{}) (ceiling *AVLTreeNode, found bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
n := tree.root
|
||||
for n != nil {
|
||||
c := tree.comparator(key, n.Key)
|
||||
switch {
|
||||
case c == 0:
|
||||
return n, true
|
||||
case c > 0:
|
||||
n = n.children[1]
|
||||
case c < 0:
|
||||
ceiling, found = n, true
|
||||
n = n.children[0]
|
||||
}
|
||||
}
|
||||
if found {
|
||||
return
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Clear removes all nodes from the tree.
|
||||
func (tree *AVLTree) Clear() {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
tree.root = nil
|
||||
tree.size = 0
|
||||
}
|
||||
|
||||
// String returns a string representation of container
|
||||
func (tree *AVLTree) String() string {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
str := "AVLTree\n"
|
||||
if tree.size != 0 {
|
||||
output(tree.root, "", true, &str)
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// Print prints the tree to stdout.
|
||||
func (tree *AVLTree) Print() {
|
||||
fmt.Println(tree.String())
|
||||
}
|
||||
|
||||
// Map returns all key-value items as map.
|
||||
func (tree *AVLTree) Map() map[interface{}]interface{} {
|
||||
m := make(map[interface{}]interface{}, tree.Size())
|
||||
tree.IteratorAsc(func(key, value interface{}) bool {
|
||||
m[key] = value
|
||||
return true
|
||||
})
|
||||
return m
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the tree to value-key.
|
||||
// Note that you should guarantee the value is the same type as key,
|
||||
// or else the comparator would panic.
|
||||
//
|
||||
// If the type of value is different with key, you pass the new <comparator>.
|
||||
func (tree *AVLTree) Flip(comparator ...func(v1, v2 interface{}) int) {
|
||||
t := (*AVLTree)(nil)
|
||||
if len(comparator) > 0 {
|
||||
t = NewAVLTree(comparator[0], !tree.mu.IsSafe())
|
||||
} else {
|
||||
t = NewAVLTree(tree.comparator, !tree.mu.IsSafe())
|
||||
}
|
||||
tree.IteratorAsc(func(key, value interface{}) bool {
|
||||
t.put(value, key, nil, &t.root)
|
||||
return true
|
||||
})
|
||||
tree.mu.Lock()
|
||||
tree.root = t.root
|
||||
tree.size = t.size
|
||||
tree.mu.Unlock()
|
||||
}
|
||||
|
||||
// Iterator is alias of IteratorAsc.
|
||||
func (tree *AVLTree) Iterator(f func(key, value interface{}) bool) {
|
||||
tree.IteratorAsc(f)
|
||||
}
|
||||
|
||||
// IteratorAsc iterates the tree in ascending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (tree *AVLTree) IteratorAsc(f func(key, value interface{}) bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node := tree.bottom(0)
|
||||
for node != nil {
|
||||
if !f(node.Key, node.Value) {
|
||||
return
|
||||
}
|
||||
node = node.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// IteratorDesc iterates the tree in descending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (tree *AVLTree) IteratorDesc(f func(key, value interface{}) bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node := tree.bottom(1)
|
||||
for node != nil {
|
||||
if !f(node.Key, node.Value) {
|
||||
return
|
||||
}
|
||||
node = node.Prev()
|
||||
}
|
||||
}
|
||||
|
||||
func (tree *AVLTree) put(key interface{}, value interface{}, p *AVLTreeNode, qp **AVLTreeNode) bool {
|
||||
q := *qp
|
||||
if q == nil {
|
||||
tree.size++
|
||||
*qp = &AVLTreeNode{Key: key, Value: value, parent: p}
|
||||
return true
|
||||
}
|
||||
|
||||
c := tree.comparator(key, q.Key)
|
||||
if c == 0 {
|
||||
q.Key = key
|
||||
q.Value = value
|
||||
return false
|
||||
}
|
||||
|
||||
if c < 0 {
|
||||
c = -1
|
||||
} else {
|
||||
c = 1
|
||||
}
|
||||
a := (c + 1) / 2
|
||||
if tree.put(key, value, q, &q.children[a]) {
|
||||
return putFix(int8(c), qp)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (tree *AVLTree) remove(key interface{}, qp **AVLTreeNode) (value interface{}, fix bool) {
|
||||
q := *qp
|
||||
if q == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
c := tree.comparator(key, q.Key)
|
||||
if c == 0 {
|
||||
tree.size--
|
||||
value = q.Value
|
||||
fix = true
|
||||
if q.children[1] == nil {
|
||||
if q.children[0] != nil {
|
||||
q.children[0].parent = q.parent
|
||||
}
|
||||
*qp = q.children[0]
|
||||
return
|
||||
}
|
||||
if removeMin(&q.children[1], &q.Key, &q.Value) {
|
||||
return value, removeFix(-1, qp)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if c < 0 {
|
||||
c = -1
|
||||
} else {
|
||||
c = 1
|
||||
}
|
||||
a := (c + 1) / 2
|
||||
value, fix = tree.remove(key, &q.children[a])
|
||||
if fix {
|
||||
return value, removeFix(int8(-c), qp)
|
||||
}
|
||||
return value, false
|
||||
}
|
||||
|
||||
func removeMin(qp **AVLTreeNode, minKey *interface{}, minVal *interface{}) bool {
|
||||
q := *qp
|
||||
if q.children[0] == nil {
|
||||
*minKey = q.Key
|
||||
*minVal = q.Value
|
||||
if q.children[1] != nil {
|
||||
q.children[1].parent = q.parent
|
||||
}
|
||||
*qp = q.children[1]
|
||||
return true
|
||||
}
|
||||
fix := removeMin(&q.children[0], minKey, minVal)
|
||||
if fix {
|
||||
return removeFix(1, qp)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func putFix(c int8, t **AVLTreeNode) bool {
|
||||
s := *t
|
||||
if s.b == 0 {
|
||||
s.b = c
|
||||
return true
|
||||
}
|
||||
|
||||
if s.b == -c {
|
||||
s.b = 0
|
||||
return false
|
||||
}
|
||||
|
||||
if s.children[(c+1)/2].b == c {
|
||||
s = singleRotate(c, s)
|
||||
} else {
|
||||
s = doubleRotate(c, s)
|
||||
}
|
||||
*t = s
|
||||
return false
|
||||
}
|
||||
|
||||
func removeFix(c int8, t **AVLTreeNode) bool {
|
||||
s := *t
|
||||
if s.b == 0 {
|
||||
s.b = c
|
||||
return false
|
||||
}
|
||||
|
||||
if s.b == -c {
|
||||
s.b = 0
|
||||
return true
|
||||
}
|
||||
|
||||
a := (c + 1) / 2
|
||||
if s.children[a].b == 0 {
|
||||
s = rotate(c, s)
|
||||
s.b = -c
|
||||
*t = s
|
||||
return false
|
||||
}
|
||||
|
||||
if s.children[a].b == c {
|
||||
s = singleRotate(c, s)
|
||||
} else {
|
||||
s = doubleRotate(c, s)
|
||||
}
|
||||
*t = s
|
||||
return true
|
||||
}
|
||||
|
||||
func singleRotate(c int8, s *AVLTreeNode) *AVLTreeNode {
|
||||
s.b = 0
|
||||
s = rotate(c, s)
|
||||
s.b = 0
|
||||
return s
|
||||
}
|
||||
|
||||
func doubleRotate(c int8, s *AVLTreeNode) *AVLTreeNode {
|
||||
a := (c + 1) / 2
|
||||
r := s.children[a]
|
||||
s.children[a] = rotate(-c, s.children[a])
|
||||
p := rotate(c, s)
|
||||
|
||||
switch {
|
||||
default:
|
||||
s.b = 0
|
||||
r.b = 0
|
||||
case p.b == c:
|
||||
s.b = -c
|
||||
r.b = 0
|
||||
case p.b == -c:
|
||||
s.b = 0
|
||||
r.b = c
|
||||
}
|
||||
|
||||
p.b = 0
|
||||
return p
|
||||
}
|
||||
|
||||
func rotate(c int8, s *AVLTreeNode) *AVLTreeNode {
|
||||
a := (c + 1) / 2
|
||||
r := s.children[a]
|
||||
s.children[a] = r.children[a^1]
|
||||
if s.children[a] != nil {
|
||||
s.children[a].parent = s
|
||||
}
|
||||
r.children[a^1] = s
|
||||
r.parent = s.parent
|
||||
s.parent = r
|
||||
return r
|
||||
}
|
||||
|
||||
func (tree *AVLTree) bottom(d int) *AVLTreeNode {
|
||||
n := tree.root
|
||||
if n == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for c := n.children[d]; c != nil; c = n.children[d] {
|
||||
n = c
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Prev returns the previous element in an inorder
|
||||
// walk of the AVL tree.
|
||||
func (node *AVLTreeNode) Prev() *AVLTreeNode {
|
||||
return node.walk1(0)
|
||||
}
|
||||
|
||||
// Next returns the next element in an inorder
|
||||
// walk of the AVL tree.
|
||||
func (node *AVLTreeNode) Next() *AVLTreeNode {
|
||||
return node.walk1(1)
|
||||
}
|
||||
|
||||
func (node *AVLTreeNode) walk1(a int) *AVLTreeNode {
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
n := node
|
||||
if n.children[a] != nil {
|
||||
n = n.children[a]
|
||||
for n.children[a^1] != nil {
|
||||
n = n.children[a^1]
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
p := n.parent
|
||||
for p != nil && p.children[a] == n {
|
||||
n = p
|
||||
p = p.parent
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func output(node *AVLTreeNode, prefix string, isTail bool, str *string) {
|
||||
if node.children[1] != nil {
|
||||
newPrefix := prefix
|
||||
if isTail {
|
||||
newPrefix += "│ "
|
||||
} else {
|
||||
newPrefix += " "
|
||||
}
|
||||
output(node.children[1], newPrefix, false, str)
|
||||
}
|
||||
*str += prefix
|
||||
if isTail {
|
||||
*str += "└── "
|
||||
} else {
|
||||
*str += "┌── "
|
||||
}
|
||||
*str += fmt.Sprintf("%v\n", node.Key)
|
||||
if node.children[0] != nil {
|
||||
newPrefix := prefix
|
||||
if isTail {
|
||||
newPrefix += " "
|
||||
} else {
|
||||
newPrefix += "│ "
|
||||
}
|
||||
output(node.children[0], newPrefix, true, str)
|
||||
}
|
||||
}
|
||||
847
g/container/gtree/gtree_btree.go
Normal file
847
g/container/gtree/gtree_btree.go
Normal file
@ -0,0 +1,847 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gtree
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/container/gvar"
|
||||
"github.com/gogf/gf/g/internal/rwmutex"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BTree holds elements of the B-tree.
|
||||
type BTree struct {
|
||||
mu *rwmutex.RWMutex
|
||||
root *BTreeNode
|
||||
comparator func(v1, v2 interface{}) int
|
||||
size int // Total number of keys in the tree
|
||||
m int // order (maximum number of children)
|
||||
}
|
||||
|
||||
// BTreeNode is a single element within the tree.
|
||||
type BTreeNode struct {
|
||||
Parent *BTreeNode
|
||||
Entries []*BTreeEntry // Contained keys in node
|
||||
Children []*BTreeNode // Children nodes
|
||||
}
|
||||
|
||||
// BTreeEntry represents the key-value pair contained within nodes.
|
||||
type BTreeEntry struct {
|
||||
Key interface{}
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
// NewBTree instantiates a B-tree with <m> (maximum number of children) and a custom key comparator.
|
||||
// The parameter <unsafe> used to specify whether using tree in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
// Note that the <m> must be greater or equal than 3, or else it panics.
|
||||
func NewBTree(m int, comparator func(v1, v2 interface{}) int, unsafe ...bool) *BTree {
|
||||
if m < 3 {
|
||||
panic("Invalid order, should be at least 3")
|
||||
}
|
||||
return &BTree{
|
||||
comparator: comparator,
|
||||
mu: rwmutex.New(unsafe...),
|
||||
m: m,
|
||||
}
|
||||
}
|
||||
|
||||
// NewBTreeFrom instantiates a B-tree with <m> (maximum number of children), a custom key comparator and data map.
|
||||
// The parameter <unsafe> used to specify whether using tree in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewBTreeFrom(m int, comparator func(v1, v2 interface{}) int, data map[interface{}]interface{}, unsafe ...bool) *BTree {
|
||||
tree := NewBTree(m, comparator, unsafe...)
|
||||
for k, v := range data {
|
||||
tree.doSet(k, v)
|
||||
}
|
||||
return tree
|
||||
}
|
||||
|
||||
// Clone returns a new tree with a copy of current tree.
|
||||
func (tree *BTree) Clone(unsafe ...bool) *BTree {
|
||||
newTree := NewBTree(tree.m, tree.comparator, !tree.mu.IsSafe())
|
||||
newTree.Sets(tree.Map())
|
||||
return newTree
|
||||
}
|
||||
|
||||
// Set inserts key-value item into the tree.
|
||||
func (tree *BTree) Set(key interface{}, value interface{}) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
tree.doSet(key, value)
|
||||
}
|
||||
|
||||
// doSet inserts key-value pair node into the tree.
|
||||
// If key already exists, then its value is updated with the new value.
|
||||
func (tree *BTree) doSet(key interface{}, value interface{}) {
|
||||
entry := &BTreeEntry{Key: key, Value: value}
|
||||
if tree.root == nil {
|
||||
tree.root = &BTreeNode{Entries: []*BTreeEntry{entry}, Children: []*BTreeNode{}}
|
||||
tree.size++
|
||||
return
|
||||
}
|
||||
|
||||
if tree.insert(tree.root, entry) {
|
||||
tree.size++
|
||||
}
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the tree.
|
||||
func (tree *BTree) Sets(data map[interface{}]interface{}) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
for k, v := range data {
|
||||
tree.doSet(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Get searches the node in the tree by <key> and returns its value or nil if key is not found in tree.
|
||||
func (tree *BTree) Get(key interface{}) (value interface{}) {
|
||||
value, _ = tree.Search(key)
|
||||
return
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given <key>,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// When setting value, if <value> is type of <func() interface {}>,
|
||||
// it will be executed with mutex.Lock of the hash map,
|
||||
// and its return value will be set to the map with <key>.
|
||||
//
|
||||
// It returns value with given <key>.
|
||||
func (tree *BTree) doSetWithLockCheck(key interface{}, value interface{}) interface{} {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if entry := tree.doSearch(key); entry != nil {
|
||||
return entry.Value
|
||||
}
|
||||
if f, ok := value.(func() interface{}); ok {
|
||||
value = f()
|
||||
}
|
||||
tree.doSet(key, value)
|
||||
return value
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or set value with given <value> if not exist and returns this value.
|
||||
func (tree *BTree) GetOrSet(key interface{}, value interface{}) interface{} {
|
||||
if v, ok := tree.Search(key); !ok {
|
||||
return tree.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with return value of callback function <f> if not exist
|
||||
// and returns this value.
|
||||
func (tree *BTree) GetOrSetFunc(key interface{}, f func() interface{}) interface{} {
|
||||
if v, ok := tree.Search(key); !ok {
|
||||
return tree.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
// or sets value with return value of callback function <f> if not exist
|
||||
// and returns this value.
|
||||
//
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
|
||||
// with mutex.Lock of the hash map.
|
||||
func (tree *BTree) GetOrSetFuncLock(key interface{}, f func() interface{}) interface{} {
|
||||
if v, ok := tree.Search(key); !ok {
|
||||
return tree.doSetWithLockCheck(key, f)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetVar returns a gvar.Var with the value by given <key>.
|
||||
// The returned gvar.Var is un-concurrent safe.
|
||||
func (tree *BTree) GetVar(key interface{}) *gvar.Var {
|
||||
return gvar.New(tree.Get(key), true)
|
||||
}
|
||||
|
||||
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
|
||||
// The returned gvar.Var is un-concurrent safe.
|
||||
func (tree *BTree) GetVarOrSet(key interface{}, value interface{}) *gvar.Var {
|
||||
return gvar.New(tree.GetOrSet(key, value), true)
|
||||
}
|
||||
|
||||
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
|
||||
// The returned gvar.Var is un-concurrent safe.
|
||||
func (tree *BTree) GetVarOrSetFunc(key interface{}, f func() interface{}) *gvar.Var {
|
||||
return gvar.New(tree.GetOrSetFunc(key, f), true)
|
||||
}
|
||||
|
||||
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
|
||||
// The returned gvar.Var is un-concurrent safe.
|
||||
func (tree *BTree) GetVarOrSetFuncLock(key interface{}, f func() interface{}) *gvar.Var {
|
||||
return gvar.New(tree.GetOrSetFuncLock(key, f), true)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
func (tree *BTree) SetIfNotExist(key interface{}, value interface{}) bool {
|
||||
if !tree.Contains(key) {
|
||||
tree.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
func (tree *BTree) SetIfNotExistFunc(key interface{}, f func() interface{}) bool {
|
||||
if !tree.Contains(key) {
|
||||
tree.doSetWithLockCheck(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
//
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function <f> with mutex.Lock of the hash map.
|
||||
func (tree *BTree) SetIfNotExistFuncLock(key interface{}, f func() interface{}) bool {
|
||||
if !tree.Contains(key) {
|
||||
tree.doSetWithLockCheck(key, f)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Contains checks whether <key> exists in the tree.
|
||||
func (tree *BTree) Contains(key interface{}) bool {
|
||||
_, ok := tree.Search(key)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Remove remove the node from the tree by key.
|
||||
// Key should adhere to the comparator's type assertion, otherwise method panics.
|
||||
func (tree *BTree) doRemove(key interface{}) (value interface{}) {
|
||||
node, index, found := tree.searchRecursively(tree.root, key)
|
||||
if found {
|
||||
value = node.Entries[index].Value
|
||||
tree.delete(node, index)
|
||||
tree.size--
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Remove removes the node from the tree by <key>.
|
||||
func (tree *BTree) Remove(key interface{}) (value interface{}) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
return tree.doRemove(key)
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the tree by <keys>.
|
||||
func (tree *BTree) Removes(keys []interface{}) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
for _, key := range keys {
|
||||
tree.doRemove(key)
|
||||
}
|
||||
}
|
||||
|
||||
// Empty returns true if tree does not contain any nodes
|
||||
func (tree *BTree) IsEmpty() bool {
|
||||
return tree.Size() == 0
|
||||
}
|
||||
|
||||
// Size returns number of nodes in the tree.
|
||||
func (tree *BTree) Size() int {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.size
|
||||
}
|
||||
|
||||
// Keys returns all keys in asc order.
|
||||
func (tree *BTree) Keys() []interface{} {
|
||||
keys := make([]interface{}, tree.Size())
|
||||
index := 0
|
||||
tree.IteratorAsc(func(key, value interface{}) bool {
|
||||
keys[index] = key
|
||||
index++
|
||||
return true
|
||||
})
|
||||
return keys
|
||||
}
|
||||
|
||||
// Values returns all values in asc order based on the key.
|
||||
func (tree *BTree) Values() []interface{} {
|
||||
values := make([]interface{}, tree.Size())
|
||||
index := 0
|
||||
tree.IteratorAsc(func(key, value interface{}) bool {
|
||||
values[index] = value
|
||||
index++
|
||||
return true
|
||||
})
|
||||
return values
|
||||
}
|
||||
|
||||
// Map returns all key-value items as map.
|
||||
func (tree *BTree) Map() map[interface{}]interface{} {
|
||||
m := make(map[interface{}]interface{}, tree.Size())
|
||||
tree.IteratorAsc(func(key, value interface{}) bool {
|
||||
m[key] = value
|
||||
return true
|
||||
})
|
||||
return m
|
||||
}
|
||||
|
||||
// Clear removes all nodes from the tree.
|
||||
func (tree *BTree) Clear() {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
tree.root = nil
|
||||
tree.size = 0
|
||||
}
|
||||
|
||||
// Height returns the height of the tree.
|
||||
func (tree *BTree) Height() int {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.root.height()
|
||||
}
|
||||
|
||||
// Left returns the left-most (min) entry or nil if tree is empty.
|
||||
func (tree *BTree) Left() *BTreeEntry {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node := tree.left(tree.root)
|
||||
return node.Entries[0]
|
||||
}
|
||||
|
||||
// Right returns the right-most (max) entry or nil if tree is empty.
|
||||
func (tree *BTree) Right() *BTreeEntry {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node := tree.right(tree.root)
|
||||
return node.Entries[len(node.Entries)-1]
|
||||
}
|
||||
|
||||
// String returns a string representation of container (for debugging purposes)
|
||||
func (tree *BTree) String() string {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
var buffer bytes.Buffer
|
||||
if _, err := buffer.WriteString("BTree\n"); err != nil {
|
||||
}
|
||||
if tree.size != 0 {
|
||||
tree.output(&buffer, tree.root, 0, true)
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// Search searches the tree with given <key>.
|
||||
// Second return parameter <found> is true if key was found, otherwise false.
|
||||
func (tree *BTree) Search(key interface{}) (value interface{}, found bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node, index, found := tree.searchRecursively(tree.root, key)
|
||||
if found {
|
||||
return node.Entries[index].Value, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Search searches the tree with given <key> without mutex.
|
||||
// It returns the entry if found or otherwise nil.
|
||||
func (tree *BTree) doSearch(key interface{}) *BTreeEntry {
|
||||
node, index, found := tree.searchRecursively(tree.root, key)
|
||||
if found {
|
||||
return node.Entries[index]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Print prints the tree to stdout.
|
||||
func (tree *BTree) Print() {
|
||||
fmt.Println(tree.String())
|
||||
}
|
||||
|
||||
// Iterator is alias of IteratorAsc.
|
||||
func (tree *BTree) Iterator(f func(key, value interface{}) bool) {
|
||||
tree.IteratorAsc(f)
|
||||
}
|
||||
|
||||
// IteratorAsc iterates the tree in ascending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (tree *BTree) IteratorAsc(f func(key, value interface{}) bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node := tree.left(tree.root)
|
||||
if node == nil {
|
||||
return
|
||||
}
|
||||
entry := node.Entries[0]
|
||||
loop:
|
||||
if entry == nil {
|
||||
return
|
||||
}
|
||||
if !f(entry.Key, entry.Value) {
|
||||
return
|
||||
}
|
||||
// Find current entry position in current node
|
||||
e, _ := tree.search(node, entry.Key)
|
||||
// Try to go down to the child right of the current entry
|
||||
if e+1 < len(node.Children) {
|
||||
node = node.Children[e+1]
|
||||
// Try to go down to the child left of the current node
|
||||
for len(node.Children) > 0 {
|
||||
node = node.Children[0]
|
||||
}
|
||||
// Return the left-most entry
|
||||
entry = node.Entries[0]
|
||||
goto loop
|
||||
}
|
||||
// Above assures that we have reached a leaf node, so return the next entry in current node (if any)
|
||||
if e+1 < len(node.Entries) {
|
||||
entry = node.Entries[e+1]
|
||||
goto loop
|
||||
}
|
||||
// Reached leaf node and there are no entries to the right of the current entry, so go up to the parent
|
||||
for node.Parent != nil {
|
||||
node = node.Parent
|
||||
// Find next entry position in current node (note: search returns the first equal or bigger than entry)
|
||||
e, _ := tree.search(node, entry.Key)
|
||||
// Check that there is a next entry position in current node
|
||||
if e < len(node.Entries) {
|
||||
entry = node.Entries[e]
|
||||
goto loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IteratorDesc iterates the tree in descending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (tree *BTree) IteratorDesc(f func(key, value interface{}) bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node := tree.right(tree.root)
|
||||
if node == nil {
|
||||
return
|
||||
}
|
||||
entry := node.Entries[len(node.Entries)-1]
|
||||
loop:
|
||||
if entry == nil {
|
||||
return
|
||||
}
|
||||
if !f(entry.Key, entry.Value) {
|
||||
return
|
||||
}
|
||||
// Find current entry position in current node
|
||||
e, _ := tree.search(node, entry.Key)
|
||||
// Try to go down to the child left of the current entry
|
||||
if e < len(node.Children) {
|
||||
node = node.Children[e]
|
||||
// Try to go down to the child right of the current node
|
||||
for len(node.Children) > 0 {
|
||||
node = node.Children[len(node.Children)-1]
|
||||
}
|
||||
// Return the right-most entry
|
||||
entry = node.Entries[len(node.Entries)-1]
|
||||
goto loop
|
||||
}
|
||||
// Above assures that we have reached a leaf node, so return the previous entry in current node (if any)
|
||||
if e-1 >= 0 {
|
||||
entry = node.Entries[e-1]
|
||||
goto loop
|
||||
}
|
||||
|
||||
// Reached leaf node and there are no entries to the left of the current entry, so go up to the parent
|
||||
for node.Parent != nil {
|
||||
node = node.Parent
|
||||
// Find previous entry position in current node (note: search returns the first equal or bigger than entry)
|
||||
e, _ := tree.search(node, entry.Key)
|
||||
// Check that there is a previous entry position in current node
|
||||
if e-1 >= 0 {
|
||||
entry = node.Entries[e-1]
|
||||
goto loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tree *BTree) output(buffer *bytes.Buffer, node *BTreeNode, level int, isTail bool) {
|
||||
for e := 0; e < len(node.Entries)+1; e++ {
|
||||
if e < len(node.Children) {
|
||||
tree.output(buffer, node.Children[e], level+1, true)
|
||||
}
|
||||
if e < len(node.Entries) {
|
||||
if _, err := buffer.WriteString(strings.Repeat(" ", level)); err != nil {
|
||||
}
|
||||
if _, err := buffer.WriteString(fmt.Sprintf("%v", node.Entries[e].Key) + "\n"); err != nil {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (node *BTreeNode) height() int {
|
||||
h := 0
|
||||
n := node
|
||||
for ; n != nil; n = n.Children[0] {
|
||||
h++
|
||||
if len(n.Children) == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (tree *BTree) isLeaf(node *BTreeNode) bool {
|
||||
return len(node.Children) == 0
|
||||
}
|
||||
|
||||
//func (tree *BTree) isFull(node *BTreeNode) bool {
|
||||
// return len(node.Entries) == tree.maxEntries()
|
||||
//}
|
||||
|
||||
func (tree *BTree) shouldSplit(node *BTreeNode) bool {
|
||||
return len(node.Entries) > tree.maxEntries()
|
||||
}
|
||||
|
||||
func (tree *BTree) maxChildren() int {
|
||||
return tree.m
|
||||
}
|
||||
|
||||
func (tree *BTree) minChildren() int {
|
||||
return (tree.m + 1) / 2 // ceil(m/2)
|
||||
}
|
||||
|
||||
func (tree *BTree) maxEntries() int {
|
||||
return tree.maxChildren() - 1
|
||||
}
|
||||
|
||||
func (tree *BTree) minEntries() int {
|
||||
return tree.minChildren() - 1
|
||||
}
|
||||
|
||||
func (tree *BTree) middle() int {
|
||||
// "-1" to favor right nodes to have more keys when splitting
|
||||
return (tree.m - 1) / 2
|
||||
}
|
||||
|
||||
// search searches only within the single node among its entries
|
||||
func (tree *BTree) search(node *BTreeNode, key interface{}) (index int, found bool) {
|
||||
low, mid, high := 0, 0, len(node.Entries)-1
|
||||
for low <= high {
|
||||
mid = (high + low) / 2
|
||||
compare := tree.comparator(key, node.Entries[mid].Key)
|
||||
switch {
|
||||
case compare > 0:
|
||||
low = mid + 1
|
||||
case compare < 0:
|
||||
high = mid - 1
|
||||
case compare == 0:
|
||||
return mid, true
|
||||
}
|
||||
}
|
||||
return low, false
|
||||
}
|
||||
|
||||
// searchRecursively searches recursively down the tree starting at the startNode
|
||||
func (tree *BTree) searchRecursively(startNode *BTreeNode, key interface{}) (node *BTreeNode, index int, found bool) {
|
||||
if tree.size == 0 {
|
||||
return nil, -1, false
|
||||
}
|
||||
node = startNode
|
||||
for {
|
||||
index, found = tree.search(node, key)
|
||||
if found {
|
||||
return node, index, true
|
||||
}
|
||||
if tree.isLeaf(node) {
|
||||
return nil, -1, false
|
||||
}
|
||||
node = node.Children[index]
|
||||
}
|
||||
}
|
||||
|
||||
func (tree *BTree) insert(node *BTreeNode, entry *BTreeEntry) (inserted bool) {
|
||||
if tree.isLeaf(node) {
|
||||
return tree.insertIntoLeaf(node, entry)
|
||||
}
|
||||
return tree.insertIntoInternal(node, entry)
|
||||
}
|
||||
|
||||
func (tree *BTree) insertIntoLeaf(node *BTreeNode, entry *BTreeEntry) (inserted bool) {
|
||||
insertPosition, found := tree.search(node, entry.Key)
|
||||
if found {
|
||||
node.Entries[insertPosition] = entry
|
||||
return false
|
||||
}
|
||||
// Insert entry's key in the middle of the node
|
||||
node.Entries = append(node.Entries, nil)
|
||||
copy(node.Entries[insertPosition+1:], node.Entries[insertPosition:])
|
||||
node.Entries[insertPosition] = entry
|
||||
tree.split(node)
|
||||
return true
|
||||
}
|
||||
|
||||
func (tree *BTree) insertIntoInternal(node *BTreeNode, entry *BTreeEntry) (inserted bool) {
|
||||
insertPosition, found := tree.search(node, entry.Key)
|
||||
if found {
|
||||
node.Entries[insertPosition] = entry
|
||||
return false
|
||||
}
|
||||
return tree.insert(node.Children[insertPosition], entry)
|
||||
}
|
||||
|
||||
func (tree *BTree) split(node *BTreeNode) {
|
||||
if !tree.shouldSplit(node) {
|
||||
return
|
||||
}
|
||||
|
||||
if node == tree.root {
|
||||
tree.splitRoot()
|
||||
return
|
||||
}
|
||||
|
||||
tree.splitNonRoot(node)
|
||||
}
|
||||
|
||||
func (tree *BTree) splitNonRoot(node *BTreeNode) {
|
||||
middle := tree.middle()
|
||||
parent := node.Parent
|
||||
|
||||
left := &BTreeNode{Entries: append([]*BTreeEntry(nil), node.Entries[:middle]...), Parent: parent}
|
||||
right := &BTreeNode{Entries: append([]*BTreeEntry(nil), node.Entries[middle+1:]...), Parent: parent}
|
||||
|
||||
// Move children from the node to be split into left and right nodes
|
||||
if !tree.isLeaf(node) {
|
||||
left.Children = append([]*BTreeNode(nil), node.Children[:middle+1]...)
|
||||
right.Children = append([]*BTreeNode(nil), node.Children[middle+1:]...)
|
||||
setParent(left.Children, left)
|
||||
setParent(right.Children, right)
|
||||
}
|
||||
|
||||
insertPosition, _ := tree.search(parent, node.Entries[middle].Key)
|
||||
|
||||
// Insert middle key into parent
|
||||
parent.Entries = append(parent.Entries, nil)
|
||||
copy(parent.Entries[insertPosition+1:], parent.Entries[insertPosition:])
|
||||
parent.Entries[insertPosition] = node.Entries[middle]
|
||||
|
||||
// Set child left of inserted key in parent to the created left node
|
||||
parent.Children[insertPosition] = left
|
||||
|
||||
// Set child right of inserted key in parent to the created right node
|
||||
parent.Children = append(parent.Children, nil)
|
||||
copy(parent.Children[insertPosition+2:], parent.Children[insertPosition+1:])
|
||||
parent.Children[insertPosition+1] = right
|
||||
|
||||
tree.split(parent)
|
||||
}
|
||||
|
||||
func (tree *BTree) splitRoot() {
|
||||
middle := tree.middle()
|
||||
left := &BTreeNode{Entries: append([]*BTreeEntry(nil), tree.root.Entries[:middle]...)}
|
||||
right := &BTreeNode{Entries: append([]*BTreeEntry(nil), tree.root.Entries[middle+1:]...)}
|
||||
|
||||
// Move children from the node to be split into left and right nodes
|
||||
if !tree.isLeaf(tree.root) {
|
||||
left.Children = append([]*BTreeNode(nil), tree.root.Children[:middle+1]...)
|
||||
right.Children = append([]*BTreeNode(nil), tree.root.Children[middle+1:]...)
|
||||
setParent(left.Children, left)
|
||||
setParent(right.Children, right)
|
||||
}
|
||||
|
||||
// Root is a node with one entry and two children (left and right)
|
||||
newRoot := &BTreeNode{
|
||||
Entries: []*BTreeEntry{tree.root.Entries[middle]},
|
||||
Children: []*BTreeNode{left, right},
|
||||
}
|
||||
|
||||
left.Parent = newRoot
|
||||
right.Parent = newRoot
|
||||
tree.root = newRoot
|
||||
}
|
||||
|
||||
func setParent(nodes []*BTreeNode, parent *BTreeNode) {
|
||||
for _, node := range nodes {
|
||||
node.Parent = parent
|
||||
}
|
||||
}
|
||||
|
||||
func (tree *BTree) left(node *BTreeNode) *BTreeNode {
|
||||
if tree.size == 0 {
|
||||
return nil
|
||||
}
|
||||
current := node
|
||||
for {
|
||||
if tree.isLeaf(current) {
|
||||
return current
|
||||
}
|
||||
current = current.Children[0]
|
||||
}
|
||||
}
|
||||
|
||||
func (tree *BTree) right(node *BTreeNode) *BTreeNode {
|
||||
if tree.size == 0 {
|
||||
return nil
|
||||
}
|
||||
current := node
|
||||
for {
|
||||
if tree.isLeaf(current) {
|
||||
return current
|
||||
}
|
||||
current = current.Children[len(current.Children)-1]
|
||||
}
|
||||
}
|
||||
|
||||
// leftSibling returns the node's left sibling and child index (in parent) if it exists, otherwise (nil,-1)
|
||||
// key is any of keys in node (could even be deleted).
|
||||
func (tree *BTree) leftSibling(node *BTreeNode, key interface{}) (*BTreeNode, int) {
|
||||
if node.Parent != nil {
|
||||
index, _ := tree.search(node.Parent, key)
|
||||
index--
|
||||
if index >= 0 && index < len(node.Parent.Children) {
|
||||
return node.Parent.Children[index], index
|
||||
}
|
||||
}
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
// rightSibling returns the node's right sibling and child index (in parent) if it exists, otherwise (nil,-1)
|
||||
// key is any of keys in node (could even be deleted).
|
||||
func (tree *BTree) rightSibling(node *BTreeNode, key interface{}) (*BTreeNode, int) {
|
||||
if node.Parent != nil {
|
||||
index, _ := tree.search(node.Parent, key)
|
||||
index++
|
||||
if index < len(node.Parent.Children) {
|
||||
return node.Parent.Children[index], index
|
||||
}
|
||||
}
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
// delete deletes an entry in node at entries' index
|
||||
// ref.: https://en.wikipedia.org/wiki/B-tree#Deletion
|
||||
func (tree *BTree) delete(node *BTreeNode, index int) {
|
||||
// deleting from a leaf node
|
||||
if tree.isLeaf(node) {
|
||||
deletedKey := node.Entries[index].Key
|
||||
tree.deleteEntry(node, index)
|
||||
tree.rebalance(node, deletedKey)
|
||||
if len(tree.root.Entries) == 0 {
|
||||
tree.root = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// deleting from an internal node
|
||||
leftLargestNode := tree.right(node.Children[index]) // largest node in the left sub-tree (assumed to exist)
|
||||
leftLargestEntryIndex := len(leftLargestNode.Entries) - 1
|
||||
node.Entries[index] = leftLargestNode.Entries[leftLargestEntryIndex]
|
||||
deletedKey := leftLargestNode.Entries[leftLargestEntryIndex].Key
|
||||
tree.deleteEntry(leftLargestNode, leftLargestEntryIndex)
|
||||
tree.rebalance(leftLargestNode, deletedKey)
|
||||
}
|
||||
|
||||
// rebalance rebalances the tree after deletion if necessary and returns true, otherwise false.
|
||||
// Note that we first delete the entry and then call rebalance, thus the passed deleted key as reference.
|
||||
func (tree *BTree) rebalance(node *BTreeNode, deletedKey interface{}) {
|
||||
// check if rebalancing is needed
|
||||
if node == nil || len(node.Entries) >= tree.minEntries() {
|
||||
return
|
||||
}
|
||||
|
||||
// try to borrow from left sibling
|
||||
leftSibling, leftSiblingIndex := tree.leftSibling(node, deletedKey)
|
||||
if leftSibling != nil && len(leftSibling.Entries) > tree.minEntries() {
|
||||
// rotate right
|
||||
node.Entries = append([]*BTreeEntry{node.Parent.Entries[leftSiblingIndex]}, node.Entries...) // prepend parent's separator entry to node's entries
|
||||
node.Parent.Entries[leftSiblingIndex] = leftSibling.Entries[len(leftSibling.Entries)-1]
|
||||
tree.deleteEntry(leftSibling, len(leftSibling.Entries)-1)
|
||||
if !tree.isLeaf(leftSibling) {
|
||||
leftSiblingRightMostChild := leftSibling.Children[len(leftSibling.Children)-1]
|
||||
leftSiblingRightMostChild.Parent = node
|
||||
node.Children = append([]*BTreeNode{leftSiblingRightMostChild}, node.Children...)
|
||||
tree.deleteChild(leftSibling, len(leftSibling.Children)-1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// try to borrow from right sibling
|
||||
rightSibling, rightSiblingIndex := tree.rightSibling(node, deletedKey)
|
||||
if rightSibling != nil && len(rightSibling.Entries) > tree.minEntries() {
|
||||
// rotate left
|
||||
node.Entries = append(node.Entries, node.Parent.Entries[rightSiblingIndex-1]) // append parent's separator entry to node's entries
|
||||
node.Parent.Entries[rightSiblingIndex-1] = rightSibling.Entries[0]
|
||||
tree.deleteEntry(rightSibling, 0)
|
||||
if !tree.isLeaf(rightSibling) {
|
||||
rightSiblingLeftMostChild := rightSibling.Children[0]
|
||||
rightSiblingLeftMostChild.Parent = node
|
||||
node.Children = append(node.Children, rightSiblingLeftMostChild)
|
||||
tree.deleteChild(rightSibling, 0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// merge with siblings
|
||||
if rightSibling != nil {
|
||||
// merge with right sibling
|
||||
node.Entries = append(node.Entries, node.Parent.Entries[rightSiblingIndex-1])
|
||||
node.Entries = append(node.Entries, rightSibling.Entries...)
|
||||
deletedKey = node.Parent.Entries[rightSiblingIndex-1].Key
|
||||
tree.deleteEntry(node.Parent, rightSiblingIndex-1)
|
||||
tree.appendChildren(node.Parent.Children[rightSiblingIndex], node)
|
||||
tree.deleteChild(node.Parent, rightSiblingIndex)
|
||||
} else if leftSibling != nil {
|
||||
// merge with left sibling
|
||||
entries := append([]*BTreeEntry(nil), leftSibling.Entries...)
|
||||
entries = append(entries, node.Parent.Entries[leftSiblingIndex])
|
||||
node.Entries = append(entries, node.Entries...)
|
||||
deletedKey = node.Parent.Entries[leftSiblingIndex].Key
|
||||
tree.deleteEntry(node.Parent, leftSiblingIndex)
|
||||
tree.prependChildren(node.Parent.Children[leftSiblingIndex], node)
|
||||
tree.deleteChild(node.Parent, leftSiblingIndex)
|
||||
}
|
||||
|
||||
// make the merged node the root if its parent was the root and the root is empty
|
||||
if node.Parent == tree.root && len(tree.root.Entries) == 0 {
|
||||
tree.root = node
|
||||
node.Parent = nil
|
||||
return
|
||||
}
|
||||
|
||||
// parent might underflow, so try to rebalance if necessary
|
||||
tree.rebalance(node.Parent, deletedKey)
|
||||
}
|
||||
|
||||
func (tree *BTree) prependChildren(fromNode *BTreeNode, toNode *BTreeNode) {
|
||||
children := append([]*BTreeNode(nil), fromNode.Children...)
|
||||
toNode.Children = append(children, toNode.Children...)
|
||||
setParent(fromNode.Children, toNode)
|
||||
}
|
||||
|
||||
func (tree *BTree) appendChildren(fromNode *BTreeNode, toNode *BTreeNode) {
|
||||
toNode.Children = append(toNode.Children, fromNode.Children...)
|
||||
setParent(fromNode.Children, toNode)
|
||||
}
|
||||
|
||||
func (tree *BTree) deleteEntry(node *BTreeNode, index int) {
|
||||
copy(node.Entries[index:], node.Entries[index+1:])
|
||||
node.Entries[len(node.Entries)-1] = nil
|
||||
node.Entries = node.Entries[:len(node.Entries)-1]
|
||||
}
|
||||
|
||||
func (tree *BTree) deleteChild(node *BTreeNode, index int) {
|
||||
if index >= len(node.Children) {
|
||||
return
|
||||
}
|
||||
copy(node.Children[index:], node.Children[index+1:])
|
||||
node.Children[len(node.Children)-1] = nil
|
||||
node.Children = node.Children[:len(node.Children)-1]
|
||||
}
|
||||
835
g/container/gtree/gtree_redblacktree.go
Normal file
835
g/container/gtree/gtree_redblacktree.go
Normal file
@ -0,0 +1,835 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gtree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/container/gvar"
|
||||
"github.com/gogf/gf/g/internal/rwmutex"
|
||||
)
|
||||
|
||||
type color bool
|
||||
|
||||
const (
|
||||
black, red color = true, false
|
||||
)
|
||||
|
||||
// RedBlackTree holds elements of the red-black tree.
|
||||
type RedBlackTree struct {
|
||||
mu *rwmutex.RWMutex
|
||||
root *RedBlackTreeNode
|
||||
size int
|
||||
comparator func(v1, v2 interface{}) int
|
||||
}
|
||||
|
||||
// RedBlackTreeNode is a single element within the tree.
|
||||
type RedBlackTreeNode struct {
|
||||
Key interface{}
|
||||
Value interface{}
|
||||
color color
|
||||
left *RedBlackTreeNode
|
||||
right *RedBlackTreeNode
|
||||
parent *RedBlackTreeNode
|
||||
}
|
||||
|
||||
// NewRedBlackTree instantiates a red-black tree with the custom key comparator.
|
||||
// The parameter <unsafe> used to specify whether using tree in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewRedBlackTree(comparator func(v1, v2 interface{}) int, unsafe ...bool) *RedBlackTree {
|
||||
return &RedBlackTree{
|
||||
mu: rwmutex.New(unsafe...),
|
||||
comparator: comparator,
|
||||
}
|
||||
}
|
||||
|
||||
// NewRedBlackTreeFrom instantiates a red-black tree with the custom key comparator and <data> map.
|
||||
// The parameter <unsafe> used to specify whether using tree in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewRedBlackTreeFrom(comparator func(v1, v2 interface{}) int, data map[interface{}]interface{}, unsafe ...bool) *RedBlackTree {
|
||||
tree := NewRedBlackTree(comparator, unsafe...)
|
||||
for k, v := range data {
|
||||
tree.doSet(k, v)
|
||||
}
|
||||
return tree
|
||||
}
|
||||
|
||||
// Clone returns a new tree with a copy of current tree.
|
||||
func (tree *RedBlackTree) Clone(unsafe ...bool) *RedBlackTree {
|
||||
newTree := NewRedBlackTree(tree.comparator, !tree.mu.IsSafe())
|
||||
newTree.Sets(tree.Map())
|
||||
return newTree
|
||||
}
|
||||
|
||||
// Set inserts key-value item into the tree.
|
||||
func (tree *RedBlackTree) Set(key interface{}, value interface{}) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
tree.doSet(key, value)
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the tree.
|
||||
func (tree *RedBlackTree) Sets(data map[interface{}]interface{}) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
for k, v := range data {
|
||||
tree.doSet(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// doSet inserts key-value item into the tree without mutex.
|
||||
func (tree *RedBlackTree) doSet(key interface{}, value interface{}) {
|
||||
insertedNode := (*RedBlackTreeNode)(nil)
|
||||
if tree.root == nil {
|
||||
// Assert key is of comparator's type for initial tree
|
||||
tree.comparator(key, key)
|
||||
tree.root = &RedBlackTreeNode{Key: key, Value: value, color: red}
|
||||
insertedNode = tree.root
|
||||
} else {
|
||||
node := tree.root
|
||||
loop := true
|
||||
for loop {
|
||||
compare := tree.comparator(key, node.Key)
|
||||
switch {
|
||||
case compare == 0:
|
||||
//node.Key = key
|
||||
node.Value = value
|
||||
return
|
||||
case compare < 0:
|
||||
if node.left == nil {
|
||||
node.left = &RedBlackTreeNode{Key: key, Value: value, color: red}
|
||||
insertedNode = node.left
|
||||
loop = false
|
||||
} else {
|
||||
node = node.left
|
||||
}
|
||||
case compare > 0:
|
||||
if node.right == nil {
|
||||
node.right = &RedBlackTreeNode{Key: key, Value: value, color: red}
|
||||
insertedNode = node.right
|
||||
loop = false
|
||||
} else {
|
||||
node = node.right
|
||||
}
|
||||
}
|
||||
}
|
||||
insertedNode.parent = node
|
||||
}
|
||||
tree.insertCase1(insertedNode)
|
||||
tree.size++
|
||||
}
|
||||
|
||||
// Get searches the node in the tree by <key> and returns its value or nil if key is not found in tree.
|
||||
func (tree *RedBlackTree) Get(key interface{}) (value interface{}) {
|
||||
value, _ = tree.Search(key)
|
||||
return
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given <key>,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// When setting value, if <value> is type of <func() interface {}>,
|
||||
// it will be executed with mutex.Lock of the hash map,
|
||||
// and its return value will be set to the map with <key>.
|
||||
//
|
||||
// It returns value with given <key>.
|
||||
func (tree *RedBlackTree) doSetWithLockCheck(key interface{}, value interface{}) interface{} {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if node := tree.doSearch(key); node != nil {
|
||||
return node.Value
|
||||
}
|
||||
if f, ok := value.(func() interface{}); ok {
|
||||
value = f()
|
||||
}
|
||||
tree.doSet(key, value)
|
||||
return value
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or set value with given <value> if not exist and returns this value.
|
||||
func (tree *RedBlackTree) GetOrSet(key interface{}, value interface{}) interface{} {
|
||||
if v, ok := tree.Search(key); !ok {
|
||||
return tree.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with return value of callback function <f> if not exist
|
||||
// and returns this value.
|
||||
func (tree *RedBlackTree) GetOrSetFunc(key interface{}, f func() interface{}) interface{} {
|
||||
if v, ok := tree.Search(key); !ok {
|
||||
return tree.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
// or sets value with return value of callback function <f> if not exist
|
||||
// and returns this value.
|
||||
//
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
|
||||
// with mutex.Lock of the hash map.
|
||||
func (tree *RedBlackTree) GetOrSetFuncLock(key interface{}, f func() interface{}) interface{} {
|
||||
if v, ok := tree.Search(key); !ok {
|
||||
return tree.doSetWithLockCheck(key, f)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetVar returns a gvar.Var with the value by given <key>.
|
||||
// The returned gvar.Var is un-concurrent safe.
|
||||
func (tree *RedBlackTree) GetVar(key interface{}) *gvar.Var {
|
||||
return gvar.New(tree.Get(key), true)
|
||||
}
|
||||
|
||||
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
|
||||
// The returned gvar.Var is un-concurrent safe.
|
||||
func (tree *RedBlackTree) GetVarOrSet(key interface{}, value interface{}) *gvar.Var {
|
||||
return gvar.New(tree.GetOrSet(key, value), true)
|
||||
}
|
||||
|
||||
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
|
||||
// The returned gvar.Var is un-concurrent safe.
|
||||
func (tree *RedBlackTree) GetVarOrSetFunc(key interface{}, f func() interface{}) *gvar.Var {
|
||||
return gvar.New(tree.GetOrSetFunc(key, f), true)
|
||||
}
|
||||
|
||||
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
|
||||
// The returned gvar.Var is un-concurrent safe.
|
||||
func (tree *RedBlackTree) GetVarOrSetFuncLock(key interface{}, f func() interface{}) *gvar.Var {
|
||||
return gvar.New(tree.GetOrSetFuncLock(key, f), true)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
func (tree *RedBlackTree) SetIfNotExist(key interface{}, value interface{}) bool {
|
||||
if !tree.Contains(key) {
|
||||
tree.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
func (tree *RedBlackTree) SetIfNotExistFunc(key interface{}, f func() interface{}) bool {
|
||||
if !tree.Contains(key) {
|
||||
tree.doSetWithLockCheck(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
|
||||
// It returns false if <key> exists, and <value> would be ignored.
|
||||
//
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function <f> with mutex.Lock of the hash map.
|
||||
func (tree *RedBlackTree) SetIfNotExistFuncLock(key interface{}, f func() interface{}) bool {
|
||||
if !tree.Contains(key) {
|
||||
tree.doSetWithLockCheck(key, f)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Contains checks whether <key> exists in the tree.
|
||||
func (tree *RedBlackTree) Contains(key interface{}) bool {
|
||||
_, ok := tree.Search(key)
|
||||
return ok
|
||||
}
|
||||
|
||||
// doRemove removes the node from the tree by <key> without mutex.
|
||||
func (tree *RedBlackTree) doRemove(key interface{}) (value interface{}) {
|
||||
child := (*RedBlackTreeNode)(nil)
|
||||
node := tree.doSearch(key)
|
||||
if node == nil {
|
||||
return
|
||||
}
|
||||
value = node.Value
|
||||
if node.left != nil && node.right != nil {
|
||||
p := node.left.maximumNode()
|
||||
node.Key = p.Key
|
||||
node.Value = p.Value
|
||||
node = p
|
||||
}
|
||||
if node.left == nil || node.right == nil {
|
||||
if node.right == nil {
|
||||
child = node.left
|
||||
} else {
|
||||
child = node.right
|
||||
}
|
||||
if node.color == black {
|
||||
node.color = tree.nodeColor(child)
|
||||
tree.deleteCase1(node)
|
||||
}
|
||||
tree.replaceNode(node, child)
|
||||
if node.parent == nil && child != nil {
|
||||
child.color = black
|
||||
}
|
||||
}
|
||||
tree.size--
|
||||
return
|
||||
}
|
||||
|
||||
// Remove removes the node from the tree by <key>.
|
||||
func (tree *RedBlackTree) Remove(key interface{}) (value interface{}) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
return tree.doRemove(key)
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the tree by <keys>.
|
||||
func (tree *RedBlackTree) Removes(keys []interface{}) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
for _, key := range keys {
|
||||
tree.doRemove(key)
|
||||
}
|
||||
}
|
||||
|
||||
// IsEmpty returns true if tree does not contain any nodes.
|
||||
func (tree *RedBlackTree) IsEmpty() bool {
|
||||
return tree.Size() == 0
|
||||
}
|
||||
|
||||
// Size returns number of nodes in the tree.
|
||||
func (tree *RedBlackTree) Size() int {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.size
|
||||
}
|
||||
|
||||
// Keys returns all keys in asc order.
|
||||
func (tree *RedBlackTree) Keys() []interface{} {
|
||||
keys := make([]interface{}, tree.Size())
|
||||
index := 0
|
||||
tree.IteratorAsc(func(key, value interface{}) bool {
|
||||
keys[index] = key
|
||||
index++
|
||||
return true
|
||||
})
|
||||
return keys
|
||||
}
|
||||
|
||||
// Values returns all values in asc order based on the key.
|
||||
func (tree *RedBlackTree) Values() []interface{} {
|
||||
values := make([]interface{}, tree.Size())
|
||||
index := 0
|
||||
tree.IteratorAsc(func(key, value interface{}) bool {
|
||||
values[index] = value
|
||||
index++
|
||||
return true
|
||||
})
|
||||
return values
|
||||
}
|
||||
|
||||
// Map returns all key-value items as map.
|
||||
func (tree *RedBlackTree) Map() map[interface{}]interface{} {
|
||||
m := make(map[interface{}]interface{}, tree.Size())
|
||||
tree.IteratorAsc(func(key, value interface{}) bool {
|
||||
m[key] = value
|
||||
return true
|
||||
})
|
||||
return m
|
||||
}
|
||||
|
||||
// Left returns the left-most (min) node or nil if tree is empty.
|
||||
func (tree *RedBlackTree) Left() *RedBlackTreeNode {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node := tree.leftNode()
|
||||
if tree.mu.IsSafe() {
|
||||
return &RedBlackTreeNode{
|
||||
Key: node.Key,
|
||||
Value: node.Value,
|
||||
}
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// Right returns the right-most (max) node or nil if tree is empty.
|
||||
func (tree *RedBlackTree) Right() *RedBlackTreeNode {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node := tree.rightNode()
|
||||
if tree.mu.IsSafe() {
|
||||
return &RedBlackTreeNode{
|
||||
Key: node.Key,
|
||||
Value: node.Value,
|
||||
}
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// leftNode returns the left-most (min) node or nil if tree is empty.
|
||||
func (tree *RedBlackTree) leftNode() *RedBlackTreeNode {
|
||||
p := (*RedBlackTreeNode)(nil)
|
||||
n := tree.root
|
||||
for n != nil {
|
||||
p = n
|
||||
n = n.left
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// rightNode returns the right-most (max) node or nil if tree is empty.
|
||||
func (tree *RedBlackTree) rightNode() *RedBlackTreeNode {
|
||||
p := (*RedBlackTreeNode)(nil)
|
||||
n := tree.root
|
||||
for n != nil {
|
||||
p = n
|
||||
n = n.right
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// Floor Finds floor node of the input key, return the floor node or nil if no floor node is found.
|
||||
// Second return parameter is true if floor was found, otherwise false.
|
||||
//
|
||||
// Floor node is defined as the largest node that its key is smaller than or equal to the given <key>.
|
||||
// A floor node may not be found, either because the tree is empty, or because
|
||||
// all nodes in the tree are larger than the given node.
|
||||
func (tree *RedBlackTree) Floor(key interface{}) (floor *RedBlackTreeNode, found bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
n := tree.root
|
||||
for n != nil {
|
||||
compare := tree.comparator(key, n.Key)
|
||||
switch {
|
||||
case compare == 0:
|
||||
return n, true
|
||||
case compare < 0:
|
||||
n = n.left
|
||||
case compare > 0:
|
||||
floor, found = n, true
|
||||
n = n.right
|
||||
}
|
||||
}
|
||||
if found {
|
||||
return
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Ceiling finds ceiling node of the input key, return the ceiling node or nil if no ceiling node is found.
|
||||
// Second return parameter is true if ceiling was found, otherwise false.
|
||||
//
|
||||
// Ceiling node is defined as the smallest node that its key is larger than or equal to the given <key>.
|
||||
// A ceiling node may not be found, either because the tree is empty, or because
|
||||
// all nodes in the tree are smaller than the given node.
|
||||
func (tree *RedBlackTree) Ceiling(key interface{}) (ceiling *RedBlackTreeNode, found bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
n := tree.root
|
||||
for n != nil {
|
||||
compare := tree.comparator(key, n.Key)
|
||||
switch {
|
||||
case compare == 0:
|
||||
return n, true
|
||||
case compare > 0:
|
||||
n = n.right
|
||||
case compare < 0:
|
||||
ceiling, found = n, true
|
||||
n = n.left
|
||||
}
|
||||
}
|
||||
if found {
|
||||
return
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Iterator is alias of IteratorAsc.
|
||||
func (tree *RedBlackTree) Iterator(f func(key, value interface{}) bool) {
|
||||
tree.IteratorAsc(f)
|
||||
}
|
||||
|
||||
// IteratorAsc iterates the tree in ascending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (tree *RedBlackTree) IteratorAsc(f func(key, value interface{}) bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node := tree.leftNode()
|
||||
if node == nil {
|
||||
return
|
||||
}
|
||||
loop:
|
||||
if node == nil {
|
||||
return
|
||||
}
|
||||
if !f(node.Key, node.Value) {
|
||||
return
|
||||
}
|
||||
if node.right != nil {
|
||||
node = node.right
|
||||
for node.left != nil {
|
||||
node = node.left
|
||||
}
|
||||
goto loop
|
||||
}
|
||||
if node.parent != nil {
|
||||
old := node
|
||||
for node.parent != nil {
|
||||
node = node.parent
|
||||
if tree.comparator(old.Key, node.Key) <= 0 {
|
||||
goto loop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IteratorDesc iterates the tree in descending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
func (tree *RedBlackTree) IteratorDesc(f func(key, value interface{}) bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node := tree.rightNode()
|
||||
if node == nil {
|
||||
return
|
||||
}
|
||||
loop:
|
||||
if node == nil {
|
||||
return
|
||||
}
|
||||
if !f(node.Key, node.Value) {
|
||||
return
|
||||
}
|
||||
if node.left != nil {
|
||||
node = node.left
|
||||
for node.right != nil {
|
||||
node = node.right
|
||||
}
|
||||
goto loop
|
||||
}
|
||||
if node.parent != nil {
|
||||
old := node
|
||||
for node.parent != nil {
|
||||
node = node.parent
|
||||
if tree.comparator(old.Key, node.Key) >= 0 {
|
||||
goto loop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear removes all nodes from the tree.
|
||||
func (tree *RedBlackTree) Clear() {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
tree.root = nil
|
||||
tree.size = 0
|
||||
}
|
||||
|
||||
// String returns a string representation of container.
|
||||
func (tree *RedBlackTree) String() string {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
str := "RedBlackTree\n"
|
||||
if tree.size != 0 {
|
||||
tree.output(tree.root, "", true, &str)
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// Print prints the tree to stdout.
|
||||
func (tree *RedBlackTree) Print() {
|
||||
fmt.Println(tree.String())
|
||||
}
|
||||
|
||||
// Search searches the tree with given <key>.
|
||||
// Second return parameter <found> is true if key was found, otherwise false.
|
||||
func (tree *RedBlackTree) Search(key interface{}) (value interface{}, found bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node := tree.doSearch(key)
|
||||
if node != nil {
|
||||
return node.Value, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the tree to value-key.
|
||||
// Note that you should guarantee the value is the same type as key,
|
||||
// or else the comparator would panic.
|
||||
//
|
||||
// If the type of value is different with key, you pass the new <comparator>.
|
||||
func (tree *RedBlackTree) Flip(comparator ...func(v1, v2 interface{}) int) {
|
||||
t := (*RedBlackTree)(nil)
|
||||
if len(comparator) > 0 {
|
||||
t = NewRedBlackTree(comparator[0], !tree.mu.IsSafe())
|
||||
} else {
|
||||
t = NewRedBlackTree(tree.comparator, !tree.mu.IsSafe())
|
||||
}
|
||||
tree.IteratorAsc(func(key, value interface{}) bool {
|
||||
t.doSet(value, key)
|
||||
return true
|
||||
})
|
||||
tree.mu.Lock()
|
||||
tree.root = t.root
|
||||
tree.size = t.size
|
||||
tree.mu.Unlock()
|
||||
}
|
||||
|
||||
func (tree *RedBlackTree) output(node *RedBlackTreeNode, prefix string, isTail bool, str *string) {
|
||||
if node.right != nil {
|
||||
newPrefix := prefix
|
||||
if isTail {
|
||||
newPrefix += "│ "
|
||||
} else {
|
||||
newPrefix += " "
|
||||
}
|
||||
tree.output(node.right, newPrefix, false, str)
|
||||
}
|
||||
*str += prefix
|
||||
if isTail {
|
||||
*str += "└── "
|
||||
} else {
|
||||
*str += "┌── "
|
||||
}
|
||||
*str += fmt.Sprintf("%v\n", node.Key)
|
||||
if node.left != nil {
|
||||
newPrefix := prefix
|
||||
if isTail {
|
||||
newPrefix += " "
|
||||
} else {
|
||||
newPrefix += "│ "
|
||||
}
|
||||
tree.output(node.left, newPrefix, true, str)
|
||||
}
|
||||
}
|
||||
|
||||
// doSearch searches the tree with given <key> without mutex.
|
||||
// It returns the node if found or otherwise nil.
|
||||
func (tree *RedBlackTree) doSearch(key interface{}) *RedBlackTreeNode {
|
||||
node := tree.root
|
||||
for node != nil {
|
||||
compare := tree.comparator(key, node.Key)
|
||||
switch {
|
||||
case compare == 0:
|
||||
return node
|
||||
case compare < 0:
|
||||
node = node.left
|
||||
case compare > 0:
|
||||
node = node.right
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (node *RedBlackTreeNode) grandparent() *RedBlackTreeNode {
|
||||
if node != nil && node.parent != nil {
|
||||
return node.parent.parent
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (node *RedBlackTreeNode) uncle() *RedBlackTreeNode {
|
||||
if node == nil || node.parent == nil || node.parent.parent == nil {
|
||||
return nil
|
||||
}
|
||||
return node.parent.sibling()
|
||||
}
|
||||
|
||||
func (node *RedBlackTreeNode) sibling() *RedBlackTreeNode {
|
||||
if node == nil || node.parent == nil {
|
||||
return nil
|
||||
}
|
||||
if node == node.parent.left {
|
||||
return node.parent.right
|
||||
}
|
||||
return node.parent.left
|
||||
}
|
||||
|
||||
func (tree *RedBlackTree) rotateLeft(node *RedBlackTreeNode) {
|
||||
right := node.right
|
||||
tree.replaceNode(node, right)
|
||||
node.right = right.left
|
||||
if right.left != nil {
|
||||
right.left.parent = node
|
||||
}
|
||||
right.left = node
|
||||
node.parent = right
|
||||
}
|
||||
|
||||
func (tree *RedBlackTree) rotateRight(node *RedBlackTreeNode) {
|
||||
left := node.left
|
||||
tree.replaceNode(node, left)
|
||||
node.left = left.right
|
||||
if left.right != nil {
|
||||
left.right.parent = node
|
||||
}
|
||||
left.right = node
|
||||
node.parent = left
|
||||
}
|
||||
|
||||
func (tree *RedBlackTree) replaceNode(old *RedBlackTreeNode, new *RedBlackTreeNode) {
|
||||
if old.parent == nil {
|
||||
tree.root = new
|
||||
} else {
|
||||
if old == old.parent.left {
|
||||
old.parent.left = new
|
||||
} else {
|
||||
old.parent.right = new
|
||||
}
|
||||
}
|
||||
if new != nil {
|
||||
new.parent = old.parent
|
||||
}
|
||||
}
|
||||
|
||||
func (tree *RedBlackTree) insertCase1(node *RedBlackTreeNode) {
|
||||
if node.parent == nil {
|
||||
node.color = black
|
||||
} else {
|
||||
tree.insertCase2(node)
|
||||
}
|
||||
}
|
||||
|
||||
func (tree *RedBlackTree) insertCase2(node *RedBlackTreeNode) {
|
||||
if tree.nodeColor(node.parent) == black {
|
||||
return
|
||||
}
|
||||
tree.insertCase3(node)
|
||||
}
|
||||
|
||||
func (tree *RedBlackTree) insertCase3(node *RedBlackTreeNode) {
|
||||
uncle := node.uncle()
|
||||
if tree.nodeColor(uncle) == red {
|
||||
node.parent.color = black
|
||||
uncle.color = black
|
||||
node.grandparent().color = red
|
||||
tree.insertCase1(node.grandparent())
|
||||
} else {
|
||||
tree.insertCase4(node)
|
||||
}
|
||||
}
|
||||
|
||||
func (tree *RedBlackTree) insertCase4(node *RedBlackTreeNode) {
|
||||
grandparent := node.grandparent()
|
||||
if node == node.parent.right && node.parent == grandparent.left {
|
||||
tree.rotateLeft(node.parent)
|
||||
node = node.left
|
||||
} else if node == node.parent.left && node.parent == grandparent.right {
|
||||
tree.rotateRight(node.parent)
|
||||
node = node.right
|
||||
}
|
||||
tree.insertCase5(node)
|
||||
}
|
||||
|
||||
func (tree *RedBlackTree) insertCase5(node *RedBlackTreeNode) {
|
||||
node.parent.color = black
|
||||
grandparent := node.grandparent()
|
||||
grandparent.color = red
|
||||
if node == node.parent.left && node.parent == grandparent.left {
|
||||
tree.rotateRight(grandparent)
|
||||
} else if node == node.parent.right && node.parent == grandparent.right {
|
||||
tree.rotateLeft(grandparent)
|
||||
}
|
||||
}
|
||||
|
||||
func (node *RedBlackTreeNode) maximumNode() *RedBlackTreeNode {
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
for node.right != nil {
|
||||
return node.right
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
func (tree *RedBlackTree) deleteCase1(node *RedBlackTreeNode) {
|
||||
if node.parent == nil {
|
||||
return
|
||||
}
|
||||
tree.deleteCase2(node)
|
||||
}
|
||||
|
||||
func (tree *RedBlackTree) deleteCase2(node *RedBlackTreeNode) {
|
||||
sibling := node.sibling()
|
||||
if tree.nodeColor(sibling) == red {
|
||||
node.parent.color = red
|
||||
sibling.color = black
|
||||
if node == node.parent.left {
|
||||
tree.rotateLeft(node.parent)
|
||||
} else {
|
||||
tree.rotateRight(node.parent)
|
||||
}
|
||||
}
|
||||
tree.deleteCase3(node)
|
||||
}
|
||||
|
||||
func (tree *RedBlackTree) deleteCase3(node *RedBlackTreeNode) {
|
||||
sibling := node.sibling()
|
||||
if tree.nodeColor(node.parent) == black &&
|
||||
tree.nodeColor(sibling) == black &&
|
||||
tree.nodeColor(sibling.left) == black &&
|
||||
tree.nodeColor(sibling.right) == black {
|
||||
sibling.color = red
|
||||
tree.deleteCase1(node.parent)
|
||||
} else {
|
||||
tree.deleteCase4(node)
|
||||
}
|
||||
}
|
||||
|
||||
func (tree *RedBlackTree) deleteCase4(node *RedBlackTreeNode) {
|
||||
sibling := node.sibling()
|
||||
if tree.nodeColor(node.parent) == red &&
|
||||
tree.nodeColor(sibling) == black &&
|
||||
tree.nodeColor(sibling.left) == black &&
|
||||
tree.nodeColor(sibling.right) == black {
|
||||
sibling.color = red
|
||||
node.parent.color = black
|
||||
} else {
|
||||
tree.deleteCase5(node)
|
||||
}
|
||||
}
|
||||
|
||||
func (tree *RedBlackTree) deleteCase5(node *RedBlackTreeNode) {
|
||||
sibling := node.sibling()
|
||||
if node == node.parent.left &&
|
||||
tree.nodeColor(sibling) == black &&
|
||||
tree.nodeColor(sibling.left) == red &&
|
||||
tree.nodeColor(sibling.right) == black {
|
||||
sibling.color = red
|
||||
sibling.left.color = black
|
||||
tree.rotateRight(sibling)
|
||||
} else if node == node.parent.right &&
|
||||
tree.nodeColor(sibling) == black &&
|
||||
tree.nodeColor(sibling.right) == red &&
|
||||
tree.nodeColor(sibling.left) == black {
|
||||
sibling.color = red
|
||||
sibling.right.color = black
|
||||
tree.rotateLeft(sibling)
|
||||
}
|
||||
tree.deleteCase6(node)
|
||||
}
|
||||
|
||||
func (tree *RedBlackTree) deleteCase6(node *RedBlackTreeNode) {
|
||||
sibling := node.sibling()
|
||||
sibling.color = tree.nodeColor(node.parent)
|
||||
node.parent.color = black
|
||||
if node == node.parent.left && tree.nodeColor(sibling.right) == red {
|
||||
sibling.right.color = black
|
||||
tree.rotateLeft(node.parent)
|
||||
} else if tree.nodeColor(sibling.left) == red {
|
||||
sibling.left.color = black
|
||||
tree.rotateRight(node.parent)
|
||||
}
|
||||
}
|
||||
|
||||
func (tree *RedBlackTree) nodeColor(node *RedBlackTreeNode) color {
|
||||
if node == nil {
|
||||
return black
|
||||
}
|
||||
return node.color
|
||||
}
|
||||
250
g/container/gtree/gtree_z_avl_tree_test.go
Normal file
250
g/container/gtree/gtree_z_avl_tree_test.go
Normal file
@ -0,0 +1,250 @@
|
||||
// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with gm file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gtree_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/g/container/gtree"
|
||||
"github.com/gogf/gf/g/container/gvar"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"github.com/gogf/gf/g/util/gutil"
|
||||
)
|
||||
|
||||
func Test_AVLTree_Basic(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
m := gtree.NewAVLTree(gutil.ComparatorString)
|
||||
m.Set("key1", "val1")
|
||||
gtest.Assert(m.Keys(), []interface{}{"key1"})
|
||||
|
||||
gtest.Assert(m.Get("key1"), "val1")
|
||||
gtest.Assert(m.Size(), 1)
|
||||
gtest.Assert(m.IsEmpty(), false)
|
||||
|
||||
gtest.Assert(m.GetOrSet("key2", "val2"), "val2")
|
||||
gtest.Assert(m.GetOrSet("key2", "val2"), "val2")
|
||||
gtest.Assert(m.SetIfNotExist("key2", "val2"), false)
|
||||
|
||||
gtest.Assert(m.SetIfNotExist("key3", "val3"), true)
|
||||
|
||||
gtest.Assert(m.Remove("key2"), "val2")
|
||||
gtest.Assert(m.Contains("key2"), false)
|
||||
|
||||
gtest.AssertIN("key3", m.Keys())
|
||||
gtest.AssertIN("key1", m.Keys())
|
||||
gtest.AssertIN("val3", m.Values())
|
||||
gtest.AssertIN("val1", m.Values())
|
||||
|
||||
m.Sets(map[interface{}]interface{}{"key3": "val3", "key1": "val1"})
|
||||
|
||||
m.Flip()
|
||||
gtest.Assert(m.Map(), map[interface{}]interface{}{"val3": "key3", "val1": "key1"})
|
||||
|
||||
m.Flip(gutil.ComparatorString)
|
||||
gtest.Assert(m.Map(), map[interface{}]interface{}{"key3": "val3", "key1": "val1"})
|
||||
|
||||
m.Clear()
|
||||
gtest.Assert(m.Size(), 0)
|
||||
gtest.Assert(m.IsEmpty(), true)
|
||||
|
||||
m2 := gtree.NewAVLTreeFrom(gutil.ComparatorString, map[interface{}]interface{}{1: 1, "key1": "val1"})
|
||||
gtest.Assert(m2.Map(), map[interface{}]interface{}{1: 1, "key1": "val1"})
|
||||
})
|
||||
}
|
||||
func Test_AVLTree_Set_Fun(t *testing.T) {
|
||||
//GetOrSetFunc lock or unlock
|
||||
gtest.Case(t, func() {
|
||||
m := gtree.NewAVLTree(gutil.ComparatorString)
|
||||
gtest.Assert(m.GetOrSetFunc("fun", getValue), 3)
|
||||
gtest.Assert(m.GetOrSetFunc("fun", getValue), 3)
|
||||
gtest.Assert(m.GetOrSetFuncLock("funlock", getValue), 3)
|
||||
gtest.Assert(m.GetOrSetFuncLock("funlock", getValue), 3)
|
||||
gtest.Assert(m.Get("funlock"), 3)
|
||||
gtest.Assert(m.Get("fun"), 3)
|
||||
})
|
||||
//SetIfNotExistFunc lock or unlock
|
||||
gtest.Case(t, func() {
|
||||
m := gtree.NewAVLTree(gutil.ComparatorString)
|
||||
gtest.Assert(m.SetIfNotExistFunc("fun", getValue), true)
|
||||
gtest.Assert(m.SetIfNotExistFunc("fun", getValue), false)
|
||||
gtest.Assert(m.SetIfNotExistFuncLock("funlock", getValue), true)
|
||||
gtest.Assert(m.SetIfNotExistFuncLock("funlock", getValue), false)
|
||||
gtest.Assert(m.Get("funlock"), 3)
|
||||
gtest.Assert(m.Get("fun"), 3)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func Test_AVLTree_Get_Set_Var(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
m := gtree.NewAVLTree(gutil.ComparatorString)
|
||||
gtest.AssertEQ(m.SetIfNotExist("key1", "val1"), true)
|
||||
gtest.AssertEQ(m.SetIfNotExist("key1", "val1"), false)
|
||||
gtest.AssertEQ(m.GetVarOrSet("key1", "val1"), gvar.New("val1", true))
|
||||
gtest.AssertEQ(m.GetVar("key1"), gvar.New("val1", true))
|
||||
})
|
||||
|
||||
gtest.Case(t, func() {
|
||||
m := gtree.NewAVLTree(gutil.ComparatorString)
|
||||
gtest.AssertEQ(m.GetVarOrSetFunc("fun", getValue), gvar.New(3, true))
|
||||
gtest.AssertEQ(m.GetVarOrSetFunc("fun", getValue), gvar.New(3, true))
|
||||
gtest.AssertEQ(m.GetVarOrSetFuncLock("funlock", getValue), gvar.New(3, true))
|
||||
gtest.AssertEQ(m.GetVarOrSetFuncLock("funlock", getValue), gvar.New(3, true))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_AVLTree_Batch(t *testing.T) {
|
||||
m := gtree.NewAVLTree(gutil.ComparatorString)
|
||||
m.Sets(map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
|
||||
gtest.Assert(m.Map(), map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
|
||||
m.Removes([]interface{}{"key1", 1})
|
||||
gtest.Assert(m.Map(), map[interface{}]interface{}{"key2": "val2", "key3": "val3"})
|
||||
}
|
||||
|
||||
func Test_AVLTree_Iterator(t *testing.T) {
|
||||
keys := []string{"1", "key1", "key2", "key3", "key4"}
|
||||
keyLen := len(keys)
|
||||
index := 0
|
||||
|
||||
expect := map[interface{}]interface{}{"key4": "val4", 1: 1, "key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
|
||||
m := gtree.NewAVLTreeFrom(gutil.ComparatorString, expect)
|
||||
m.Iterator(func(k interface{}, v interface{}) bool {
|
||||
gtest.Assert(k, keys[index])
|
||||
index++
|
||||
gtest.Assert(expect[k], v)
|
||||
return true
|
||||
})
|
||||
|
||||
m.IteratorDesc(func(k interface{}, v interface{}) bool {
|
||||
index--
|
||||
gtest.Assert(k, keys[index])
|
||||
gtest.Assert(expect[k], v)
|
||||
return true
|
||||
})
|
||||
|
||||
m.Print()
|
||||
// 断言返回值对遍历控制
|
||||
gtest.Case(t, func() {
|
||||
i := 0
|
||||
j := 0
|
||||
m.Iterator(func(k interface{}, v interface{}) bool {
|
||||
i++
|
||||
return true
|
||||
})
|
||||
m.Iterator(func(k interface{}, v interface{}) bool {
|
||||
j++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(i, keyLen)
|
||||
gtest.Assert(j, 1)
|
||||
})
|
||||
|
||||
gtest.Case(t, func() {
|
||||
i := 0
|
||||
j := 0
|
||||
m.IteratorDesc(func(k interface{}, v interface{}) bool {
|
||||
i++
|
||||
return true
|
||||
})
|
||||
m.IteratorDesc(func(k interface{}, v interface{}) bool {
|
||||
j++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(i, keyLen)
|
||||
gtest.Assert(j, 1)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func Test_AVLTree_Clone(t *testing.T) {
|
||||
//clone 方法是深克隆
|
||||
m := gtree.NewAVLTreeFrom(gutil.ComparatorString, map[interface{}]interface{}{1: 1, "key1": "val1"})
|
||||
m_clone := m.Clone()
|
||||
m.Remove(1)
|
||||
//修改原 map,clone 后的 map 不影响
|
||||
gtest.AssertIN(1, m_clone.Keys())
|
||||
|
||||
m_clone.Remove("key1")
|
||||
//修改clone map,原 map 不影响
|
||||
gtest.AssertIN("key1", m.Keys())
|
||||
}
|
||||
|
||||
func Test_AVLTree_LRNode(t *testing.T) {
|
||||
expect := map[interface{}]interface{}{"key4": "val4", "key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
//safe
|
||||
gtest.Case(t, func() {
|
||||
m := gtree.NewAVLTreeFrom(gutil.ComparatorString, expect)
|
||||
gtest.Assert(m.Left().Key, "key1")
|
||||
gtest.Assert(m.Right().Key, "key4")
|
||||
})
|
||||
//unsafe
|
||||
gtest.Case(t, func() {
|
||||
m := gtree.NewAVLTreeFrom(gutil.ComparatorString, expect, true)
|
||||
gtest.Assert(m.Left().Key, "key1")
|
||||
gtest.Assert(m.Right().Key, "key4")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_AVLTree_CeilingFloor(t *testing.T) {
|
||||
expect := map[interface{}]interface{}{
|
||||
20: "val20",
|
||||
6: "val6",
|
||||
10: "val10",
|
||||
12: "val12",
|
||||
1: "val1",
|
||||
15: "val15",
|
||||
19: "val19",
|
||||
8: "val8",
|
||||
4: "val4"}
|
||||
//found and eq
|
||||
gtest.Case(t, func() {
|
||||
m := gtree.NewAVLTreeFrom(gutil.ComparatorInt, expect)
|
||||
c, cf := m.Ceiling(8)
|
||||
gtest.Assert(cf, true)
|
||||
gtest.Assert(c.Value, "val8")
|
||||
f, ff := m.Floor(20)
|
||||
gtest.Assert(ff, true)
|
||||
gtest.Assert(f.Value, "val20")
|
||||
})
|
||||
//found and neq
|
||||
gtest.Case(t, func() {
|
||||
m := gtree.NewAVLTreeFrom(gutil.ComparatorInt, expect)
|
||||
c, cf := m.Ceiling(9)
|
||||
gtest.Assert(cf, true)
|
||||
gtest.Assert(c.Value, "val10")
|
||||
f, ff := m.Floor(5)
|
||||
gtest.Assert(ff, true)
|
||||
gtest.Assert(f.Value, "val4")
|
||||
})
|
||||
//nofound
|
||||
gtest.Case(t, func() {
|
||||
m := gtree.NewAVLTreeFrom(gutil.ComparatorInt, expect)
|
||||
c, cf := m.Ceiling(21)
|
||||
gtest.Assert(cf, false)
|
||||
gtest.Assert(c, nil)
|
||||
f, ff := m.Floor(-1)
|
||||
gtest.Assert(ff, false)
|
||||
gtest.Assert(f, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_AVLTree_Remove(t *testing.T) {
|
||||
m := gtree.NewAVLTree(gutil.ComparatorInt)
|
||||
for i := 1; i <= 50; i++ {
|
||||
m.Set(i, fmt.Sprintf("val%d", i))
|
||||
}
|
||||
expect := m.Map()
|
||||
gtest.Case(t, func() {
|
||||
for k, v := range expect {
|
||||
m1 := m.Clone()
|
||||
gtest.Assert(m1.Remove(k), v)
|
||||
gtest.Assert(m1.Remove(k), nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user