Go語言全棧開發:數組

本文主要介紹Go語言中數組(array)及它的基本使用。

Array(數組)

數組是同一種數據類型元素的集合。 在Go語言中,數組從聲明時就確定,使用時可以修改數組成員,但是數組大小不可變化。 基本語法:

// 定義一個長度爲3元素類型爲int的數組a
var a [3]int
數組定義
var 數組變量名 [元素數量]T

比如:var a [5]int, 數組的長度必須是常量,並且長度是數組類型的一部分。一旦定義,長度不能變。 [5]int[10]int是不同的類型。

var a [3]int
var b [4]int
a = b //不可以這樣做,因爲此時a和b是不同的類型

數組可以通過下標進行訪問,下標是從0開始,最後一個元素下標是:len-1,訪問越界(下標在合法範圍之外),則觸發訪問越界,會panic

數組的初始化

數組的初始化也有很多方式。

| 方法一

初始化數組時可以使用初始化列表來設置數組元素的值。

func main() {
	var testArray [3]int                        //數組會初始化爲int類型的零值
	var numArray = [3]int{1, 2}                 //使用指定的初始值完成初始化
	var cityArray = [3]string{"北京", "上海", "深圳"} //使用指定的初始值完成初始化
	fmt.Println(testArray)                      //[0 0 0]
	fmt.Println(numArray)                       //[1 2 0]
	fmt.Println(cityArray)                      //[北京 上海 深圳]
}

| 方法二

按照上面的方法每次都要確保提供的初始值和數組長度一致,一般情況下我們可以讓編譯器根據初始值的個數自行推斷數組的長度,例如:

func main() {
	var testArray [3]int
	var numArray = [...]int{1, 2}
	var cityArray = [...]string{"北京", "上海", "深圳"}
	fmt.Println(testArray)                          //[0 0 0]
	fmt.Println(numArray)                           //[1 2]
	fmt.Printf("type of numArray:%T\n", numArray)   //type of numArray:[2]int
	fmt.Println(cityArray)                          //[北京 上海 深圳]
	fmt.Printf("type of cityArray:%T\n", cityArray) //type of cityArray:[3]string
}

| 方法三

我們還可以使用指定索引值的方式來初始化數組,例如:

func main() {
	a := [...]int{1: 1, 3: 5}
	fmt.Println(a)                  // [0 1 0 5]
	fmt.Printf("type of a:%T\n", a) //type of a:[4]int
}
數組的遍歷

遍歷數組a有以下兩種方法:

func main() {
	var a = [...]string{"北京", "上海", "深圳"}
	// 方法1:for循環遍歷
	for i := 0; i < len(a); i++ {
		fmt.Println(a[i])
	}

	// 方法2:for range遍歷
	for index, value := range a {
		fmt.Println(index, value)
	}
}
多維數組

Go語言是支持多維數組的,我們這裏以二維數組爲例(數組中又嵌套數組)。

| 二維數組的定義

func main() {
	a := [3][2]string{
		{"北京", "上海"},
		{"廣州", "深圳"},
		{"成都", "重慶"},
	}
	fmt.Println(a) //[[北京 上海] [廣州 深圳] [成都 重慶]]
	fmt.Println(a[2][1]) //支持索引取值:重慶
}

| 二維數組的遍歷

func main() {
	a := [3][2]string{
		{"北京", "上海"},
		{"廣州", "深圳"},
		{"成都", "重慶"},
	}
	for _, v1 := range a {
		for _, v2 := range v1 {
			fmt.Printf("%s\t", v2)
		}
		fmt.Println()
	}
}

輸出:

北京	上海	
廣州	深圳	
成都	重慶	

注意: 多維數組 只有第一層 可以使用來讓編譯器推導數組長度。例如:

//支持的寫法
a := [...][2]string{
	{"北京", "上海"},
	{"廣州", "深圳"},
	{"成都", "重慶"},
}
//不支持多維數組的內層使用...
b := [3][...]string{
	{"北京", "上海"},
	{"廣州", "深圳"},
	{"成都", "重慶"},
}
數組是值類型
數組是值類型,賦值和傳參會複製整個數組。因此改變副本的值,不會改變本身的值。
func modifyArray(x [3]int) {
	x[0] = 100
}

