# Recognise numbers 1 to 6 in various notations

I'm embarrassed I spent so much time on this seemingly simple but tricky coding exercise.

The task is this: input is various strings - digits, characters, non-English characters, e.g. some could be Cyrillic. The input must be split into words and then each word must be interpreted numerically if possible. It should handle actual digits from 1 to 6 as valid numbers. All others should be printed as is.

Valid input includes positive, int or float type numbers 1 to 6, such as the following:

• 1,2,3,4,5,6
• +1, +2, ... , +6
• 1.0, 2.0,... 6.0
• any scientific notation that represent numbers 1 to 6.

if number is 1 - print "rule1" , 2- "rule2" etc.

Special case is if input number can be interpreted as 0, or any other variation of 0: .0 , 0.0000 or 0.e00. It prints all 6 - "rule1, rule2,.... rule6".

Invalid input includes negative numbers, they should be printed as-is: -1 prints '-1', -2 prints -2.

• any digits in scientific notation that are not in [1 .. 6] should be printed as is.
• any single char or string, English and non-English (like dot) is printed as-is; 'nine' is printed 'nine' and "ывъхзъх" is printed "ывъхзъх".

Sample input:

1.0  03

+5

2.0e0
+.4e1
6.E+00
20e-1 2e1 2ва . 1e-307


expected output:

rule1
rule3
rule5
rule2
rule4
rule6
rule2
2e1
2ва
.
1e-307


Here is my code. I'm looking to make it more elegant.

from math import modf
import fileinput

rule1 = 'rule1'
rule2 = 'rule2'
rule3 = 'rule3'
rule4 = 'rule4'
rule5 = 'rule5'
rule6 = 'rule6'

def print_rule(n):
if n == 1:
print(rule1)
elif n == 2:
print(rule2)
elif n == 3:
print(rule3)
elif n == 4:
print(rule4)
elif n == 5:
print(rule5)
elif n == 6:
print(rule6)

def print_all():
print(rule1)
print(rule2)
print(rule3)
print(rule4)
print(rule5)
print(rule6)

def get_num(s):
try:
s = int(s)

if s == 0:
print_all()

elif s in [1, 2, 3, 4, 5, 6]:
print_rule(s)
elif s < 1 or s > 6:
print(s)
except ValueError:
try:
saved = s
s = float(s)
if s == 0:
print_all()
elif s < 1 or s > 6:
print(saved)

else:
s = float(s)
s = check_float(s)
if s == 0:
print_all()
elif s in [1, 2, 3, 4, 5, 6]:
print_rule(s)
else:
print(s)

except ValueError:
print(s)

def check_float(n):
frac, _ = modf(n)
if frac != 0:
return n
else:
return n

def test2():

for line in fileinput.input():
line = line.strip().split()

for item in line:
get_num(item)

if __name__ == "__main__":

test2()


Your code for print all and print rule is needlessly verbose. You can convert the float into a integer and fraction part - like you did in check_float but never used. And then just check if the integer is in the bounds and that there is no fraction part.

def print_rule(number):
integer, fraction = divmod(number, 0)
if 1 <= integer <= 6 and not fraction:
print(f"rule{int(number)}")


Print all can also just be a for loop.

def print_all():
for number in range(1, 7):
print(f"rule{number}")


The programming challenge is bad. The challenge is just complicated and handling 0 differently to 1-6 is just a gimmick. I'd suggest using a different site, because the challenge is figuring out the challenge is just poorly worded.

When you get a programming challenge that says "this is ok, this is ok, this is ok" and what is ok is constantly being expanded on then the recommended approach is normally to regex. However we can think of it in a more general way; split the code into three parts:

1. Extracting raw data.
You start with building a means to extract what you need from the input. If the challenge says not to allow something this is a problem for later. This is normally done with regex but sometimes using plain Python can be advantageous.

2. Evaluate the raw data.
Here we're converting the raw data into something we can use. Here this is just using float, but sometimes this can be a bit more hands on.

