<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://linguifex.com/w/index.php?action=history&amp;feed=atom&amp;title=Module%3Atable%2FdeepEquals</id>
	<title>Module:table/deepEquals - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://linguifex.com/w/index.php?action=history&amp;feed=atom&amp;title=Module%3Atable%2FdeepEquals"/>
	<link rel="alternate" type="text/html" href="https://linguifex.com/w/index.php?title=Module:table/deepEquals&amp;action=history"/>
	<updated>2026-04-06T06:14:17Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.43.6</generator>
	<entry>
		<id>https://linguifex.com/w/index.php?title=Module:table/deepEquals&amp;diff=478826&amp;oldid=prev</id>
		<title>Sware: Created page with &quot;local getmetatable = getmetatable local next = next local pairs = pairs local type = type  local function is_eq(a, b, seen, include_mt) 	-- If `a` and `b` have been compared before, return the memoized result. This will usually be true, since failures normally fail the whole check outright, but match failures are tolerated during the laborious check without this happening, since it compares key/value pairs until it finds a match, so it could be false. 	local memo_a = see...&quot;</title>
		<link rel="alternate" type="text/html" href="https://linguifex.com/w/index.php?title=Module:table/deepEquals&amp;diff=478826&amp;oldid=prev"/>
		<updated>2025-11-25T17:11:26Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;local getmetatable = getmetatable local next = next local pairs = pairs local type = type  local function is_eq(a, b, seen, include_mt) 	-- If `a` and `b` have been compared before, return the memoized result. This will usually be true, since failures normally fail the whole check outright, but match failures are tolerated during the laborious check without this happening, since it compares key/value pairs until it finds a match, so it could be false. 	local memo_a = see...&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;local getmetatable = getmetatable&lt;br /&gt;
