Extended Pairs (Small module that adds some iterators)

Extended Pairs

This is a small roblox lua module that adds some iterators (or more like generators).

Atm this module has 5 iterators.

msipairs

A parallel stateless iterator, which stops at first nil.

It returns the index and the value for each given array at that index.

local arr1 = {10, 9, 8}
local arr2 = {1, 2, 3, 4}

for i, v1, v2 in l.msipairs(arr1, arr2) do -- multi shortest ipairs
    print(i , v1, v2)
end

[[ RESULT
1    10   1
2    9    2
3    8    3
]]

mlipairs

A parallel stateless iterator, which stops when every value is nil. It returns the index and the value for each given array at that index.

local arr1 = {10, 9, 8}
local arr2 = {1, 2, 3, 4}

for i, v1, v2 in l.mlipairs(arr1, arr2) do -- multi longest ipairs
    print(i , v1, v2)
end

[[ RESULT
1    10   1
2    9    2
3    8    3
4    nil  4       
]]

cpairs

A sequentially stateless iterator. It returns the index and the value.

local arr1 = {10, 9, 8}
local arr2 = {1, 2, 3, 4}
local dict = {b = 5, c = 6, d = 7}

for i, v in l.cpairs(arr1, arr, dict) do -- chained pairs
    print(i , v)
end

[[ RESULT
1    10 
2    9  
3    8  
1    1
2    2
3    3
4    4
b    5
c    6
d    7       
]] 

vpairs

