Documentation for this module may be created at Module:TableTools/doc

  1. --[[
  2. ------------------------------------------------------------------------------------
  3. -- TableTools --
  4. -- --
  5. -- This module includes a number of functions for dealing with Lua tables. --
  6. -- It is a meta-module, meant to be called from other Lua modules, and should --
  7. -- not be called directly from #invoke. --
  8. ------------------------------------------------------------------------------------
  9. --]]
  10.  
  11. local libraryUtil = require('libraryUtil')
  12.  
  13. local p = {}
  14.  
  15. -- Define often-used variables and functions.
  16. local floor = math.floor
  17. local infinity = math.huge
  18. local checkType = libraryUtil.checkType
  19.  
  20. --[[
  21. ------------------------------------------------------------------------------------
  22. -- isPositiveInteger
  23. --
  24. -- This function returns true if the given value is a positive integer, and false
  25. -- if not. Although it doesn't operate on tables, it is included here as it is
  26. -- useful for determining whether a given table key is in the array part or the
  27. -- hash part of a table.
  28. ------------------------------------------------------------------------------------
  29. --]]
  30. function p.isPositiveInteger(v)
  31. if type(v) == 'number' and v >= 1 and floor(v) == v and v < infinity then
  32. return true
  33. else
  34. return false
  35. end
  36. end
  37.  
  38. --[[
  39. ------------------------------------------------------------------------------------
  40. -- isNan
  41. --
  42. -- This function returns true if the given number is a NaN value, and false
  43. -- if not. Although it doesn't operate on tables, it is included here as it is
  44. -- useful for determining whether a value can be a valid table key. Lua will
  45. -- generate an error if a NaN is used as a table key.
  46. ------------------------------------------------------------------------------------
  47. --]]
  48. function p.isNan(v)
  49. if type(v) == 'number' and tostring(v) == '-nan' then
  50. return true
  51. else
  52. return false
  53. end
  54. end
  55.  
  56. --[[
  57. ------------------------------------------------------------------------------------
  58. -- shallowClone
  59. --
  60. -- This returns a clone of a table. The value returned is a new table, but all
  61. -- subtables and functions are shared. Metamethods are respected, but the returned
  62. -- table will have no metatable of its own.
  63. ------------------------------------------------------------------------------------
  64. --]]
  65. function p.shallowClone(t)
  66. local ret = {}
  67. for k, v in pairs(t) do
  68. ret[k] = v
  69. end
  70. return ret
  71. end
  72.  
  73. --[[
  74. ------------------------------------------------------------------------------------
  75. -- removeDuplicates
  76. --
  77. -- This removes duplicate values from an array. Non-positive-integer keys are
  78. -- ignored. The earliest value is kept, and all subsequent duplicate values are
  79. -- removed, but otherwise the array order is unchanged.
  80. ------------------------------------------------------------------------------------
  81. --]]
  82. function p.removeDuplicates(t)
  83. checkType('removeDuplicates', 1, t, 'table')
  84. local isNan = p.isNan
  85. local ret, exists = {}, {}
  86. for i, v in ipairs(t) do
  87. if isNan(v) then
  88. -- NaNs can't be table keys, and they are also unique, so we don't need to check existence.
  89. ret[#ret + 1] = v
  90. else
  91. if not exists[v] then
  92. ret[#ret + 1] = v
  93. exists[v] = true
  94. end
  95. end
  96. end
  97. return ret
  98. end
  99.  
  100. --[[
  101. ------------------------------------------------------------------------------------
  102. -- numKeys
  103. --
  104. -- This takes a table and returns an array containing the numbers of any numerical
  105. -- keys that have non-nil values, sorted in numerical order.
  106. ------------------------------------------------------------------------------------
  107. --]]
  108. function p.numKeys(t)
  109. checkType('numKeys', 1, t, 'table')
  110. local isPositiveInteger = p.isPositiveInteger
  111. local nums = {}
  112. for k, v in pairs(t) do
  113. if isPositiveInteger(k) then
  114. nums[#nums + 1] = k
  115. end
  116. end
  117. table.sort(nums)
  118. return nums
  119. end
  120.  
  121. --[[
  122. ------------------------------------------------------------------------------------
  123. -- affixNums
  124. --
  125. -- This takes a table and returns an array containing the numbers of keys with the
  126. -- specified prefix and suffix. For example, for the table
  127. -- {a1 = 'foo', a3 = 'bar', a6 = 'baz'} and the prefix "a", affixNums will
  128. -- return {1, 3, 6}.
  129. ------------------------------------------------------------------------------------
  130. --]]
  131. function p.affixNums(t, prefix, suffix)
  132. checkType('affixNums', 1, t, 'table')
  133. checkType('affixNums', 2, prefix, 'string', true)
  134. checkType('affixNums', 3, suffix, 'string', true)
  135.  
  136. local function cleanPattern(s)
  137. -- Cleans a pattern so that the magic characters ()%.[]*+-?^$ are interpreted literally.
  138. s = s:gsub('([%(%)%%%.%[%]%*%+%-%?%^%$])', '%%%1')
  139. return s
  140. end
  141.  
  142. prefix = prefix or ''
  143. suffix = suffix or ''
  144. prefix = cleanPattern(prefix)
  145. suffix = cleanPattern(suffix)
  146. local pattern = '^' .. prefix .. '([1-9]%d*)' .. suffix .. '$'
  147.  
  148. local nums = {}
  149. for k, v in pairs(t) do
  150. if type(k) == 'string' then
  151. local num = mw.ustring.match(k, pattern)
  152. if num then
  153. nums[#nums + 1] = tonumber(num)
  154. end
  155. end
  156. end
  157. table.sort(nums)
  158. return nums
  159. end
  160.  
  161. --[[
  162. ------------------------------------------------------------------------------------
  163. -- numData
  164. --
  165. -- Given a table with keys like ("foo1", "bar1", "foo2", "baz2"), returns a table
  166. -- of subtables in the format
  167. -- { [1] = {foo = 'text', bar = 'text'}, [2] = {foo = 'text', baz = 'text'} }
  168. -- Keys that don't end with an integer are stored in a subtable named "other".
  169. -- The compress option compresses the table so that it can be iterated over with
  170. -- ipairs.
  171. ------------------------------------------------------------------------------------
  172. --]]
  173. function p.numData(t, compress)
  174. checkType('numData', 1, t, 'table')
  175. checkType('numData', 2, compress, 'boolean', true)
  176. local ret = {}
  177. for k, v in pairs(t) do
  178. local prefix, num = mw.ustring.match(tostring(k), '^([^0-9]*)([1-9][0-9]*)$')
  179. if num then
  180. num = tonumber(num)
  181. local subtable = ret[num] or {}
  182. if prefix == '' then
  183. -- Positional parameters match the blank string; put them at the start of the subtable instead.
  184. prefix = 1
  185. end
  186. subtable[prefix] = v
  187. ret[num] = subtable
  188. else
  189. local subtable = ret.other or {}
  190. subtable[k] = v
  191. ret.other = subtable
  192. end
  193. end
  194. if compress then
  195. local other = ret.other
  196. ret = p.compressSparseArray(ret)
  197. ret.other = other
  198. end
  199. return ret
  200. end
  201.  
  202. --[[
  203. ------------------------------------------------------------------------------------
  204. -- compressSparseArray
  205. --
  206. -- This takes an array with one or more nil values, and removes the nil values
  207. -- while preserving the order, so that the array can be safely traversed with
  208. -- ipairs.
  209. ------------------------------------------------------------------------------------
  210. --]]
  211. function p.compressSparseArray(t)
  212. checkType('compressSparseArray', 1, t, 'table')
  213. local ret = {}
  214. local nums = p.numKeys(t)
  215. for _, num in ipairs(nums) do
  216. ret[#ret + 1] = t[num]
  217. end
  218. return ret
  219. end
  220.  
  221. --[[
  222. ------------------------------------------------------------------------------------
  223. -- sparseIpairs
  224. --
  225. -- This is an iterator for sparse arrays. It can be used like ipairs, but can
  226. -- handle nil values.
  227. ------------------------------------------------------------------------------------
  228. --]]
  229. function p.sparseIpairs(t)
  230. checkType('sparseIpairs', 1, t, 'table')
  231. local nums = p.numKeys(t)
  232. local i = 0
  233. local lim = #nums
  234. return function ()
  235. i = i + 1
  236. if i <= lim then
  237. local key = nums[i]
  238. return key, t[key]
  239. else
  240. return nil, nil
  241. end
  242. end
  243. end
  244.  
  245. --[[
  246. ------------------------------------------------------------------------------------
  247. -- size
  248. --
  249. -- This returns the size of a key/value pair table. It will also work on arrays,
  250. -- but for arrays it is more efficient to use the # operator.
  251. ------------------------------------------------------------------------------------
  252. --]]
  253. function p.size(t)
  254. checkType('size', 1, t, 'table')
  255. local i = 0
  256. for k in pairs(t) do
  257. i = i + 1
  258. end
  259. return i
  260. end
  261.  
  262. return p