go 移除字符串中的空格
本文目的 通过记录 一个小的面试题,来熟悉语言的特性
题目: 给定一个字符串,移除字符串的空格
主要用两种语言(go 和 c)实现 同时对比两种的语言特性的差异
解析思路
1.我面试时候的思路
- 先遍历一下字符串 看其 有几个空格 ,将其 空格的所以记录下来 ,再 将其连接起来
- 这是一个非常愚蠢的思路 问我为什么想这个 、、、因为我紧张的思维被限制了 果然,面试的时候要做到在战略上要藐视敌人,在战术上要重视敌人
2. 面试时候的第二思路
- 通过判断字符串元素,如果是空格 则用后面的覆盖空格的所在位置
- 这样算法 是有点 0(n^2)
3.后面冷静下来后的算法
- 申请一个新的切片 判断字符串元素,如果是不是空格,则将这字符串元素拷贝这新的切片或者数组中
- 这样是 通过空间换时间的方法`
实现
思路1
func removeSpace2(s string) string {
var indexs []int
for i, elem := range s {
// fmt.Println(reflect.TypeOf(elem).Kind())
// fmt.Printf("i is %d elem is %c\n", i, elem)
if elem == ' ' {
indexs = append(indexs, i)
}
}
retS := make([]rune, 0)
old := 0
sRune := []rune(s)
for _, elem := range indexs {
// fmt.Println("elem is ", elem)
retS = append(retS, sRune[old:elem]...)
// fmt.Printf("len ret is %d and the retS%v\n", len(retS), retS)
old = elem + 1
}
return string(retS[:])
}
可能会遇到的问题
- 首先是 字符串类型是啥 怎么利用for 循环 c 里面的字符串 本身 就是个char * 指针 每个char 都是读取 ascii 0-128,同时 用 0x00 来表示字符串的结束, 而go string 是一个类型,16字节 ,其实就是包含了 一个void 指针以及 对应的长度 相比较切片而言 少了一个cap 而对于slice 能做的操作除了 修改元素,其他都能做,也就是string 类型是一个只读的切片
- 首先 是 string 类型的 元素 类型 rune rune 是int32 的别名
- 其次字符串使用utf-8编码,而在for range 循环 中自动解码 而解码后就是 32位Unicode编码 ,所以string 每个元素的就是int32 即 rune
- 当打印遇到错误的utf-8编码的时候 会打印?
- 如何将 rune 和s 相互转化 直接 一个 []rune(s) string(s) 是不是很惊奇 其实底层就是utf-8解码 和编码 也就是 string 类型 就是 utf-8编码
- 而如果将 string 类型和 []byte类型进行转换呢 c 其实没简单 byte 对应的 unsigned char 而 go 和 []byte 也是如此吗 ,[]byte 里面存的也是utf-8编码后的unicode 码嘛 答案是 是的,确实存的 就是utf-8编码后的unicode 码 ,而且转换方式也简单[]byte(string) string([]byte) 而底层实现 就是复制 底层数组 什么时候一定要复制能 []byte(string) 这时候一定要复制 ,因为确保string 类型 只读,string([]byte) 如果byte 没有修改,编译器优化可以避免复制
- 顺便提下 字符串和 数字的转换 是通过strcov包来的 ,其实里面就是ascii 和 int(uint) 类型之间的转换
思路 2
func removeSpace4(s string) string {
sRune := []rune(s)
spaceCount := 1
count := 0
for i := 0; i < len(sRune); {
// if i >= len(sRune)-1 {
// break
// }
// fmt.Println("RUNE", string(sRune))
fmt.Println("i = ", i)
if sRune[i] == ' ' && sRune[i+1] != ' ' {
fmt.Println("spaceCount is", spaceCount)
for j := i + 1; j < len(sRune); j++ {
sRune[j-spaceCount] = sRune[j]
// fmt.Println("s[j]", string(sRune[j-spaceCount]))
}
sRune = sRune[:len(sRune)-spaceCount]
fmt.Println("RUNE", string(sRune))
spaceCount = 1
count++
i = i - spaceCount + 1
} else if sRune[i] == ' ' {
spaceCount++
count++
continue
}
i++
}
return string(sRune[:])
}
遇到的问题
- 首先 的确 确认 range的 时候 sRune 有没有变化 ,这点非常重要 如果有发生变化的话 那么上面的写法就有问题了 因为 你是思路 是通过 如果是空格 就往 空格里面填 。这样会造成一个问题 连续 空格的时候 2[ ],3[ ],4[d]的时候 都是空格,往前移后 2[],3[d],4[d] ,而接下 解析的 下个3 此时的3[d] 为 非空格 ,此时会不会移动
- for range 中的 sRune 是一个拷贝值 但是的数组没有进行拷贝
- 而for ; < len(sRune); 这个不是一个拷贝值
思路3
func removeSpace(s string) string {
var retS []rune
for i, elem := range s {
// fmt.Println(reflect.TypeOf(elem).Kind())
fmt.Printf("i is %d elem is %c\n", i, elem)
if elem != ' ' {
retS = append(retS, elem)
}
}
return string(retS[:])
}
思路4
时间和空间都能省下来 通过双指针 只要保证后面的指针一直快于前面的指针即可 什么时候覆盖呢 当然不是指针指向的值不是空格的时候进行覆盖 非常重要的思想 放一个指针难以控制的时候 就用两个指正 能够避免思路二 遇到的问题
func removeSpace5(s string) string {
sRune := []rune(s)
spaceIndex := 0
for i := 0; i < len(sRune); i++ {
if sRune[i] == ' ' {
spaceIndex = i
break
}
}
charIndex := spaceIndex + 1
for sRune[charIndex] == ' ' {
charIndex++
}
for charIndex < len(sRune) {
// if i >= len(sRune)-1 {
// break
// }
// fmt.Println("RUNE", string(sRune))
if sRune[charIndex] != ' ' {
sRune[spaceIndex] = sRune[charIndex]
spaceIndex++
}
charIndex++
}
return string(sRune[:spaceIndex])
}
c 语言实现 c 没有切片 字符串的结束 主要用0x00 ('/0')进行标识 也就是说c 的数组大小其实编译器是不知道 ,他只负责开辟那么多的空间,至于数组有没有越界完全不管 ,a[n] 其实 a 和 n 编译器都 没有存储 a 变成了一个地址,而 n 是用于给指针 拓展栈空间