9

How can I write a function that determines whether it's table argument is a true array?

isArray({1, 2, 4, 8, 16}) -> true
isArray({1, "two", 3, 4, 5}) -> true
isArray({1, [3]="two", [2]=3, 4, 5}) -> true
isArray({1, dictionaryKey = "not an array", 3, 4, 5}) -> false

I can't see any way of finding out if the numeric keys are the only keys.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Eric
  • 95,302
  • 53
  • 242
  • 374

7 Answers7

22

EDIT: Here's a new way to test for arrays that I discovered just recently. For each element returned by pairs, it simply checks that the nth item on it is not nil. As far as I know, this is the fastest and most elegant way to test for array-ness.

local function isArray(t)
  local i = 0
  for _ in pairs(t) do
    i = i + 1
    if t[i] == nil then return false end
  end
  return true
end
kikito
  • 51,734
  • 32
  • 149
  • 189
  • Yep, this is indeed much more complete than the solution I provided ! – SolarBear May 23 '11 at 18:31
  • 2
    Optimization: drop `isSequential`. Instead, if the number of positive integer keys is `n`, then you could `return n == max` – sbk Sep 23 '11 at 15:32
  • Indeed. I'm updating my solution with sbk's suggestion. I must point out that n has to be calculated manually in the loop, you can't use #array since sometimes it will be wrong: `a = {1,nil,3}; print(#a)` prints 3 instead of 1. – kikito Sep 23 '11 at 15:45
  • Regardless of whether #array is 1 or 3 in your example, it's still not equal to n (2). I think it's possible to remove the max calculation. – BMitch Sep 23 '11 at 16:08
  • It was just an example. My point was that # can't be trusted. – kikito Sep 24 '11 at 10:39
  • Shouldn't keys in tables be ordered? In that case a test for sequentially increasing key values should be sufficient; see my anser below. – radiospiel Oct 26 '12 at 23:34
  • Updated this answer with a better implementation – kikito Mar 02 '14 at 17:48
4

ipairs iterates over indices 1..n, where n+1 is the first integer index with a nil value
pairs iterates over all keys.
if there are more keys than there are sequential indices, then it cannot be an array.

So all you have to do is see if the number of elements in pairs(table) is equal to the number of elements in ipairs(table)
the code can be written as follows:

function isArray(tbl)
    local numKeys = 0
    for _, _ in pairs(tbl) do
        numKeys = numKeys+1
    end
    local numIndices = 0
    for _, _ in ipairs(tbl) do
        numIndices = numIndices+1
    end
    return numKeys == numIndices
end

I'm pretty new to Lua, so there might be some builtin function to reduce the numKeys and numIndices calculations to simple function calls.

Ponkadoodle
  • 5,777
  • 5
  • 38
  • 62
2

By "true array", I suppose you mean a table whose keys are only numbers. To do this, check the type of every key of your table. Try this :

function isArray(array)
    for k, _ in pairs(array) do
        if type(k) ~= "number" then
            return false
        end
    end
    return true --Found nothing but numbers !
end
SolarBear
  • 4,534
  • 4
  • 37
  • 53
  • **+1** I hadn't thought about it this way. Although I'd also quite like it to reject tables which contain a gap in their indices – Eric May 20 '11 at 20:30
  • It would be possible to also test for gaps in the numeric indices. The straightforward way to do that is to build a set of numeric indices, sort it, then scan it for discontinuities. There's probably a more clever and faster way to do the work with less storage and without the second pass. – RBerteig May 20 '11 at 20:41
  • 2
    Also not that 1.5 is a number, but is not an index into a "true array". For that matter, -1 is not really an array index either since it is outside the range [`1`, `#t`]. – RBerteig May 20 '11 at 20:43
1

Note: as @eric points out, pairs is not defined to iterate in a specific order. Hence this is no valid answer.

The following should be sufficient; it checks that the keys are sequential from 1 until the end:

local function isArray(array)
  local n = 1
  for k, _ in pairs(array) do
    if k ~= n then return false end
    n = n + 1
  end

  return true
end
radiospiel
  • 2,450
  • 21
  • 28
0

Here's my take on this, using #array to detect a gap or stop when too many keys have been read:

