Smaller changes

This commit is contained in:
Jakob Kordež 2024-06-24 09:08:37 +02:00
parent 0f06cb8b83
commit e968b4d344
3 changed files with 49 additions and 226 deletions

View File

@ -1,208 +0,0 @@
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")))

View File

@ -12,6 +12,9 @@ export class TrieNode {
else this.id = ++nodeCounter;
}
/**
* Insert a prefix into the trie.
*/
insert(prefix: string, entity: number): void {
if (!prefix) {
if (this.entity && this.entity !== entity)
@ -27,12 +30,18 @@ export class TrieNode {
next.insert(prefix.slice(1), entity);
}
/**
* Traverse the trie to find the node that matches the prefix.
*/
findRaw(prefix: string): TrieNode | null {
if (!prefix) return this;
const next = this.children.get(prefix[0]);
return next ? next.findRaw(prefix.slice(1)) : null;
}
/**
* Returns all the nodes in the trie.
*/
getAllNodes(): Set<TrieNode> {
const nodes: TrieNode[] = [this];
for (const child of this.children.values()) {
@ -41,6 +50,9 @@ export class TrieNode {
return new Set(nodes);
}
/**
* Checks if this node can be merged with another node.
*/
canMerge(other: TrieNode): boolean {
if (this === other) return false;
if (this.entity !== other.entity) return false;
@ -54,6 +66,9 @@ export class TrieNode {
return true;
}
/**
* Returns an encoded string of the whole trie.
*/
encodeToString(): string {
return [...this.getAllNodes()].map((n) => n._encodeToString()).join('\n');
}
@ -74,6 +89,9 @@ export class TrieNode {
return s.join('\n');
}
/**
* Decodes a trie from an encoded string.
*/
static decodeFromString(s: string): TrieNode {
let root: TrieNode | null = null;
const nodes: Map<number, TrieNode> = new Map();

View File

@ -1,29 +1,33 @@
<script lang="ts">
import { getSecondarySuffixDescription, parseCallsign } from '$lib/callsign';
import { dxccEntities } from '$lib/dxcc-util';
import { dxccEntities, findDxcc } from '$lib/dxcc-util';
import CallsignInput from '../components/callsign-input.svelte';
const baseClass = 'text-red-400';
const prefixClass = 'text-blue-400';
const suffixClass = 'text-green-400';
let callsign = '9a/s52kj/p';
$: callsignData = parseCallsign(callsign);
$: rawDxcc = findDxcc(callsign);
$: suffixPartOf = [null, 'base', 'prefix'].indexOf(callsignData?.suffixPartOf ?? null);
function styleText(): string {
if (!callsignData) return callsign;
const baseClass = 'text-red-400';
const prefixClass = 'text-blue-400';
if (!callsignData) {
const dxcc = findDxcc(callsign);
if (!dxcc) return callsign;
return `<span class="${baseClass}">${callsign.slice(0, dxcc.prefixLength)}</span>${callsign.slice(dxcc.prefixLength)}`;
}
const { base, basePrefix, baseSuffix, secondaryPrefix, secondarySuffix } = callsignData;
// TODO Check if base and prefix same dxcc
const suffClass = [suffixClass, baseClass, prefixClass][suffixPartOf];
const suffixClass = ['text-green-400', baseClass, prefixClass][suffixPartOf];
return [
secondaryPrefix ? `<span class="${prefixClass}">${secondaryPrefix}/</span>` : '',
basePrefix ? `<span class="${baseClass}">${basePrefix}</span>${baseSuffix}` : base,
secondarySuffix ? `<span class="${suffClass}">/${secondarySuffix}</span>` : ''
secondarySuffix ? `<span class="${suffixClass}">/${secondarySuffix}</span>` : ''
].join('');
}
</script>
@ -32,7 +36,7 @@
<h1 class="text-center text-3xl font-medium">Callsign Tester</h1>
<div>
<div class="text-center">Enter a callsign</div>
<div class="mb-1 text-center">Enter a callsign</div>
<CallsignInput bind:inputText={callsign} generateStyledText={styleText} />
</div>
@ -40,40 +44,49 @@
<div class="flex flex-col gap-4 md:flex-row">
{#if callsignData.prefixDxcc}
<div class="data-box prefix">
<h2 class="text-xl">Secondary prefix</h2>
<h2>Secondary prefix</h2>
<div class="font-mono text-2xl font-medium">{callsignData.secondaryPrefix}</div>
<div>{dxccEntities.get(callsignData.prefixDxcc)?.name}</div>
<div>{dxccEntities.get(callsignData.prefixDxcc)?.name ?? '?'}</div>
</div>
{/if}
{#if callsignData.baseDxcc}
<div class="data-box base">
<h2 class="text-xl">Prefix</h2>
<h2>Prefix</h2>
<div class="font-mono text-2xl font-medium">{callsignData.basePrefix}</div>
<div>{dxccEntities.get(callsignData.baseDxcc)?.name}</div>
<div>{dxccEntities.get(callsignData.baseDxcc)?.name ?? '?'}</div>
</div>
{/if}
{#if callsignData.secondarySuffix}
<div class={`data-box ${['suffix', 'base', 'prefix'][suffixPartOf]}`}>
<h2 class="text-xl">Secondary suffix</h2>
<h2>Secondary suffix</h2>
<div class="font-mono text-2xl font-medium">{callsignData.secondarySuffix}</div>
<div>{getSecondarySuffixDescription(callsignData) ?? ''}</div>
</div>
{/if}
</div>
{:else if rawDxcc}
<div class="data-box base">
<h2>Prefix</h2>
<div class="font-mono text-2xl font-medium">{callsign.slice(0, rawDxcc.prefixLength)}</div>
<div>{dxccEntities.get(rawDxcc.entity)?.name ?? '?'}</div>
</div>
{/if}
</div>
<style lang="postcss">
.data-box {
@apply flex-1 rounded-xl p-4;
@apply flex-1 rounded-xl p-4 text-center;
}
.data-box > h2 {
@apply text-sm;
}
.data-box.prefix {
@apply bg-blue-700/40;
@apply bg-blue-600/40;
}
.data-box.base {
@apply bg-red-700/40;
@apply bg-red-600/40;
}
.data-box.suffix {
@apply bg-green-700/40;
@apply bg-green-600/40;
}
</style>