Completed
Push — master ( f43547...71985b )
by Chris
12:00 queued 10s
created

abydos.phonetic._phonex   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 205
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 78
dl 0
loc 205
ccs 69
cts 69
cp 1
rs 9.68
c 0
b 0
f 0
wmc 34

1 Method

Rating   Name   Duplication   Size   Complexity  
F Phonex.encode() 0 118 33

1 Function

Rating   Name   Duplication   Size   Complexity  
A phonex() 0 32 1
1
# -*- coding: utf-8 -*-
2
3
# Copyright 2014-2018 by Christopher C. Little.
4
# This file is part of Abydos.
5
#
6
# Abydos is free software: you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation, either version 3 of the License, or
9
# (at your option) any later version.
10
#
11
# Abydos is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with Abydos. If not, see <http://www.gnu.org/licenses/>.
18
19 1
"""abydos.phonetic._phonex.
20
21
Phonex
22
"""
23
24 1
from __future__ import (
25
    absolute_import,
26
    division,
27
    print_function,
28
    unicode_literals,
29
)
30
31 1
from unicodedata import normalize as unicode_normalize
32
33 1
from six import text_type
34 1
from six.moves import range
35
36 1
from ._phonetic import _Phonetic
37
38 1
__all__ = ['Phonex', 'phonex']
39
40
41 1
class Phonex(_Phonetic):
0 ignored issues
show
Unused Code introduced by
The variable __class__ seems to be unused.
Loading history...
42
    """Phonex code.
43
44
    Phonex is an algorithm derived from Soundex, defined in :cite:`Lait:1996`.
45
    """
46
47 1
    def encode(self, word, max_length=4, zero_pad=True):
0 ignored issues
show
Bug introduced by
Parameters differ from overridden 'encode' method
Loading history...
48
        """Return the Phonex code for a word.
49
50
        Parameters
51
        ----------
52
        word : str
53
            The word to transform
54
        max_length : int
55
            The length of the code returned (defaults to 4)
56
        zero_pad : bool
57
            Pad the end of the return value with 0s to achieve a max_length
58
            string
59
60
        Returns
61
        -------
62
        str
63
            The Phonex value
64
65
        Examples
66
        --------
67
        >>> pe = Phonex()
68
        >>> pe.encode('Christopher')
69
        'C623'
70
        >>> pe.encode('Niall')
71
        'N400'
72
        >>> pe.encode('Schmidt')
73
        'S253'
74
        >>> pe.encode('Smith')
75
        'S530'
76
77
        """
78 1
        name = unicode_normalize('NFKD', text_type(word.upper()))
79 1
        name = name.replace('ß', 'SS')
80
81
        # Clamp max_length to [4, 64]
82 1
        if max_length != -1:
83 1
            max_length = min(max(4, max_length), 64)
84
        else:
85 1
            max_length = 64
86
87 1
        name_code = last = ''
88
89
        # Deletions effected by replacing with next letter which
90
        # will be ignored due to duplicate handling of Soundex code.
91
        # This is faster than 'moving' all subsequent letters.
92
93
        # Remove any trailing Ss
94 1
        while name[-1:] == 'S':
95 1
            name = name[:-1]
96
97
        # Phonetic equivalents of first 2 characters
98
        # Works since duplicate letters are ignored
99 1
        if name[:2] == 'KN':
100 1
            name = 'N' + name[2:]  # KN.. == N..
101 1
        elif name[:2] == 'PH':
102 1
            name = 'F' + name[2:]  # PH.. == F.. (H ignored anyway)
103 1
        elif name[:2] == 'WR':
104 1
            name = 'R' + name[2:]  # WR.. == R..
105
106 1
        if name:
107
            # Special case, ignore H first letter (subsequent Hs ignored
108
            # anyway)
109
            # Works since duplicate letters are ignored
110 1
            if name[0] == 'H':
111 1
                name = name[1:]
112
113 1
        if name:
114
            # Phonetic equivalents of first character
115 1
            if name[0] in self._uc_vy_set:
116 1
                name = 'A' + name[1:]
117 1
            elif name[0] in {'B', 'P'}:
118 1
                name = 'B' + name[1:]
119 1
            elif name[0] in {'V', 'F'}:
120 1
                name = 'F' + name[1:]
121 1
            elif name[0] in {'C', 'K', 'Q'}:
122 1
                name = 'C' + name[1:]
123 1
            elif name[0] in {'G', 'J'}:
124 1
                name = 'G' + name[1:]
125 1
            elif name[0] in {'S', 'Z'}:
126 1
                name = 'S' + name[1:]
127
128 1
            name_code = last = name[0]
129
130
        # Modified Soundex code
131 1
        for i in range(1, len(name)):
132 1
            code = '0'
133 1
            if name[i] in {'B', 'F', 'P', 'V'}:
134 1
                code = '1'
135 1
            elif name[i] in {'C', 'G', 'J', 'K', 'Q', 'S', 'X', 'Z'}:
136 1
                code = '2'
137 1
            elif name[i] in {'D', 'T'}:
138 1
                if name[i + 1 : i + 2] != 'C':
139 1
                    code = '3'
140 1
            elif name[i] == 'L':
141 1
                if name[i + 1 : i + 2] in self._uc_vy_set or i + 1 == len(
142
                    name
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
143
                ):
144 1
                    code = '4'
145 1
            elif name[i] in {'M', 'N'}:
146 1
                if name[i + 1 : i + 2] in {'D', 'G'}:
147 1
                    name = name[: i + 1] + name[i] + name[i + 2 :]
148 1
                code = '5'
149 1
            elif name[i] == 'R':
150 1
                if name[i + 1 : i + 2] in self._uc_vy_set or i + 1 == len(
151
                    name
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
152
                ):
153 1
                    code = '6'
154
155 1
            if code != last and code != '0' and i != 0:
156 1
                name_code += code
157
158 1
            last = name_code[-1]
159
160 1
        if zero_pad:
161 1
            name_code += '0' * max_length
162 1
        if not name_code:
163 1
            name_code = '0'
164 1
        return name_code[:max_length]
165
166
167 1
def phonex(word, max_length=4, zero_pad=True):
168
    """Return the Phonex code for a word.
169
170
    This is a wrapper for :py:meth:`Phonex.encode`.
171
172
    Parameters
173
    ----------
174
    word : str
175
        The word to transform
176
    max_length : int
177
        The length of the code returned (defaults to 4)
178
    zero_pad : bool
179
        Pad the end of the return value with 0s to achieve a max_length string
180
181
    Returns
182
    -------
183
    str
184
        The Phonex value
185
186
    Examples
187
    --------
188
    >>> phonex('Christopher')
189
    'C623'
190
    >>> phonex('Niall')
191
    'N400'
192
    >>> phonex('Schmidt')
193
    'S253'
194
    >>> phonex('Smith')
195
    'S530'
196
197
    """
198 1
    return Phonex().encode(word, max_length, zero_pad)
199
200
201
if __name__ == '__main__':
202
    import doctest
203
204
    doctest.testmod()
205