function isArray(array)
  local count=0
  for k,_ in pairs(array) do
    count=count+1
    if (type(k) ~= "number" or k < 1 or k > #array or count > #array or math.floor(k) ~= k) then 
      return false
    end
  end
  if count ~= #array then
    return false
  end
  return true
end
BMitch
  • 231,797
  • 42
  • 475
  • 450
  • 1
    You can't use the `#` operator for this because by definition its behavior is undefined when used on a table without sequential numerical indexes. This means there will be circumstances where everything lines up just right to make it give a false positive. – Arrowmaster Jul 04 '11 at 19:48
  • Hmm, now you're making me rethink the C code I wrote that was based on the same concept. I was really hoping to avoid making 3 passes over the table (2 to test if it's an array, and 1 to process the array or table). – BMitch Jul 04 '11 at 20:17
  • I've updated this for a 2 pass solution that quickly returns if it finds a gap and numbers either above or below the `#` result. I can't think of any situation where it wouldn't work, but now it looks close enough to kikito's answer that I may just delete this. – BMitch Jul 04 '11 at 20:42
  • Now your answer is starting to look a bit like kikito's. If you add an integer check, I believe you can remove the second loop and do it all with only one pass. – Arrowmaster Jul 05 '11 at 02:38
  • Like I was saying "now it looks close enough to kikito's answer that I may just delete this". I just didn't want to leave broken up as an answer. This is a translation of what I'm doing in C, and in that code I'm not actually looking at the values, yet. But an integer check to get it down to one pass may be worth the extra code. – BMitch Jul 05 '11 at 03:35
0

I wrote this code for another similar question lately:

---Checks if a table is used as an array. That is: the keys start with one and are sequential numbers
-- @param t table
-- @return nil,error string if t is not a table
-- @return true/false if t is an array/isn't an array
-- NOTE: it returns true for an empty table
function isArray(t)
    if type(t)~="table" then return nil,"Argument is not a table! It is: "..type(t) end
    --check if all the table keys are numerical and count their number
    local count=0
    for k,v in pairs(t) do
        if type(k)~="number" then return false else count=count+1 end
    end
    --all keys are numerical. now let's see if they are sequential and start with 1
    for i=1,count do
        --Hint: the VALUE might be "nil", in that case "not t[i]" isn't enough, that's why we check the type
        if not t[i] and type(t[i])~="nil" then return false end
    end
    return true
end
Community
  • 1
  • 1
AlexStack
  • 16,766
  • 21
  • 72
  • 104
  • In the second loop, you need `type(t[i])==nil`, not `~=`. Why not just `if t[i] == nil` – sbk Sep 23 '11 at 16:42
-1

Iterate from 0 to the number of elements, and check if all elements with the counter's index exist. If it's not an array, some indexes will miss in the sequence.

Gabriel
  • 1,803
  • 1
  • 13
  • 18
  • By definition, the number of elements, `#myTable`, is the number of sequential integral indeces! – Eric May 20 '11 at 20:29
  • @Eric, no `#t` is defined to be a number such that `t[#t]` is `nil`, and `t[#t-1]` is not `nil`. It is completely possible that there is still a missing index before `#t`, or additional indices after it. – RBerteig May 20 '11 at 20:38
  • @Gabriel, the convention for arrays in Lua starts at index 1, not index 0. – RBerteig May 20 '11 at 20:38
  • @RBerteig: No it isn't: `print(#{1, [3]=2})` -> `1` – Eric May 20 '11 at 20:43
  • 2
    @Eric, try `t = {1,2,[7]=7,[8]=8}; t[3]=1; print(#t)`. On my copy of Lua 5.1.4 on Windows, it prints `8`, not `2`. The definition I cited is paraphrased from [the manual](http://www.lua.org/manual/5.1/manual.html#2.5.5). – RBerteig May 20 '11 at 20:50
  • @RBerteig: I stand corrected. Interesting how `t[3]=1;` changes the length of the array... – Eric May 20 '11 at 21:08
  • @Eric, this is a subtle consequence of the implementation, and the source of a *lot* of discussion on the lua-l mailing list. Getting the guarantee that the sequence is unbroken from `1` to `#t` turns out to increase the cost of all table insert operations, and doesn't add much value to most programs. – RBerteig May 21 '11 at 01:29
  • The solution proposed here by @Gabriel will catch some tables with gaps, such as my example in the comments above. It won't catch all, such as @Eric's example. Since `ipairs` has its own quirks, you can't count on it to help, either. – RBerteig May 21 '11 at 01:32
  • 2
    @rberteig your definition is close but off by one. #t is defined to be a number such that t[#t] is not nil, and t[#t+1] is nil. The exact meaning of #t confuses me sometimes so when I saw your comment I thought I forgot again. – jhocking May 21 '11 at 11:16
  • @jhocking, you are right. I tend to remember the idiom `t[#t+1] = newvalue` to extend a list, and that is only correct if `t[#t]` is *not* `nil`. I must have been reset to C thinking by the 0 in the answer... ;-) – RBerteig May 22 '11 at 08:21