call-tester/scripts/clublog_prefix.py
2024-06-22 15:02:05 +02:00

209 lines
5.1 KiB
Python

import xml.etree.ElementTree as ET
from sys import argv
from collections import defaultdict
import re
root = ET.parse(argv[1]).getroot()
entities = {}
prefixes = []
def capitalize(s: str):
s = s.lower()
for m in re.findall(r"\b\w+", s):
s = s.replace(m, m.capitalize(), 1)
return s
for child in root:
if child.tag == "entities":
for entity in child:
name = adif = None
for prop in entity:
if prop.tag == "name":
name = prop.text
if prop.tag == "adif":
adif = int(prop.text)
entities[adif] = capitalize(name)
if child.tag == "prefixes":
for prefix in child:
call = entity = end = None
for prop in prefix:
if prop.tag == "call":
call = prop.text
if prop.tag == "adif":
entity = int(prop.text)
if prop.tag == "end":
end = prop.text
if end and end < "2024-06-20":
continue
# if entity != 1:
# continue
prefixes.append((call, entity))
# print(len(prefixes), "prefixes found")
# Build trie
class Node:
counter = 0
def __init__(self):
Node.counter += 1
self.id = Node.counter
self.parent: Node = None
self.children: dict[str, Node] = {}
self.entity: int = None
root = Node()
allNodes = []
# Build trie
def insert(node: Node, prefix, entity):
if not prefix:
if node.entity and node.entity != entity:
print(f"Conflict: {node.entity} vs {entity}")
node.entity = entity
return
nextNode = node.children.get(prefix[0])
if not nextNode:
nextNode = Node()
nextNode.parent = node
node.children[prefix[0]] = nextNode
allNodes.append(nextNode)
insert(nextNode, prefix[1:], entity)
for call, entity in prefixes:
insert(root, call, entity)
# Merge nodes
# print("Merge start,", len(allNodes), "nodes")
def canMerge(node: Node, other: Node):
if node.entity != other.entity:
return False
l = set(node.children.keys()).union(other.children.keys())
return all(node.children.get(k) == other.children.get(k) for k in l)
def merge(first: Node, second: Node):
# if first.id > second.id:
# first, second = second, first
for k, c in second.children.items():
c.parent = first
for k, c in second.parent.children.items():
if c == second:
second.parent.children[k] = first
allNodes.remove(second)
anyChanged = True
while anyChanged:
anyChanged = False
i = 0
while i < len(allNodes):
node = allNodes[i]
for other in allNodes:
if node != other and canMerge(node, other):
anyChanged = True
merge(node, other)
break
else:
i += 1
for i in range(len(allNodes)):
for j in range(i + 1, len(allNodes)):
if canMerge(allNodes[i], allNodes[j]):
raise Exception("Merge not completed")
# for n in allNodes:
# print(n.children.items())
# print("Merge done,", len(allNodes), "nodes left")
# Save compiled trie
for node in [root, *allNodes]:
if node.entity:
print(f"{node.id}={node.entity}")
for c in set(node.children.values()):
print(
f"{node.id}-{''.join(sorted(k for k, v in node.children.items() if v == c))}-{c.id}"
)
# Print trie
def rangeToStr(a: str, b: str):
if a == b:
return a
if ord(b) - ord(a) > 1:
return f"{a}-{b}"
return f"{a}{b}"
def toRange(s: list[str]):
s = sorted(s)
ranges = [(s[0], s[0])]
for c in s[1:]:
if ord(c) == ord(ranges[-1][1]) + 1:
ranges[-1] = (ranges[-1][0], c)
else:
ranges.append((c, c))
return "".join(rangeToStr(a, b) for a, b in ranges)
defined = set()
def genGraphvizNode(node: Node):
label = "" if not node.entity else entities[node.entity]
shape = "circle" if not node.entity else "box"
return f' {node.id} [label="{label}" shape="{shape}"];'
def toGraphviz(node: Node, root=True):
ret = []
if node in defined:
return ret
defined.add(node)
ret.append(genGraphvizNode(node))
dd = defaultdict(list)
for k, c in node.children.items():
dd[c.id].append(k.replace("/", "_"))
ret += toGraphviz(c, False)
for k in sorted(dd.keys()):
ret.append(f' {node.id} -> {k} [label="{toRange(dd[k])}"];')
if root:
curr = node
while curr.parent:
ret.append(genGraphvizNode(curr.parent))
label = toRange(k for k, v in curr.parent.children.items() if v == curr)
ret.append(f' {curr.parent.id} -> {curr.id} [label="{label}"];')
curr = curr.parent
ret = "\n".join(["digraph {", *ret, "}"])
return ret
def traverse(s: str, node: Node = root):
if not s:
return node
nextNode = node.children.get(s[0])
if not nextNode:
return None
return traverse(s[1:], nextNode)
# print(toGraphviz(traverse("R")))