"""Split the Project Gutenberg plain text of Arthur Conan Doyle, *The Adventures of Sherlock Holmes* (eBook #1661), into stories, then cut scenes with the same rule as ../jacobs/split_se.py and ../wharton/split_gutenberg.py (paragraph boundaries, ~320 words, tail >= 80). Deterministic: titles are matched as the roman-numeral story headers in the body (not the contents list), and the Gutenberg licence after END is dropped.""" import json, pathlib, re TITLES = ['A Scandal in Bohemia', 'The Red-Headed League', 'A Case of Identity', 'The Boscombe Valley Mystery', 'The Five Orange Pips', 'The Man with the Twisted Lip', 'The Adventure of the Blue Carbuncle', 'The Adventure of the Speckled Band', "The Adventure of the Engineer's Thumb", 'The Adventure of the Noble Bachelor', 'The Adventure of the Beryl Coronet', 'The Adventure of the Copper Beeches'] TARGET = 320 VOLUME = 'The Adventures of Sherlock Holmes (Arthur Conan Doyle, 1892)' raw = pathlib.Path('corpus/adventures-of-sherlock-holmes.txt').read_text(encoding='utf-8').replace('\r', '') raw = raw[:raw.index('*** END OF THE PROJECT GUTENBERG')] lines = raw.split('\n') ROMAN = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X', 'XI', 'XII'] heads = [] for n, t in enumerate(TITLES): norm_t = t.upper().replace("'", "’") pat = re.compile(r'^' + re.escape(ROMAN[n]) + r'\. ' + re.escape(norm_t) + r'$') hits = [i for i, l in enumerate(lines) if pat.match(l.strip())] assert len(hits) == 1, (t, hits) heads.append(hits[0]) assert heads == sorted(heads) stories = [] for n, (t, a) in enumerate(zip(TITLES, heads)): b = heads[n + 1] if n + 1 < len(heads) else len(lines) body = '\n'.join(lines[a + 1:b]) paras = [] for p in re.split(r'\n\s*\n', body): p = re.sub(r'\s+', ' ', p).strip() if not p: continue paras.append(p) stories.append({'volume': VOLUME, 'title': t, 'words': sum(len(p.split()) for p in paras), 'text': '\n\n'.join(paras)}) chunks = [] for si, s in enumerate(stories): buf, n = [], 0 for p in s['text'].split('\n\n'): w = len(p.split()) if n + w > TARGET and buf: chunks.append({'story': si, 'title': s['title'], 'volume': s['volume'], 'text': ' '.join(buf), 'words': n}) buf, n = [], 0 buf.append(p); n += w if buf and n >= 80: chunks.append({'story': si, 'title': s['title'], 'volume': s['volume'], 'text': ' '.join(buf), 'words': n}) pathlib.Path('stories.json').write_text(json.dumps(stories, indent=1, ensure_ascii=False), encoding='utf-8') pathlib.Path('chunks.json').write_text(json.dumps(chunks, ensure_ascii=False), encoding='utf-8') for si, s in enumerate(stories): print(f"{si:2} {s['title']:40} {s['words']:6} words {sum(c['story']==si for c in chunks):3} scenes") ws = sorted(c['words'] for c in chunks) print(f'stories {len(stories)} scenes {len(chunks)} words/scene min {ws[0]} median {ws[len(ws)//2]} max {ws[-1]} total {sum(ws):,}')