Coverage for src/toolbox_python/strings.py: 100%
22 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-02-24 10:34 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-02-24 10:34 +0000
1# ============================================================================ #
2# #
3# Title : Strings #
4# Purpose : Manipulate and check strings. #
5# #
6# ============================================================================ #
9# ---------------------------------------------------------------------------- #
10# #
11# Overview ####
12# #
13# ---------------------------------------------------------------------------- #
16# ---------------------------------------------------------------------------- #
17# Description ####
18# ---------------------------------------------------------------------------- #
21"""
22!!! note "Summary"
23 The `strings` module is for manipulating and checking certain string objects.
24"""
27# ---------------------------------------------------------------------------- #
28# #
29# Setup ####
30# #
31# ---------------------------------------------------------------------------- #
34# ---------------------------------------------------------------------------- #
35# Imports ####
36# ---------------------------------------------------------------------------- #
39# ## Python StdLib Imports ----
40import re
41import string
43# ## Python Third Party Imports ----
44from typeguard import typechecked
46# ## Local First Party Imports ----
47from toolbox_python.collection_types import str_list, str_list_tuple
50# ---------------------------------------------------------------------------- #
51# Exports ####
52# ---------------------------------------------------------------------------- #
54__all__: str_list = [
55 "str_replace",
56 "str_contains",
57 "str_contains_any",
58 "str_contains_all",
59 "str_separate_number_chars",
60]
63# ---------------------------------------------------------------------------- #
64# #
65# Functions ####
66# #
67# ---------------------------------------------------------------------------- #
70@typechecked
71def str_replace(
72 old_string: str,
73 replace_chars: str = string.punctuation + string.whitespace,
74 replace_with: str = "",
75) -> str:
76 """
77 !!! note "Summary"
78 Replace the characters with a given string.
80 ???+ abstract "Details"
81 Similar to the Python `#!py str.replace()` method, but provides more customisation through the use of the [`re`](https://docs.python.org/3/library/re.html) package.
83 Params:
84 old_string (str):
85 The old string to be replaced.
86 replace_chars (str, optional):
87 The characters that need replacing.<br>
88 Defaults to `#!py string.punctuation + string.whitespace`.
89 replace_with (str, optional):
90 The value to replace the characters with.<br>
91 Defaults to `""`.
93 Raises:
94 TypeError: If any of the inputs parsed to the parameters of this function are not the correct type. Uses the [`@typeguard.typechecked`](https://typeguard.readthedocs.io/en/stable/api.html#typeguard.typechecked) decorator.
96 Returns:
97 (str):
98 The new formatted string.
100 ???+ example "Examples"
102 ```{.py .python linenums="1" title="Set up"}
103 >>> from toolbox_python.strings import str_replace
104 >>> long_string = "This long string"
105 >>> complex_sentence = "Because my pizza was cold, I put it in the microwave."
106 ```
108 ```{.py .python linenums="1" title="Example 1: Replace all spaces (` `) with underscore (`_`)"}
109 >>> print(str_replace(long_string, " ", "_"))
110 ```
111 <div class="result" markdown>
112 ```{.sh .shell title="Terminal"}
113 "This_long_string"
114 ```
115 !!! success "Conclusion: Successful conversion."
116 </div>
118 ```{.py .python linenums="1" title="Example 2: Remove all punctuation and white space"}
119 >>> print(str_replace(complex_sentence))
120 ```
121 <div class="result" markdown>
122 ```{.sh .shell title="Terminal"}
123 "BecausemylunchwascoldIputitinthemicrowave"
124 ```
125 !!! success "Conclusion: Successful conversion."
126 </div>
128 ```{.py .python linenums="1" title="Example 3: Invalid `old_string` input"}
129 >>> print(str_replace(123))
130 ```
131 <div class="result" markdown>
132 ```{.sh .shell title="Terminal"}
133 TypeError: ...
134 ```
135 !!! failure "Conclusion: Invalid input."
136 !!! observation "Note: The same error will occur if `replace_chars` or `replace_with` are not of type `str`."
137 </div>
139 ??? success "Credit"
140 Full credit goes to:<br>
141 https://stackoverflow.com/questions/23996118/replace-special-characters-in-a-string-python#answer-23996414
143 ??? tip "See Also"
144 - [`re`](https://docs.python.org/3/library/re.html)
145 """
146 chars: str = re.escape(replace_chars)
147 return re.sub(rf"[{chars}]", replace_with, old_string)
150@typechecked
151def str_contains(check_string: str, sub_string: str) -> bool:
152 """
153 !!! note "Summary"
154 Check whether one string contains another string.
156 ???+ abstract "Details"
157 This is a super simple one-line function.
159 ```py linenums="1" title="Example"
160 return True if sub_string in check_string else False
161 ```
163 Params:
164 check_string (str):
165 The main string to check.
166 sub_string (str):
167 The substring to check.
169 Raises:
170 TypeError: If any of the inputs parsed to the parameters of this function are not the correct type. Uses the [`@typeguard.typechecked`](https://typeguard.readthedocs.io/en/stable/api.html#typeguard.typechecked) decorator.
172 Returns:
173 (bool):
174 `#!py True` if `#!py sub_string` in `#!py check_string`
176 ???+ example "Examples"
178 ```{.py .python linenums="1" title="Set up"}
179 >>> from toolbox_python.strings import str_contains
180 >>> long_string = "This long string"
181 ```
183 ```{.py .python linenums="1" title="Example 1: String is contained"}
184 >>> print(str_contains(long_string, "long"))
185 ```
186 <div class="result" markdown>
187 ```{.sh .shell title="Terminal"}
188 True
189 ```
190 !!! success "Conclusion: `#!py long_string` contains `#!py "long"`."
191 </div>
193 ```{.py .python linenums="1" title="Example 2: String is not contained"}
194 >>> print(str_contains(long_string, "short"))
195 ```
196 <div class="result" markdown>
197 ```{.sh .shell title="Terminal"}
198 False
199 ```
200 !!! success "Conclusion: `#!py long_string` does not contain `#!py "short"`."
201 </div>
203 ```{.py .python linenums="1" title="Example 3: Invalid `check_string` input"}
204 >>> print(str_contains(123, "short"))
205 ```
206 <div class="result" markdown>
207 ```{.sh .shell title="Terminal"}
208 TypeError: ...
209 ```
210 !!! failure "Conclusion: Invalid input."
211 !!! observation "Note: The same error will occur if `sub_string` is not of type `str`."
212 </div>
214 ??? tip "See Also"
215 - [`str_contains_any()`][toolbox_python.strings.str_contains_any]
216 - [`str_contains_all()`][toolbox_python.strings.str_contains_all]
217 """
218 return sub_string in check_string
221@typechecked
222def str_contains_any(
223 check_string: str,
224 sub_strings: str_list_tuple,
225) -> bool:
226 """
227 !!! note "Summary"
228 Check whether any one of a number of strings are contained within a main string.
230 Params:
231 check_string (str):
232 The main string to check.
233 sub_strings (str_list_tuple):
234 The collection of substrings to check.
236 Raises:
237 TypeError: If any of the inputs parsed to the parameters of this function are not the correct type. Uses the [`@typeguard.typechecked`](https://typeguard.readthedocs.io/en/stable/api.html#typeguard.typechecked) decorator.
239 Returns:
240 (bool):
241 `#!py True` if `#!py any` of the strings in `#!py sub_strings` are contained within `#!py check_string`.
243 ???+ example "Examples"
245 ```{.py .python linenums="1" title="Set up"}
246 >>> from toolbox_python.strings import str_contains_any
247 >>> long_string = "This long string"
248 ```
250 ```{.py .python linenums="1" title="Example 1: Contains any"}
251 >>> print(str_contains_any(long_string, ["long", "short"]))
252 ```
253 <div class="result" markdown>
254 ```{.sh .shell title="Terminal"}
255 True
256 ```
257 !!! success "Conclusion: `#!py long_string` contains either `#!py "long"` or `#!py "short"`."
258 </div>
260 ```{.py .python linenums="1" title="Example 2: Contains none"}
261 >>> print(str_contains_any(long_string, ["this", "that"]))
262 ```
263 <div class="result" markdown>
264 ```{.sh .shell title="Terminal"}
265 False
266 ```
267 !!! success "Conclusion: `#!py long_string` contains neither `#!py "this"` nor `#!py "that"`."
268 </div>
270 ```{.py .python linenums="1" title="Example 3: Invalid `check_string` input"}
271 >>> print(str_contains_any(123, ["short", "long"]))
272 ```
273 <div class="result" markdown>
274 ```{.sh .shell title="Terminal"}
275 TypeError: ...
276 ```
277 !!! failure "Conclusion: Invalid input."
278 !!! observation "Note: The same error will occur if any of the elements in `sub_strings` are not of type `str`."
279 </div>
281 ??? tip "See Also"
282 - [`str_contains()`][toolbox_python.strings.str_contains]
283 - [`str_contains_all()`][toolbox_python.strings.str_contains_all]
284 """
285 return any(
286 str_contains(
287 check_string=check_string,
288 sub_string=sub_string,
289 )
290 for sub_string in sub_strings
291 )
294@typechecked
295def str_contains_all(
296 check_string: str,
297 sub_strings: str_list_tuple,
298) -> bool:
299 """
300 !!! note "Summary"
301 Check to ensure that all sub-strings are contained within a main string.
303 Params:
304 check_string (str):
305 The main string to check.
306 sub_strings (str_list_tuple):
307 The collection of substrings to check.
309 Raises:
310 TypeError: If any of the inputs parsed to the parameters of this function are not the correct type. Uses the [`@typeguard.typechecked`](https://typeguard.readthedocs.io/en/stable/api.html#typeguard.typechecked) decorator.
312 Returns:
313 (bool):
314 `#!py True` if `#!py all` of the strings in `#!py sub_strings` are contained within `#!py check_string`.
316 ???+ example "Examples"
318 ```{.py .python linenums="1" title="Set up"}
319 >>> from toolbox_python.strings import str_contains_all
320 >>> long_string = "This long string"
321 ```
323 ```{.py .python linenums="1" title="Example 1: Contains all"}
324 >>> print(str_contains_all(long_string, ["long", "string"]))
325 ```
326 <div class="result" markdown>
327 ```{.sh .shell title="Terminal"}
328 True
329 ```
330 !!! success "Conclusion: `#!py long_string` contains both `#!py "long"` and `#!py "string"`."
331 </div>
333 ```{.py .python linenums="1" title="Example 2: Contains some"}
334 >>> print(str_contains_all(long_string, ["long", "something"]))
335 ```
336 <div class="result" markdown>
337 ```{.sh .shell title="Terminal"}
338 False
339 ```
340 !!! failure "Conclusion: `#!py long_string` contains `#!py "long"` but not `#!py "something"`."
341 </div>
343 ```{.py .python linenums="1" title="Example 3: Contains none"}
344 >>> print(str_contains_all(long_string, ["this", "that"]))
345 ```
346 <div class="result" markdown>
347 ```{.sh .shell title="Terminal"}
348 False
349 ```
350 !!! failure "Conclusion: `#!py long_string` contains neither `#!py "this"` nor `#!py "that"`."
351 </div>
353 ```{.py .python linenums="1" title="Example 4: Invalid `check_string` input"}
354 >>> print(str_contains_all(123, ["short", "long"]))
355 ```
356 <div class="result" markdown>
357 ```{.sh .shell title="Terminal"}
358 TypeError: ...
359 ```
360 !!! failure "Conclusion: Invalid input."
361 !!! observation "Note: The same error will occur if any of the elements in `sub_strings` are not of type `str`."
362 </div>
364 ??? tip "See Also"
365 - [`str_contains()`][toolbox_python.strings.str_contains]
366 - [`str_contains_any()`][toolbox_python.strings.str_contains_any]
367 """
368 return all(
369 str_contains(
370 check_string=check_string,
371 sub_string=sub_string,
372 )
373 for sub_string in sub_strings
374 )
377@typechecked
378def str_separate_number_chars(text: str) -> str_list:
379 """
380 !!! note "Summary"
381 Take in a string that contains both numbers and letters, and output a list of strings, separated to have each element containing either entirely number or entirely letters.
383 ???+ abstract "Details"
384 Uses regex ([`re.split()`](https://docs.python.org/3/library/re.html#re.split)) to perform the actual splitting.<br>
385 Note, it _will_ preserve special characters & punctuation, but it _will not_ preserve whitespaces.
387 Params:
388 text (str):
389 The string to split.
391 Raises:
392 TypeError: If any of the inputs parsed to the parameters of this function are not the correct type. Uses the [`@typeguard.typechecked`](https://typeguard.readthedocs.io/en/stable/api.html#typeguard.typechecked) decorator.
394 Returns:
395 (str_list):
396 The updated list, with each element of the list containing either entirely characters or entirely numbers.
398 ???+ example "Examples"
400 ```{.py .python linenums="1" title="Set up"}
401 >>> from toolbox_python.strings import str_contains_all
402 >>> simple_string = "-12.1grams"
403 >>> complex_string = "abcd2343 abw34324 abc3243-23A 123"
404 ```
406 ```{.py .python linenums="1" title="Example 1: Simple split"}
407 >>> print(str_separate_number_chars(simple_string))
408 ```
409 <div class="result" markdown>
410 ```{.sh .shell title="Terminal"}
411 ["-12.1", "grams"]
412 ```
413 !!! success "Conclusion: Successful split."
414 </div>
416 ```{.py .python linenums="1" title="Example 2: Complex split"}
417 >>> print(str_separate_number_chars(complex_string))
418 ```
419 <div class="result" markdown>
420 ```{.sh .shell title="Terminal"}
421 [
422 "abcd",
423 "2343",
424 "abw",
425 "34324",
426 "abc",
427 "3243",
428 "-23",
429 "A",
430 "123",
431 ]
432 ```
433 !!! success "Conclusion: Successful split."
434 </div>
436 ```{.py .python linenums="1" title="Example 3: `text` does not contain any numbers"}
437 >>> print(str_separate_number_chars("abcd"))
438 ```
439 <div class="result" markdown>
440 ```{.sh .shell title="Terminal"}
441 ["abcd"]
442 ```
443 !!! success "Conclusion: No numbers in `#!py text`, so returns a single-element long list."
444 </div>
446 ```{.py .python linenums="1" title="Example 4: Invalid `text` input"}
447 >>> print(str_separate_number_chars(123))
448 ```
449 <div class="result" markdown>
450 ```{.sh .shell title="Terminal"}
451 TypeError: ...
452 ```
453 !!! failure "Conclusion: Invalid input."
454 </div>
456 ??? success "Credit"
457 Full credit goes to:<br>
458 https://stackoverflow.com/questions/3340081/product-code-looks-like-abcd2343-how-to-split-by-letters-and-numbers#answer-63362709.
460 ??? tip "See Also"
461 - [`re`](https://docs.python.org/3/library/re.html)
462 """
463 res = re.split(r"([-+]?\d+\.\d+)|([-+]?\d+)", text.strip())
464 return [r.strip() for r in res if r is not None and r.strip() != ""]