Table 在 Lua 中过滤多维表

Table Filter in Lua for multidimensional tables

我正在设计一个通用的 table 过滤器,它可以从给定的 table 中删除条目,问题是完成此操作的键不是唯一的,而且类型也不同。

举个例子说得更清楚

SampleTable = {
    { key1 = 10, key2 = 'name_1', Str = 'sample_string_1'     },
    { key1 = 10, key2 = 'name_3', Str = 'sample_string_2'     },
    { key1 = 11, key2 = 'name_2', Mac = {'ID1', 'ID2', 'ID3'} },
    { key1 = 11, key2 = 'name_2', Mac = {'ID1'}               },
    { key1 = 12, key2 = 'name_4', Mac = {'ID2', 'ID3'}        }
}

function filter(inputTable, ...) 
    filterItems = {...}
end

我想传递任意数量的键来过滤这个 table

local key1 = 11
local Mac = 'ID1'
filter(SampleTable, key1, Mac)
 -- Should return -> { key1 = 11, key2 = 'name_2', Mac = 'ID1'},


key1 = 12
Mac = 'ID3'
filter(SampleTable, key1, Mac)
-- Should return -> { key1 = 12, key2 = 'name_4', Mac = ID3'}

key1 = 11
Mac = 'ID2'
filter(SampleTable, key1, Mac)
 -- Should return both    
-- { key1 = 11, key2 = 'name_2', Mac = ID2'},
-- { key1 = 11, key2 = 'name_5', Mac = ID2'},

key1 = 10
Str = 'sample_string_2'
filter(SampleTable, key1, Str)
 -- Should return { key1 = 10, key2 = 'name_3', Str = 'sample_string_2'}

我目前的解决方案是搜索 tables

中的每个键值对
function filter(tIn, tFilter) 
    local retain = true
    local exist  = nil
    local tOut = tIn
    local _findInTable = function (t, k, v)
        if(not t[k]) then return true
        elseif(t[k] and t[k] == v) then return true
        else return false end
    end

    for i, t in ipairs (tIn) do
        for k,v in pairs (tFilter) do
            exist = _findInTable(t, k, v)
            retain = retain and exist
        end
        if not retain then tOut[i] = nil end
        retain = true
    end
    return tOut
end

local myTable = filter(SampleTable, {key1 = 11, Mac = 'ID1'})

问题是我无法预见递归会有多大帮助。 当我有以下 SampleTable 时,这段代码有效,如您所见,Mac 不是我代码的子 table。

SampleTable = {
    { key1 = 10, key2 = 'name_1', Str = 'sample_string_1'     },
    { key1 = 10, key2 = 'name_3', Str = 'sample_string_2'     },
    { key1 = 11, key2 = 'name_2', Mac = 'ID1'                 }
    -- { key1 = 11, key2 = 'name_2', Mac = {'ID1', 'ID2', 'ID3'} },
    -- { key1 = 11, key2 = 'name_2', Mac = {'ID1'}               },
    -- { key1 = 12, key2 = 'name_4', Mac = {'ID2', 'ID3'}        }
}

从你的问题来看,你是否正在处理递归模式(任意深度和分支结构),或者所提供的样本是否就是它的全部(键总是分配给 n 值,模式中没有递归)。由于没有更复杂的示例,我决定针对更简单的情况实施此方法。


这是我对你的问题的解决方案,包括你的示例:

local sample = {
    { key1 = 10, key2 = 'name_1', Str = 'sample_string_1'     },
    { key1 = 10, key2 = 'name_3', Str = 'sample_string_2'     },
    { key1 = 11, key2 = 'name_2', Mac = {'ID1', 'ID2', 'ID3'} },
    { key1 = 11, key2 = 'name_2', Mac = {'ID1'}               },
    { key1 = 12, key2 = 'name_4', Mac = {'ID2', 'ID3'}        }
}

--- Check if a row matches the specified key constraints.
-- @param row The row to check
-- @param key_constraints The key constraints to apply
-- @return A boolean result
local function filter_row(row, key_constraints)
    -- Loop through all constraints
    for k, v in pairs(key_constraints) do
        if v and not row[k] then
            -- The row is missing the key entirely,
            -- definitely not a match
            return false
        end

        -- Wrap the key and constraint values in arrays,
        -- if they're not arrays already (so we can loop through them)
        local actual_values = type(row[k]) == "table" and row[k] or {row[k]}
        local required_values = type(v) == "table" and v or {v}

        -- Loop through the values we *need* to find
        for i = 1, #required_values do
            local found
            -- Loop through the values actually present
            for j = 1, #actual_values do
                if actual_values[j] == required_values[i] then
                    -- This object has the required value somewhere in the key,
                    -- no need to look any farther
                    found = true
                    break
                end
            end

            if not found then
                return false
            end
        end
    end

    return true
end

--- Filter an array, returning entries matching `key_values`.
-- @param input The array to process
-- @param key_values A table of keys mapped to their viable values
-- @return An array of matches
local function filter(input, key_values)
    local result = {}

    for i = 1, #input do
        local row = input[i]
        if filter_row(row, key_values) then
            result[#result + 1] = row
        end
    end

    return result
end

下面是示例输出,带有实用程序 deep_print() 函数:

--- Recursively print out a Lua value.
-- @param value The value to print
-- @param indent Indentation level (defaults to 0)
-- @param no_newline If true, won't print a newline at the end
local function deep_print(value, indent, no_newline)
    indent = indent or 0

    if type(value) == "table" then
        print("{")
        for k, v in pairs(value) do
            io.write(string.rep(" ", indent + 2) .. "[")
            deep_print(k, indent + 2, true)
            io.write("] = ")
            deep_print(v, indent + 2, true)
            print(";")
        end
        io.write(string.rep(" ", indent) .. "}")
    elseif type(value) == "string" then
        io.write(("%q"):format(value))
    else
        io.write(tostring(value))
    end

    if not no_newline then
        print()
    end
end

-- The following is a mix of Lua code
-- and the script's output

deep_print(filter(sample, {key1 = 10}))
-- outputs
{
  [1] = {
    ["key1"] = 10;
    ["key2"] = "name_1";
    ["Str"] = "sample_string_1";
  };
  [2] = {
    ["key1"] = 10;
    ["key2"] = "name_3";
    ["Str"] = "sample_string_2";
  };
}

deep_print(filter(sample, {key2 = "name_4"}))
-- outputs
{
  [1] = {
    ["key1"] = 12;
    ["key2"] = "name_4";
    ["Mac"] = {
      [1] = "ID2";
      [2] = "ID3";
    };
  };
}

deep_print(filter(sample, {Mac = {"ID2", "ID3"}}))
-- outputs
{
  [1] = {
    ["key1"] = 11;
    ["key2"] = "name_2";
    ["Mac"] = {
      [1] = "ID1";
      [2] = "ID2";
      [3] = "ID3";
    };
  };
  [2] = {
    ["key1"] = 12;
    ["key2"] = "name_4";
    ["Mac"] = {
      [1] = "ID2";
      [2] = "ID3";
    };
  };
}

deep_print(filter(sample, {Mac = {"ID2"}}))
-- also outputs
{
  [1] = {
    ["key1"] = 11;
    ["key2"] = "name_2";
    ["Mac"] = {
      [1] = "ID1";
      [2] = "ID2";
      [3] = "ID3";
    };
  };
  [2] = {
    ["key1"] = 12;
    ["key2"] = "name_4";
    ["Mac"] = {
      [1] = "ID2";
      [2] = "ID3";
    };
  };
}

-- Specifying multiple keys works too:
deep_print(filter(sample, {key2 = "name_3", Str = "sample_string_2"}))
-- outputs
{
  [1] = {
    ["key1"] = 10;
    ["key2"] = "name_3";
    ["Str"] = "sample_string_2";
  };
}

如您所见,filter_row() 是非递归的。它仅对行级键进行操作。如果您的模式是扁平的,这应该有效,如示例中所示。如果你要过滤的数据其实比较复杂,请多举例子。

通过先将值包装在数组(表)中,使键比较的操作变得更简单。这允许对所有可能的情况使用统一的比较方法(有一点额外的开销)。

这是我对SO的第一个回答,如有不妥请edit/comment。谢谢。