Module:UtilsTable

local p = {} local h = {}

local inspect = require("Module:UtilsTable/Inspect") local utilsNumber = require("Module:UtilsNumber")

-- HELPERS

function h.append(tbl, val) tbl[table.maxn(tbl)+1] = val end

function h.mapper(iteratee) if type(iteratee) == "function" then return iteratee end if type(iteratee) == "string" or type(iteratee) == "number" then return p._property(iteratee) end end

function h.predicate(iteratee) if type(iteratee) == "function" then return iteratee end if type(iteratee) == "string" or type(iteratee) == "number" then return p._property(iteratee) end if type(iteratee) == "table" then return p._isMatch(iteratee) end return iteratee end

-- GENERAL

function p.clone(val) if type(val) ~= "table" then return val end local result = {} for k, v in pairs(val) do		result[k] = v	end return result end

function p.cloneDeep(val) if type(val) ~= "table" then return val end return p.merge({}, val) end

function p.hasArray(tbl) if type(tbl) ~= "table" then return false end return p.size(tbl) == 0 or p.len(tbl) > 0 end

function p.hasKey(tbl, key) return p._hasKey(key)(tbl) end function p._hasKey(key) return function(tbl) return not not tbl[key] end end

function p.invert(tbl) local inverted = {} for k, v in pairs(tbl) do		inverted[v] = k	end return inverted end

function p.isArray(tbl) if type(tbl) ~= "table" then return false end for k, v in pairs(tbl) do		if type(k) ~= "number" then return false end end return true end

function p.isEmpty(tbl) return p.size(tbl) == 0 end

function p.isEqual(tbl, other) return p._isEqual(tbl)(other) end function p._isEqual(tbl) return function(other) if type(tbl) ~= "table" or type(other) ~= "table" then return tbl == other end return p.isMatch(other, tbl) and p.isMatch(tbl, other) end end

function p.isMatch(tbl, source) return p._isMatch(source)(tbl) end function p._isMatch(sourceTbl) return function(tbl) if sourceTbl == tbl then return true end if type(sourceTbl) ~= type(tbl) then return false end if p.size(sourceTbl) == 0 and p.size(tbl) > 0 then return false end for k, v in pairs(sourceTbl) do			if type(v) ~= "table" and v ~= tbl[k] then return false end if type(v) == "table" and not p.isMatch(tbl[k], v) then return false end end return true end end

function p.ivalues(tbl) local result = {} for _, v in ipairs(tbl) do		table.insert(result, v)	end return result end

function p.keyOf(tbl, val) for k, v in pairs(tbl) do		if v == val then return k		end end return nil end

function p.keys(tbl) local result = {} for k in pairs(tbl) do  		h.append(result, k)   end return result end

function p.mapValues(tbl, iteratee) return p._mapValues(iteratee)(tbl) end function p._mapValues(iteratee) local mappingFunction = h.mapper(iteratee) return function(tbl) local result = {} for k, v in pairs(tbl) do			result[k] = mappingFunction(v) end return result end end

function p.merge(tbl, ...) if tbl == nil then return nil end for _, source in ipairs({...}) do		for k, v in pairs(source) do			if type(v) == "table" and type(tbl[k]) == "table" then tbl[k] = p.merge({}, tbl[k], v)			else tbl[k] = p.clone(v) end end end return tbl end

