Schokokex / addon_template_butt

A template for Dota But Mods. Initiated by Baumis Twitch/Youtube channel and the community.
https://www.twitch.tv/l34um1
22 stars 13 forks source link

Compare Tables #25

Closed GniLudio closed 4 years ago

GniLudio commented 4 years ago

Compare Tables

What I want

It would be nice to have a function that compares two tables.

Problems

1) Comparing tables that include other tables. 2) Especially dictionaries are complicated.

My little Function

Schokokex commented 4 years ago

print(table.equals({a=12,b=2},{a=1,b=2})) returns true problem being every value being compared with every value...

GniLudio commented 4 years ago

@Jochnickel I don't know how you want to solve table comparison.

Problems

These things must be considered: 1) Multiple times the same value

Schokokex commented 4 years ago

what it does now: it checks if a table has exactly the same k-v pairs (recursive). so {a={b=1}} == {a={b=1}} i didnt respect arrays so far, so {[1]=1,[2]=2} ~= {[1]=2,[2]=1}


function table.compare(table1, table2)
    if "table"~=type(table1) then error("1st argument of table.compare() is not a table") end
    if "table"~=type(table2) then error("2nd argument of table.compare() is not a table") end

    for k, v1 in pairs(table1) do
        local v2 = table2[k]
        if "table"==type(v1) and "table"==type(v2) then
            if not table.compare(v1,v2) then return false end
        else
            if v1~=v2 then return false end
        end
    end
    for k, _ in pairs(table2) do
        local v1 = table1[k]
        if v1==nil then return false end
    end

    return true
end
GniLudio commented 4 years ago

@Jochnickel I think you treat arrays already as indented.

They are equally

1) Array

Schokokex commented 4 years ago

i could now introduce flags:


TABLE_COMPARE_STRICT = 0
TABLE_COMPARE_IGNORE_KEYS = 1
TABLE_COMPARE_IGNORE_KEYS_AND_DUPLICATES = 2

function table.compare(table1, table2, flags)
    if "table"~=type(table1) then error("1st argument of table.compare() is not a table") end
    if "table"~=type(table2) then error("2nd argument of table.compare() is not a table") end
    if nil==flags or 0==flags then
        -- as before
    elseif TABLE_COMPARE_IGNORE_KEYS==flags then
        local arr1 = {}
        for _, v1 in pairs(table1) do
            table.insert(arr1,v1)
        end
        -- arr1 is array with all table1 values
        for _, v2 in pairs(table2) do
            --checking every table2 value
            local foundV1 = false
            for k1, v1 in pairs(arr1) do
                if v2==v1
                or "table"==type(v1) and "table"==type(v2) and table.compare(v1,v2, flags)
                then
                    -- found the counterpart in arr1
                    foundV1 = true
                    arr1[k1] = nil
                    break
                end
            end
            if not foundV1 then
                -- no arr1 counterpart found
                return false
            end
        end
        for _ in pairs(arr1) do
            --  arr1 value is left with no table2 counterpart
            return false
        end
    elseif TABLE_COMPARE_IGNORE_KEYS_AND_DUPLICATES==flags then
        local values = {}
        local tabValues1 = {}
        for _, v1 in pairs(table1) do
            if "table"==type(v1) then
                -- v1 is a table
                local tableAlreadyListed = false
                for v0 in pairs(tabValues1) do
                    if table.compare(v1,v0,flags) then
                        tableAlreadyListed = true
                        break
                    end
                end
                if not tableAlreadyListed then
                    tabValues1[v1] = v1
                    values[v1] = 1
                end
            else
                -- v1 is a normal value, not table
                values[v1] = 1
            end
        end
        -- so now all table1 values are stored as keys in values    {12 = 1, "1a5" = 1,...}
        -- also tabValues1 contains all different tables            {table51 = table51, table32 = table32, ...}
        -- for every tabValue1 there is an entry in values          {..., "1a5" = 1, table51 = 1, ...}
        for k2, v2 in pairs(table2) do
            if "table"==type(v2) then
                local foundV1 = false
                for v1 in pairs(tabValues1) do
                    if table.compare(v1,v2,flags) then
                        foundV1 = true
                        values[v1] = 2
                        break
                    end
                end
                if not foundV1 then return false end
            else
                -- v2 is a normal value, not table
                if nil==values[v2] then
                    -- lonely value2
                    return false
                end
                values[v2] = 2
            end
        end
        -- now all 1 inside values should have turned into 2
        -- values[normal values] == 2 now if they appeared in the 2nd table
        -- values[tables] == 2 if there was a similar (sub)table in table2
        for k,v in pairs(values) do
            if 1==v then
                -- lonely value1
                return false
            end
        end
    end
    return true
end
Schokokex commented 4 years ago

@GniLudio now that looks like a lot, because its 2 different implementations

Schokokex commented 4 years ago

strict: {a=1,b=2} ~= {a=2,b=1} as mentioned ignore keys: {a=1,b=2} == {c=1,d=2} but {a=1,b=2,z=2}~={c=1,d=2} ignore keys and duplicates: {a=1, b=2, z=2} == {c=1, d=2} and {a=1, z={2,3}} == {c=1, d={2,3}, f={2,3}}

GniLudio commented 4 years ago

@Jochnickel

My Compare Function

Not perfect but a lot shorter I didn't test much but it should work... :)

Function