func modifyArray2(x [3][2]int) {
	x[2][0] = 100
}
func main() {
	a := [3]int{10, 20, 30}
	modifyArray(a) //在modify中修改的是a的副本x
	fmt.Println(a) //[10 20 30]
	b := [3][2]int{
		{1, 1},
		{1, 1},
		{1, 1},
	}
	modifyArray2(b) //在modify中修改的是b的副本x
	fmt.Println(b)  //[[1 1] [1 1] [1 1]]
}

注意:

  1. 數組支持 “==“、”!=” 操作符,因爲內存總是被初始化過的。
  2. [n]* T 表示指針數組, * [n]T 表示數組指針 。

練習題

  1. 求數組[1, 3, 5, 7, 8]所有元素的和
package main

func sumArray(x [5]int) {
	var v int
	sum := 0
	for _, v = range x {
		sum += v
	}
	println(sum)
}

func main() {
	array1 := [5]int{1, 3, 5, 7, 8}
	sumArray(array1)
}
  1. 找出數組中和爲指定值的兩個元素的下標,比如從數組[1, 3, 5, 7, 8]中找出和爲8的兩個元素的下標分別爲(0,3)(1,2)
package main

import "fmt"

func findAddSum(x [5]int) ([]int) {
	resultMap := make(map[string]int, 8)
	var resultArry []int
	var v int
	var i int
	target := 8
	for i, v = range x {
		_, b := resultMap[string(v)]
		if b {
			resultArry = append(resultArry, resultMap[string(v)], i)
		}
		resultMap[string(target-v)] = i
	}
	return resultArry
}

func main() {
	array1 := [5]int{1, 3, 5, 7, 8}
	a := findAddSum(array1)
	for i := 0; i < len(a); i = i + 2 {
		fmt.Printf("(%d,%d)", a[i], a[i+1])
	}
}

【對比python】

| 理解python的數據類型:

Python 的用戶往往被其易用性所吸引,其中一個易用之處就在於動態輸入。靜態類型的語言(如C或Java)往往需要每一個變量都明確地聲明,而動態類型的語言(例如Python)可以跳過這個特殊規定。例如在C語言中,你可能會按照如下方式指定一個特殊的操作:

/* C代碼 */ 
int result = 0; 
for(int i=0; i<100; i++){ 
    result += i; 
}

而在Python 中,同等的操作可以按照如下方式實現:

# Python代碼 
result = 0 
for i in range(100):
    result += i

注意這裏最大的不同之處:在C語言中,每個變量的數據類型被明確地聲明;而在Python中,類型是動態推斷的。這意味着可以將任何類型的數據指定給任何變量:

# Python代碼 
x = 4 
x = "four"

這裏已經將x 變量的內容由整型轉變成了字符串,而同樣的操作在C語言中將會導致(取決於編譯器設置)編譯錯誤或其他未知的後果:

/* C代碼 */ 
int x = 4; 
x = "four";  // 編譯失敗

這種靈活性是使Python 和其他動態類型的語言更易用的原因之一。理解這一特性如何工作是學習用Python 有效且高效地分析數據的重要因素。但是這種類型靈活性也指出了一個事實:Python 變量不僅是它們的值,還包括了關於值的類型的一些額外信息。

| Python整型不僅僅是一個整型

標準的Python 實現是用C語言編寫的。這意味着每一個Python 對象都是一個聰明的僞C語言結構體,該結構體不僅包含其值,還有其他信息。例如,當我們在Python 中定義一個整型,例如x = 10000 時,x 並不是一個“原生”整型,而是一個指針,指向一個C語言的複合結構體,結構體裏包含了一些值。查看Python 3.4 的源代碼,可以發現整型(長整型)的定義,如下所示(C語言的宏經過擴展之後):

struct _longobject { 
    long ob_refcnt; 
    PyTypeObject *ob_type; 
    size_t ob_size; 
    long ob_digit[1]; 
};

Python 3.4 中的一個整型實際上包括4 個部分。
• ob_refcnt 是一個引用計數,它幫助Python 默默地處理內存的分配和回收。
• ob_type 將變量的類型編碼。
• ob_size 指定接下來的數據成員的大小。
• ob_digit 包含我們希望Python 變量表示的實際整型值。

這意味着與C語言這樣的編譯語言中的整型相比,在Python 中存儲一個整型會有一些開銷。
在這裏插入圖片描述
PyObject_HEAD :是結構體中包含引用計數、類型編碼和其他之前提到的內容的部分

