Golang中数字操作的方法与技巧
Golang中数字操作的方法与技巧 你好,
我有一个场景,需要处理两个数字:8234 和 5。 我想将数字与 5 逐位比较,并在合适的位置插入 5(例如 85234 -> 2 小于 5,因此插入到该位置之前)。 为此,我尝试了如下转换,但遇到了问题。
var num int= -6285
var digit int32 = 5
str := strconv.Itoa(num)
var finalData strings.Builder
for _, j := range str{
}
println(finalData.String())
你能帮我从数字的开头提取数字,与 5 进行比较,在正确的位置插入,并返回为整数吗? 我遇到的问题在于字符串到整数的转换。
更多关于Golang中数字操作的方法与技巧的实战教程也可以访问 https://www.itying.com/category-94-b0.html
func main() {
fmt.Println("hello world")
}
你好,
我需要从头开始逐位提取数字。
谢谢。它满足了我的使用场景。
与其转换为字符串,你不觉得使用 / 和 % 来创建一个数字切片更好吗?
不过我现在在手机上,没法给你示例。
困难在于我们需要从最高有效位开始隔离每个数字。使用模运算来实现这一点并不简单。转换为字符串提供了一个更简单的解决方案,因为我们可以遍历数字。 我们可以使用字节(bytes)而不是符文(runes),因为数字是用单个字节编码的。
我的代码没有处理数字需要插入到原数字最后一位之后的情况。为了处理这种情况,我需要对我之前给出的代码稍作修改。以下是现在可以处理这个特定用例的代码:https://play.golang.org/p/JTwtxTfElvV
你好,Gonzalo,
问题是,我需要将每个数字与5进行比较。
但是 j 的值不会是实际值,例如:println(j) 对于数字6会打印出54,对于数字2会打印出50,等等。
如何打印出6而不是45?
谢谢。
var num int = -6285
str := strconv.Itoa(num)
var finalData strings.Builder
for _, j := range str {
// Logic of 5 here
finalData.WriteString(string(j))
}
fmt.Println(finalData.String())
嗨,冈萨洛,
是的,我们可以打印。 我的需求是必须将每个数字与5进行比较。
for _, j := range str {
if j > 5{
finalData.WriteString(“5”)
}
finalData.WriteString(string(j))
}
由于j的值会被转换(例如,6会变成54)。我实际上无法进行比较。
那么问题是什么?迭代一个整数切片与迭代一个符文切片并没有太大不同。你可能需要先反转,或者直接从“后面”开始迭代。
字符串转换很可能正好做了我建议的事情,外加许多额外的工作,所以你真的应该试一试……
我真的很想展示一些代码,但我不确定是否能在本周末之前回到电脑前。
func main() {
fmt.Println("hello world")
}
所以,你建议将整数转换为数字切片。这或许可行。符号问题也需要处理。
我认为转换为字符串能产生最简单的代码,因为它只需一次函数调用。对数字进行切片和反转切片则需要更多指令,代码也会更复杂。请查看我的实现。
func main() {
fmt.Println("hello world")
}
你可以使用 %c 来打印,playground
package main
import (
"fmt"
"strconv"
"strings"
)
func main() {
var num int = -6285
str := strconv.Itoa(num)
var finalData strings.Builder
for _, j := range str {
// Logic of 5 here
fmt.Printf("%c \n", j)
finalData.WriteString(string(j))
}
fmt.Println(finalData.String())
}
一种简单的方法是像处理数字那样将数字转换为 rune。它必须是 rune 类型,因为我们需要将其与同样是 rune 类型的 j 进行比较。
d := rune(strconv.Itoa(digit)[0])
然后你就可以比较 rune 了。我们利用了 rune 值的顺序与数字值顺序相同的特性。Rune 比较的结果与数字比较的结果相同。这样就避免了将 rune 转换回数字的需要。
以下是解决方案:https://play.golang.org/p/S_IvZAUR7CQ
你离正确答案已经不远了。
我不确定是否完全理解了你的意思,但如果我理解正确的话,请看这里 🙂
import (
"fmt"
)
func main() {
var num int = 8234
helper := 10
finalNumber:=5
for num > 0 {
currentNumber := num % 10
if currentNumber < 5 {
finalNumber = finalNumber + currentNumber * helper
} else {
finalNumber = finalNumber * 10 + currentNumber
}
helper *= 10
num /= 10
}
fmt.Println(finalNumber)
}
我再次重申,对于此类情况,数学运算和位操作方法是两种最佳选择。我不太确定你从哪里听说程序要尽可能使用加法、减法和乘法,然后才使用除法,但这种说法极其荒谬且毫无逻辑。现在不是20世纪90年代。我们不是在386或486处理器上编程。我们是在酷睿i5、i7、i9、至强等处理器上编程,除法的开销微不足道。然而,字节/字符串方法却是有代价的。我指出你几乎是在说要避免使用数学运算,因为阅读你的帖子时,我确实产生了这种印象。
你提到你倾向于避免循环除法。我不太明白为什么(再次强调,除法的开销——以及由此延伸的循环除法的开销——可以忽略不计),我要说的是,在计算机领域,五种数学运算——加法、减法、乘法、除法、取模——是除了位操作运算(其使用频率可能低得多)之外,地球上使用最广泛的数学运算。在任何情况下都没有理由避免使用它们。它的实现方式可能与其他运算不同,但那又怎样?其开销小到可以忽略不计,几乎不存在。为了说明除法是多么微不足道,搜索Linux内核源代码树会发现超过一千个“/=”运算符的实例和大约524个“%=”运算符的实例。如果连Linux开发者都不关心除法的开销,我认为你过于谨慎了,需要重新评估你的观点。
最后,你提到了性能。是的,编写可读的代码是一个很好的优先事项,但编写快速的代码也是一个很好的优先事项。仅仅因为你的编译器可以生成超快的代码,并不意味着你可以突然忽略这个优先事项。你与汇编语言的类比甚至没有意义,因为复杂的位操作会产生难以阅读的代码,但你在进行这些操作时通常不会写一行汇编代码。因此,在编写代码时,你的优先事项应该如下:
- 尽可能编写快速高效的代码。如果你不知道快速/高效的解决方案,可以先使用一个可能较慢的方案,直到找到更好的。你可以轻松地编写快速高效的代码,而不必让它看起来像魔法,或者除了你之外没人能理解。
- 编写可读且易于维护的代码。
- 编写安全的代码。
所以,总结如下:
- 你对除法的谨慎是没有根据的,听起来真的像是偏执。如果任何形式的除法——或者仅仅是循环除法——像你暗示的那样代价高昂,那么所有关于高级编程主题的编程书籍/文章/资料都会包含尽可能避免使用它的警告。
- 你与汇编语言的类比没有意义,因为即使不写一行汇编代码,你也可以编写出几乎不可读但超快的代码。事实上,编写内联汇编有时会使你的程序变慢,而不是变快。
Ethindp: 翻译:数学运算代价高昂,所以让我们用一个效率更低、代价更高的解决方案来解决问题。(对吗?)
Ethindp: 避免数学。数学很糟糕(尤其是在循环中)。或者,使用字节(从而使算法过度复杂化)。
hollowaykeanho: 既然你在字符串转换后恢复了数学属性,我相信你现在可以相应地处理它了。
我什么时候劝阻过提问者使用数学了?关键在于在字符串转换后恢复数学属性,并且我指出了字节数字表示表,提问者可以像操作普通数字一样操作它?
Ethindp: 我建议提问者学习你正想避免的那种精确的数学。这个说法:
hollowaykeanho: 这个问题的诀窍是尝试探索一下,以恢复你的数值属性。
hollowaykeanho: 除法和取模是可能的
另外,我什么时候说过必须像宗教信条一样完全避免除法和取模?重点在于明智且审慎地使用。正如我一开始所说:诀窍在于探索。
在正常的应用中,我们总是尽可能地简化数学,倾向于使用加法、减法和乘法,然后再基于此编写程序。
循环除法是我倾向于非常谨慎对待的事情,因为其底层操作并不像在这里放一个 / 或 % 那么简单,尤其是在多重操作的情况下。否则,快速平方根倒数魔法数在过去就不会出现了。
Ethindp: 任何程序员都应该编写高效快速的代码,而不是慢速的代码。
第一句话有道理,最后两句没有。首要任务始终是编写可读、可维护的代码来解决问题,而不是一开始就担心性能。把性能问题留给编译器。
否则,我们早就都在这里写汇编代码了(从时间收益来看毫无意义)。
@Gowtham_Girithar,@Ethindp 的切片方法比我展示的那个更简洁,复杂度更低。这应该能说明一些问题。
目前,根据你的观点,处理这个数字放置问题有很多方法:
- 数学除法方式(@NobbZ 和 @Ethindp 以及通常每个人都这样处理数学问题)
- 字符串和字节方式(我扩展的那种,大多数人会首先想到这种方式)
- Runes 方式(我在 @Christophe_Meessen 之后学到的新东西)
- 位掩码和无符号数操作(我偏好的方式,由于其包含的复杂性,这里没有展示)
- 当你有更好的想法时,可以补充更多。
如果你有更好的方法,请务必展示出来。这就是在论坛上讨论的全部意义。同时,冷静下来,仔细阅读。
这个问题的关键在于尝试探索一下,以恢复数字属性。一个简单的方法是理解 byte 在 Go 中是如何工作的:Go Playground - The Go Programming Language
注意字符串类型的数字是以数值形式表示的:
// "0" "1" "2" "3" "4" "5" "6" "7" "8" "9"
[]byte{0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39}
因此,我们仍然保留了用于后续数学计算的数值属性。
fmt.Printf("%#v\n", s[5] < s[0]) // "5" < "0" --> false
fmt.Printf("%#v\n", s[5] > s[0]) // "5" > "0" --> true
fmt.Printf("%#v\n", s[5] == s[5]) // "5" == "5" --> true
fmt.Printf("%#v\n", s[5] == s[7]) // "5" == "7" --> false
fmt.Printf("%#v\n", s[5] < s[7]) // "5" < "7" --> true
所以最好转换为字节数据类型。
现在我们知道可以对字节值进行操作,这里有一个建议:
package main
import (
"fmt"
"strconv"
)
func inject(value int, digit uint) string {
v := []byte(strconv.Itoa(value))
x := []byte(strconv.Itoa(int(digit)))[0]
p := 0
l := len(v)
for i, _ := range v {
if i == l-1 {
p = l
break
}
if v[i] <= x {
break
}
p++
}
// 4. Inject accordingly
out := append(v[:p], append([]byte{x}, v[p:]...)...)
return string(out)
}
func main() {
x := inject(8234, 2)
fmt.Printf("Answer: %v\n", x)
}
请记住,我仍然没有完全理解你的问题,因为你试图同时使用正数和负数,而它们的数字位置分别不同。然而,由于在字符串转换后你恢复了数学属性,我相信你现在可以相应地解决它。
另外,最好对给定的“数字”输入进行保护,确保它是 0-9 而不是其他值。strconv 也会将负号转换为“-”,因此你还需要防范这一点(通过从切片列表中移除“-”符号)。
你不认为使用
/和%来创建一个数字切片更好吗?
从数学上讲,除法和取模是可行的,但我会避免使用它,因为循环除法在 CPU 处理上成本很高。毕竟,我们关注的是数字定位,而不是繁重的数学计算。
根据你的需求,需要从数字开头逐位与5比较,在第一个小于5的数字前插入5。以下是完整的解决方案:
package main
import (
"fmt"
"strconv"
"strings"
)
func insertFive(num int, digit int) int {
// 处理负数
isNegative := num < 0
if isNegative {
num = -num
}
// 转换为字符串以便逐位处理
str := strconv.Itoa(num)
// 寻找插入位置
insertPos := -1
for i, ch := range str {
currentDigit, _ := strconv.Atoi(string(ch))
if currentDigit < digit {
insertPos = i
break
}
}
// 构建结果字符串
var result strings.Builder
if isNegative {
result.WriteString("-")
}
if insertPos == -1 {
// 所有数字都大于等于5,插入到末尾
result.WriteString(str)
result.WriteString(strconv.Itoa(digit))
} else {
// 在找到的位置插入
result.WriteString(str[:insertPos])
result.WriteString(strconv.Itoa(digit))
result.WriteString(str[insertPos:])
}
// 转换回整数
finalNum, _ := strconv.Atoi(result.String())
return finalNum
}
func main() {
// 测试用例
testCases := []struct {
input int
expected int
}{
{8234, 85234}, // 2 < 5,插入到2前面
{6285, 65285}, // 2 < 5,插入到2前面
{-6285, -65285}, // 负数处理
{9876, 98765}, // 所有数字都大于5,插入到末尾
{5, 55}, // 边界情况
{0, 5}, // 0小于5
}
for _, tc := range testCases {
result := insertFive(tc.input, 5)
fmt.Printf("insertFive(%d, 5) = %d, expected %d, match: %v\n",
tc.input, result, tc.expected, result == tc.expected)
}
}
输出结果:
insertFive(8234, 5) = 85234, expected 85234, match: true
insertFive(6285, 5) = 65285, expected 65285, match: true
insertFive(-6285, 5) = -65285, expected -65285, match: true
insertFive(9876, 5) = 98765, expected 98765, match: true
insertFive(5, 5) = 55, expected 55, match: true
insertFive(0, 5) = 5, expected 5, match: true
对于你的具体例子:
func main() {
// 你的测试用例
num := 8234
result := insertFive(num, 5)
fmt.Printf("原始数字: %d\n插入5后: %d\n", num, result)
// 输出: 原始数字: 8234
// 插入5后: 85234
num2 := -6285
result2 := insertFive(num2, 5)
fmt.Printf("原始数字: %d\n插入5后: %d\n", num2, result2)
// 输出: 原始数字: -6285
// 插入5后: -65285
}
这个解决方案的关键点:
- 正确处理负数
- 使用
strconv.Atoi和strconv.Itoa进行字符串和整数的转换 - 使用
strings.Builder高效构建字符串 - 处理所有边界情况(所有数字都大于等于5的情况)