function compare(table1, table2, flags)
    if "table"~=type(table1) then error("The 1st parameter of compare() is not a table.") end
    if "table"~=type(table2) then error("The 2nd parameter of compare() is not a table.") end
    if "number"~=type(flags) then error("The 3rd parameter of compare() is not a number.") end
    -- shouldn't be in the function...
    local STRICT, IGNORE_KEYS, IGNORE_KEYS_AND_DUPLICATES = 0,1,2
    -- IF STRICT --> COMPARE KEYS
    if flags==STRICT then
        print("STRICT")
        -- Has table2 all keys of table1?
        for k, _ in pairs(table1) do
            if not table2[k] then return false end
        end
        -- Has table1 all keys of table2?
        for k, _ in pairs(table2) do
            if not table1[k] then return false end
        end
    -- IF KEYS ARE IGNORED --> CONVERT INTO VALUE--COUNT
    else
        -- convert table1
        local t1_converted = {}
        for k, v in pairs(table1) do
            -- creates the value--0 pair
            t1_converted[v] = t1_converted[v] or 0
            -- if duplicates are not ignored --> count the value
            if IGNORE_KEYS==flags then
                t1_converted[v] = t1_converted[v]+1
            end
        end
        table1 = t1_converted
        -- convert table2
        local t2_converted = {}
        for k, v in pairs(table2) do
            -- creates the value--0 pair
            t2_converted[v] = t2_converted[v] or 0
            -- if duplicates are not ignored --> count the value
            if IGNORE_KEYS==flags then
                t2_converted[v] = t2_converted[v]+1
            end
        end
        table2 = t2_converted
    end

    -- compares the key--value pairs
    for k,v in pairs(table1) do
        if table2[k]~=v then return false end
    end
    return true
end
GniLudio commented 4 years ago

@Jochnickel This is my best and shortest idea. (You need table.copy and table.length for that.)


local STRICT, IGNORE_KEYS, IGNORE_KEYS_AND_DUPLICATES = 0,1,2
function table.compare(table1, table2, flags)
    if "table"~=type(table1) then error("The 1st parameter of compare() is not a table.") end
    if "table"~=type(table2) then error("The 2nd parameter of compare() is not a table.") end
    if "number"~=type(flags) then error("The 3rd parameter of compare() is not a number.") end
    -- compare keys
    if flags==STRICT then
        local table2_unique_keys = table.copy(table2)
        for k, _ in pairs(table1) do
            -- unique key to table1
            if not table2[k] then return false end
            -- this key is not unique for table2
            table2_unique_keys[k] = nil
        end
        -- not removed keys are unique to table2
        if table.length(table2_unique_keys)~=0 then return false end
    -- converts the tables into value--count
    else
                local both_tables = {table1, table2}
        -- convert tables into value--count
        for i, t in pairs(both_tables) do
            -- the converted table
            local converted = {}
            for k,v in pairs(t) do
                -- new value
                if not converted[v] then converted[v] = 0 end
                -- add 1 if duplicates are not ignored
                if flags~=IGNORE_KEYS_AND_DUPLICATES then
                    converted[v] = converted[v] + 1
                end
            end
            -- overrides the table with the converted one
            both_tables[i] = converted
        end
    end
    -- compares the values
    for k,v in pairs(t1) do
        if table2[k]~=v then return false end
    end
    return true
end
GniLudio commented 4 years ago

@Jochnickel @GniLudio

Final version(fixed and tested)

The function

STRICT, IGNORE_KEYS, IGNORE_KEYS_AND_DUPLICATES = 0,1,2
function table.compare(table1, table2, flags)
    if "table"~=type(table1) then error("The 1st parameter of compare() is not a table.") end
    if "table"~=type(table2) then error("The 2nd parameter of compare() is not a table.") end
    if "number"~=type(flags) then error("The 3rd parameter of compare() is not a number.") end
    -- compare keys
    if flags==STRICT then
    --  local table2_unique_keys = table.copy(table2)
    --  for k1 in pairs(table1) do
    --      -- unique key to table1
    --      if nil==table2[k1] then return false end
    --      -- this key is not unique for table2
    --      table2_unique_keys[k1] = nil
    --  end
    --  -- not removed keys are unique to table2
    --  if table.length(table2_unique_keys)~=0 then 
    --      return false
    --  end
    -- -- converts the tables into value--count
    else
        local both_tables = {table1, table2}
        -- convert tables into value--count
        for i, t in pairs(both_tables) do
            -- the converted table
            local converted = {}
            for k,v in pairs(t) do
                -- new value
                if not converted[v] then converted[v] = 0 end
                -- add 1 if duplicates are not ignored
                if flags~=IGNORE_KEYS_AND_DUPLICATES then
                    converted[v] = converted[v] + 1
                end
            end
            -- overrides the table with the converted one
            both_tables[i] = converted
        end
        table1, table2 = both_tables[1], both_tables[2]
    end
    -- compares the values
    for k,v in pairs(table1) do
        if not (table2[k]==v) then return false end
    end
    for k,v in pairs(table2) do
        if not (table1[k]==v) then return false end
    end
    return true
end

Tests

for i, test in pairs(tests) do t1,t2, flags, bool = test[1], test[2], test[3], test[4] print(bool==table.compare(t1,t2,flags)) end

Schokokex commented 4 years ago

did edit your comment be: :P


(not table2[k1])->(nil==table2[k1])
-- (not table2[k1])==(false==table2[k1])

-- edit: first part of code not necessary
GniLudio commented 4 years ago

@Jochnickel