C語言整型本質上是對應某個內存位置的標籤,裏面存儲的字節會編碼成整型。而Python 的整型其實是一個指針,指向包含這個Python 對象所有信息的某個內存位置,其中包括可以轉換成整型的字節。由於Python 的整型結構體裏面還包含了大量額外的信息,所以Python 可以自由、動態地編碼。但是,Python 類型中的這些額外信息也會成爲負擔,在多個對象組合的結構體中尤其明顯。

| Python列表不僅僅是一個列表

如果使用一個包含很多Python 對象的Python 數據結構,會發生什麼?Python 中的標準可變多元素容器是列表。可利用Python 的動態類型特性,創建一個異構的列表:

L3 = [True, "2", 3.0, 4]
print([type(item) for item in L3])
# [<class 'bool'>, <class 'str'>, <class 'float'>, <class 'int'>]

擁有這種靈活性也是要付出一定代價的:爲了獲得這些靈活的類型,列表中的每一項必須包含各自的類型信息、引用計數和其他信息;也就是說,每一項都是一個完整的Python 對象。來看一個特殊的例子,如果列表中的所有變量都是同一類型的,那麼很多信息都會顯得多餘——將數據存儲在固定類型的數組中應該會更高效。

動態類型的列表和固定類型的(NumPy 式)數組間的區別如下所示:
在這裏插入圖片描述
在實現層面,數組基本上包含一個指向連續數據塊的指針。另一方面,Python 列表包含一個指向指針塊的指針,這其中的每一個指針對應一個完整的Python 對象(如前面看到的Python 整型)。另外,列表的優勢是靈活,因爲每個列表元素是一個包含數據和類型信息的完整結構體,而且列表可以用任意類型的數據填充。固定類型的NumPy 式數組缺乏這種靈活性,但是能更有效地存儲和操作數據。

| Python中的固定類型數組

Python 提供了幾種將數據存儲在有效的、固定類型的數據緩存中的選項。內置的數組(array)模塊可以用於創建統一類型的密集數組。但其只支持一維數組,不支持多維數組(在TensorFlow裏面偏向於矩陣理解),也沒有各種運算函數。因而不適合數值運算,NumPy的出現彌補了這些不足。

Numpy是高性能科學計算和數據分析的基礎包。它也是pandas等其他數據分析的工具的基礎,基本所有數據分析的包都用過它。NumPy爲Python帶來了真正的多維數組功能,並且提供了豐富的函數庫處理這些數組。它將常用的數學函數都支持向量化運算,使得這些數學函數能夠直接對數組進行操作,將本來需要在Python級別進行的循環,放到C語言的運算中,明顯地提高了程序的運算速度。

| NumPy ndarray:多維數組對象

創建ndarray對象:

生成數組最簡單的方式就是使用array函數。array函數接收任意的序列型對象(當然也包括其他的數組),生成一個新的包含傳遞數據的NumPy數組。例如,列表的轉換就是一個好例子:

import numpy as np

data1 = [6, 7.5, 8, 0, 1]
arr1 = np.array(data1)
print(arr1,type(arr1))
#[6.  7.5 8.  0.  1. ] <class 'numpy.ndarray'>

嵌套序列,例如同等長度的列表,將會自動轉換成多維數組:

import numpy as np

data2 = [[1, 2, 3, 4], [5, 6, 7, 8]]
arr2 = np.array(data2)
print(arr2,arr2.ndim,arr2.shape,arr2.dtype)
#array([[1, 2, 3, 4],
#      [5, 6, 7, 8]]) 2 (2, 4) int32

常用屬性:
在這裏插入圖片描述
其他的屬性包括表示每個數組元素字節大小的itemsize,以及表示數組總字節大小的屬性nbytes

標準數據類型:
在這裏插入圖片描述

除了np.array,還有很多其他函數可以創建新數組。

# 創建一個長度爲10的數組,數組的值都是0
np.zeros(10, dtype=int)
#array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])

# 創建一個3×5的浮點型數組,數組的值都是1
np.ones((3, 5), dtype=float)
#array([[1., 1., 1., 1., 1.],
#                [1., 1., 1., 1., 1.],
#                [1., 1., 1., 1., 1.]])

# 創建一個3×5的浮點型數組,數組的值都是3.14
np.full((3, 5), 3.14)
#array([[3.14, 3.14, 3.14, 3.14, 3.14],
#                [3.14, 3.14, 3.14, 3.14, 3.14],
#                [3.14, 3.14, 3.14, 3.14, 3.14]])

