Seite 1 von 1

Sätze zählen

Verfasst: 12.02.2012 11:30
von Alfista
Hallo zusammen,

ich möchte in einem beliebigen Text, den ich aus einem Editorgadget auslese, die Zeichen, Wörter und Sätze zählen. Die Zeichen lassen sich ja einfach mit

Code: Alles auswählen

Len(Trim(text.s))
zählen, wobei sich je nach gewünschter Zählweise die Leerzeichen ja auch noch mit RemoveString() entfernen lassen.

Das Zählen der Wörter ist auch noch recht einfach zu machen. String durchlaufen und jedesmal wenn ein Leerzeichen kommt ein neues Wort dazuzählen.

Meine Frage ist nun: Wie geht das mit Sätzen? Mir ist bis jetzt nur eingefallen, das über die Interpunktion zu machen, also nach Punkt, Ausrufezeichen oder Fragezeichen wird ein neuer Satz dazu gezählt. Das arbeitet leider sehr ungenau, denn auch in Abkürzungen, URls usw. tauchen Punkte auf, die aber nicht das Satzende markieren.

Da komme ich nicht so recht weiter. Hat jemand dafür vielleicht eine clevere Idee?

Re: Sätze zählen

Verfasst: 12.02.2012 12:57
von sibru
´n bischen umfangreich:

Code: Alles auswählen

#PB_Vers  = "4.20"

 ;==========  Begin Modul "Wort.PBI"  ==========
;Modul      Wort           Version 1.13 vom 13.01.2011 (PB_V3.73)
#PB_Vers  = "4.20"
;                 (Basis: THEOS-Modul SYSTEM.MODLIB.WORT V 3.03 vom 11.05.1997)
;
;Funktion: liefert erstes Wort in einem String und verkürzt Diesen entsprechend
;          entspricht prinzipell der PB-Funktion "StringField()", benötigt jedoch
;          keinen Wort-Index und erkennt Wort aufgrund diverser Trenn- / Klammerungs=
;          zeichen. Außerdem wird die Wort-Basis (Eingangs-String-Parameter) wie
;          eine Queue gehandhabt und nach FunktionsEnde ist das erkannte Wort am
;          Anfang dieses Strings entfernt...
;
;Aufruf: Wort$ = Wort(@String$) - liefert nächstes Wort von String$ (bis Leerzeichen bzw. geklammert)
;      wobei: String$ = Text-Variable !!!, in der ggf. mehrere Worte enthalten sind.
;             Ein Wort ist:
;             - alle Zeichen bis zum nächsten Blank(führende Blank´s werden ignoriert)  oder
;             - geklammert durch " (^34), ' (^39), ´(^180), " " (^160) oder ^255       oder
;             - bis zum nächsten Zeichen lt. Global ´Wort_Ende$´                       oder
;             - geklammter durch Zeichen lt. Global ´Wort_Klammer$´
;     Die Global´s Wort_Ende$ und Wort_Klammer$ sind nach Funktions-Rückkehr resetet
;     (=leer!!), müßen also -sofern erforderlich- _vor jedem Aufruf_ dieser Funktion
;     entsprechend gesetzt werden !!!!
;
;     Diese Funktion liefert das 1. Wort im String (führende Leerzeichen werden ignoriert)
;     und der String wird entsprechend verkürzt
;     Beispiel:
;     A$ = "hallo ´du da´ alles klar"
;     B$ = Wort(@A$) ;1. Aufruf
;      (--> B$ ist "hallo",    A$ ist nun "´du da´ alles klar")
;     B$ = Wort(@A$) ;2. Aufruf
;      (--> B$ ist "du da",    A$ ist nun "alles klar") (wg. ´´-Klammerung)
;     B$ = Wort(@A$) ;3. Aufruf
;      (--> B$ ist "alles",    A$ ist nun "klar")
;     B$ = Wort(@A$) ;4. Aufruf
;      (--> B$ ist "klar",     A$ ist nun leer)
;
Global Wort_Ende$       ;Zeichen für Wort-Ende (darf nur 1 Zeichen sein !!!, nur für 1 Aufruf !!!)
Global Wort_Klammer$    ;Klammerungs-Zeichen: alle Zeichen, die als Wort-Anfangs- oder Ende-Kennung
                        ;beim folgenden Aufruf zulässig sein sollen
