Module:Requests/union of

From Wikidata
Jump to navigation Jump to search
Lua
CodeDiscussionLinksLink count SubpagesDocumentationTestsResultsSandboxLive code All modules

Example of calls:

{{#invoke:Requests/union_of|put_queries|id=Q11163999}}
{{#invoke:Requests/union_of|put_queries|id=Q188745}}


Translations of resulting wikitext possible in:



Code

local f = require("Module:Functional")
local requests = require("Module:Requests")
local fun = require("Module:Luafun")
local props = require("Module:properties")

local union = {}


-- test item for "union of" : Q11163999
-- test item for "disjoint union of" : Q188745


-- "constant", used as a default name for the query result variable
local var_name = "?item"

----------------------------------------------------------------
-- utility functions
----------------------------------------------------------------

-- Utility : local functions, dependant of the constant above (transformation into one parameter function of existing functions)

local function instances(class) 
	return requests.instances(var_name, class)
end

local function subclasses_instances(subclass_qidlist) 
	return requests.union(f.map(instances, subclass_qidlist))
end

-- utility : stack manipulation functions

local function pop(list)
		local elem = list[#list]
		table.remove(list, #list)
		return elem, list
end

union.pop = pop

local function push(list, elem)
	table.insert(list, elem)
end

union.push = push

---------------------------------
-- query generators
---------------------------------

-- Union of query : verify if each of the instances of the subject class 
-- is an instance of at least one of the child class, as present as values of the "of" qualifier of the statement 

-- Sample result for not_in_union("Q79529", {"Q11173", "Q2512777"}) ) :
--select ?item where {
--  ?item wd:P279*/wd:P31 wd:Q79529  minus {
--        { ?item wdt:P279*/wdt:P31 wd:Q11173 } union { ?item wdt:P279*/wdt:P31 wd:Q2512777 }
--  }
--}

function union.not_in_union(class_qid, subclass_qidlist) 
	local out_items = requests.minus(instances(class_qid), subclasses_instances(subclass_qidlist))
	return requests.pi({var_name}, out_items)
end


-- disjoint union of query : verify that no instance of one subclas is an instance of another subclass 
-- is an instance of at least one of the child class, as present as values of the "of" qualifier of the statement 

-- Sample result for not_in_union("Q5", {"Q51", "Q52"}) ) :

--select ?item where {
--  ?item wdt:P279*/wdt:P31 wd:Q5 .
--  
--  {
--    ?item  wdt:P279*/wdt:P31 wd:Q51 .
--    ?item  wdt:P279*/wdt:P31 wd:Q52 .
--  } union {
--    { ?item  wdt:P279*/wdt:P31 wd:Q51 } union { ?item wdt:P279*/wdt:P31 wd:Q52 } .
--    ?item  wdt:P279*/wdt:P31 wd:Q53 .
--  }
--}

function union.disjoint(class_qid, subclass_qidlist)
	local intersected = {}
	local to_union = {}
	
	--init : the first element is automatically intersected with itself
	push(intersected, pop(subclass_qidlist))
	
	while #subclass_qidlist>0 do
		local elem, subclass_qidlist = pop(subclass_qidlist)
		local intersect_with_elem_instances = requests.intersect({subclasses_instances(intersected), instances(elem)})
		push(to_union, intersect_with_elem_instances)
		push(intersected, elem)
	end
	
	return requests.pi({var_name}, requests.intersect({instances(class_qid), requests.union(to_union)})) 
end


------------------------------------------------------
-- function that generates a pair of query with their description for documentation
------------------------------------------------------

-- returns a list of elements from a list of lists of elements
-- composes by the elements of the sublists

local function flatten(list)
	return fun.reduce(fun.chain, {}, list):totable()
end

local function call_template(template, class_qid, subclass_qidlist)
	return mw.getCurrentFrame():expandTemplate{
		title=template,
		args=flatten({{class_qid}, subclass_qidlist})
	}
end

local function describe_not_in_union(class_qid, subclass_qidlist) 
	return call_template("doc not in union", class_qid, subclass_qidlist)
end

local function describe_disjoint(class_qid, subclass_qidlist) 
	return call_template("doc disjoint union", class_qid, subclass_qidlist)
end

local function described_not_in_union(class_qid, subclass_qidlist)
	local res = {
		["query"] = union.not_in_union(class_qid, subclass_qidlist);
		["descr"] = describe_not_in_union(class_qid, subclass_qidlist);
	}
	return res
end

local function described_disjoint(class_qid, subclass_qidlist)
	local res = {
		["query"] = union.disjoint(class_qid, subclass_qidlist);
		["descr"] = describe_disjoint(class_qid, subclass_qidlist);
	}
	return res
end


-- constants  

-- map of query generation function list relevant for a given property
local prop_query_map = { 
	-- for a disjoint union : verify both
	[props.disjoint_union_of] = {described_disjoint; described_not_in_union} ;
	-- for a simple union : verify only the covering of the subclasses wrt. the parent one
	[props.union_of] = {described_not_in_union} ;
}

--------------------------------------------------------------
-- list of queries generators
--------------------------------------------------------------

-- get a list of relevant queries from a statement
function union.queries_from_statement(stmt, item_qid, query_gens)
	local ids = fun.map(
		function(snak) return "Q" .. tostring(snak.datavalue.value["numeric-id"]) end, 
		stmt.qualifiers[props.list_item]
	)
	
	-- apply each generator for this kind of statements to get a list of queries
	local described_queries = fun.map(function (generator) return generator(item_qid, ids:totable()) end, 
		query_gens)
	
	return described_queries
end

local function getRelevantProperties(item)
	local props = item:getProperties() -- all properties used in statement of the item
	local present_claims = fun.filter( -- filter those who are not in our list (see prop_query_map )
		function (prop) return (prop_query_map[prop] ~= nil) end,
		props)
	return present_claims
end

function union.has_union_claims(item)
	return getRelevantProperties(item):length() > 0
end

local function load_item(item_or_qid)
	if type(item_or_qid) == "table" then 
		return item_or_qid
	else 
		return mw.wikibase.getEntity(item_or_qid)
	end
end

-- get a list of all relevant queries from a whole item
function union.queries(item_qid) 
	-- loading item datas
	local item = load_item(item_qid)
	local queries = {}
	
	-- compute the id of the subject class
	local id = item.id

	-- for each statements of a relevant property, get a list of queries, and flatten this into a list of queries for each property
	queries = getRelevantProperties(item):map(
		function(property) 
			return fun.map(
				function(claim)
					return union.queries_from_statement(claim, id, prop_query_map[property])
				end, 
				item.claims[property]
			):reduce(fun.chain, {})
	    end
	)

	-- flatten the list of queries for each property into a plain list of query 
	return flatten(queries)
end

------------------------------------------------------------
-- Interface : generate wikitext
------------------------------------------------------------

-- Interface auxiliary : arguments cleanup

local function args(frame)
	local cleanargs ={}
	
	for i, j in pairs(frame.args) do
		if j ~= '' then cleanargs[i] = j end
	end

	if not cleanargs.id then
		cleanargs["id"] = frame:preprocess("{{BASEPAGENAME}}")
	end
	return cleanargs
end


-- computes a list of queries formatted in wikitext ; to be used in item doc related templates
function union.put_queries(frame)
	local res = ""
	local cleanargs = args(frame)
	
	for _, query in pairs(union.queries(cleanargs["id"])) do
		res = res .. "*"  .. query.descr .. frame:expandTemplate{title="Wdquery", args={["query"] = query.query}}  .. "\n"
	end
	
	return res
end

-- Documentation : get a list of localized values the "of" arguments qualifiers
function union.doc_class_list(frame)
	
	local classes = frame:getParent().args
	
	-- apply Q' on each classes and get a linguistic conjuction
	return mw.text.listToText(
		fun.tail(classes)                                              -- the first argument is the parent class ; remove it
		:filter(function(elem) return elem ~= nil and elem ~= "" end)  -- clean empty arguments
		:map(
			function(class) return frame:expandTemplate{title="Q'", args = {class}} end
		)
		:totable()
    )
end

function union.has_queries(frame)
	local cleanargs = args(frame)
	local item=load_item(cleanargs.id)
	
	if union.has_union_claims(item) then 
		return "yes" 
	else 
		return ""
	end
end

return union