String-To-Integer问题[Medium]
问题描述
将一个字符串转换为整数,注意溢出,前空格等case,官网具体描述点这里 .
实现
实现思路:顺寻遍历源字符串,并作如下处理
跳过串头空格
将空格后可能包含正负号的整数字符串转换为整数
忽略掉整数后面的非数字字符串
返回整数,注意加上正负号
实现代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def myAtoi(self, s: str) -> int:
if not s:
return 0
# 遍历索引
startIndex = 0
# 跳过空格
while startIndex < len(s):
if s[startIndex] == ' ':
startIndex += 1
else:
break
if startIndex == len(s):
return 0
sign = {'-': -1, '+':1}
numbers = { str(x) : x for x in range(10) }
# 获取正负号
signNumber = 1
if s[startIndex] in sign:
signNumber = sign[s[startIndex]]
startIndex += 1
if startIndex == len(s) or s[startIndex] not in numbers:
return 0
result = 0
# 转换
while startIndex < len(s):
if s[startIndex] not in numbers:
break
result = result * 10 + numbers[s[startIndex]]
# 越界判断
if result * signNumber > (1 << 31 ) - 1:
return (1 << 31) - 1
elif result * signNumber < -(1 << 31):
return -( 1 << 31)
startIndex += 1
return result * signNumber
遍历过程中,各种边界要特别注意,数组越界,正负号位置,等等
重构
上面的实现代码,看上去真的很难受,问题主要有以下几点:
myAtoi
函数过长缩进
有3
层虽然比标准的
4
层要少,但要尽最大努力减少startIndex
变量生命周期太长变量的生命周期长,其声明和结束横跨的代码逻辑较多,也就意味着变量的变化较多。可读性,维护性都比较差。
局部变量
略多注释
较多代码注释较多时,往往意味着程序逻辑复杂,业务耦合严重。严格的软件工程理论要求
Single Responsibility
, 当一个函数只做一件事情
,并且函数名
能清晰的表达出要做的这件事情
时,完全可以省略注释
,甚至是函数注释
。
重构方法其实很简单
对于
缩进
的通用处理方法是尝试对内层缩进Extract Method
对于
局部变量
多的问题,可以采用Replace Temp With Query
函数过长时一般要将程序分块,可以根据实际代码逻辑按照
前后顺序
或者业务逻辑
分块,然后Extract Method
。然而做这一步之前一般要先处理局部变量
,内部的逻辑耦合
等,所以一般来说Extract Method
放到最后做比较合适仔细分析转换逻辑可以发现,业务逻辑大致可以分为
- 字符串预处理,包括前置处理和后置处理
- 字符转换为整数
- 溢出处理
- 生成结果返回
并且可以抽取出
正负号判断
,正负号获取
,数字判断
等公共方法复用
重构后的代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
def afterRefactor(self, s: str) -> int:
integerStr = self.preprocess(s)
if not integerStr:
return 0
if len(integerStr) == 1 and self.isSign(integerStr[0]):
return 0
result = 0
startIndex = 1 if self.isSign(integerStr[0]) else 0
while startIndex < len(integerStr):
result = result * 10 + NUMBER_DICT[integerStr[startIndex]]
overflow, bound = self.boundOverflow(self.getSignNumber(integerStr[0]), result)
if overflow:
return bound
startIndex += 1
return result * self.getSignNumber(integerStr[0])
def preprocess(self, s):
return self.processTail(self.processHeader(s))
def processHeader(self, s):
if not s:
return ''
for i in range(len(s)):
if s[i] != ' ':
return s[i:]
return ''
def processTail(self, s):
if not s:
return ''
for i in range(len(s)):
if i == 0:
if not self.isSign(s[i]) and not self.isNumber(s[i]):
return ''
elif not self.isNumber(s[i]):
return s[:i]
return s
def isNumber(self, c):
return c in NUMBER_DICT
def isSign(self, c):
return c == '-' or c == '+'
def getSignNumber(self, c):
return -1 if c == '-' else 1
def boundOverflow(self, signNumber, absoluteValue):
if self.upperBoundOverflow(signNumber * absoluteValue):
return (True, (1 << 31 ) - 1)
if self.lowerBoundOverflow(signNumber * absoluteValue):
return (True, -(1 << 31))
return (False, None)
def upperBoundOverflow(self, integer):
return integer > (1 << 31 ) - 1
def lowerBoundOverflow(self, integer):
return integer < -(1 << 31)
可以明显感觉出重构后的代码
直观的舒服
每个函数都是只负责一件事情
函数长,缩进,局部变量多以及变量生命周期长的问题都不存在
代码完全不需要注释,
afterRefactor
仅作说明代码复用性提升
业务逻辑更加清晰,维护性更强
总结
很简单的一道题,用来练练重构也不错