Skip to content

Commit 136ff93

Browse files
committed
improved is_palindrome and docs
1 parent 4a6a4f1 commit 136ff93

File tree

6 files changed

+128
-31
lines changed

6 files changed

+128
-31
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ leading or trailing underscores and string containing multiple underscores in se
6464
- String checks should now be a bit faster (switched from `.search()` to `.match()` in internal regex when the goal
6565
is to match the full string)
6666
- `is_palindrome()` algorithm has been redesigned to offer a faster check and better memory usage
67-
(only 2 chars are now being access at the same time instead of the whole string)
67+
(only 2 chars are now being access at the same time instead of the whole string)...
68+
signature has changed (now it has two optional boolean arguments: `ignore_spaces` and `ignore_case`)
6869
- `slugify()` is now able to translate more non-ascii chars during the string conversion
6970
(it now makes use of the new extracted method `asciify()`)
7071
- `is_uuid()` has now a second parameter `allow_hex` that if true, considers as valid UUID hex value

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ contains_html('my string is not bold') # returns false
178178
**words_count**: Returns the number of words contained in the string
179179
~~~~
180180
words_count('hello world') # returns 2
181-
words_count('one,two,three') # returns 3 (no need for spaces, punctuation is regnognized!)
181+
words_count('one,two,three') # returns 3 (no need for spaces, punctuation is recognized!)
182182
~~~~
183183

184184
**is_palindrome**: Checks if the string is a palindrome
@@ -322,3 +322,4 @@ secure_random_hex(12)
322322
## Documentation
323323
324324
Full API documentation available on: http://python-string-utils.readthedocs.org/en/latest/
325+

string_utils/manipulation.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,11 @@ def format(self) -> str:
301301

302302
def reverse(input_string: str) -> str:
303303
"""
304-
Returns the string reversed ("abc" -> "cba").
304+
Returns the string with its chars reversed.
305+
306+
*Example:*
307+
308+
>>> reverse('hello') # returns 'olleh'
305309
306310
:param input_string: String to revert.
307311
:type input_string: str
@@ -318,6 +322,10 @@ def camel_case_to_snake(input_string, separator='_'):
318322
Convert a camel case string into a snake case one.
319323
(The original string is returned if is not a valid camel case string)
320324
325+
*Example:*
326+
327+
>>> camel_case_to_snake('ThisIsACamelStringTest') # returns 'this_is_a_camel_case_string_test'
328+
321329
:param input_string: String to convert.
322330
:type input_string: str
323331
:param separator: Sign to use as separator.
@@ -338,6 +346,10 @@ def snake_case_to_camel(input_string: str, upper_case_first: bool = True, separa
338346
Convert a snake case string into a camel case one.
339347
(The original string is returned if is not a valid snake case string)
340348
349+
*Example:*
350+
351+
>>> snake_case_to_camel('the_snake_is_green') # returns 'TheSnakeIsGreen'
352+
341353
:param input_string: String to convert.
342354
:type input_string: str
343355
:param upper_case_first: True to turn the first letter into uppercase (default).
@@ -364,7 +376,11 @@ def snake_case_to_camel(input_string: str, upper_case_first: bool = True, separa
364376

365377
def shuffle(input_string: str) -> str:
366378
"""
367-
Return a new string containing shuffled items.
379+
Return a new string containing same chars of the given one but in a randomized order.
380+
381+
*Example:*
382+
383+
>>> shuffle('hello world') # possible output: 'l wodheorll'
368384
369385
:param input_string: String to shuffle
370386
:type input_string: str
@@ -387,6 +403,11 @@ def strip_html(input_string: str, keep_tag_content: bool = False) -> str:
387403
"""
388404
Remove html code contained into the given string.
389405
406+
*Examples:*
407+
408+
>>> strip_html('test: <a href="foo/bar">click here</a>') # returns 'test: '
409+
>>> strip_html('test: <a href="foo/bar">click here</a>', keep_tag_content=True) # returns 'test: click here'
410+
390411
:param input_string: String to manipulate.
391412
:type input_string: str
392413
:param keep_tag_content: True to preserve tag content, False to remove tag and its content too (default).
@@ -417,6 +438,11 @@ def prettify(input_string: str) -> str:
417438
- Percentage sign ("%") cannot be preceded by a space if there is a number before ("100 %" -> "100%")\
418439
- Saxon genitive is correct ("Dave' s dog" -> "Dave's dog")
419440
441+
*Examples:*
442+
443+
>>> prettify(' unprettified string ,, like this one,will be"prettified" .it\\' s awesome! ')
444+
>>> # the ouput will be: 'Unprettified string, like this one, will be "prettified". It\'s awesome!'
445+
420446
:param input_string: String to manipulate
421447
:return: Prettified string.
422448
"""

string_utils/validation.py

Lines changed: 79 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66

