使⽤go语⾔实现查两个数组的异同操作
最近项⽬上碰到个⼩需求,输⼊是两个数组,⼀个旧数组⼀个新数组,要求获取新数组相对旧数组所有新增和删除的元素,例如:
输⼊:
arr_old: {"1", "2", "4", "5", "7", "9"}
arr_new: {"2", "3", "4", "6", "7"}
返回:
arr_added: {"3", "6"}
arr_deleted: {"1", "5", "9"}
go的标准库中没有类似的直接⽐较的⽅法,需要⾃⼰具体实现,最简单的⽅法当然是旧数组的每个元素去新数组,不到的就是删除的,然后新数组的元素再挨个去旧数组⼀遍,不到就是新增的,但这个⽅法效率实在太低了。
这⾥我使⽤了⼀种基于集合运算的思想,即分别求两个数组的交集和并集,并集减去交集就是所有发⽣变化的元素(要么是新增的,要么是删除的),遍历这个集合中的元素去旧数组中查,如果在旧数组中到,那么就是删除掉的元素;反之,如果不到,则⼀定能在新数组中到(⽤不着真的再去遍历⼀次),那么就是新增的元素。
上代码,这⾥有个技巧,就是利⽤go中map键唯⼀性的特性,⽤数组的元素作为map的key,通过map来实现快速查。
package main
import (
"fmt"
)
func main() {
//fmt.Println("Hello World!")
src := []string{"1", "2", "4", "5", "7", "9"}
dest := []string{"2", "3", "4", "6", "7"}
added, removed := Arrcmp(src, dest)
fmt.Printf("add: %v\nrem: %v\n", added, removed)
}
func Arrcmp(src []string, dest []string) ([]string, []string) {
msrc := make(map[string]byte) //按源数组建索引
mall := make(map[string]byte) //源+⽬所有元素建索引
var set []string //交集
//1.源数组建⽴map
for _, v := range src {
msrc[v] = 0
mall[v] = 0
}
//2.⽬数组中,存不进去,即重复元素,所有存不进去的集合就是并集
for _, v := range dest {
l := len(mall)
mall[v] = 1
if l != len(mall) { //长度变化,即可以存
l = len(mall)
} else { //存不了,进并集
set = append(set, v)
}
}
//3.遍历交集,在并集中,到就从并集中删,删完后就是补集(即并-交=所有变化的元素)
for _, v := range set {
delete(mall, v)
}
//4.此时,mall是补集,所有元素去源中,到就是删除的,不到的必定能在⽬数组中到,即新加的
var added, deleted []string
for v, _ := range mall {
_, exist := msrc[v]
if exist {
deleted = append(deleted, v)
} else {
added = append(added, v)
}
}python获取数组长度
return added, deleted
}
运⾏结果:
add: [6 3]
rem: [1 5 9]
欢迎⼤家交流效率更⾼的⽅法。
补充:go语⾔教程之浅谈数组和切⽚的异同
本期的分享我们来讲解⼀下关于go语⾔的数组和切⽚的概念、⽤法和区别。
在go语⾔的程序开发过程中,我们避免不了数组和切⽚。关于他们的⽤法和区别却使得有的⼩伙伴感觉困惑。所以⼩栈君这⾥也归纳和总结了关于数组和切⽚的⼲货帮助⼩伙伴进⾏理解。
数组的定义
数组是具有相同唯⼀类型的⼀组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整形、字符串或者⾃定义类型。
相对于去声明 number0, number1, ..., number99 的变量,使⽤数组形式 numbers[0], numbers[1] ..., numbers[99] 更加⽅便且易于扩展。
数组元素可以通过索引(位置)来读取(或者修改),索引从 0 开始,第⼀个元素索引为 0,第⼆个索引为 1,以此类推。
总体来讲的话数组就是同⼀种类型的固定长度的序列。
在go语⾔中数组的定义是很简单的。
如图所⽰,我们定义了⼀个长度为2的数组,在数组定义的过程中,系统已经对这个数组进⾏了初始化并分配了空间。所以我们如果想要进⾏赋值可以通过数组名加上下标的⽅式进⾏赋值。但是值得注意的⼀点是我们并不能进⾏数组的超长处理。这⼀点有别于java的数组定义,java的定长数组添加值后如果对于超出的值会有⾃动扩容功能。
但是在go语⾔中并没有⽅法来进⾏增删改查值,只有通过下标的⽅式,所以我们如果进⾏了越界处理编译都会进⾏报错。所以才⼊门的
⼩伙伴们需要注意⼀下哦。数组的下标在数组的合法范围之外就会出发访问越界,会有panic出现。所以⼩栈君也是通过了⼀个实例给⼤家说明⼀下,因为编译可能会不通过,所以我们巧妙的避开编译器的编译进⾏数组的越界操作说明。
当然需要值得注意的⼀点是,数组的长度也是数组类型的⼀部分,因此var a [2]int 和 var b [3] int 是两个不同的类型。
知识点来了,在go语⾔中的数组是属于值类型传递,当我们传递⼀个数组到⼀个⽅法中,改变副本的值并不会修改到原本数组的值。所以得到的数组还是原来的样⼦。
因此如果我们要对数组进⾏值的修改的话,就只有进⾏指针操作啦~。
切⽚的概念
在go语⾔中数组中长度不可以更改,所以在实际的应⽤环境中并不是⾮常实⽤,所以Go语⾔衍⽣出了⼀种灵活性强和功能更强⼤的内置类型,即为切⽚。
与上⾯所讲的数组相⽐,切⽚的长度是不固定的,并且切⽚是可以进⾏扩容。切⽚对象⾮常⼩,是因为它是只有3个字段的数据结构:⼀个是指向底层数组的指针,⼀个是切⽚的长度,⼀个是切⽚的容量。这3个字段,就是Go语⾔操作底层数组的元数据,有了它们,我们就可以任意的操作切⽚了。
当然,切⽚作为数组的引⽤,所以切⽚属于是引⽤类型,各位⼩伙伴可千万要记住了哦。在切⽚的使⽤过程当中,我们可以通过遍历数组的⽅式进⾏对于切⽚的遍历,我们也可以内置⽅法len对数组或切⽚进⾏长度的计算。
当然我们也可以对切⽚的容量进⾏计算,之前有讲过Go语⾔有丰富的内置库提供给我们使⽤,所以我们也可以cap内置函数进⾏容量的计算。多个切⽚如果表⽰同⼀个数组的⽚段,它们可以共享数据;因此⼀个切⽚和相关数组的其他切⽚是共享存储的,相反,不同的数组总是代表不同的存储。数组实际上是切⽚的构建块。
上⾯的例⼦⼩栈君分别⽤数组和切⽚进⾏了测试,我们可以看到数组的容量⼀旦确定后就⽆法进⾏更改,当我们的切⽚进⾏初始化,初始的容量是2,此时切⽚的容量和长度都是2,但是我通过内置的appe
nd⽅法进⾏了切⽚的增加。此时的切⽚的容量和长度都是4。此时我们并不能确定切⽚内置扩容的机制,但是隐约猜测是倍增。
⾔归正传,为了测试⼀下切⽚的扩容机制,所以⼩栈君⼜进⾏了切⽚的增加,此时,细⼼的⼩伙伴应该发现,这次⼩栈君⼀次性增加了两个元素在⼀个append⾥⾯,因为这是append⽅法是⼀个可变长度的传值。这也是⼀个⼩知识点哦。
如果切⽚的底层数组,没有⾜够的容量时,就会新建⼀个底层数组,把原来数组的值复制到新底层数组⾥,再追加新值,这时候就不会影响原来的底层数组了。
append⽬前的算法是:容量⼩于1000个时,总是成倍的增长,⼀旦容量超过1000个,增长因⼦设为1.25,也就是说每次会增加25%的容量。
之后我们发现切⽚的容量和长度发⽣了变化,如果说上次容量的扩张是4是我们猜测的倍数扩容⽅式,那么这次我们就实锤了他的扩容机制就是倍增。⽽且在Go语⾔的容量和长度不⼀样,所以我们也可以得出结论,就是在 0 <= len(arry) <= cap(slice)。
在我们声明好切⽚后我们可以使⽤new或是make⽅法对切⽚进⾏初始化,当然⼩栈君也试着尝试证明切⽚如果没有进⾏初始化是会panic的。结果并没有出现。因为如果slice没有初始化,它仅仅相当于⼀个nil,长度和容量都为0,并不会panic。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论