# Pre order sets and partial order set (ie hierarchies)
module poset
+import serialization
+
# Pre-order set graph.
# This class models an incremental pre-order graph where new nodes and edges can be added (but not removed).
# Pre-order graph has two characteristics:
#
# Thanks to the `[]` method, elements can be considered relatively to the poset.
# SEE `POSetElement`
-class POSet[E: Object]
+class POSet[E]
super Collection[E]
super Comparator
+ super Cloneable
+ super Serializable
redef type COMPARED: E is fixed
# Update the transitive reduction
if te.tos.has(f) then return # Skip the reduction if there is a loop
- for x in te.dfroms.to_a do
+ # Remove transitive edges.
+ # Because the sets of direct is iterated, the list of edges to remove
+ # is stored and is applied after the iteration.
+ # The usual case is that no direct edges need to be removed,
+ # so start with a `null` list of edges.
+ var to_remove: nullable Array[E] = null
+ for x in te.dfroms do
var xe = self.elements[x]
if xe.tos.has(f) then
- te.dfroms.remove(x)
+ if to_remove == null then to_remove = new Array[E]
+ to_remove.add x
xe.dtos.remove(t)
end
end
- for x in fe.dtos.to_a do
+ if to_remove != null then
+ for x in to_remove do te.dfroms.remove(x)
+ to_remove.clear
+ end
+
+ for x in fe.dtos do
var xe = self.elements[x]
if xe.froms.has(t) then
xe.dfroms.remove(f)
- fe.dtos.remove(x)
+ if to_remove == null then to_remove = new Array[E]
+ to_remove.add x
end
end
+ if to_remove != null then
+ for x in to_remove do fe.dtos.remove(x)
+ end
+
fe.dtos.add t
te.dfroms.add f
end
# Write the POSet as a graphviz digraph.
#
- # Nodes are identified with their `to_s`.
+ # Nodes are labeled with their `to_s` so homonymous nodes may appear.
# Edges are unlabeled.
- fun write_dot(f: OStream)
+ fun write_dot(f: Writer)
do
f.write "digraph \{\n"
+ var ids = new HashMap[E, Int]
for x in elements.keys do
- var xstr = x.to_s.escape_to_dot
- f.write "\"{xstr}\";\n"
+ ids[x] = ids.length
+ end
+ for x in elements.keys do
+ var xstr = (x or else "null").to_s.escape_to_dot
+ var nx = "n{ids[x]}"
+ f.write "{nx}[label=\"{xstr}\"];\n"
var xe = self.elements[x]
for y in xe.dtos do
- var ystr = y.to_s.escape_to_dot
+ var ny = "n{ids[y]}"
if self.has_edge(y,x) then
- f.write "\"{xstr}\" -> \"{ystr}\"[dir=both];\n"
+ f.write "{nx} -> {ny}[dir=both];\n"
else
- f.write "\"{xstr}\" -> \"{ystr}\";\n"
+ f.write "{nx} -> {ny};\n"
end
end
end
# See `write_dot` for details.
fun show_dot
do
- var f = new OProcess("dot", "-Txlib")
- f.write "\}\n"
+ var f = new ProcessWriter("dot", "-Txlib")
write_dot(f)
f.close
f.wait
# ~~~~
#
# Note that the linear extension is stable, unless a new node or a new edge is added.
- redef fun compare(a, b: E): Int
+ redef fun compare(a, b)
do
var ae = self.elements[a]
var be = self.elements[b]
sort(lin)
return lin
end
+
+ redef fun clone do return sub(self)
+
+ # Return an induced sub-poset
+ #
+ # The elements of the result are those given in argument.
+ #
+ # ~~~
+ # var pos = new POSet[String]
+ # pos.add_chain(["A", "B", "C", "D", "E"])
+ # pos.add_chain(["A", "X", "C", "Y", "E"])
+ #
+ # var pos2 = pos.sub(["A", "B", "D", "Y", "E"])
+ # assert pos2.has_exactly(["A", "B", "D", "Y", "E"])
+ # ~~~
+ #
+ # The full relationship is preserved between the provided elements.
+ #
+ # ~~~
+ # for e1 in pos2 do for e2 in pos2 do
+ # assert pos2.has_edge(e1, e2) == pos.has_edge(e1, e2)
+ # end
+ # ~~~
+ #
+ # Not that by definition, the direct relationship is the transitive
+ # reduction of the full reduction. Thus, the direct relationship of the
+ # sub-poset may not be included in the direct relationship of self because an
+ # indirect edge becomes a direct one if all the intermediates elements
+ # are absent in the sub-poset.
+ #
+ # ~~~
+ # assert pos.has_direct_edge("B", "D") == false
+ # assert pos2.has_direct_edge("B", "D") == true
+ #
+ # assert pos2["B"].direct_greaters.has_exactly(["D", "Y"])
+ # ~~~
+ #
+ # If the `elements` contains all then the result is a clone of self.
+ #
+ # ~~~
+ # var pos3 = pos.sub(pos)
+ # assert pos3 == pos
+ # assert pos3 == pos.clone
+ # ~~~
+ fun sub(elements: Collection[E]): POSet[E]
+ do
+ var res = new POSet[E]
+ for e in self do
+ if not elements.has(e) then continue
+ res.add_node(e)
+ end
+ for e in res do
+ for f in self[e].greaters do
+ if not elements.has(f) then continue
+ res.add_edge(e, f)
+ end
+ end
+ return res
+ end
+
+ # Two posets are equal if they contain the same elements and edges.
+ #
+ # ~~~
+ # var pos1 = new POSet[String]
+ # pos1.add_chain(["A", "B", "C", "D", "E"])
+ # pos1.add_chain(["A", "X", "C", "Y", "E"])
+ #
+ # var pos2 = new POSet[Object]
+ # pos2.add_edge("Y", "E")
+ # pos2.add_chain(["A", "X", "C", "D", "E"])
+ # pos2.add_chain(["A", "B", "C", "Y"])
+ #
+ # assert pos1 == pos2
+ #
+ # pos1.add_edge("D", "Y")
+ # assert pos1 != pos2
+ #
+ # pos2.add_edge("D", "Y")
+ # assert pos1 == pos2
+ #
+ # pos1.add_node("Z")
+ # assert pos1 != pos2
+ # ~~~
+ redef fun ==(other) do
+ if not other isa POSet[nullable Object] then return false
+ if not self.elements.keys.has_exactly(other.elements.keys) then return false
+ for e, ee in elements do
+ if ee.direct_greaters != other[e].direct_greaters then return false
+ end
+ assert hash == other.hash
+ return true
+ end
+
+ redef fun hash
+ do
+ var res = 0
+ for e, ee in elements do
+ if e == null then continue
+ res += e.hash
+ res += ee.direct_greaters.length
+ end
+ return res
+ end
+
+ redef fun core_serialize_to(serializer)
+ do
+ # Optimize written data because this structure has duplicated data
+ # For example, serializing the class hierarchy of a simple program where E is String
+ # result is before: 200k, after: 56k.
+ serializer.serialize_attribute("elements", elements)
+ end
+
+ redef init from_deserializer(deserializer)
+ do
+ deserializer.notify_of_creation self
+ var elements = deserializer.deserialize_attribute("elements")
+ if elements isa HashMap[E, POSetElement[E]] then
+ self.elements = elements
+ end
+ end
end
-# View of an objet in a poset
+# View of an object in a poset
# This class is a helper to handle specific queries on a same object
#
# For instance, one common usage is to add a specific attribute for each poset a class belong.
#
-# class Thing
-# var in_some_relation: POSetElement[Thing]
-# var in_other_relation: POSetElement[Thing]
-# end
-# var t: Thing # ...
-# t.in_some_relation.greaters
-#
-class POSetElement[E: Object]
+# ~~~nitish
+# class Thing
+# var in_some_relation: POSetElement[Thing]
+# var in_other_relation: POSetElement[Thing]
+# end
+# var t: Thing
+# # ...
+# t.in_some_relation.greaters
+# ~~~
+class POSetElement[E]
+ super Serializable
+
# The poset self belong to
var poset: POSet[E]
end
end
return min
+ end
+
+ redef fun core_serialize_to(serializer)
+ do
+ serializer.serialize_attribute("poset", poset)
+ serializer.serialize_attribute("element", element)
+ serializer.serialize_attribute("tos", tos)
+ serializer.serialize_attribute("froms", froms)
+ serializer.serialize_attribute("dtos", dtos)
+ serializer.serialize_attribute("dfroms", dfroms)
+ serializer.serialize_attribute("count", count)
+ # Don't serialize `froms`, `dtos` and `tos` as they duplicate information.
+ # TODO serialize them if a flag for extra info is set on `serializer`.
+ end
+
+ redef init from_deserializer(v)
+ do
+ # Code generated by the serialization_phase from the compiler frontend,
+ # copied here for compatibility with nith.
+
+ super
+ v.notify_of_creation self
+
+ var poset = v.deserialize_attribute("poset", "POSet[nullable Object]")
+ if v.deserialize_attribute_missing then
+ v.errors.add new Error("Deserialization Error: attribute `{class_name}::poset` missing from JSON object")
+ else if not poset isa POSet[E] then
+ v.errors.add new AttributeTypeError(self, "poset", poset, "POSet[nullable Object]")
+ if v.keep_going == false then return
+ else
+ self.poset = poset
+ end
+
+ var element = v.deserialize_attribute("element", "nullable Object")
+ if v.deserialize_attribute_missing then
+ v.errors.add new Error("Deserialization Error: attribute `{class_name}::element` missing from JSON object")
+ else if not element isa E then
+ v.errors.add new AttributeTypeError(self, "element", element, "nullable Object")
+ if v.keep_going == false then return
+ else
+ self.element = element
+ end
+
+ var tos = v.deserialize_attribute("tos", "HashSet[nullable Object]")
+ if v.deserialize_attribute_missing then
+ else if not tos isa HashSet[E] then
+ v.errors.add new AttributeTypeError(self, "tos", tos, "HashSet[nullable Object]")
+ if v.keep_going == false then return
+ else
+ self.tos = tos
+ end
+
+ var froms = v.deserialize_attribute("froms", "HashSet[nullable Object]")
+ if v.deserialize_attribute_missing then
+ else if not froms isa HashSet[E] then
+ v.errors.add new AttributeTypeError(self, "froms", froms, "HashSet[nullable Object]")
+ if v.keep_going == false then return
+ else
+ self.froms = froms
+ end
+
+ var dtos = v.deserialize_attribute("dtos", "HashSet[nullable Object]")
+ if v.deserialize_attribute_missing then
+ else if not dtos isa HashSet[E] then
+ v.errors.add new AttributeTypeError(self, "dtos", dtos, "HashSet[nullable Object]")
+ if v.keep_going == false then return
+ else
+ self.dtos = dtos
+ end
+
+ var dfroms = v.deserialize_attribute("dfroms", "HashSet[nullable Object]")
+ if v.deserialize_attribute_missing then
+ else if not dfroms isa HashSet[E] then
+ v.errors.add new AttributeTypeError(self, "dfroms", dfroms, "HashSet[nullable Object]")
+ if v.keep_going == false then return
+ else
+ self.dfroms = dfroms
+ end
+
+ var count = v.deserialize_attribute("count", "Int")
+ if v.deserialize_attribute_missing then
+ v.errors.add new Error("Deserialization Error: attribute `{class_name}::count` missing from JSON object")
+ else if not count isa Int then
+ v.errors.add new AttributeTypeError(self, "count", count, "Int")
+ if v.keep_going == false then return
+ else
+ self.count = count
+ end
end
end