Global Wort_EndKz$      ;Rückgabe: gefundenes/benutztes Wort-Ende-Zeichen
#Wort_BlankReplace = Chr(28) ;siehe Modul "WortForm()"...
 ;==========  Begin Modul "CharChg.PBI"  ==========
;Modul CharChg  Version 1.13 vom 27.11.2011
#PB_Vers  = "4.20"
;
;Funktion:  mehrfach-Austausch in einem String, in einem BasisString
;           wird ein Suchbegriff (EinzelZeichen oder auch ZeichenKette)
;           gesucht und durch einem Ersatzbegriff (leer, EinzelZeichen
;           oder auch Zeichenkette) ersetzt, das ganze auch mehrfach
;           (jedes Vorkommen des Suchbegriffes wird durch den Ersatz=
;           begriff ersetzt), innerhalb des Ersatzbegriffes kann der
;           Suchbegriff vorkommen, wird jedoch nicht verändert.
;
;           Gegenüber der PureBasic-StandartFunktion ReplaceString(),
;           die standartmäßig eingesetzt werden sollte, bestehen
;           folgende Unterschiede:
;           - bei der GROSS-/klein-Schrift-Suche werden auch deutsche
;             Sonderzeichen (Ä, Ö, Ü, ß) korrekt gehandhabt
;           - Wort-Austausch möglich (vor und nach Suchbegriff darf
;             kein Buchstabe im Basis-String stehen)
;           - es wird die Anzahl durchgeführter Tauschungen zurück=
;             geliefert
;           - Mehrfach-Austausch (z.Bsp. TrimAll: "  "->" ") hier
;             fehlerfrei (klappt nicht in ReplaceString of PB3.80)
;           Für nähere Details siehe unten (Global´s)...
;
;
;Aufruf:    newstring = CharChg(base$, such$, ersatz$, Mode)   -Zeichen(ketten)-Austausch
;wobei:     base$   = Basis-String, in dem Teile ausgetauscht werden
;                     sollen
;           such$   = Zeichen(-kette), die in base$ gesucht und ersetzt
;                     werden soll
;           ersatz$ = Zeichen(-kette), die anstelle von such$ in base$
;                     eingesetzt werden soll
;           Mode    = Steuerungen (bitorientiert):
#CharChg_noCase = 1 ;  = noCase (siehe Global "CharChg_noCase")
#CharChg_Word = 2 ;    = Word (siehe Global "CharChg_Word")
;                     Sobald entweder die Steuerung aktiv ist _oder_ die
;                     entsprechende globale Variable, so wird die Funktion
;                     ausgeführt.
;
Global CharChg_noCase  ;wenn ungleich 0, so wird GROSS-/klein-Schrift
                       ;ignoriert ("AnKe" wird in "TaNkEn" gefunden
                       ;und ausgetauscht) !!! Variable ist nach
                       ;FunktionsEnde resetet !!! (auf 0 gestellt)
Global CharChg_Word    ;wenn ungleich 0, so wird nur ausgetauscht, wenn
                       ;vor und nach dem Suchbegriff kein Buchstabe und
                       ;keine Ziffer steht ("anke" wird _nicht_ in
                       ;"tanken" gefunden). Variable ist nach
                       ;FunktionsEnde resetet !!! (auf 0 gestellt)
Global CharChg_Cnt     ;liefert die Anzahl der durchgeführten Aus=
                       ;tauschungen
#CharChg_WordChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ÄÖÜabcdefghijklmnopqrstuwvxyzöäü"
;#jaPBeExt exit
Procedure.s CharChg(CC_Base$, CC_Such$, CC_Repl$, Mode = 0)
   Protected Base$, pos.l, Such$
   CharChg_Cnt = 0
  If Mode&1 : CharChg_noCase = 1 : EndIf
  If Mode&2 : CharChg_Word = 1 : EndIf
   If CC_Such$>""
    charChg_suchen:
    If CharChg_noCase
      Base$ = CC_Base$ : Such$ = CC_Such$
      pos = FindString(PeekS(CharLower_(Base$)), PeekS(CharLower_(Such$)), pos + 1)
    Else : pos = FindString(CC_Base$, CC_Such$, pos + 1)
    EndIf
      If pos
        If CharChg_Word