3. Validate the data.
Here you're coding the "if number == 0 ...". Since we've parsed the data this makes the code much simpler.

Your code bundles all this together.

The programming challenge can be reworded to be something far simpler.

• Split the input by whitespace into segments.

for segment in TEXT.split():

• Evaluate the number from scientific notation; if not possible output the segment.

try:
number = float(segment)
except ValueError:
print(segment)
else:
# ...

• If the evaluated number is 1, 2, 3, 4, 5 or 6 then output "rule{number}".
If the evaluated number is 0 output "rule1", "rule2", ... "rule6".

if number in range(0, 7):
numbers = range(1, 7) if number == 0 else [int(number)]
for number in numbers:
print(f"rule{number}")

• Otherwise output the segment.

print(segment)


Then the challenge just becomes as simple benign for loop with some horrible conditions. If value is this and value is this and value is this... And ultimately forces you to use the arrow anti-pattern to have clean code. Not a fan.

TEXT = """\
1.0  03

+5

2.0e0
+.4e1
6.E+00
20e-1 2e1 2ва . 1e-307\
"""

def main():
for segment in TEXT.split():
try:
number = float(segment)
except ValueError:
pass
else:
if number in range(0, 7):
numbers = range(1, 7) if number == 0 else [int(number)]
for number in numbers:
print(f"rule{number}")
continue
print(segment)

if __name__ == "__main__":
main()


If you think just calling float is cheating then you can manually extract and evaluate the number.

To extract we should turn to regex. We can build up from the items in the challenge description.

• 1,2,3 ,4,5,6

[0-6]

• +1, +2, ... +6

\+?[0-6]

• 1.0, 2.0,... 6.0

\+?[0-6](?:\.0)?

• Negative numbers should simply be printed as-is: -1 prints '-1'

[\-\+]?[0-6](?:\.0)?

• any scientific notation that represent numbers 1 to 6.

Firstly we now need to accept any digit not just 0 and 0-6, because +.4e1 = 4. And the numbers can be any length, .04e2 = 4.

[\-\+]?(?:\d*\.\d*|\d+)


Then we need to get scientific notation, which is just smacking an e in-between two numbers.

{number}(?:[eE]{number})?

[\-\+]?(?:\d*\.\d*|\d+)(?:[eE][\-\+]?(?:\d*\.\d*|\d+))?

• Since we want to check if the segment is completely a number then we can check that the match starts at the start and ends at the end too. Or use re.fullmatch

^[\-\+]?(?:\d*\.\d*|\d+)(?:[eE][\-\+]?(?:\d*\.\d*|\d+))?\$


Evaluating a number in scientific notation is simple as we split out the base and exponent. We then convert both to floats and then put them in the equation $$\b10^e\$$, where b is base and e is exponent. Defaulting the exponent to 0 if it doesn't exist.

def scientific_float(number):
base, _, exponent = number.partition("e")
return float(base) * 10**float(exponent or "0")


This automatically handles +\d, -\d, .\d and \d. if you think this is cheating then you can make your own float function.

In all this shows the three stages

import re

# Extract
NUMBER = "[\-\+]?(?:\d*\.\d*|\d+)"
SCIENTIFIC_NUMBER = f"{NUMBER}(?:[eE]{NUMBER})?"
NUMBER_MATCHER = re.compile(SCIENTIFIC_NUMBER)

def scientific_float(number):  # Evaluate
base, _, exponent = number.partition("e")
return float(base) * 10**float(exponent or "0")

def main():
for segment in TEXT.split():  # Extract
if None is not (match := NUMBER_MATCHER.fullmatch(segment)):  # Extract + Validate
try:
number = scientific_float(match.group(0))  # Evaluate
except ValueError:
pass
else:
if number in range(0, 7):
numbers = range(1, 7) if number == 0 else [int(number)]
for number in numbers:
print(f"rule{number}")
continue
print(segment)

if __name__ == "__main__":
main()

• hahah, this print_all is stupid yes Commented Feb 15, 2021 at 22:06