1. 程式人生 > 程式設計 >使用go語言實現查詢兩個陣列的異同操作

使用go語言實現查詢兩個陣列的異同操作

最近專案上碰到個小需求,輸入是兩個陣列,一箇舊陣列一個新陣列,要求獲取新陣列相對舊陣列所有新增和刪除的元素,例如:

輸入:

arr_old: {"1","2","4","5","7","9"}

arr_new: {"2","3","6","7"}

返回:

arr_added: {"3","6"}

arr_deleted: {"1","9"}

go的標準庫中沒有類似的直接比較的方法,需要自己具體實現,最簡單的方法當然是舊陣列的每個元素去新陣列,找不到的就是刪除的,然後新陣列的元素再挨個去舊陣列找一遍,找不到就是新增的,但這個方法效率實在太低了。

這裡我使用了一種基於集合運算的思想,即分別求兩個陣列的交集和並集,並集減去交集就是所有發生變化的元素(要麼是新增的,要麼是刪除的),遍歷這個集合中的元素去舊陣列中查詢,如果在舊陣列中找到,那麼就是刪除掉的元素;反之,如果找不到,則一定能在新陣列中找到(用不著真的再去遍歷一次),那麼就是新增的元素。

上程式碼,這裡有個技巧,就是利用go中map鍵唯一性的特性,用陣列的元素作為map的key,通過map來實現快速查詢。

package main
import (
 "fmt"
)
func main() {
 //fmt.Println("Hello World!")
 src := []string{"1","9"}
 dest := []string{"2","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)
 }
 }
 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語言實現查詢兩個陣列的異同操作

總體來講的話陣列就是同一種類型的固定長度的序列。

在go語言中陣列的定義是很簡單的。

使用go語言實現查詢兩個陣列的異同操作

如圖所示,我們定義了一個長度為2的陣列,在陣列定義的過程中,系統已經對這個陣列進行了初始化並分配了空間。所以我們如果想要進行賦值可以通過陣列名加上下標的方式進行賦值。但是值得注意的一點是我們並不能進行陣列的超長處理。這一點有別於java的陣列定義,java的定長陣列新增值後如果對於超出的值會有自動擴容功能。

使用go語言實現查詢兩個陣列的異同操作

但是在go語言中並沒有方法來進行增刪改查值,只有通過下標的方式,所以我們如果進行了越界處理編譯都會進行報錯。所以才入門的小夥伴們需要注意一下哦。陣列的下標在陣列的合法範圍之外就會出發訪問越界,會有panic出現。所以小棧君也是通過了一個例項給大家說明一下,因為編譯可能會不通過,所以我們巧妙的避開編譯器的編譯進行陣列的越界操作說明。

使用go語言實現查詢兩個陣列的異同操作

當然需要值得注意的一點是,陣列的長度也是陣列型別的一部分,因此var a [2]int 和 var b [3] int 是兩個不同的型別。

知識點來了,在go語言中的陣列是屬於值型別傳遞,當我們傳遞一個數組到一個方法中,改變副本的值並不會修改到原本陣列的值。所以得到的陣列還是原來的樣子。

使用go語言實現查詢兩個陣列的異同操作

因此如果我們要對陣列進行值的修改的話,就只有進行指標操作啦~。

使用go語言實現查詢兩個陣列的異同操作

切片的概念

在go語言中陣列中長度不可以更改,所以在實際的應用環境中並不是非常實用,所以Go語言衍生出了一種靈活性強和功能更強大的內建型別,即為切片。

與上面所講的陣列相比,切片的長度是不固定的,並且切片是可以進行擴容。切片物件非常小,是因為它是隻有3個欄位的資料結構:一個是指向底層陣列的指標,一個是切片的長度,一個是切片的容量。這3個欄位,就是Go語言操作底層陣列的元資料,有了它們,我們就可以任意的操作切片了。

當然,切片作為陣列的引用,所以切片屬於是引用型別,各位小夥伴可千萬要記住了哦。在切片的使用過程當中,我們可以通過遍歷陣列的方式進行對於切片的遍歷,我們也可以內建方法len對陣列或切片進行長度的計算。

當然我們也可以對切片的容量進行計算,之前有講過Go語言有豐富的內建庫提供給我們使用,所以我們也可以cap內建函式進行容量的計算。多個切片如果表示同一個陣列的片段,它們可以共享資料;因此一個切片和相關陣列的其他切片是共享儲存的,相反,不同的陣列總是代表不同的儲存。陣列實際上是切片的構建塊。

使用go語言實現查詢兩個陣列的異同操作