local MAX_SINGLELINE = 50 function p.print(tbl, singleLine) return inspect(tbl, {		multiline = function(t)			if singleLine then				return false			end			local size = 0			for key, val in pairs(t) do				if type(val) == "table" then					return true				end				size = size + 1			end			local singleLineLength = #inspect(t, { multiline = false })			return singleLineLength > MAX_SINGLELINE or size > #t and size - #t > 1		end	}) end

function p.printPath(pathComponents) local path = "" for _, pathComponent in pairs(pathComponents or {}) do		if string.match(pathComponent, "^[A-Za-z_][A-Za-z_0-9]*$") then path = path .. "." .. pathComponent elseif string.match(pathComponent, "^%[.*%]$") then path = path .. pathComponent else path = path .. "[" .. p.print(pathComponent) .. "]"		end end return path end

function p.property(tbl, path) return p._property(path)(tbl) end function p._property(path) return function(tbl) if type(path) ~= "table" then path = { path } end local result = tbl for i, key in ipairs(path) do			result = result[key] or result[mw.text.trim(key, '%[%]"')]			if result == nil then				return result			end		end		return result	end end

function p.size(tbl) return #p.keys(tbl) end

function p.stringKeys(tbl) local result = {} for k in pairs(tbl) do		if type(k) == "string" then table.insert(result, k)		end end return result end

function p.toArray(tbl, keys, padValue) return p._toArray(keys, padValue)(tbl) end function p._toArray(keys, padValue) return function(tbl) local array = {} if keys then for i, key in ipairs(keys) do				array[i] = tbl[key] or padValue end else for k, v in pairs(tbl) do				table.insert(array, v)			end end return array end end

--	"Array" functions (using the `ipairs` iterator, mostly) --

function p.compact(array) local result = {} local j = 1 for i = 1, table.maxn(array) do		if array[i] then result[j] = array[i] j = j + 1 end end return result end

function p.concat(array, ...) if type(array) ~= "table" then array = {array} end local result = p.clone(array) for i, arrayOrValue in ipairs({...}) do		if not p.hasArray(arrayOrValue) then h.append(result, arrayOrValue) else for _, value in ipairs(arrayOrValue) do				h.append(result, value) end end end return result end

function p.difference(array, other) local result = {} for i, v in ipairs(array) do		if not p.keyOf(other, v) then table.insert(result, v)		end end return result end

function p.dropRightWhile(array, predicate) return p._dropRightWhile(predicate)(array) end function p._dropRightWhile(predicate) local predicate = h.predicate(predicate) return function(array) local result = {} local i = p.len(array) while i > 0 and predicate(array[i], i) do			i = i - 1 end while i > 0 do			table.insert(result, 1, array[i]) i = i - 1 end return result end end

function p.filter(array, iteratee) return p._filter(iteratee)(array) end function p._filter(iteratee) return function(array) local predicateFn = h.predicate(iteratee) local results = {} for i, v in ipairs(array) do			if predicateFn(v) then table.insert(results, v)			end end return results end end

function p.find(array, iteratee) return p._find(iteratee)(array) end function p._find(iteratee) local predicate = h.predicate(iteratee) return function(array) for i, v in ipairs(array) do			if predicate(v) then return v, i			end end return nil, nil end end

function p.findIndex(array, iteratee) local index = select(2, p.find(array, iteratee)) return index end

function p.findLast(array, iteratee) return p._findLast(iteratee)(array) end function p._findLast(iteratee) local predicate = h.predicate(iteratee) return function(array) local v, i = p.find(p.reverse(array), predicate) if not i then return nil, nil end return v, p.len(array) - i + 1 end end

function p.flatten(array) local result = {} for _, v in ipairs(array) do		result = p.concat(result, v)	end return result end

function p.flatMap(array, mappingFunction) return p._flatMap(mappingFunction)(array) end function p._flatMap(mappingFunction) return function(array) return p.flatten(p.map(array, mappingFunction)) end end

function p.groupBy(array, iteratee) return p._groupBy(iteratee)(array) end function p._groupBy(iteratee) local mappingFn = h.mapper(iteratee) return function(array) local result = {} for _, v in ipairs(array) do			local groupingKey = mappingFn(v) if groupingKey then local group = result[groupingKey] or {} result[groupingKey] = p.concat(group, v)			end end return result end end

function p.includes(array, value) for _, v in ipairs(array) do		if v == value then return true end end return false end

function p.intersection(array, other) local result = {} for i, v in ipairs(array) do		if p.keyOf(other, v) then table.insert(result, v)		end end return result end

function p.keyBy(array, iteratee) return p._keyBy(iteratee)(array) end function p._keyBy(iteratee) local mapper = h.mapper(iteratee) return function(array) local result = {} for i, v in ipairs(array) do			result[mapper(v)] = v		end return result end end

function p.len(array) return #p.clone(array) end

function p.map(array, iteratee) return p._map(iteratee)(array) end function p._map(iteratee) return function(array) local mappingFunction = iteratee if type(iteratee) == "string" then mappingFunction = function(val) return val[iteratee] end end local tbl2 = {} for k, v in ipairs(array) do			tbl2[k] = mappingFunction(v) end return tbl2 end end

function p.min(array) local min for _, v in ipairs(array) do		min = math.min(min or utilsNumber.MAX, v)	end return min end

function p.max(array) local max for _, v in ipairs(array) do		max = math.max(max or utilsNumber.MIN, v)	end return max end

function p.padNils(array, padValue, max) return p._padNils(padValue, max)(array) end function p._padNils(padValue, max) return function(array) padValue = padValue or '' max = max or table.maxn(array) local result = p.clone(array) for i = 1, max do			if result[i] == nil then result[i] = padValue end end return result end end

-- returns a copy of tbl with the elements in opposite order (not a deep copy) function p.reverse(array) local tbl2 = {} local len = p.len(array) for i = len, 1, -1 do		tbl2[len - i + 1] = array[i] end return tbl2 end

function p.slice(array, s, e)	return p._slice(s, e)(array) end function p._slice(s, e)	return function(array) local tbl2 = {} e = e or p.len(array) local j = 1 for i = s, e do			tbl2[j] = array[i] i = i + 1 j = j + 1 end return tbl2 end end

function p.sortBy(array, iteratees) return p._sortBy(iteratees)(array) end function p._sortBy(iteratees) if type(iteratees) ~= "table" then iteratees = {iteratees} end local comparators = {} for i, iteratee in ipairs(iteratees) do		local mappingFn = h.mapper(iteratee) comparators[i] = h.comparator(mappingFn) end return function(array) local result = p.clone(array) table.sort(result, function(a, b)			local isEqual, isLessThan			local i = 1			repeat				isEqual, isLessThan = comparators[i](a, b)				i = i + 1			until not isEqual or i > #iteratees			return isLessThan		end) return result end end function h.comparator(mappingFn) return function(a, b)		a = mappingFn(a) b = mappingFn(b) return a == b, a < b	end end

function p.tail(array) local result = {} for i in ipairs(array) do		if i > 1 then result[i - 1] = array[i] end end return result end

function p.take(array, n)	return p._take(n)(array) end function p._take(n) return function(array) return p.slice(array, 1, n)	end end

function p.takeWhile(array, predicate) return p._takeWhile(predicate)(array) end function p._takeWhile(predicate) local predicate = h.predicate(predicate) return function(array) local result = {} local i = 1 while i <= p.len(array) and predicate(array[i], i) do			result[i] = array[i] i = i + 1 end return result end end

function p.unique(array) local result = {} for _, v in ipairs(array) do		if not p.find(result, p._isEqual(v)) then table.insert(result, v)		end end return result end

function p.uniqueBy(array, iteratee) return p._uniqueBy(iteratee)(array) end function p._uniqueBy(iteratee) local mapper = h.mapper(iteratee) return function(array) local result = {} for _, v in ipairs(array) do			local match = p.find(result, function(val)				return mapper(val) == mapper(v)			end) if not match then table.insert(result, v)			end end return result end end

-- Based on https://github.com/lua-stdlib/functional -- See https://lua-stdlib.github.io/lua-stdlib/modules/std.functional.html#zip function p.zip(array, padValue) local result = {} local len = p.len(array) for outerk, innerTbl in ipairs(array) do		innerTbl = p.padNils(innerTbl, "NIL") -- to properly handle sparse arrays for k, v in ipairs(innerTbl) do			result[k] = result[k] or {} if v ~= "NIL" then result[k][outerk] = v			end end if padValue then for k in ipairs(result) do				result[k] = p.padNils(result[k], padValue, len) end end end return result end

p.Schemas = { print = { singleLine = { type = "boolean", }	},	padNils = { padValue = { type = "any", default = '""', },		max = { type = "number", default = "table.maxn(array)", },	},	slice = { ["start"] = { type = "number", required = true, },		["end"] = { type = "number", default = "#array", },	},	toArray = { keys = { type = "array", items = { oneOf = { { type = "string" }, { type = "number" }, },			},		},		padValue = { type = "any" }, },	zip = { arrays = { type = "array", required = true, items = { type = "any" }, },		padValue = { type = "any", },	}, }

local propertyShorthand = " shorthand" local isMatchShorthand = " shorthand"

p.Documentation = { sections = { {			heading = "Tables", section = { clone = { params = {"tbl"}, returns = "Creates a shallow clone of .", cases = { {							snippet = 1, expect = { { foo = {} }, true, true },						},					},				},				cloneDeep = { desc = "Similar to except that it differs in how it handles metatables.", params = {"tbl"}, returns = "Creates a recursive clone of .", cases = { {							snippet = 1, expect = { { foo = {} }, true, true, },						},					},				},				hasArray = { params = {"tbl"}, returns = "True if the table contains integer keys (or is empty).", cases = { {							args = , expect = true, },						{							args = – , expect = true, },						{							args = , expect = false, },						{							args = , expect = true, },					}				},				hasKey = { params = {"tbl", "key"}, _params = {{"key"}, {"tbl"}}, returns = "True if  is not nil, else false.", cases = { {							args = {{"foo", nil, "bar"}, 3}, expect = true, },						{							args = {{foo = "bar", baz = "quux"}, "baz"}, expect = true, },						{							args = {{"foo", "bar"}, 3}, expect = false },						{							args = {{foo = "a", baz = "b"}, "quux"}, expect = false, },					},				},				isArray = { params = {"tbl"}, returns = "True if the table contains only integer keys (or is empty).", cases = { {							args = , expect = true, },						{							args = – , expect = true, },						{							args = , expect = true, },						{							args = , expect = true, },						{							args = , expect = false, },						{							args = , expect = false, },					},				},				isEmpty = { params = {"tbl"}, returns = "True if  has no keys whatsoever", cases = { {							args = – , expect = true, },						{							args = , expect = false, },						{							args = , expect = false, },					}				},				isEqual = { params = {"tbl", "other"}, _params = {{"tbl"}, {"other"}}, returns = " if   deep equals  .", cases = { {							args = { { foo = { bar = "baz" } }, { foo = { bar = "baz" } }, },							expect = true, },						{							args = { { foo = { bar = "baz" } }, { foo = { bar = "quux" } }, },							expect = false, },					}				},				invert = { params = {"tbl"}, returns = "A table with the values of  as its keys, and vice-versa.", cases = { {							args = , expect = { foo = 1, bar = 2, quux = "baz" },						},						{							desc = "Values will be overwritten if  has duplicate values. Overwrite order is not guaranteeed.", args = , expect = { Power = "Ganondorf", Courage = "Link", Wisdom = "Zelda", },						},					},				},				isMatch = { desc = "Performs a partial deep comparison between  and   to determine if   contains equivalent values.", params = {"tbl", "source"}, _params = {{"source"}, {"tbl"}}, returns = "Returns  if   is a match, else false", cases = { {							args = { { foo = { bar = "baz", flip = "flop" } }, { foo = { bar = "baz" } }, },							expect = true, },						{							args = { {1, 2, 3},								{1, 2},							},							expect = true, },						{							args = { { foo = { bar = "baz" } }, { foo = { bar = "quux" } }, },							expect = false },					},				},				keyOf = { params = {"tbl", "value"}, returns = "First key found whose value is shallow-equal to, or nil if none found.", cases = { outputOnly = true, {							args = {{"foo", nil, "bar"}, "bar"}, expect = 3, },						{							args = {{foo = "bar", baz = "quux"}, "quux"}, expect = "baz", },						{							args = {{"foo", "bar"}, "quux"}, expect = nil },						{							desc = "Does not perform deep-equal checks on tables.", args = {{{}, {}}, {}}, expect = nil },					},				},				keys = { desc = "See also and .", params = {"tbl"}, returns = "Array of  keys.", cases = { {							args = , expect = {1, 2, 3}, },						{							args = , expect = {"foo", "baz"}, },						{							args = , expect = {1, 3, "bar"} },					},				},				mapValues = { params = {"tbl", "iteratee"}, returns = "Creates a table with the same keys as  and values generated by running each value of   thru iteratee.", cases = { {							snippet = 1, expect = { arg1 = "foo", arg2 = "bar"} },						{							desc = propertyShorthand, args = { {									Link = { Triforce = "Courage", },									Zelda = { Triforce = "Wisdom" },								},								"Triforce", },							expect = { Link = "Courage", Zelda = "Wisdom", },						},					},				},				merge = { desc = "Recursively merges tables.", params = {"tbl", "..." },					returns = " with merged values. Subsequent sources overwrite key assignments of previous sources.", cases = { {							snippet = 1, expect = { flib = "flob", foo = { bar = {"noot", "flob"}, baz = "noot", wibble = "wobble", },							}						},						{							desc = "Common use: merging keys into new table.", args = { {}, 								{ flib = "flob" }, { wibble = "wobble" }, },							expect = { flib = "flob", wibble = "wobble", },						},					}				},				print = { params = {"tbl", "singleLine"}, returns = " pretty-printed as a string.", cases = { outputOnly = true, {							desc = "Prints array items on a single line.", args = , expect = '{"foo", "bar", "baz"}', },						{							desc = "Prints single line when tables has one string key.", args = , expect = '{ foo = "bar" }', },						{							args = , expect = '{1, 2, 3, foo = "bar"}', },						{							desc = "Prints one value per line if more than one string key.", args = , expect = {							 flib = "flub",							  foo = "bar",							}, },						{							args = , expect = {							 1,							  2,							  3,							  flib = "flub",							  foo = "bar",							} },						{							desc = "Prints one value per line if any are tables.", args = , expect = {							 {1},							  {2},							}, },						{							desc = string.format("Prints one value per line if single-line would exceed %s characters.", MAX_SINGLELINE), args = , expect = {							 "abcdefghijklmnopqrstuvwxyz",							  "abcdefghijklmnopqrstuvwxyz",							} },						{							desc = "Always prints single-line when  is true", args = {{"abcdefghijklmnopqrstuvwxyz", "abcdefghijklmnopqrstuvwxyz"}, true}, expect = '{"abcdefghijklmnopqrstuvwxyz", "abcdefghijklmnopqrstuvwxyz"}' },					},				},				printPath = { params = {"path"}, returns = "Property path as a string.", cases = { outputOnly = true, {							args = , expect = ".foo[1].bar[1]", },						{							args = , expect = '.foo["bar"]', },					},				},				property = { params = {"tbl", "path"}, _params = {{"path"}, {"tbl"}}, returns = "The value at  for  .", cases = { outputOnly = true, {							args = {{ foo = {"bar"} }, {"foo", 1}}, expect = "bar", },						{							args = {{ foo = {"bar"} }, {"foo", "bar", "baz"}}, expect = nil, },					},				},				size = { params = {"tbl"}, returns = "Total number of keys in .", cases = { {							args = , expect = 3, },					},				},				stringKeys = { params = {"tbl"}, returns = "Array of string keys in ", cases = { {							args = , expect = {"foo"}, },					},				},				ivalues = { params = {"tbl"}, returns = "Array of contiguous, integer-keyed values in  starting from 1.", cases = { {							args = , expect = {"foo", "bar"}, },					},				},				toArray = { params = {"tbl", "keys", "padValue"}, _params = {{"keys", "padValue"}, {"tbl"}}, returns = "An array of all the values in . Order not guaranteed unless   is specified. If so, the returned array is such that  . Note that the function will return a sparse array when  .", cases = { {							desc = "Order not guaranteed.", args = { { a = "foo", b = 2, c = true }, },							expect = {"foo", true, 2}, },						{							desc = " param can be used to specify order.", args = { { a = "foo", b = 2, c = true }, {"c", "a", "b"}, },							expect = {true, "foo", 2}, },						{							desc = " param can be used to grab a subset of the keys.", args = { { a = "foo", b = 2, c = true }, {"a", "b"}, },							expect = {"foo", 2}, },						{							desc = " param can result in a sparse array if key not in  .", args = { { a = "foo", b = 2, c = true }, {"b", "a", "d", "c"}, },							expect = {2, "foo", [4] = true}, },						{							desc = "Sparse arrays can be padded with a custom .", args = { { a = "foo", b = 2, c = true }, {"b", "a", "d", "c"}, ""							},							expect = {2, "foo", "", true}, },					},				},			},		},		{			heading = "Arrays", section = { compact = { params = {"array"}, returns = "Creates an array with falsey values removed.", cases = { {							args = , expect = {0, 1, ""} },					},				},				concat = { params = {"array", "..."}, returns = "Creates a new array concatenating array with any additional arrays and/or values.", cases = { {							snippet = 1, expect = {1, 2, 3, 4, {5}, 6}, },						{							desc = "Tables with only string keys are treated as single values.", args = {{}, {1, 2}, {3, 4, foo = "bar"}, { foo = "quux" }}, expect = {1, 2, 3, 4, { foo = "quux" }}, },					},				},				difference = { params = {"array", "other"}, returns = "An array of all the elements in  that are not in  .", cases = { {							args = {{"a", "b", "c"}, {"c", "d"}}, expect = {"a", "b"}, },						{							desc = "Does not deep-compare.", args = {, }, expect = , }					},				},				dropRightWhile = { params = {"array", "predicate"}, _params = {{"predicate"}, {"array"}}, returns = "Creates a slice of  excluding elements dropped from the end. Elements are dropped until   returns falsey.", cases = { {							snippet = 1, expect = {"...", "SS", "ALBW"}, },						{							desc = propertyShorthand, args = { {									{ game = "SS", hasMasterSword = true }, { game = "ALBW", hasMasterSword = true }, { game = "TFH", hasMasterSword = false }, { game = "BotW", hasMasterSword = true }, },								"hasMasterSword", },							expect = { { game = "SS", hasMasterSword = true }, { game = "ALBW", hasMasterSword = true }, { game = "TFH", hasMasterSword = false }, },						},						{							desc = isMatchShorthand, args = { {									{ game = "SS", system = "console" }, { game = "ALBW", system = "handheld" }, { game = "TFH", system = "handheld" }, { game = "BotW", system = "console" }, },								{ system = "console" }, },							expect = { { game = "SS", system = "console" }, { game = "ALBW", system = "handheld" }, { game = "TFH", system = "handheld" }, },						},					},				},				keyBy = { params = {"array", "iteratee"}, _params = {{"iteratee"}, {"array"}}, returns = "Creates a table composed of keys generated from the results of running each element of  thru  ", cases = { {							snippet = 1, expect = { ["TWW Link"] = { name = "Link", game = "TWW", age = 10, },								["TP Link"] = { name = "Link", game = "TP", age = 17, },							},						},						{							desc = propertyShorthand, args = {{ {									name = "Link", age = 10, },								{									name = "Zelda", age = 10, },								{									name = "Zelda", age = 17, },							}, "name"}, expect = { ["Link"] = { name = "Link", age = 10 },								["Zelda"] = { name = "Zelda", age = 17, },							}						},					},				},				filter = { params = {"array", "iteratee"}, _params = {{"iteratee"}, {"array"}}, returns = "Iterates over array elements in, returning an array of all elements   returns truthy for.", cases = { {							snippet = 1, expect = {"foo", "bar"}, },						{							desc = propertyShorthand, args = { {									{ game = "The Wind Waker", canon = true }, { game = "Twilight Princess", canon = true }, { game = "Tingle's Rosy Rupeeland", canon = false }, },								"canon", },							expect = { { game = "The Wind Waker", canon = true }, { game = "Twilight Princess", canon = true }, },						},						{							desc = isMatchShorthand, args = { {									{ game = "The Wind Waker", type = "main" }, { game = "Twilight Princess", type = "main" }, { game = "Tingle's Rosy Rupeeland", type = "spinoff" }, },								{ type = "main" }, },							expect = { { game = "The Wind Waker", type = "main" }, { game = "Twilight Princess", type = "main" }, },						},					},				},				find = { params = {"tbl", "iteratee"}, _params = {{"iteratee"}, {"tbl"}}, returns = {"The value if found, else ", "The index of the value found, else  "}, cases = { outputOnly = true, {							snippet = 1, expect = {"bar", 2} },						{							snippet = 2, expect = {nil, nil}, },						{							desc = propertyShorthand, args = { {									{ game = "Tingle's Rosy Rupeeland", canon = false }, { game = "The Wind Waker", canon = true }, { game = "Breath of the Wild", canon = true }, },								"canon" },							expect = { { game = "The Wind Waker", canon = true }, 2,							},						},						{							desc = isMatchShorthand, args = { {									{ 										game = "Tingle's Rosy Rupeeland", canon = false, type = "spinoff", },									{										game = "Twilight Princess HD", canon = true, type = "remake", },									{ 										game = "Breath of the Wild", canon = true, type = "main" },								},								{ canon = true, type = "main" } },							expect = { { 									game = "Breath of the Wild", canon = true, type = "main" },								3							}						}					}				},				findIndex = { params = {"tbl", "iteratee"}, returns = "The index of the value if found, else ", cases = { outputOnly = true, {							snippet = 1, expect = 2 },						{							snippet = 2, expect = nil, },						{							desc = propertyShorthand, args = { {									{ game = "Tingle's Rosy Rupeeland", canon = false }, { game = "The Wind Waker", canon = true }, { game = "Breath of the Wild", canon = true }, },								"canon" },							expect = 2, },						{							desc = isMatchShorthand, args = { {									{ 										game = "Tingle's Rosy Rupeeland", canon = false, type = "spinoff", },									{										game = "Twilight Princess HD", canon = true, type = "remake", },									{ 										game = "Breath of the Wild", canon = true, type = "main" },								},								{ canon = true, type = "main" } },							expect = 3 }					},				},				findLast = { params = {"array", "iteratee"}, _params = {{"iteratee"}, {"array"}}, returns = { "The last value found matching, else  .", "The index of the value, else ." },					cases = { outputOnly = true, {							snippet = 1, expect = {"baz", 3}, },						{							snippet = 2, expect = {nil, nil}, },						{							desc = propertyShorthand, args = { {									{ game = "Tingle's Rosy Rupeeland", canon = false }, { game = "The Wind Waker", canon = true }, { game = "Breath of the Wild", canon = true }, },								"canon" },							expect = { { game = "Breath of the Wild", canon = true }, 3,							},						},						{							desc = isMatchShorthand, args = { {									{ 										game = "Tingle's Rosy Rupeeland", canon = false, type = "spinoff", },									{										game = "Twilight Princess", canon = true, type = "main", },									{ 										game = "Breath of the Wild", canon = true, type = "main" },								},								{ canon = true, type = "main" } },							expect = { { 									game = "Breath of the Wild", canon = true, type = "main" },								3,							},						},					},				},				flatten = { params = {"array"}, returns = "Returns  but flattened a single level deep.", cases = { {							args = , expect = {1, 2, {3, 4}, 5} },					},				},				flatMap = { params = {"array", "iteratee"}, _params = {{"iteratee"}, {"array"}}, returns = "A flattened array of values created by running each element in  thru   and flattening the mapped results.", cases = { {							snippet = 1, expect = {1, 1, 2, 2}, },					},				},				groupBy = { params = {"array", "iteratee"}, _params = {{"iteratee"}, {"array"}}, returns = "A table composed of keys generated from the results of running each element of  thru  . The order of grouped values is determined by the order they occur in  . The corresponding value of each key is an array of elements responsible for generating the key. If a   key is generated for a given value, that value will be absent from the resulting table.", cases = { {							snippet = 1, expect = { [6] = {6.1, 6.3},								[4] = {4.2},							},						},						{							desc = propertyShorthand, args = { {									{										page = "Link", term = "Hero of Time", game = "OoT", },									{										page = "Link", term = "Hero of Winds", game = "TWW" },									{										page = "Princess Zelda", term = "Zelda", game = "SS", },									{										someOtherData = "foo", },								},								"page", },							expect = { ["Link"] = { {										page = "Link", term = "Hero of Time", game = "OoT", },									{										page = "Link", term = "Hero of Winds", game = "TWW" },								},								["Princess Zelda"] = { {										page = "Princess Zelda", term = "Zelda", game = "SS", },								},							},						},					},				},				includes = { params = {"array", "value"}, returns = "True if and only if  is in  .", cases = { {							args = {{"foo", "bar"}, "foo"}, expect = true },						{							args = {{"foo", "bar"}, "baz"}, expect = false },					},				},				intersection = { params = {"array", "other"}, returns = "An array of all the elements that are in both  and  ", cases = { {							args = {{"a", "b", "c"}, {"b"}}, expect = {"b"}, },						{							desc = "Does not deep-compare.", args = {, }, expect = {}, },					},				},				len = { params = {"array"}, returns = "The length of the array. Works with and tables loaded thru .", cases = { {							args = , expect = 3, },					},				},				map = { params = {"array", "iteratee"}, _params = {{"iteratee"}, {"array"}}, returns = "Creates an array of values by running each array element in  thru  .", cases = { {							snippet = 1, expect = {"true", "false"}, },						{							desc = propertyShorthand, args = { {									{										name = "Link", triforce = "Courage", },									{										name = "Zelda", triforce = "Wisdom", },									{										name = "Ganon", triforce = "Power" },								},								"triforce" },							expect = {"Courage", "Wisdom", "Power"}, },					},				},				min = { params = {"array"}, returns = "The maximum value of, or nil if empty.", cases = { {							args = , expect = 1, },						{							args = – , expect = nil, },					},				},				max = { params = {"array"}, returns = "The maximum value of, or nil if empty.", cases = { {							args = , expect = 20, },						{							args = – , expect = nil, },					},				},				padNils = { params = {"array", "padValue", "max"}, _params = {{"padValue", "max"}, {"array"}}, returns = "Returns a version of  such that nil-valued indices up to and including   are replaced with  .", cases = { {							args = {{nil, 2, nil, 4}, 0}, expect = {0, 2, 0, 4}, },						{							args = {{nil, 2, nil, 4}, 0, 1}, expect = {0, 2, nil, 4}, },						{							args = {{nil, 2, nil, 4}, 0, 6}, expect = {0, 2, 0, 4, 0, 0}, },						{							args = , expect = {"foo", "", "baz"}, },					},				},				reverse = { params = {"array"}, returns = "Reverses  so that the first element becomes the last, the second element becomes the second to last, and so on.", cases = { {							args = , expect = {3, 2, 1}, },						{							desc = "Ignores non-consecutive integer keys", args = , expect = {3, 2, 1}, },					},				},				slice = { params = {"array", "start", "end"}, _params = {{"start", "end"}, {"array"}}, returns = "Creates a slice of  from   up to and including  .", cases = { {							args = {{"a", "b", "c"}, 1, 2}, expect = {"a", "b"}, },						{							args = {{"a", "b", "c"}, 2, nil}, expect = {"b", "c"}, },					}				},				sortBy = { params = {"array", "iteratees"}, _params = {{"iteratees"}, {"array"}}, returns = "An array of elements, sorted in ascending order by the results of running each element in  thru each iteratee. This method performs an unstable sort, that is, it does not guarantee that the original order of equal elements will be preserved.", cases = { {							snippet = 1, expect = { {									game = "TWW", systems = {"GCN", "Wii U"}, },								{									game = "LA", systems = {"GB", "GBC", "3DS VC"} },								{									game = "OoT", systems = {"N64", "GCN", "iQue", "Wii VC", "Wii U VC"}, },							},						},						{							desc = propertyShorthand, args = { {									{										name = "Link", age = 10, },									{										name = "Zelda", age = 10 },									{										name = "Zelda", age = 17 },									{										name = "Link", age = 17 },								},								"name", },							expect = { {									name = "Link", age = 10, },								{									name = "Link", age = 17, },								{									name = "Zelda", age = 17, },								{									name = "Zelda", age = 10, },							},						},						{							desc = "Multiple sort criteria", args = { {									{										name = "Link", age = 10, },									{										name = "Zelda", age = 10 },									{										name = "Zelda", age = 17 },									{										name = "Link", age = 17 },								},								{"name", "age"}, },							expect = { {									name = "Link", age = 10, },								{									name = "Link", age = 17, },								{									name = "Zelda", age = 10, },								{									name = "Zelda", age = 17, },							},						},					},				},				tail = { params = {"array"}, returns = "Gets all but the first element of .", cases = { {							args = , expect = {2, 3}, },						{							args = – , expect = {}, },					}				},				take = { params = {"array", "n"}, _params = {{"n"}, {"array"}}, returns = "Creates a slice of array with n elements taken from the beginning.", cases = { {							args = {{1, 2, 3}, 2}, expect = {1, 2}, },						{							args = {{1, 2, 3}, 5}, expect = {1, 2, 3}, },						{							args = {{1, 2, 3}, 0}, expect = {}, },					},				},				takeWhile = { params = {"array", "predicate"}, _params = {{"predicate"}, {"array"}}, returns = "Creates a slice of  with elements taken from the beginning. Elements are taken until   returns falsey.", cases = { {							snippet = 1, expect = {"TLoZ", "TAoL", "ALttP", "LA"}, },						{							desc = propertyShorthand, args = { {									{ game = "TLoZ", is2D = true }, { game = "TAoL", is2D = true }, { game = "ALttP", is2D = true }, { game = "LA", is2D = true }, { game = "OoT", is2D = false }, { game = "TMC", is2D = true }, },								"is2D", },							expect = { { game = "TLoZ", is2D = true }, { game = "TAoL", is2D = true }, { game = "ALttP", is2D = true }, { game = "LA", is2D = true }, },						},						{							desc = isMatchShorthand, args = { {									{ game = "TLoZ", system = "console" }, { game = "TAoL", system = "console" }, { game = "ALttP", system = "console" }, { game = "LA", system = "handheld" }, { game = "OoT", system = "console" }, },								{ system = "console" }, },							expect = { { game = "TLoZ", system = "console" }, { game = "TAoL", system = "console" }, { game = "ALttP", system = "console" }, },						},					},				},				unique = { params = {"array"}, returns = "A copy of  but without the duplicate values. Elements are deep-compared. he order of result values is determined by the order they occur in the array.", cases = { {							args = { {1, 2, 2, {foo = "bar"}, {foo = "quux"}, {foo = "bar"}}, },							expect = {1, 2, {foo = "bar"}, {foo = "quux"}}, },					},				},				uniqueBy = { params = {"array", "iteratee"}, _params = {{"iteratee"}, {"array"}}, returns = "A copy of  but without the duplicate values.   is invoked for each element in array to generate the criterion by which uniqueness is computed.", cases = { {							snippet = 1, expect = { { title = "LA", game = "LA" }, { title = "OoT", game = "OoT" }, },						},						{							desc = propertyShorthand, args = { {									{ title = "LA", game = "LA" }, { title = "OoT", game = "OoT" }, { title = "LADX", game = "LA" }, { title = "OoT3D", game = "OoT" }, { title = "LANS", game = "LA" }, },								"game" },							expect = { { title = "LA", game = "LA" }, { title = "OoT", game = "OoT" }, },						},					},				},				zip = { params = {"arrays", "padValue"}, returns = 						Creates a transposition of . That is, an array of grouped elements, the first of which contains the first elements of the given arrays, the second of which contains the second elements of the given arrays, and so on.						Should the elements of   not be of the same length, the resulting groups can be padded to the same length with  .					, cases = { {							args = , expect = { {1, "a", true}, {2, "b", false}, },						},						{							args = {{ {1, 2},								{"a", "b", "c"}, {true} }, ""},							expect = { {1, "a", true}, {2, "b", ""}, {"", "c", ""}, },						},						{							args = , expect = { {1, "a", true}, {2, "b", nil}, {nil, "c", nil}, }						}					},				},			},		},	}, }

return p