# 創建一個3×5的浮點型數組,數組的值是一個線性序列
# 從0開始,到20結束,步長爲2
# (它和內置的range()函數類似)
np.arange(0, 20, 2)
#array([0, 2, 4, 6, 8, 10, 12, 14, 16, 18])

# 創建一個5個元素的數組,這5個數均勻地分配到0~1
np.linspace(0, 1, 5)
#array([0., 0.25, 0.5, 0.75, 1.])

# 創建一個3×3的、在0~1均勻分佈的隨機數組成的數組
np.random.random((3, 3))
#array([[0.99844933, 0.52183819, 0.22421193],
#                [0.08007488, 0.45429293, 0.20941444],
#                [0.14360941, 0.96910973, 0.946117]])

# 創建一個3×3的、均值爲0、方差爲1的
# 正態分佈的隨機數數組
np.random.normal(0, 1, (3, 3))
#array([[1.51772646, 0.39614948, -0.10634696],
#                [0.25671348, 0.00732722, 0.37783601],
#                [0.68446945, 0.15926039, -0.70744073]])

# 創建一個3×3的、[0, 10)區間的隨機整型數組
np.random.randint(0, 10, (3, 3))
#array([[2, 3, 4],
#                [5, 7, 8],
#                [0, 5, 0]])

# 創建一個3×3的單位矩陣
np.eye(3)
#array([[1., 0., 0.],
#                [0., 1., 0.],
#                [0., 0., 1.]])

# 創建一個由3個整型數組成的未初始化的數組
# 數組的值是內存空間中的任意值
np.empty(3)
#array([1., 1., 1.])

| NumPy數組基礎

索引:

和Python列表一樣,在一維數組中,你也可以通過中括號指定索引獲取第i 個值(從0 開始計數):

arr1=np.array([1,2,3,4,5,6])
print(arr1[0],arr1[-1])
# 1  6

在多維數組中,可以用逗號分隔的索引元組獲取元素:

arr2=np.array([[1, 2, 3, 4],
                [5, 6, 7, 8],
                [9, 10, 11, 12]])
print(arr2.shape)
# (3, 4)
print(arr2[0, 0],arr2[2, 0],arr2[2, -1])
# 1 9 12

注意: 也可以用以上索引方式修改元素值,但由於NumPy 數組是固定類型的。這意味着當你試圖將一個浮點值插入一個整型數組時,浮點值會被截短成整型。

切片:

NumPy 切片語法和Python 列表的標準切片語法相同。爲了獲取數組x 的一個切片,可以用以下方式:x[start:stop:step]

一維子數組:

 x = np.arange(10)
print(x[:5])  # 前五個元素 
#array([0, 1, 2, 3, 4])

print(x[5:]) # 索引五之後的元素 
#array([5, 6, 7, 8, 9])

print(x[4:7])  # 中間的子數組 
#array([4, 5, 6])

print(x[::2])  # 每隔一個元素 
#array([0, 2, 4, 6, 8])

print(x[1::2])  # 每隔一個元素,從索引1開始 
#array([1, 3, 5, 7, 9])

print(x[::-1])  # 所有元素,逆序的
#array([9, 8, 7, 6, 5, 4, 3, 2, 1, 0])

print(x[5::-2])  # 從索引5開始每隔一個元素逆序
#array([5, 3, 1])

多維子數組:

多維切片也採用同樣的方式處理,用冒號分隔。

x2=np.array([[1, 2, 3, 4],
                [5, 6, 7, 8],
                [9, 10, 11, 12]])

print(x2[:2, :3])  # 兩行,三列
#array([[1 2 3]
#      [5 6 7]])

print(x2[:3, ::2])  # 所有行,每隔一列
#array([[ 1  3]
#       [ 5  7]
#       [ 9 11]])

print(x2[::-1, ::-1]) # 同時被逆序
#array([[12 11 10  9]
#       [ 8  7  6  5]
#       [ 4  3  2  1]])

一種常見的需求是獲取數組的單行和單列。你可以將索引與切片組合起來實現這個功能,用一個冒號(:)表示空切片:

x2=np.array([[1, 2, 3, 4],
                [5, 6, 7, 8],
                [9, 10, 11, 12]])

print(x2[:, 0])  # x2的第一列
#[1 5 9]

print(x2[0, :])  # x2的第一行
#[1 2 3 4]

print(x2[0])  # 等於x2[0, :]
#[1 2 3 4]

注意: 數組切片返回的是數組數據的視圖,而不是數值數據的副本。這一點也是NumPy 數組切片和Python 列表切片的不同之處:在Python 列表中,切片是值的副本。

