Progress
From KIproBatt Wiki
Simon Stier (talk | contribs) (import from DevWiki) |
Simon Stier (talk | contribs) (add sparql query) |
||
(4 intermediate revisions by the same user not shown) | |||
Line 2: | Line 2: | ||
-- Module:SMW | -- Module:SMW | ||
local p = {} | local p = {} | ||
− | + | local linkedwiki = require 'linkedwiki' | |
function p.GetProcessesGraph() | function p.GetProcessesGraph() | ||
− | local process_types = {"LabProcess:OSL9a645a64b15442398ad3c057e1b64d87", "LabProcess:OSL4c1f7444e389471a8250f53407191735"} | + | local process_types = {"LabProcess:OSL9a645a64b15442398ad3c057e1b64d87", "LabProcess:OSL4c1f7444e389471a8250f53407191735", "LabProcess:OSLd0c734a239844a0d8820856add12aeca"} |
+ | --local process_types = {"LabProcess:OSL4c1f7444e389471a8250f53407191735", "LabProcess:OSLd0c734a239844a0d8820856add12aeca"} | ||
local result = p.GetProcesses(process_types) | local result = p.GetProcesses(process_types) | ||
--mw.logObject(result) | --mw.logObject(result) | ||
Line 14: | Line 15: | ||
rankdir=LR | rankdir=LR | ||
]] | ]] | ||
+ | local edges = "" | ||
for id,process in pairs(result) do | for id,process in pairs(result) do | ||
--mw.logObject(process) | --mw.logObject(process) | ||
local nodes = "" | local nodes = "" | ||
− | local | + | local nodes2 = "" |
+ | |||
for obj_id, obj in pairs(process['objects']) do | for obj_id, obj in pairs(process['objects']) do | ||
− | + | ||
− | + | if p.IsGlobalObject(obj) then | |
− | for succ_id, succ in pairs( | + | local successors = nil |
+ | successors = p.concatTables(obj['successor_object'], obj['successor_object_static']) | ||
+ | --if (successors == nil) then successors = p.toTable(obj['successor_object_semistatic']) end | ||
+ | if (successors == nil and p.tableContains(p.toTable(obj['process_output']),obj_id) == false) then successors = p.toTable(obj['process_output']) end | ||
+ | successors = p.removeDuplicates(successors) | ||
+ | obj['successors'] = successors | ||
+ | successors = p.GetNextGlobalObject(result, obj); | ||
+ | --if (obj['successor_object'] ~= nil) then | ||
+ | -- successors = p.toTable(obj['successor_object']); | ||
+ | --end | ||
+ | --if (obj['successor_process_output'] ~= nil) then | ||
+ | -- _successors = p.toTable(obj['successor_process_output']); | ||
+ | -- if (p.tablelength(_successors) == 1) then successors = _successors end | ||
+ | --end | ||
+ | local color = "black" | ||
+ | if (successors ~= nil) then | ||
+ | color = "green" | ||
+ | for succ_id, succ in pairs(successors) do | ||
edges = edges .. '"' .. obj_id .. '" -> "' .. succ .. '";' | edges = edges .. '"' .. obj_id .. '" -> "' .. succ .. '";' | ||
end | end | ||
+ | nodes2 = nodes2 .. '"' .. obj_id .. '" [color=' .. color .. ' label="' .. obj['label'] .. '" href="/wiki/' .. obj_id .. '"];' | ||
+ | else | ||
+ | nodes = nodes .. '"' .. obj_id .. '" [color=' .. color .. ' label="' .. obj['label'] .. '" href="/wiki/' .. obj_id .. '"];' | ||
+ | end | ||
end | end | ||
end | end | ||
g = g .. [[ | g = g .. [[ | ||
subgraph cluster_]] .. id:gsub(":","_") .. [[ { | subgraph cluster_]] .. id:gsub(":","_") .. [[ { | ||
− | node [ | + | node [shape=rect]; |
label = "]] .. process['label'] .. [["; | label = "]] .. process['label'] .. [["; | ||
color=blue; | color=blue; | ||
]] .. nodes .. [[ | ]] .. nodes .. [[ | ||
+ | ]] .. nodes2 .. [[ | ||
} | } | ||
− | |||
]] | ]] | ||
end | end | ||
g = g .. [[ | g = g .. [[ | ||
+ | |||
+ | ]] .. edges .. [[ | ||
+ | |||
}]] | }]] | ||
d:wikitext(g) | d:wikitext(g) | ||
Line 53: | Line 80: | ||
results[process_type] = {} | results[process_type] = {} | ||
results[process_type]['objects'] = {} | results[process_type]['objects'] = {} | ||
− | result = p.GetOutputs(process_type) | + | --result = p.GetOutputs(process_type) |
+ | result = p.GetProcessObjects(process_type) | ||
if (result ~= nil) then | if (result ~= nil) then | ||
for index, obj in pairs(result) do | for index, obj in pairs(result) do | ||
Line 75: | Line 103: | ||
end | end | ||
end | end | ||
+ | --mw.logObject(results) | ||
+ | return results | ||
+ | end | ||
− | return | + | function p.GetNextGlobalObject(dataset, src_obj) |
+ | if (src_obj['successors'] == nil) then return nil end | ||
+ | local result = {} | ||
+ | for id,process in pairs(dataset) do | ||
+ | for obj_id, obj in pairs(process['objects']) do | ||
+ | for succ_id, succ in pairs(src_obj['successors']) do | ||
+ | --mw.log("Compare " .. obj_id .. " with " .. succ) | ||
+ | if obj_id == succ then | ||
+ | local isGlobal = p.IsGlobalObject(obj) | ||
+ | if isGlobal == false then | ||
+ | --mw.log("Follow " .. obj_id) | ||
+ | p.concatTables(result,p.GetNextGlobalObject(dataset, obj)) | ||
+ | elseif (succ ~= nil) then | ||
+ | --mw.log("Found " .. succ) | ||
+ | table.insert(result, succ) | ||
+ | end | ||
+ | end | ||
+ | end | ||
+ | end | ||
+ | end | ||
+ | return result | ||
+ | end | ||
+ | |||
+ | function p.IsGlobalObject(obj) | ||
+ | local isGlobal = false | ||
+ | local visible_types = {"Material:OSL344e5c0f20e04894abe53678e2f68351"} | ||
+ | for category_id, category in pairs(p.toTable(obj['category'])) do | ||
+ | if (category == "Category:LabProcessOutput") then isGlobal = true end | ||
+ | end | ||
+ | if (obj['type'] ~= nil) then | ||
+ | for obj_type_id, obj_type in pairs(p.toTable(obj['type'])) do | ||
+ | for vis_type_id, vis_type in pairs(p.toTable(visible_types)) do | ||
+ | if (obj_type == vis_type) then isGlobal = true end | ||
+ | end | ||
+ | end | ||
+ | end | ||
+ | return isGlobal | ||
end | end | ||
Line 84: | Line 151: | ||
-- note also: unlinking via parameter link is not supported at this point | -- note also: unlinking via parameter link is not supported at this point | ||
--local process_type = "LabProcess:OSL9a645a64b15442398ad3c057e1b64d87" | --local process_type = "LabProcess:OSL9a645a64b15442398ad3c057e1b64d87" | ||
− | local query = "[[Category:LabProcessOutput]] [[-HasObject.IsInstanceOf::" .. process_type .. "]] |?#-=id |?HasId#-=local_id |?-HasPredecessor#-=successor_param |?-HasPredecessor.IsProcessParameterOf#-=successor_subprocess |?-HasPredecessor.IsObjectParameterOf#-=successor_object |?-HasPredecessor.IsProcessParameterOf.IsSubprocessOf#-=successor_process |format=plain" | + | local query = "[[Category:LabProcessOutput]] [[-HasObject.IsInstanceOf::" .. process_type .. "]] " .. [[ |
+ | |?#-=id | ||
+ | |?HasId#-=local_id | ||
+ | |?Display title of#-=label | ||
+ | |?-HasPredecessor#-=successor_param | ||
+ | |?-HasPredecessor.IsProcessParameterOf#-=successor_subprocess | ||
+ | |?-HasPredecessor.IsObjectParameterOf#-=successor_object | ||
+ | |?-HasPredecessor.IsProcessParameterOf.IsSubprocessOf#-=successor_process | ||
+ | |?-HasPredecessor.IsProcessParameterOf.IsSubprocessOf.-IsOutputOf#-=successor_process_output | ||
+ | |format=plain | ||
+ | |limit=10000 | ||
+ | ]] | ||
+ | local result = mw.smw.ask( query ) | ||
+ | --mw.logObject(result) | ||
+ | return result | ||
+ | end | ||
+ | |||
+ | function p.GetProcessObjects(process_types) | ||
+ | --local process_types = {"LabProcess:OSL9a645a64b15442398ad3c057e1b64d87", "LabProcess:OSL4c1f7444e389471a8250f53407191735", "LabProcess:OSLd0c734a239844a0d8820856add12aeca"} | ||
+ | process_types = p.toTable(process_types) | ||
+ | local query = "[[-HasObject.IsInstanceOf::" .. table.concat(process_types, "]] OR [[-HasObject.IsInstanceOf::") .. "]]" | ||
+ | query = query .. [[ | ||
+ | |?#-=id | ||
+ | |?HasId#-=local_id | ||
+ | |?Display title of#-=label | ||
+ | |?-HasPredecessor.IsProcessParameterOf.HasOutput#-=successor_object_semistatic | ||
+ | |?-HasPredecessor.IsObjectParameterOf#-=successor_object | ||
+ | |?-Has subobject.-IsOutputOf#-=process_output | ||
+ | |?-HasInput.HasOutput#-=successor_object_static | ||
+ | |?Category#-=category | ||
+ | |?IsInstanceOf#-=type | ||
+ | |format=plain | ||
+ | |limit=10000 | ||
+ | ]] | ||
local result = mw.smw.ask( query ) | local result = mw.smw.ask( query ) | ||
+ | --mw.logObject(result) | ||
+ | |||
return result | return result | ||
+ | end | ||
+ | |||
+ | function p.GetProcessObjectsSPARQL(process_types) | ||
+ | --local process_types = {"LabProcess:OSL9a645a64b15442398ad3c057e1b64d87", "LabProcess:OSL4c1f7444e389471a8250f53407191735", "LabProcess:OSLd0c734a239844a0d8820856add12aeca"} | ||
+ | local object = linkedwiki.new() | ||
+ | object:setConfig("https://kiprobatt.de") | ||
+ | mw.log(object:getConfig()) | ||
+ | |||
+ | local query = [[ | ||
+ | PREFIX dc: <http://purl.org/dc/elements/1.1/> | ||
+ | PREFIX property: <https://kiprobatt.de/id/Property-3A> | ||
+ | PREFIX file: <https://kiprobatt.de/id/File-3A> | ||
+ | PREFIX category: <https://kiprobatt.de/id/Category-3A> | ||
+ | PREFIX material: <https://kiprobatt.de/id/Material-3A> | ||
+ | PREFIX process: <https://kiprobatt.de/id/LabProcess-3A> | ||
+ | PREFIX wiki: <https://kiprobatt.de/id/> | ||
+ | PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> | ||
+ | PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> | ||
+ | PREFIX owl: <http://www.w3.org/2002/07/owl#> | ||
+ | PREFIX swivt: <http://semantic-mediawiki.org/swivt/1.0#> | ||
+ | PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> | ||
+ | |||
+ | SELECT ?id ?local_id ?label ?successor_object_semistatic ?successor_object ?process_output ?successor_object_static ?category ?type | ||
+ | WHERE { | ||
+ | ?id ^property:HasObject/property:IsInstanceOf process:OSLd0c734a239844a0d8820856add12aeca | ||
+ | optional { ?id property:HasId ?local_id } | ||
+ | optional { ?id property:Display_title_of ?label } | ||
+ | optional { ?id ^property:HasPredecessor/property:IsProcessParameterOf/property:HasOutput ?successor_object_semistatic } | ||
+ | optional { ?id ^property:HasPredecessor/property:IsObjectParameterOf ?successor_object } | ||
+ | optional { ?id ^property:HasObject/^property:IsOutputOf ?process_output } | ||
+ | optional { ?id ^property:HasInput/property:HasOutput ?successor_object_static } | ||
+ | optional { ?id rdf:type ?category } | ||
+ | optional { ?id property:IsInstanceOf ?type } | ||
+ | } | ||
+ | LIMIT 10000 | ||
+ | ]] | ||
+ | |||
+ | local rtbl = object:query( query ) | ||
+ | mw.logObject(rtbl) | ||
+ | while( row ~= nil ) | ||
+ | do | ||
+ | if row['subject_label'] then --set subject label | ||
+ | text = text .. '"' .. row['subject'] .. '"' .. '[label="'.. row['subject_label'] .. '" href="' .. row['subject'] .. '"]' .. newline | ||
+ | end | ||
+ | i = i + 1 | ||
+ | row = rtbl['result']['rows'][i] | ||
+ | end | ||
+ | return result | ||
+ | end | ||
+ | -- expands single values for properties to tables with len 1 | ||
+ | function p.toTable(v) | ||
+ | if (v == nil) then return nil end | ||
+ | if (type(v) ~= "table") then | ||
+ | v = {v} | ||
+ | end | ||
+ | return v | ||
+ | end | ||
+ | |||
+ | function p.tablelength(T) | ||
+ | local count = 0 | ||
+ | for _ in pairs(T) do count = count + 1 end | ||
+ | return count | ||
+ | end | ||
+ | |||
+ | function p.concatTables(t1,t2) | ||
+ | t1 = p.toTable(t1) | ||
+ | t2 = p.toTable(t2) | ||
+ | if (t1 == nil) then | ||
+ | t1 = t2 | ||
+ | elseif (t2 ~= nil) then | ||
+ | for _,v in ipairs(t2) do | ||
+ | table.insert(t1, v) | ||
+ | end | ||
+ | end | ||
+ | return t1 | ||
+ | end | ||
+ | |||
+ | function p.tableContains(t,v) | ||
+ | if (t == nil or v == nil) then return false end | ||
+ | local contained = false | ||
+ | for _,_v in ipairs(t) do | ||
+ | if (_v == v) then contained = true end | ||
+ | end | ||
+ | return contained | ||
+ | end | ||
+ | |||
+ | function p.removeDuplicates(t) | ||
+ | if (t == nil) then return nil end | ||
+ | local hash = {} | ||
+ | local res = {} | ||
+ | for _,v in ipairs(t) do | ||
+ | if (not hash[v]) then | ||
+ | res[#res+1] = v | ||
+ | hash[v] = true | ||
+ | end | ||
+ | end | ||
+ | return res | ||
end | end | ||
return p | return p |
Latest revision as of 06:02, 11 July 2022
Documentation for this module may be created at Module:LabProcess/Progress/doc
-- creates an overview over a process chain
-- Module:SMW
local p = {}
local linkedwiki = require 'linkedwiki'
function p.GetProcessesGraph()
local process_types = {"LabProcess:OSL9a645a64b15442398ad3c057e1b64d87", "LabProcess:OSL4c1f7444e389471a8250f53407191735", "LabProcess:OSLd0c734a239844a0d8820856add12aeca"}
--local process_types = {"LabProcess:OSL4c1f7444e389471a8250f53407191735", "LabProcess:OSLd0c734a239844a0d8820856add12aeca"}
local result = p.GetProcesses(process_types)
--mw.logObject(result)
local d = mw.html.create( 'div' )
d:attr( 'class', 'graphviz' )
local g = [[
digraph G {
rankdir=LR
]]
local edges = ""
for id,process in pairs(result) do
--mw.logObject(process)
local nodes = ""
local nodes2 = ""
for obj_id, obj in pairs(process['objects']) do
if p.IsGlobalObject(obj) then
local successors = nil
successors = p.concatTables(obj['successor_object'], obj['successor_object_static'])
--if (successors == nil) then successors = p.toTable(obj['successor_object_semistatic']) end
if (successors == nil and p.tableContains(p.toTable(obj['process_output']),obj_id) == false) then successors = p.toTable(obj['process_output']) end
successors = p.removeDuplicates(successors)
obj['successors'] = successors
successors = p.GetNextGlobalObject(result, obj);
--if (obj['successor_object'] ~= nil) then
-- successors = p.toTable(obj['successor_object']);
--end
--if (obj['successor_process_output'] ~= nil) then
-- _successors = p.toTable(obj['successor_process_output']);
-- if (p.tablelength(_successors) == 1) then successors = _successors end
--end
local color = "black"
if (successors ~= nil) then
color = "green"
for succ_id, succ in pairs(successors) do
edges = edges .. '"' .. obj_id .. '" -> "' .. succ .. '";'
end
nodes2 = nodes2 .. '"' .. obj_id .. '" [color=' .. color .. ' label="' .. obj['label'] .. '" href="/wiki/' .. obj_id .. '"];'
else
nodes = nodes .. '"' .. obj_id .. '" [color=' .. color .. ' label="' .. obj['label'] .. '" href="/wiki/' .. obj_id .. '"];'
end
end
end
g = g .. [[
subgraph cluster_]] .. id:gsub(":","_") .. [[ {
node [shape=rect];
label = "]] .. process['label'] .. [[";
color=blue;
]] .. nodes .. [[
]] .. nodes2 .. [[
}
]]
end
g = g .. [[
]] .. edges .. [[
}]]
d:wikitext(g)
mw.logObject(d)
return tostring( d )
end
function p.GetProcesses(process_types)
local results = {}
for key,process_type in pairs(process_types) do
results[process_type] = {}
results[process_type]['objects'] = {}
--result = p.GetOutputs(process_type)
result = p.GetProcessObjects(process_type)
if (result ~= nil) then
for index, obj in pairs(result) do
--mw.logObject(obj['id'])
results[process_type]['objects'][obj['id']] = obj
end
end
end
local process_type_query = "[[" .. table.concat(process_types, "]] OR [[") .. "]]"
process_type_query = process_type_query .. " |?Display title of#-=label |?#-=process_type |?HasId#-=id |format=plain"
--mw.logObject(process_type_query)
local process_type_infos = mw.smw.ask( process_type_query )
for key,process_type_info in pairs(process_type_infos) do
--mw.logObject(process_type_info)
process_type = process_type_info['process_type']
if (process_type ~= nil) then
for k,v in pairs(process_type_info) do
if (k ~= "process_type") then results[process_type][k] = v end
end
end
end
--mw.logObject(results)
return results
end
function p.GetNextGlobalObject(dataset, src_obj)
if (src_obj['successors'] == nil) then return nil end
local result = {}
for id,process in pairs(dataset) do
for obj_id, obj in pairs(process['objects']) do
for succ_id, succ in pairs(src_obj['successors']) do
--mw.log("Compare " .. obj_id .. " with " .. succ)
if obj_id == succ then
local isGlobal = p.IsGlobalObject(obj)
if isGlobal == false then
--mw.log("Follow " .. obj_id)
p.concatTables(result,p.GetNextGlobalObject(dataset, obj))
elseif (succ ~= nil) then
--mw.log("Found " .. succ)
table.insert(result, succ)
end
end
end
end
end
return result
end
function p.IsGlobalObject(obj)
local isGlobal = false
local visible_types = {"Material:OSL344e5c0f20e04894abe53678e2f68351"}
for category_id, category in pairs(p.toTable(obj['category'])) do
if (category == "Category:LabProcessOutput") then isGlobal = true end
end
if (obj['type'] ~= nil) then
for obj_type_id, obj_type in pairs(p.toTable(obj['type'])) do
for vis_type_id, vis_type in pairs(p.toTable(visible_types)) do
if (obj_type == vis_type) then isGlobal = true end
end
end
end
return isGlobal
end
function p.GetOutputs(process_type)
--mw.logObject(obj)
-- please note the unlinking of properties 'page authors', and mainlabel '?' by using the #- operator
-- note also: unlinking via parameter link is not supported at this point
--local process_type = "LabProcess:OSL9a645a64b15442398ad3c057e1b64d87"
local query = "[[Category:LabProcessOutput]] [[-HasObject.IsInstanceOf::" .. process_type .. "]] " .. [[
|?#-=id
|?HasId#-=local_id
|?Display title of#-=label
|?-HasPredecessor#-=successor_param
|?-HasPredecessor.IsProcessParameterOf#-=successor_subprocess
|?-HasPredecessor.IsObjectParameterOf#-=successor_object
|?-HasPredecessor.IsProcessParameterOf.IsSubprocessOf#-=successor_process
|?-HasPredecessor.IsProcessParameterOf.IsSubprocessOf.-IsOutputOf#-=successor_process_output
|format=plain
|limit=10000
]]
local result = mw.smw.ask( query )
--mw.logObject(result)
return result
end
function p.GetProcessObjects(process_types)
--local process_types = {"LabProcess:OSL9a645a64b15442398ad3c057e1b64d87", "LabProcess:OSL4c1f7444e389471a8250f53407191735", "LabProcess:OSLd0c734a239844a0d8820856add12aeca"}
process_types = p.toTable(process_types)
local query = "[[-HasObject.IsInstanceOf::" .. table.concat(process_types, "]] OR [[-HasObject.IsInstanceOf::") .. "]]"
query = query .. [[
|?#-=id
|?HasId#-=local_id
|?Display title of#-=label
|?-HasPredecessor.IsProcessParameterOf.HasOutput#-=successor_object_semistatic
|?-HasPredecessor.IsObjectParameterOf#-=successor_object
|?-Has subobject.-IsOutputOf#-=process_output
|?-HasInput.HasOutput#-=successor_object_static
|?Category#-=category
|?IsInstanceOf#-=type
|format=plain
|limit=10000
]]
local result = mw.smw.ask( query )
--mw.logObject(result)
return result
end
function p.GetProcessObjectsSPARQL(process_types)
--local process_types = {"LabProcess:OSL9a645a64b15442398ad3c057e1b64d87", "LabProcess:OSL4c1f7444e389471a8250f53407191735", "LabProcess:OSLd0c734a239844a0d8820856add12aeca"}
local object = linkedwiki.new()
object:setConfig("https://kiprobatt.de")
mw.log(object:getConfig())
local query = [[
PREFIX dc: <http://purl.org/dc/elements/1.1/>
PREFIX property: <https://kiprobatt.de/id/Property-3A>
PREFIX file: <https://kiprobatt.de/id/File-3A>
PREFIX category: <https://kiprobatt.de/id/Category-3A>
PREFIX material: <https://kiprobatt.de/id/Material-3A>
PREFIX process: <https://kiprobatt.de/id/LabProcess-3A>
PREFIX wiki: <https://kiprobatt.de/id/>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
PREFIX swivt: <http://semantic-mediawiki.org/swivt/1.0#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
SELECT ?id ?local_id ?label ?successor_object_semistatic ?successor_object ?process_output ?successor_object_static ?category ?type
WHERE {
?id ^property:HasObject/property:IsInstanceOf process:OSLd0c734a239844a0d8820856add12aeca
optional { ?id property:HasId ?local_id }
optional { ?id property:Display_title_of ?label }
optional { ?id ^property:HasPredecessor/property:IsProcessParameterOf/property:HasOutput ?successor_object_semistatic }
optional { ?id ^property:HasPredecessor/property:IsObjectParameterOf ?successor_object }
optional { ?id ^property:HasObject/^property:IsOutputOf ?process_output }
optional { ?id ^property:HasInput/property:HasOutput ?successor_object_static }
optional { ?id rdf:type ?category }
optional { ?id property:IsInstanceOf ?type }
}
LIMIT 10000
]]
local rtbl = object:query( query )
mw.logObject(rtbl)
while( row ~= nil )
do
if row['subject_label'] then --set subject label
text = text .. '"' .. row['subject'] .. '"' .. '[label="'.. row['subject_label'] .. '" href="' .. row['subject'] .. '"]' .. newline
end
i = i + 1
row = rtbl['result']['rows'][i]
end
return result
end
-- expands single values for properties to tables with len 1
function p.toTable(v)
if (v == nil) then return nil end
if (type(v) ~= "table") then
v = {v}
end
return v
end
function p.tablelength(T)
local count = 0
for _ in pairs(T) do count = count + 1 end
return count
end
function p.concatTables(t1,t2)
t1 = p.toTable(t1)
t2 = p.toTable(t2)
if (t1 == nil) then
t1 = t2
elseif (t2 ~= nil) then
for _,v in ipairs(t2) do
table.insert(t1, v)
end
end
return t1
end
function p.tableContains(t,v)
if (t == nil or v == nil) then return false end
local contained = false
for _,_v in ipairs(t) do
if (_v == v) then contained = true end
end
return contained
end
function p.removeDuplicates(t)
if (t == nil) then return nil end
local hash = {}
local res = {}
for _,v in ipairs(t) do
if (not hash[v]) then
res[#res+1] = v
hash[v] = true
end
end
return res
end
return p