25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.

55 satır
1.7 KiB

  1. import logging
  2. from structs.headline import Headline
  3. from structs.story import Story
  4. logger = logging.getLogger(__name__)
  5. def cluster_stories(headlines: list[Headline], threshold: float) -> list[Story]:
  6. """Group headlines into stories using transitive similarity.
  7. Any two headlines whose cosine similarity meets ``threshold`` are linked,
  8. and links are unioned transitively, so a chain of near-duplicates collapses
  9. into a single story cluster even when the endpoints are not directly similar.
  10. """
  11. n = len(headlines)
  12. if n == 0:
  13. logger.info("No headlines to cluster.")
  14. return []
  15. parent = list(range(n))
  16. def find(x):
  17. while parent[x] != x:
  18. parent[x] = parent[parent[x]]
  19. x = parent[x]
  20. return x
  21. def union(a, b):
  22. ra, rb = find(a), find(b)
  23. if ra != rb:
  24. parent[rb] = ra
  25. links = 0
  26. for i in range(n):
  27. for j in range(i + 1, n):
  28. try:
  29. score = headlines[i].compare_headlines(headlines[j])
  30. except Exception as e:
  31. logger.error("Error comparing headlines [%d] and [%d] during clustering: %s",
  32. i, j, e, exc_info=True)
  33. continue
  34. if score >= threshold:
  35. union(i, j)
  36. links += 1
  37. # Assemble clusters (connected components) keyed by root index.
  38. components = {}
  39. for idx in range(n):
  40. components.setdefault(find(idx), []).append(headlines[idx])
  41. stories = [Story(members) for members in components.values()]
  42. logger.info("Clustering complete: %d headlines -> %d stories via %d similarity links.",
  43. n, len(stories), links)
  44. return stories