通过示例学习-Go-语言-2023-二十七-
通过示例学习 Go 语言 2023(二十七)
用 Go 语言编写的所得税计算程序
目录
-
概述
-
程序
概述
给定一个二维数组,表示所得税的税率区间。输入数组为税率区间,其中
brackets[i] = [upperi, percenti]
这意味着第 i 个区间的上限为upperi,税率为percent。 税率区间数组按上限排序。以下是计算税率的方法
-
upper0 以下的金额税率为 percent0
-
upper1-upper0 的税率为 percent1
-
.. 以此类推
你还需要输入收入。你需要计算该收入的所得税。已知最后一个区间的上限大于收入。
示例 1
Input: brackets = [[4,10],[9,20],[12,30]], income = 10
Output: 1.7
示例 2
Input: brackets = [[3,10]], income = 1
Output: 0.3
程序
下面是该程序的内容
package main
import "fmt"
func calculateTax(brackets [][]int, income int) float64 {
if income == 0 {
return 0
}
var totalTax float64
numBrackets := len(brackets)
upper := 0
for i := 0; i < numBrackets; i++ {
if i == 0 {
upper = brackets[i][0]
} else {
upper = brackets[i][0] - brackets[i-1][0]
}
taxPer := brackets[i][1]
if income <= upper {
totalTax += float64(income) * float64(taxPer) / 100
break
} else {
totalTax += float64(upper) * float64(taxPer) / 100
income = income - upper
}
}
return totalTax
}
func main() {
output := calculateTax([][]int{{4, 10}, {9, 20}, {12, 30}}, 10)
fmt.Println(output)
output = calculateTax([][]int{{3, 10}}, 10)
fmt.Println(output)
}
输出:
1.7
0.3
注意:查看我们的 Golang 高级教程。本系列的教程内容详尽,我们尝试用示例覆盖所有概念。该教程适合希望获得 Golang 专业知识和扎实理解的人 - Golang 高级教程
如果你有兴趣了解所有设计模式如何在 Golang 中实现。如果是,那么这篇文章适合你 - 所有设计模式 Golang
另外,您可以在这里查看我们的系统设计教程系列 - 系统设计教程系列*
计算字符串中最后一个单词长度的 Go 程序 (Golang)
目录
-
概述
-
程序
概述
目标是找到给定字符串中最后一个单词的长度
示例
Input: "computer science"
Output: 7
The last word is science and its length is 7
Input: "computer science is a subject "
Output: 7
The last word is subject and ts length is 7
Input: " "
Output: 0
There is no last word hence answer is zero
程序
下面是相应的程序。
package main
import "fmt"
func lengthOfLastWord(s string) int {
lenS := len(s)
lenLastWord := 0
for i := lenS - 1; i >= 0; {
for i >= 0 && string(s[i]) == " " {
i--
}
if i < 0 {
return 0
}
for i >= 0 && string(s[i]) != " " {
//fmt.Println(i)
//fmt.Println(string(s[i]))
i--
lenLastWord++
}
return lenLastWord
}
return 0
}
func main() {
length := lengthOfLastWord("computer science")
fmt.Println(length)
length = lengthOfLastWord("computer science is a subject")
fmt.Println(length)
length = lengthOfLastWord(" ")
fmt.Println(length)
}
输出
7
7
0
注意: 查看我们的 Golang 高级教程。该系列教程详细而全面,我们尝试用示例覆盖所有概念。本教程适合那些希望获得专业知识和扎实理解 Golang 的人 – Golang 高级教程
如果你有兴趣了解如何在 Golang 中实现所有设计模式。如果是的话,那么这篇文章适合你 – 所有设计模式 Golang
通过删除在 Go (Golang) 中查找字典中最长单词的程序
目录
-
概述
-
程序
概述
给定一个字符串和一个单词字典。目标是找到字典中作为子序列出现在给定字符串中的最长单词。如果可能的结果数量超过 1,则返回字典中按字典顺序最小的最长单词。
示例 1
s = "mbacnago", dictionary = ["ale","mango","monkey","plea"]
Output: "mango"
示例 2
s = "mbacnago", dictionary = ["ba","ag"]
Output: "ag"
程序
下面是相同的程序
package main
import (
"fmt"
"sort"
)
func findLongestWord(s string, dictionary []string) string {
sort.Slice(dictionary, func(i, j int) bool {
lenI := len(dictionary[i])
lenJ := len(dictionary[j])
if lenI == lenJ {
return dictionary[i] < dictionary[j]
}
return lenI > lenJ
})
lenS := len(s)
for i := 0; i < len(dictionary); i++ {
if isSubstring(s, dictionary[i], lenS) {
return dictionary[i]
}
}
return ""
}
func isSubstring(s string, sub string, lenS int) bool {
lenSub := len(sub)
if lenSub == 0 {
return true
}
if lenSub > lenS {
return false
}
for i, j := 0, 0; i < lenS && j < lenSub; {
if i+lenSub-j-1 >= lenS {
return false
}
if s[i] == sub[j] {
j++
}
if j == lenSub {
return true
}
i++
}
return false
}
func main() {
output := findLongestWord("mbacnago", []string{"ale", "mango", "monkey", "plea"})
fmt.Println(output)
output = findLongestWord("mbacnago", []string{"ba", "ag"})
fmt.Println(output)
}
输出
mango
ag
注意: 请查看我们的 Golang 高级教程。此系列教程内容详尽,我们尽力涵盖所有概念及示例。本教程适合希望获得专业知识和对 Golang 有扎实理解的学习者 – Golang 高级教程
如果你对理解所有设计模式在 Golang 中的实现感兴趣,那么这篇文章适合你 – 所有设计模式 Golang
同时,可以在这里查看我们的系统设计教程系列 – 系统设计教程系列
Go (Golang) 中的帕斯卡三角形程序
目录
-
概述
-
程序
概述
目标是打印 n 行帕斯卡三角形。数字 n 作为输入提供给程序
示例 1
Input: numRows = 4
Output: [[1],[1,1],[1,2,1],[1,3,3,1]]
示例 2
Input: numRows = 5
Output: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]
请参考此链接以了解更多关于帕斯卡三角形的信息 - en.wikipedia.org/wiki/Pascal%27s_triangle
这里的思路是使用动态规划。
程序
以下是相同的程序
package main
import "fmt"
func generate(numRows int) [][]int {
firstRow := []int{1}
if numRows == 1 {
return [][]int{firstRow}
}
secondRow := []int{1, 1}
if numRows == 2 {
return [][]int{firstRow, secondRow}
}
output := [][]int{firstRow, secondRow}
for i := 2; i < numRows; i++ {
temp := make([]int, i+1)
lastRow := output[i-1]
temp[0] = 1
temp[i] = 1
for j := 1; j < i; j++ {
temp[j] = lastRow[j-1] + lastRow[j]
}
output = append(output, temp)
}
return output
}
func main() {
output := generate(4)
fmt.Println(output)
output = generate(5)
fmt.Println(output)
}
输出:
[1] [1 1] [1 2 1] [1 3 3 1]]
[[1] [1 1] [1 2 1] [1 3 3 1] [1 4 6 4 1]]
注意: 查看我们的 Golang 高级教程。本系列教程内容详尽,我们努力涵盖所有概念并提供示例。本教程适合那些希望获得专业知识并深入理解 Golang 的人 - Golang 高级教程
如果你有兴趣了解所有设计模式如何在 Golang 中实现。如果是的话,那么这篇文章适合你 - 所有设计模式 Golang
此外,还可以查看我们的系统设计教程系列 - 系统设计教程系列
用 Go (Golang)编写的给定整数数组的幂集程序。
目录
概述
- 程序
概述
给定一个包含所有唯一元素的整数数组。目标是返回该数组的幂集。
Input: [1, 2]
Output: [[],[1],[2],[1,2]]
Input: [1, 2, 3]
Output: [[] [1] [2] [1 2] [3] [1 3] [2 3] [1 2 3]]
如果给定数组中的元素数量为 n,则幂集中的元素数量将为 pow(2, n)。假设 n 为 3,则幂集中的元素数量为 pow(2, n)=8。
假设我们对数字从 0 到(8-1)进行所有二进制转换,即从 0 到 7。
000
001
010
011
100
101
110
111
上面的每个二进制数字表示一个幂集。
例如
000 - []
001 - [1]
010 - [2]
011 - [1, 2]
100 - [3]
101 - [1, 3]
110 - [2, 3]
111 - [1, 2, 3]
程序
这是相应的程序。
package main
import (
"fmt"
"math"
)
func subsets(nums []int) [][]int {
lengthNums := len(nums)
powerSetLength := int(math.Pow(2, float64(lengthNums)))
output := make([][]int, 0)
for i := 0; i < powerSetLength; i++ {
result := make([]int, 0)
for j := 0; j < lengthNums; j++ {
val := int(i) & int(1<
输出
[[] [1] [2] [1 2]]
[[] [1] [2] [1 2] [3] [1 3] [2 3] [1 2 3]]
注意: 请查看我们的 Golang 高级教程。本系列教程详尽,我们尽力涵盖所有概念和示例。本教程适合那些希望获得专业知识和扎实理解 golang 的人 - Golang 高级教程
如果你有兴趣了解所有设计模式如何在 Golang 中实现。如果是的话,这篇文章适合你 - 所有设计模式 Golang*
Go(Golang)中的相同二叉树程序
目录
-
概述
-
程序
概述
目标是检查给定的两棵二叉树是否相同。以下树是相同的
树 1
树 2
程序
这里是相同的程序。
package main
import (
"fmt"
)
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
func isSameTree(p *TreeNode, q *TreeNode) bool {
if p == nil && q == nil {
return true
}
if p == nil || q == nil {
return false
}
if p.Val != q.Val {
return false
}
return isSameTree(p.Left, q.Left) && isSameTree(p.Right, q.Right)
}
func main() {
root1 := TreeNode{Val: 1}
root1.Left = &TreeNode{Val: 2}
root1.Left.Left = &TreeNode{Val: 4}
root1.Right = &TreeNode{Val: 3}
root1.Right.Left = &TreeNode{Val: 5}
root1.Right.Right = &TreeNode{Val: 6}
root2 := TreeNode{Val: 1}
root2.Left = &TreeNode{Val: 2}
root2.Left.Left = &TreeNode{Val: 4}
root2.Right = &TreeNode{Val: 3}
root2.Right.Left = &TreeNode{Val: 5}
root2.Right.Right = &TreeNode{Val: 6}
output := isSameTree(&root1, &root2)
fmt.Println(output)
root1 = TreeNode{Val: 1}
root1.Left = &TreeNode{Val: 2}
root2 = TreeNode{Val: 1}
root2.Left = &TreeNode{Val: 3}
output = isSameTree(&root1, &root2)
fmt.Println(output)
}
输出
true
false
注意: 查看我们的 Golang 高级教程。本系列教程详尽,我们尽力覆盖所有概念并提供示例。本教程适合希望获得专业知识和扎实理解 Golang 的读者 – Golang 高级教程
如果你有兴趣了解所有设计模式如何在 Golang 中实现。如果是的话,这篇文章适合你 – 所有设计模式 Golang
在 Go(Golang)中计算句子中单词总数的程序
目录
-
概述
-
程序
概述
给定一个句子,找出其中的单词数量。句子中的每个单词只包含英语字母。
示例
Input: "Hello World"
Output: 2
Input: "This is hat"
Output: 3
程序
这是相同的程序。
package main
import "fmt"
func countW(s string) int {
lenS := len(s)
numWords := 0
for i := 0; i < lenS; {
for i < lenS && string(s[i]) == " " {
i++
}
if i < lenS {
numWords++
}
for i < lenS && string(s[i]) != " " {
i++
}
}
return numWords
}
func main() {
output := countW("Hello World")
fmt.Println(output)
output = countW("This is hat")
fmt.Println(output)
}
输出
2
3
注意: 请查看我们的 Golang 高级教程。该系列教程内容丰富,我们尝试用例子覆盖所有概念。本教程适合希望获得专业知识和对 Golang 有深入理解的人 - Golang 高级教程
如果你对如何在 Golang 中实现所有设计模式感兴趣,那么这篇文章就是为你准备的 - 所有设计模式 Golang
Go (Golang) 中的丑陋数字 2 的程序
来源:
golangbyexample.com/program-for-ugly-number-2-in-go-golang/
目录
-
概述
-
程序
概述
丑陋数字是其质因数限于 2、3 和 5 的数字。我们已经见过一个程序,它给定一个数字 n,返回 true 如果它是丑陋数字,否则返回 false。下面是那个程序的链接 golangbyexample.com/ugly-number-golang/
在本教程中,我们将编写一个程序,给定一个数字 n,找到第 n 个丑陋数字。
示例 1
Input: 5
Output: 5
Reason: First five ugly numbers are 1, 2, 3, 4, 5 hence 5th ugly number is 5
示例 2
Input: 20
Output: 36
这个想法是使用动态编程。我们将跟踪 2、3 和 5 的倍数。下一个丑陋数字将始终是这三个中的最小值。
程序
以下是相同的程序
package main
import "fmt"
func nthUglyNumber(n int) int {
dpUgly := make([]int, n)
dpUgly[0] = 1
next_multiple_of_2 := 2
next_multiple_of_3 := 3
next_multiple_of_5 := 5
i2 := 0
i3 := 0
i5 := 0
for i := 1; i < n; i++ {
nextUglyNumber := minOfThree(next_multiple_of_2, next_multiple_of_3, next_multiple_of_5)
dpUgly[i] = nextUglyNumber
if nextUglyNumber == next_multiple_of_2 {
i2++
next_multiple_of_2 = 2 * dpUgly[i2]
}
if nextUglyNumber == next_multiple_of_3 {
i3++
next_multiple_of_3 = 3 * dpUgly[i3]
}
if nextUglyNumber == next_multiple_of_5 {
i5++
next_multiple_of_5 = 5 * dpUgly[i5]
}
}
return dpUgly[n-1]
}
func minOfThree(a, b, c int) int {
if a < b && a < c {
return a
}
if b < c {
return b
}
return c
}
func main() {
output := nthUglyNumber(5)
fmt.Println(output)
output = nthUglyNumber(20)
fmt.Println(output)
}
输出
5
36
注意: 请查看我们的 Golang 高级教程。本系列教程内容详尽,试图覆盖所有概念和示例。本教程适合那些希望获得 Golang 专业知识和扎实理解的人 - Golang 高级教程
如果你有兴趣了解所有设计模式如何在 Golang 中实现。那么这篇文章适合你 - 所有设计模式 Golang
此外,请查看我们的系统设计教程系列 - 系统设计教程系列
在 Go (Golang) 中的丑数程序
目录
-
概述
-
程序
概述
丑数是其质因数仅限于 2、3 和 5 的数字。
给定一个数字 n,如果它是一个丑数则返回 true,否则返回 false。
示例 1
Input: 12
Output: true
示例 2
Input: 7
Output: false
思路是
-
当数字是 2 的因数时,不断将数字除以 2。
-
当数字是 3 的因数时,不断将数字除以 3。
-
当数字是 5 的因数时,不断将数字除以 5。
最后,如果数字等于 1,则返回 true,否则返回 false。
程序
以下是相同的程序
package main
import "fmt"
func isUgly(n int) bool {
if n == 0 {
return false
}
if n == 1 {
return true
}
for {
if n%2 != 0 {
break
}
n = n / 2
}
for {
if n%3 != 0 {
break
}
n = n / 3
}
for {
if n%5 != 0 {
break
}
n = n / 5
}
return n == 1
}
func main() {
output := isUgly(12)
fmt.Println(output)
output = isUgly(7)
fmt.Println(output)
}
输出
true
false
注意: 请查看我们的 Golang 高级教程。本系列教程内容详尽,我们尽量涵盖所有概念并配有示例。此教程适合那些希望获得专业知识和对 Golang 有深入理解的人—— Golang 高级教程
如果你对了解所有设计模式如何在 Golang 中实现感兴趣。如果是的话,这篇文章适合你—— 所有设计模式 Golang
此外,请查看我们的系统设计教程系列—— 系统设计教程系列*
在 Go (Golang) 中添加数字的所有数字的程序
目录
-
概述
-
程序
概述
目标是反复相加一个数字的所有数字,直到结果仅为一个单一数字。
例如
Input: 453
Step 1: 4+5+3 = 12
Step 2: 1+2 =3
Output: 3
另一个示例
Input: 45
Step 1: 4+5 = 9
Output: 9
程序
这是相同程序的代码
package main
import "fmt"
func addDigits(num int) int {
if num < 10 {
return num
}
for num > 9 {
num = sum(num)
}
return num
}
func sum(num int) int {
output := 0
for num > 0 {
output = output + num%10
num = num / 10
}
return output
}
func main() {
output := addDigits(453)
fmt.Println(output)
output = addDigits(45)
fmt.Println(output)
}
输出
3
9
注意: 请查看我们的 Golang 高级教程。本系列教程内容详尽,我们尽力覆盖所有概念及示例。此教程适合那些希望获得专业知识和对 Golang 有深入理解的读者 – Golang 高级教程
如果你有兴趣了解所有设计模式如何在 Golang 中实现。如果是的话,那么这篇文章适合你 – 所有设计模式 Golang
用 Go(Golang)编写的计算一个数字的幂的程序。
目录
-
概述
-
程序
概述
目标是计算给定整数的幂。将有两个输入。
-
数字本身 – 这个数字可以是正数也可以是负数,也可以是浮点数。
-
幂 – 幂可以是正数,也可以是负数。
示例
Input: Num:2, Power:4
Output: 16
Input: Num:2, Power:-4
Output: 0.0625
程序
这是相同程序的代码。
package main
import "fmt"
func pow(x float64, n int) float64 {
if x == 0 {
return 0
}
if n == 0 {
return 1
}
if n == 1 {
return x
}
if n == -1 {
return 1 / x
}
val := pow(x, n/2)
m := x
if n < 0 {
m = 1 / x
}
if n%2 == 1 || n%2 == -1 {
return val * val * m
} else {
return val * val
}
}
func main() {
output := pow(2, 4)
fmt.Println(output)
output = pow(2, -4)
fmt.Println(output)
}
输出
16
0.0625
注意: 请查看我们的 Golang 高级教程。本系列的教程内容详尽,我们尝试用实例覆盖所有概念。本教程适合那些希望获得专业知识和对 Golang 有深刻理解的人 - Golang 高级教程。
如果你对了解所有设计模式在 Golang 中的实现感兴趣,那么这篇文章就是为你准备的 - 所有设计模式 Golang。
用 Go(Golang)乘两个字符串的程序
目录
-
概述
-
程序
概述
编写一个程序来乘两个字符串。
示例
Input: "12"*"12"
Output: 144
Input: "123"*"12"
Output: 1476
程序
这是相应的程序。
package main
import (
"fmt"
"math"
"strconv"
)
func multiply(num1 string, num2 string) string {
if len(num1) > len(num2) {
num2, num1 = num1, num2
}
output := 0
k := 0
carry := 0
for i := len(num1) - 1; i >= 0; i-- {
x := 0
temp := 0
for j := len(num2) - 1; j >= 0; j-- {
digit1, _ := strconv.Atoi(string(num1[i]))
digit2, _ := strconv.Atoi(string(num2[j]))
multiply_output := digit1*digit2 + carry
carry = multiply_output / 10
temp = multiply_output%10*int(math.Pow(10, float64(x))) + temp
x = x + 1
}
temp = carry*int(math.Pow(10, float64(x))) + temp
carry = 0
output = temp*int(math.Pow(10, float64(k))) + output
k = k + 1
}
return strconv.Itoa(output)
}
func main() {
output := multiply("12", "12")
fmt.Println(output)
output = multiply("123", "12")
fmt.Println(output)
}
输出
144
1476
注意: 查看我们的 Golang 高级教程。该系列的教程内容详尽,我们尝试用实例覆盖所有概念。本教程适合那些希望获得专业知识和对 Golang 有深入理解的学习者 – Golang 高级教程
如果你有兴趣了解所有设计模式如何在 Golang 中实现。那么这篇文章就是为你准备的 – 所有设计模式 Golang
协议缓冲与 Go:入门指南
在本教程中,我们将看到协议缓冲如何在 GO 语言中使用。
什么是协议缓冲
协议缓冲是一种以结构化格式存储数据的数据格式。协议缓冲格式中的数据可以被多种语言序列化和反序列化。
听起来很混乱。你可以把它理解为 JSON、XML,但它提供了许多优势。仍然感到困惑吗?那么不用担心,随着教程的进行,我们将理解为什么甚至需要一个新的数据格式。
让我们先看看最简单的协议缓冲文件的例子。
person.proto
syntax = "proto3";
message Person {
string name = 1;
}
关于上述文件,有几点需要注意。
-
这个文件只是我们协议缓冲结构的蓝图。尚未关联任何数据。这与 JSON/XML 不同,后者的文件也表示实际数据。
-
在上述文件中,有一个 person 消息,其字段 name 的类型为 string。 “proto3” 意味着该消息与协议缓冲版本三不兼容。
-
从上面的例子可以注意到一个不同之处,即它具有类型信息。这种类型的信息在不同语言的代码自动生成中将非常有用。让我们看看在 Golang 中的自动生成示例。
GO 代码的自动生成:
- 我们可以使用上述
person.proto
文件生成相应的 Golang 代码。但为此我们需要进行一些安装:
安装:
-
首先安装协议缓冲的 C++ 实现。每个平台的安装方式各不相同。请参见此链接 –
github.com/protocolbuffers/protobuf/blob/master/src/README.md
-
安装 Golang
-
安装 protoc-gen-go – go get -u github.com/golang/protobuf/protoc-gen-go。 这个包将用于 Go 代码的自动生成。
安装完成后,进入包含 person.proto 文件的目录。运行此命令:
protoc -I ./ --go_out=./ ./person.proto
它将在同一目录下生成名为 person.pb.go 的数据访问 Go 文件。
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: person.proto
package person
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type Person struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Person) Reset() { *m = Person{} }
func (m *Person) String() string { return proto.CompactTextString(m) }
func (*Person) ProtoMessage() {}
func (*Person) Descriptor() ([]byte, []int) {
return fileDescriptor_4c9e10cf24b1156d, []int{0}
}
func (m *Person) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Person.Unmarshal(m, b)
}
func (m *Person) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Person.Marshal(b, m, deterministic)
}
func (m *Person) XXX_Merge(src proto.Message) {
xxx_messageInfo_Person.Merge(m, src)
}
func (m *Person) XXX_Size() int {
return xxx_messageInfo_Person.Size(m)
}
func (m *Person) XXX_DiscardUnknown() {
xxx_messageInfo_Person.DiscardUnknown(m)
}
var xxx_messageInfo_Person proto.InternalMessageInfo
func (m *Person) GetName() string {
if m != nil {
return m.Name
}
return ""
}
func init() {
proto.RegisterType((*Person)(nil), "Person")
}
func init() { proto.RegisterFile("person.proto", fileDescriptor_4c9e10cf24b1156d) }
var fileDescriptor_4c9e10cf24b1156d = []byte{
// 67 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x29, 0x48, 0x2d, 0x2a,
0xce, 0xcf, 0xd3, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x57, 0x92, 0xe1, 0x62, 0x0b, 0x00, 0xf3, 0x85,
0x84, 0xb8, 0x58, 0xf2, 0x12, 0x73, 0x53, 0x25, 0x18, 0x15, 0x18, 0x35, 0x38, 0x83, 0xc0, 0xec,
0x24, 0x36, 0xb0, 0x22, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa4, 0xaf, 0x53, 0x72, 0x34,
0x00, 0x00, 0x00,
}
现在最大的问题是,什么是通过 protoc 使用 person.proto
自动生成的 person.pb.go
文件。首先要注意几点。
- Person 结构体如下。请注意
person.proto
文件中的类型信息如何用于了解 Name 字段是字符串。
type Person struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
- Person 结构体还实现了一些方法,使其成为接口类型 proto.Message。
基本上,这个自动生成的文件为 Person 结构体生成数据访问器,并提供允许将 Person 结构体类型与实际字节之间进行序列化/反序列化的方法。现在让我们编写一个 main.go 程序,实际创建Person结构体的具体对象。在这里,我们将看到协议缓冲区所提供的几个优势。以下程序还展示了 Person 结构体的读写操作。
main.go
package main
import (
"fmt"
"io/ioutil"
"log"
proto "github.com/golang/protobuf/proto"
)
func main() {
person := &Person{Name: "XXX"}
fmt.Printf("Person's name is %s\n", person.GetName())
//Now lets write this person object to file
out, err := proto.Marshal(person)
if err != nil {
log.Fatalf("Serialization error: %s", err.Error())
}
if err := ioutil.WriteFile("person.bin", out, 0644); err != nil {
log.Fatalf("Write File Error: %s ", err.Error())
}
fmt.Println("Write Success")
//Read from file
in, err := ioutil.ReadFile("person.bin")
if err != nil {
log.Fatalf("Read File Error: %s ", err.Error())
}
person2 := &Person{}
err2 := proto.Unmarshal(in, person2)
if err2 != nil {
log.Fatalf("DeSerialization error: %s", err.Error())
}
fmt.Println("Read Success")
fmt.Printf("Person2's name is %s\n", person2.GetName())
}
要运行此文件,首先使用“go get github.com/golang/protobuf/proto”安装 protobuf/proto,然后使用命令“go run *.go”运行此文件。输出:
Person's name is XXX
Write Success
Read Success
Person2's name is XXX
请注意,在上述程序中我们
-
我们将一个具体的person结构体写入文件“person.bin”。这是一个二进制文件,不可读。
-
我们也从文件中读取。能够成功读取并打印“Person2 的名字是 XXX”。
“person.bin”文件的惊人之处在于其仅有 5 个字节,而如果创建一个相同数据的 JSON 文件,其大小将超过 15 个字节。此外,从字节到具体对象及其反向操作的序列化和反序列化速度也非常快,相比之下,JSON 文件的序列化和反序列化速度较慢。
现在我们提供了理论。让我们再一次列出使用协议缓冲区的优势。
-
相比于相应的 JSON 和 XML,更加清晰且不含歧义,因为它们还存储了类型信息。
-
存储的数据相对较小,约小 2-3 倍。
-
它快得多。例如,使用协议缓冲区的序列化和反序列化速度更快。
-
自动代码生成——你编写一个协议缓冲文件,系统会自动生成相应的 GO 文件。
-
协议缓冲区用于 GRPC,后者是 REST 协议的下一代替代品——请关注此处,我们将很快添加相关文章。
结论: 协议缓冲区提供的内容远不止我们在文章中讨论的。这为协议缓冲区是什么及其与 JSON/XML 格式相比的优势提供了快速概述。
- buffer * go * grpc * protocol * protocolbuffer
Go (Golang)中的原型模式
注意: 对于理解其他所有设计模式在 GO 中的实现有兴趣的人,请查看此完整参考 – Go (Golang)中的所有设计模式
目录
** 定义:
-
使用时机
-
UML 图
-
映射
-
实际示例:
定义:
这是一种创建型设计模式,允许你创建对象的副本。在此模式中,克隆对象的创建责任委托给实际的克隆对象。
要克隆的对象暴露一个克隆方法,该方法返回对象的克隆副本。
使用时机
-
当克隆对象的创建过程复杂时,我们使用原型模式,即克隆可能涉及深拷贝、层次拷贝等处理。此外,可能还有一些私有成员也无法直接访问。
-
创建的是对象的副本,而不是从头创建一个新实例。这防止了在创建新对象时涉及的昂贵操作,例如数据库操作。
-
当你想要创建新对象的副本,但它仅作为接口可用时。因此,你不能直接创建该对象的副本。
UML 图
映射
下表表示 UML 图中参与者到代码中实际实现参与者的映射。
原型接口 | inode.go |
---|---|
具体原型 1 | file.go |
具体原型 2 | folder.go |
客户端 | main.go |
实际示例:
在 golang 的上下文中,让我们尝试通过 os 文件系统的示例来理解它。os 文件系统有文件和文件夹,文件夹本身也包含文件和文件夹。每个文件和文件夹可以由inode接口表示。inode接口还有clone()函数。
inode.go
package main
type inode interface {
print(string)
clone() inode
}
文件结构体表示为
file.go
package main
import "fmt"
type file struct {
name string
}
func (f *file) print(indentation string) {
fmt.Println(indentation + f.name)
}
func (f *file) clone() inode {
return &file{name: f.name + "_clone"}
}
文件夹结构体表示为
folder.go
package main
import "fmt"
type folder struct {
childrens []inode
name string
}
func (f *folder) print(indentation string) {
fmt.Println(indentation + f.name)
for _, i := range f.childrens {
i.print(indentation + indentation)
}
}
func (f *folder) clone() inode {
cloneFolder := &folder{name: f.name + "_clone"}
var tempChildrens []inode
for _, i := range f.childrens {
copy := i.clone()
tempChildrens = append(tempChildrens, copy)
}
cloneFolder.childrens = tempChildrens
return cloneFolder
}
由于文件和文件夹结构体都实现了打印和克隆函数,因此它们属于inode类型。此外,请注意文件和文件夹中的克隆函数。它们的克隆函数返回各自文件或文件夹的副本。在克隆时,我们为名称字段附加关键字“_clone”。让我们编写主函数来测试一下。
main.go
package main
import "fmt"
func main() {
file1 := &file{name: "File1"}
file2 := &file{name: "File2"}
file3 := &file{name: "File3"}
folder1 := &folder{
childrens: []inode{file1},
name: "Folder1",
}
folder2 := &folder{
childrens: []inode{folder1, file2, file3},
name: "Folder2",
}
fmt.Println("\nPrinting hierarchy for Folder2")
folder2.print(" ")
cloneFolder := folder2.clone()
fmt.Println("\nPrinting hierarchy for clone Folder")
cloneFolder.print(" ")
}
输出:
Printing hierarchy for Folder2
Folder2
Folder1
File1
File2
File3
Printing hierarchy for clone Folder
Folder2_clone
Folder1_clone
File1_clone
File2_clone
File3_clone
Go(Golang)中的代理设计模式
注意:想了解其他所有设计模式如何在 GO 中实现吗?请参见这个完整参考 – Go(Golang)中的所有设计模式
目录
** 介绍:
-
UML 图:
-
映射
-
实际示例:
-
完整工作代码:
介绍:
代理设计模式是一种结构设计模式。此模式建议为对主对象的受控和智能访问提供额外的间接层。
在这个模式中,创建了一个新的代理类,它实现了与主对象相同的接口。这使你可以在主对象的实际逻辑之前执行某些行为。让我们通过一个例子来更好地理解它。
-
一张借记卡是你银行账户的代理。它遵循与银行账户相同的接口,更容易使用。
-
像 Nginx 这样的网络服务器可以作为你的应用服务器的代理。它提供
-
对你的应用服务器的受控访问 – 例如,它可以进行速率限制。
-
附加行为 – 例如,它可以进行某些缓存。
-
让我们看看 UML 图。
UML 图:
在下面的 UML 图中
-
主题:它表示代理和真实主题应遵循的接口。
-
代理:代理类嵌入真实主题,并在完成处理后将请求传递给真实主题。
-
真实主题:这是持有主业务逻辑的类,保存在代理之后。
-
客户端: 可以与代理和真实主题进行交互,因为它们都遵循相同的接口。
下面是与上述 nginx 和应用服务器的实际示例相对应的映射 UML 图。
映射
下面的表格表示 UML 图中的参与者与代码中的实际实现参与者之间的映射。
subject | server.go |
---|---|
proxy | nginx.go |
realSubject | application.go |
client | main.go |
实际示例:
server.go
package main
type server interface {
handleRequest(string, string) (int, string)
}
nginx.go
package main
type nginx struct {
application *application
maxAllowedRequest int
rateLimiter map[string]int
}
func newNginxServer() *nginx {
return &nginx{
application: &application{},
maxAllowedRequest: 2,
rateLimiter: make(map[string]int),
}
}
func (n *nginx) handleRequest(url, method string) (int, string) {
allowed := n.checkRateLimiting(url)
if !allowed {
return 403, "Not Allowed"
}
return n.application.handleRequest(url, method)
}
func (n *nginx) checkRateLimiting(url string) bool {
if n.rateLimiter[url] == 0 {
n.rateLimiter[url] = 1
}
if n.rateLimiter[url] > n.maxAllowedRequest {
return false
}
n.rateLimiter[url] = n.rateLimiter[url] + 1
return true
}
application.go
package main
type application struct {
}
func (a *application) handleRequest(url, method string) (int, string) {
if url == "/app/status" && method == "GET" {
return 200, "Ok"
}
if url == "/create/user" && method == "POST" {
return 201, "User Created"
}
return 404, "Not Ok"
}
main.go
package main
import "fmt"
func main() {
nginxServer := newNginxServer()
appStatusURL := "/app/status"
createuserURL := "/create/user"
httpCode, body := nginxServer.handleRequest(appStatusURL, "GET")
fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", appStatusURL, httpCode, body)
httpCode, body = nginxServer.handleRequest(appStatusURL, "GET")
fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", appStatusURL, httpCode, body)
httpCode, body = nginxServer.handleRequest(appStatusURL, "GET")
fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", appStatusURL, httpCode, body)
httpCode, body = nginxServer.handleRequest(createuserURL, "POST")
fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", appStatusURL, httpCode, body)
httpCode, body = nginxServer.handleRequest(createuserURL, "GET")
fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", appStatusURL, httpCode, body)
}
输出:
Url: /app/status
HttpCode: 200
Body: Ok
Url: /app/status
HttpCode: 200
Body: Ok
Url: /app/status
HttpCode: 403
Body: Not Allowed
Url: /app/status
HttpCode: 201
Body: User Created
Url: /app/status
HttpCode: 404
Body: Not Ok
完整工作代码:
package main
import "fmt"
type server interface {
handleRequest(string, string) (int, string)
}
type nginx struct {
application *application
maxAllowedRequest int
rateLimiter map[string]int
}
func newNginxServer() *nginx {
return &nginx{
application: &application{},
maxAllowedRequest: 2,
rateLimiter: make(map[string]int),
}
}
func (n *nginx) handleRequest(url, method string) (int, string) {
allowed := n.checkRateLimiting(url)
if !allowed {
return 403, "Not Allowed"
}
return n.application.handleRequest(url, method)
}
func (n *nginx) checkRateLimiting(url string) bool {
if n.rateLimiter[url] == 0 {
n.rateLimiter[url] = 1
}
if n.rateLimiter[url] > n.maxAllowedRequest {
return false
}
n.rateLimiter[url] = n.rateLimiter[url] + 1
return true
}
type application struct {
}
func (a *application) handleRequest(url, method string) (int, string) {
if url == "/app/status" && method == "GET" {
return 200, "Ok"
}
if url == "/create/user" && method == "POST" {
return 201, "User Created"
}
return 404, "Not Ok"
}
func main() {
nginxServer := newNginxServer()
appStatusURL := "/app/status"
createuserURL := "/create/user"
httpCode, body := nginxServer.handleRequest(appStatusURL, "GET")
fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", appStatusURL, httpCode, body)
httpCode, body = nginxServer.handleRequest(appStatusURL, "GET")
fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", appStatusURL, httpCode, body)
httpCode, body = nginxServer.handleRequest(appStatusURL, "GET")
fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", appStatusURL, httpCode, body)
httpCode, body = nginxServer.handleRequest(createuserURL, "POST")
fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", appStatusURL, httpCode, body)
httpCode, body = nginxServer.handleRequest(createuserURL, "GET")
fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", appStatusURL, httpCode, body)
}
输出:
Url: /app/status
HttpCode: 200
Body: Ok
Url: /app/status
HttpCode: 200
Body: Ok
Url: /app/status
HttpCode: 403
Body: Not Allowed
Url: /app/status
HttpCode: 201
Body: User Created
Url: /app/status
HttpCode: 404
Body: Not Ok
Golang 中的队列
目录
** 介绍
-
列表实现
-
切片实现
介绍
可以使用 GO 中的
-
container/list 包实现一个简单的队列
-
切片
队列将有以下操作:
-
入队
-
出队
-
前端
-
大小
-
空
列表实现
package main
import (
"container/list"
"fmt"
)
type customQueue struct {
queue *list.List
}
func (c *customQueue) Enqueue(value string) {
c.queue.PushBack(value)
}
func (c *customQueue) Dequeue() error {
if c.queue.Len() > 0 {
ele := c.queue.Front()
c.queue.Remove(ele)
}
return fmt.Errorf("Pop Error: Queue is empty")
}
func (c *customQueue) Front() (string, error) {
if c.queue.Len() > 0 {
if val, ok := c.queue.Front().Value.(string); ok {
return val, nil
}
return "", fmt.Errorf("Peep Error: Queue Datatype is incorrect")
}
return "", fmt.Errorf("Peep Error: Queue is empty")
}
func (c *customQueue) Size() int {
return c.queue.Len()
}
func (c *customQueue) Empty() bool {
return c.queue.Len() == 0
}
func main() {
customQueue := &customQueue{
queue: list.New(),
}
fmt.Printf("Enqueue: A\n")
customQueue.Enqueue("A")
fmt.Printf("Enqueue: B\n")
customQueue.Enqueue("B")
fmt.Printf("Size: %d\n", customQueue.Size())
for customQueue.Size() > 0 {
frontVal, _ := customQueue.Front()
fmt.Printf("Front: %s\n", frontVal)
fmt.Printf("Dequeue: %s\n", frontVal)
customQueue.Dequeue()
}
fmt.Printf("Size: %d\n", customQueue.Size())
}
输出:
Enqueue: A
Enqueue: B
Size: 2
Front: A
Dequeue: A
Front: B
Dequeue: B
Size: 0
切片实现
package main
import (
"fmt"
"sync"
)
type customQueue struct {
queue []string
lock sync.RWMutex
}
func (c *customQueue) Enqueue(name string) {
c.lock.Lock()
defer c.lock.Unlock()
c.queue = append(c.queue, name)
}
func (c *customQueue) Dequeue() error {
if len(c.queue) > 0 {
c.lock.Lock()
defer c.lock.Unlock()
c.queue = c.queue[1:]
return nil
}
return fmt.Errorf("Pop Error: Queue is empty")
}
func (c *customQueue) Front() (string, error) {
if len(c.queue) > 0 {
c.lock.Lock()
defer c.lock.Unlock()
return c.queue[0], nil
}
return "", fmt.Errorf("Peep Error: Queue is empty")
}
func (c *customQueue) Size() int {
return len(c.queue)
}
func (c *customQueue) Empty() bool {
return len(c.queue) == 0
}
func main() {
customQueue := &customQueue{
queue: make([]string, 0),
}
fmt.Printf("Enqueue: A\n")
customQueue.Enqueue("A")
fmt.Printf("Enqueue: B\n")
customQueue.Enqueue("B")
fmt.Printf("Len: %d\n", customQueue.Size())
for customQueue.Size() > 0 {
frontVal, _ := customQueue.Front()
fmt.Printf("Front: %s\n", frontVal)
fmt.Printf("Dequeue: %s\n", frontVal)
customQueue.Dequeue()
}
fmt.Printf("Len: %d\n", customQueue.Size())
}
输出:
Enqueue: A
Enqueue: B
Size: 2
Front: A
Dequeue: A
Front: B
Dequeue: B
Size: 0
```*
<!--yml
类别:未分类
日期:2024-10-13 06:47:13
-->
# Golang 中的范围和二维数组程序
> 来源:[`golangbyexample.com/range-sum-2d-array-go/`](https://golangbyexample.com/range-sum-2d-array-go/)
目录
+ 概述
+ 程序
## **概述**
给定一个数字的二维矩阵。目标是计算矩阵中由**左上角**(row1, col1)和**右下角**(row2, col2)定义的矩形内元素的**和**。
看起来简单,对吧。只需从左角迭代到右角并返回总和。但这里有个问题。允许的时间复杂度是 O(1)
这里是我们可以遵循的方法,以便能够在 O(1)时间复杂度内返回答案
+ 为该二维矩阵预计算另一个 sum_array
+ sum_array[i][j] = 从**左角**(0, 0)和**右角**(i, j)计算的数字之和。
+ 对于给定的左角(row1, col1)和右角(row2, col2),计算
```go
topSum = sum_matrix[row1-1][col2]
leftSum = sum_matrix[row2][col1-1]
cornerSum = sum_matrix[row1-1][col1-1]
- 然后返回
sum_matrix[row2][col2] - topSum - leftSum + cornerSum
程序
这里是相同的程序。
package main
import "fmt"
type NumMatrix struct {
matrix [][]int
sum_matrix [][]int
numColumn int
numRows int
}
func initNumArray(matrix [][]int) NumMatrix {
numRows := len(matrix)
numColumn := len(matrix[0])
if numColumn == 0 || numRows == 0 {
return NumMatrix{}
}
sum_matrix := make([][]int, numRows)
for i := 0; i < numRows; i++ {
sum_matrix[i] = make([]int, numColumn)
}
sum_matrix[0][0] = matrix[0][0]
for i := 1; i < numRows; i++ {
sum_matrix[i][0] = matrix[i][0] + sum_matrix[i-1][0]
}
for i := 1; i < numColumn; i++ {
sum_matrix[0][i] = matrix[0][i] + sum_matrix[0][i-1]
}
for i := 1; i < numRows; i++ {
for j := 1; j < numColumn; j++ {
sum_matrix[i][j] = matrix[i][j] + sum_matrix[i-1][j] + sum_matrix[i][j-1] - sum_matrix[i-1][j-1]
}
}
num_matrix := NumMatrix{
matrix: matrix,
sum_matrix: sum_matrix,
numColumn: numColumn,
numRows: numRows,
}
return num_matrix
}
func (this *NumMatrix) SumRegion(row1 int, col1 int, row2 int, col2 int) int {
topSum := 0
leftSum := 0
cornerSum := 0
if row1 > 0 {
topSum = this.sum_matrix[row1-1][col2]
}
if col1 > 0 {
leftSum = this.sum_matrix[row2][col1-1]
}
if row1 > 0 && col1 > 0 {
cornerSum = this.sum_matrix[row1-1][col1-1]
}
return this.sum_matrix[row2][col2] - topSum - leftSum + cornerSum
}
func main() {
matrix := [][]int{{1, 3, 5}, {6, 7, 4}}
na := initNumArray(matrix)
output := na.SumRegion(0, 1, 1, 2)
fmt.Println(output)
}
输出
19
注意: 查看我们的 Golang 高级教程。本系列的教程内容详尽,我们努力涵盖所有概念及其示例。本教程适合那些希望获得专业知识并深入理解 Golang 的人 – Golang 高级教程
如果你有兴趣了解如何在 Golang 中实现所有设计模式。如果是的话,这篇文章适合你 – 所有设计模式 Golang
Go(Golang)中的范围和数组程序
目录
-
概述
-
程序
概述
有一个给定的数字数组。目标是在该数组中找到范围和。这意味着将给定一个范围,其中包含左索引和右索引。我们必须在给定的数字数组中找到左索引和右索引之间的和。
看起来很简单,对吧?只需从给定数组的左索引迭代到右索引并返回和。但这里有个陷阱。允许的时间复杂度是 O(1)
这里是我们可以遵循的方法,以便能够在 O(1)时间复杂度内返回答案
-
从给定的数字数组预计算另一个 sum_array
-
sum_array[i] = 从 0 索引到第 i 索引的数字之和。
-
对于给定的左索引和右索引,只需返回 sum_array[left-1] – sum_array[right]。当然,我们需要验证 left-1 是否大于或等于零。
程序
这是相同程序的实现。
package main
import "fmt"
type NumArray struct {
sum_nums []int
}
func initNumArray(nums []int) NumArray {
length := len(nums)
sum_nums := make([]int, length)
sum_nums[0] = nums[0]
for i := 1; i < length; i++ {
sum_nums[i] = nums[i] + sum_nums[i-1]
}
return NumArray{
sum_nums: sum_nums,
}
}
func (this *NumArray) SumRange(left int, right int) int {
leftSum := 0
if left > 0 {
leftSum = this.sum_nums[left-1]
}
return this.sum_nums[right] - leftSum
}
func main() {
nums := []int{1, 3, 5, 6, 2}
na := initNumArray(nums)
output := na.SumRange(2, 4)
fmt.Println(output)
}
输出
13
注意: 查看我们的 Golang 高级教程。本系列教程内容丰富,涵盖了所有概念及其示例。本教程适合那些希望获得专业知识和对 Golang 有扎实理解的人 – Golang 高级教程
如果你对了解如何在 Golang 中实现所有设计模式感兴趣,那么这篇文章适合你 – 所有设计模式 Golang
在 Go (Golang) 中将文件读入变量
目录
-
概述
-
使用 ioutil 包提供的 ReadFile 函数
-
使用 os.Open 和 bytes.Buffer
-
使用 os.Open 和 strings.Builder
概述
有多种方法可以在 golang 中将文件读入变量。以下是一些方法
-
使用 ioutil 包提供的 ReadFile 函数
-
使用 os.Open 然后使用 bytes.Buffer
-
使用 os.Open 然后使用 strings.Builder
另外,请注意,只有在读取小文件时,将整个文件读入变量才有意义。如果文件很大,那么逐行读取就更有意义。请参考这篇文章。
golangbyexample.com/read-large-file-line-by-line-go/
注意:在尝试本教程中的示例之前,请在运行程序的目录中创建一个名为 test.png 的文件
使用 ioutil 包提供的 ReadFile 函数
golang.org/pkg/io/ioutil/#ReadFile
下面是相应的程序
package main
import (
"fmt"
"io/ioutil"
)
func main() {
fileBytes, err := ioutil.ReadFile("test.png")
if err != nil {
panic(err)
}
fileString := string(fileBytes)
fmt.Println(fileString)
}
输出
Some Garbage Output depending upon the file
使用 os.Open 和 bytes.Buffer
下面是相应的程序
package main
import (
"bytes"
"fmt"
"log"
"os"
)
func main() {
file, err := os.Open("test.png")
if err != nil {
log.Fatalf("Error while opening file. Err: %s", err)
}
defer file.Close()
fileBuffer := new(bytes.Buffer)
fileBuffer.ReadFrom(file)
fileString := fileBuffer.String()
fmt.Print(fileString)
}
输出
Some Garbage Output depending upon the file
使用 os.Open 然后使用 strings.Builder
golang.org/pkg/strings/#Builder
下面是相应的程序
package main
import (
"bytes"
"fmt"
"log"
"os"
)
func main() {
file, err := os.Open("test.png")
if err != nil {
log.Fatalf("Error while opening file. Err: %s", err)
}
defer file.Close()
fileBuffer := new(bytes.Buffer)
fileBuffer.ReadFrom(file)
fileString := fileBuffer.String()
fmt.Print(fileString)
}
输出
Some Garbage Output depending upon the file
另外,查看我们的 Golang 高级教程系列 – Golang 高级教程
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~