_s2_options = { "a": ( (["a", "t", "i", "o", "n", "a", "l"], ["a", "t", "e"]), (["t", "i", "o", "n", "a", "l"], ["t", "i", "o", "n"]), ), "c": ( (["e", "n", "c", "i"], ["e", "n", "c", "e"]), (["a", "n", "c", "i"], ["a", "n", "c", "e"]), ), "e": ((["i", "z", "e", "r"], ["i", "z", "e"]),), "l": ( (["b", "l", "i"], ["b", "l", "e"]), (["a", "l", "l", "i"], ["a", "l"]), (["e", "n", "t", "l", "i"], ["e", "n", "t"]), (["e", "l", "i"], ["e"]), (["o", "u", "s", "l", "i"], ["o", "u", "s"]), ), "o": ( (["i", "z", "a", "t", "i", "o", "n"], ["i", "z", "e"]), (["a", "t", "i", "o", "n"], ["a", "t", "e"]), (["a", "t", "o", "r"], ["a", "t", "e"]), ), "s": ( (["a", "l", "i", "s", "m"], ["a", "l"]), (["i", "v", "e", "n", "e", "s", "s"], ["i", "v", "e"]), (["f", "u", "l", "n", "e", "s", "s"], ["f", "u", "l"]), (["o", "u", "s", "n", "e", "s", "s"], ["o", "u", "s"]), ), "t": ( (["a", "l", "i", "t", "i"], ["a", "l"]), (["i", "v", "i", "t", "i"], ["i", "v", "e"]), (["b", "i", "l", "i", "t", "i"], ["b", "l", "e"]), ), "g": ((["l", "o", "g", "i"], ["l", "o", "g"]),), } _s3_options = { "e": ( (["i", "c", "a", "t", "e"], ["i", "c"]), (["a", "t", "i", "v", "e"], []), (["a", "l", "i", "z", "e"], ["a", "l"]), ), "i": ((["i", "c", "i", "t", "i"], ["i", "c"]),), "l": ((["i", "c", "a", "l"], ["i", "c"]), (["f", "u", "l"], [])), "s": ((["n", "e", "s", "s"], []),), } _s4_endings = { "a": (["a", "l"],), "c": (["a", "n", "c", "e"], ["e", "n", "c", "e"]), "e": (["e", "r"],), "i": (["i", "c"],), "l": (["a", "b", "l", "e"], ["i", "b", "l", "e"]), "n": ( ["a", "n", "t"], ["e", "m", "e", "n", "t"], ["m", "e", "n", "t"], ["e", "n", "t"], ), # handle 'o' separately "s": (["i", "s", "m"],), "t": (["a", "t", "e"], ["i", "t", "i"]), "u": (["o", "u", "s"],), "v": (["i", "v", "e"],), "z": (["i", "z", "e"],), } class Stemmer(object): def __init__(self, b): self.b = list(b) self.k = len(b) - 1 self.j = 0 def cons(self, i): """ True iff b[i] is a consonant """ if self.b[i] in "aeiou": return False elif self.b[i] == "y": return True if i == 0 else not self.cons(i - 1) return True def m(self): n = i = 0 while True: if i > self.j: return n if not self.cons(i): break i += 1 i += 1 while True: while True: if i > self.j: return n if self.cons(i): break i += 1 i += 1 n += 1 while True: if i > self.j: return n if not self.cons(i): break i += 1 i += 1 def vowel_in_stem(self): """ True iff 0...j contains vowel """ for i in range(0, self.j + 1): if not self.cons(i): return True return False def doublec(self, j): """ True iff j, j-1 contains double consonant """ if j < 1 or self.b[j] != self.b[j - 1]: return False return self.cons(j) def cvc(self, i): """ True iff i-2,i-1,i is consonant-vowel consonant and if second c isn't w,x, or y. used to restore e at end of short words like cave, love, hope, crime """ if ( i < 2 or not self.cons(i) or self.cons(i - 1) or not self.cons(i - 2) or self.b[i] in "wxy" ): return False return True def ends(self, s): length = len(s) """ True iff 0...k ends with string s """ res = self.b[self.k - length + 1 : self.k + 1] == s if res: self.j = self.k - length return res def setto(self, s): """ set j+1...k to string s, readjusting k """ length = len(s) self.b[self.j + 1 : self.j + 1 + length] = s self.k = self.j + length def r(self, s): if self.m() > 0: self.setto(s) def step1ab(self): if self.b[self.k] == "s": if self.ends(["s", "s", "e", "s"]): self.k -= 2 elif self.ends(["i", "e", "s"]): self.setto(["i"]) elif self.b[self.k - 1] != "s": self.k -= 1 if self.ends(["e", "e", "d"]): if self.m() > 0: self.k -= 1 elif ( self.ends(["e", "d"]) or self.ends(["i", "n", "g"]) ) and self.vowel_in_stem(): self.k = self.j if self.ends(["a", "t"]): self.setto(["a", "t", "e"]) elif self.ends(["b", "l"]): self.setto(["b", "l", "e"]) elif self.ends(["i", "z"]): self.setto(["i", "z", "e"]) elif self.doublec(self.k): self.k -= 1 if self.b[self.k] in "lsz": self.k += 1 elif self.m() == 1 and self.cvc(self.k): self.setto(["e"]) def step1c(self): """ turn terminal y into i if there's a vowel in stem """ if self.ends(["y"]) and self.vowel_in_stem(): self.b[self.k] = "i" def step2and3(self): for end, repl in _s2_options.get(self.b[self.k - 1], []): if self.ends(end): self.r(repl) break for end, repl in _s3_options.get(self.b[self.k], []): if self.ends(end): self.r(repl) break def step4(self): ch = self.b[self.k - 1] if ch == "o": if not ( (self.ends(["i", "o", "n"]) and self.b[self.j] in "st") or self.ends(["o", "u"]) ): return else: endings = _s4_endings.get(ch, []) for end in endings: if self.ends(end): break else: return if self.m() > 1: self.k = self.j def step5(self): self.j = self.k if self.b[self.k] == "e": a = self.m() if a > 1 or a == 1 and not self.cvc(self.k - 1): self.k -= 1 if self.b[self.k] == "l" and self.doublec(self.k) and self.m() > 1: self.k -= 1 def result(self): return "".join(self.b[: self.k + 1]) def stem(self): if self.k > 1: self.step1ab() self.step1c() self.step2and3() self.step4() self.step5() return self.result()