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。谢谢。
我正在设计一个通用的 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。谢谢。