X-Git-Url: http://nitlanguage.org diff --git a/lib/poset.nit b/lib/poset.nit index 3288e5c..767b27d 100644 --- a/lib/poset.nit +++ b/lib/poset.nit @@ -17,6 +17,8 @@ # 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: @@ -74,6 +76,8 @@ module poset class POSet[E] super Collection[E] super Comparator + super Cloneable + super Serializable redef type COMPARED: E is fixed @@ -253,7 +257,7 @@ class POSet[E] ids[x] = ids.length end for x in elements.keys do - var xstr = x.to_s.escape_to_dot + 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] @@ -308,7 +312,7 @@ class POSet[E] # ~~~~ # # 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] @@ -380,9 +384,129 @@ class POSet[E] 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. @@ -397,6 +521,8 @@ end # t.in_some_relation.greaters # ~~~ class POSetElement[E] + super Serializable + # The poset self belong to var poset: POSet[E] @@ -512,6 +638,175 @@ class POSetElement[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 + +redef class MapRead[K, V] + # Return all elements of `keys` that have a value. + # + # ~~~ + # var map = new Map[String, String] + # map["A"] = "a" + # map["B"] = "b" + # map["C"] = "c" + # + # assert map.filter_keys(["B"]) == ["B"] + # assert map.filter_keys(["A", "Z", "C"]) == ["A", "C"] + # assert map.filter_keys(["X", "Y", "Z"]).is_empty + # ~~~ + # + # `has_key` is used to filter. + fun filter_keys(keys: Collection[nullable Object]): Array[K] + do + var res = new Array[K] + for e in keys do + if has_key(e) then res.add e + end + return res + end + + # Search all the values in `pe.greaters`. + # + # Elements without values are ignored. + # + # Basically, values defined in all greater elements of `pe` are inherited. + # + # ~~~ + # var pos = new POSet[String] + # pos.add_chain(["E", "D", "C", "B", "A"]) + # pos.add_chain(["D", "X", "B"]) + # + # var map = new HashMap[String, String] + # map["A"] = "a" + # map["C"] = "c" + # map["X"] = "x" + # map["E"] = "e" + # + # assert map.lookup_all_values(pos["B"]).has_exactly(["a"]) + # assert map.lookup_all_values(pos["C"]).has_exactly(["a", "c"]) + # assert map.lookup_all_values(pos["D"]).has_exactly(["a", "c", "x"]) + # ~~~ + fun lookup_all_values(pe: POSetElement[K]): Set[V] + do + var res = new Set[V] + for k in filter_keys(pe.greaters) do res.add self[k] + return res + end + + # Combine the values in `pe.greaters` from the most smaller elements that have a value. + # + # Elements without values are ignored. + # + # Basically, values defined in nearest greater elements of `pe` are inherited. + # + # ~~~ + # var pos = new POSet[String] + # pos.add_chain(["E", "D", "C", "B", "A"]) + # pos.add_chain(["D", "X", "B"]) + # + # var map = new HashMap[String, String] + # map["A"] = "a" + # map["C"] = "c" + # map["X"] = "x" + # map["E"] = "e" + # + # assert map.lookup_values(pos["B"]).has_exactly(["a"]) + # assert map.lookup_values(pos["C"]).has_exactly(["c"]) + # assert map.lookup_values(pos["D"]).has_exactly(["c", "x"]) + # ~~~ + fun lookup_values(pe: POSetElement[K]): Set[V] + do + var res = new Set[V] + for k in pe.poset.select_smallest(filter_keys(pe.greaters)) do res.add self[k] + return res end end