How to check if a given table is a valid sequence?

Viewed 133
local seq = { 1, 2, 3, 4, 5 } -- obviously, it's a sequence.
local non_seq = { 1, 2, 3, nil, 5 } -- but it's not.

The defination about sequence: The Length Operator

This question is in chapter 5 of Programming in Lua 4st.

Any comments I would greatly appreciate

3 Answers

There might be a better way, but the first thing I could come up with would look something like this:

local function sequence(tab)
   local border
   for index in ipairs(tab) do
      border = index
   end
   for index in pairs(tab) do
      if type(index)=="number" and index > border then
         return false
      end
   end
end

The problem is that you need to iterate over the table twice; the first time up to the first border, then either until the first integer key larger than the first border, or through the entire table if it is indeed a sequence.


As @Nifim pointed out, you can also implement the definition directly. A slightly improved version of their answer:

local function natural(value)
   return type(value)=="number" and value >= 1 and value % 1 == 0
end
local function sequence(tab)
   -- 0 can be a border even when it's nil
   if tab[1] == nil then
      for index in pairs(tab) do
         if natural(index) then
            return false
         end
      end
   else
      local border = false
      for index in pairs(tab) do
         if natural(index) and tab[index+1]==nil then
            if border then
               return false
            else
               border = true
            end
         end
      end
   end
   return true
end

You linked the information you need for your answer.

A border in Lua 5.3 is defined as:

(border == 0 or t[border] ~= nil) and t[border + 1] == nil

A proper sequence can only contain one border. However to cover some other condition the code does require a bit more leg work, such as validating the index can be in a sequence.

function is_sequence(t)
    local borders = 0

    if t[1] ~= nil then -- all sequences must start at 1.
        for index in pairs(t) do
            if natural_index(index) and t[index + 1] == nil then
                borders = borders + 1
                if borders > 1 then
                  break
                end
            end
        end
    end

    return borders == 1 or valid_no_borders(t) and borders == 0
end

function natural_index(index)
    return type(index) == "number" and index > 0 and math.floor(index) == index
end

function valid_no_borders(t)
    result = true
    for k in pairs(t) do
      if natural_index(k) then
          result = false
          break
      end
    end
    
    return t[1] == nil and result
end
-- Tests
local seqs = {
  { 1, 2, 3, 4, 5 }, -- obviously, it's a sequence.
  { 1, 2, 3, 4, ["potato"] = 5 }, 
  { 1, 2, [3.3] = 3 },
  { [2.2] = 2 },
  { [-1] = -1, [0] = 0, 1, 2, 3 }, 
  {},
}
for _, v in ipairs(seqs) do
    print("seq: ", is_sequence(v))
end

local non_seqs ={
    { 1, 2, 3, nil, 5 }, -- but it's not.
    { [2] = 2 },
}

for _, v in ipairs(non_seqs) do
    print("non_seq: ", is_sequence(v))
end

Results

seq:    true
seq:    true
seq:    true
seq:    true
seq:    true
seq:    true
non_seq:    false
non_seq:    false

This method has the benefit of only evaluating each element once and exiting as early as possible if the table is not a valid sequence.


All you need to do is check if the array part of the table has any holes.

local function isSequence(t)
  for k in pairs(t) do
    -- Is k an integer greater than 1 whose previous value is nil?
    if
      type(k) == 'number' and k == math.floor(k) and k > 1 and t[k - 1] == nil
    then
      return false
    end
  end
  return true
end

If you detect an integer key greater than 1 whose previous value is nil, then the table must have at least 2 borders, making it an invalid sequence. One is somewhere between 0 and that key, and the other is somewhere after that key.

Related