;          Debug "WordSuch [" + CC_Such$ + "] in [" + CC_Base$ + "]"
;          Debug "pre=[" + Mid(" " + CC_Base$, Pos, 1) + "], post=[" + Mid(CC_Base$ + " ", Pos + Len(CC_Such$), 1) + "]"
            If FindString(#CharChg_WordChars, Mid(" " + CC_Base$, pos, 1), 1)Or FindString(#CharChg_WordChars, Mid(CC_Base$ + " ", pos + Len(CC_Such$), 1), 1)
               pos + 1
               Goto charChg_suchen
            EndIf
         EndIf
         CC_Base$ = Left(CC_Base$, pos - 1) + CC_Repl$ + Mid(CC_Base$, pos + Len(CC_Such$), 99999)
         CharChg_Cnt = CharChg_Cnt + 1
         pos + Len(CC_Repl$) - Len(CC_Such$)
         Goto charChg_suchen
      EndIf
   EndIf
   CharChg_noCase = 0 : CharChg_Word = 0
   ProcedureReturn CC_Base$
EndProcedure
 ;==========  Ende Modul "CharChg.PBI"  ==========
;#jaPBeExt exit
Procedure.s Wort(*Param)
  Protected Param$, Wort$
  If * Param>1
    Param$ = LTrim(PeekS(*Param))
    If Wort_Ende$ = ""
      If Wort_Klammer$ = ""
        Wort_Klammer$ = #DQUOTE$ + Chr(39) + Chr(180) + Chr(255) + Chr(160)  ;", ', ´ oder ^255
      EndIf
      If FindString(Wort_Klammer$, Left(Param$, 1), 1) And Param$>""
        Wort_Ende$ = Left(Param$, 1)
        Param$ = Right(Param$, Len(Param$) - 1)
      Else
        Wort_Ende$ = " "
      EndIf
    EndIf
    While Left(Param$, 1)<>Wort_Ende$ And Param$>""
      Wort$ + Left(Param$, 1)
      Param$ = Mid(Param$, 2)
    Wend
    Wort$ = LTrim(CharChg(Wort$, #Wort_BlankReplace, Chr(32)))
    Wort_EndKz$ = Wort_Ende$
    Wort_Ende$ = "" : Wort_Klammer$ = ""
    PokeS(*Param, LTrim(Right(Param$, Len(Param$) - 1)))
  EndIf
  ProcedureReturn Wort$
EndProcedure
 ;==========  Ende Modul "Wort.PBI"  ==========

Procedure ContentCount(GadNr)       ;- ??? noRef ???
  Protected i, Saetze    
  Protected Zeichen, Worte, Saetze ;die Zähler-Var´s, Zeichen, Worte und Sätze
  Protected Text$ = GetGadgetText(GadNr);Text aus Gadget holen
  #SatzEnd = ".?!" ;alle zulässigen Satz-Ende-Kennungen definieren
  Text$ = ReplaceString(Text$, #LF$, " ");Zeilenumbruch wie Blank behandeln !!!
  While Text$>""
    Wort$ = Wort(@Text$);Wort$ enthält nun wort bis nächsten Blank, Text$ entsprechend gekürzt
    Debug Wort$
    Zeichen + Len(Wort$);Blanks sind schon raus !!!
    Worte + 1
    For i = 1 To Len(#SatzEnd)
      If FindString(Wort$, Mid(#SatzEnd, i, 1), 1): Saetze + 1 : i = Len(#SatzEnd): EndIf
    Next
  Wend
  Text$ = Str(Zeichen) + " Zeichen" + #LF$ ;Text$ wird nun zum Anzeige-Aufbau benutzt
  Text$ + Str(Worte) + " Worte" + #LF$
  Text$ + Str(Saetze) + " Sätze" + #LF$
  MessageRequester("Text-Analyse", Text$)
EndProcedure

DisableExplicit
WinNr = OpenWindow(#PB_Any, 1, 1, 200, 200, "TextZähler", #PB_Window_SystemMenu)
CreateGadgetList(WindowID(WinNr))
TxtGad = EditorGadget(#PB_Any, 1, 1, 200, 150)

Repeat
  Event = WaitWindowEvent()
  Select Event

  EndSelect
Until Event = #PB_Event_CloseWindow
ContentCount(TxtGad)

Re: Sätze zählen

Verfasst: 12.02.2012 15:46
von Alfista
Hallo sibru,

vielen Dank für deinen Code! Deine Methode funktioniert etwas besser als meine ersten Versuche und ist deshalb schon mal sehr hilfreich. Ein Grundproblem bleibt aber die fehlerafte Satzzählung. Der folgende Beispieltext besteht aus drei Sätzen, es werden aber sieben gezählt:
Ich war schon bei ca. zehn verschiedenen Zahnärzten. Gestern war ich bei Dr. Florian S. in Behandlung. Ich war jetzt das 3. Mal bei ihm.

Dass nicht immer Ausdrücken wie "ca. " oder "Dr. " als Satzende interpretiert werden, lässt sich wahrscheinlich ohne semantische Analyse nicht vermeiden. Diese würde allerdings den Rahmen meiner Möglichkeiten sprengen. Schade, Hatte die leise Hoffnung, dass es noch irgendweinen coolen Trick gibt. :mrgreen:
Die einzige Möglichkeit, die mir einfällt, wäre eine Liste mit den gebräuchlichsten Abkürzungen als Filter zu verwenden. So könnte die Genauigkeit zumindest erhöht werden.

Re: Sätze zählen

Verfasst: 12.02.2012 19:24
von Nino
Alfista hat geschrieben:Die einzige Möglichkeit, die mir einfällt, wäre eine Liste mit den gebräuchlichsten Abkürzungen als Filter zu verwenden. So könnte die Genauigkeit zumindest erhöht werden.
Ich glaube, ich würde es ebenfalls so machen, dass Punkte direkt nach "ca", "Dr" und einigen anderen gebräuchlichen Abkürzungen nicht mitgezählt werden. Dabei muss man aber bedenken, dass laut Duden nur ein Punkt gesetzt wird, wenn Abkürzungs- und Schlusspunkt aufeinandertreffen:
Roman Herzog ist Bundespräsident a.D.
Dass heißt Punkte nach Abkürzungen, die auch am Satzende stehen können, müssen evtl. doch mitgezählt werden. Ich würde überhaupt nur Punkte zählen, auf die ein Leerzeichen (oder Zeilenende oder Dateiende) folgt -- damit sind Punkte in IP-Adressen und Datumsangaben schonmal außen vor. Am Satzende muss das nächste Zeichen nach dem Punkt -- das kein Leerzeichen, Tabulator oder Zeilenumbruch-Zeichen ist -- ein Großbuchstabe sein (oder eine Ziffer). Anders herum garantieren Leerzeichen und Großbuchstabe nach einem Punkt aber leider nicht, dass der betr. Punkt ein Satzende kennzeichnet:
Sie wohnt im 3. Stockwerk.
Eine zuverlässige Satzzählung wird wohl sehr schwierig.

Grüße, Nino

Re: Sätze zählen

Verfasst: 13.02.2012 11:28
von Alfista
Dabei muss man aber bedenken, dass laut Duden nur ein Punkt gesetzt wird, wenn Abkürzungs- und Schlusspunkt aufeinandertreffen:
Richtig, auch wenn das von Autoren meist vermieden wird, ist das ein weiteres Problem, das sich ohne echte semantische Analyse überhaupt nicht lösen lässt. Dazu kommt ja auch noch, dass die Texte korrekt formatiert sein müssen. Der Code von sibru funktioniert ja schon ganz gut und interpretiert http://www.purebasic.com nicht als Satzende. Wenn die Texte allerdings z.B. durch 'Klempen' oder 'Plenken' fehlerhaft formatiert sind, wie bei Schülertexten an der Tagesordnung, wird falsch gezählt. Man müsste die Texte also vorher durchgehen und entsprechende Stellen abändern. Bei kürzeren Texten könnte man das ja machen, allerdings könnte man dann die Sätze auch gleich manuell zählen. Ich werde mal versuchen, einen Filter einzubauen, mal sehen, wie die Ergebnisse dann sind. Ist wohl noch viel Testerei angesagt. Danke euch!