1- # NOTE: This file is intentionally written with poor style for the exam.
2- # Your task: refactor to be PEP 8-friendly while keeping behavior unchanged.
1+ """CLI tool for simple statistics (exam task).
32
4- import math ,os ,sys
3+ This module is intentionally included in the exam with poor style in the
4+ starter version. The candidate should refactor it to be PEP 8 friendly
5+ without changing its observable behavior.
6+ """
57
8+ from __future__ import annotations
69
7- def calc (Arr ,mode = "mean" ,round_to = 2 , allow_negative = False ):
8- """
9- do calculation for mean/median/std of list.
10- mode can be: mean,median,std. round_to means rounding digits.
11- allow_negative False means raise if negative number in Arr
10+ import math
11+ import os
12+ import sys
13+ from collections .abc import Iterable
14+
15+
16+ def calc (
17+ arr : Iterable [float ] | None ,
18+ mode : str = "mean" ,
19+ round_to : int = 2 ,
20+ allow_negative : bool = False ,
21+ ) -> float | None :
22+ """Calculate mean/median/std for a sequence.
23+
24+ Args:
25+ arr: Input numbers. If None or empty, return None.
26+ mode: One of "mean", "median", "std".
27+ round_to: Number of decimal digits to round to.
28+ allow_negative: If False, raise Exception when encountering negative value.
29+
30+ Returns:
31+ Rounded statistic value, or None if arr is None/empty.
32+
33+ Raises:
34+ Exception: When allow_negative is False and a negative value appears.
35+ ValueError: When mode is unknown.
1236 """
13- if Arr == None or len (Arr )== 0 : return None
14- for x in Arr :
15- if (not allow_negative ) and x < 0 :
16- raise Exception ("negative not allowed: " + str (x ))
17-
18- if mode == "mean" :
19- s = 0
20- for x in Arr : s += x
21- v = s / len (Arr )
22- elif mode == "median" :
23- B = sorted (Arr )
24- n = len (B )
25- mid = n // 2
26- if n % 2 == 1 : v = B [mid ]
27- else : v = (B [mid - 1 ]+ B [mid ])/ 2
28- elif mode == "std" :
29- m = calc (Arr ,"mean" ,round_to ,allow_negative )
30- s = 0
31- for x in Arr :
32- s += (x - m )** 2
33- v = math .sqrt (s / len (Arr ))
37+ if arr is None :
38+ return None
39+
40+ values = list (arr )
41+ if not values :
42+ return None
43+
44+ if not allow_negative :
45+ for x in values :
46+ if x < 0 :
47+ # Keep behavior compatible with the original tests.
48+ raise Exception (f"negative not allowed: { x } " )
49+
50+ if mode == "mean" :
51+ value = sum (values ) / len (values )
52+ elif mode == "median" :
53+ sorted_values = sorted (values )
54+ n = len (sorted_values )
55+ mid = n // 2
56+ if n % 2 == 1 :
57+ value = sorted_values [mid ]
58+ else :
59+ value = (sorted_values [mid - 1 ] + sorted_values [mid ]) / 2
60+ elif mode == "std" :
61+ mean_value = calc (values , "mean" , round_to , allow_negative )
62+ assert mean_value is not None
63+ value = math .sqrt (sum ((x - mean_value ) ** 2 for x in values ) / len (values ))
3464 else :
35- raise ValueError ("Unknown mode:" + mode )
65+ raise ValueError (f "Unknown mode:{ mode } " )
3666
37- return round (v , round_to )
67+ return round (value , round_to )
3868
3969
40- def parse_numbers (text ):
41- # expects "1,2,3" or "1 2 3" but does it badly
42- if text is None : return []
43- t = text .replace ("," ," " ).replace ("\t " ," " )
44- parts = t .split (" " )
45- out = []
46- for p in parts :
47- if p .strip ()== "" :
48- continue
49- out .append (float (p ))
50- return out
70+ def parse_numbers (text : str | None ) -> list [float ]:
71+ """Parse numbers from a string.
72+
73+ Accept input like:
74+ - "1,2,3"
75+ - "1 2 3"
76+ - "1\\ t2\\ t3"
77+ """
78+ if text is None :
79+ return []
80+
81+ normalized = text .replace ("," , " " ).replace ("\t " , " " )
82+ return [float (p ) for p in normalized .split ()]
5183
5284
53- def read_file (path ):
54- # reads first line
55- f = open (path ,"r" )
56- line = f .readline ()
57- f .close ()
58- return line
85+ def read_file (path : str ) -> str :
86+ """Read the first line from the file."""
87+ with open (path , encoding = "utf-8" ) as f :
88+ return f .readline ()
5989
6090
61- def main (argv = None ):
62- if argv is None : argv = sys .argv
63- if len (argv )< 3 :
91+ def main (argv : list [str ] | None = None ) -> int :
92+ """Entry point for CLI.
93+
94+ Usage:
95+ python -m src.bad_style <mode> <numbers_or_file_path> [--file]
96+ """
97+ if argv is None :
98+ argv = sys .argv
99+
100+ if len (argv ) < 3 :
64101 print ("Usage: python -m src.bad_style <mode> <numbers_or_file_path> [--file]" )
65102 return 2
66103
67- mode = argv [1 ]
68- value = argv [2 ]
69- is_file = False
70- if len (argv )> 3 and argv [3 ]== "--file" :
71- is_file = True
104+ mode = argv [1 ]
105+ value = argv [2 ]
106+ is_file = len (argv ) > 3 and argv [3 ] == "--file"
72107
73108 if is_file :
74109 if not os .path .exists (value ):
75- print ("file not found:" + value )
110+ print (f "file not found:{ value } " )
76111 return 2
77- value = read_file (value )
112+ value = read_file (value )
78113
79- nums = parse_numbers (value )
80- r = calc (nums ,mode = mode )
81- print (r )
114+ nums = parse_numbers (value )
115+ result = calc (nums , mode = mode )
116+ print (result )
82117 return 0
83118
84119
85- if __name__ == "__main__" :
86- raise SystemExit (main ())
120+ if __name__ == "__main__" :
121+ raise SystemExit (main ())
0 commit comments