77
__all__ = [
88
'is_string',
9+
'is_full_string',
910
'is_number',
1011
'is_integer',
1112
'is_decimal',
12-
'is_full_string',
1313
'is_url',
1414
'is_email',
1515
'is_credit_card',
@@ -83,6 +83,11 @@ def is_string(obj: Any) -> bool:
8383
"""
8484
Checks if an object is a string.
8585
86+
*Example:*
87+
88+
>>> is_string('foo') # returns true
89+
>>> is_string(b'foo') # returns false
90+
8691
:param obj: Object to test.
8792
:return: True if string, false otherwise.
8893
"""
@@ -423,43 +428,61 @@ def is_ip(input_string: Any) -> bool:
423428
return is_ip_v6(input_string) or is_ip_v4(input_string)
424429

425430

426-
def is_palindrome(input_string: Any, strict: bool = True) -> bool:
431+
def is_palindrome(input_string: Any, ignore_spaces: bool = False, ignore_case: bool = False) -> bool:
427432
"""
428433
Checks if the string is a palindrome (https://en.wikipedia.org/wiki/Palindrome).
429434
435+
*Examples:*
436+
437+
>>> is_palindrome('LOL') # returns true
438+
>>> is_palindrome('Lol') # returns false
439+
>>> is_palindrome('Lol', ignore_case=True) # returns true
440+
>>> is_palindrome('ROTFL') # returns false
441+
430442
:param input_string: String to check.
431443
:type input_string: str
432-
:param strict: True if white spaces matter (default), false otherwise.
433-
:type strict: bool
444+
:param ignore_spaces: False if white spaces matter (default), true otherwise.
445+
:type ignore_spaces: bool
446+
:param ignore_case: False if char case matters (default), true otherwise.
447+
:type ignore_case: bool
434448
:return: True if the string is a palindrome (like "otto", or "i topi non avevano nipoti" if strict=False),\
435449
False otherwise
436450
"""
437-
if is_full_string(input_string):
438-
if strict:
439-
string_len = len(input_string)
451+
if not is_full_string(input_string):
452+
return False
440453

441-
# Traverse the string one char at step, and for each step compares the
442-
# "head_char" (the one on the left of the string) to the "tail_char" (the one on the right).
443-
# In this way we avoid to manipulate the whole string in advance if not necessary and provide a faster
444-
# algorithm which can scale very well for long strings.
445-
for index in range(string_len):
446-
head_char = input_string[index]
447-
tail_char = input_string[string_len - index - 1]
454+
if ignore_spaces:
455+
input_string = SPACES_RE.sub('', input_string)
448456

449-
if head_char != tail_char:
450-
return False
457+
string_len = len(input_string)
451458

452-
return True
459+
# Traverse the string one char at step, and for each step compares the
460+
# "head_char" (the one on the left of the string) to the "tail_char" (the one on the right).
461+
# In this way we avoid to manipulate the whole string in advance if not necessary and provide a faster
462+
# algorithm which can scale very well for long strings.
463+
for index in range(string_len):
464+
head_char = input_string[index]
465+
tail_char = input_string[string_len - index - 1]
453466

454-
return is_palindrome(SPACES_RE.sub('', input_string))
467+
if ignore_case:
468+
head_char = head_char.lower()
469+
tail_char = tail_char.lower()
455470

456-
return False
471+
if head_char != tail_char:
472+
return False
473+
474+
return True
457475

458476

459477
def is_pangram(input_string: Any) -> bool:
460478
"""
461479
Checks if the string is a pangram (https://en.wikipedia.org/wiki/Pangram).
462480
481+
*Examples:*
482+
483+
>>> is_pangram('The quick brown fox jumps over the lazy dog') # returns true
484+
>>> is_pangram('hello world') # returns false
485+
463486
:param input_string: String to check.
464487
:type input_string: str
465488
:return: True if the string is a pangram, False otherwise.
@@ -474,6 +497,11 @@ def is_isogram(input_string: Any) -> bool:
474497
"""
475498
Checks if the string is an isogram (https://en.wikipedia.org/wiki/Isogram).
476499
500+
*Examples:*
501+
502+
>>> is_isogram('dermatoglyphics') # returns true
503+
>>> is_isogram('hello') # returns false
504+
477505
:param input_string: String to check.
478506
:type input_string: str
479507
:return: True if isogram, false otherwise.
@@ -485,6 +513,11 @@ def is_slug(input_string: Any, sign: str = '-') -> bool:
485513
"""
486514
Checks if a given string is a slug.
487515
516+
*Examples:*
517+
518+
>>> is_slug('my-blog-post-title') # returns true
519+
>>> is_slug('My blog post title') # returns false
520+
488521
:param input_string: String to check.
489522
:type input_string: str
490523
:param sign: Join sign used by the slug.
@@ -505,6 +538,11 @@ def contains_html(input_string: str) -> bool:
505538
By design, this function is very permissive regarding what to consider html code, don't expect to use it
506539
as an html validator, its goal is to detect "malicious" or undesired html tags in the text.
507540
541+
*Examples:*
542+
543+
>>> contains_html('my string is <strong>bold</strong>') # returns true
544+
>>> contains_html('my string is not bold') # returns false
545+
508546
:param input_string: Text to check
509547
:type input_string: str
510548
:return: True if string contains html, false otherwise.
@@ -524,6 +562,11 @@ def words_count(input_string: str) -> int:
524562
Moreover it is aware of punctuation, so the count for a string like "one,two,three.stop"
525563
will be 4 not 1 (even if there are no spaces in the string).
526564
565+
*Examples:*
566+
567+
>>> words_count('hello world') # returns 2
568+
>>> words_count('one,two,three') # returns 3 (no need for spaces, punctuation is recognized!)
569+
527570
:param input_string: String to check.
528571
:type input_string: str
529572
:return: Number of words.
@@ -540,6 +583,12 @@ def is_isbn_10(input_string: str, normalize: bool = True) -> bool:
540583
By default hyphens in the string are ignored, so digits can be separated in different ways, by calling this
541584
function with `normalize=False` only digit-only strings will pass the validation.
542585
586+
*Examples:*
587+
588+
>>> is_isbn_10('1506715214') # returns true
589+
>>> is_isbn_10('150-6715214') # returns true
590+
>>> is_isbn_10('150-6715214', normalize=False) # returns false
591+
543592
:param input_string: String to check.
544593
:param normalize: True to ignore hyphens ("-") in the string (default), false otherwise.
545594
:return: True if valid ISBN 10, false otherwise.
@@ -554,6 +603,12 @@ def is_isbn_13(input_string: str, normalize: bool = True) -> bool:
554603
By default hyphens in the string are ignored, so digits can be separated in different ways, by calling this
555604
function with `normalize=False` only digit-only strings will pass the validation.
556605
606+
*Examples:*
607+
608+
>>> is_isbn_13('9780312498580') # returns true
609+
>>> is_isbn_13('978-0312498580') # returns true
610+
>>> is_isbn_13('978-0312498580', normalize=False) # returns false
611+
557612
:param input_string: String to check.
558613
:param normalize: True to ignore hyphens ("-") in the string (default), false otherwise.
559614
:return: True if valid ISBN 13, false otherwise.
@@ -568,6 +623,11 @@ def is_isbn(input_string: str, normalize: bool = True) -> bool:
568623
By default hyphens in the string are ignored, so digits can be separated in different ways, by calling this
569624
function with `normalize=False` only digit-only strings will pass the validation.
570625
626+
*Examples:*
627+
628+
>>> is_isbn('9780312498580') # returns true
629+
>>> is_isbn('1506715214') # returns true
630+
571631
:param input_string: String to check.
572632
:param normalize: True to ignore hyphens ("-") in the string (default), false otherwise.
573633
:return: True if valid ISBN (10 or 13), false otherwise.

tests/test_is_palindrome.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,20 @@ def test_non_string_objects_return_false(self):
2626
def test_empty_strings_are_not_palindromes(self):
2727
self.assertFalse(is_palindrome(''))
2828
self.assertFalse(is_palindrome(' '))
29-
self.assertFalse(is_palindrome(' \n\t '))
29+
self.assertFalse(is_palindrome('\n\t\n'))
3030

31-
def test_strict_checking(self):
32-
self.assertFalse(is_palindrome('nope!'))
33-
self.assertFalse(is_palindrome('i topi non avevano nipoti'))
31+
def test_returns_true_if_palindrome_with_default_options(self):
32+
self.assertTrue(is_palindrome('LOL'))
3433
self.assertTrue(is_palindrome('otto'))
3534

36-
def test_no_strict_mode(self):
37-
self.assertFalse(is_palindrome('nope!', False))
38-
self.assertTrue(is_palindrome('i topi non avevano nipoti', False))
39-
self.assertTrue(is_palindrome('otto', False))
35+
def test_returns_false_if_not_palindrome_with_default_options(self):
36+
self.assertFalse(is_palindrome('nope!'))
37+
self.assertFalse(is_palindrome('ROTFL'))
38+
39+
def test_if_not_specified_case_matters(self):
40+
self.assertFalse(is_palindrome('Lol'))
41+
self.assertTrue(is_palindrome('Lol', ignore_case=True))
42+
43+
def test_if_not_specified_spaces_matter(self):
44+
self.assertFalse(is_palindrome('i topi non avevano nipoti'))
45+
self.assertTrue(is_palindrome('i topi non avevano nipoti', ignore_spaces=True))

tests/test_is_string.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ def test_return_false_for_non_string_objects(self):
1111
self.assertFalse(is_string([]))
1212
self.assertFalse(is_string({'a': 1}))
1313

14+
def test_bytes_sequence_is_ont_considered_string(self):
15+
self.assertFalse(is_string(b'nope!'))
16+
1417
def test_return_true_for_string_objects(self):
1518
self.assertTrue(is_string(''))
1619
self.assertTrue(is_string('hello world'))

0 commit comments

Comments
 (0)