CombinedPairs - loop through several tables in one loop!

Hello everyone.
I have been working on this for the past half hour, as it got a bit annoying that I had to keep on make 3-4 loops in a lot of my functions. So, the solution for this? a custom iterator, which accept inputs of several tables to iterate through at once!

What do I mean by this?
Well, take a look at this:

local t1 = {1, 3, 5}
local t2 = {2, 4, 6}

for i1, v1, i2, v2 in CombinedPairs(t1, t2) do
    print(i1, v1, '|', i2, v2)
end

Expected output:
image
I hope you can already see the usecases for this :tongue:
:warning: When it comes to dictionaries, the order is undefined due to the nature of tables. Beware!

The code for this is (pretty) basic: it simply iterates through all the tables you input to it, returning the key | value pair of each table, and then combining the pairs of every table and returning those.

The code is really small, and as such there’s no need to upload it anywhere.
You can just grab the source code directly from here:

Click me for the source
function CombinedPairs(...)
	local Tables = {...}
	for Idx, Obj in next, Tables do
		assert(type(Obj) == 'table', 'Bad argument #' .. Idx .. ' (table expected, got ' .. typeof(Obj) .. ')')
	end
	
	local NumInput = #Tables
	
	local Idx = nil
	local Working = table.create(NumInput, true)

	return function()
		local Returns = {}
		local IsEmpty = true
		
		for TableIdx, Table in next, Tables do
			if Working[TableIdx] then
				local Worked, Val, Key = pcall(next, Table, Idx)
				
				if Worked == false and Key == nil then
					Working[TableIdx] = false
					continue
				end
				IsEmpty = false

				Returns[#Returns + 1] = Val
				Returns[#Returns + 1] = Key
			end	
		end

		if IsEmpty then
			return
		end
		
		Idx = Idx and Idx + 1 or 1
		return unpack(Returns)
	end
end

Example usage:

local t1 = {1, 3, 5}
local t2 = {2, 4, 6}

for i1, v1, i2, v2 in CombinedPairs(t1, t2) do
	print(i1, v1, '|', i2, v2)
end

Enjoy! hopefully this turns out useful for anyone :slightly_smiling_face:

20 Likes

Please stop spamming threads with this, I’m getting really annoyed with these posts.
The source is available to anyone; there is no reason to have a Github for such small releases.

22 Likes

If both tables don’t have the same amount of keys eventually they’ll start printing nil. Is there anything that can be done about this?

Hey, could you give me a script to replicate this error?
Thanks!

Never mind, after further testing i found out that it only happens if the second table has more keys than the first which is a simple swip swap for the user. But if you still want to fix it here’s what i did.

image

Ps. Thanks so much for this

The issue is that since table1 only has 3 indices while table2 has four, the fourth index of table2 being 4, would become v1 rather than v2. This happens due how the module works - if v1 is nil, all the values after v1 (v2, v3, …) get pushed down by one, taking over v1s spot.

What can be done to fix it? I just found out that what i said up above about swapping doesn’t necessarily work for instances.


image

there’s nothing you can do about an index missing. If you still want to do something to v1 when v2 is nil, just check if v2 is nil or not.
Also, your code can be improved to just:

local table1 = workspace.test1:GetChildren()
local table2 = workspace.test2:GetChildren()
2 Likes

The order of iteration is unspecified for tables, even for numeric indices. So it’s not just dictionaries, it’s all tables.

I’ve never needed something like this, where would it be useful?

Tables such as

{
    [1] = x,
    [2] = y,
    [3] = z
}

are actually considered dictionaries according to lua, since it doesn’t bother checking whether or not theyre properly sorted or not. All it sees is that the indices are manually specified.
A “true” array would be this;

{
    x,
    y,
    z
}

I was referring to those types of arrays.

The usecase for this would be to combine several tables into one loop / scope, obviously.
For instance, you have this:

for i,v in next, c1:GetChildren() do
    v.Name = "Hello"
end
for i,v in next, c2:GetChildren() do
    v.Name = "Hello"
end

this could be shortened with this resource.
Obviously there are other methods for this, but there are other usecases which this module can easily accommodate for, allowing you to do something shorter (and faster?).

1 Like

The table {[1]=1,[2]=2,[3]=3,[4]=4} is equivalent to {1,2,3,4}. Lua tables having an array part is an implementation detail, and shouldn’t be relied upon. The Lua manual makes no guarantees of there being an array part, or that numeric indices will be enumerated in order (it states the opposite).

run those two tables in a loop.
{[1]=1,[2]=2,[3]=3,[4]=4} wouldnt have a guaranteed order, whilst {1,2,3,4} would.

This occurs due to the fact that it gets inserted into the hash part, not the array part. Obviously if you use ipairs or numeric for when iterating, there’d be no difference as it would “force” guaranteed order

Both of the tables {[1]=1,[2]=2,[3]=3,[4]=4} and {1,2,3,4} have index 1 associated to 1, index 2 associated to 2, index 3 associated to 3, and index 4 associated to 4.

Again, the Lua manual doesn’t guarantee there is an array part (or that if such a thing exists it must iterate in a guaranteed order), or that numeric indices are enumerated in order. The table {1,2,3,4} could be iterated in the order 4, 3, 2, 1, or any other order. There is no guarantee that it will iterate in the order 1, 2, 3, 4.

I’ve never seen cases where iterating through {1,2,3,4} would give any other order than 1, 2, 3, 4. Could you show me cases of this occurring?
I’m unsure what point you’re trying to make with t[1] being associated with 1, but I’m talking about what happens when iterating. {[1]=1,[2]=2,[3]=3,[4]=4} would not be iterated in order because it’s seen as a dictionary, whilst {1,2,3,4} would be seen as an array, and put in their respective ““categories”” (I’m not well-versed with Lua’s internals) - the “hash” category and the “array” category, where they would be handled differently depending on what category they’re in.

Numerical indices are being iterated in an unspecified order because they don’t exist in the array portion. The fundamental logic is that you wouldn’t assume there is any sort of order when using the iterative functions next and pairs.

Adding onto this (citation from CntKillMe);



So, we were sort-of both right in a sense?

My original point is that the order of iteration is undefined for all tables, not just dictionaries, so the disclaimer should say tables instead of dictionaries.

1 Like

I’ve edited the thread to accommodate for your point.