Module:TableTools: Difference between revisions

From NvWiki
Jump to navigation Jump to search
Created page with "------------------------------------------------------------------------------------ -- TableTools -- -- -- -- This module includes a number of functions for dealing with Lua tables. -- -- It is a meta-module, meant to be called from other Lua modules, and should not -- -- be called directly from #invoke...."
Undid revision 1317119166 by 70.62.87.34 (talk) nothing to do with improving this module
Line 1: Line 1:
------------------------------------------------------------------------------------
{{Permanently protected}}
--                                  TableTools                                  --
{{oldtfdfull|date= 2018 May 13 |result=Merge |disc=Module:Table}}
--                                                                                --
-- This module includes a number of functions for dealing with Lua tables.        --
-- It is a meta-module, meant to be called from other Lua modules, and should not --
-- be called directly from #invoke.                                              --
------------------------------------------------------------------------------------


local libraryUtil = require('libraryUtil')
== removeDuplicate does not remove duplicate NaN ==
 
<syntaxhighlight lang="lua">
local p = {}
function p.removeDuplicates(t)
 
checkType('removeDuplicates', 1, t, 'table')
-- Define often-used variables and functions.
local floor = math.floor
local infinity = math.huge
local checkType = libraryUtil.checkType
local checkTypeMulti = libraryUtil.checkTypeMulti
 
------------------------------------------------------------------------------------
-- isPositiveInteger
--
-- This function returns true if the given value is a positive integer, and false
-- if not. Although it doesn't operate on tables, it is included here as it is
-- useful for determining whether a given table key is in the array part or the
-- hash part of a table.
------------------------------------------------------------------------------------
function p.isPositiveInteger(v)
return type(v) == 'number' and v >= 1 and floor(v) == v and v < infinity
end
 
------------------------------------------------------------------------------------
-- isNan
--
-- This function returns true if the given number is a NaN value, and false if
-- not. Although it doesn't operate on tables, it is included here as it is useful
-- for determining whether a value can be a valid table key. Lua will generate an
-- error if a NaN is used as a table key.
------------------------------------------------------------------------------------
function p.isNan(v)
return type(v) == 'number' and v ~= v
end
 
------------------------------------------------------------------------------------
-- shallowClone
--
-- This returns a clone of a table. The value returned is a new table, but all
-- subtables and functions are shared. Metamethods are respected, but the returned
-- table will have no metatable of its own.
------------------------------------------------------------------------------------
function p.shallowClone(t)
checkType('shallowClone', 1, t, 'table')
local ret = {}
for k, v in pairs(t) do
ret[k] = v
end
return ret
end
 
