RJ3vans commited on
Commit
f22141b
·
verified ·
1 Parent(s): 6475da6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +78 -59
app.py CHANGED
@@ -2733,95 +2733,114 @@ def expand_paren_lists(sent: str, lists: list[list[str]]) -> list[str]:
2733
 
2734
  ###############################################################################
2735
  def build_shared_dag(sentences):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2736
  cache = {}
2737
  labels = {}
 
 
 
 
 
 
 
2738
  edges = set()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2739
 
2740
- def node_for(words):
2741
- key = tuple(words)
2742
- if key in cache:
2743
- return cache[key]
2744
- nid = f"n{len(cache)}"
2745
- cache[key] = nid
2746
- labels[nid] = " ".join(words) if words else ""
2747
- return nid
2748
-
2749
- root = node_for([])
2750
- for sent in sentences:
2751
- words = sent.rstrip(".").split()
2752
- prev = root
2753
- for i in range(len(words)):
2754
- nxt = node_for(words[i:])
2755
- edges.add((prev, nxt))
2756
- prev = nxt
2757
  return labels, edges
2758
  ###############################################################################
2759
 
2760
  ###############################################################################
2761
- def dag_to_svg(sentences, width=900):
2762
  labels, edges = build_shared_dag(sentences)
2763
- # drop the empty root
2764
- nodes = [nid for nid, lab in labels.items() if lab]
2765
- # simple layered layout by first appearance
2766
- order = []
2767
- seen = set()
2768
- for sent in sentences:
2769
- words = sent.rstrip(".").split()
2770
- for i in range(len(words)):
2771
- key = tuple(words[i:])
2772
- # find nid
2773
- for nid, lab in labels.items():
2774
- if lab == " ".join(key) and nid not in seen:
2775
- order.append(nid)
2776
- seen.add(nid)
2777
- break
2778
- for nid in nodes:
2779
- if nid not in seen:
2780
- order.append(nid)
2781
 
2782
- # group into rows by roughly similar remaining length
2783
- rows = {}
 
 
2784
  for nid in order:
2785
- length = len(labels[nid].split())
2786
- rows.setdefault(length, []).append(nid)
2787
  lengths = sorted(rows.keys(), reverse=True)
2788
 
2789
  positions = {}
2790
- y = 40
 
2791
  for length in lengths:
2792
  row = rows[length]
2793
  n = len(row)
 
2794
  for i, nid in enumerate(row):
2795
- x = 40 + i * (width - 80) / max(n, 1)
2796
- positions[nid] = (x, y)
2797
- y += 90
 
2798
 
2799
- # SVG
2800
  parts = [
2801
- f'<svg xmlns="http://www.w3.org/2000/svg" width="{width}" height="{y + 20}" '
2802
- f'style="font-family:Helvetica,Arial,sans-serif;font-size:13px;">'
 
 
2803
  ]
2804
- # edges
2805
  for a, b in edges:
2806
  if a not in positions or b not in positions:
2807
  continue
2808
- x1, y1 = positions[a]
2809
- x2, y2 = positions[b]
2810
  parts.append(
2811
- f'<line x1="{x1+60}" y1="{y1+20}" x2="{x2+60}" y2="{y2}" '
2812
  f'stroke="#888" stroke-width="1.2" marker-end="url(#arrow)"/>'
2813
  )
2814
- parts.append(
2815
- '<defs><marker id="arrow" markerWidth="8" markerHeight="8" '
2816
- 'refX="6" refY="3" orient="auto"><path d="M0,0 L0,6 L8,3 z" fill="#888"/></marker></defs>'
2817
- )
2818
- # nodes
2819
- for nid, (x, y) in positions.items():
2820
  lab = labels[nid].replace("&", "&amp;").replace("<", "&lt;")
2821
  parts.append(
2822
- f'<rect x="{x}" y="{y}" width="120" height="36" rx="6" '
2823
  f'fill="#f7f7f7" stroke="#555"/>'
2824
- f'<text x="{x+60}" y="{y+22}" text-anchor="middle">{lab}</text>'
2825
  )
2826
  parts.append("</svg>")
2827
  return "".join(parts)
 
2733
 
2734
  ###############################################################################
2735
  def build_shared_dag(sentences):
