Skip to content

Commit 4064481

Browse files
committed
perf: optimize mapping of category->tags
1 parent c14734d commit 4064481

File tree

2 files changed

+57
-78
lines changed

2 files changed

+57
-78
lines changed

src/tagstudio/core/library/alchemy/library.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1456,6 +1456,34 @@ def get_tag_color(self, slug: str, namespace: str) -> TagColorGroup | None:
14561456

14571457
return session.scalar(statement)
14581458

1459+
def get_tag_hierarchy(
1460+
self, tag_ids: Iterable[int]
1461+
) -> tuple[dict[int, list[int]], dict[int, Tag]]:
1462+
current_tag_ids: set[int] = set(tag_ids)
1463+
all_tag_ids: set[int] = set()
1464+
all_tags: dict[int, Tag] = {}
1465+
all_tag_parents: dict[int, list[int]] = {}
1466+
1467+
with Session(self.engine) as session:
1468+
while len(current_tag_ids) > 0:
1469+
all_tag_ids.update(current_tag_ids)
1470+
statement = select(TagParent).where(TagParent.parent_id.in_(current_tag_ids))
1471+
tag_parents = session.scalars(statement).fetchall()
1472+
current_tag_ids.clear()
1473+
for tag_parent in tag_parents:
1474+
all_tag_parents.setdefault(tag_parent.parent_id, []).append(tag_parent.child_id)
1475+
current_tag_ids.add(tag_parent.child_id)
1476+
current_tag_ids = current_tag_ids.difference(all_tag_ids)
1477+
1478+
statement = select(Tag).where(Tag.id.in_(all_tag_ids)).options(noload(Tag.parent_tags))
1479+
tags = session.scalars(statement).fetchall()
1480+
for tag in tags:
1481+
all_tags[tag.id] = tag
1482+
for tag in all_tags.values():
1483+
tag.parent_tags = {all_tags[p] for p in all_tag_parents.get(tag.id, [])}
1484+
1485+
return all_tag_parents, all_tags
1486+
14591487
def add_parent_tag(self, parent_id: int, child_id: int) -> bool:
14601488
if parent_id == child_id:
14611489
return False

src/tagstudio/qt/widgets/preview/field_containers.py

Lines changed: 29 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -158,86 +158,37 @@ def hide_containers(self):
158158
c.setHidden(True)
159159

160160
def get_tag_categories(self, tags: set[Tag]) -> dict[Tag | None, set[Tag]]:
161-
"""Get a dictionary of category tags mapped to their respective tags."""
162-
cats: dict[Tag | None, set[Tag]] = {}
163-
cats[None] = set()
164-
165-
base_tag_ids: set[int] = {x.id for x in tags}
166-
exhausted: set[int] = set()
167-
cluster_map: dict[int, set[int]] = {}
168-
169-
def add_to_cluster(tag_id: int, p_ids: list[int] | None = None):
170-
"""Maps a Tag's child tags' IDs back to it's parent tag's ID.
171-
172-
Example:
173-
Tag: ["Johnny Bravo", Parent Tags: "Cartoon Network (TV)", "Character"] maps to:
174-
"Cartoon Network" -> Johnny Bravo,
175-
"Character" -> "Johnny Bravo",
176-
"TV" -> Johnny Bravo"
177-
"""
178-
tag_obj = self.lib.get_tag(tag_id) # Get full object
179-
if p_ids is None:
180-
p_ids = tag_obj.parent_ids
181-
182-
for p_id in p_ids:
183-
if cluster_map.get(p_id) is None:
184-
cluster_map[p_id] = set()
185-
# If the p_tag has p_tags of its own, recursively link those to the original Tag.
186-
if tag_id not in cluster_map[p_id]:
187-
cluster_map[p_id].add(tag_id)
188-
p_tag = self.lib.get_tag(p_id) # Get full object
189-
if p_tag.parent_ids:
190-
add_to_cluster(
191-
tag_id,
192-
[sub_id for sub_id in p_tag.parent_ids if sub_id != tag_id],
193-
)
194-
exhausted.add(p_id)
195-
exhausted.add(tag_id)
196-
197-
for tag in tags:
198-
add_to_cluster(tag.id)
199-
200-
logger.info("[FieldContainers] Entry Cluster", entry_cluster=exhausted)
201-
logger.info("[FieldContainers] Cluster Map", cluster_map=cluster_map)
161+
"""Get a dictionary of category tags mapped to their respective tags.
162+
Example:
163+
Tag: ["Johnny Bravo", Parent Tags: "Cartoon Network (TV)", "Character"] maps to:
164+
"Cartoon Network" -> Johnny Bravo,
165+
"Character" -> "Johnny Bravo",
166+
"TV" -> Johnny Bravo"
167+
"""
168+
tag_parents, hierarchy_tags = self.lib.get_tag_hierarchy(t.id for t in tags)
202169

203-
# Initialize all categories from parents.
204-
tags_ = {self.lib.get_tag(x) for x in exhausted}
205-
for tag in tags_:
170+
categories: dict[int | None, set[int]] = {None: set()}
171+
for tag in hierarchy_tags.values():
206172
if tag.is_category:
207-
cats[tag] = set()
208-
logger.info("[FieldContainers] Blank Tag Categories", cats=cats)
209-
210-
# Add tags to any applicable categories.
211-
added_ids: set[int] = set()
212-
for key in cats:
213-
logger.info("[FieldContainers] Checking category tag key", key=key)
214-
215-
if key:
216-
logger.info(
217-
"[FieldContainers] Key cluster:", key=key, cluster=cluster_map.get(key.id)
218-
)
219-
220-
if final_tags := cluster_map.get(key.id, set()).union([key.id]):
221-
cats[key] = {self.lib.get_tag(x) for x in final_tags if x in base_tag_ids}
222-
added_ids = added_ids.union({x for x in final_tags if x in base_tag_ids})
223-
224-
# Add remaining tags to None key (general case).
225-
cats[None] = {self.lib.get_tag(x) for x in base_tag_ids if x not in added_ids}
226-
logger.info(
227-
f"[FieldContainers] [{key}] Key cluster: None, general case!",
228-
general_tags=cats[key],
229-
added=added_ids,
230-
base_tag_ids=base_tag_ids,
231-
)
232-
233-
# Remove unused categories
234-
empty: list[Tag] = []
235-
for k, v in list(cats.items()):
236-
if not v:
237-
empty.append(k)
238-
for key in empty:
239-
cats.pop(key, None)
240-
173+
categories[tag.id] = set()
174+
for tag in tags:
175+
has_category_parent = False
176+
parent_ids = tag_parents.get(tag.id, [])
177+
while len(parent_ids) > 0:
178+
grandparent_ids = set()
179+
for parent_id in parent_ids:
180+
if parent_id in categories:
181+
categories[parent_id].add(tag.id)
182+
has_category_parent = True
183+
grandparent_ids.update(tag_parents.get(parent_id, []))
184+
parent_ids = grandparent_ids
185+
if not has_category_parent:
186+
categories[None].add(tag.id)
187+
188+
cats = {}
189+
for category_id, descendent_ids in categories.items():
190+
key = None if category_id is None else hierarchy_tags[category_id]
191+
cats[key] = {hierarchy_tags[d] for d in descendent_ids}
241192
logger.info("[FieldContainers] Tag Categories", categories=cats)
242193
return cats
243194

0 commit comments

Comments
 (0)