------------------------------------------------------------------------------------
-- removeDuplicates
--
-- This removes duplicate values from an array. Non-positive-integer keys are
-- ignored. The earliest value is kept, and all subsequent duplicate values are
-- removed, but otherwise the array order is unchanged.
------------------------------------------------------------------------------------
function p.removeDuplicates(arr)
checkType('removeDuplicates', 1, arr, 'table')
local isNan = p.isNan
local isNan = p.isNan
local ret, exists = {}, {}
local ret, exists = {}, {}
for _, v in ipairs(arr) do
for i, v in ipairs(t) do
if isNan(v) then
if isNan(v) then
-- NaNs can't be table keys, and they are also unique, so we don't need to check existence.
-- NaNs can't be table keys, and they are also unique, so we don't need to check existence.
ret[#ret + 1] = v
ret[#ret + 1] = v
elseif not exists[v] then
else
ret[#ret + 1] = v
if not exists[v] then
exists[v] = true
ret[#ret + 1] = v
end
exists[v] = true
end
end
end
end
return ret
return ret
end
end
 
</syntaxhighlight>
------------------------------------------------------------------------------------
This should be:
-- numKeys
<syntaxhighlight lang="lua">
--
function p.removeDuplicates(t, uniqueNan)
-- This takes a table and returns an array containing the numbers of any numerical
checkType('removeDuplicates', 1, t, 'table')
-- keys that have non-nil values, sorted in numerical order.
local ret, isNan, hasNan, exists = {}, p.isNan, false, {}
------------------------------------------------------------------------------------
for _, v in ipairs(t) do
function p.numKeys(t)
-- NaNs can't be table keys in exists[], and they are also equal to each other in Lua.
checkType('numKeys', 1, t, 'table')
if isNan(v) then
local isPositiveInteger = p.isPositiveInteger
-- But we may want only one Nan in ret[], and there may be multiple Nan's in t[].
local nums = {}
if uniqueNan == nil or uniqueNan == false or uniqueNan == true and not hasNan then
for k in pairs(t) do
hasNan = true
if isPositiveInteger(k) then
ret[#ret + 1] = v
nums[#nums + 1] = k
end
end
else
if not exists[v] then
exists[v] = true
ret[#ret + 1] = v
end
end
end
end
table.sort(nums)
return ret
return nums
end
end
</syntaxhighlight>
-- [[User:Verdy p|verdy_p]] ([[User talk:Verdy p|talk]]) 07:50, 2 February 2014 (UTC)
:<span class="template-ping">@[[:User:Verdy p|Verdy p]]:</span> This was by design, as comparing two NaNs always results in <code>false</code>. My reasoning was that since two NaNs can never be equal to each other - even if they were made by the exact same calculation - then they shouldn't be treated as duplicates by the algorithm. Although if there's some sort of precedent for doing things a different way, please let me know. I'm fairly new to the world of NaNs, after all. — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr. Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪ talk ♪]]</sup> 08:01, 2 February 2014 (UTC)
:: That's the Lua interpretation anyway. Even if it has a single Nan value (no distinction between signaling and non-signaling ones, or Nan's carrying an integer type, like in IEEE binary 32-bit float and 64-bit double formats, neither does Java...), there are some apps that depend on using Nan as a distinctive key equal to itself, but still different from nil.
:: The other kind of usage of Nan is "value not set, ignore it": when computing averages for example, Nan must not be summed and not counted, so all Nan's should be removed from the table. For this case May be there should be an option to either preserve all Nan's, or nuke them all from the result: the kill option would be tested in the if-branch of your first version, and a second alternate option tested after it would be to make Nan's unique in the result.... The first case being quite common for statistics when it means "unset", while nil means something else (such as compute this value before determinig if it's a Nan, nil bring used also for weak references that can be retreived from another slow data store, and the table storing nil being a fast cache of that slow data store) [[User:Verdy p|verdy_p]] ([[User talk:Verdy p|talk]]) 08:29, 2 February 2014 (UTC)
:::I had a quick look at the functions, but cannot work out the intended usage—that usage would probably determine what should happen with NaNs. The docs for <code>removeDuplicates</code> says that keys that are not positive integers are ignored—but what it means is that such keys are ''removed''. On that principle, I think it would be better to default to removing NaNs. I cannot imagine a usage example where I would want to call this function and have a NaN in the result—what would I do with it? If it were desirable to test for the presence of NaN members, why not return an extra value that is true if one or more NaNs are encountered and omitted? How would it help to ever have both <code>0/0</code> and <code>-0/0</code> (or multiple instances of <code>0/0</code>) in the result?
:::Regarding verdy_p's code: I don't think it is a good idea to deviate from Lua's idioms, and if there were a <code>uniqueNan</code> parameter, it should not be tested for explicit "false" and "true" values. The function regards "hello" as neither false nor true and while that is a very defensible position, it's not how Lua code is supposed to work. [[User:Johnuniq|Johnuniq]] ([[User talk:Johnuniq|talk]]) 03:26, 3 February 2014 (UTC)
:: It takes three values (it is a "tri-state boolean"):
::* nil the default is your existing code that keeps them all,
::* true keeps a single Nan,
::* false does not even keep this one
::* any other (unsupported) value in other types is considered like nil.
:: There's no such "Lua idiom" (about NaN) except that the default value is nil (already a common Lua practive), and otherwise the parameter is used as a boolean flag whose value is meant by its explicit name (and is a general good practive for naming boolean flags)... Tri-state boooleans are very common objects in many situations where we need a supplementary "may be / unknown / don't know / other / not specified / not available / missing data / to be determined later / uninitialized / refuse to say / no opinion / fuzzy" value. This fuzzy concept is also carried by Nan (within the field of numbers instead of booleans) so it is really appropriate here !
:: There's also no such ideom of "NaN" in Mediawiki parameters. Given that Lua already states that multiple NaN values compare effectively as "equal", it makes no sense to keep these duplicates which occur occasionally as result of computed numeric expressions (numbers in Lua never distinguish any NaN, neither does MediaWiki). You "idiom" is that used in Java or C++, which is NOT directly usd in MadiaWiki or Lua (hidden in the underlying internal representation). Keeping these duplicates should complicates situation where we expect "keys" in Lua tables to be unique. Not doing that means that we'll not properly detect overrides, and it will be impossible to map any action of value to NaN, meaning that we'll always get unspecified results that we can never catch in MediaWiki templates (this means undetected errors and unspecified behavior, wrong results in all cases). Lua treats all NaN as unique values which compare equal between each other, but different from all other non-NaN values (including "nil"/unmapped). Note that "nil" in (which matches unspecified MediaWiki values) is NOT "NaN" and is also NOT an "empty string" value (which is only one of the possible default values for unspecified but cannot match any "NaN"). "NaN" in Mediawiki is a string, and does not match the Lua NaN numeric value.
:: It's only when we start speaking about fuzzy logic with measured accuracy, that we introduce many other values by representing fuzzy booleans by a real number between 0.0 and 1.0, but then even this accuracy may be sometimes not measurable and will also needs an additional Nan value, so fuzzy booleans have infinite number of values between 0.0 and 1.0 plus Nan; (0.0 representing absolutely false, 0.5 representing false or true equally, and 1.0 representing absolutely true, the rule being that if you sum the response rates, you'll get always 1.0). [User:Verdy p|verdy_p]] ([[User talk:Verdy p|talk]])
:: In summary the statement "-- NaNs can't be table keys, and they are also unique, so we don't need to check existence." is compeltely false: not checking this existence means that the line "ret[#ret + 1] = v" will be executed multiple times (each time "v" is NaN) so "ret[]" will contain multiple "NaN" values... And this is not expected.
:: Note you may want to have "uniqueNan" set to true by default (the code above keeps the existing default behavior when the optional "uniqueNan" parameter is not specified, i.e. it keeps these duplicates).
:: If you drop that optional parameter, by forcing the function to behave like if it was always true, all that is needed is to change the line "if uniqueNan == nil or uniqueNan == false or uniqueNan == true and not hasNan then" to "if not hasNan then"... I.e. you just need to check and set "hasNan = true" before adding to "ret[]". [[User:Verdy p|verdy_p]] ([[User talk:Verdy p|talk]]) 20:40, 5 August 2018 (UTC)
::: I don't know what the "correct" behavior for removeDuplicates should be when passed a NaN, either keeping them all or keeping only one. I do know that the following statements are incorrect or irrelevant:
:::* {{tq|there are some apps that depend on using Nan as a distinctive key equal to itself, but still different from nil}} – Whether such apps exist or not, in Lua neither NaN nor nil can be a ''key'' in a table. Only a value.
:::* {{tq|The other kind of usage of Nan is "value not set, ignore it": when computing averages for example [...] while nil means something else}} – That seems a rather idiosyncratic use, and not relevant to a generic "removeDuplicates" method. Particularly since for calculating averages you wouldn't want to remove duplicates in the first place.
:::* {{tq|For this case May be there should be an option to either preserve all Nan's, or nuke them all from the result}} – Removing all NaNs would be better done by a "removeNaNs" method rather than a weird flag on "removeDuplicates".
:::* {{tq|Given that Lua already states that multiple NaN values compare effectively as "equal"}} – That's incorrect. Lua treats all NaN values as ''unequal''.
:::* {{tq|numbers in Lua never distinguish any NaN}} – I have no idea what you're trying to say here. Lua does have NaNs, for example as the result of calculating "0/0".
:::* {{tq|You "idiom" is that used in Java or C++, which is NOT directly usd in MadiaWiki or Lua (hidden in the underlying internal representation).}} – MediaWiki and Lua both use IEEE NaNs, although neither does anything special with the various types of NaN (quiet versus signaling and so on). Lua is implemented in C and uses its floating point types, including NaNs, while MediaWiki is written in PHP which is written in C (or C++ when using HHVM) and again uses C's floating point types.
:::* {{tq|Keeping these duplicates should complicates situation where we expect "keys" in Lua tables to be unique.}} — Since Lua tables cannot have NaN as a key, this seems irrelevant.
:::* {{tq|Lua treats all NaN as unique values which compare equal between each other}} – Again, this is wrong.
:::* {{tq|Note that "nil" in (which matches unspecified MediaWiki values) is NOT "NaN" and is also NOT an "empty string" value}} – I don't see where anyone else ever said it was.
:::* {{tq|"NaN" in Mediawiki is a string, and does not match the Lua NaN numeric value.}} – If you pass <code>NaN</code> as a template parameter, then yes, it will come into Lua as a string and will not match an actual Lua NaN. That doesn't seem at all relevant to this removeDuplicates method though.
:::* {{tq|It's only when we start speaking about fuzzy logic with measured accuracy}} – You seem to be going of on some completely unrelated tangent here.
:::* {{tq|In summary the statement "-- NaNs can't be table keys, and they are also unique, so we don't need to check existence." is compeltely false}} – On the contrary, it's largely true. In Lua NaN cannot be a table key. Since <code>nan ~= nan</code> is always true, arguably every NaN is "unique".
::: HTH. [[User:Anomie|Anomie]][[User talk:Anomie|⚔]] 23:11, 5 August 2018 (UTC)
== Template-protected edit request on 4 March 2019 ==
{{edit template-protected|Module:TableTools|answered=yes}}
Please add function ipairsAt()/ipairsAtOffset() as demonstrated {{diff|
Module:TableTools/sandbox|diff=886292279|oldid=884152164|label=here}}. [[User:Ans|Ans]] ([[User talk:Ans|talk]]) 10:23, 4 March 2019 (UTC)
: {{ping|Ans}} What is the usecase for this function? [[User:Pppery|&#123;&#123;3x&#124;p&#125;&#125;ery]] ([[User talk:Pppery|talk]])  15:08, 4 March 2019 (UTC)
:: I plan to use in function p.call() in [[Module:LuaCall]] --[[User:Ans|Ans]] ([[User talk:Ans|talk]]) 10:40, 5 March 2019 (UTC)
::: Those are both so simple you could just do it inline. Also, I really hope you're not planning on actually using [[Module:LuaCall]] somewhere outside of maybe your user space, per [[Module talk:LuaCall#I sincerely hope that no one ever actually uses this]]. [[User:Anomie|Anomie]][[User talk:Anomie|⚔]] 13:35, 5 March 2019 (UTC)
*{{notdone}} per above. — [[User:Xaosflux|<span style="color:#FF9933; font-weight:bold; font-family:monotype;">xaosflux</span>]] <sup>[[User talk:Xaosflux|<span style="color:#009933;">Talk</span>]]</sup> 15:00, 5 March 2019 (UTC)
::: It is not so simple, as the user need to understand the mechanism of ipairs() and iterator to do it inline.  The proposed function could help users who want to iterate like this, but don't know how to do it inline.  Moreover, using ipairsAt() instead of inline will improve code readability.  It does not just help developers write code, but also help beginners to understand what the code do when reading the code that use this function rather than inline code.
::: [[Module:LuaCall]] could be used to help write debugging code instantaneously in template.
::: --[[User:Ans|Ans]] ([[User talk:Ans|talk]]) 05:00, 7 March 2019 (UTC)
*{{not done}} {{tq|Edit requests to fully protected pages should only be used for edits that are either uncontroversial or supported by consensus.}} As this has been objected to already consider you are already at stage 3 of [[WP:BRD]]. — [[User:Xaosflux|<span style="color:#FF9933; font-weight:bold; font-family:monotype;">xaosflux</span>]] <sup>[[User talk:Xaosflux|<span style="color:#009933;">Talk</span>]]</sup> 02:51, 8 March 2019 (UTC)
:If after some period (ex. 7 days), no one has responsed or objected to my last reasons, does it be considered to be uncontroversial? --[[User:Ans|Ans]] ([[User talk:Ans|talk]]) 04:40, 12 March 2019 (UTC)


------------------------------------------------------------------------------------
== Template-protected edit request on 8 March 2019 ==
-- affixNums
--
-- This takes a table and returns an array containing the numbers of keys with the
-- specified prefix and suffix. For example, for the table
-- {a1 = 'foo', a3 = 'bar', a6 = 'baz'} and the prefix "a", affixNums will return
-- {1, 3, 6}.
------------------------------------------------------------------------------------
function p.affixNums(t, prefix, suffix)
checkType('affixNums', 1, t, 'table')
checkType('affixNums', 2, prefix, 'string', true)
checkType('affixNums', 3, suffix, 'string', true)


local function cleanPattern(s)
{{edit template-protected|Module:TableTools|answered=yes}}
-- Cleans a pattern so that the magic characters ()%.[]*+-?^$ are interpreted literally.
Please change function p.length() to reduce loops count by one, as demonstrated {{diff|
return s:gsub('([%(%)%%%.%[%]%*%+%-%?%^%$])', '%%%1')
Module:TableTools/sandbox|diff=886736778|oldid=886292279|label=here}} (also add note for #frame.args). [[User:Ans|Ans]] ([[User talk:Ans|talk]]) 05:38, 8 March 2019 (UTC)
end
:[[File:Yes check.svg|20px|link=|alt=]] '''Done'''<!-- Template:ETp --> -- <span style="text-shadow:0 0 1px #8dd">''/[[User:Alex 21|<span style="color:#008">Alex</span>]]/[[User talk:Alex 21|<sub style="color:#008">21</sub>]]''</span> 13:06, 12 March 2019 (UTC)


prefix = prefix or ''
==Delete this module==
suffix = suffix or ''
{{ping|Hhkohh}} Is it a mistake that you added a tag saying this module is going to be deleted? I can't see that from the TfD. [[User:Christian75|Christian75]] ([[User talk:Christian75|talk]]) 12:37, 13 March 2019 (UTC)
prefix = cleanPattern(prefix)
:{{u|Christian75}}, not delete, just being merged per TfD outcome. [[User:Hhkohh|Hhkohh]] ([[User talk:Hhkohh|talk]]) 12:46, 13 March 2019 (UTC)
suffix = cleanPattern(suffix)
::I thought that, but the template you added says "This template is currently being deleted." [[User:Christian75|Christian75]] ([[User talk:Christian75|talk]]) 12:49, 13 March 2019 (UTC)
local pattern = '^' .. prefix .. '([1-9]%d*)' .. suffix .. '$'


local nums = {}
==Help in writing better testcases==
for k in pairs(t) do
Hello developers, I am working with [[mw:Multilingual Templates and Modules]] and to convert this module into a shared one, we need better [[mw:Module:TableTools/testcases]]. Can anyone please help? {{ping|Frietjes}}, {{ping|RexxS}} [[User:Capankajsmilyo|Capankajsmilyo]]<sup>([[User talk:Capankajsmilyo|Talk]] | [[Wikipedia:WikiProject Infoboxes/assistance|Infobox assistance]])</sup> 10:32, 22 May 2019 (UTC)
if type(k) == 'string' then
:Already done, sorry to bother. [[User:Capankajsmilyo|Capankajsmilyo]]<sup>([[User talk:Capankajsmilyo|Talk]] | [[Wikipedia:WikiProject Infoboxes/assistance|Infobox assistance]])</sup> 10:38, 22 May 2019 (UTC)
local num = mw.ustring.match(k, pattern)
if num then
nums[#nums + 1] = tonumber(num)
end
end
end
table.sort(nums)
return nums
end


------------------------------------------------------------------------------------
== Array length merger ==
-- numData
--
-- Given a table with keys like {"foo1", "bar1", "foo2", "baz2"}, returns a table
-- of subtables in the format
-- {[1] = {foo = 'text', bar = 'text'}, [2] = {foo = 'text', baz = 'text'}}.
-- Keys that don't end with an integer are stored in a subtable named "other". The
-- compress option compresses the table so that it can be iterated over with
-- ipairs.
------------------------------------------------------------------------------------
function p.numData(t, compress)
checkType('numData', 1, t, 'table')
checkType('numData', 2, compress, 'boolean', true)
local ret = {}
for k, v in pairs(t) do
local prefix, num = mw.ustring.match(tostring(k), '^([^0-9]*)([1-9][0-9]*)$')
if num then
num = tonumber(num)
local subtable = ret[num] or {}
if prefix == '' then
-- Positional parameters match the blank string; put them at the start of the subtable instead.
prefix = 1
end
subtable[prefix] = v
ret[num] = subtable
else
local subtable = ret.other or {}
subtable[k] = v
ret.other = subtable
end
end
if compress then
local other = ret.other
ret = p.compressSparseArray(ret)
ret.other = other
end
return ret
end


------------------------------------------------------------------------------------
{{ping|Pppery}} Is there any reason the merged version in the sandbox was never synced with the main template? --[[User:Trialpears|Trialpears]] ([[User talk:Trialpears|talk]]) 20:48, 31 July 2019 (UTC)
-- compressSparseArray
: Lack of confidence in my own ability to edit a module used on 8% of all pages without breaking something (Yes, I know I did edit this module in February). The code should be ready to go live. [[User:Pppery|* Pppery *]] [[User talk:Pppery|<sub style="color:#800000">it has begun...</sub>]] 21:02, 31 July 2019 (UTC)
--
::That is probably the correct response when dealing with these things... Having a second pair of eyes look at it (which won't be me with no module coding experience yet) sounds sensible. Also sorry for deleting your note tat you had merged it, my bad. --[[User:Trialpears|Trialpears]] ([[User talk:Trialpears|talk]]) 22:06, 31 July 2019 (UTC)
-- This takes an array with one or more nil values, and removes the nil values
::: {{ping|Mr. Stradivarius}} Does the merged code in [[Module:TableTools/sandbox]] ([[Special:Diff/899722883/899724164]]) look good to you? [[User:Pppery|* Pppery *]] [[User talk:Pppery|<sub style="color:#800000">it has begun...</sub>]] 01:43, 23 September 2019 (UTC)
-- while preserving the order, so that the array can be safely traversed with
:::: {{ping|Pppery}} Sorry for the late reply. The implementation is mostly good, but for empty arrays it should return 0, whereas currently it returns nil. Also, any chance you could add some test cases for it to [[Module:TableTools/testcases]]? As for naming, I would go with something different. "Binary length" sounds like it could mean the length in bytes of a table (which I know doesn't really have a meaning for Lua tables, but hey). It also isn't clear that this only works for arrays or array-like tables. Finally and most nit-pickingly, it isn't ''technically'' a [[binary search]]; [[exponential search]]es and binary searches are slightly different. How about "arrayLength" instead? The explanation of which algorithm is used would have to be relegated to the doc page, but I think this might be a clearer approach overall. Best — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr.&nbsp;Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪&nbsp;talk&nbsp;♪]]</sup> 08:53, 29 September 2019 (UTC)
-- ipairs.
::::: {{ping|Mr. Stradivarius}} OK, I've [[Special:Diff/918679523|fixed the 0 versus nil bug]], and [[Special:Diff/587868130/918677165|added some test cases]]. As to the naming, I don't like <code>arrayLength</code>, because it is not meaningfully distinguishable from the pre-existing <code>length</code> function, and two functions having names that are synonyms feels like bad coding style. [[User:Pppery|* Pppery *]] [[User talk:Pppery|<sub style="color:#800000">it has begun...</sub>]] 20:57, 29 September 2019 (UTC)
------------------------------------------------------------------------------------
:::::: {{ping|Pppery}} Ah, I had forgotten that there was also a <code>length</code> function. I agree that it is confusing to have both <code>length</code> and <code>arrayLength</code>. How about replacing <code>length</code> with <code>binaryLength</code>? It is a much more efficient algorithm for large arrays, and only marginally less efficient for very small arrays; plus it has the same requirement that no intermediate elements in the array can be nil. If any code is affected by switching from an incremental to an exponential algorithm, that code was probably broken to begin with. — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr.&nbsp;Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪&nbsp;talk&nbsp;♪]]</sup> 14:25, 30 September 2019 (UTC)
function p.compressSparseArray(t)
:::::: Also, thank you for the algorithm fix and for adding the test case. I split the test case up into four separate test cases; this should make it easier to pinpoint the error if a future edit causes a problem with the function. — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr.&nbsp;Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪&nbsp;talk&nbsp;♪]]</sup> 14:27, 30 September 2019 (UTC)
checkType('compressSparseArray', 1, t, 'table')
::::::: {{ping|Mr. Stradivarius}} I didn't go with that originally because [[Module:Array length]] has text on its documentation page saying it shouldn't be used for small arrays but yes, that suggestion does seem reasonable. [[User:Pppery|* Pppery *]] [[User talk:Pppery|<sub style="color:#800000">it has begun...</sub>]] 21:13, 30 September 2019 (UTC)
local ret = {}
::::::: Replacement of <code>length</code> with <code>binaryLength</code> done. [[User:Pppery|* Pppery *]] [[User talk:Pppery|<sub style="color:#800000">it has begun...</sub>]] 18:46, 5 October 2019 (UTC)
local nums = p.numKeys(t)
{{od}} And {{done|merged}} with main module. Let's see if anything breaks ... [[User:Pppery|* Pppery *]] [[User talk:Pppery|<sub style="color:#800000">it has begun...</sub>]] 03:41, 21 December 2019 (UTC)
for _, num in ipairs(nums) do
ret[#ret + 1] = t[num]
end
return ret
end


------------------------------------------------------------------------------------
== Potential code simplification ==
-- sparseIpairs
--
-- This is an iterator for sparse arrays. It can be used like ipairs, but can
-- handle nil values.
------------------------------------------------------------------------------------
function p.sparseIpairs(t)
checkType('sparseIpairs', 1, t, 'table')
local nums = p.numKeys(t)
local i = 0
local lim = #nums
return function ()
i = i + 1
if i <= lim then
local key = nums[i]
return key, t[key]
else
return nil, nil
end
end
end


------------------------------------------------------------------------------------
Keeping in mind that I don't actually know much about Lua and could easily be overlooking some obvious problem with this suggestion, the functions <code>isPositiveInteger()</code> and <code>isNan()</code> could be slightly simplified by removing the <code>if</code> structures and instead directly returning on the logic, since it should return <code>true</code> or <code>false</code>, as appropriate, already. I'd demonstrate exactly what I mean in the sandbox, but there are other outstanding changes there and I didn't want to introduce irrelevant changes without approval first. <span class=nowrap>「[[User:Dinoguy1000|<span style=color:#00f>ディノ</span><span style=color:#080>奴</span>]][[Special:Contributions/Dinoguy1000|<span style=color:#F90>千?!</span>]]」<sup>[[User talk:Dinoguy1000#top|☎ Dinoguy1000]]</sup></span> 02:15, 31 October 2019 (UTC)
-- size
: {{ping|Dinoguy1000}} As the user who coded the changes currently in the sandbox, feel free to demonstrate your simplification edit. [[User:Pppery|* Pppery *]] [[User talk:Pppery|<sub style="color:#800000">it has begun...</sub>]] 23:43, 4 November 2019 (UTC)
--
::[https://en.wikipedia.org/w/index.php?title=Module:TableTools/sandbox&diff=924665819&oldid=923649632 Done]. Promisingly, all [[Module talk:TableTools/testcases|testcases]] appear to be passing after this, assuming I'm understanding that page correctly. <span class=nowrap>「[[User:Dinoguy1000|<span style=color:#00f>ディノ</span><span style=color:#080>奴</span>]][[Special:Contributions/Dinoguy1000|<span style=color:#F90>千?!</span>]]」<sup>[[User talk:Dinoguy1000#top|☎ Dinoguy1000]]</sup></span> 05:38, 5 November 2019 (UTC)
-- This returns the size of a key/value pair table. It will also work on arrays,
::: {{ping|Dinoguy1000}} Looks like an improvement to me, which I will include if I stop worrying too much about editing a module with this many transclusions and actually implement the TfD I initiated in February. [[User:Pppery|* Pppery *]] [[User talk:Pppery|<sub style="color:#800000">it has begun...</sub>]] 01:59, 8 November 2019 (UTC)
-- but for arrays it is more efficient to use the # operator.
::::{{ping|Pppery}} I think a similar improvement can be made in the <code>cleanPattern()</code> subroutine of the <code>affixNums()</code> function, though I'm not sure (again, I don't know enough about Lua or Scribunto to know if there might be some gotcha there), in the event you want to look at another thing to bundle into the edit too. <span class=nowrap>「[[User:Dinoguy1000|<span style=color:#00f>ディノ</span><span style=color:#080>奴</span>]][[Special:Contributions/Dinoguy1000|<span style=color:#F90>千?!</span>]]」<sup>[[User talk:Dinoguy1000#top|☎ Dinoguy1000]]</sup></span> 03:56, 8 November 2019 (UTC)
------------------------------------------------------------------------------------
function p.size(t)
checkType('size', 1, t, 'table')
local i = 0
for _ in pairs(t) do
i = i + 1
end
return i
end


local function defaultKeySort(item1, item2)
== Protected edit request on 7 March 2020 ==
-- "number" < "string", so numbers will be sorted before strings.
local type1, type2 = type(item1), type(item2)
if type1 ~= type2 then
return type1 < type2
elseif type1 == 'table' or type1 == 'boolean' or type1 == 'function' then
return tostring(item1) < tostring(item2)
else
return item1 < item2
end
end
------------------------------------------------------------------------------------
-- keysToList
--
-- Returns an array of the keys in a table, sorted using either a default
-- comparison function or a custom keySort function.
------------------------------------------------------------------------------------
function p.keysToList(t, keySort, checked)
if not checked then
checkType('keysToList', 1, t, 'table')
checkTypeMulti('keysToList', 2, keySort, {'function', 'boolean', 'nil'})
end


local arr = {}
{{edit fully-protected|Module:TableTools|answered=yes}}
local index = 1
change -- requiring module inline so that [[Module:Exponental search]] to -- requiring module inline so that [[Module:Exponential search]]
for k in pairs(t) do
(rationale : typo in Exponental / Exponential) [[Special:Contributions/82.255.235.43|82.255.235.43]] ([[User talk:82.255.235.43|talk]]) 03:45, 7 March 2020 (UTC)
arr[index] = k
:[[File:Red information icon with gradient background.svg|20px|link=|alt=]] '''Not done for now:'''<!-- Template:EP --> Please feel free to queue this in the sandbox. This module is used on many pages and making minor typos like that one doesn't make sense as its own edit. [[User:Izno|Izno]] ([[User talk:Izno|talk]]) 03:49, 7 March 2020 (UTC)
index = index + 1
::I'm pretty late to mention this, but I fixed this and another comment, and made another minor code simplification, in [https://en.wikipedia.org/w/index.php?title=Module:TableTools/sandbox&diff=prev&oldid=952640189 this edit] to the sandbox last month. I'll leave it to more regular module editors to decide if that's significant enough to edit the live module now, though. <span class=nowrap>「[[User:Dinoguy1000|<span style=color:#00f>ディノ</span><span style=color:#080>奴</span>]][[Special:Contributions/Dinoguy1000|<span style=color:#F90>千?!</span>]]」<sup>[[User talk:Dinoguy1000#top|☎ Dinoguy1000]]</sup></span> 18:22, 19 May 2020 (UTC)
end
::: This module gets edited so rarely that the approach "wait for a substantive edit" isn't really feasible. [[User:Pppery|* Pppery *]] [[User talk:Pppery|<sub style="color:#800000">it has begun...</sub>]] 21:49, 19 May 2020 (UTC)
::::Fair enough, I've gone ahead and deployed the sandbox version. If I misunderstood your intent, feel free to [[WP:TROUT|correct me]]. =) <span class=nowrap>「[[User:Dinoguy1000|<span style=color:#00f>ディノ</span><span style=color:#080>奴</span>]][[Special:Contributions/Dinoguy1000|<span style=color:#F90>千?!</span>]]」<sup>[[User talk:Dinoguy1000#top|☎ Dinoguy1000]]</sup></span> 15:51, 23 May 2020 (UTC)
::::: You did not misunderstand me; I would have done it myself as soon as this module was no longer used on the Main Page. [[User:Pppery|* Pppery *]] [[User talk:Pppery|<sub style="color:#800000">it has begun...</sub>]] 16:08, 23 May 2020 (UTC)


if keySort ~= false then
== Bug in deepCopy ==
keySort = type(keySort) == 'function' and keySort or defaultKeySort
table.sort(arr, keySort)
end


return arr
Has anyone tried p.deepCopy which was apparently merged in from [[Module:Table]] by {{u|Pppery}} in [[Special:Diff/884153516|diff]]? The function has a bug: in function <code>_deepCopy</code>, the three occurrences of <code>deepcopy</code> should be <code>_deepCopy</code>. If the function has never been used, perhaps it should be deleted? I see the documentation mentions that it has some benefits over mw.clone, but I don't think functions should be included in this massively used module unless they are needed by established modules. Also, there has to be some kind of testcase for each function. Searching shows there are a few modules using their own deepcopy functions, but I can't find any trying to use this one. Please don't fix the bug yet because if the module is to be changed, the proposed changes should be in the sandbox for a week or so to give others a chance to tweak things. There are a couple of comment inelegancies that might be fixed if the module is updated. [[User:Johnuniq|Johnuniq]] ([[User talk:Johnuniq|talk]]) 04:02, 2 November 2020 (UTC)
end
: I have no objections to deleting the functionality. Kind of embarassing that I made a mistake like that when editing a highly-visible module. [[User:Pppery|* Pppery *]] [[User talk:Pppery|<sub style="color:#800000">it has begun...</sub>]] 04:42, 2 November 2020 (UTC)


------------------------------------------------------------------------------------
== reverseNumKeys, reverseSparseIpairs ==
-- sortedPairs
--
-- Iterates through a table, with the keys sorted using the keysToList function.
-- If there are only numerical keys, sparseIpairs is probably more efficient.
------------------------------------------------------------------------------------
function p.sortedPairs(t, keySort)
checkType('sortedPairs', 1, t, 'table')
checkType('sortedPairs', 2, keySort, 'function', true)


local arr = p.keysToList(t, keySort, true)
I have added these functions to the sandbox and test cases. [[User:Trigenibinion|Trigenibinion]] ([[User talk:Trigenibinion|talk]]) 17:32, 22 February 2021 (UTC)


local i = 0
== Shouldn't isArray be more generic? ==
return function ()
i = i + 1
local key = arr[i]
if key ~= nil then
return key, t[key]
else
return nil, nil
end
end
end


------------------------------------------------------------------------------------
{{ping|Pppery|Johnuniq|Izno}} Currently, isArray raises an error if the input is not a table, shouldn't it just return false, which is what a user would expect from an isXxx function? Furthermore, wouldn't it be better if it checked if the input was array-like? This would allow custom objects/containers to be considered array-like if they are iterable and only have numeric keys. [[User:Alexiscoutinho|Alexiscoutinho]] ([[User talk:Alexiscoutinho|talk]]) 14:23, 9 July 2021 (UTC)
-- isArray
:I agree that isArray should return true or false and should never raise an error—that would be much more in keeping with Lua's style of doing the right thing when reasonable. The checkType should be replaced with a line to return false if the input is not a table. BTW, that's an amazing implementation which would never have occurred to me. I see [[#Bug in deepCopy]] above has not been addressed yet. [[User:Johnuniq|Johnuniq]] ([[User talk:Johnuniq|talk]]) 00:13, 10 July 2021 (UTC)
--
::{{re|Johnuniq}} What do you think of this?
-- Returns true if the given value is a table and all keys are consecutive
<syntaxhighlight lang="lua">
-- integers starting at 1.
function (obj)
------------------------------------------------------------------------------------
if not pcall(pairs, obj) then
function p.isArray(v)
if type(v) ~= 'table' then
return false
return false
end
end
local i = 0
local i = 0
for _ in pairs(v) do
for _ in pairs(obj) do
i = i + 1
i = i + 1
if v[i] == nil then
if obj[i] == nil then
return false
return false
end
end
Line 310: Line 170:
return true
return true
end
end
 
</syntaxhighlight>
------------------------------------------------------------------------------------
:::Good, but I would stick with:
-- isArrayLike
<syntaxhighlight lang="lua">
--
function p.isArray(t)
-- Returns true if the given value is iterable and all keys are consecutive
if type(t) ~= 'table' then
-- integers starting at 1.
------------------------------------------------------------------------------------
function p.isArrayLike(v)
if not pcall(pairs, v) then
return false
return false
end
end
local i = 0
local i = 0
for _ in pairs(v) do
for _ in pairs(t) do
i = i + 1
i = i + 1
if v[i] == nil then
if t[i] == nil then
return false
return false
end
end
Line 330: Line 186:
return true
return true
end
end
</syntaxhighlight>
:::That's using <code>t</code> for simplicity and consistency with the rest of the module. It would not be possible for <code>pcall(pairs, obj)</code> to be successful unless <code>obj</code> is a table so whereas it is more pure to test for that, it is clearer to admit that a table is what works. I see that the function returns true if given <code>{}</code> (an empty table) and that seems desirable. [[User:Johnuniq|Johnuniq]] ([[User talk:Johnuniq|talk]]) 07:16, 10 July 2021 (UTC)
::::{{re|Johnuniq}} <code>pcall(pairs, obj)</code> would work if <code>obj</code> implements <code>__pairs</code>. Although <code>obj</code> would technically still be a table, it could have a custom type (by overriding <code>type</code>, which is what [[Module:Lua class]] does). Do you think it would be better to name my function something different (isArrayLike) then? The original one could have your tweak too. [[User:Alexiscoutinho|Alexiscoutinho]] ([[User talk:Alexiscoutinho|talk]]) 15:23, 10 July 2021 (UTC)
:::::Yikes, I hadn't absorbed that [[Module:Lua class]] was seriously implementing classes. The question becomes a matter of what approach would be best in the long term, given that modules are (or will be) maintained by Wikipedians who might struggle when faced with code that has no apparent starting point or path of execution (which is the consequence of fully using classes). Also, consistency is important and isArray would look out-of-place in [[Module:TableTools]] if it is the only function with <code>obj</code> as the parameter and which tests for the method it is going to use rather than the variable type. I'm highly pragmatic and would use the naive code I put above, but you have a reason for wanting the proper implementation and that's ok by me. Regarding pragmatism, the heading comment for isArray (with its mention of a table) might be confusing, but a fully correct comment would be three times longer with the new code. Regarding duplicating the function, that's ugly as I'm sure you know. I haven't done anything with this module and I'm not sure why I'm watching here, although I have commented a couple of times. In other words, I'm happy for you to do whatever is wanted. It might be best to try something in the sandbox, preferably also fixing the deepCopy bug I mentioned, then update the module. [[User:Johnuniq|Johnuniq]] ([[User talk:Johnuniq|talk]]) 02:21, 11 July 2021 (UTC)
::::::I personally think that learning to maintain class based modules is just as easy as function based ones. For more complex modules, I think OOP greatly improves readability/understanding for anyone for many reasons, e.g. variables wouldn't have to be carried everywhere in function signatures. Of course, simpler modules might not need classes. It's just a matter of using it where it's appropriate. I agree it wouldn't be symmetrical if <code>isArray</code> received <code>obj</code>, thus I will propose <code>isArrayLike</code> instead. Since these functions are so small, I don't think a small duplication would be bad. The two functions would have their own different use cases too. [[User:Alexiscoutinho|Alexiscoutinho]] ([[User talk:Alexiscoutinho|talk]]) 21:04, 11 July 2021 (UTC)
:::::::To stick up for functions, at least you can tell what the inputs are! I'm happy for you to edit as wanted and there doesn't appear to be anyone else with an opinion so please go for it. [[User:Johnuniq|Johnuniq]] ([[User talk:Johnuniq|talk]]) 22:32, 11 July 2021 (UTC)
@Alexiscoutinho: I checked your edit to [[Module:TableTools/sandbox]] and it looks good, thanks. You will see that I removed trailing whitespace and removed two unused functions added to the sandbox on 22 February 2021 because I don't think we should add functions without a specified use. After my minor tweaks, along with your comment changes (a very good idea to make them consistent), it is now a nightmare to compare the main module with the sandbox, but I believe the change is good.
{{#invoke:convert/tester|compare|TableTools}}
[[User:Johnuniq|Johnuniq]] ([[User talk:Johnuniq|talk]]) 05:37, 12 July 2021 (UTC)


------------------------------------------------------------------------------------
=== Protected edit request on 25 July 2021 ===
-- invert
{{edit fully-protected|Module:TableTools|answered=y}}
--
This request is to implement all the changes made to the sandbox until this time. The differences can be checked with the diff link of the above section. Summarizing changes: improved module formatting; improved isArray; added isArrayLike; fixed _deepCopy; and improved defaultKeySort. [[User:Alexiscoutinho|Alexiscoutinho]] ([[User talk:Alexiscoutinho|talk]]) 14:27, 25 July 2021 (UTC)
-- Transposes the keys and values in an array. For example, {"a", "b", "c"} ->
:Sorry this has taken so long to get to. {{done}}, on assurances this has been tested by you and also checked by Johnuniq. &mdash;&nbsp;Martin <small>([[User:MSGJ|MSGJ]]&nbsp;·&nbsp;[[User talk:MSGJ|talk]])</small> 10:42, 4 October 2021 (UTC)
-- {a = 1, b = 2, c = 3}. Duplicates are not supported (result values refer to
 
-- the index of the last duplicate) and NaN values are ignored.
== keySort documentation or examples please ==
------------------------------------------------------------------------------------
 
function p.invert(arr)
Could someone add to documentation, or point to good live examples, of the '''{{para|keySort}}''' ''function'', as used in {{slink|Module:TableTools|keysToList|nopage=true}} and {{slink|Module:TableTools|sortedPairs|nopage=true}}? Related to [[:mw:Extension:Scribunto/Lua_reference_manual#table.sort|Lua manual:table.sort]]? I need a simple non-numerical sort ;-) Thx. -[[User:DePiep|DePiep]] ([[User talk:DePiep|talk]]) 10:19, 20 November 2021 (UTC)
checkType("invert", 1, arr, "table")
:If you provide a short example of dummy data to be sorted I'll have a look. As a quick example, if <code>keys</code> is a list of values each of which might be a number or a string, the following would sort the table so numbers are sorted before strings.
local isNan = p.isNan
<syntaxhighlight lang="lua">
local map = {}
table.sort(keys, function (a, b)
for i, v in ipairs(arr) do
    if type(a) == type(b) then
if not isNan(v) then
        return a < b
map[v] = i
    elseif type(a) == 'number' then
end
        return true
end
    end
end)
</syntaxhighlight>
:[[User:Johnuniq|Johnuniq]] ([[User talk:Johnuniq|talk]]) 23:29, 20 November 2021 (UTC)
 
== Improved NaN handling ==
 
{{edit template-protected|Module:TableTools|answered=y}}
Please pull in [[Special:Diff/1048120640/1060800426|my changes from the sandbox]] that correct issues with NaN handling. I already [[Special:Diff/1060767140/prev|updated the testcases]] (I first made them fail and then fixed the issue in the sandbox). I considered further simplifying {{code|isNaN|lua}} since I believe NaNs are the only values in Lua that fail the not equal to self test but it might be possible with external code, e.g., userdata (but I believe Scribunto uses none of that). That said, anything not equal to itself probably should not be considered for removal in {{code|removeDuplicates|lua}}. In Lua, NaNs are always an issue when attempting to use arbitrary table values as table keys (unlike {{code|nil|lua}} which cannot be used as a table value or key and mostly come into play when dealing with sparse tables or arbitrary expression lists). —[[User:Uzume|Uzume]] ([[User talk:Uzume|talk]]) 19:37, 17 December 2021 (UTC)
 
{{ping|Verdy p|Johnuniq|Anomie}} It should be noted that both {{code|nil|lua}} and NaN values cannot be table keys, however, it is not an error to attempt to index a table with such values. Lua will throw errors preventing these values from being used as actual keys (i.e., {{code|rawset(tableX, nil, true)|lua}} and {{code|rawset(tableX, 0/0, true)|lua}} are guaranteed to error) but these values can be handled in index and newindex events (via {{code|__index|lua}} and {{code|__newindex|lua}} metamethods) without issue. As an example, I [[Special:Diff/1060785316/prev|initially modified]] {{code|listToSet|lua}} to consider all NaN values as if they were the same (i.e., if there is at least one NaN in the input array, indexing the returned set with any NaN yeilds {{code|true|lua}}) but decided that was not particularly useful, given Lua considers them never equal to any value (including other NaNs or even themselves). If that is considered valuable we could revert to such. —[[User:Uzume|Uzume]] ([[User talk:Uzume|talk]]) 19:37, 17 December 2021 (UTC)
:Thanks but the sandbox p.listToSet has a typo due to use of <code>v</code> in p.invert and <code>item</code> in p.listToSet. It's probably best to use <code>v</code> in both of them. By the way, the ping did not work because it was added in an edit of the comment. [[User:Johnuniq|Johnuniq]] ([[User talk:Johnuniq|talk]]) 00:50, 18 December 2021 (UTC)
::{{re|Johnuniq}} the mispaste was easily fixed but I cleaned up the variables across the functions anyway. Thanks. —[[User:Uzume|Uzume]] ([[User talk:Uzume|talk]]) 22:43, 30 December 2021 (UTC)
:::I'm not going to think about anything remotely tricky for at least a week. Someone else might want to action this but I'm posting to let you know that if nothing happens you might ping me in ten days. [[User:Johnuniq|Johnuniq]] ([[User talk:Johnuniq|talk]]) 23:30, 30 December 2021 (UTC)
:{{re|Uzume|Johnuniq}} is this still being worked on, or is there an agreeable version ready to go? — [[User:Xaosflux|<span style="color:#FF9933; font-weight:bold; font-family:monotype;">xaosflux</span>]] <sup>[[User talk:Xaosflux|<span style="color:#009933;">Talk</span>]]</sup> 15:29, 10 January 2022 (UTC)
::{{re|Xaosflux}} As far as I am concerned the version in the current sandbox is "ready to go". I have rectified the one issue {{U|Johnuniq}} found since I originally made the request. —[[User:Uzume|Uzume]] ([[User talk:Uzume|talk]]) 16:10, 10 January 2022 (UTC)
:::I looked at the new code. There is another problem at line 67 due to replacement of <code>t</code> with <code>arr</code> in <code>p.removeDuplicates</code> in two of the three places where <code>t</code> was used. I will have a look at what should be done but introducing <code>arr</code> when the rest of the module uses <code>t</code> is not desirable and I suspect the new <code>arr</code> should go. [[User:Johnuniq|Johnuniq]] ([[User talk:Johnuniq|talk]]) 02:47, 11 January 2022 (UTC)
::::{{re|Johnuniq|Xaosflux}} That too was easily rectified. Please find the fixes in the current sandbox. Thank you, —[[User:Uzume|Uzume]] ([[User talk:Uzume|talk]]) 20:10, 27 January 2022 (UTC)
:::::That looks good. However now we need to think about whether a module used on 4.9 million pages should be updated with some nice-to-have improvements that are very unlikely to make a practical difference. I'm happy either way but will wait in the hope that someone else makes that decision. [[User:Johnuniq|Johnuniq]] ([[User talk:Johnuniq|talk]]) 02:20, 28 January 2022 (UTC)
::::::{{re|Johnuniq}} I find it amusing you consider correcting a bug in the NaN handling "some nice-to-have improvements that are very unlikely to make a practical difference". Such is certainly true if one never has any NaN values anywhere but clearly it has (broken) NaN support so apparently someone needed that. This was the main reason for updating the testcases first (to demonstrate the brokenness and subsequently rectify such). —[[User:Uzume|Uzume]] ([[User talk:Uzume|talk]]) 13:12, 30 January 2022 (UTC)
::::::I've made the changes. Thanks for working on this and Johnuniq for checking code &mdash;&nbsp;Martin <small>([[User:MSGJ|MSGJ]]&nbsp;·&nbsp;[[User talk:MSGJ|talk]])</small> 13:10, 31 January 2022 (UTC)


return map
== Duplicate handling with "invert" ==
end


------------------------------------------------------------------------------------
I wonder if it would be useful to have something like {{code|invert|lua}} that supports duplicates by providing values that are arrays of indices from the original input array. —[[User:Uzume|Uzume]] ([[User talk:Uzume|talk]]) 19:37, 17 December 2021 (UTC)
-- listToSet
:Currently, <code>p.invert({'a','b','c','a'})</code> would give <code>{a=4,b=2,c=3}</code>. I think the proposal is that it might give <code>{a={1,4},b=2,c=3}</code>. Perhaps, but this kind of consideration might be best handled after considering a real need at Wikipedia. Another possibility would be to throw an error if a duplicate occurs, possibly controlled by a parameter saying what should happen with duplicates. I don't think we should worry about that unless a need arises. [[User:Johnuniq|Johnuniq]] ([[User talk:Johnuniq|talk]]) 01:01, 18 December 2021 (UTC)
--
::{{re|Johnuniq}} I agree about waiting for an actual need but I thought I would bring up this lack of functionality anyway. I was thinking more along the lines of: {{code|p.invert{'a','b','c','a'}|lua}}→{{code|{a{{=}}{1,4},b{{=}}{2},c{{=}}{3}<noinclude/>}|lua}} but you got the gist of my suggestion/observation.—[[User:Uzume|Uzume]] ([[User talk:Uzume|talk]]) 23:00, 30 December 2021 (UTC)
-- Creates a set from the array part of the table. Indexing the set by any of the
-- values of the array returns true. For example, {"a", "b", "c"} ->
-- {a = true, b = true, c = true}. NaN values are ignored as Lua considers them
-- never equal to any value (including other NaNs or even themselves).
------------------------------------------------------------------------------------
function p.listToSet(arr)
checkType("listToSet", 1, arr, "table")
local isNan = p.isNan
local set = {}
for _, v in ipairs(arr) do
if not isNan(v) then
set[v] = true
end
end


return set
== p.merge() function ==
end


------------------------------------------------------------------------------------
Hello, this module is missing one function that allows to merge multiple tables into one table. It would be nice if someone add it:
-- deepCopy
<syntaxhighlight lang="lua">
--
function p.merge(...)
-- Recursive deep copy function. Preserves identities of subtables.
local tables = { ... }
------------------------------------------------------------------------------------
local result = {}
local function _deepCopy(orig, includeMetatable, already_seen)
for i, t in ipairs(tables) do
if type(orig) ~= "table" then
checkType('merge', i, t, 'table')
return orig
end
for k, v in pairs(t) do
if not result[k] then
-- already_seen stores copies of tables indexed by the original table.
result[k] = v
local copy = already_seen[orig]
end
if copy ~= nil then
return copy
end
copy = {}
already_seen[orig] = copy -- memoize before any recursion, to avoid infinite loops
for orig_key, orig_value in pairs(orig) do
copy[_deepCopy(orig_key, includeMetatable, already_seen)] = _deepCopy(orig_value, includeMetatable, already_seen)
end
if includeMetatable then
local mt = getmetatable(orig)
if mt ~= nil then
setmetatable(copy, _deepCopy(mt, true, already_seen))
end
end
end
end
return copy
return result
end
end
</syntaxhighlight>


function p.deepCopy(orig, noMetatable, already_seen)
Example: <code>p.merge({1, 2, ["a"] = "b"}, {10, [3] = 3, ["a"] = "a"}, {["b"] = "test"})</code>
checkType("deepCopy", 3, already_seen, "table", true)
return _deepCopy(orig, not noMetatable, already_seen or {})
end


------------------------------------------------------------------------------------
Output: <code>{1, 2, 3, ["a"] = "b", ["b"] = "test"}</code> — [[User:Антрактидов|🎭 <span style="color: #A23B6C">Антарктидов (AKA Antraktidov)</span>]] ([[User talk:Антрактидов|talk page]] | [[Special:Contributions/Антрактидов|contribution]]) 21:41, 13 December 2023 (UTC)
-- sparseConcat
--
-- Concatenates all values in the table that are indexed by a number, in order.
-- sparseConcat{a, nil, c, d}  ="acd"
-- sparseConcat{nil, b, c, d} ="bcd"
------------------------------------------------------------------------------------
function p.sparseConcat(t, sep, i, j)
local arr = {}


local arr_i = 0
:{{re|Антрактидов}} [[Module:Set]] already implements such merging functions of arbitrary tables with its "union" functions. [[User:Alexiscoutinho|Alexis Coutinho]] ([[User talk:Alexiscoutinho|talk]]) <sup>&#91;''[[WP:PING|ping&nbsp;me]]''&#93;</sup> 23:59, 22 April 2024 (UTC)
for _, v in p.sparseIpairs(t) do
arr_i = arr_i + 1
arr[arr_i] = v
end


return table.concat(arr, sep, i, j)
== deepEquals function ==
end


------------------------------------------------------------------------------------
@[[User:Pppery|Pppery]] You may or may not be interested in a revised version of the <code>deepEquals</code> function currently at [[wikt:Module:table]], which I note this module lacks, which checks for structural equivalence between key/value pairs; arbitrary recursive nesting and tables-as-keys are both supported. It's got a few speed optimisations that make the code a little verbose in places, but parts could easily be simplified if you feel that's not necessary here. [[User:Theknightwho|Theknightwho]] ([[User talk:Theknightwho|talk]]) 11:24, 14 April 2024 (UTC)
-- length
--
-- Finds the length of an array, or of a quasi-array with keys such as "data1",
-- "data2", etc., using an exponential search algorithm. It is similar to the
-- operator #, but may return a different value when there are gaps in the array
-- portion of the table. Intended to be used on data loaded with mw.loadData. For
-- other tables, use #.
-- Note: #frame.args in frame object always be set to 0, regardless of  the number
-- of unnamed template parameters, so use this function for frame.args.
------------------------------------------------------------------------------------
function p.length(t, prefix)
-- requiring module inline so that [[Module:Exponential search]] which is
-- only needed by this one function doesn't get millions of transclusions
local expSearch = require("Module:Exponential search")
checkType('length', 1, t, 'table')
checkType('length', 2, prefix, 'string', true)
return expSearch(function (i)
local key
if prefix then
key = prefix .. tostring(i)
else
key = i
end
return t[key] ~= nil
end) or 0
end


------------------------------------------------------------------------------------
== Infinite loop in deepCopy ==
-- inArray
--
-- Returns true if searchElement is a member of the array, and false otherwise.
-- Equivalent to JavaScript array.includes(searchElement) or
-- array.includes(searchElement, fromIndex), except fromIndex is 1 indexed
------------------------------------------------------------------------------------
function p.inArray(array, searchElement, fromIndex)
checkType("inArray", 1, array, "table")
-- if searchElement is nil, error?


fromIndex = tonumber(fromIndex)
There's a bug in the <code>deepCopy</code> function which manifests with any kind of recursive table nesting. However, it's easy to fix, as you just need to cache the new copy in {{code|already_seen|lua}} before doing the {{code|pairs|lua}} loop, like this:
if fromIndex then
<syntaxhighlight lang=lua>
if (fromIndex < 0) then
if type(orig) == 'table' then
fromIndex = #array + fromIndex + 1
copy = {}
already_seen[orig] = copy
for orig_key, orig_value in pairs(orig) do
copy[_deepCopy(orig_key, includeMetatable, already_seen)] = _deepCopy(orig_value, includeMetatable, already_seen)
end
end
if fromIndex < 1 then fromIndex = 1 end
 
for _, v in ipairs({unpack(array, fromIndex)}) do
if includeMetatable then
if v == searchElement then
local mt = getmetatable(orig)
return true
if mt ~= nil then
end
local mt_copy = _deepCopy(mt, includeMetatable, already_seen)
end
setmetatable(copy, mt_copy)
else
already_seen[mt] = mt_copy
for _, v in pairs(array) do
if v == searchElement then
return true
end
end
end
end
else -- number, string, boolean, etc
copy = orig
end
end
return false
</syntaxhighlight>
end
[[User:Theknightwho|Theknightwho]] ([[User talk:Theknightwho|talk]]) 13:03, 14 April 2024 (UTC)
 
:{{re|Theknightwho}} You may want to propose such changes in the sandbox. [[User:Alexiscoutinho|Alexis Coutinho]] ([[User talk:Alexiscoutinho|talk]]) <sup>&#91;''[[WP:PING|ping&nbsp;me]]''&#93;</sup> 00:09, 23 April 2024 (UTC)
::@[[User:Alexiscoutinho|Alexiscoutinho]] I've updated <code>deepCopy</code> with a fix in the sandbox, and introduced various optimisations, but I haven't added <code>deepEquals</code> since I'm not sure whether the WP would want table metamethods to be respected when traversing over the tables. The Wiktionary version does a strict comparison (i.e. metamethods are ignored), but that's what makes sense for our needs; however, that might be unintuitive for users who want to compare frame argument tables, for instance. [[User:Theknightwho|Theknightwho]] ([[User talk:Theknightwho|talk]]) 16:05, 24 April 2024 (UTC)
 
== Edit request 23 April 2024 ==
{{Edit fully-protected|answered=yes}}
 
This request is to implement all the changes made to the sandbox until this time. The differences can be checked with [https://en.wikipedia.org/w/index.php?title=Module%3ATableTools%2Fsandbox&diff=1220306247&oldid=1068321711 this diff link].


------------------------------------------------------------------------------------
Notably, I added the <code>merge</code> and <code>extend</code> functions for arrays. Pretty sure such functions are standard in any language that has array-like objects. Therefore, I found it appropriate to make them available here since Scribunto apparently didn't port them from base Lua. [[User:Alexiscoutinho|Alexis Coutinho]] ([[User talk:Alexiscoutinho|talk]]) <sup>&#91;''[[WP:PING|ping&nbsp;me]]''&#93;</sup> 00:07, 23 April 2024 (UTC)
-- merge
--
-- Given the arrays, returns an array containing the elements of each input array
-- in sequence.
------------------------------------------------------------------------------------
function p.merge(...)
local arrays = {...}
local ret = {}
for i, arr in ipairs(arrays) do
checkType('merge', i, arr, 'table')
for _, v in ipairs(arr) do
ret[#ret + 1] = v
end
end
return ret
end


------------------------------------------------------------------------------------
:Guess I should add test cases. [[User:Alexiscoutinho|Alexis Coutinho]] ([[User talk:Alexiscoutinho|talk]]) <sup>&#91;''[[WP:PING|ping&nbsp;me]]''&#93;</sup> 18:13, 24 April 2024 (UTC)
-- extend
::@[[User:Alexiscoutinho|Alexiscoutinho]] I don't really see any benefit from throwing an error if <code>merge</code> only receives 1 argument; it's plausible that a module might want to merge an arbitrary number of arrays based on user input, and all this restriction does is push the check for 1 argument onto the calling module. It's the same reason you can call (e.g.) {{code|math.min()|lua}} or {{code|math.max()|lua}} with only 1 argument, despite that being pointless in many situations. [[User:Theknightwho|Theknightwho]] ([[User talk:Theknightwho|talk]]) 00:30, 25 April 2024 (UTC)
--
:::👍 [[User:Alexiscoutinho|Alexis Coutinho]] ([[User talk:Alexiscoutinho|talk]]) <sup>&#91;''[[WP:PING|ping&nbsp;me]]''&#93;</sup> 01:14, 25 April 2024 (UTC)
-- Extends the first array in place by appending all elements from the second
:::{{done}}. But I wonder if a check should exist to verify if the inputs form a sequence (i.e. not sparse table)... [[User:Alexiscoutinho|Alexis Coutinho]] ([[User talk:Alexiscoutinho|talk]]) <sup>&#91;''[[WP:PING|ping&nbsp;me]]''&#93;</sup> 07:09, 26 April 2024 (UTC)
-- array.
::{{done}} aswell. This edit request is ready for review (and I guess {{u|Theknightwho}}'s changes too). [[User:Alexiscoutinho|Alexis Coutinho]] ([[User talk:Alexiscoutinho|talk]]) <sup>&#91;''[[WP:PING|ping&nbsp;me]]''&#93;</sup> 08:03, 26 April 2024 (UTC)
------------------------------------------------------------------------------------
: {{done|Copied}} over to live module. [[User:Pppery|* Pppery *]] [[User talk:Pppery|<sub style="color:#800000">it has begun...</sub>]] 01:49, 27 April 2024 (UTC)
function p.extend(arr1, arr2)
::{{ty}}. [[User:Alexiscoutinho|Alexis Coutinho]] ([[User talk:Alexiscoutinho|talk]]) <sup>&#91;''[[WP:PING|ping&nbsp;me]]''&#93;</sup> 06:07, 27 April 2024 (UTC)
checkType('extend', 1, arr1, 'table')
checkType('extend', 2, arr2, 'table')


for _, v in ipairs(arr2) do
== Protected edit request on 14 August 2024 ==
arr1[#arr1 + 1] = v
end
end


return p
{{edit fully-protected|Module:TableTools|answered=yes}}
Replace inArray with the code from [[Module:Includes]], as done in the [[Special:Permalink/1240320809|sandbox]]. Functionality is equivalent to the current function, but it adds a "fromIndex" parameter to bring it up to feature parity with the javascript [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes Array.prototype.includes()] function. <span class="nowrap">--[[User:Ahecht|Ahecht]] ([[User talk:Ahecht|<b style="color:#FFF;background:#04A;display:inline-block;padding:1px;vertical-align:middle;font:bold 50%/1 sans-serif;text-align:center">TALK<br />PAGE</b>]])</span> 20:05, 14 August 2024 (UTC)
: {{done}} [[User:Pppery|* Pppery *]] [[User talk:Pppery|<sub style="color:#800000">it has begun...</sub>]] 04:19, 18 August 2024 (UTC)

Revision as of 21:18, 16 October 2025

Template:Permanently protected Template:Oldtfdfull

removeDuplicate does not remove duplicate NaN

function p.removeDuplicates(t)
	checkType('removeDuplicates', 1, t, 'table')
	local isNan = p.isNan
	local ret, exists = {}, {}
	for i, v in ipairs(t) do
		if isNan(v) then
			-- NaNs can't be table keys, and they are also unique, so we don't need to check existence.
			ret[#ret + 1] = v
		else
			if not exists[v] then
				ret[#ret + 1] = v
				exists[v] = true
			end
		end	
	end
	return ret
end

This should be:

function p.removeDuplicates(t, uniqueNan)
	checkType('removeDuplicates', 1, t, 'table')
	local ret, isNan, hasNan, exists = {}, p.isNan, false, {}
	for _, v in ipairs(t) do
		-- NaNs can't be table keys in exists[], and they are also equal to each other in Lua.
		if isNan(v) then
			-- But we may want only one Nan in ret[], and there may be multiple Nan's in t[].
			if uniqueNan == nil or uniqueNan == false or uniqueNan == true and not hasNan then
				hasNan = true
				ret[#ret + 1] = v
			end
		else
			if not exists[v] then
				exists[v] = true
				ret[#ret + 1] = v
			end
		end	
	end
	return ret
end

-- verdy_p (talk) 07:50, 2 February 2014 (UTC)

@Verdy p: This was by design, as comparing two NaNs always results in false. My reasoning was that since two NaNs can never be equal to each other - even if they were made by the exact same calculation - then they shouldn't be treated as duplicates by the algorithm. Although if there's some sort of precedent for doing things a different way, please let me know. I'm fairly new to the world of NaNs, after all. — Mr. Stradivarius ♪ talk ♪ 08:01, 2 February 2014 (UTC)
That's the Lua interpretation anyway. Even if it has a single Nan value (no distinction between signaling and non-signaling ones, or Nan's carrying an integer type, like in IEEE binary 32-bit float and 64-bit double formats, neither does Java...), there are some apps that depend on using Nan as a distinctive key equal to itself, but still different from nil.
The other kind of usage of Nan is "value not set, ignore it": when computing averages for example, Nan must not be summed and not counted, so all Nan's should be removed from the table. For this case May be there should be an option to either preserve all Nan's, or nuke them all from the result: the kill option would be tested in the if-branch of your first version, and a second alternate option tested after it would be to make Nan's unique in the result.... The first case being quite common for statistics when it means "unset", while nil means something else (such as compute this value before determinig if it's a Nan, nil bring used also for weak references that can be retreived from another slow data store, and the table storing nil being a fast cache of that slow data store) verdy_p (talk) 08:29, 2 February 2014 (UTC)
I had a quick look at the functions, but cannot work out the intended usage—that usage would probably determine what should happen with NaNs. The docs for removeDuplicates says that keys that are not positive integers are ignored—but what it means is that such keys are removed. On that principle, I think it would be better to default to removing NaNs. I cannot imagine a usage example where I would want to call this function and have a NaN in the result—what would I do with it? If it were desirable to test for the presence of NaN members, why not return an extra value that is true if one or more NaNs are encountered and omitted? How would it help to ever have both 0/0 and -0/0 (or multiple instances of 0/0) in the result?
Regarding verdy_p's code: I don't think it is a good idea to deviate from Lua's idioms, and if there were a uniqueNan parameter, it should not be tested for explicit "false" and "true" values. The function regards "hello" as neither false nor true and while that is a very defensible position, it's not how Lua code is supposed to work. Johnuniq (talk) 03:26, 3 February 2014 (UTC)
It takes three values (it is a "tri-state boolean"):
  • nil the default is your existing code that keeps them all,
  • true keeps a single Nan,
  • false does not even keep this one
  • any other (unsupported) value in other types is considered like nil.
There's no such "Lua idiom" (about NaN) except that the default value is nil (already a common Lua practive), and otherwise the parameter is used as a boolean flag whose value is meant by its explicit name (and is a general good practive for naming boolean flags)... Tri-state boooleans are very common objects in many situations where we need a supplementary "may be / unknown / don't know / other / not specified / not available / missing data / to be determined later / uninitialized / refuse to say / no opinion / fuzzy" value. This fuzzy concept is also carried by Nan (within the field of numbers instead of booleans) so it is really appropriate here !
There's also no such ideom of "NaN" in Mediawiki parameters. Given that Lua already states that multiple NaN values compare effectively as "equal", it makes no sense to keep these duplicates which occur occasionally as result of computed numeric expressions (numbers in Lua never distinguish any NaN, neither does MediaWiki). You "idiom" is that used in Java or C++, which is NOT directly usd in MadiaWiki or Lua (hidden in the underlying internal representation). Keeping these duplicates should complicates situation where we expect "keys" in Lua tables to be unique. Not doing that means that we'll not properly detect overrides, and it will be impossible to map any action of value to NaN, meaning that we'll always get unspecified results that we can never catch in MediaWiki templates (this means undetected errors and unspecified behavior, wrong results in all cases). Lua treats all NaN as unique values which compare equal between each other, but different from all other non-NaN values (including "nil"/unmapped). Note that "nil" in (which matches unspecified MediaWiki values) is NOT "NaN" and is also NOT an "empty string" value (which is only one of the possible default values for unspecified but cannot match any "NaN"). "NaN" in Mediawiki is a string, and does not match the Lua NaN numeric value.
It's only when we start speaking about fuzzy logic with measured accuracy, that we introduce many other values by representing fuzzy booleans by a real number between 0.0 and 1.0, but then even this accuracy may be sometimes not measurable and will also needs an additional Nan value, so fuzzy booleans have infinite number of values between 0.0 and 1.0 plus Nan; (0.0 representing absolutely false, 0.5 representing false or true equally, and 1.0 representing absolutely true, the rule being that if you sum the response rates, you'll get always 1.0). [User:Verdy p|verdy_p]] (talk)
In summary the statement "-- NaNs can't be table keys, and they are also unique, so we don't need to check existence." is compeltely false: not checking this existence means that the line "ret[#ret + 1] = v" will be executed multiple times (each time "v" is NaN) so "ret[]" will contain multiple "NaN" values... And this is not expected.
Note you may want to have "uniqueNan" set to true by default (the code above keeps the existing default behavior when the optional "uniqueNan" parameter is not specified, i.e. it keeps these duplicates).
If you drop that optional parameter, by forcing the function to behave like if it was always true, all that is needed is to change the line "if uniqueNan == nil or uniqueNan == false or uniqueNan == true and not hasNan then" to "if not hasNan then"... I.e. you just need to check and set "hasNan = true" before adding to "ret[]". verdy_p (talk) 20:40, 5 August 2018 (UTC)
I don't know what the "correct" behavior for removeDuplicates should be when passed a NaN, either keeping them all or keeping only one. I do know that the following statements are incorrect or irrelevant:
  • Template:Tq – Whether such apps exist or not, in Lua neither NaN nor nil can be a key in a table. Only a value.
  • Template:Tq – That seems a rather idiosyncratic use, and not relevant to a generic "removeDuplicates" method. Particularly since for calculating averages you wouldn't want to remove duplicates in the first place.
  • Template:Tq – Removing all NaNs would be better done by a "removeNaNs" method rather than a weird flag on "removeDuplicates".
  • Template:Tq – That's incorrect. Lua treats all NaN values as unequal.
  • Template:Tq – I have no idea what you're trying to say here. Lua does have NaNs, for example as the result of calculating "0/0".
  • Template:Tq – MediaWiki and Lua both use IEEE NaNs, although neither does anything special with the various types of NaN (quiet versus signaling and so on). Lua is implemented in C and uses its floating point types, including NaNs, while MediaWiki is written in PHP which is written in C (or C++ when using HHVM) and again uses C's floating point types.
  • Template:Tq — Since Lua tables cannot have NaN as a key, this seems irrelevant.
  • Template:Tq – Again, this is wrong.
  • Template:Tq – I don't see where anyone else ever said it was.
  • Template:Tq – If you pass NaN as a template parameter, then yes, it will come into Lua as a string and will not match an actual Lua NaN. That doesn't seem at all relevant to this removeDuplicates method though.
  • Template:Tq – You seem to be going of on some completely unrelated tangent here.
  • Template:Tq – On the contrary, it's largely true. In Lua NaN cannot be a table key. Since nan ~= nan is always true, arguably every NaN is "unique".
HTH. Anomie 23:11, 5 August 2018 (UTC)

Template-protected edit request on 4 March 2019

Template:Edit template-protected Please add function ipairsAt()/ipairsAtOffset() as demonstrated Template:Diff. Ans (talk) 10:23, 4 March 2019 (UTC)

Template:Ping What is the usecase for this function? {{3x|p}}ery (talk) 15:08, 4 March 2019 (UTC)
I plan to use in function p.call() in Module:LuaCall --Ans (talk) 10:40, 5 March 2019 (UTC)
Those are both so simple you could just do it inline. Also, I really hope you're not planning on actually using Module:LuaCall somewhere outside of maybe your user space, per Module talk:LuaCall#I sincerely hope that no one ever actually uses this. Anomie 13:35, 5 March 2019 (UTC)
It is not so simple, as the user need to understand the mechanism of ipairs() and iterator to do it inline. The proposed function could help users who want to iterate like this, but don't know how to do it inline. Moreover, using ipairsAt() instead of inline will improve code readability. It does not just help developers write code, but also help beginners to understand what the code do when reading the code that use this function rather than inline code.
Module:LuaCall could be used to help write debugging code instantaneously in template.
--Ans (talk) 05:00, 7 March 2019 (UTC)
If after some period (ex. 7 days), no one has responsed or objected to my last reasons, does it be considered to be uncontroversial? --Ans (talk) 04:40, 12 March 2019 (UTC)

Template-protected edit request on 8 March 2019

Template:Edit template-protected Please change function p.length() to reduce loops count by one, as demonstrated Template:Diff (also add note for #frame.args). Ans (talk) 05:38, 8 March 2019 (UTC)

File:Yes check.svg Done -- /Alex/21 13:06, 12 March 2019 (UTC)

Delete this module

Template:Ping Is it a mistake that you added a tag saying this module is going to be deleted? I can't see that from the TfD. Christian75 (talk) 12:37, 13 March 2019 (UTC)

Template:U, not delete, just being merged per TfD outcome. Hhkohh (talk) 12:46, 13 March 2019 (UTC)
I thought that, but the template you added says "This template is currently being deleted." Christian75 (talk) 12:49, 13 March 2019 (UTC)

Help in writing better testcases

Hello developers, I am working with mw:Multilingual Templates and Modules and to convert this module into a shared one, we need better mw:Module:TableTools/testcases. Can anyone please help? Template:Ping, Template:Ping Capankajsmilyo(Talk | Infobox assistance) 10:32, 22 May 2019 (UTC)

Already done, sorry to bother. Capankajsmilyo(Talk | Infobox assistance) 10:38, 22 May 2019 (UTC)

Array length merger

Template:Ping Is there any reason the merged version in the sandbox was never synced with the main template? --Trialpears (talk) 20:48, 31 July 2019 (UTC)

Lack of confidence in my own ability to edit a module used on 8% of all pages without breaking something (Yes, I know I did edit this module in February). The code should be ready to go live. * Pppery * it has begun... 21:02, 31 July 2019 (UTC)
That is probably the correct response when dealing with these things... Having a second pair of eyes look at it (which won't be me with no module coding experience yet) sounds sensible. Also sorry for deleting your note tat you had merged it, my bad. --Trialpears (talk) 22:06, 31 July 2019 (UTC)
Template:Ping Does the merged code in Module:TableTools/sandbox (Special:Diff/899722883/899724164) look good to you? * Pppery * it has begun... 01:43, 23 September 2019 (UTC)
Template:Ping Sorry for the late reply. The implementation is mostly good, but for empty arrays it should return 0, whereas currently it returns nil. Also, any chance you could add some test cases for it to Module:TableTools/testcases? As for naming, I would go with something different. "Binary length" sounds like it could mean the length in bytes of a table (which I know doesn't really have a meaning for Lua tables, but hey). It also isn't clear that this only works for arrays or array-like tables. Finally and most nit-pickingly, it isn't technically a binary search; exponential searches and binary searches are slightly different. How about "arrayLength" instead? The explanation of which algorithm is used would have to be relegated to the doc page, but I think this might be a clearer approach overall. Best — Mr. Stradivarius ♪ talk ♪ 08:53, 29 September 2019 (UTC)
Template:Ping OK, I've fixed the 0 versus nil bug, and added some test cases. As to the naming, I don't like arrayLength, because it is not meaningfully distinguishable from the pre-existing length function, and two functions having names that are synonyms feels like bad coding style. * Pppery * it has begun... 20:57, 29 September 2019 (UTC)
Template:Ping Ah, I had forgotten that there was also a length function. I agree that it is confusing to have both length and arrayLength. How about replacing length with binaryLength? It is a much more efficient algorithm for large arrays, and only marginally less efficient for very small arrays; plus it has the same requirement that no intermediate elements in the array can be nil. If any code is affected by switching from an incremental to an exponential algorithm, that code was probably broken to begin with. — Mr. Stradivarius ♪ talk ♪ 14:25, 30 September 2019 (UTC)
Also, thank you for the algorithm fix and for adding the test case. I split the test case up into four separate test cases; this should make it easier to pinpoint the error if a future edit causes a problem with the function. — Mr. Stradivarius ♪ talk ♪ 14:27, 30 September 2019 (UTC)
Template:Ping I didn't go with that originally because Module:Array length has text on its documentation page saying it shouldn't be used for small arrays but yes, that suggestion does seem reasonable. * Pppery * it has begun... 21:13, 30 September 2019 (UTC)
Replacement of length with binaryLength done. * Pppery * it has begun... 18:46, 5 October 2019 (UTC)

Template:Od And Template:Done with main module. Let's see if anything breaks ... * Pppery * it has begun... 03:41, 21 December 2019 (UTC)

Potential code simplification

Keeping in mind that I don't actually know much about Lua and could easily be overlooking some obvious problem with this suggestion, the functions isPositiveInteger() and isNan() could be slightly simplified by removing the if structures and instead directly returning on the logic, since it should return true or false, as appropriate, already. I'd demonstrate exactly what I mean in the sandbox, but there are other outstanding changes there and I didn't want to introduce irrelevant changes without approval first. ディノ千?!☎ Dinoguy1000 02:15, 31 October 2019 (UTC)

Template:Ping As the user who coded the changes currently in the sandbox, feel free to demonstrate your simplification edit. * Pppery * it has begun... 23:43, 4 November 2019 (UTC)
Done. Promisingly, all testcases appear to be passing after this, assuming I'm understanding that page correctly. ディノ千?!☎ Dinoguy1000 05:38, 5 November 2019 (UTC)
Template:Ping Looks like an improvement to me, which I will include if I stop worrying too much about editing a module with this many transclusions and actually implement the TfD I initiated in February. * Pppery * it has begun... 01:59, 8 November 2019 (UTC)
Template:Ping I think a similar improvement can be made in the cleanPattern() subroutine of the affixNums() function, though I'm not sure (again, I don't know enough about Lua or Scribunto to know if there might be some gotcha there), in the event you want to look at another thing to bundle into the edit too. ディノ千?!☎ Dinoguy1000 03:56, 8 November 2019 (UTC)

Protected edit request on 7 March 2020

Template:Edit fully-protected change -- requiring module inline so that Module:Exponental search to -- requiring module inline so that Module:Exponential search (rationale : typo in Exponental / Exponential) 82.255.235.43 (talk) 03:45, 7 March 2020 (UTC)

File:Red information icon with gradient background.svg Not done for now: Please feel free to queue this in the sandbox. This module is used on many pages and making minor typos like that one doesn't make sense as its own edit. Izno (talk) 03:49, 7 March 2020 (UTC)
I'm pretty late to mention this, but I fixed this and another comment, and made another minor code simplification, in this edit to the sandbox last month. I'll leave it to more regular module editors to decide if that's significant enough to edit the live module now, though. ディノ千?!☎ Dinoguy1000 18:22, 19 May 2020 (UTC)
This module gets edited so rarely that the approach "wait for a substantive edit" isn't really feasible. * Pppery * it has begun... 21:49, 19 May 2020 (UTC)
Fair enough, I've gone ahead and deployed the sandbox version. If I misunderstood your intent, feel free to correct me. =) ディノ千?!☎ Dinoguy1000 15:51, 23 May 2020 (UTC)
You did not misunderstand me; I would have done it myself as soon as this module was no longer used on the Main Page. * Pppery * it has begun... 16:08, 23 May 2020 (UTC)

Bug in deepCopy

Has anyone tried p.deepCopy which was apparently merged in from Module:Table by Template:U in diff? The function has a bug: in function _deepCopy, the three occurrences of deepcopy should be _deepCopy. If the function has never been used, perhaps it should be deleted? I see the documentation mentions that it has some benefits over mw.clone, but I don't think functions should be included in this massively used module unless they are needed by established modules. Also, there has to be some kind of testcase for each function. Searching shows there are a few modules using their own deepcopy functions, but I can't find any trying to use this one. Please don't fix the bug yet because if the module is to be changed, the proposed changes should be in the sandbox for a week or so to give others a chance to tweak things. There are a couple of comment inelegancies that might be fixed if the module is updated. Johnuniq (talk) 04:02, 2 November 2020 (UTC)

I have no objections to deleting the functionality. Kind of embarassing that I made a mistake like that when editing a highly-visible module. * Pppery * it has begun... 04:42, 2 November 2020 (UTC)

reverseNumKeys, reverseSparseIpairs

I have added these functions to the sandbox and test cases. Trigenibinion (talk) 17:32, 22 February 2021 (UTC)

Shouldn't isArray be more generic?

Template:Ping Currently, isArray raises an error if the input is not a table, shouldn't it just return false, which is what a user would expect from an isXxx function? Furthermore, wouldn't it be better if it checked if the input was array-like? This would allow custom objects/containers to be considered array-like if they are iterable and only have numeric keys. Alexiscoutinho (talk) 14:23, 9 July 2021 (UTC)

I agree that isArray should return true or false and should never raise an error—that would be much more in keeping with Lua's style of doing the right thing when reasonable. The checkType should be replaced with a line to return false if the input is not a table. BTW, that's an amazing implementation which would never have occurred to me. I see #Bug in deepCopy above has not been addressed yet. Johnuniq (talk) 00:13, 10 July 2021 (UTC)
Template:Re What do you think of this?
function (obj)
	if not pcall(pairs, obj) then
		return false
	end
	local i = 0
	for _ in pairs(obj) do
		i = i + 1
		if obj[i] == nil then
			return false
		end
	end
	return true
end
Good, but I would stick with:
function p.isArray(t)
	if type(t) ~= 'table' then
		return false
	end
	local i = 0
	for _ in pairs(t) do
		i = i + 1
		if t[i] == nil then
			return false
		end
	end
	return true
end
That's using t for simplicity and consistency with the rest of the module. It would not be possible for pcall(pairs, obj) to be successful unless obj is a table so whereas it is more pure to test for that, it is clearer to admit that a table is what works. I see that the function returns true if given {} (an empty table) and that seems desirable. Johnuniq (talk) 07:16, 10 July 2021 (UTC)
Template:Re pcall(pairs, obj) would work if obj implements __pairs. Although obj would technically still be a table, it could have a custom type (by overriding type, which is what Module:Lua class does). Do you think it would be better to name my function something different (isArrayLike) then? The original one could have your tweak too. Alexiscoutinho (talk) 15:23, 10 July 2021 (UTC)
Yikes, I hadn't absorbed that Module:Lua class was seriously implementing classes. The question becomes a matter of what approach would be best in the long term, given that modules are (or will be) maintained by Wikipedians who might struggle when faced with code that has no apparent starting point or path of execution (which is the consequence of fully using classes). Also, consistency is important and isArray would look out-of-place in Module:TableTools if it is the only function with obj as the parameter and which tests for the method it is going to use rather than the variable type. I'm highly pragmatic and would use the naive code I put above, but you have a reason for wanting the proper implementation and that's ok by me. Regarding pragmatism, the heading comment for isArray (with its mention of a table) might be confusing, but a fully correct comment would be three times longer with the new code. Regarding duplicating the function, that's ugly as I'm sure you know. I haven't done anything with this module and I'm not sure why I'm watching here, although I have commented a couple of times. In other words, I'm happy for you to do whatever is wanted. It might be best to try something in the sandbox, preferably also fixing the deepCopy bug I mentioned, then update the module. Johnuniq (talk) 02:21, 11 July 2021 (UTC)
I personally think that learning to maintain class based modules is just as easy as function based ones. For more complex modules, I think OOP greatly improves readability/understanding for anyone for many reasons, e.g. variables wouldn't have to be carried everywhere in function signatures. Of course, simpler modules might not need classes. It's just a matter of using it where it's appropriate. I agree it wouldn't be symmetrical if isArray received obj, thus I will propose isArrayLike instead. Since these functions are so small, I don't think a small duplication would be bad. The two functions would have their own different use cases too. Alexiscoutinho (talk) 21:04, 11 July 2021 (UTC)
To stick up for functions, at least you can tell what the inputs are! I'm happy for you to edit as wanted and there doesn't appear to be anyone else with an opinion so please go for it. Johnuniq (talk) 22:32, 11 July 2021 (UTC)

@Alexiscoutinho: I checked your edit to Module:TableTools/sandbox and it looks good, thanks. You will see that I removed trailing whitespace and removed two unused functions added to the sandbox on 22 February 2021 because I don't think we should add functions without a specified use. After my minor tweaks, along with your comment changes (a very good idea to make them consistent), it is now a nightmare to compare the main module with the sandbox, but I believe the change is good. Script error: No such module "convert/tester". Johnuniq (talk) 05:37, 12 July 2021 (UTC)

Protected edit request on 25 July 2021

Template:Edit fully-protected This request is to implement all the changes made to the sandbox until this time. The differences can be checked with the diff link of the above section. Summarizing changes: improved module formatting; improved isArray; added isArrayLike; fixed _deepCopy; and improved defaultKeySort. Alexiscoutinho (talk) 14:27, 25 July 2021 (UTC)

Sorry this has taken so long to get to. Template:Done, on assurances this has been tested by you and also checked by Johnuniq. — Martin (MSGJ · talk) 10:42, 4 October 2021 (UTC)

keySort documentation or examples please

Could someone add to documentation, or point to good live examples, of the |keySort= function, as used in Template:Slink and Template:Slink? Related to Lua manual:table.sort? I need a simple non-numerical sort ;-) Thx. -DePiep (talk) 10:19, 20 November 2021 (UTC)

If you provide a short example of dummy data to be sorted I'll have a look. As a quick example, if keys is a list of values each of which might be a number or a string, the following would sort the table so numbers are sorted before strings.
table.sort(keys, function (a, b)
    if type(a) == type(b) then
        return a < b
    elseif type(a) == 'number' then
        return true
    end
end)
Johnuniq (talk) 23:29, 20 November 2021 (UTC)

Improved NaN handling

Template:Edit template-protected Please pull in my changes from the sandbox that correct issues with NaN handling. I already updated the testcases (I first made them fail and then fixed the issue in the sandbox). I considered further simplifying Template:Code since I believe NaNs are the only values in Lua that fail the not equal to self test but it might be possible with external code, e.g., userdata (but I believe Scribunto uses none of that). That said, anything not equal to itself probably should not be considered for removal in Template:Code. In Lua, NaNs are always an issue when attempting to use arbitrary table values as table keys (unlike Template:Code which cannot be used as a table value or key and mostly come into play when dealing with sparse tables or arbitrary expression lists). —Uzume (talk) 19:37, 17 December 2021 (UTC)

Template:Ping It should be noted that both Template:Code and NaN values cannot be table keys, however, it is not an error to attempt to index a table with such values. Lua will throw errors preventing these values from being used as actual keys (i.e., Template:Code and Template:Code are guaranteed to error) but these values can be handled in index and newindex events (via Template:Code and Template:Code metamethods) without issue. As an example, I initially modified Template:Code to consider all NaN values as if they were the same (i.e., if there is at least one NaN in the input array, indexing the returned set with any NaN yeilds Template:Code) but decided that was not particularly useful, given Lua considers them never equal to any value (including other NaNs or even themselves). If that is considered valuable we could revert to such. —Uzume (talk) 19:37, 17 December 2021 (UTC)

Thanks but the sandbox p.listToSet has a typo due to use of v in p.invert and item in p.listToSet. It's probably best to use v in both of them. By the way, the ping did not work because it was added in an edit of the comment. Johnuniq (talk) 00:50, 18 December 2021 (UTC)
Template:Re the mispaste was easily fixed but I cleaned up the variables across the functions anyway. Thanks. —Uzume (talk) 22:43, 30 December 2021 (UTC)
I'm not going to think about anything remotely tricky for at least a week. Someone else might want to action this but I'm posting to let you know that if nothing happens you might ping me in ten days. Johnuniq (talk) 23:30, 30 December 2021 (UTC)
Template:Re is this still being worked on, or is there an agreeable version ready to go? — xaosflux Talk 15:29, 10 January 2022 (UTC)
Template:Re As far as I am concerned the version in the current sandbox is "ready to go". I have rectified the one issue Template:U found since I originally made the request. —Uzume (talk) 16:10, 10 January 2022 (UTC)
I looked at the new code. There is another problem at line 67 due to replacement of t with arr in p.removeDuplicates in two of the three places where t was used. I will have a look at what should be done but introducing arr when the rest of the module uses t is not desirable and I suspect the new arr should go. Johnuniq (talk) 02:47, 11 January 2022 (UTC)
Template:Re That too was easily rectified. Please find the fixes in the current sandbox. Thank you, —Uzume (talk) 20:10, 27 January 2022 (UTC)
That looks good. However now we need to think about whether a module used on 4.9 million pages should be updated with some nice-to-have improvements that are very unlikely to make a practical difference. I'm happy either way but will wait in the hope that someone else makes that decision. Johnuniq (talk) 02:20, 28 January 2022 (UTC)
Template:Re I find it amusing you consider correcting a bug in the NaN handling "some nice-to-have improvements that are very unlikely to make a practical difference". Such is certainly true if one never has any NaN values anywhere but clearly it has (broken) NaN support so apparently someone needed that. This was the main reason for updating the testcases first (to demonstrate the brokenness and subsequently rectify such). —Uzume (talk) 13:12, 30 January 2022 (UTC)
I've made the changes. Thanks for working on this and Johnuniq for checking code — Martin (MSGJ · talk) 13:10, 31 January 2022 (UTC)

Duplicate handling with "invert"

I wonder if it would be useful to have something like Template:Code that supports duplicates by providing values that are arrays of indices from the original input array. —Uzume (talk) 19:37, 17 December 2021 (UTC)

Currently, p.invert({'a','b','c','a'}) would give {a=4,b=2,c=3}. I think the proposal is that it might give {a={1,4},b=2,c=3}. Perhaps, but this kind of consideration might be best handled after considering a real need at Wikipedia. Another possibility would be to throw an error if a duplicate occurs, possibly controlled by a parameter saying what should happen with duplicates. I don't think we should worry about that unless a need arises. Johnuniq (talk) 01:01, 18 December 2021 (UTC)
Template:Re I agree about waiting for an actual need but I thought I would bring up this lack of functionality anyway. I was thinking more along the lines of: Template:CodeTemplate:Code but you got the gist of my suggestion/observation.—Uzume (talk) 23:00, 30 December 2021 (UTC)

p.merge() function

Hello, this module is missing one function that allows to merge multiple tables into one table. It would be nice if someone add it:

function p.merge(...)
	local tables = { ... }
	local result = {}
	for i, t in ipairs(tables) do
		checkType('merge', i, t, 'table')
		
		for k, v in pairs(t) do
			if not result[k] then
				result[k] = v
			end
		end
	end
	
	return result
end

Example: p.merge({1, 2, ["a"] = "b"}, {10, [3] = 3, ["a"] = "a"}, {["b"] = "test"})

Output: {1, 2, 3, ["a"] = "b", ["b"] = "test"}🎭 Антарктидов (AKA Antraktidov) (talk page | contribution) 21:41, 13 December 2023 (UTC)

Template:Re Module:Set already implements such merging functions of arbitrary tables with its "union" functions. Alexis Coutinho (talk) [ping me] 23:59, 22 April 2024 (UTC)

deepEquals function

@Pppery You may or may not be interested in a revised version of the deepEquals function currently at wikt:Module:table, which I note this module lacks, which checks for structural equivalence between key/value pairs; arbitrary recursive nesting and tables-as-keys are both supported. It's got a few speed optimisations that make the code a little verbose in places, but parts could easily be simplified if you feel that's not necessary here. Theknightwho (talk) 11:24, 14 April 2024 (UTC)

Infinite loop in deepCopy

There's a bug in the deepCopy function which manifests with any kind of recursive table nesting. However, it's easy to fix, as you just need to cache the new copy in Template:Code before doing the Template:Code loop, like this:

	if type(orig) == 'table' then
		copy = {}
		already_seen[orig] = copy
		for orig_key, orig_value in pairs(orig) do
			copy[_deepCopy(orig_key, includeMetatable, already_seen)] = _deepCopy(orig_value, includeMetatable, already_seen)
		end

		if includeMetatable then
			local mt = getmetatable(orig)
			if mt ~= nil then
				local mt_copy = _deepCopy(mt, includeMetatable, already_seen)
				setmetatable(copy, mt_copy)
				already_seen[mt] = mt_copy
			end
		end
	else -- number, string, boolean, etc
		copy = orig
	end

Theknightwho (talk) 13:03, 14 April 2024 (UTC)

Template:Re You may want to propose such changes in the sandbox. Alexis Coutinho (talk) [ping me] 00:09, 23 April 2024 (UTC)
@Alexiscoutinho I've updated deepCopy with a fix in the sandbox, and introduced various optimisations, but I haven't added deepEquals since I'm not sure whether the WP would want table metamethods to be respected when traversing over the tables. The Wiktionary version does a strict comparison (i.e. metamethods are ignored), but that's what makes sense for our needs; however, that might be unintuitive for users who want to compare frame argument tables, for instance. Theknightwho (talk) 16:05, 24 April 2024 (UTC)

Edit request 23 April 2024

Template:Edit fully-protected

This request is to implement all the changes made to the sandbox until this time. The differences can be checked with this diff link.

Notably, I added the merge and extend functions for arrays. Pretty sure such functions are standard in any language that has array-like objects. Therefore, I found it appropriate to make them available here since Scribunto apparently didn't port them from base Lua. Alexis Coutinho (talk) [ping me] 00:07, 23 April 2024 (UTC)

Guess I should add test cases. Alexis Coutinho (talk) [ping me] 18:13, 24 April 2024 (UTC)
@Alexiscoutinho I don't really see any benefit from throwing an error if merge only receives 1 argument; it's plausible that a module might want to merge an arbitrary number of arrays based on user input, and all this restriction does is push the check for 1 argument onto the calling module. It's the same reason you can call (e.g.) Template:Code or Template:Code with only 1 argument, despite that being pointless in many situations. Theknightwho (talk) 00:30, 25 April 2024 (UTC)
👍 Alexis Coutinho (talk) [ping me] 01:14, 25 April 2024 (UTC)
Template:Done. But I wonder if a check should exist to verify if the inputs form a sequence (i.e. not sparse table)... Alexis Coutinho (talk) [ping me] 07:09, 26 April 2024 (UTC)
Template:Done aswell. This edit request is ready for review (and I guess Template:U's changes too). Alexis Coutinho (talk) [ping me] 08:03, 26 April 2024 (UTC)
Template:Done over to live module. * Pppery * it has begun... 01:49, 27 April 2024 (UTC)
Template:Ty. Alexis Coutinho (talk) [ping me] 06:07, 27 April 2024 (UTC)

Protected edit request on 14 August 2024

Template:Edit fully-protected Replace inArray with the code from Module:Includes, as done in the sandbox. Functionality is equivalent to the current function, but it adds a "fromIndex" parameter to bring it up to feature parity with the javascript Array.prototype.includes() function. --Ahecht (TALK
PAGE
)
20:05, 14 August 2024 (UTC)

Template:Done * Pppery * it has begun... 04:19, 18 August 2024 (UTC)