x2=np.array([[1, 2, 3, 4],
                [5, 6, 7, 8],
                [9, 10, 11, 12]])

x2_sub = x2[:2, :2]
print(x2_sub)
# [[1 2]
#  [5 6]]

# 修改x2_sub這個子數組
x2_sub[0, 0] = 99
print(x2_sub)
# 原始數組也被修改了!
print(x2)

這種默認切片返回視圖的處理方式實際上非常有用:它意味着在處理非常大的數據集時,可以獲取或處理這些數據集的片段,而不用複製底層的數據緩存。但如果不想返回視圖,只想創建數組的副本,可以通過x2_sub = x2[:2, :2].copy() 方法實現。

數組變形:
例如,如果你希望將數字1~9 放入一個3×3 的矩陣中,可以採用如下方法:

  grid = np.arange(1, 10).reshape((3, 3)) 

注意: 原始數組的大小必須和變形後數組的大小一致。

將一個一維數組轉變爲二維的行或列的矩陣。你也可以通過reshape 方法來實現,或者更簡單地在一個切片操作中利用newaxis 關鍵字:

x = np.array([1, 2, 3])

# 通過變形獲得的行向量
print(x.reshape((1, 3)))
#array([[1, 2, 3]])

# 通過newaxis獲得的行向量
print(x[np.newaxis, :])
#array([[1, 2, 3]])

# 通過變形獲得的列向量
print(x.reshape((3, 1)))
#array([[1],
#                [2],
#                [3]])

# 通過newaxis獲得的列向量
print(x[:, np.newaxis])
#array([[1],
#                [2],
#                [3]])

數組拼接和分裂:

拼接或連接NumPy 中的兩個數組主要由np.concatenate、np.vstack 和np.hstack 例程實現。

x = np.array([1, 2, 3])
y = np.array([3, 2, 1])
np.concatenate([x, y])
# array([1, 2, 3, 3, 2, 1])

#你也可以一次性拼接兩個以上數組:
z = [99, 99, 99]
print(np.concatenate([x, y, z]))
#[1  2  3  3  2  1 99 99 99]

#二維數組的拼接:
grid = np.array([[1, 2, 3],
                 [4, 5, 6]])

# 沿着第一個軸拼接
np.concatenate([grid, grid])
#array([[1, 2, 3],
#       [4, 5, 6],
#       [1, 2, 3],
#       [4, 5, 6]])


# 沿着第二個軸拼接(從0開始索引)
np.concatenate([grid, grid], axis=1)
#array([[1, 2, 3, 1, 2, 3],
#       [4, 5, 6, 4, 5, 6]])

沿着固定維度處理數組時,使用np.vstack(垂直棧)和np.hstack(水平棧)函數會更簡潔:

x = np.array([1, 2, 3])
grid = np.array([[9, 8, 7],
                 [6, 5, 4]])

# 垂直棧數組
np.vstack([x, grid])
#array([[1, 2, 3],
#       [9, 8, 7],
#       [6, 5, 4]])


# 水平棧數組 
y = np.array([[99],
              [99]])
np.hstack([grid, y])
#array([[9, 8, 7, 99],
#       [6, 5, 4, 99]])

與拼接相反的過程是分裂。分裂可以通過np.split、np.hsplit 和np.vsplit 函數來實現。可以向以上函數傳遞一個索引列表作爲參數,索引列表記錄的是分裂點位置:

x = np.array([1, 2, 3, 99, 99, 3, 2, 1])
x1, x2, x3 = np.split(x, [3, 5])
print(x1, x2, x3)
# [1 2 3] [99 99] [3 2 1]

注意: N 分裂點會得到N + 1個子數組。相關的np.hsplit 和np.vsplit 的用法也類似:

grid = np.arange(16).reshape((4, 4))
print(grid)
#array([[0, 1, 2, 3],
#       [4, 5, 6, 7],
#       [8, 9, 10, 11],
#       [12, 13, 14, 15]])

upper, lower = np.vsplit(grid, [2])
print(upper)
#[[0 1 2 3]
# [4 5 6 7]]
print(lower)
#[[8  9 10 11]
# [12 13 14 15]]

left, right = np.hsplit(grid, [2])
print(left)
#[[ 0  1] 
# [ 4  5] 
# [ 8  9] 
# [12 13]] 
print(right)
#[[ 2  3] 
# [ 6  7] 
# [10 11] 
# [14 15]]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章