A basic ability when dealing with text is to be able to properly manipulate that text. That means to be able to join it, split it into regular chunks, or change it to be uppercase or lowercase. We'll discuss more advanced methods for parsing text and separating it later, but in lots of cases it is useful to divide a paragraph into lines, sentences, or even words. Other times, words will have to have some characters removed or replaced with a canonical version to be able to compare it with a determined value.
Manipulating strings
Getting ready
We'll define a basic text to transform it into its main components, and then we'll reconstruct it. As an example, a report needs to be transformed into a new format to be sent via email.
The input format we'll use in this example will be this:
AFTER THE CLOSE OF THE SECOND QUARTER, OUR COMPANY, CASTAÑACORP
HAS ACHIEVED A GROWTH IN THE REVENUE OF 7.47%. THIS IS IN LINE
WITH THE OBJECTIVES FOR THE YEAR. THE MAIN DRIVER OF THE SALES HAS BEEN
THE NEW PACKAGE DESIGNED UNDER THE SUPERVISION OF OUR MARKETING DEPARTMENT.
OUR EXPENSES HAS BEEN CONTAINED, INCREASING ONLY BY 0.7%, THOUGH THE BOARD
CONSIDERS IT NEEDS TO BE FURTHER REDUCED. THE EVALUATION IS SATISFACTORY
AND THE FORECAST FOR THE NEXT QUARTER IS OPTIMISTIC. THE BOARD EXPECTS
AN INCREASE IN PROFIT OF AT LEAST 2 MILLION DOLLARS.
We need to redact the text to eliminate any references to numbers. It needs to be properly formatted by adding a new line after each period, justified with 80 characters, and transformed into ASCII for compatibility reasons.
The text will be stored in the INPUT_TEXT variable in the interpreter.
How to do it...
- After entering the text, split it into individual words:
>>> INPUT_TEXT = '''
... AFTER THE CLOSE OF THE SECOND QUARTER, OUR COMPANY, CASTAÑACORP
... HAS ACHIEVED A GROWTH IN THE REVENUE OF 7.47%. THIS IS IN LINE
...
'''
>>> words = INPUT_TEXT.split()
- Replace any numerical digits with an 'X' character:
>>> redacted = [''.join('X' if w.isdigit() else w for w in word) for word in words]
- Transform the text into pure ASCII (note that the name of the company contains a letter, ñ, which is not ASCII):
>>> ascii_text = [word.encode('ascii', errors='replace').decode('ascii')
... for word in redacted]
- Group the words into 80-character lines:
>>> newlines = [word + '\n' if word.endswith('.') else word for word in ascii_text]
>>> LINE_SIZE = 80
>>> lines = []
>>> line = ''
>>> for word in newlines:
... if line.endswith('\n') or len(line) + len(word) + 1 > LINE_SIZE:
... lines.append(line)
... line = ''
... line = line + ' ' + word
- Format all lines as titles and join them as a single piece of text:
>>> lines = [line.title() for line in lines]
>>> result = '\n'.join(lines)
- Print the result:
>>> print(result)
After The Close Of The Second Quarter, Our Company, Casta?Acorp Has Achieved A
Growth In The Revenue Of X.Xx%.
This Is In Line With The Objectives For The Year.
The Main Driver Of The Sales Has Been The New Package Designed Under The
Supervision Of Our Marketing Department.
Our Expenses Has Been Contained, Increasing Only By X.X%, Though The Board
Considers It Needs To Be Further Reduced.
The Evaluation Is Satisfactory And The Forecast For The Next Quarter Is
Optimistic.
How it works...
Each of the steps performs a specific transformation of the text:
- The first one splits the text on the default separators, whitespaces, and new lines. This splits it into individual words with no lines or multiple spaces for separation.
- To replace the digits, we go through every character of each word. For each one, if it's a digit, an 'X' is returned instead. This is done with two list comprehensions, one to run on the list, and another on each word, replacing only if there's a digit—['X' if w.isdigit() else w for w in word]. Note that the words are joined together again.
- Each of the words is encoded into an ASCII byte sequence and decoded back again into the Python string type. Note the use of the errors parameter to force the replacement of unknown characters such as ñ.
- This step first adds an extra newline character (the \n character) for all words ending with a period. This marks the different paragraphs. After that, it creates a line and adds the words one by one. If an extra word will make it go over 80 characters, it finishes the line and starts a new one. If the line already ends with a new line, it finishes it and starts another one as well. Note that there's an extra space added to separate the words.
- Finally, each of the lines is capitalized as a Title (the first letter of each word is upper cased) and all the lines are joined through new lines.
There's more...
Some other useful operations that can be performed on strings are as follows:
- Strings can be sliced like any other list. This means that 'word'[0:2] will return 'wo'.
- Use .splitlines() to separate lines by newline character.
- There are .upper() and .lower() methods, which return a copy with all the characters set to uppercase or lowercase. Their use is very similar to .title():
>>> 'UPPERCASE'.lower()
'uppercase'
- For easy replacements (for example, change all A to B or change mine to ours), use .replace(). This method is useful for very simple cases, but replacements can get tricky easily. Be careful with the order of replacements to avoid collisions and case sensitivity issues. Note the wrong replacement in the following example:
>>> 'One ring to rule them all, one ring to find them, One ring to bring them all and in the darkness bind them.'.replace('ring', 'necklace')
'One necklace to rule them all, one necklace to find them, One necklace to bnecklace them all and in the darkness bind them.'
This is similar to the issues we'll see with regular expressions matching unexpected parts of your code.
If you work with multiple languages, or with any kind of non-English input, it is very useful to learn the basics of Unicode and encodings. In a nutshell, given the vast amount of characters in all the different languages in the world, including alphabets not related to the Latin one, such as Chinese or Arabic, there's a standard to try and cover all of them so that computers can properly understand them. Python 3 greatly improved this situation, making the strings internal objects to deal with all of those characters. The encoding that Python uses, and the most common and compatible one, is currently UTF-8.
Dealing with encodings is still relevant when reading from external files that can be encoded in different encodings (for example, CP-1252 or windows-1252, which is a common encoding produced by legacy Microsoft systems, or ISO 8859-15, which is the industry standard).
See also
- The Creating strings with formatted values recipe
- The Introducing regular expressions recipe
- The Going deeper into regular expressions recipe
- The Dealing with Encodings recipe in Chapter 4, Searching and Reading Local Files