A stateless iterator, very similar to ipairs but is given directly the varadic and will loop until select(‘#’, …) instead of until first nil

It returns the index and the value.

function test(...)
    for i, v in l.vpairs(...) do
        print(i, v)
    end
end

test("hello", nil, nil ,nil, 5, nil, {}, nil)

[[ RESULT
1    "hello" 
2    nil
3    nil  
4    nil
5    5
6    nil
7    {}
8    nil
]]

Code examples

local ServerStorage = game:GetService('ServerStorage')

local l = require(ServerStorage:WaitForChild("epairs"))

local arr1 = {1,2,3}

local arr2 = {1,2,3,4}

for i, v1, v2 in l.mlipairs(arr1, arr2) do -- multi longest ipairs
    print(i , v1, v2)
end

print(('-'):rep(10))

for i, v1, v2 in l.msipairs(arr1, arr2) do -- multi shortest ipairs
    print(i , v1, v2)
end

print(('-'):rep(10))

local someModel
local someOtherModel

for _, part in l.cpairs(someModel:GetChildren(), someOtherModel:GetChildren()) do
  part.Transparency = 0
end

function test(...)
    for i, v in l.vpairs(...) do
        print(i, v)
    end
end

test("hello", nil, nil ,nil, 5, nil, {}, nil)


API

The API is in the form of returnType function(argumentName:type)

Functions

function msipairs(…:table)

It returns an iterator function

function mlipairs(…:table)

It returns an iterator function

function cpairs(…:table)

It returns an iterator function

function vpairs(…:varadic)

It returns an iterator function, you pass the variadic direclty and it will loop until it full length, nil safe e.g. loops until select(‘#’, …) instead of until first nil

function bipairs(t:table)

it returns an iterator function, this function is used to iterate backwards over an array (from higest index to lowest)

individual code

Incase you just want the code for only one of the iterators or wanna take a quick look at the code.

msipairs
local function msiter(t, i)
    i += 1
    local arr = {}

    for j = 1, t.n do
        local v = t.args[j][i]
        if v == nil then
         return 
        end
        arr[j] = v
    end

    return i, unpack(arr)
end

function msipairs(...) -- multi shortest ipairs (stops at first nil)
    return msiter, {args = {...}, n = select('#', ...)}, 0
end
mlipairs
local function mliter(t, i)
    i += 1
    local arr = {}
    local flag = false

    for j = 1, t.n do
        local v = t.args[j][i]
        if v ~= nil then
         flag = true
        end
        arr[j] = v
    end

    return flag and i or nil, unpack(arr, 1, t.n)
end

function module.mlipairs(...) -- multi longest ipairs (stops when every array is nil)
    return mliter, {args = {...}, n = select('#', ...)}, 0
end
cpairs
local function cnext(t, k)
    local k1, v1 = next(t.args[t.i], k)
    if k1 ~= nil then
        return k1, v1
    elseif t.i < t.n then
        t.i += 1
        return cnext(t, nil)
    end
end

function cpairs(...) -- chained pairs
    return cnext, {args = {...}, n = select('#', ...), i = 1}, nil
end
bipairs
local function biter(t, i)
    i -= 1
    local v = t[i]
    if v ~= nil then
      return i, v
    end
end

function bipairs(t) -- backwards ipairs
    return biter, t, #t + 1
end
vpairs
local function viter(t, i)
    i += 1
    if i <= t.n then
      return i, t.args[i]
    end
end

function vpairs(...)
    return viter, {args = {...}, n = select('#', ...)}, 0
end

Changelog v1.1.0

  • Added vpairs
  • fixed bug in bipairs

Links

Feel free to ask any question! Feedback is always welcome. Also if you have any idea for an useful iterator dont hesitate to comment.

18 Likes

I actually found this really useful! Thank you!

But I would like to know if it’s possible to make a iterator to fix the issue I have on this topic?

1 Like

Quality content, that’s a very nice addition to pairs. I’ll try to find an occasion to use this module sometime in the future

1 Like

That’s not really possible. In Lua tables are associative arrays which exist out of an array part and a dictionary part (hashtable). Hashtables don’t have any particular order.

But what you can do is, keep a reverse table. A table that keeps the keys as value in an array. You use this table to index the hashtable. Which is what @WhoooDattt suggested here.

Here is something quick that i wrote which does that under the hood.

modulescript

local OrderedTable = {}

local function iter(t, k)
	t.i += 1
	local key = t.order[t.i]
	if key ~= nil then
		return key, t.values[key]
	end
end

function OrderedTable.new()
	local pr = newproxy(true)
	local mt = getmetatable(pr)
	
	local hashMap = {}
	local array = {}
	
	local function iterate()
		return iter, {i = 0, order = array, values = hashMap}
	end
	
	
	mt.__index = function(_, k)
		return k == "iterate" and iterate or hashMap[k]
	end
	
	mt.__newindex = function(_, k, v)
		if k == nil then return end -- edge case
		
		if hashMap[k] ~= nil then
			if v == nil then -- removal
				table.remove(array, table.find(array, k))
			end
		else -- insert
			table.insert(array, k)
		end
		hashMap[k] = v
	end
	
	return pr
end

return OrderedTable

example usage

local OrderedTable = require(script.Parent)

local tbl = OrderedTable.new()

tbl.a = "yes"
tbl.sf = 2
tbl.no = true
tbl.awa = "efhfdh"
tbl.cush = {"yes"}
tbl.B = 675

for k, v in tbl.iterate() do
	print(k ,v)
end
[[ -- output
  a yes  
  sf 2  
  no true 
  awa efhfdh
  cush  ▼  {
             [1] = "yes"
           }  
  B 675 
]]

tbl.no = nil
tbl.cush = nil

for k, v in tbl.iterate() do
	print(k ,v)
end
[[ -- output
   a yes 
   sf 2  
   awa efhfdh
   B 675  
]]

few notes:

  • iterate is reserved keyword for the function
  • using a double linked list would probably be better for perf, but an array was quicker to write
  • updating an existing value doesnt change its order, its solely based on its insertion
2 Likes

Thank you! This solved my problem and I will defiantly be using this! Also maybe you can add this to your post

1 Like

I’ll probably write a proper one using a double linked list, with more functionality when i find some time. And then ill post it as a seperate module. Thanks for the feedback!

1 Like