As part of my college class, I have to write a program that returns the "smallest" letter in the string. Input is assumed to be a non empty string. "Smallest" is defined as:
The smallest decimal value of the character, pertaining to this chart.
The range of acceptable values is 0 <= x <= 127
I would like to get feedback on the algorithm of the code. Other suggestions are accepted and welcome, but the main algorithm is the main focus.
def smallest_letter(string: str) -> str:
"""
Returns the smallest letter in the string
Input is restricted to ASCII characters in range 0 <= character <= 127
:param string: A string containing only ASCII letters
:return: A string length one of the smallest letter
"""
# Ensure all characters are within the acceptable range #
for character in string:
assert ord(character) <= 127
smallest_letter = 1000
for letter in string:
if ord(letter) < smallest_letter:
smallest_letter = ord(letter)
return chr(smallest_letter)
if __name__ == "__main__":
# Test Cases #
assert smallest_letter("abydsufaksjdf") == "a"
assert smallest_letter("bfsufsjfbeywafbiu") == "a"
assert smallest_letter("ABYUVGDuibfsafuofiw") == "A"
3 Answers 3
I'll second what Gloweye said in this answer, namely that assert
should not be used for control flow.
This solution combines many of the other answers:
def smallest_character(str_in: str) -> str:
min_ord = 128
for curr_char_ord in (ord(c) for c in str_in):
if curr_char_ord > 127:
raise ValueError(f'Character {chr(curr_char_ord)} in smallest_letter() arg has ord value {curr_char_ord} '
f'which is above the allowed maximum of 127')
else:
min_ord = min(min_ord, curr_char_ord)
return chr(min_ord)
This solution uses min()
and max()
:
def smallest_character(str_in: str) -> str:
min_val = min(str_in)
max_val = max(str_in)
if ord(max_val) > 127:
raise ValueError(
f'Character {max_val} in smallest_letter() arg has ord value {ord(max_val)} which is above the allowed '
f'maximum of 127')
else:
return min_val
Regarding min(string)
: Python exceeding your expectations. It happens a lot.
assert
All assert statements can be disabled with a switch to the interpreter, and sometimes are. Therefore, they're not suitable for flow control. I think you should replace:
for character in string:
assert ord(character) <= 127
With:
for character in string:
if ord(character) > 127:
raise ValueError(f"Character {character} is out of range.")
or to optimize with a generator instead of the loop (requires Python 3.8+ if you want to report the character that violates the condition):
if any(ord(character := char) > 127 for char in string):
raise ValueError(f"Character {character} out of range")
(Thanks to @Graipher for proper handling of the := token in 3.8+, which I hadn't worked with myself yet.)
That's all I can see being wrong here, though.
-
\$\begingroup\$ Me neither, took me a while to find an online interpreter, but TIO has it: tio.run/#python38pr \$\endgroup\$Graipher– Graipher2019年11月04日 16:03:44 +00:00Commented Nov 4, 2019 at 16:03
-
1\$\begingroup\$ Whoopsie. fixed. \$\endgroup\$Gloweye– Gloweye2019年11月04日 17:58:08 +00:00Commented Nov 4, 2019 at 17:58
Of course min("3sdsdf44ldfkTsdfnsnприветsdfa5É")
(contains unicode chars) approach won't be suitable in case if validating/requiring only ASCII chars.
Issues of initial approach :
- validating empty string. To ensure non-empty input string we'll add a simple assertion at start:
assert string != "", "Empty string"
doubled traversals. On valid input strings like
"3sdsdf44ldfkTe45456fghfgh678567sdfnsnsdfa23"
where the char with minimal code would be at the end part of the string the former approach will make a double traversal though 2for
loops.
To avoid that inefficiency we can combine validation and comparison/accumulation logic to be on a single iteration. (you may run time performance measurements to see the difference)ord(letter)
. Duplicated calls can be eliminated through applying Extract variable technique:char_code = ord(char)
The final optimized version:
def smallest_letter(string: str) -> str:
"""
Returns the smallest letter in the string
Input is restricted to ASCII characters in range 0 <= character <= 127
:param string: A string containing only ASCII letters
:return: A string length one of the smallest letter
"""
assert string != "", "Empty string"
max_code, min_code = 128, 128
for char in string:
char_code = ord(char)
assert char_code < max_code, f"{char} is not ASCII character" # ensure the char is in acceptable code range
if char_code < min_code:
min_code = char_code
return chr(min_code)
Explore related questions
See similar questions with these tags.
min("abydsufaksjdf")
? \$\endgroup\$min
method could do those computations. I'll tag this asreinventing-the-wheel
. \$\endgroup\$