File size: 7,259 Bytes
d916065
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# Natural Language Toolkit: CISTEM Stemmer for German
# Copyright (C) 2001-2023 NLTK Project
# Author: Leonie Weissweiler <[email protected]>
#         Tom Aarsen <> (modifications)
# Algorithm: Leonie Weissweiler <[email protected]>
#            Alexander Fraser <[email protected]>
# URL: <https://www.nltk.org/>
# For license information, see LICENSE.TXT

import re
from typing import Tuple

from nltk.stem.api import StemmerI


class Cistem(StemmerI):
    """

    CISTEM Stemmer for German



    This is the official Python implementation of the CISTEM stemmer.

    It is based on the paper

    Leonie Weissweiler, Alexander Fraser (2017). Developing a Stemmer for German

    Based on a Comparative Analysis of Publicly Available Stemmers.

    In Proceedings of the German Society for Computational Linguistics and Language

    Technology (GSCL)

    which can be read here:

    https://www.cis.lmu.de/~weissweiler/cistem/



    In the paper, we conducted an analysis of publicly available stemmers,

    developed two gold standards for German stemming and evaluated the stemmers

    based on the two gold standards. We then proposed the stemmer implemented here

    and show that it achieves slightly better f-measure than the other stemmers and

    is thrice as fast as the Snowball stemmer for German while being about as fast

    as most other stemmers.



    case_insensitive is a a boolean specifying if case-insensitive stemming

    should be used. Case insensitivity improves performance only if words in the

    text may be incorrectly upper case. For all-lowercase and correctly cased

    text, best performance is achieved by setting case_insensitive for false.



    :param case_insensitive: if True, the stemming is case insensitive. False by default.

    :type case_insensitive: bool

    """

    strip_ge = re.compile(r"^ge(.{4,})")
    repl_xx = re.compile(r"(.)\1")
    strip_emr = re.compile(r"e[mr]$")
    strip_nd = re.compile(r"nd$")
    strip_t = re.compile(r"t$")
    strip_esn = re.compile(r"[esn]$")
    repl_xx_back = re.compile(r"(.)\*")

    def __init__(self, case_insensitive: bool = False):
        self._case_insensitive = case_insensitive

    @staticmethod
    def replace_to(word: str) -> str:
        word = word.replace("sch", "$")
        word = word.replace("ei", "%")
        word = word.replace("ie", "&")
        word = Cistem.repl_xx.sub(r"\1*", word)

        return word

    @staticmethod
    def replace_back(word: str) -> str:
        word = Cistem.repl_xx_back.sub(r"\1\1", word)
        word = word.replace("%", "ei")
        word = word.replace("&", "ie")
        word = word.replace("$", "sch")

        return word

    def stem(self, word: str) -> str:
        """Stems the input word.



        :param word: The word that is to be stemmed.

        :type word: str

        :return: The stemmed word.

        :rtype: str



        >>> from nltk.stem.cistem import Cistem

        >>> stemmer = Cistem()

        >>> s1 = "Speicherbehältern"

        >>> stemmer.stem(s1)

        'speicherbehalt'

        >>> s2 = "Grenzpostens"

        >>> stemmer.stem(s2)

        'grenzpost'

        >>> s3 = "Ausgefeiltere"

        >>> stemmer.stem(s3)

        'ausgefeilt'

        >>> stemmer = Cistem(True)

        >>> stemmer.stem(s1)

        'speicherbehal'

        >>> stemmer.stem(s2)

        'grenzpo'

        >>> stemmer.stem(s3)

        'ausgefeil'

        """
        if len(word) == 0:
            return word

        upper = word[0].isupper()
        word = word.lower()

        word = word.replace("ü", "u")
        word = word.replace("ö", "o")
        word = word.replace("ä", "a")
        word = word.replace("ß", "ss")

        word = Cistem.strip_ge.sub(r"\1", word)

        return self._segment_inner(word, upper)[0]

    def segment(self, word: str) -> Tuple[str, str]:
        """

        This method works very similarly to stem (:func:'cistem.stem'). The difference is that in

        addition to returning the stem, it also returns the rest that was removed at

        the end. To be able to return the stem unchanged so the stem and the rest

        can be concatenated to form the original word, all subsitutions that altered

        the stem in any other way than by removing letters at the end were left out.



        :param word: The word that is to be stemmed.

        :type word: str

        :return: A tuple of the stemmed word and the removed suffix.

        :rtype: Tuple[str, str]



        >>> from nltk.stem.cistem import Cistem

        >>> stemmer = Cistem()

        >>> s1 = "Speicherbehältern"

        >>> stemmer.segment(s1)

        ('speicherbehält', 'ern')

        >>> s2 = "Grenzpostens"

        >>> stemmer.segment(s2)

        ('grenzpost', 'ens')

        >>> s3 = "Ausgefeiltere"

        >>> stemmer.segment(s3)

        ('ausgefeilt', 'ere')

        >>> stemmer = Cistem(True)

        >>> stemmer.segment(s1)

        ('speicherbehäl', 'tern')

        >>> stemmer.segment(s2)

        ('grenzpo', 'stens')

        >>> stemmer.segment(s3)

        ('ausgefeil', 'tere')

        """
        if len(word) == 0:
            return ("", "")

        upper = word[0].isupper()
        word = word.lower()

        return self._segment_inner(word, upper)

    def _segment_inner(self, word: str, upper: bool):
        """Inner method for iteratively applying the code stemming regexes.

        This method receives a pre-processed variant of the word to be stemmed,

        or the word to be segmented, and returns a tuple of the word and the

        removed suffix.



        :param word: A pre-processed variant of the word that is to be stemmed.

        :type word: str

        :param upper: Whether the original word started with a capital letter.

        :type upper: bool

        :return: A tuple of the stemmed word and the removed suffix.

        :rtype: Tuple[str, str]

        """

        rest_length = 0
        word_copy = word[:]

        # Pre-processing before applying the substitution patterns
        word = Cistem.replace_to(word)
        rest = ""

        # Apply the substitution patterns
        while len(word) > 3:
            if len(word) > 5:
                word, n = Cistem.strip_emr.subn("", word)
                if n != 0:
                    rest_length += 2
                    continue

                word, n = Cistem.strip_nd.subn("", word)
                if n != 0:
                    rest_length += 2
                    continue

            if not upper or self._case_insensitive:
                word, n = Cistem.strip_t.subn("", word)
                if n != 0:
                    rest_length += 1
                    continue

            word, n = Cistem.strip_esn.subn("", word)
            if n != 0:
                rest_length += 1
                continue
            else:
                break

        # Post-processing after applying the substitution patterns
        word = Cistem.replace_back(word)

        if rest_length:
            rest = word_copy[-rest_length:]

        return (word, rest)