Skript-Korrekturen (Copyright Jahr), phony target.
[wortliste.git] / skripte / python / analyse.py
blob416b9dacaad5fa46cbed7585c005044989ac4922
1 #!/usr/bin/env python
2 # -*- coding: utf8 -*-
3 # :Copyright: © 2014 Günter Milde.
4 # Released without warranty under the terms of the
5 # GNU General Public License (v. 2 or later)
6 # :Id: $Id: $
8 # analyse.py: Sammeln und Sortieren von Teilwörtern
9 # =================================================
11 # Erstelle eine Liste der Teilwörter von in der Wortliste_ markierten
12 # zusammengesetzten Wörtern mit den Häufigkeiten des Auftretens als:
14 # :S: Einzelwort (Solitär)
15 # :E: erstes Wort in Verbindungen
16 # :M: mittleres Wort in Verbindungen
17 # :L: letztes Wort in Verbindungen
19 # Format:
21 # * Teilwort mit Trennungen. Großschreibung wie Gesamtwort
22 # * Leerraum (whitespace)
23 # * Häufigkeiten in der Reihenfolge S;E;M;L
25 # Beispiel:
27 # Ho-se 1;0;0;7
29 # .. _wortliste: ../../wortliste
31 # .. contents::
33 # Vorspann
34 # ========
36 # Importiere Python Module::
38 import re # Funktionen und Klassen für reguläre Ausdrücke
39 import sys # sys.exit() zum Abbruch vor Ende (für Testzwecke)
40 import codecs
41 from collections import defaultdict # Wörterbuch mit Default
43 from werkzeug import WordFile, join_word, udiff, uebertrage, TransferError
45 # teilwoerter
46 # -----------
48 # Sammlung von `dictionaries` mit Info über Teilwörter.
50 # >>> from analyse import teilwoerter
52 # >>> words = teilwoerter()
54 # ::
56 class teilwoerter(object):
58 # Dictionary mit Listen möglicher Trennungen. Es kann unterschiedlche
59 # Trennungen eines identischen Teilworts geben, z.B. "Ba-se" (keine Säure)
60 # vs. "Base" (in Base=ball)::
62 trennvarianten = {}
64 # Häufigkeiten des Auftretens der Teilwörter::
66 S = defaultdict(int) # Einzelwort (Solitär)
67 E = defaultdict(int) # erstes Wort in Verbindungen
68 M = defaultdict(int) # mittleres Wort in Verbindungen
69 L = defaultdict(int) # letztes Wort in Verbindungen
71 # >>> words.S['na'] += 1
72 # >>> print words.S['na'], words.E['na'], words.M['na'], words.L['na']
73 # 1 0 0 0
75 # Wort eintragen
77 # >>> words.add(u'ein<tra·gen')
78 # >>> words.add(u'ein·tra-gen')
79 # >>> words.add(u'un<klar')
80 # >>> words.add(u'un<.klar')
81 # >>> words.add(u'un-klar')
82 # >>> print words.trennvarianten
83 # {u'unklar': [u'un<klar', u'un-klar'], u'eintragen': [u'ein<tra-gen']}
85 # ::
87 def add(self, wort):
89 key = join_word(wort)
91 # Ignoriere Spezialtrennungen:
92 if re.search(r'[\[{/\]}]', wort):
93 return
94 # ungünstige Trennungen:
95 if u'.' in wort:
96 # Wort ignorieren
97 # return
98 # Entferne/Ersetze Markierung
99 wort = re.sub(ur'([-<=])\.+', ur'\1', wort) # =. <. -.
100 wort = re.sub(ur'\.+', ur'·', wort)
101 # wort schon vorhanden?
102 if key in self.trennvarianten:
103 # Abgleich der Trennmarker
104 eintrag = self.trennvarianten[key]
105 try:
106 wort = uebertrage(eintrag[-1], wort, upgrade=False)
107 eintrag[-1] = uebertrage(wort, eintrag[-1], upgrade=False)
108 except TransferError:
109 pass
110 if wort != eintrag[-1]:
111 self.trennvarianten[key].append(wort)
112 else:
113 self.trennvarianten[key] = [wort]
115 # Iterator über alle trennvarianten: Rückgabewert ist ein String
117 # >>> print [word for word in words.woerter()]
118 # [u'un<klar', u'un-klar', u'ein<tra-gen']
120 # ::
122 def woerter(self):
123 for varianten in self.trennvarianten.values():
124 for wort in varianten:
125 yield wort
127 # Schreibe (Teil)wörter und Häufigkeiten in eine Datei `path`::
129 def write(self, path):
131 outfile = codecs.open(path, 'w', encoding='utf8')
132 header = u'# wort S;E;M;L (Solitär, erstes/mittleres/letztes Wort)\n'
133 outfile.write(header)
135 for key in sorted(self.trennvarianten.keys()):
136 for wort in self.trennvarianten[key]:
137 line = u'%s %d;%d;%d;%d\n' % (wort,
138 self.S[key], self.E[key], self.M[key], self.L[key])
139 outfile.write(line)
142 # Funktion zum Einlesen der Teilwortdatei::
144 def read_teilwoerter(path):
146 words = teilwoerter()
148 for line in open(path):
149 if line.startswith('#'):
150 continue
151 line = line.decode('utf8')
152 try:
153 wort, flags = line.split()
154 except ValueError:
155 wort = line
156 flags = '0;0;0;0'
157 # raise ValueError('cannot parse line '+line.encode('utf8'))
159 key = join_word(wort)
160 flags = [int(n) for n in flags.split(u';')]
162 for kategorie, n in zip([words.S, words.E, words.M, words.L], flags):
163 if n > 0: # denn += 0 erzeugt "key" (`kategorie` ist defaultdict)
164 kategorie[key] += n
165 words.add(wort)
167 return words
170 # Analyse
171 # =====================
173 # Hilfsfunktion: Erkenne (Nicht-)Teile wie ``/{ll/ll`` aus
174 # ``Fuß=ba[ll=/{ll/ll=l}]eh-re``::
176 # >>> from analyse import spezialbehandlung
177 # >>> print spezialbehandlung(u']er.be')
178 # er.be
179 # >>> print spezialbehandlung(u'er[<b/b')
180 # erb
182 def spezialbehandlung(teil):
183 if re.search(ur'[\[{/\]}]', teil):
184 # print teil,
185 teil = re.sub(ur'\[<(.+)/[^\]]+', ur'\1', teil) # [<b/b
186 teil = re.sub(ur'\{([^/]*)[^}]*$', ur'\1', teil)
187 teil = re.sub(ur'\[([^/]*)[^\]]*$', ur'\1', teil)
188 teil = re.sub(ur'^(.)}', ur'\1', teil)
189 teil = re.sub(ur'^(.)\]', ur'\1', teil)
190 teil = re.sub(ur'^\]([^/]*$)', ur'\1', teil) # ]er.be -> er.be
191 # print teil
192 return teil
194 # Zerlege Wörter der Wortliste (unter `path`). Gib eine "teilwoerter"-Instanz
195 # mit dictionaries mit getrennten Teilwörtern als key und deren Häufigkeiten
196 # an der entsprechenden Position als Wert zurück::
199 def analyse(path='../../wortliste', sprachvariante='de-1901',
200 unfertig=False, halbfertig=True):
202 wordfile = WordFile(path)
203 words = teilwoerter()
205 for entry in wordfile:
207 # Wort mit Trennungen in Sprachvariante::
209 wort = entry.get(sprachvariante)
210 if wort is None: # Wort existiert nicht in der Sprachvariante
211 continue
213 # Teilwörter suchen::
215 # Zerlegen, leere Teile (wegen Mehrfachtrennzeichen '==') weglassen,
216 # "halbe" Spezialtrennungen entfernen:
217 teile = [spezialbehandlung(teil) for teil in wort.split(u'=')
218 if teil]
220 # Einzelwort
221 if len(teile) == 1:
222 if u'·' not in wort or unfertig:
223 # skip unkategorisiert, könnte Kopositum sein
224 words.add(wort)
225 words.S[join_word(wort)] += 1
226 continue
228 gross = wort[0].istitle()
230 # erstes Teilwort:
231 if (halbfertig or u'·' not in teile[0]
232 ) and not teile[0].endswith(u'<'): # Präfix wie un<=wahr=schein-lich
233 words.add(teile[0])
234 words.E[join_word(teile[0])] += 1
236 # letztes Teilwort:
237 teil = teile[-1]
238 if (halbfertig or u'·' not in teil
239 ) and not teil.startswith(u'>'): # Suffixe wie an-dert=halb=>fach)
240 if gross: # Großschreibung übertragen
241 teil = teil[0].title() + teil[1:]
242 words.add(teil)
243 words.L[join_word(teil)] += 1
245 # mittlere Teilwörter
246 for teil in teile[1:-1]:
247 if u'/' in teil:
248 if not re.search(ur'[\[{].*[\]}]', teil):
249 continue
250 if (not(halbfertig) and u'·' in teil # unkategorisiert
251 ) or teil.endswith(u'<'): # Präfix wie un<=wahr=schein-lich
252 continue
253 if gross: # Großschreibung übertragen
254 teil = teil[0].title() + teil[1:]
255 words.add(teil)
256 words.M[join_word(teil)] += 1
258 return words
260 # Datenerhebung zum Stand der Präfixmarkierung in der Liste der Teilwörter::
262 def statistik_praefixe(teilwoerter):
264 ausnahmen = set(line.decode('utf8').strip()
265 for line in open('wortteile/vorsilbenausnahmen')
266 if not line.startswith('#'))
268 # Präfixe (auch als Präfix verwendete Partikel, Adjektive, ...):
269 praefixe = set(line.rstrip().lower().decode('utf8')
270 for line in open('wortteile/praefixe')
271 if not line.startswith('#'))
272 # Sammelboxen:
273 markiert = defaultdict(list) # mit < markierte Präfixe
274 kandidaten = defaultdict(list) # (noch) mit - markierte Präfixe
275 unkategorisiert = defaultdict(list) # mit · markierte Präfixe
276 # grundwoerter = defaultdict(int) # Wörter nach Abtrennen markierter Präfixe
277 ausnahmefaelle = defaultdict(int)
279 # Analyse
280 for wort in teilwoerter.woerter():
281 # Abtrennen markierter Präfixe:
282 restwort = wort
283 teile = restwort.split(u'<')
284 for teil in teile[:-1]:
285 if teil: # (leere Strings (wegen <<<<) weglassen)
286 markiert[join_word(teil.lower())].append(wort)
287 restwort = teile[-1]
288 # Abtrennen markierter Suffixe:
289 restwort = restwort.split(u'>')[0]
290 # Silben des Grundworts
291 silben = re.split(u'[-·.]+', restwort)
292 silben[0] = silben[0].lower()
294 if (join_word(restwort) in ausnahmen
295 or join_word(restwort.split(u'>')[0]) in ausnahmen):
296 ausnahmefaelle[silben[0]] += 1
297 continue
298 for i in range(len(silben)-1, 0, -1):
299 kandidat = u''.join(silben[:i])
300 if kandidat.lower() in praefixe:
301 # print i, kandidat, restwort, restwort[len(kandidat)+i-1]
302 if u'>' == restwort[len(kandidat)+i-1]:
303 ausnahmefaelle[kandidat] += 1
304 elif u'·' in restwort:
305 unkategorisiert[kandidat].append(wort)
306 else:
307 kandidaten[kandidat].append(wort)
308 break
310 # Ausgabe
311 print (u'\nPräfixe aus der Liste "wortteile/praefixe" und '
312 u'gleiche Wortanfangssilben\nmarkiert mit:')
313 for vs in sorted(praefixe):
314 einzel = (teilwoerter.E[vs] + teilwoerter.M[vs]
315 + teilwoerter.E[vs.title()] + teilwoerter.M[vs.title()])
316 print u'%-10s %5d = %5d < %5d - %5d · %5d offen' % (vs, einzel,
317 len(markiert[vs]), ausnahmefaelle[vs],
318 len(unkategorisiert[vs]), len(kandidaten[vs])),
319 if kandidaten[vs]:
320 print u':', u' '.join(kandidaten[vs][:30]),
321 if len(kandidaten[vs]) > 30:
322 print u' ...',
323 print
324 markiert.pop(vs, None)
326 print u'Markierte Präfixe die nicht in der Präfix-Liste stehen:'
327 # markiert.pop('lang', 0) # von "ent<lang<<"
328 for vs, i in markiert.items():
329 print vs, u' '.join(i)
332 # Trennungsvarianten zum gleichen Key::
334 def mehrdeutigkeiten(words):
335 for teil in sorted(words.trennvarianten):
336 if len(words.trennvarianten[teil]) == 1:
337 continue
338 # Bekannte Mehrdeutigkeiten (meist engl./dt.):
339 if teil in ('Anhalts', 'Base', 'George',
340 'herzog', # Her-zog/her>zog
341 'Mode', 'Made', 'Name',
342 'Page', 'Pole', 'Planes', 'Rate', 'Real',
343 'Spare', 'Station', 'Stations', 'Ville', 'Wales', 'Ware',
344 'griff' # gri[f-f/{ff/ff=f}]est
346 continue
347 # Einzelwort und Präfix gleichlautend:
348 if len(words.trennvarianten[teil]) == 2:
349 varianten = [i.rstrip(u'<') for i in words.trennvarianten[teil]]
350 if varianten[0] == varianten[1]:
351 continue
352 print teil + u': ' + u' '.join(words.trennvarianten[teil])
355 # Bei Aufruf (aber nicht bei Import)::
357 if __name__ == '__main__':
359 # sys.stdout mit UTF8 encoding.
360 sys.stdout = codecs.getwriter('UTF-8')(sys.stdout)
362 # erstelle/aktualisiere die Datei ``teilwoerter.txt`` mit den Häufigkeiten
363 # nicht zusammengesetzer Wörter als Einzelwort oder in erster, mittlerer,
364 # oder letzter Position in Wortverbindungen::
366 # sprachvariante = 'de-1901' # "traditionell"
367 sprachvariante = 'de-1996' # Reformschreibung
368 # sprachvariante = 'de-1901-x-GROSS' # ohne ß (Schweiz oder GROSS)
369 # sprachvariante = 'de-1996-x-GROSS' # ohne ß (Schweiz oder GROSS)
370 # sprachvariante = 'de-CH-1901' # ohne ß (Schweiz) ("süssauer")
372 words = analyse(sprachvariante=sprachvariante,
373 halbfertig=True, unfertig=True)
374 words.write('teilwoerter-%s.txt'%sprachvariante)
376 # Test::
378 # sys.exit()
380 words = read_teilwoerter(path='teilwoerter-%s.txt'%sprachvariante)
382 # Trennungsvarianten zum gleichen Key:
383 mehrdeutigkeiten(words)
385 # Stand der Vorsilbenmarkierung:
386 statistik_praefixe(words)