How to get the latest x entries of a table in Lua?

Viewed 118

If I have (for example) a table with 300 entries, how would I get the latest x entries only? I was thinking of doing the next, but I'm wondering if there is a better/more optimized way to do this exact thing.

local TestTable = {}

-- Populate table
for i = 1, 300, 1 do
    print('Adding: ' .. i)
    table.insert(TestTable , i)
end

-- Get latest x of table
function GetLatestFromTable(OriginalTable, Amount)
    local TableLength = #OriginalTable
    local Retval = {}

    for i = 1, Amount, 1 do
        if TableLength - i <= 0 then break end -- Dont allow to go under 0

        table.insert(Retval, OriginalTable[TableLength - i])

        print("Adding to Retval: " .. OriginalTable[TableLength - i] .. ' (Index: ' .. TableLength - i .. ')')
    end

    return Retval
end


print(#TestTable)
local LatestTable = GetLatestFromTable(TestTable, 10)
print(#LatestTable)
2 Answers

For keys in sequence (and values are string/number) a call to table.concat() allows range parameter.

local tab = {"One", "Two", "Three", "Four", "Five"}

print(table.concat(tab, '\n', #tab - 1, #tab)) -- Last two entries

See: table.concat()

As mentioned by @Luke100000, one way could be to use Lua custom iterators. In Lua, an iterator is a special function which, when called, will return the next value. It is made possible by the fact that functions are first-class citizen in Lua and they can refer to previous scope with a mecanism named closure.

To answer the question, one could start implement a general iterator over a given range.

function IterateRange (Table, Min, Max)
  
  local ClosureIndex = Min - 1
  local ClosureMax   = math.min(Max, #Table)
  
  local function Closure ()
    if (ClosureIndex < ClosureMax) then
      ClosureIndex = ClosureIndex + 1
      return Table[ClosureIndex]
    end
  end

  return Closure
end

IterateRange is a function returning an anonymous function. The anonymous function does not take any parameter. It simply update the ClosureIndex index defined in the local scope of IterateRange and return the table value.

The first thing that the anonymous function do is to increment ClosureIndex. For that reason, ClosureIndex must be initialized to Min - 1.

This function works as one might expect:

TestTable = {}

for i = 1, 300, 1 do
    print('Adding: ' .. i)
    table.insert(TestTable , i)
end

for Value in IterateRange(TestTable, 290, 300) do
  print(Value)
end

290
291
292
293
294
295
296
297
298
299
300

Now, it's trivial to reuse this general iterator to iterate over the last N entries of a given table:

function IterateLastEntries (Table, Count)
  local TableSize  = #Table
  local StartIndex = (TableSize - Count)
  return IterateRange(Table, StartIndex, TableSize)
end

It also work as one might expect:

TestTable = {}

for i = 1, 300, 1 do
    print('Adding: ' .. i)
    table.insert(TestTable , i)
end

for Value in IterateLastEntries(TestTable, 10) do
  print(Value)
end

290
291
292
293
294
295
296
297
298
299
300

And finally, to summarize all this in a fully copy & pasteable solution:

TestTable = {}

for i = 1, 300, 1 do
    print('Adding: ' .. i)
    table.insert(TestTable , i)
end

function IterateRange (Table, Min, Max)
  
  local ClosureIndex = Min - 1
  local ClosureMax   = math.min(Max, #Table)
  
  local function Closure ()
    if (ClosureIndex < ClosureMax) then
      ClosureIndex = ClosureIndex + 1
      return Table[ClosureIndex]
    end
  end

  return Closure
end

function IterateLastEntries (Table, Count)
  local TableSize  = #Table
  local StartIndex = (TableSize - Count)
  return IterateRange(Table, StartIndex, TableSize)
end

for Value in IterateLastEntries(TestTable, 10) do
  print(Value)
end

This should return:

290
291
292
293
294
295
296
297
298
299
300

I will let the OP update the code in order to achieve the same results for 30 entries.

Related