How to check if two tables(objects) have the same value in Lua
Asked Answered
P

6

26

I wanna check if two tables have the same value in Lua, but didn't find the way.

I use the operator ==, it seems just to check the same objects, but not the elements in the table.

If I have two tables,

a={}
b={}

the value of a==b is false.

but if

a={}
b=a

the value of a==b is true.

I wonder know if there a way to check two tables having the same elements in Lua. Is there a built-in function like table.equals() to check?

Polik answered 2/12, 2013 at 9:54 Comment(4)
Keep in mind that the 2 cases are actually different. In the second case if you would do a.test=true b.test will be the sameWadlinger
The key to understand it is Lua compares tables by reference, not contents. You need to compare the content by your own.Backstairs
Ok, I see. I was wondering there might be different operators like '==' and 'is' in python. I just want to do a comparison between two simple tables. So I transfer them to json first, then compare the strings. Thank you.Polik
Note that comparing JSON string is generally a terrible idea, because dictionaries are unordered, and whitespaces are not mandated. TL;DR: don't compare JSON strings.Fatigued
J
11

There is no built-in function for comparing tables by contents.

You'll have to write your own. You'll need to decide whether you want to compare tables by contents shallowly or deeply. See https://web.archive.org/web/20131225070434/http://snippets.luacode.org/snippets/Deep_Comparison_of_Two_Values_3 for some ideas.

Jeanniejeannine answered 2/12, 2013 at 10:47 Comment(1)
on SO we prefer the answer to be copied inDormancy
C
13

I offer some improvements for Rutrus solution.

---@param o1 any|table First object to compare
---@param o2 any|table Second object to compare
---@param ignore_mt boolean True to ignore metatables (a recursive function to tests tables inside tables)
function equals(o1, o2, ignore_mt)
    if o1 == o2 then return true end
    local o1Type = type(o1)
    local o2Type = type(o2)
    if o1Type ~= o2Type then return false end
    if o1Type ~= 'table' then return false end

    if not ignore_mt then
        local mt1 = getmetatable(o1)
        if mt1 and mt1.__eq then
            --compare using built in method
            return o1 == o2
        end
    end

    local keySet = {}

    for key1, value1 in pairs(o1) do
        local value2 = o2[key1]
        if value2 == nil or equals(value1, value2, ignore_mt) == false then
            return false
        end
        keySet[key1] = true
    end

    for key2, _ in pairs(o2) do
        if not keySet[key2] then return false end
    end
    return true
end

Be aware that this solution doesn't take into account self references. You may use pequals (below). It's usefull when you have some tricks in your code. But do not use this method for regular checks! It's slower. Also if your object has self reference, you should reanalyze your structure. Self references can be a sign of bad architecture.

local function internalProtectedEquals(o1, o2, ignore_mt, callList)
    if o1 == o2 then return true end
    local o1Type = type(o1)
    local o2Type = type(o2)
    if o1Type ~= o2Type then return false end
    if o1Type ~= 'table' then return false end

    -- add only when objects are tables, cache results
    local oComparisons = callList[o1]
    if not oComparisons then
        oComparisons = {}
        callList[o1] = oComparisons
    end
    -- false means that comparison is in progress
    oComparisons[o2] = false

    if not ignore_mt then
        local mt1 = getmetatable(o1)
        if mt1 and mt1.__eq then
            --compare using built in method
            return o1 == o2
        end
    end

    local keySet = {}
    for key1, value1 in pairs(o1) do
        local value2 = o2[key1]
        if value2 == nil then return false end

        local vComparisons = callList[value1]
        if not vComparisons or vComparisons[value2] == nil then
            if not internalProtectedEquals(value1, value2, ignore_mt, callList) then
                return false
            end
        end

        keySet[key1] = true
    end

    for key2, _ in pairs(o2) do
        if not keySet[key2] then
            return false
        end
    end

    -- comparison finished - objects are equal do not compare again
    oComparisons[o2] = true
    return true
end

function pequals(o1, o2, ignore_mt)
    return internalProtectedEquals(o1, o2, ignore_mt, {})
end

Also you may analyze CompareTables on lua wiki.

Cristiano answered 18/9, 2015 at 20:47 Comment(2)
what is ignore_mt ?Sudoriferous
This should be the better solution!Prosopopoeia
L
12

If you actually want to test simple tables try this...

function do_tables_match( a, b )
    return table.concat(a) == table.concat(b)
end

On a separate note, something that compares to your specific example as follows...

function is_table_empty( table_to_test )
    -- Doesn't work
    return table_to_test == {}
    -- Works only if the table is numeric keyed with no gaps
    return #table_to_test = 0 
    -- Works!
    return next( table_to_test ) ~= nil 
end
Lesleylesli answered 11/1, 2019 at 3:53 Comment(1)
Unfortunately this won't work for tables with key/values, only for indexed valuesEthmoid
J
11

There is no built-in function for comparing tables by contents.

You'll have to write your own. You'll need to decide whether you want to compare tables by contents shallowly or deeply. See https://web.archive.org/web/20131225070434/http://snippets.luacode.org/snippets/Deep_Comparison_of_Two_Values_3 for some ideas.

Jeanniejeannine answered 2/12, 2013 at 10:47 Comment(1)
on SO we prefer the answer to be copied inDormancy
H
4

By the way, I checked @lhf link and is broken, I found this useful example:

function is_table_equal(t1,t2,ignore_mt)
   local ty1 = type(t1)
   local ty2 = type(t2)
   if ty1 ~= ty2 then return false end
   -- non-table types can be directly compared
   if ty1 ~= 'table' and ty2 ~= 'table' then return t1 == t2 end
   -- as well as tables which have the metamethod __eq
   local mt = getmetatable(t1)
   if not ignore_mt and mt and mt.__eq then return t1 == t2 end
   for k1,v1 in pairs(t1) do
      local v2 = t2[k1]
      if v2 == nil or not is_table_equal(v1,v2) then return false end
   end
   for k2,v2 in pairs(t2) do
      local v1 = t1[k2]
      if v1 == nil or not is_table_equal(v1,v2) then return false end
   end
   return true
end
Haemoid answered 10/6, 2015 at 13:12 Comment(3)
what is ignore_mt?Sudoriferous
it is a boolean if you want ignore megatables (a recursive function to tests tables inside tables)Haemoid
Just add ref link: web.archive.org/web/20131225070434/http://snippets.luacode.org/…Prosopopoeia
I
1

I currently use this

local tableCompare
do
    local compare
    compare = function(src, tmp, _reverse)
        if (type(src) ~= "table" or type(tmp) ~= "table") then
            return src == tmp
        end

        for k, v in next, src do
            if type(v) == "table" then
                if type(tmp[k]) ~= "table" or not compare(v, tmp[k]) then
                    return false
                end
            else
                if tmp[k] ~= v then
                    return false
                end
            end
        end
        return _reverse and true or compare(tmp, src, true)
    end
    tableCompare = function(src, tmp, checkMeta)
        return compare(src, tmp) and (not checkMeta or compare(getmetatable(src), getmetatable(tmp)))
    end
end

print(tableCompare({ 1 , b = 30 }, { b = 30, 1 }, false))
Isolate answered 24/8, 2019 at 22:38 Comment(0)
O
0

if you just want to compare 2 small tables, you may (ab)use inspect.

local ins = require 'inspect'
local assert_equal = require 'luassert' .equal

assert_equal(ins({ 1 , b = 30 }), ins({ b = 30, 1 }))

This approach takes advantage of that inspect sorts table elements when serializing an object. while cjson doesn't, which makes it unusable in this case.

Objurgate answered 19/9, 2019 at 17:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.