roblox-opencode 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -122
- package/commands/setup-game.md +108 -108
- package/commands/sync-check.md +53 -53
- package/core/roblox-core.md +93 -93
- package/dist/server.js +189 -167
- package/package.json +35 -35
- package/skills/roblox-analytics/SKILL.md +277 -277
- package/skills/roblox-analytics/references/event-batcher.luau +75 -75
- package/skills/roblox-animation-vfx/SKILL.md +1325 -1325
- package/skills/roblox-architecture/SKILL.md +877 -863
- package/skills/roblox-architecture/references/combat-systems.md +1381 -1381
- package/skills/roblox-code-review/SKILL.md +686 -686
- package/skills/roblox-data/SKILL.md +889 -889
- package/skills/roblox-data/references/inventory-systems.md +1729 -1729
- package/skills/roblox-debug/SKILL.md +98 -98
- package/skills/roblox-gui/SKILL.md +1103 -1103
- package/skills/roblox-gui-fusion/SKILL.md +150 -150
- package/skills/roblox-gui-fusion/references/inventory.luau +427 -427
- package/skills/roblox-gui-fusion/references/settings-menu.luau +579 -579
- package/skills/roblox-gui-fusion/references/shop.luau +411 -411
- package/skills/roblox-luau-mastery/SKILL.md +1618 -1519
- package/skills/roblox-monetization/SKILL.md +1084 -1084
- package/skills/roblox-monetization/references/process-receipt.luau +131 -131
- package/skills/roblox-networking/SKILL.md +669 -669
- package/skills/roblox-networking/references/remote-validator.luau +193 -193
- package/skills/roblox-publish-checklist/SKILL.md +127 -127
- package/skills/roblox-runtime/SKILL.md +753 -753
- package/skills/roblox-sharp-edges/SKILL.md +294 -294
- package/skills/roblox-sync/SKILL.md +126 -126
- package/skills/roblox-testing/SKILL.md +943 -943
- package/skills/roblox-tooling/SKILL.md +149 -149
- package/vendor/LICENSES/ProfileStore-LICENSE +201 -201
- package/vendor/LICENSES/RbxUtil-LICENSE +7 -7
- package/vendor/LICENSES/promise-LICENSE +20 -20
- package/vendor/LICENSES/t-LICENSE +21 -21
- package/vendor/LICENSES/testez-LICENSE +200 -200
- package/vendor/README.md +83 -83
- package/vendor/fusion/Animation/ExternalTime.luau +83 -83
- package/vendor/fusion/Animation/Spring.luau +321 -321
- package/vendor/fusion/Animation/Stopwatch.luau +127 -127
- package/vendor/fusion/Animation/Tween.luau +187 -187
- package/vendor/fusion/Animation/getTweenDuration.luau +27 -27
- package/vendor/fusion/Animation/getTweenRatio.luau +47 -47
- package/vendor/fusion/Animation/lerpType.luau +163 -163
- package/vendor/fusion/Animation/packType.luau +99 -99
- package/vendor/fusion/Animation/springCoefficients.luau +80 -80
- package/vendor/fusion/Animation/unpackType.luau +102 -102
- package/vendor/fusion/Colour/Oklab.luau +70 -70
- package/vendor/fusion/Colour/sRGB.luau +54 -54
- package/vendor/fusion/External.luau +167 -167
- package/vendor/fusion/ExternalDebug.luau +69 -69
- package/vendor/fusion/Graph/Observer.luau +113 -113
- package/vendor/fusion/Graph/castToGraph.luau +28 -28
- package/vendor/fusion/Graph/change.luau +80 -80
- package/vendor/fusion/Graph/depend.luau +32 -32
- package/vendor/fusion/Graph/evaluate.luau +55 -55
- package/vendor/fusion/Instances/Attribute.luau +57 -57
- package/vendor/fusion/Instances/AttributeChange.luau +46 -46
- package/vendor/fusion/Instances/AttributeOut.luau +63 -63
- package/vendor/fusion/Instances/Child.luau +21 -21
- package/vendor/fusion/Instances/Children.luau +147 -147
- package/vendor/fusion/Instances/Hydrate.luau +32 -32
- package/vendor/fusion/Instances/New.luau +52 -52
- package/vendor/fusion/Instances/OnChange.luau +49 -49
- package/vendor/fusion/Instances/OnEvent.luau +53 -53
- package/vendor/fusion/Instances/Out.luau +69 -69
- package/vendor/fusion/Instances/applyInstanceProps.luau +148 -148
- package/vendor/fusion/Instances/defaultProps.luau +194 -194
- package/vendor/fusion/LICENSE +21 -21
- package/vendor/fusion/Logging/formatError.luau +48 -48
- package/vendor/fusion/Logging/messages.luau +51 -51
- package/vendor/fusion/Logging/parseError.luau +24 -24
- package/vendor/fusion/Memory/checkLifetime.luau +133 -133
- package/vendor/fusion/Memory/deriveScope.luau +23 -23
- package/vendor/fusion/Memory/deriveScopeImpl.luau +44 -44
- package/vendor/fusion/Memory/doCleanup.luau +78 -78
- package/vendor/fusion/Memory/innerScope.luau +33 -33
- package/vendor/fusion/Memory/legacyCleanup.luau +17 -17
- package/vendor/fusion/Memory/needsDestruction.luau +16 -16
- package/vendor/fusion/Memory/poisonScope.luau +33 -33
- package/vendor/fusion/Memory/scopePool.luau +54 -54
- package/vendor/fusion/Memory/scoped.luau +26 -26
- package/vendor/fusion/Memory/whichLivesLonger.luau +74 -74
- package/vendor/fusion/RobloxExternal.luau +97 -97
- package/vendor/fusion/State/Computed.luau +138 -138
- package/vendor/fusion/State/For/Disassembly.luau +210 -210
- package/vendor/fusion/State/For/ForTypes.luau +30 -30
- package/vendor/fusion/State/For/init.luau +109 -109
- package/vendor/fusion/State/ForKeys.luau +93 -93
- package/vendor/fusion/State/ForPairs.luau +96 -96
- package/vendor/fusion/State/ForValues.luau +93 -93
- package/vendor/fusion/State/Value.luau +87 -87
- package/vendor/fusion/State/castToState.luau +25 -25
- package/vendor/fusion/State/peek.luau +30 -30
- package/vendor/fusion/Types.luau +314 -314
- package/vendor/fusion/Utility/Contextual.luau +90 -90
- package/vendor/fusion/Utility/Safe.luau +22 -22
- package/vendor/fusion/Utility/isSimilar.luau +29 -29
- package/vendor/fusion/Utility/merge.luau +35 -35
- package/vendor/fusion/Utility/nameOf.luau +34 -34
- package/vendor/fusion/Utility/never.luau +13 -13
- package/vendor/fusion/Utility/nicknames.luau +10 -10
- package/vendor/fusion/Utility/xtypeof.luau +26 -26
- package/vendor/fusion/init.luau +82 -82
- package/vendor/profilestore/init.luau +2242 -2242
- package/vendor/promise/init.luau +1982 -1982
- package/vendor/rbxutil/buffer-util/Buffer.test.luau +25 -25
- package/vendor/rbxutil/buffer-util/BufferReader.luau +228 -228
- package/vendor/rbxutil/buffer-util/BufferWriter.luau +269 -269
- package/vendor/rbxutil/buffer-util/DataTypeBuffer.luau +223 -223
- package/vendor/rbxutil/buffer-util/Types.luau +60 -60
- package/vendor/rbxutil/buffer-util/index.d.ts +153 -153
- package/vendor/rbxutil/buffer-util/init.luau +41 -41
- package/vendor/rbxutil/buffer-util/package.json +16 -16
- package/vendor/rbxutil/buffer-util/wally.toml +9 -9
- package/vendor/rbxutil/comm/Client/ClientComm.luau +232 -232
- package/vendor/rbxutil/comm/Client/ClientRemoteProperty.luau +156 -156
- package/vendor/rbxutil/comm/Client/ClientRemoteSignal.luau +109 -109
- package/vendor/rbxutil/comm/Client/init.luau +135 -135
- package/vendor/rbxutil/comm/Server/RemoteProperty.luau +295 -295
- package/vendor/rbxutil/comm/Server/RemoteSignal.luau +211 -211
- package/vendor/rbxutil/comm/Server/ServerComm.luau +211 -211
- package/vendor/rbxutil/comm/Server/init.luau +140 -140
- package/vendor/rbxutil/comm/Types.luau +18 -18
- package/vendor/rbxutil/comm/Util.luau +27 -27
- package/vendor/rbxutil/comm/init.luau +35 -35
- package/vendor/rbxutil/comm/wally.toml +13 -13
- package/vendor/rbxutil/component/init.luau +759 -759
- package/vendor/rbxutil/component/init.test.luau +311 -311
- package/vendor/rbxutil/component/wally.toml +14 -14
- package/vendor/rbxutil/concur/init.luau +542 -542
- package/vendor/rbxutil/concur/init.test.luau +364 -364
- package/vendor/rbxutil/concur/wally.toml +8 -8
- package/vendor/rbxutil/enum-list/init.luau +101 -101
- package/vendor/rbxutil/enum-list/init.test.luau +91 -91
- package/vendor/rbxutil/enum-list/wally.toml +8 -8
- package/vendor/rbxutil/find/index.d.ts +20 -20
- package/vendor/rbxutil/find/init.luau +44 -44
- package/vendor/rbxutil/find/package.json +17 -17
- package/vendor/rbxutil/find/wally.toml +8 -8
- package/vendor/rbxutil/input/Gamepad.luau +559 -559
- package/vendor/rbxutil/input/Keyboard.luau +124 -124
- package/vendor/rbxutil/input/Mouse.luau +278 -278
- package/vendor/rbxutil/input/PreferredInput.luau +91 -91
- package/vendor/rbxutil/input/Touch.luau +120 -120
- package/vendor/rbxutil/input/init.luau +33 -33
- package/vendor/rbxutil/input/wally.toml +12 -12
- package/vendor/rbxutil/loader/index.d.ts +15 -15
- package/vendor/rbxutil/loader/init.luau +137 -137
- package/vendor/rbxutil/loader/wally.toml +8 -8
- package/vendor/rbxutil/log/index.d.ts +38 -38
- package/vendor/rbxutil/log/init.luau +746 -746
- package/vendor/rbxutil/log/wally.toml +8 -8
- package/vendor/rbxutil/net/init.luau +190 -190
- package/vendor/rbxutil/net/wally.toml +8 -8
- package/vendor/rbxutil/option/index.d.ts +44 -44
- package/vendor/rbxutil/option/init.luau +489 -489
- package/vendor/rbxutil/option/init.test.luau +342 -342
- package/vendor/rbxutil/option/wally.toml +8 -8
- package/vendor/rbxutil/pid/index.d.ts +53 -53
- package/vendor/rbxutil/pid/init.luau +195 -195
- package/vendor/rbxutil/pid/package.json +16 -16
- package/vendor/rbxutil/pid/wally.toml +9 -9
- package/vendor/rbxutil/quaternion/index.d.ts +117 -117
- package/vendor/rbxutil/quaternion/init.luau +570 -570
- package/vendor/rbxutil/quaternion/package.json +16 -16
- package/vendor/rbxutil/quaternion/wally.toml +9 -9
- package/vendor/rbxutil/query/index.d.ts +43 -43
- package/vendor/rbxutil/query/init.luau +117 -117
- package/vendor/rbxutil/query/package.json +18 -18
- package/vendor/rbxutil/query/wally.toml +9 -9
- package/vendor/rbxutil/sequent/index.d.ts +28 -28
- package/vendor/rbxutil/sequent/init.luau +340 -340
- package/vendor/rbxutil/sequent/package.json +16 -16
- package/vendor/rbxutil/sequent/wally.toml +9 -9
- package/vendor/rbxutil/ser/init.luau +175 -175
- package/vendor/rbxutil/ser/init.test.luau +50 -50
- package/vendor/rbxutil/ser/wally.toml +11 -11
- package/vendor/rbxutil/shake/index.d.ts +36 -36
- package/vendor/rbxutil/shake/init.luau +532 -532
- package/vendor/rbxutil/shake/init.test.luau +267 -267
- package/vendor/rbxutil/shake/package.json +16 -16
- package/vendor/rbxutil/shake/wally.toml +9 -9
- package/vendor/rbxutil/signal/index.d.ts +100 -100
- package/vendor/rbxutil/signal/init.luau +432 -432
- package/vendor/rbxutil/signal/init.test.luau +190 -190
- package/vendor/rbxutil/signal/package.json +17 -17
- package/vendor/rbxutil/signal/wally.toml +9 -9
- package/vendor/rbxutil/silo/TableWatcher.luau +65 -65
- package/vendor/rbxutil/silo/Util.luau +55 -55
- package/vendor/rbxutil/silo/init.luau +338 -338
- package/vendor/rbxutil/silo/init.test.luau +215 -215
- package/vendor/rbxutil/silo/wally.toml +8 -8
- package/vendor/rbxutil/spring/index.d.ts +40 -40
- package/vendor/rbxutil/spring/init.luau +97 -97
- package/vendor/rbxutil/spring/package.json +17 -17
- package/vendor/rbxutil/spring/wally.toml +8 -8
- package/vendor/rbxutil/stream/index.d.ts +88 -88
- package/vendor/rbxutil/stream/init.luau +597 -597
- package/vendor/rbxutil/stream/package.json +18 -18
- package/vendor/rbxutil/stream/wally.toml +9 -9
- package/vendor/rbxutil/streamable/Streamable.luau +202 -202
- package/vendor/rbxutil/streamable/StreamableUtil.luau +80 -80
- package/vendor/rbxutil/streamable/init.luau +8 -8
- package/vendor/rbxutil/streamable/wally.toml +12 -12
- package/vendor/rbxutil/symbol/init.luau +56 -56
- package/vendor/rbxutil/symbol/init.test.luau +37 -37
- package/vendor/rbxutil/symbol/wally.toml +8 -8
- package/vendor/rbxutil/table-util/init.luau +938 -938
- package/vendor/rbxutil/table-util/init.test.luau +439 -439
- package/vendor/rbxutil/task-queue/index.d.ts +27 -27
- package/vendor/rbxutil/task-queue/init.luau +97 -97
- package/vendor/rbxutil/task-queue/wally.toml +8 -8
- package/vendor/rbxutil/timer/index.d.ts +81 -81
- package/vendor/rbxutil/timer/init.luau +249 -249
- package/vendor/rbxutil/timer/init.test.luau +73 -73
- package/vendor/rbxutil/timer/wally.toml +11 -11
- package/vendor/rbxutil/tree/index.d.ts +15 -15
- package/vendor/rbxutil/tree/init.luau +137 -137
- package/vendor/rbxutil/tree/wally.toml +8 -8
- package/vendor/rbxutil/trove/index.d.ts +46 -46
- package/vendor/rbxutil/trove/init.luau +787 -787
- package/vendor/rbxutil/trove/init.test.luau +203 -203
- package/vendor/rbxutil/trove/wally.toml +8 -8
- package/vendor/rbxutil/typed-remote/init.luau +196 -196
- package/vendor/rbxutil/typed-remote/wally.toml +8 -8
- package/vendor/rbxutil/wait-for/index.d.ts +17 -17
- package/vendor/rbxutil/wait-for/init.luau +257 -257
- package/vendor/rbxutil/wait-for/init.test.luau +182 -182
- package/vendor/rbxutil/wait-for/wally.toml +11 -11
- package/vendor/t/t.lua +1350 -1350
- package/vendor/testez/Context.lua +26 -26
- package/vendor/testez/Expectation.lua +311 -311
- package/vendor/testez/ExpectationContext.lua +38 -38
- package/vendor/testez/LifecycleHooks.lua +89 -89
- package/vendor/testez/Reporters/TeamCityReporter.lua +101 -101
- package/vendor/testez/Reporters/TextReporter.lua +105 -105
- package/vendor/testez/Reporters/TextReporterQuiet.lua +96 -96
- package/vendor/testez/TestBootstrap.lua +146 -146
- package/vendor/testez/TestEnum.lua +27 -27
- package/vendor/testez/TestPlan.lua +304 -304
- package/vendor/testez/TestPlanner.lua +39 -39
- package/vendor/testez/TestResults.lua +111 -111
- package/vendor/testez/TestRunner.lua +188 -188
- package/vendor/testez/TestSession.lua +243 -243
- package/vendor/testez/init.lua +39 -39
|
@@ -1,938 +1,938 @@
|
|
|
1
|
-
--!strict
|
|
2
|
-
|
|
3
|
-
-- TableUtil
|
|
4
|
-
-- Stephen Leitnick
|
|
5
|
-
-- September 13, 2017
|
|
6
|
-
|
|
7
|
-
--[=[
|
|
8
|
-
@class TableUtil
|
|
9
|
-
|
|
10
|
-
A collection of helpful table utility functions. Many of these functions are carried over from JavaScript or
|
|
11
|
-
Python that are not present in Lua.
|
|
12
|
-
|
|
13
|
-
Tables that only work specifically with arrays or dictionaries are marked as such in the documentation.
|
|
14
|
-
|
|
15
|
-
:::info Immutability
|
|
16
|
-
All functions (_except_ `SwapRemove`, `SwapRemoveFirstValue`, and `Lock`) treat tables as immutable and will return
|
|
17
|
-
copies of the given table(s) with the operations performed on the copies.
|
|
18
|
-
:::
|
|
19
|
-
]=]
|
|
20
|
-
local TableUtil = {}
|
|
21
|
-
|
|
22
|
-
local HttpService = game:GetService("HttpService")
|
|
23
|
-
local rng = Random.new()
|
|
24
|
-
|
|
25
|
-
--[=[
|
|
26
|
-
@within TableUtil
|
|
27
|
-
@function Copy
|
|
28
|
-
@param tbl table -- Table to copy
|
|
29
|
-
@param deep boolean? -- Whether or not to perform a deep copy
|
|
30
|
-
@return table
|
|
31
|
-
|
|
32
|
-
Creates a copy of the given table. By default, a shallow copy is
|
|
33
|
-
performed. For deep copies, a second boolean argument must be
|
|
34
|
-
passed to the function.
|
|
35
|
-
|
|
36
|
-
:::caution No cyclical references
|
|
37
|
-
Deep copies are _not_ protected against cyclical references. Passing
|
|
38
|
-
a table with cyclical references _and_ the `deep` parameter set to
|
|
39
|
-
`true` will result in a stack-overflow.
|
|
40
|
-
:::
|
|
41
|
-
]=]
|
|
42
|
-
local function Copy<T>(t: T, deep: boolean?): T
|
|
43
|
-
if not deep then
|
|
44
|
-
return (table.clone(t :: any) :: any) :: T
|
|
45
|
-
end
|
|
46
|
-
local function DeepCopy(tbl: { any })
|
|
47
|
-
local tCopy = table.clone(tbl)
|
|
48
|
-
for k, v in tCopy do
|
|
49
|
-
if type(v) == "table" then
|
|
50
|
-
tCopy[k] = DeepCopy(v)
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
return tCopy
|
|
54
|
-
end
|
|
55
|
-
return DeepCopy(t :: any) :: T
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
--[=[
|
|
59
|
-
@within TableUtil
|
|
60
|
-
@function Sync
|
|
61
|
-
@param srcTbl table -- Source table
|
|
62
|
-
@param templateTbl table -- Template table
|
|
63
|
-
@return table
|
|
64
|
-
|
|
65
|
-
Synchronizes the `srcTbl` based on the `templateTbl`. This will make
|
|
66
|
-
sure that `srcTbl` has all of the same keys as `templateTbl`, including
|
|
67
|
-
removing keys in `srcTbl` that are not present in `templateTbl`. This
|
|
68
|
-
is a _deep_ operation, so any nested tables will be synchronized as
|
|
69
|
-
well.
|
|
70
|
-
|
|
71
|
-
```lua
|
|
72
|
-
local template = {kills = 0, deaths = 0, xp = 0}
|
|
73
|
-
local data = {kills = 10, experience = 12}
|
|
74
|
-
data = TableUtil.Sync(data, template)
|
|
75
|
-
print(data) --> {kills = 10, deaths = 0, xp = 0}
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
:::caution Data Loss Warning
|
|
79
|
-
This is a two-way sync, so the source table will have data
|
|
80
|
-
_removed_ that isn't found in the template table. This can
|
|
81
|
-
be problematic if used for player data, where there might
|
|
82
|
-
be dynamic data added that isn't in the template.
|
|
83
|
-
|
|
84
|
-
For player data, use `TableUtil.Reconcile` instead.
|
|
85
|
-
:::
|
|
86
|
-
]=]
|
|
87
|
-
local function Sync<S, T>(srcTbl: S, templateTbl: T): T
|
|
88
|
-
assert(type(srcTbl) == "table", "First argument must be a table")
|
|
89
|
-
assert(type(templateTbl) == "table", "Second argument must be a table")
|
|
90
|
-
|
|
91
|
-
local tbl = table.clone(srcTbl)
|
|
92
|
-
|
|
93
|
-
-- If 'tbl' has something 'templateTbl' doesn't, then remove it from 'tbl'
|
|
94
|
-
-- If 'tbl' has something of a different type than 'templateTbl', copy from 'templateTbl'
|
|
95
|
-
-- If 'templateTbl' has something 'tbl' doesn't, then add it to 'tbl'
|
|
96
|
-
for k, v in pairs(tbl) do
|
|
97
|
-
local vTemplate = templateTbl[k]
|
|
98
|
-
|
|
99
|
-
-- Remove keys not within template:
|
|
100
|
-
if vTemplate == nil then
|
|
101
|
-
tbl[k] = nil
|
|
102
|
-
|
|
103
|
-
-- Synchronize data types:
|
|
104
|
-
elseif type(v) ~= type(vTemplate) then
|
|
105
|
-
if type(vTemplate) == "table" then
|
|
106
|
-
tbl[k] = Copy(vTemplate, true)
|
|
107
|
-
else
|
|
108
|
-
tbl[k] = vTemplate
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
-- Synchronize sub-tables:
|
|
112
|
-
elseif type(v) == "table" then
|
|
113
|
-
tbl[k] = Sync(v, vTemplate)
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
-- Add any missing keys:
|
|
118
|
-
for k, vTemplate in pairs(templateTbl) do
|
|
119
|
-
local v = tbl[k]
|
|
120
|
-
|
|
121
|
-
if v == nil then
|
|
122
|
-
if type(vTemplate) == "table" then
|
|
123
|
-
tbl[k] = Copy(vTemplate, true)
|
|
124
|
-
else
|
|
125
|
-
tbl[k] = vTemplate
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
return (tbl :: any) :: T
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
--[=[
|
|
134
|
-
@within TableUtil
|
|
135
|
-
@function Reconcile
|
|
136
|
-
@param source table
|
|
137
|
-
@param template table
|
|
138
|
-
@return table
|
|
139
|
-
|
|
140
|
-
Performs a one-way sync on the `source` table against the
|
|
141
|
-
`template` table. Any keys found in `template` that are
|
|
142
|
-
not found in `source` will be added to `source`. This is
|
|
143
|
-
useful for syncing player data against data template tables
|
|
144
|
-
to ensure players have all the necessary keys, while
|
|
145
|
-
maintaining existing keys that may no longer be in the
|
|
146
|
-
template.
|
|
147
|
-
|
|
148
|
-
This is a deep operation, so nested tables will also be
|
|
149
|
-
properly reconciled.
|
|
150
|
-
|
|
151
|
-
```lua
|
|
152
|
-
local template = {kills = 0, deaths = 0, xp = 0}
|
|
153
|
-
local data = {kills = 10, abc = 20}
|
|
154
|
-
local correctedData = TableUtil.Reconcile(data, template)
|
|
155
|
-
|
|
156
|
-
print(correctedData) --> {kills = 10, deaths = 0, xp = 0, abc = 20}
|
|
157
|
-
```
|
|
158
|
-
]=]
|
|
159
|
-
local function Reconcile<S, T>(src: S, template: T): S & T
|
|
160
|
-
assert(type(src) == "table", "First argument must be a table")
|
|
161
|
-
assert(type(template) == "table", "Second argument must be a table")
|
|
162
|
-
|
|
163
|
-
local tbl = table.clone(src)
|
|
164
|
-
|
|
165
|
-
for k, v in template do
|
|
166
|
-
local sv = src[k]
|
|
167
|
-
if sv == nil then
|
|
168
|
-
if type(v) == "table" then
|
|
169
|
-
tbl[k] = Copy(v, true)
|
|
170
|
-
else
|
|
171
|
-
tbl[k] = v
|
|
172
|
-
end
|
|
173
|
-
elseif type(sv) == "table" then
|
|
174
|
-
if type(v) == "table" then
|
|
175
|
-
tbl[k] = Reconcile(sv, v)
|
|
176
|
-
else
|
|
177
|
-
tbl[k] = Copy(sv, true)
|
|
178
|
-
end
|
|
179
|
-
end
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
return (tbl :: any) :: S & T
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
--[=[
|
|
186
|
-
@within TableUtil
|
|
187
|
-
@function SwapRemove
|
|
188
|
-
@param tbl table -- Array
|
|
189
|
-
@param i number -- Index
|
|
190
|
-
|
|
191
|
-
Removes index `i` in the table by swapping the value at `i` with
|
|
192
|
-
the last value in the array, and then trimming off the last
|
|
193
|
-
value from the array.
|
|
194
|
-
|
|
195
|
-
This allows removal of the value at `i` in `O(1)` time, but does
|
|
196
|
-
not preserve array ordering. If a value needs to be removed from
|
|
197
|
-
an array, but ordering of the array does not matter, using
|
|
198
|
-
`SwapRemove` is always preferred over `table.remove`.
|
|
199
|
-
|
|
200
|
-
In the following example, we remove "B" at index 2. SwapRemove does
|
|
201
|
-
this by moving the last value "E" over top of "B", and then trimming
|
|
202
|
-
off "E" at the end of the array:
|
|
203
|
-
```lua
|
|
204
|
-
local t = {"A", "B", "C", "D", "E"}
|
|
205
|
-
TableUtil.SwapRemove(t, 2) -- Remove "B"
|
|
206
|
-
print(t) --> {"A", "E", "C", "D"}
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
:::note Arrays only
|
|
210
|
-
This function works on arrays, but not dictionaries.
|
|
211
|
-
:::
|
|
212
|
-
]=]
|
|
213
|
-
local function SwapRemove<T>(t: { T }, i: number)
|
|
214
|
-
local n = #t
|
|
215
|
-
t[i] = t[n]
|
|
216
|
-
t[n] = nil
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
--[=[
|
|
220
|
-
@within TableUtil
|
|
221
|
-
@function SwapRemoveFirstValue
|
|
222
|
-
@param tbl table -- Array
|
|
223
|
-
@param v any -- Value to find
|
|
224
|
-
@return number?
|
|
225
|
-
|
|
226
|
-
Performs `table.find(tbl, v)` to find the index of the given
|
|
227
|
-
value, and then performs `TableUtil.SwapRemove` on that index.
|
|
228
|
-
|
|
229
|
-
```lua
|
|
230
|
-
local t = {"A", "B", "C", "D", "E"}
|
|
231
|
-
TableUtil.SwapRemoveFirstValue(t, "C")
|
|
232
|
-
print(t) --> {"A", "B", "E", "D"}
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
:::note Arrays only
|
|
236
|
-
This function works on arrays, but not dictionaries.
|
|
237
|
-
:::
|
|
238
|
-
]=]
|
|
239
|
-
local function SwapRemoveFirstValue<T>(t: { T }, v: T): number?
|
|
240
|
-
local index: number? = table.find(t, v)
|
|
241
|
-
if index then
|
|
242
|
-
SwapRemove(t, index)
|
|
243
|
-
end
|
|
244
|
-
return index
|
|
245
|
-
end
|
|
246
|
-
|
|
247
|
-
--[=[
|
|
248
|
-
@within TableUtil
|
|
249
|
-
@function Map
|
|
250
|
-
@param tbl table
|
|
251
|
-
@param predicate (value: any, key: any, tbl: table) -> newValue: any
|
|
252
|
-
@return table
|
|
253
|
-
|
|
254
|
-
Performs a map operation against the given table, which can be used to
|
|
255
|
-
map new values based on the old values at given keys/indices.
|
|
256
|
-
|
|
257
|
-
For example:
|
|
258
|
-
|
|
259
|
-
```lua
|
|
260
|
-
local t = {A = 10, B = 20, C = 30}
|
|
261
|
-
local t2 = TableUtil.Map(t, function(value)
|
|
262
|
-
return value * 2
|
|
263
|
-
end)
|
|
264
|
-
print(t2) --> {A = 20, B = 40, C = 60}
|
|
265
|
-
```
|
|
266
|
-
]=]
|
|
267
|
-
local function Map<T, M>(t: { T }, f: (T, number, { T }) -> M): { M }
|
|
268
|
-
assert(type(t) == "table", "First argument must be a table")
|
|
269
|
-
assert(type(f) == "function", "Second argument must be a function")
|
|
270
|
-
local newT = table.create(#t)
|
|
271
|
-
for k, v in t do
|
|
272
|
-
newT[k] = f(v, k, t)
|
|
273
|
-
end
|
|
274
|
-
return newT
|
|
275
|
-
end
|
|
276
|
-
|
|
277
|
-
--[=[
|
|
278
|
-
@within TableUtil
|
|
279
|
-
@function Filter
|
|
280
|
-
@param tbl table
|
|
281
|
-
@param predicate (value: any, key: any, tbl: table) -> keep: boolean
|
|
282
|
-
@return table
|
|
283
|
-
|
|
284
|
-
Performs a filter operation against the given table, which can be used to
|
|
285
|
-
filter out unwanted values from the table.
|
|
286
|
-
|
|
287
|
-
For example:
|
|
288
|
-
|
|
289
|
-
```lua
|
|
290
|
-
local t = {A = 10, B = 20, C = 30}
|
|
291
|
-
local t2 = TableUtil.Filter(t, function(value, key)
|
|
292
|
-
return value > 15
|
|
293
|
-
end)
|
|
294
|
-
print(t2) --> {B = 40, C = 60}
|
|
295
|
-
```
|
|
296
|
-
]=]
|
|
297
|
-
local function Filter<T>(t: { T }, predicate: (T, any, { T }) -> boolean): { T }
|
|
298
|
-
assert(type(t) == "table", "First argument must be a table")
|
|
299
|
-
assert(type(predicate) == "function", "Second argument must be a function")
|
|
300
|
-
local newT = table.create(#t)
|
|
301
|
-
if #t > 0 then
|
|
302
|
-
local n = 0
|
|
303
|
-
for i, v in t do
|
|
304
|
-
if predicate(v, i, t) then
|
|
305
|
-
n += 1
|
|
306
|
-
newT[n] = v
|
|
307
|
-
end
|
|
308
|
-
end
|
|
309
|
-
else
|
|
310
|
-
for k, v in t do
|
|
311
|
-
if predicate(v, k, t) then
|
|
312
|
-
newT[k] = v
|
|
313
|
-
end
|
|
314
|
-
end
|
|
315
|
-
end
|
|
316
|
-
return newT
|
|
317
|
-
end
|
|
318
|
-
|
|
319
|
-
--[=[
|
|
320
|
-
@within TableUtil
|
|
321
|
-
@function Reduce
|
|
322
|
-
@param tbl table
|
|
323
|
-
@param predicate (accumulator: any, value: any, index: any, tbl: table) -> result: any
|
|
324
|
-
@return table
|
|
325
|
-
|
|
326
|
-
Performs a reduce operation against the given table, which can be used to
|
|
327
|
-
reduce the table into a single value. This could be used to sum up a table
|
|
328
|
-
or transform all the values into a compound value of any kind.
|
|
329
|
-
|
|
330
|
-
For example:
|
|
331
|
-
|
|
332
|
-
```lua
|
|
333
|
-
local t = {10, 20, 30, 40}
|
|
334
|
-
local result = TableUtil.Reduce(t, function(accum, value)
|
|
335
|
-
return accum + value
|
|
336
|
-
end)
|
|
337
|
-
print(result) --> 100
|
|
338
|
-
```
|
|
339
|
-
]=]
|
|
340
|
-
local function Reduce<T, R>(t: { T }, predicate: (R, T, any, { T }) -> R, init: R): R
|
|
341
|
-
assert(type(t) == "table", "First argument must be a table")
|
|
342
|
-
assert(type(predicate) == "function", "Second argument must be a function")
|
|
343
|
-
local result = init :: R
|
|
344
|
-
if #t > 0 then
|
|
345
|
-
local start = 1
|
|
346
|
-
if init == nil then
|
|
347
|
-
result = (t[1] :: any) :: R
|
|
348
|
-
start = 2
|
|
349
|
-
end
|
|
350
|
-
for i = start, #t do
|
|
351
|
-
result = predicate(result, t[i], i, t)
|
|
352
|
-
end
|
|
353
|
-
else
|
|
354
|
-
local start = nil
|
|
355
|
-
if init == nil then
|
|
356
|
-
result = (next(t) :: any) :: R
|
|
357
|
-
start = result
|
|
358
|
-
end
|
|
359
|
-
for k, v in next, t, start :: any? do
|
|
360
|
-
result = predicate(result, v, k, t)
|
|
361
|
-
end
|
|
362
|
-
end
|
|
363
|
-
return result
|
|
364
|
-
end
|
|
365
|
-
|
|
366
|
-
--[=[
|
|
367
|
-
@within TableUtil
|
|
368
|
-
@function Assign
|
|
369
|
-
@param target table
|
|
370
|
-
@param ... table
|
|
371
|
-
@return table
|
|
372
|
-
|
|
373
|
-
Copies all values of the given tables into the `target` table.
|
|
374
|
-
|
|
375
|
-
```lua
|
|
376
|
-
local t = {A = 10}
|
|
377
|
-
local t2 = {B = 20}
|
|
378
|
-
local t3 = {C = 30, D = 40}
|
|
379
|
-
local newT = TableUtil.Assign(t, t2, t3)
|
|
380
|
-
print(newT) --> {A = 10, B = 20, C = 30, D = 40}
|
|
381
|
-
```
|
|
382
|
-
]=]
|
|
383
|
-
local function Assign<T>(target: { T }, ...: { any }): { T } & { any }
|
|
384
|
-
local tbl = table.clone(target)
|
|
385
|
-
for _, src in { ... } do
|
|
386
|
-
for k, v in src do
|
|
387
|
-
tbl[k] = v
|
|
388
|
-
end
|
|
389
|
-
end
|
|
390
|
-
return tbl
|
|
391
|
-
end
|
|
392
|
-
|
|
393
|
-
--[=[
|
|
394
|
-
@within TableUtil
|
|
395
|
-
@function Extend
|
|
396
|
-
@param target table
|
|
397
|
-
@param extension table
|
|
398
|
-
@return table
|
|
399
|
-
|
|
400
|
-
Extends the target array with the extension array.
|
|
401
|
-
|
|
402
|
-
```lua
|
|
403
|
-
local t = {10, 20, 30}
|
|
404
|
-
local t2 = {30, 40, 50}
|
|
405
|
-
local tNew = TableUtil.Extend(t, t2)
|
|
406
|
-
print(tNew) --> {10, 20, 30, 30, 40, 50}
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
:::note Arrays only
|
|
410
|
-
This function works on arrays, but not dictionaries.
|
|
411
|
-
:::
|
|
412
|
-
]=]
|
|
413
|
-
local function Extend<T, E>(target: { T }, extension: { E }): { T } & { E }
|
|
414
|
-
local tbl = table.clone(target) :: { any }
|
|
415
|
-
for _, v in extension do
|
|
416
|
-
table.insert(tbl, v)
|
|
417
|
-
end
|
|
418
|
-
return tbl
|
|
419
|
-
end
|
|
420
|
-
|
|
421
|
-
--[=[
|
|
422
|
-
@within TableUtil
|
|
423
|
-
@function Reverse
|
|
424
|
-
@param tbl table
|
|
425
|
-
@return table
|
|
426
|
-
|
|
427
|
-
Reverses the array.
|
|
428
|
-
|
|
429
|
-
```lua
|
|
430
|
-
local t = {1, 5, 10}
|
|
431
|
-
local tReverse = TableUtil.Reverse(t)
|
|
432
|
-
print(tReverse) --> {10, 5, 1}
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
:::note Arrays only
|
|
436
|
-
This function works on arrays, but not dictionaries.
|
|
437
|
-
:::
|
|
438
|
-
]=]
|
|
439
|
-
local function Reverse<T>(tbl: { T }): { T }
|
|
440
|
-
local n = #tbl
|
|
441
|
-
local tblRev = table.create(n)
|
|
442
|
-
for i = 1, n do
|
|
443
|
-
tblRev[i] = tbl[n - i + 1]
|
|
444
|
-
end
|
|
445
|
-
return tblRev
|
|
446
|
-
end
|
|
447
|
-
|
|
448
|
-
--[=[
|
|
449
|
-
@within TableUtil
|
|
450
|
-
@function Shuffle
|
|
451
|
-
@param tbl table
|
|
452
|
-
@param rngOverride Random?
|
|
453
|
-
@return table
|
|
454
|
-
|
|
455
|
-
Shuffles the table.
|
|
456
|
-
|
|
457
|
-
```lua
|
|
458
|
-
local t = {1, 2, 3, 4, 5, 6, 7, 8, 9}
|
|
459
|
-
local shuffled = TableUtil.Shuffle(t)
|
|
460
|
-
print(shuffled) --> e.g. {9, 4, 6, 7, 3, 1, 5, 8, 2}
|
|
461
|
-
```
|
|
462
|
-
|
|
463
|
-
:::note Arrays only
|
|
464
|
-
This function works on arrays, but not dictionaries.
|
|
465
|
-
:::
|
|
466
|
-
]=]
|
|
467
|
-
local function Shuffle<T>(tbl: { T }, rngOverride: Random?): { T }
|
|
468
|
-
assert(type(tbl) == "table", "First argument must be a table")
|
|
469
|
-
local shuffled = table.clone(tbl)
|
|
470
|
-
local random = if typeof(rngOverride) == "Random" then rngOverride else rng
|
|
471
|
-
for i = #tbl, 2, -1 do
|
|
472
|
-
local j = random:NextInteger(1, i)
|
|
473
|
-
shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
|
|
474
|
-
end
|
|
475
|
-
return shuffled
|
|
476
|
-
end
|
|
477
|
-
|
|
478
|
-
--[=[
|
|
479
|
-
@within TableUtil
|
|
480
|
-
@function Sample
|
|
481
|
-
@param tbl table
|
|
482
|
-
@param sampleSize number
|
|
483
|
-
@param rngOverride Random?
|
|
484
|
-
@return table
|
|
485
|
-
|
|
486
|
-
Returns a random sample of the table.
|
|
487
|
-
|
|
488
|
-
```lua
|
|
489
|
-
local t = {1, 2, 3, 4, 5, 6, 7, 8, 9}
|
|
490
|
-
local sample = TableUtil.Sample(t, 3)
|
|
491
|
-
print(sample) --> e.g. {6, 2, 5}
|
|
492
|
-
```
|
|
493
|
-
|
|
494
|
-
:::note Arrays only
|
|
495
|
-
This function works on arrays, but not dictionaries.
|
|
496
|
-
:::
|
|
497
|
-
]=]
|
|
498
|
-
local function Sample<T>(tbl: { T }, size: number, rngOverride: Random?): { T }
|
|
499
|
-
assert(type(tbl) == "table", "First argument must be a table")
|
|
500
|
-
assert(type(size) == "number", "Second argument must be a number")
|
|
501
|
-
|
|
502
|
-
-- If given table is empty, just return a new empty table:
|
|
503
|
-
local len = #tbl
|
|
504
|
-
if len == 0 then
|
|
505
|
-
return {}
|
|
506
|
-
end
|
|
507
|
-
|
|
508
|
-
local shuffled = table.clone(tbl)
|
|
509
|
-
local sample = table.create(size)
|
|
510
|
-
local random = if typeof(rngOverride) == "Random" then rngOverride else rng
|
|
511
|
-
|
|
512
|
-
-- Clamp sample size to be no larger than the given table size:
|
|
513
|
-
size = math.clamp(size, 1, len)
|
|
514
|
-
|
|
515
|
-
for i = 1, size do
|
|
516
|
-
local j = random:NextInteger(i, len)
|
|
517
|
-
shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
|
|
518
|
-
end
|
|
519
|
-
|
|
520
|
-
table.move(shuffled, 1, size, 1, sample)
|
|
521
|
-
|
|
522
|
-
return sample
|
|
523
|
-
end
|
|
524
|
-
|
|
525
|
-
--[=[
|
|
526
|
-
@within TableUtil
|
|
527
|
-
@function Flat
|
|
528
|
-
@param tbl table
|
|
529
|
-
@param depth number?
|
|
530
|
-
@return table
|
|
531
|
-
|
|
532
|
-
Returns a new table where all sub-arrays have been
|
|
533
|
-
bubbled up to the top. The depth at which the scan
|
|
534
|
-
is performed is dictated by the `depth` parameter,
|
|
535
|
-
which is set to `1` by default.
|
|
536
|
-
|
|
537
|
-
```lua
|
|
538
|
-
local t = {{10, 20}, {90, 100}, {30, 15}}
|
|
539
|
-
local flat = TableUtil.Flat(t)
|
|
540
|
-
print(flat) --> {10, 20, 90, 100, 30, 15}
|
|
541
|
-
```
|
|
542
|
-
|
|
543
|
-
:::note Arrays only
|
|
544
|
-
This function works on arrays, but not dictionaries.
|
|
545
|
-
:::
|
|
546
|
-
]=]
|
|
547
|
-
local function Flat<T>(tbl: { T }, depth: number?): { T }
|
|
548
|
-
local maxDepth: number = if type(depth) == "number" then depth else 1
|
|
549
|
-
local flatTbl = table.create(#tbl)
|
|
550
|
-
local function Scan(t: { any }, d: number)
|
|
551
|
-
for _, v in t do
|
|
552
|
-
if type(v) == "table" and d < maxDepth then
|
|
553
|
-
Scan(v, d + 1)
|
|
554
|
-
else
|
|
555
|
-
table.insert(flatTbl, v)
|
|
556
|
-
end
|
|
557
|
-
end
|
|
558
|
-
end
|
|
559
|
-
Scan(tbl, 0)
|
|
560
|
-
return flatTbl
|
|
561
|
-
end
|
|
562
|
-
|
|
563
|
-
--[=[
|
|
564
|
-
@within TableUtil
|
|
565
|
-
@function FlatMap
|
|
566
|
-
@param tbl table
|
|
567
|
-
@param predicate (key: any, value: any, tbl: table) -> newValue: any
|
|
568
|
-
@return table
|
|
569
|
-
|
|
570
|
-
Calls `TableUtil.Map` on the given table and predicate, and then
|
|
571
|
-
calls `TableUtil.Flat` on the result from the map operation.
|
|
572
|
-
|
|
573
|
-
```lua
|
|
574
|
-
local t = {10, 20, 30}
|
|
575
|
-
local result = TableUtil.FlatMap(t, function(value)
|
|
576
|
-
return {value, value * 2}
|
|
577
|
-
end)
|
|
578
|
-
print(result) --> {10, 20, 20, 40, 30, 60}
|
|
579
|
-
```
|
|
580
|
-
|
|
581
|
-
:::note Arrays only
|
|
582
|
-
This function works on arrays, but not dictionaries.
|
|
583
|
-
:::
|
|
584
|
-
]=]
|
|
585
|
-
local function FlatMap<T, M>(tbl: { T }, callback: (T, number, { T }) -> M): { M }
|
|
586
|
-
return Flat(Map(tbl, callback))
|
|
587
|
-
end
|
|
588
|
-
|
|
589
|
-
--[=[
|
|
590
|
-
@within TableUtil
|
|
591
|
-
@function Keys
|
|
592
|
-
@param tbl table
|
|
593
|
-
@return table
|
|
594
|
-
|
|
595
|
-
Returns an array with all the keys in the table.
|
|
596
|
-
|
|
597
|
-
```lua
|
|
598
|
-
local t = {A = 10, B = 20, C = 30}
|
|
599
|
-
local keys = TableUtil.Keys(t)
|
|
600
|
-
print(keys) --> {"A", "B", "C"}
|
|
601
|
-
```
|
|
602
|
-
|
|
603
|
-
:::caution Ordering
|
|
604
|
-
The ordering of the keys is never guaranteed. If order is imperative, call
|
|
605
|
-
`table.sort` on the resulting `keys` array.
|
|
606
|
-
```lua
|
|
607
|
-
local keys = TableUtil.Keys(t)
|
|
608
|
-
table.sort(keys)
|
|
609
|
-
```
|
|
610
|
-
:::
|
|
611
|
-
]=]
|
|
612
|
-
local function Keys<K, V>(tbl: { [K]: V }): { K }
|
|
613
|
-
local keys = table.create(#tbl)
|
|
614
|
-
for k in tbl do
|
|
615
|
-
table.insert(keys, k)
|
|
616
|
-
end
|
|
617
|
-
return keys
|
|
618
|
-
end
|
|
619
|
-
|
|
620
|
-
--[=[
|
|
621
|
-
@within TableUtil
|
|
622
|
-
@function Values
|
|
623
|
-
@param tbl table
|
|
624
|
-
@return table
|
|
625
|
-
|
|
626
|
-
Returns an array with all the values in the table.
|
|
627
|
-
|
|
628
|
-
```lua
|
|
629
|
-
local t = {A = 10, B = 20, C = 30}
|
|
630
|
-
local values = TableUtil.Values(t)
|
|
631
|
-
print(values) --> {10, 20, 30}
|
|
632
|
-
```
|
|
633
|
-
|
|
634
|
-
:::caution Ordering
|
|
635
|
-
The ordering of the values is never guaranteed. If order is imperative, call
|
|
636
|
-
`table.sort` on the resulting `values` array.
|
|
637
|
-
```lua
|
|
638
|
-
local values = TableUtil.Values(t)
|
|
639
|
-
table.sort(values)
|
|
640
|
-
```
|
|
641
|
-
:::
|
|
642
|
-
]=]
|
|
643
|
-
local function Values<K, V>(tbl: { [K]: V }): { V }
|
|
644
|
-
local values = table.create(#tbl)
|
|
645
|
-
for _, v in tbl do
|
|
646
|
-
table.insert(values, v)
|
|
647
|
-
end
|
|
648
|
-
return values
|
|
649
|
-
end
|
|
650
|
-
|
|
651
|
-
--[=[
|
|
652
|
-
@within TableUtil
|
|
653
|
-
@function Find
|
|
654
|
-
@param tbl table
|
|
655
|
-
@param callback (value: any, index: any, tbl: table) -> boolean
|
|
656
|
-
@return (value: any?, key: any?)
|
|
657
|
-
|
|
658
|
-
Performs a linear scan across the table and calls `callback` on
|
|
659
|
-
each item in the array. Returns the value and key of the first
|
|
660
|
-
pair in which the callback returns `true`.
|
|
661
|
-
|
|
662
|
-
```lua
|
|
663
|
-
local t = {
|
|
664
|
-
{Name = "Bob", Age = 20};
|
|
665
|
-
{Name = "Jill", Age = 30};
|
|
666
|
-
{Name = "Ann", Age = 25};
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
-- Find first person who has a name starting with J:
|
|
670
|
-
local firstPersonWithJ = TableUtil.Find(t, function(person)
|
|
671
|
-
return person.Name:sub(1, 1):lower() == "j"
|
|
672
|
-
end)
|
|
673
|
-
|
|
674
|
-
print(firstPersonWithJ) --> {Name = "Jill", Age = 30}
|
|
675
|
-
```
|
|
676
|
-
|
|
677
|
-
:::caution Dictionary Ordering
|
|
678
|
-
While `Find` can also be used with dictionaries, dictionary ordering is never
|
|
679
|
-
guaranteed, and thus the result could be different if there are more
|
|
680
|
-
than one possible matches given the data and callback function.
|
|
681
|
-
:::
|
|
682
|
-
]=]
|
|
683
|
-
local function Find<K, V>(tbl: { [K]: V }, callback: (V, K, { [K]: V }) -> boolean): (V?, K?)
|
|
684
|
-
for k, v in tbl do
|
|
685
|
-
if callback(v, k, tbl) then
|
|
686
|
-
return v, k
|
|
687
|
-
end
|
|
688
|
-
end
|
|
689
|
-
return nil, nil
|
|
690
|
-
end
|
|
691
|
-
|
|
692
|
-
--[=[
|
|
693
|
-
@within TableUtil
|
|
694
|
-
@function Every
|
|
695
|
-
@param tbl table
|
|
696
|
-
@param callback (value: any, index: any, tbl: table) -> boolean
|
|
697
|
-
@return boolean
|
|
698
|
-
|
|
699
|
-
Returns `true` if the `callback` also returns `true` for _every_
|
|
700
|
-
item in the table.
|
|
701
|
-
|
|
702
|
-
```lua
|
|
703
|
-
local t = {10, 20, 40, 50, 60}
|
|
704
|
-
|
|
705
|
-
local allAboveZero = TableUtil.Every(t, function(value)
|
|
706
|
-
return value > 0
|
|
707
|
-
end)
|
|
708
|
-
|
|
709
|
-
print("All above zero:", allAboveZero) --> All above zero: true
|
|
710
|
-
```
|
|
711
|
-
]=]
|
|
712
|
-
local function Every<K, V>(tbl: { [K]: V }, callback: (V, K, { [K]: V }) -> boolean): boolean
|
|
713
|
-
for k, v in tbl do
|
|
714
|
-
if not callback(v, k, tbl) then
|
|
715
|
-
return false
|
|
716
|
-
end
|
|
717
|
-
end
|
|
718
|
-
return true
|
|
719
|
-
end
|
|
720
|
-
|
|
721
|
-
--[=[
|
|
722
|
-
@within TableUtil
|
|
723
|
-
@function Some
|
|
724
|
-
@param tbl table
|
|
725
|
-
@param callback (value: any, index: any, tbl: table) -> boolean
|
|
726
|
-
@return boolean
|
|
727
|
-
|
|
728
|
-
Returns `true` if the `callback` also returns `true` for _at least
|
|
729
|
-
one_ of the items in the table.
|
|
730
|
-
|
|
731
|
-
```lua
|
|
732
|
-
local t = {10, 20, 40, 50, 60}
|
|
733
|
-
|
|
734
|
-
local someBelowTwenty = TableUtil.Some(t, function(value)
|
|
735
|
-
return value < 20
|
|
736
|
-
end)
|
|
737
|
-
|
|
738
|
-
print("Some below twenty:", someBelowTwenty) --> Some below twenty: true
|
|
739
|
-
```
|
|
740
|
-
]=]
|
|
741
|
-
local function Some<K, V>(tbl: { [K]: V }, callback: (V, K, { [K]: V }) -> boolean): boolean
|
|
742
|
-
for k, v in tbl do
|
|
743
|
-
if callback(v, k, tbl) then
|
|
744
|
-
return true
|
|
745
|
-
end
|
|
746
|
-
end
|
|
747
|
-
return false
|
|
748
|
-
end
|
|
749
|
-
|
|
750
|
-
--[=[
|
|
751
|
-
@within TableUtil
|
|
752
|
-
@function Truncate
|
|
753
|
-
@param tbl table
|
|
754
|
-
@param length number
|
|
755
|
-
@return table
|
|
756
|
-
|
|
757
|
-
Returns a new table truncated to the length of `length`. Any length
|
|
758
|
-
equal or greater than the current length will simply return a
|
|
759
|
-
shallow copy of the table.
|
|
760
|
-
|
|
761
|
-
```lua
|
|
762
|
-
local t = {10, 20, 30, 40, 50, 60, 70, 80}
|
|
763
|
-
local tTruncated = TableUtil.Truncate(t, 3)
|
|
764
|
-
print(tTruncated) --> {10, 20, 30}
|
|
765
|
-
```
|
|
766
|
-
]=]
|
|
767
|
-
local function Truncate<T>(tbl: { T }, len: number): { T }
|
|
768
|
-
local n = #tbl
|
|
769
|
-
len = math.clamp(len, 1, n)
|
|
770
|
-
return if len == n then table.clone(tbl) else table.move(tbl, 1, len, 1, table.create(len))
|
|
771
|
-
end
|
|
772
|
-
|
|
773
|
-
--[=[
|
|
774
|
-
@within TableUtil
|
|
775
|
-
@function Zip
|
|
776
|
-
@param ... table
|
|
777
|
-
@return (iter: (t: table, k: any) -> (key: any?, values: table?), tbl: table, startIndex: any?)
|
|
778
|
-
|
|
779
|
-
Returns an iterator that can scan through multiple tables at the same time side-by-side, matching
|
|
780
|
-
against shared keys/indices.
|
|
781
|
-
|
|
782
|
-
```lua
|
|
783
|
-
local t1 = {10, 20, 30, 40, 50}
|
|
784
|
-
local t2 = {60, 70, 80, 90, 100}
|
|
785
|
-
|
|
786
|
-
for key,values in TableUtil.Zip(t1, t2) do
|
|
787
|
-
print(key, values)
|
|
788
|
-
end
|
|
789
|
-
|
|
790
|
-
--[[
|
|
791
|
-
Outputs:
|
|
792
|
-
1 {10, 60}
|
|
793
|
-
2 {20, 70}
|
|
794
|
-
3 {30, 80}
|
|
795
|
-
4 {40, 90}
|
|
796
|
-
5 {50, 100}
|
|
797
|
-
--]]
|
|
798
|
-
```
|
|
799
|
-
]=]
|
|
800
|
-
local function Zip(...: { [any]: any }): ((t: { any }, k: any) -> (any, any), { any }, any)
|
|
801
|
-
assert(select("#", ...) > 0, "Must supply at least 1 table")
|
|
802
|
-
local function ZipIteratorArray(all: { any }, k: number): (number?, { any }?)
|
|
803
|
-
k += 1
|
|
804
|
-
local values = {}
|
|
805
|
-
for i, t in all do
|
|
806
|
-
local v = t[k]
|
|
807
|
-
if v ~= nil then
|
|
808
|
-
values[i] = v
|
|
809
|
-
else
|
|
810
|
-
return nil, nil
|
|
811
|
-
end
|
|
812
|
-
end
|
|
813
|
-
return k, values
|
|
814
|
-
end
|
|
815
|
-
local function ZipIteratorMap(all: { [any]: any }, k: any): (number?, { any }?)
|
|
816
|
-
local values = {}
|
|
817
|
-
for i, t in all do
|
|
818
|
-
local v = next(t, k)
|
|
819
|
-
if v ~= nil then
|
|
820
|
-
values[i] = v
|
|
821
|
-
else
|
|
822
|
-
return nil, nil
|
|
823
|
-
end
|
|
824
|
-
end
|
|
825
|
-
return k, values
|
|
826
|
-
end
|
|
827
|
-
local all = { ... }
|
|
828
|
-
if #all[1] > 0 then
|
|
829
|
-
return ZipIteratorArray, all, 0
|
|
830
|
-
else
|
|
831
|
-
return ZipIteratorMap, all, nil
|
|
832
|
-
end
|
|
833
|
-
end
|
|
834
|
-
|
|
835
|
-
--[=[
|
|
836
|
-
@within TableUtil
|
|
837
|
-
@function Lock
|
|
838
|
-
@param tbl table
|
|
839
|
-
@return table
|
|
840
|
-
|
|
841
|
-
Locks the table using `table.freeze`, as well as any
|
|
842
|
-
nested tables within the given table. This will lock
|
|
843
|
-
the whole deep structure of the table, disallowing any
|
|
844
|
-
further modifications.
|
|
845
|
-
|
|
846
|
-
```lua
|
|
847
|
-
local tbl = {xyz = {abc = 32}}
|
|
848
|
-
tbl.xyz.abc = 28 -- Works fine
|
|
849
|
-
TableUtil.Lock(tbl)
|
|
850
|
-
tbl.xyz.abc = 64 -- Will throw an error (cannot modify readonly table)
|
|
851
|
-
```
|
|
852
|
-
]=]
|
|
853
|
-
local function Lock<T>(tbl: T): T
|
|
854
|
-
local function Freeze(t: { [any]: any })
|
|
855
|
-
for k, v in pairs(t) do
|
|
856
|
-
if type(v) == "table" then
|
|
857
|
-
t[k] = Freeze(v)
|
|
858
|
-
end
|
|
859
|
-
end
|
|
860
|
-
return table.freeze(t)
|
|
861
|
-
end
|
|
862
|
-
return Freeze(tbl :: any)
|
|
863
|
-
end
|
|
864
|
-
|
|
865
|
-
--[=[
|
|
866
|
-
@within TableUtil
|
|
867
|
-
@function IsEmpty
|
|
868
|
-
@param tbl table
|
|
869
|
-
@return boolean
|
|
870
|
-
|
|
871
|
-
Returns `true` if the given table is empty. This is
|
|
872
|
-
simply performed by checking if `next(tbl)` is `nil`
|
|
873
|
-
and works for both arrays and dictionaries. This is
|
|
874
|
-
useful when needing to check if a table is empty but
|
|
875
|
-
not knowing if it is an array or dictionary.
|
|
876
|
-
|
|
877
|
-
```lua
|
|
878
|
-
TableUtil.IsEmpty({}) -- true
|
|
879
|
-
TableUtil.IsEmpty({"abc"}) -- false
|
|
880
|
-
TableUtil.IsEmpty({abc = 32}) -- false
|
|
881
|
-
```
|
|
882
|
-
]=]
|
|
883
|
-
local function IsEmpty(tbl: { any }): boolean
|
|
884
|
-
return next(tbl) == nil
|
|
885
|
-
end
|
|
886
|
-
|
|
887
|
-
--[=[
|
|
888
|
-
@within TableUtil
|
|
889
|
-
@function EncodeJSON
|
|
890
|
-
@param value any
|
|
891
|
-
@return string
|
|
892
|
-
|
|
893
|
-
Proxy for [`HttpService:JSONEncode`](https://developer.roblox.com/en-us/api-reference/function/HttpService/JSONEncode).
|
|
894
|
-
]=]
|
|
895
|
-
local function EncodeJSON(value: any): string
|
|
896
|
-
return HttpService:JSONEncode(value)
|
|
897
|
-
end
|
|
898
|
-
|
|
899
|
-
--[=[
|
|
900
|
-
@within TableUtil
|
|
901
|
-
@function DecodeJSON
|
|
902
|
-
@param value any
|
|
903
|
-
@return string
|
|
904
|
-
|
|
905
|
-
Proxy for [`HttpService:JSONDecode`](https://developer.roblox.com/en-us/api-reference/function/HttpService/JSONDecode).
|
|
906
|
-
]=]
|
|
907
|
-
local function DecodeJSON(str: string): any
|
|
908
|
-
return HttpService:JSONDecode(str)
|
|
909
|
-
end
|
|
910
|
-
|
|
911
|
-
TableUtil.Copy = Copy
|
|
912
|
-
TableUtil.Sync = Sync
|
|
913
|
-
TableUtil.Reconcile = Reconcile
|
|
914
|
-
TableUtil.SwapRemove = SwapRemove
|
|
915
|
-
TableUtil.SwapRemoveFirstValue = SwapRemoveFirstValue
|
|
916
|
-
TableUtil.Map = Map
|
|
917
|
-
TableUtil.Filter = Filter
|
|
918
|
-
TableUtil.Reduce = Reduce
|
|
919
|
-
TableUtil.Assign = Assign
|
|
920
|
-
TableUtil.Extend = Extend
|
|
921
|
-
TableUtil.Reverse = Reverse
|
|
922
|
-
TableUtil.Shuffle = Shuffle
|
|
923
|
-
TableUtil.Sample = Sample
|
|
924
|
-
TableUtil.Flat = Flat
|
|
925
|
-
TableUtil.FlatMap = FlatMap
|
|
926
|
-
TableUtil.Keys = Keys
|
|
927
|
-
TableUtil.Values = Values
|
|
928
|
-
TableUtil.Find = Find
|
|
929
|
-
TableUtil.Every = Every
|
|
930
|
-
TableUtil.Some = Some
|
|
931
|
-
TableUtil.Truncate = Truncate
|
|
932
|
-
TableUtil.Zip = Zip
|
|
933
|
-
TableUtil.Lock = Lock
|
|
934
|
-
TableUtil.IsEmpty = IsEmpty
|
|
935
|
-
TableUtil.EncodeJSON = EncodeJSON
|
|
936
|
-
TableUtil.DecodeJSON = DecodeJSON
|
|
937
|
-
|
|
938
|
-
return TableUtil
|
|
1
|
+
--!strict
|
|
2
|
+
|
|
3
|
+
-- TableUtil
|
|
4
|
+
-- Stephen Leitnick
|
|
5
|
+
-- September 13, 2017
|
|
6
|
+
|
|
7
|
+
--[=[
|
|
8
|
+
@class TableUtil
|
|
9
|
+
|
|
10
|
+
A collection of helpful table utility functions. Many of these functions are carried over from JavaScript or
|
|
11
|
+
Python that are not present in Lua.
|
|
12
|
+
|
|
13
|
+
Tables that only work specifically with arrays or dictionaries are marked as such in the documentation.
|
|
14
|
+
|
|
15
|
+
:::info Immutability
|
|
16
|
+
All functions (_except_ `SwapRemove`, `SwapRemoveFirstValue`, and `Lock`) treat tables as immutable and will return
|
|
17
|
+
copies of the given table(s) with the operations performed on the copies.
|
|
18
|
+
:::
|
|
19
|
+
]=]
|
|
20
|
+
local TableUtil = {}
|
|
21
|
+
|
|
22
|
+
local HttpService = game:GetService("HttpService")
|
|
23
|
+
local rng = Random.new()
|
|
24
|
+
|
|
25
|
+
--[=[
|
|
26
|
+
@within TableUtil
|
|
27
|
+
@function Copy
|
|
28
|
+
@param tbl table -- Table to copy
|
|
29
|
+
@param deep boolean? -- Whether or not to perform a deep copy
|
|
30
|
+
@return table
|
|
31
|
+
|
|
32
|
+
Creates a copy of the given table. By default, a shallow copy is
|
|
33
|
+
performed. For deep copies, a second boolean argument must be
|
|
34
|
+
passed to the function.
|
|
35
|
+
|
|
36
|
+
:::caution No cyclical references
|
|
37
|
+
Deep copies are _not_ protected against cyclical references. Passing
|
|
38
|
+
a table with cyclical references _and_ the `deep` parameter set to
|
|
39
|
+
`true` will result in a stack-overflow.
|
|
40
|
+
:::
|
|
41
|
+
]=]
|
|
42
|
+
local function Copy<T>(t: T, deep: boolean?): T
|
|
43
|
+
if not deep then
|
|
44
|
+
return (table.clone(t :: any) :: any) :: T
|
|
45
|
+
end
|
|
46
|
+
local function DeepCopy(tbl: { any })
|
|
47
|
+
local tCopy = table.clone(tbl)
|
|
48
|
+
for k, v in tCopy do
|
|
49
|
+
if type(v) == "table" then
|
|
50
|
+
tCopy[k] = DeepCopy(v)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
return tCopy
|
|
54
|
+
end
|
|
55
|
+
return DeepCopy(t :: any) :: T
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
--[=[
|
|
59
|
+
@within TableUtil
|
|
60
|
+
@function Sync
|
|
61
|
+
@param srcTbl table -- Source table
|
|
62
|
+
@param templateTbl table -- Template table
|
|
63
|
+
@return table
|
|
64
|
+
|
|
65
|
+
Synchronizes the `srcTbl` based on the `templateTbl`. This will make
|
|
66
|
+
sure that `srcTbl` has all of the same keys as `templateTbl`, including
|
|
67
|
+
removing keys in `srcTbl` that are not present in `templateTbl`. This
|
|
68
|
+
is a _deep_ operation, so any nested tables will be synchronized as
|
|
69
|
+
well.
|
|
70
|
+
|
|
71
|
+
```lua
|
|
72
|
+
local template = {kills = 0, deaths = 0, xp = 0}
|
|
73
|
+
local data = {kills = 10, experience = 12}
|
|
74
|
+
data = TableUtil.Sync(data, template)
|
|
75
|
+
print(data) --> {kills = 10, deaths = 0, xp = 0}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
:::caution Data Loss Warning
|
|
79
|
+
This is a two-way sync, so the source table will have data
|
|
80
|
+
_removed_ that isn't found in the template table. This can
|
|
81
|
+
be problematic if used for player data, where there might
|
|
82
|
+
be dynamic data added that isn't in the template.
|
|
83
|
+
|
|
84
|
+
For player data, use `TableUtil.Reconcile` instead.
|
|
85
|
+
:::
|
|
86
|
+
]=]
|
|
87
|
+
local function Sync<S, T>(srcTbl: S, templateTbl: T): T
|
|
88
|
+
assert(type(srcTbl) == "table", "First argument must be a table")
|
|
89
|
+
assert(type(templateTbl) == "table", "Second argument must be a table")
|
|
90
|
+
|
|
91
|
+
local tbl = table.clone(srcTbl)
|
|
92
|
+
|
|
93
|
+
-- If 'tbl' has something 'templateTbl' doesn't, then remove it from 'tbl'
|
|
94
|
+
-- If 'tbl' has something of a different type than 'templateTbl', copy from 'templateTbl'
|
|
95
|
+
-- If 'templateTbl' has something 'tbl' doesn't, then add it to 'tbl'
|
|
96
|
+
for k, v in pairs(tbl) do
|
|
97
|
+
local vTemplate = templateTbl[k]
|
|
98
|
+
|
|
99
|
+
-- Remove keys not within template:
|
|
100
|
+
if vTemplate == nil then
|
|
101
|
+
tbl[k] = nil
|
|
102
|
+
|
|
103
|
+
-- Synchronize data types:
|
|
104
|
+
elseif type(v) ~= type(vTemplate) then
|
|
105
|
+
if type(vTemplate) == "table" then
|
|
106
|
+
tbl[k] = Copy(vTemplate, true)
|
|
107
|
+
else
|
|
108
|
+
tbl[k] = vTemplate
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
-- Synchronize sub-tables:
|
|
112
|
+
elseif type(v) == "table" then
|
|
113
|
+
tbl[k] = Sync(v, vTemplate)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
-- Add any missing keys:
|
|
118
|
+
for k, vTemplate in pairs(templateTbl) do
|
|
119
|
+
local v = tbl[k]
|
|
120
|
+
|
|
121
|
+
if v == nil then
|
|
122
|
+
if type(vTemplate) == "table" then
|
|
123
|
+
tbl[k] = Copy(vTemplate, true)
|
|
124
|
+
else
|
|
125
|
+
tbl[k] = vTemplate
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
return (tbl :: any) :: T
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
--[=[
|
|
134
|
+
@within TableUtil
|
|
135
|
+
@function Reconcile
|
|
136
|
+
@param source table
|
|
137
|
+
@param template table
|
|
138
|
+
@return table
|
|
139
|
+
|
|
140
|
+
Performs a one-way sync on the `source` table against the
|
|
141
|
+
`template` table. Any keys found in `template` that are
|
|
142
|
+
not found in `source` will be added to `source`. This is
|
|
143
|
+
useful for syncing player data against data template tables
|
|
144
|
+
to ensure players have all the necessary keys, while
|
|
145
|
+
maintaining existing keys that may no longer be in the
|
|
146
|
+
template.
|
|
147
|
+
|
|
148
|
+
This is a deep operation, so nested tables will also be
|
|
149
|
+
properly reconciled.
|
|
150
|
+
|
|
151
|
+
```lua
|
|
152
|
+
local template = {kills = 0, deaths = 0, xp = 0}
|
|
153
|
+
local data = {kills = 10, abc = 20}
|
|
154
|
+
local correctedData = TableUtil.Reconcile(data, template)
|
|
155
|
+
|
|
156
|
+
print(correctedData) --> {kills = 10, deaths = 0, xp = 0, abc = 20}
|
|
157
|
+
```
|
|
158
|
+
]=]
|
|
159
|
+
local function Reconcile<S, T>(src: S, template: T): S & T
|
|
160
|
+
assert(type(src) == "table", "First argument must be a table")
|
|
161
|
+
assert(type(template) == "table", "Second argument must be a table")
|
|
162
|
+
|
|
163
|
+
local tbl = table.clone(src)
|
|
164
|
+
|
|
165
|
+
for k, v in template do
|
|
166
|
+
local sv = src[k]
|
|
167
|
+
if sv == nil then
|
|
168
|
+
if type(v) == "table" then
|
|
169
|
+
tbl[k] = Copy(v, true)
|
|
170
|
+
else
|
|
171
|
+
tbl[k] = v
|
|
172
|
+
end
|
|
173
|
+
elseif type(sv) == "table" then
|
|
174
|
+
if type(v) == "table" then
|
|
175
|
+
tbl[k] = Reconcile(sv, v)
|
|
176
|
+
else
|
|
177
|
+
tbl[k] = Copy(sv, true)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
return (tbl :: any) :: S & T
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
--[=[
|
|
186
|
+
@within TableUtil
|
|
187
|
+
@function SwapRemove
|
|
188
|
+
@param tbl table -- Array
|
|
189
|
+
@param i number -- Index
|
|
190
|
+
|
|
191
|
+
Removes index `i` in the table by swapping the value at `i` with
|
|
192
|
+
the last value in the array, and then trimming off the last
|
|
193
|
+
value from the array.
|
|
194
|
+
|
|
195
|
+
This allows removal of the value at `i` in `O(1)` time, but does
|
|
196
|
+
not preserve array ordering. If a value needs to be removed from
|
|
197
|
+
an array, but ordering of the array does not matter, using
|
|
198
|
+
`SwapRemove` is always preferred over `table.remove`.
|
|
199
|
+
|
|
200
|
+
In the following example, we remove "B" at index 2. SwapRemove does
|
|
201
|
+
this by moving the last value "E" over top of "B", and then trimming
|
|
202
|
+
off "E" at the end of the array:
|
|
203
|
+
```lua
|
|
204
|
+
local t = {"A", "B", "C", "D", "E"}
|
|
205
|
+
TableUtil.SwapRemove(t, 2) -- Remove "B"
|
|
206
|
+
print(t) --> {"A", "E", "C", "D"}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
:::note Arrays only
|
|
210
|
+
This function works on arrays, but not dictionaries.
|
|
211
|
+
:::
|
|
212
|
+
]=]
|
|
213
|
+
local function SwapRemove<T>(t: { T }, i: number)
|
|
214
|
+
local n = #t
|
|
215
|
+
t[i] = t[n]
|
|
216
|
+
t[n] = nil
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
--[=[
|
|
220
|
+
@within TableUtil
|
|
221
|
+
@function SwapRemoveFirstValue
|
|
222
|
+
@param tbl table -- Array
|
|
223
|
+
@param v any -- Value to find
|
|
224
|
+
@return number?
|
|
225
|
+
|
|
226
|
+
Performs `table.find(tbl, v)` to find the index of the given
|
|
227
|
+
value, and then performs `TableUtil.SwapRemove` on that index.
|
|
228
|
+
|
|
229
|
+
```lua
|
|
230
|
+
local t = {"A", "B", "C", "D", "E"}
|
|
231
|
+
TableUtil.SwapRemoveFirstValue(t, "C")
|
|
232
|
+
print(t) --> {"A", "B", "E", "D"}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
:::note Arrays only
|
|
236
|
+
This function works on arrays, but not dictionaries.
|
|
237
|
+
:::
|
|
238
|
+
]=]
|
|
239
|
+
local function SwapRemoveFirstValue<T>(t: { T }, v: T): number?
|
|
240
|
+
local index: number? = table.find(t, v)
|
|
241
|
+
if index then
|
|
242
|
+
SwapRemove(t, index)
|
|
243
|
+
end
|
|
244
|
+
return index
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
--[=[
|
|
248
|
+
@within TableUtil
|
|
249
|
+
@function Map
|
|
250
|
+
@param tbl table
|
|
251
|
+
@param predicate (value: any, key: any, tbl: table) -> newValue: any
|
|
252
|
+
@return table
|
|
253
|
+
|
|
254
|
+
Performs a map operation against the given table, which can be used to
|
|
255
|
+
map new values based on the old values at given keys/indices.
|
|
256
|
+
|
|
257
|
+
For example:
|
|
258
|
+
|
|
259
|
+
```lua
|
|
260
|
+
local t = {A = 10, B = 20, C = 30}
|
|
261
|
+
local t2 = TableUtil.Map(t, function(value)
|
|
262
|
+
return value * 2
|
|
263
|
+
end)
|
|
264
|
+
print(t2) --> {A = 20, B = 40, C = 60}
|
|
265
|
+
```
|
|
266
|
+
]=]
|
|
267
|
+
local function Map<T, M>(t: { T }, f: (T, number, { T }) -> M): { M }
|
|
268
|
+
assert(type(t) == "table", "First argument must be a table")
|
|
269
|
+
assert(type(f) == "function", "Second argument must be a function")
|
|
270
|
+
local newT = table.create(#t)
|
|
271
|
+
for k, v in t do
|
|
272
|
+
newT[k] = f(v, k, t)
|
|
273
|
+
end
|
|
274
|
+
return newT
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
--[=[
|
|
278
|
+
@within TableUtil
|
|
279
|
+
@function Filter
|
|
280
|
+
@param tbl table
|
|
281
|
+
@param predicate (value: any, key: any, tbl: table) -> keep: boolean
|
|
282
|
+
@return table
|
|
283
|
+
|
|
284
|
+
Performs a filter operation against the given table, which can be used to
|
|
285
|
+
filter out unwanted values from the table.
|
|
286
|
+
|
|
287
|
+
For example:
|
|
288
|
+
|
|
289
|
+
```lua
|
|
290
|
+
local t = {A = 10, B = 20, C = 30}
|
|
291
|
+
local t2 = TableUtil.Filter(t, function(value, key)
|
|
292
|
+
return value > 15
|
|
293
|
+
end)
|
|
294
|
+
print(t2) --> {B = 40, C = 60}
|
|
295
|
+
```
|
|
296
|
+
]=]
|
|
297
|
+
local function Filter<T>(t: { T }, predicate: (T, any, { T }) -> boolean): { T }
|
|
298
|
+
assert(type(t) == "table", "First argument must be a table")
|
|
299
|
+
assert(type(predicate) == "function", "Second argument must be a function")
|
|
300
|
+
local newT = table.create(#t)
|
|
301
|
+
if #t > 0 then
|
|
302
|
+
local n = 0
|
|
303
|
+
for i, v in t do
|
|
304
|
+
if predicate(v, i, t) then
|
|
305
|
+
n += 1
|
|
306
|
+
newT[n] = v
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
else
|
|
310
|
+
for k, v in t do
|
|
311
|
+
if predicate(v, k, t) then
|
|
312
|
+
newT[k] = v
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
return newT
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
--[=[
|
|
320
|
+
@within TableUtil
|
|
321
|
+
@function Reduce
|
|
322
|
+
@param tbl table
|
|
323
|
+
@param predicate (accumulator: any, value: any, index: any, tbl: table) -> result: any
|
|
324
|
+
@return table
|
|
325
|
+
|
|
326
|
+
Performs a reduce operation against the given table, which can be used to
|
|
327
|
+
reduce the table into a single value. This could be used to sum up a table
|
|
328
|
+
or transform all the values into a compound value of any kind.
|
|
329
|
+
|
|
330
|
+
For example:
|
|
331
|
+
|
|
332
|
+
```lua
|
|
333
|
+
local t = {10, 20, 30, 40}
|
|
334
|
+
local result = TableUtil.Reduce(t, function(accum, value)
|
|
335
|
+
return accum + value
|
|
336
|
+
end)
|
|
337
|
+
print(result) --> 100
|
|
338
|
+
```
|
|
339
|
+
]=]
|
|
340
|
+
local function Reduce<T, R>(t: { T }, predicate: (R, T, any, { T }) -> R, init: R): R
|
|
341
|
+
assert(type(t) == "table", "First argument must be a table")
|
|
342
|
+
assert(type(predicate) == "function", "Second argument must be a function")
|
|
343
|
+
local result = init :: R
|
|
344
|
+
if #t > 0 then
|
|
345
|
+
local start = 1
|
|
346
|
+
if init == nil then
|
|
347
|
+
result = (t[1] :: any) :: R
|
|
348
|
+
start = 2
|
|
349
|
+
end
|
|
350
|
+
for i = start, #t do
|
|
351
|
+
result = predicate(result, t[i], i, t)
|
|
352
|
+
end
|
|
353
|
+
else
|
|
354
|
+
local start = nil
|
|
355
|
+
if init == nil then
|
|
356
|
+
result = (next(t) :: any) :: R
|
|
357
|
+
start = result
|
|
358
|
+
end
|
|
359
|
+
for k, v in next, t, start :: any? do
|
|
360
|
+
result = predicate(result, v, k, t)
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
return result
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
--[=[
|
|
367
|
+
@within TableUtil
|
|
368
|
+
@function Assign
|
|
369
|
+
@param target table
|
|
370
|
+
@param ... table
|
|
371
|
+
@return table
|
|
372
|
+
|
|
373
|
+
Copies all values of the given tables into the `target` table.
|
|
374
|
+
|
|
375
|
+
```lua
|
|
376
|
+
local t = {A = 10}
|
|
377
|
+
local t2 = {B = 20}
|
|
378
|
+
local t3 = {C = 30, D = 40}
|
|
379
|
+
local newT = TableUtil.Assign(t, t2, t3)
|
|
380
|
+
print(newT) --> {A = 10, B = 20, C = 30, D = 40}
|
|
381
|
+
```
|
|
382
|
+
]=]
|
|
383
|
+
local function Assign<T>(target: { T }, ...: { any }): { T } & { any }
|
|
384
|
+
local tbl = table.clone(target)
|
|
385
|
+
for _, src in { ... } do
|
|
386
|
+
for k, v in src do
|
|
387
|
+
tbl[k] = v
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
return tbl
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
--[=[
|
|
394
|
+
@within TableUtil
|
|
395
|
+
@function Extend
|
|
396
|
+
@param target table
|
|
397
|
+
@param extension table
|
|
398
|
+
@return table
|
|
399
|
+
|
|
400
|
+
Extends the target array with the extension array.
|
|
401
|
+
|
|
402
|
+
```lua
|
|
403
|
+
local t = {10, 20, 30}
|
|
404
|
+
local t2 = {30, 40, 50}
|
|
405
|
+
local tNew = TableUtil.Extend(t, t2)
|
|
406
|
+
print(tNew) --> {10, 20, 30, 30, 40, 50}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
:::note Arrays only
|
|
410
|
+
This function works on arrays, but not dictionaries.
|
|
411
|
+
:::
|
|
412
|
+
]=]
|
|
413
|
+
local function Extend<T, E>(target: { T }, extension: { E }): { T } & { E }
|
|
414
|
+
local tbl = table.clone(target) :: { any }
|
|
415
|
+
for _, v in extension do
|
|
416
|
+
table.insert(tbl, v)
|
|
417
|
+
end
|
|
418
|
+
return tbl
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
--[=[
|
|
422
|
+
@within TableUtil
|
|
423
|
+
@function Reverse
|
|
424
|
+
@param tbl table
|
|
425
|
+
@return table
|
|
426
|
+
|
|
427
|
+
Reverses the array.
|
|
428
|
+
|
|
429
|
+
```lua
|
|
430
|
+
local t = {1, 5, 10}
|
|
431
|
+
local tReverse = TableUtil.Reverse(t)
|
|
432
|
+
print(tReverse) --> {10, 5, 1}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
:::note Arrays only
|
|
436
|
+
This function works on arrays, but not dictionaries.
|
|
437
|
+
:::
|
|
438
|
+
]=]
|
|
439
|
+
local function Reverse<T>(tbl: { T }): { T }
|
|
440
|
+
local n = #tbl
|
|
441
|
+
local tblRev = table.create(n)
|
|
442
|
+
for i = 1, n do
|
|
443
|
+
tblRev[i] = tbl[n - i + 1]
|
|
444
|
+
end
|
|
445
|
+
return tblRev
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
--[=[
|
|
449
|
+
@within TableUtil
|
|
450
|
+
@function Shuffle
|
|
451
|
+
@param tbl table
|
|
452
|
+
@param rngOverride Random?
|
|
453
|
+
@return table
|
|
454
|
+
|
|
455
|
+
Shuffles the table.
|
|
456
|
+
|
|
457
|
+
```lua
|
|
458
|
+
local t = {1, 2, 3, 4, 5, 6, 7, 8, 9}
|
|
459
|
+
local shuffled = TableUtil.Shuffle(t)
|
|
460
|
+
print(shuffled) --> e.g. {9, 4, 6, 7, 3, 1, 5, 8, 2}
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
:::note Arrays only
|
|
464
|
+
This function works on arrays, but not dictionaries.
|
|
465
|
+
:::
|
|
466
|
+
]=]
|
|
467
|
+
local function Shuffle<T>(tbl: { T }, rngOverride: Random?): { T }
|
|
468
|
+
assert(type(tbl) == "table", "First argument must be a table")
|
|
469
|
+
local shuffled = table.clone(tbl)
|
|
470
|
+
local random = if typeof(rngOverride) == "Random" then rngOverride else rng
|
|
471
|
+
for i = #tbl, 2, -1 do
|
|
472
|
+
local j = random:NextInteger(1, i)
|
|
473
|
+
shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
|
|
474
|
+
end
|
|
475
|
+
return shuffled
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
--[=[
|
|
479
|
+
@within TableUtil
|
|
480
|
+
@function Sample
|
|
481
|
+
@param tbl table
|
|
482
|
+
@param sampleSize number
|
|
483
|
+
@param rngOverride Random?
|
|
484
|
+
@return table
|
|
485
|
+
|
|
486
|
+
Returns a random sample of the table.
|
|
487
|
+
|
|
488
|
+
```lua
|
|
489
|
+
local t = {1, 2, 3, 4, 5, 6, 7, 8, 9}
|
|
490
|
+
local sample = TableUtil.Sample(t, 3)
|
|
491
|
+
print(sample) --> e.g. {6, 2, 5}
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
:::note Arrays only
|
|
495
|
+
This function works on arrays, but not dictionaries.
|
|
496
|
+
:::
|
|
497
|
+
]=]
|
|
498
|
+
local function Sample<T>(tbl: { T }, size: number, rngOverride: Random?): { T }
|
|
499
|
+
assert(type(tbl) == "table", "First argument must be a table")
|
|
500
|
+
assert(type(size) == "number", "Second argument must be a number")
|
|
501
|
+
|
|
502
|
+
-- If given table is empty, just return a new empty table:
|
|
503
|
+
local len = #tbl
|
|
504
|
+
if len == 0 then
|
|
505
|
+
return {}
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
local shuffled = table.clone(tbl)
|
|
509
|
+
local sample = table.create(size)
|
|
510
|
+
local random = if typeof(rngOverride) == "Random" then rngOverride else rng
|
|
511
|
+
|
|
512
|
+
-- Clamp sample size to be no larger than the given table size:
|
|
513
|
+
size = math.clamp(size, 1, len)
|
|
514
|
+
|
|
515
|
+
for i = 1, size do
|
|
516
|
+
local j = random:NextInteger(i, len)
|
|
517
|
+
shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
table.move(shuffled, 1, size, 1, sample)
|
|
521
|
+
|
|
522
|
+
return sample
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
--[=[
|
|
526
|
+
@within TableUtil
|
|
527
|
+
@function Flat
|
|
528
|
+
@param tbl table
|
|
529
|
+
@param depth number?
|
|
530
|
+
@return table
|
|
531
|
+
|
|
532
|
+
Returns a new table where all sub-arrays have been
|
|
533
|
+
bubbled up to the top. The depth at which the scan
|
|
534
|
+
is performed is dictated by the `depth` parameter,
|
|
535
|
+
which is set to `1` by default.
|
|
536
|
+
|
|
537
|
+
```lua
|
|
538
|
+
local t = {{10, 20}, {90, 100}, {30, 15}}
|
|
539
|
+
local flat = TableUtil.Flat(t)
|
|
540
|
+
print(flat) --> {10, 20, 90, 100, 30, 15}
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
:::note Arrays only
|
|
544
|
+
This function works on arrays, but not dictionaries.
|
|
545
|
+
:::
|
|
546
|
+
]=]
|
|
547
|
+
local function Flat<T>(tbl: { T }, depth: number?): { T }
|
|
548
|
+
local maxDepth: number = if type(depth) == "number" then depth else 1
|
|
549
|
+
local flatTbl = table.create(#tbl)
|
|
550
|
+
local function Scan(t: { any }, d: number)
|
|
551
|
+
for _, v in t do
|
|
552
|
+
if type(v) == "table" and d < maxDepth then
|
|
553
|
+
Scan(v, d + 1)
|
|
554
|
+
else
|
|
555
|
+
table.insert(flatTbl, v)
|
|
556
|
+
end
|
|
557
|
+
end
|
|
558
|
+
end
|
|
559
|
+
Scan(tbl, 0)
|
|
560
|
+
return flatTbl
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
--[=[
|
|
564
|
+
@within TableUtil
|
|
565
|
+
@function FlatMap
|
|
566
|
+
@param tbl table
|
|
567
|
+
@param predicate (key: any, value: any, tbl: table) -> newValue: any
|
|
568
|
+
@return table
|
|
569
|
+
|
|
570
|
+
Calls `TableUtil.Map` on the given table and predicate, and then
|
|
571
|
+
calls `TableUtil.Flat` on the result from the map operation.
|
|
572
|
+
|
|
573
|
+
```lua
|
|
574
|
+
local t = {10, 20, 30}
|
|
575
|
+
local result = TableUtil.FlatMap(t, function(value)
|
|
576
|
+
return {value, value * 2}
|
|
577
|
+
end)
|
|
578
|
+
print(result) --> {10, 20, 20, 40, 30, 60}
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
:::note Arrays only
|
|
582
|
+
This function works on arrays, but not dictionaries.
|
|
583
|
+
:::
|
|
584
|
+
]=]
|
|
585
|
+
local function FlatMap<T, M>(tbl: { T }, callback: (T, number, { T }) -> M): { M }
|
|
586
|
+
return Flat(Map(tbl, callback))
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
--[=[
|
|
590
|
+
@within TableUtil
|
|
591
|
+
@function Keys
|
|
592
|
+
@param tbl table
|
|
593
|
+
@return table
|
|
594
|
+
|
|
595
|
+
Returns an array with all the keys in the table.
|
|
596
|
+
|
|
597
|
+
```lua
|
|
598
|
+
local t = {A = 10, B = 20, C = 30}
|
|
599
|
+
local keys = TableUtil.Keys(t)
|
|
600
|
+
print(keys) --> {"A", "B", "C"}
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
:::caution Ordering
|
|
604
|
+
The ordering of the keys is never guaranteed. If order is imperative, call
|
|
605
|
+
`table.sort` on the resulting `keys` array.
|
|
606
|
+
```lua
|
|
607
|
+
local keys = TableUtil.Keys(t)
|
|
608
|
+
table.sort(keys)
|
|
609
|
+
```
|
|
610
|
+
:::
|
|
611
|
+
]=]
|
|
612
|
+
local function Keys<K, V>(tbl: { [K]: V }): { K }
|
|
613
|
+
local keys = table.create(#tbl)
|
|
614
|
+
for k in tbl do
|
|
615
|
+
table.insert(keys, k)
|
|
616
|
+
end
|
|
617
|
+
return keys
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
--[=[
|
|
621
|
+
@within TableUtil
|
|
622
|
+
@function Values
|
|
623
|
+
@param tbl table
|
|
624
|
+
@return table
|
|
625
|
+
|
|
626
|
+
Returns an array with all the values in the table.
|
|
627
|
+
|
|
628
|
+
```lua
|
|
629
|
+
local t = {A = 10, B = 20, C = 30}
|
|
630
|
+
local values = TableUtil.Values(t)
|
|
631
|
+
print(values) --> {10, 20, 30}
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
:::caution Ordering
|
|
635
|
+
The ordering of the values is never guaranteed. If order is imperative, call
|
|
636
|
+
`table.sort` on the resulting `values` array.
|
|
637
|
+
```lua
|
|
638
|
+
local values = TableUtil.Values(t)
|
|
639
|
+
table.sort(values)
|
|
640
|
+
```
|
|
641
|
+
:::
|
|
642
|
+
]=]
|
|
643
|
+
local function Values<K, V>(tbl: { [K]: V }): { V }
|
|
644
|
+
local values = table.create(#tbl)
|
|
645
|
+
for _, v in tbl do
|
|
646
|
+
table.insert(values, v)
|
|
647
|
+
end
|
|
648
|
+
return values
|
|
649
|
+
end
|
|
650
|
+
|
|
651
|
+
--[=[
|
|
652
|
+
@within TableUtil
|
|
653
|
+
@function Find
|
|
654
|
+
@param tbl table
|
|
655
|
+
@param callback (value: any, index: any, tbl: table) -> boolean
|
|
656
|
+
@return (value: any?, key: any?)
|
|
657
|
+
|
|
658
|
+
Performs a linear scan across the table and calls `callback` on
|
|
659
|
+
each item in the array. Returns the value and key of the first
|
|
660
|
+
pair in which the callback returns `true`.
|
|
661
|
+
|
|
662
|
+
```lua
|
|
663
|
+
local t = {
|
|
664
|
+
{Name = "Bob", Age = 20};
|
|
665
|
+
{Name = "Jill", Age = 30};
|
|
666
|
+
{Name = "Ann", Age = 25};
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
-- Find first person who has a name starting with J:
|
|
670
|
+
local firstPersonWithJ = TableUtil.Find(t, function(person)
|
|
671
|
+
return person.Name:sub(1, 1):lower() == "j"
|
|
672
|
+
end)
|
|
673
|
+
|
|
674
|
+
print(firstPersonWithJ) --> {Name = "Jill", Age = 30}
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
:::caution Dictionary Ordering
|
|
678
|
+
While `Find` can also be used with dictionaries, dictionary ordering is never
|
|
679
|
+
guaranteed, and thus the result could be different if there are more
|
|
680
|
+
than one possible matches given the data and callback function.
|
|
681
|
+
:::
|
|
682
|
+
]=]
|
|
683
|
+
local function Find<K, V>(tbl: { [K]: V }, callback: (V, K, { [K]: V }) -> boolean): (V?, K?)
|
|
684
|
+
for k, v in tbl do
|
|
685
|
+
if callback(v, k, tbl) then
|
|
686
|
+
return v, k
|
|
687
|
+
end
|
|
688
|
+
end
|
|
689
|
+
return nil, nil
|
|
690
|
+
end
|
|
691
|
+
|
|
692
|
+
--[=[
|
|
693
|
+
@within TableUtil
|
|
694
|
+
@function Every
|
|
695
|
+
@param tbl table
|
|
696
|
+
@param callback (value: any, index: any, tbl: table) -> boolean
|
|
697
|
+
@return boolean
|
|
698
|
+
|
|
699
|
+
Returns `true` if the `callback` also returns `true` for _every_
|
|
700
|
+
item in the table.
|
|
701
|
+
|
|
702
|
+
```lua
|
|
703
|
+
local t = {10, 20, 40, 50, 60}
|
|
704
|
+
|
|
705
|
+
local allAboveZero = TableUtil.Every(t, function(value)
|
|
706
|
+
return value > 0
|
|
707
|
+
end)
|
|
708
|
+
|
|
709
|
+
print("All above zero:", allAboveZero) --> All above zero: true
|
|
710
|
+
```
|
|
711
|
+
]=]
|
|
712
|
+
local function Every<K, V>(tbl: { [K]: V }, callback: (V, K, { [K]: V }) -> boolean): boolean
|
|
713
|
+
for k, v in tbl do
|
|
714
|
+
if not callback(v, k, tbl) then
|
|
715
|
+
return false
|
|
716
|
+
end
|
|
717
|
+
end
|
|
718
|
+
return true
|
|
719
|
+
end
|
|
720
|
+
|
|
721
|
+
--[=[
|
|
722
|
+
@within TableUtil
|
|
723
|
+
@function Some
|
|
724
|
+
@param tbl table
|
|
725
|
+
@param callback (value: any, index: any, tbl: table) -> boolean
|
|
726
|
+
@return boolean
|
|
727
|
+
|
|
728
|
+
Returns `true` if the `callback` also returns `true` for _at least
|
|
729
|
+
one_ of the items in the table.
|
|
730
|
+
|
|
731
|
+
```lua
|
|
732
|
+
local t = {10, 20, 40, 50, 60}
|
|
733
|
+
|
|
734
|
+
local someBelowTwenty = TableUtil.Some(t, function(value)
|
|
735
|
+
return value < 20
|
|
736
|
+
end)
|
|
737
|
+
|
|
738
|
+
print("Some below twenty:", someBelowTwenty) --> Some below twenty: true
|
|
739
|
+
```
|
|
740
|
+
]=]
|
|
741
|
+
local function Some<K, V>(tbl: { [K]: V }, callback: (V, K, { [K]: V }) -> boolean): boolean
|
|
742
|
+
for k, v in tbl do
|
|
743
|
+
if callback(v, k, tbl) then
|
|
744
|
+
return true
|
|
745
|
+
end
|
|
746
|
+
end
|
|
747
|
+
return false
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
--[=[
|
|
751
|
+
@within TableUtil
|
|
752
|
+
@function Truncate
|
|
753
|
+
@param tbl table
|
|
754
|
+
@param length number
|
|
755
|
+
@return table
|
|
756
|
+
|
|
757
|
+
Returns a new table truncated to the length of `length`. Any length
|
|
758
|
+
equal or greater than the current length will simply return a
|
|
759
|
+
shallow copy of the table.
|
|
760
|
+
|
|
761
|
+
```lua
|
|
762
|
+
local t = {10, 20, 30, 40, 50, 60, 70, 80}
|
|
763
|
+
local tTruncated = TableUtil.Truncate(t, 3)
|
|
764
|
+
print(tTruncated) --> {10, 20, 30}
|
|
765
|
+
```
|
|
766
|
+
]=]
|
|
767
|
+
local function Truncate<T>(tbl: { T }, len: number): { T }
|
|
768
|
+
local n = #tbl
|
|
769
|
+
len = math.clamp(len, 1, n)
|
|
770
|
+
return if len == n then table.clone(tbl) else table.move(tbl, 1, len, 1, table.create(len))
|
|
771
|
+
end
|
|
772
|
+
|
|
773
|
+
--[=[
|
|
774
|
+
@within TableUtil
|
|
775
|
+
@function Zip
|
|
776
|
+
@param ... table
|
|
777
|
+
@return (iter: (t: table, k: any) -> (key: any?, values: table?), tbl: table, startIndex: any?)
|
|
778
|
+
|
|
779
|
+
Returns an iterator that can scan through multiple tables at the same time side-by-side, matching
|
|
780
|
+
against shared keys/indices.
|
|
781
|
+
|
|
782
|
+
```lua
|
|
783
|
+
local t1 = {10, 20, 30, 40, 50}
|
|
784
|
+
local t2 = {60, 70, 80, 90, 100}
|
|
785
|
+
|
|
786
|
+
for key,values in TableUtil.Zip(t1, t2) do
|
|
787
|
+
print(key, values)
|
|
788
|
+
end
|
|
789
|
+
|
|
790
|
+
--[[
|
|
791
|
+
Outputs:
|
|
792
|
+
1 {10, 60}
|
|
793
|
+
2 {20, 70}
|
|
794
|
+
3 {30, 80}
|
|
795
|
+
4 {40, 90}
|
|
796
|
+
5 {50, 100}
|
|
797
|
+
--]]
|
|
798
|
+
```
|
|
799
|
+
]=]
|
|
800
|
+
local function Zip(...: { [any]: any }): ((t: { any }, k: any) -> (any, any), { any }, any)
|
|
801
|
+
assert(select("#", ...) > 0, "Must supply at least 1 table")
|
|
802
|
+
local function ZipIteratorArray(all: { any }, k: number): (number?, { any }?)
|
|
803
|
+
k += 1
|
|
804
|
+
local values = {}
|
|
805
|
+
for i, t in all do
|
|
806
|
+
local v = t[k]
|
|
807
|
+
if v ~= nil then
|
|
808
|
+
values[i] = v
|
|
809
|
+
else
|
|
810
|
+
return nil, nil
|
|
811
|
+
end
|
|
812
|
+
end
|
|
813
|
+
return k, values
|
|
814
|
+
end
|
|
815
|
+
local function ZipIteratorMap(all: { [any]: any }, k: any): (number?, { any }?)
|
|
816
|
+
local values = {}
|
|
817
|
+
for i, t in all do
|
|
818
|
+
local v = next(t, k)
|
|
819
|
+
if v ~= nil then
|
|
820
|
+
values[i] = v
|
|
821
|
+
else
|
|
822
|
+
return nil, nil
|
|
823
|
+
end
|
|
824
|
+
end
|
|
825
|
+
return k, values
|
|
826
|
+
end
|
|
827
|
+
local all = { ... }
|
|
828
|
+
if #all[1] > 0 then
|
|
829
|
+
return ZipIteratorArray, all, 0
|
|
830
|
+
else
|
|
831
|
+
return ZipIteratorMap, all, nil
|
|
832
|
+
end
|
|
833
|
+
end
|
|
834
|
+
|
|
835
|
+
--[=[
|
|
836
|
+
@within TableUtil
|
|
837
|
+
@function Lock
|
|
838
|
+
@param tbl table
|
|
839
|
+
@return table
|
|
840
|
+
|
|
841
|
+
Locks the table using `table.freeze`, as well as any
|
|
842
|
+
nested tables within the given table. This will lock
|
|
843
|
+
the whole deep structure of the table, disallowing any
|
|
844
|
+
further modifications.
|
|
845
|
+
|
|
846
|
+
```lua
|
|
847
|
+
local tbl = {xyz = {abc = 32}}
|
|
848
|
+
tbl.xyz.abc = 28 -- Works fine
|
|
849
|
+
TableUtil.Lock(tbl)
|
|
850
|
+
tbl.xyz.abc = 64 -- Will throw an error (cannot modify readonly table)
|
|
851
|
+
```
|
|
852
|
+
]=]
|
|
853
|
+
local function Lock<T>(tbl: T): T
|
|
854
|
+
local function Freeze(t: { [any]: any })
|
|
855
|
+
for k, v in pairs(t) do
|
|
856
|
+
if type(v) == "table" then
|
|
857
|
+
t[k] = Freeze(v)
|
|
858
|
+
end
|
|
859
|
+
end
|
|
860
|
+
return table.freeze(t)
|
|
861
|
+
end
|
|
862
|
+
return Freeze(tbl :: any)
|
|
863
|
+
end
|
|
864
|
+
|
|
865
|
+
--[=[
|
|
866
|
+
@within TableUtil
|
|
867
|
+
@function IsEmpty
|
|
868
|
+
@param tbl table
|
|
869
|
+
@return boolean
|
|
870
|
+
|
|
871
|
+
Returns `true` if the given table is empty. This is
|
|
872
|
+
simply performed by checking if `next(tbl)` is `nil`
|
|
873
|
+
and works for both arrays and dictionaries. This is
|
|
874
|
+
useful when needing to check if a table is empty but
|
|
875
|
+
not knowing if it is an array or dictionary.
|
|
876
|
+
|
|
877
|
+
```lua
|
|
878
|
+
TableUtil.IsEmpty({}) -- true
|
|
879
|
+
TableUtil.IsEmpty({"abc"}) -- false
|
|
880
|
+
TableUtil.IsEmpty({abc = 32}) -- false
|
|
881
|
+
```
|
|
882
|
+
]=]
|
|
883
|
+
local function IsEmpty(tbl: { any }): boolean
|
|
884
|
+
return next(tbl) == nil
|
|
885
|
+
end
|
|
886
|
+
|
|
887
|
+
--[=[
|
|
888
|
+
@within TableUtil
|
|
889
|
+
@function EncodeJSON
|
|
890
|
+
@param value any
|
|
891
|
+
@return string
|
|
892
|
+
|
|
893
|
+
Proxy for [`HttpService:JSONEncode`](https://developer.roblox.com/en-us/api-reference/function/HttpService/JSONEncode).
|
|
894
|
+
]=]
|
|
895
|
+
local function EncodeJSON(value: any): string
|
|
896
|
+
return HttpService:JSONEncode(value)
|
|
897
|
+
end
|
|
898
|
+
|
|
899
|
+
--[=[
|
|
900
|
+
@within TableUtil
|
|
901
|
+
@function DecodeJSON
|
|
902
|
+
@param value any
|
|
903
|
+
@return string
|
|
904
|
+
|
|
905
|
+
Proxy for [`HttpService:JSONDecode`](https://developer.roblox.com/en-us/api-reference/function/HttpService/JSONDecode).
|
|
906
|
+
]=]
|
|
907
|
+
local function DecodeJSON(str: string): any
|
|
908
|
+
return HttpService:JSONDecode(str)
|
|
909
|
+
end
|
|
910
|
+
|
|
911
|
+
TableUtil.Copy = Copy
|
|
912
|
+
TableUtil.Sync = Sync
|
|
913
|
+
TableUtil.Reconcile = Reconcile
|
|
914
|
+
TableUtil.SwapRemove = SwapRemove
|
|
915
|
+
TableUtil.SwapRemoveFirstValue = SwapRemoveFirstValue
|
|
916
|
+
TableUtil.Map = Map
|
|
917
|
+
TableUtil.Filter = Filter
|
|
918
|
+
TableUtil.Reduce = Reduce
|
|
919
|
+
TableUtil.Assign = Assign
|
|
920
|
+
TableUtil.Extend = Extend
|
|
921
|
+
TableUtil.Reverse = Reverse
|
|
922
|
+
TableUtil.Shuffle = Shuffle
|
|
923
|
+
TableUtil.Sample = Sample
|
|
924
|
+
TableUtil.Flat = Flat
|
|
925
|
+
TableUtil.FlatMap = FlatMap
|
|
926
|
+
TableUtil.Keys = Keys
|
|
927
|
+
TableUtil.Values = Values
|
|
928
|
+
TableUtil.Find = Find
|
|
929
|
+
TableUtil.Every = Every
|
|
930
|
+
TableUtil.Some = Some
|
|
931
|
+
TableUtil.Truncate = Truncate
|
|
932
|
+
TableUtil.Zip = Zip
|
|
933
|
+
TableUtil.Lock = Lock
|
|
934
|
+
TableUtil.IsEmpty = IsEmpty
|
|
935
|
+
TableUtil.EncodeJSON = EncodeJSON
|
|
936
|
+
TableUtil.DecodeJSON = DecodeJSON
|
|
937
|
+
|
|
938
|
+
return TableUtil
|