local next = next&lt;br /&gt;
local pairs = pairs&lt;br /&gt;
local type = type&lt;br /&gt;
&lt;br /&gt;
local function is_eq(a, b, seen, include_mt)&lt;br /&gt;
	-- If `a` and `b` have been compared before, return the memoized result. This will usually be true, since failures normally fail the whole check outright, but match failures are tolerated during the laborious check without this happening, since it compares key/value pairs until it finds a match, so it could be false.&lt;br /&gt;
	local memo_a = seen[a]&lt;br /&gt;
	if memo_a then&lt;br /&gt;
		local result = memo_a[b]&lt;br /&gt;
		if result ~= nil then&lt;br /&gt;
			return result&lt;br /&gt;
		end&lt;br /&gt;
		-- To avoid recursive references causing infinite loops, assume the tables currently being compared are equivalent by memoizing the comparison as true; this will be corrected to false if there&amp;#039;s a match failure.&lt;br /&gt;
		memo_a[b] = true&lt;br /&gt;
	else&lt;br /&gt;
		memo_a = {[b] = true}&lt;br /&gt;
		seen[a] = memo_a&lt;br /&gt;
	end&lt;br /&gt;
	-- Don&amp;#039;t bother checking `memo_b` for `a`, since if `a` and `b` had been compared before then `b` would be in `memo_a`, but it isn&amp;#039;t.&lt;br /&gt;
	local memo_b = seen[b]&lt;br /&gt;
	if memo_b then&lt;br /&gt;
		memo_b[a] = true&lt;br /&gt;
	else&lt;br /&gt;
		memo_b = {[a] = true}&lt;br /&gt;
		seen[b] = memo_b&lt;br /&gt;
	end&lt;br /&gt;
	-- If `include_mt` is set, check the metatables are equivalent.&lt;br /&gt;
	if include_mt then&lt;br /&gt;
		local mt_a, mt_b = getmetatable(a), getmetatable(b)&lt;br /&gt;
		if not (mt_a == mt_b or type(mt_a) == &amp;quot;table&amp;quot; and type(mt_b) == &amp;quot;table&amp;quot; and is_eq(mt_a, mt_b, seen, true)) then&lt;br /&gt;
			memo_a[b], memo_b[a] = false, false&lt;br /&gt;
			return false&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	-- Copy all key/values pairs in `b` to `remaining_b`, and count the size: this uses `pairs`, which will also be used to iterate over `a`, ensuring that `a` and `b` are iterated over using the same iterator. This is necessary to ensure that `deepEquals(a, b)` and `deepEquals(b, a)` always give the same result. Simply iterating over `a` while accessing keys in `b` for comparison would ignore any `__pairs` metamethod that `b` has, which could cause asymmetrical outputs if `__pairs` returns more or less than the complete set of key/value pairs accessible via `__index`, so using `pairs` for both `a` and `b` prevents this.&lt;br /&gt;
	-- TODO: handle exotic `__pairs` methods which return the same key multiple times with different values.&lt;br /&gt;
	local remaining_b, size_b = {}, 0&lt;br /&gt;
	for k_b, v_b in pairs(b) do&lt;br /&gt;
		remaining_b[k_b], size_b = v_b, size_b + 1&lt;br /&gt;
	end&lt;br /&gt;
	-- Fast check: iterate over the keys in `a`, checking if an equivalent value exists at the same key in `remaining_b`. As matches are found, key/value pairs are removed from `remaining_b`. If any keys in `a` or `remaining_b` are tables, the fast check will only work if the exact same object exists as a key in the other table. Any others from `a` that don&amp;#039;t match anything in `remaining_b` are added to `remaining_a`, while those in `remaining_b` that weren&amp;#039;t found will still remain once the loop ends. `remaining_a` and `remaining_b` are then compared at the end with the laborious check.&lt;br /&gt;
	local size_a, remaining_a = 0&lt;br /&gt;
	for k, v_a in pairs(a) do&lt;br /&gt;
		local v_b = remaining_b[k]&lt;br /&gt;
		-- If `k` isn&amp;#039;t in `remaining_b`, `a` and `b` can&amp;#039;t be equivalent unless it&amp;#039;s a table.&lt;br /&gt;
		if v_b == nil then&lt;br /&gt;
			if type(k) ~= &amp;quot;table&amp;quot; then&lt;br /&gt;
				memo_a[b], memo_b[a] = false, false&lt;br /&gt;
				return false&lt;br /&gt;
			-- Otherwise, add the `k`/`v_a` pair to `remaining_a` for the laborious check.&lt;br /&gt;
			elseif not remaining_a then&lt;br /&gt;
				remaining_a = {}&lt;br /&gt;
			end&lt;br /&gt;
			remaining_a[k], size_a = v_a, size_a + 1&lt;br /&gt;
		-- Otherwise, if `k` exists in `a` and `remaining_b`, `v_a` and `v_b` must be equivalent for there to be a match.&lt;br /&gt;
		elseif v_a == v_b or type(v_a) == &amp;quot;table&amp;quot; and type(v_b) == &amp;quot;table&amp;quot; and is_eq(v_a, v_b, seen, include_mt) then&lt;br /&gt;
			remaining_b[k], size_b = nil, size_b - 1&lt;br /&gt;
		else&lt;br /&gt;
			memo_a[b], memo_b[a] = false, false&lt;br /&gt;
			return false&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	-- Must be the same number of remaining keys in each table.&lt;br /&gt;
	if size_a ~= size_b then&lt;br /&gt;
		memo_a[b], memo_b[a] = false, false&lt;br /&gt;
		return false&lt;br /&gt;
	-- If the size is 0, there&amp;#039;s nothing left to check.&lt;br /&gt;
	elseif size_a == 0 then&lt;br /&gt;
		return true&lt;br /&gt;
	end&lt;br /&gt;
	-- Laborious check: since it&amp;#039;s not possible to use table lookups to check if two keys are equivalent when they&amp;#039;re tables, check each key/value pair in `remaining_a` against every key/value pair in `remaining_b` until a match is found, removing the matching key/value pair from `remaining_b` each time, to ensure one-to-one equivalence.&lt;br /&gt;
	for k_a, v_a in next, remaining_a do&lt;br /&gt;
		local success&lt;br /&gt;
		for k_b, v_b in next, remaining_b do&lt;br /&gt;
			-- Keys/value pairs must be equivalent in order to match.&lt;br /&gt;
			if ( -- More efficient to compare the values first, as they might not be tables.&lt;br /&gt;
				(v_a == v_b or type(v_a) == &amp;quot;table&amp;quot; and type(v_b) == &amp;quot;table&amp;quot; and is_eq(v_a, v_b, seen, include_mt)) and&lt;br /&gt;
				(k_a == k_b or type(k_a) == &amp;quot;table&amp;quot; and type(k_b) == &amp;quot;table&amp;quot; and is_eq(k_a, k_b, seen, include_mt))&lt;br /&gt;
			) then&lt;br /&gt;
				-- Remove matched key from `remaining_b`, and break the inner loop.&lt;br /&gt;
				success, remaining_b[k_b] = true, nil&lt;br /&gt;
				break&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- Fail if `remaining_b` runs out of keys, as the `k_a`/`v_a` pair still hasn&amp;#039;t matched.&lt;br /&gt;
		if not success then&lt;br /&gt;
			memo_a[b], memo_b[a] = false, false&lt;br /&gt;
			return false&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	-- If every key/value pair in `remaining_a` matched with one in `remaining_b`, `a` and `b` must be equivalent. Note that `remaining_b` will now be empty, since the laborious check only starts if `remaining_a` and `remaining_b` are the same size.&lt;br /&gt;
	return true&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[==[&lt;br /&gt;
Recursively compare two values that may be tables, and returns true if all key-value pairs are structurally equivalent. Note that this handles arbitrary nesting of subtables (including recursive nesting) to any depth, for keys as well as values.&lt;br /&gt;
&lt;br /&gt;
If `include_mt` is true, then metatables are also compared.]==]&lt;br /&gt;
return function(a, b, include_mt)&lt;br /&gt;
	-- Do simple checks before calling is_eq to avoid generating an unnecessary memo.&lt;br /&gt;
	-- Simple equality check and type check; if not a ~= b, a and b can only be equivalent if they&amp;#039;re both tables.&lt;br /&gt;
	return a == b or type(a) == &amp;quot;table&amp;quot; and type(b) == &amp;quot;table&amp;quot; and is_eq(a, b, {}, include_mt)&lt;br /&gt;
end&lt;/div&gt;</summary>
		<author><name>Sware</name></author>
	</entry>
</feed>