元组(tuple)

向量按存储的数据类型可以分为元组和强类型向量。强类型向量要求所有元素是具有相同的数据类型的标量,因此强类型向量具有明确的数据类型。而元组的存储对象可以是标量或向量,其类型可以相同或不同,因此元组没有明确的数据类型,DolphinDB 定义元组的数据类型为 ANY。

元组的声明符号是圆括号,如:”(1, 2, 3)”。用方括号声明强类型向量时,若其中包含不同的数据类型或包含向量,系统会自动将其转换为一个元组,如:”[1, ‘A’]”,”[[1,2,3], [4,5]]”。在本章后面将会提到,可以使用方括号来构造元组。

创建元组

(1) 圆括号内用逗号分隔定义。使用这种方式,无论元素的类型是否相同,一定会生成一个元组。

$ a=(1 2 3, `IBM`MSFT`GOOG, 2.5);
$ a;
([1,2,3],["IBM","MSFT","GOOG"],2.5)

$ a=(1, 2, 3);
$ typestr a;
ANY VECTOR

$ x=(1..10, `GOOG);
$ x;
([1,2,3,4,5,6,7,8,9,10],"GOOG")

$ x=1..10;
$ y=rand(100,10);
$ a=(x+y,x-y);
$ a;
([73,26,32,96,26,86,11,63,21,49],[-71,-22,-26,-88,-16,-74,3,-47,-3,-29])

可以用()来生成一个空的元组。

$ x=();
$ x.append!(1..3);
([1,2,3])
$ x.append!(`GS);
([1,2,3],"GS")

$ x=(1,2,3);
$ x;
(1,2,3)
$ x.append!(`GS);
(1,2,3,"GS")

不可使用 append! 函数将强类型向量转换为元组。

$ x=1 2 3;
$ x;
[1,2,3]
$ x.append!(`GS);
Incompatible type. Expected: INT, Actual: STRING

(2) 用空格分隔不同类型的数据,或者在方括号内使用逗号分隔不同类型的数据。方括号 “[]” 首先会尝试创建一个强类型向量。如果不成功,则会创建一个元组。

$ a = 3 2012.02.03 `GOOG;
(3,2012.02.03,"GOOG")
$ typestr a;
ANY VECTOR

$ a=[[1,2,3,4,5,6,7,8,9,10],2,[`IBM, `MSFT,`GOOG]];
$ a;
([1,2,3,4,5,6,7,8,9,10],2,["IBM","MSFT","GOOG"])

$ x=1..10;
$ y=rand(100,10);
$ b=[x+y,x-y];
$ b;
([73,26,32,96,26,86,11,63,21,49],[-71,-22,-26,-88,-16,-74,3,-47,-3,-29])

(3) 使用函数 array 将第一个输入变量设为 ANY,然后定义每个元素。

$ a=array(ANY, 3);
$ a[0]=1..10;
$ a[1]=2;
$ a[2]=`IBM`MSFT`GOOG;
$ a;
([1,2,3,4,5,6,7,8,9,10],2,["IBM","MSFT","GOOG"])

访问元组

$ x=(1 2 3 4 5 6 7 8 9 10,(5 7 8, 11 3 5));
$ x;
([1,2,3,4,5,6,7,8,9,10],([5,7,8],[11,3,5]))

$ x[1];
([5,7,8],[11,3,5])

$ x[1,1];
[11,3,5]

$ x[1,1,1];
3

$ x[0 1];
([1,2,3,4,5,6,7,8,9,10],([5,7,8],[11,3,5]))

$ x[0 1,1];
(2,[11,3,5])

$ x[, 1];
(2,[11,3,5])

$ x[1, ,0 2];
([5,8],[11,5])

$ x[,1,1];
(2,3)

$ x[1 2];
(([5,7,8],[11,3,5]),)

修改元组

我们可以向一个元组中添加新元素,也可将新的对象赋值给元组里的元素。

  1. 向一个元组中添加新元素。

$ x=`C 120`BH 100;
$ x;
("C",120,"BH",100)

$ x.append!("AAPL");
("C",120,"BH",100,"AAPL")

$ x.append!(300);
("C",120,"BH",100,"AAPL",300)

$ x.append!(`IBM 600);
("C",120,"BH",100,"AAPL",300,("IBM",600))
  1. 通过将新的对象赋值给元组里的元素来修改元素值。

$ tp = [["aaa", "bbb"], "ccc"]
$ tp[0] = tp[0].replace(tp[0][1], "A")
$ tp
(["aaa","A"],["ccc"])

涉及元组的计算

元组是用来容纳不同的数据类型,而不是为了高效的计算。即使元组的元素都是数字,仍可使用元组进行计算。

我们可以使用如下函数 max, min, prod, sum, cummin, cummax, cumprod, cumsum 对元组进行计算。

$ a=(2, [3, 5], 10);

$ max a;
[10,10]

$ prod a;
[60,100]

$ cumsum a;
(2,[5,7],[15,17])

我们可以将逻辑或关系运算符应用于元组。

$ a=(2, [3, 5], 10);

$ a>3;
(0,[0,1],1)

$ a==2;
(1,[0,0],0)

$ isNull a;
[0,0,0]

我们可以对元组使用某些运算符,比如 add, sub, mul, div, ratio, mod, powabs.

$ a=(2, [3, 5], 10);

$ a+2;
(4,[5,7],12)

$ a pow 3;
(8,[27,125],1000)

对多个元组赋值

$ x,y=(1 2 3, 2:5);
$ x;
[1,2,3]
$ y;
2 : 5

元组用于函数返回值,返回多个值

$ def foo(a,b){return a+b, a-b};

$ x = foo(15,10);

$ x;
(25,5)

$ typestr x;
ANY VECTOR

特殊的使用场景

实质上,元组的每个元素存储的是对象的地址。当在元组上应用 take 函数进行循环取值生成一个新的元组时,新元组实际拷贝的是原元组各个元素的地址(引用),而非实际的元素值,即进行的是浅拷贝(shallow copy)。

在某些特殊场景下,如更新字典:字典的 value 是通过对一个元组应用 take 函数生成的元组,多个元素实际上指向了同一个对象。此时对某个 key 对应的 value 进行追加,可能会导致其他 key 对应的 value 发生改变。

下例对 key=1 追加一个元素 0,可以发现 key=4 的值也发生了改变:

$ t = [[1,2],[2,3],[3]]
$ d = dict(take(0..10,5),take(t, 5))
$ d;
0->[1,2]
1->[2,3]
2->[3]
3->[1,2]
4->[2,3]


$ d.dictUpdate!(append!,1,0)
$ d;
0->[1,2]
1->[2,3,0]
2->[3]
3->[1,2]
4->[2,3,0]

$ t = [[1,2],[2,3],[3]]
$ d = dict(take(0..10,5),[[1,2],[2,3],[3],[1,2],[2,3]])
$ d;
0->[1,2]
1->[2,3]
2->[3]
3->[1,2]
4->[2,3]

$ d.dictUpdate!(append!,1,0)
$ d;
0->[1,2]
1->[2,3,0]
2->[3]
3->[1,2]
4->[2,3]