使用go語言實現查詢兩個陣列的異同操作

上面的例子小棧君分別用陣列和切片進行了測試,我們可以看到陣列的容量一旦確定後就無法進行更改,當我們的切片進行初始化,初始的容量是2,此時切片的容量和長度都是2,但是我通過內建的append方法進行了切片的增加。此時的切片的容量和長度都是4。此時我們並不能確定切片內建擴容的機制,但是隱約猜測是倍增。

言歸正傳,為了測試一下切片的擴容機制,所以小棧君又進行了切片的增加,此時,細心的小夥伴應該發現,這次小棧君一次性增加了兩個元素在一個append裡面,因為這是append方法是一個可變長度的傳值。這也是一個小知識點哦。

如果切片的底層陣列,沒有足夠的容量時,就會新建一個底層陣列,把原來陣列的值複製到新底層數組裡,再追加新值,這時候就不會影響原來的底層陣列了。

append目前的演算法是:容量小於1000個時,總是成倍的增長,一旦容量超過1000個,增長因子設為1.25,也就是說每次會增加25%的容量。

之後我們發現切片的容量和長度發生了變化,如果說上次容量的擴張是4是我們猜測的倍數擴容方式,那麼這次我們就實錘了他的擴容機制就是倍增。而且在Go語言的容量和長度不一樣,所以我們也可以得出結論,就是在 0 <= len(arry) <= cap(slice)。

在我們宣告好切片後我們可以使用new或是make方法對切片進行初始化,當然小棧君也試著嘗試證明切片如果沒有進行初始化是會panic的。結果並沒有出現。因為如果slice沒有初始化,它僅僅相當於一個nil,長度和容量都為0,並不會panic。

使用go語言實現查詢兩個陣列的異同操作

小棧君也考慮到可能是因為沒有內建增加方法或是沒有報錯僅僅只是因為我後面利用對Carry陣列的切割進行賦值的緣故。所以不甘心又做了一次嘗試,定義好相應的切片後直接使用append方法,結果如下:

使用go語言實現查詢兩個陣列的異同操作

我們同樣可以通過上述的例子瞭解到切片的下標是左閉右開區間,因為我們carry陣列的內容如上圖所示, 我們最終得到的結果是IT乾貨棧,下標來講的話是屬於1。所以我們得到的結論和事實是一樣的。對於切片我們也有很多用法,如下圖所示:

使用go語言實現查詢兩個陣列的異同操作

下圖是Python中對於切片的操作,並且Python中的陣列更為靈活,在後面我為大家分享Python教程的時候會詳細分享哦。

使用go語言實現查詢兩個陣列的異同操作

切片的初始化

對於切片的初始化我們可以make方法和new方法

使用go語言實現查詢兩個陣列的異同操作

new(T) 為每個新的型別T分配一片記憶體,初始化為 0 並且返回型別為*T的記憶體地址:這種方法 返回一個指向型別為 T,值為 0 的地址的指標,它適用於值型別如陣列和結構體;它相當於 &T{}。

make(T) 返回一個型別為 T 的初始值,它只適用於3種內建的引用型別:切片、map 和 channel。

拷貝

因為在go語言的陣列是屬於值傳遞,之前的方法也證實了這一點,在方法傳遞值的時候系統會進行拷貝一份副本進行傳遞,如果需要改變的值的話就需要使用指標。但是在使用切片處理的時候,因為切片屬於引用傳遞,所以go語言有內建的函式copy方法進行值的拷貝。

使用go語言實現查詢兩個陣列的異同操作

上述的一個例子可以綜合說明幾點問題了,最開始我們定義了一個切片並且容量是2,內容是1和2,我們同樣定義了切片b但是並沒有做初始化處理。直接使用copy操作。使用copy操作的時候,小棧君也複製了原始碼出來,第一個是我們的資料來源,第二個引數傳遞我們的目標源。直接使用的話我們可以從結果看出並沒有成功。

所以接下來小棧君又進行了初始化操作。這裡舉例的目的是提醒各位,在操作切片的時候沒有初始化就相當於nil,最好是進行切片的初始化操作。在早期go語言的版本中,沒有初始化切片會直接報錯。接下來我又進行了再一次的複製操作並且打印出他們的地址和容量、長度。可以看出進行切片的拷貝是不會進行切片的擴容處理。而且他們分別指向了不同的地址說明拷貝成功。

以上為個人經驗,希望能給大家一個參考,也希望大家多多支援我們。如有錯誤或未考慮完全的地方,望不吝賜教。