2736
+ sents = [tuple(s.rstrip(".").split()) for s in sentences if s.strip()]
2737
+ if not sents:
2738
+ return {}, set()
2739
+
2740
+ # count every suffix
2741
+ suffix_count = defaultdict(int)
2742
+ for sent in sents:
2743
+ for i in range(len(sent)):
2744
+ suffix_count[sent[i:]] += 1
2745
+
2746
+ # keep full sentences, and any suffix that is truly shared
2747
+ keep = set()
2748
+ for sent in sents:
2749
+ keep.add(sent)
2750
+ for i in range(1, len(sent)):
2751
+ if suffix_count[sent[i:]] > 1:
2752
+ keep.add(sent[i:])
2753
+
2754
+ # also keep shared prefixes (length >= 2, count >= 2)
2755
+ prefix_count = defaultdict(int)
2756
+ for sent in sents:
2757
+ for i in range(2, len(sent) + 1):
2758
+ prefix_count[sent[:i]] += 1
2759
+ for p, c in prefix_count.items():
2760
+ if c >= 2:
2761
+ keep.add(p)
2762
+
2763
  cache = {}
2764
  labels = {}
2765
+ def node_for(words):
2766
+ if words not in cache:
2767
+ nid = f"n{len(cache)}"
2768
+ cache[words] = nid
2769
+ labels[nid] = " ".join(words)
2770
+ return cache[words]
2771
+
2772
  edges = set()
2773
+ for sent in sents:
2774
+ # walk the sentence, emitting an edge only when we hit a kept span
2775
+ prev = None
2776
+ for i in range(len(sent)):
2777
+ span = sent[i:]
2778
+ if span in keep:
2779
+ nid = node_for(span)
2780
+ if prev is not None:
2781
+ edges.add((prev, nid))
2782
+ prev = nid
2783
+ # also connect shared prefixes into the first kept continuation
2784
+ for i in range(2, len(sent)):
2785
+ pref = sent[:i]
2786
+ if pref in keep:
2787
+ # find the next kept span after this prefix
2788
+ for j in range(i, len(sent)):
2789
+ if sent[j:] in keep:
2790
+ edges.add((node_for(pref), node_for(sent[j:])))
2791
+ break
2792
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2793
  return labels, edges
2794
  ###############################################################################
2795
 
2796
  ###############################################################################
2797
+ def dag_to_svg(sentences, width=1000):
2798
  labels, edges = build_shared_dag(sentences)
2799
+ if not labels:
2800
+ return "<p>No sentences.</p>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2801
 
2802
+ # order nodes by descending length so shared tails sit lower
2803
+ order = sorted(labels.keys(), key=lambda n: -len(labels[n].split()))
2804
+ # group into rows by word count
2805
+ rows = defaultdict(list)
2806
  for nid in order:
2807
+ rows[len(labels[nid].split())].append(nid)
 
2808
  lengths = sorted(rows.keys(), reverse=True)
2809
 
2810
  positions = {}
2811
+ y = 30
2812
+ max_w = 0
2813
  for length in lengths:
2814
  row = rows[length]
2815
  n = len(row)
2816
+ box_w = min(280, max(140, width // max(n, 1) - 20))
2817
  for i, nid in enumerate(row):
2818
+ x = 20 + i * (box_w + 30)
2819
+ positions[nid] = (x, y, box_w)
2820
+ max_w = max(max_w, x + box_w)
2821
+ y += 70
2822
 
 
2823
  parts = [
2824
+ f'<svg xmlns="http://www.w3.org/2000/svg" width="{max(width, max_w + 40)}" '
2825
+ f'height="{y + 20}" style="font-family:Helvetica,Arial,sans-serif;font-size:13px;">',
2826
+ '<defs><marker id="arrow" markerWidth="8" markerHeight="8" refX="7" refY="3" '
2827
+ 'orient="auto"><path d="M0,0 L0,6 L8,3 z" fill="#888"/></marker></defs>',
2828
  ]
 
2829
  for a, b in edges:
2830
  if a not in positions or b not in positions:
2831
  continue
2832
+ x1, y1, w1 = positions[a]
2833
+ x2, y2, w2 = positions[b]
2834
  parts.append(
2835
+ f'<line x1="{x1 + w1/2}" y1="{y1 + 32}" x2="{x2 + w2/2}" y2="{y2}" '
2836
  f'stroke="#888" stroke-width="1.2" marker-end="url(#arrow)"/>'
2837
  )
2838
+ for nid, (x, y, w) in positions.items():
 
 
 
 
 
2839
  lab = labels[nid].replace("&", "&amp;").replace("<", "&lt;")
2840
  parts.append(
2841
+ f'<rect x="{x}" y="{y}" width="{w}" height="32" rx="5" '
2842
  f'fill="#f7f7f7" stroke="#555"/>'
2843
+ f'<text x="{x + w/2}" y="{y + 20}" text-anchor="middle">{lab}</text>'
2844
  )
2845
  parts.append("</svg>")
2846
  return "".join(parts)