元组 (tuple)
向量按存储的数据类型可以分为元组和强类型向量。强类型向量要求所有元素是具有相同的数据类型的标量,因此强类型向量具有明确的数据类型。而元组的存储对象可以是标量或向量,其类型可以相同或不同,因此元组没有明确的数据类型,DolphinDB 定义元组的数据类型为 ANY。
元组的声明符号是圆括号,如:"(1, 2, 3)"。用方括号声明强类型向量时,若其中包含不同的数据类型或包含向量,系统会自动将其转换为一个元组,如:"[1, 'A']","[[1,2,3], [4,5]]"。在本章后面将会提到,可以使用方括号来构造元组。
创建元组
(1) 圆括号内用逗号分隔定义。使用这种方式,无论元素的类型是否相同,一定会生成一个元组。元组内的元素可以跨行书写。
a=(1 2 3, `IBM`MSFT`GOOG, 2.5);
a;
// output: ([1,2,3],["IBM","MSFT","GOOG"],2.5)
// 也可跨行书写式
a=(1 2 3,
`IBM`MSFT`GOOG, 2.5);
a;
// output: ([1,2,3],["IBM","MSFT","GOOG"],2.5)
a=(1, 2, 3);
typestr a;
// output: ANY VECTOR
x=(1..10, `GOOG);
x;
// output: ([1,2,3,4,5,6,7,8,9,10],"GOOG")
x=1..10;
y=rand(100,10);
a=(x+y,x-y);
a;
// output: ([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);
// output: ([1,2,3])
x.append!(`GS);
// output: ([1,2,3],"GS")
x=(1,2,3);
x;
// output: (1,2,3)
x.append!(`GS);
// output: (1,2,3,"GS")
不可使用 append! 函数将强类型向量转换为元组。
x=1 2 3;
x;
// output: [1,2,3]
x.append!(`GS);
// output: Incompatible type. Expected: INT, Actual: STRING
(2) 用空格分隔不同类型的数据,或者在方括号内使用逗号分隔不同类型的数据。方括号 "[]" 首先会尝试创建一个强类型向量。如果不成功,则会创建一个元组。
a = 3 2012.02.03 `GOOG;
// output: (3,2012.02.03,"GOOG")
typestr a;
// output: ANY VECTOR
a=[[1,2,3,4,5,6,7,8,9,10],2,[`IBM, `MSFT,`GOOG]];
a;
// output: ([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;
// output: ([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;
// output: ([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;
// output: ([1,2,3,4,5,6,7,8,9,10],([5,7,8],[11,3,5]))
x[1];
// output: ([5,7,8],[11,3,5])
x[1,1];
// output: [11,3,5]
x[1,1,1];
// output: 3
x[0 1];
// output: ([1,2,3,4,5,6,7,8,9,10],([5,7,8],[11,3,5]))
x[0 1,1];
// output: (2,[11,3,5])
x[, 1];
// output: (2,[11,3,5])
x[1, ,0 2];
// output: ([5,8],[11,5])
x[,1,1];
// output: (2,3)
x[1 2];
// output: (([5,7,8],[11,3,5]),)
修改元组
我们可以向一个元组中添加新元素,也可将新的对象赋值给元组里的元素。
-
向一个元组中添加新元素。
x=`C 120`BH 100; x; // output: ("C",120,"BH",100) x.append!("AAPL"); // output: ("C",120,"BH",100,"AAPL") x.append!(300); // output: ("C",120,"BH",100,"AAPL",300) x.append!(`IBM 600); // output: ("C",120,"BH",100,"AAPL",300,("IBM",600))
-
通过将新的对象赋值给元组里的元素来修改元素值。
tp = [["aaaa", "ccccccc"], ["dddd"]] //将元组 tp 的第一个元素赋值给对象 a a = tp[0] //修改对象 a 的第2个元素值 a[1] = "A" a; // output: ["aaaa","A"] //通过将对象 a 赋值给 tp[0],来修改 tp 里的第一个元素值 tp[0] = a tp // output: (["aaaa","A"],["dddd"])
涉及元组的计算
元组是用来容纳不同的数据类型,而不是为了高效的计算。即使元组的元素都是数字,仍可使用元组进行计算。
我们可以使用如下函数对元组进行计算:
a=(2, [3, 5], 10);
max a;
// output: [10,10]
prod a;
// output: [60,100]
cumsum a;
// output: (2,[5,7],[15,17])
我们可以将逻辑或关系运算符应用于元组。
a=(2, [3, 5], 10);
a>3;
// output: (0,[0,1],1)
a==2;
// output: (1,[0,0],0)
isNull a;
// output: (0,[0,0],0)
我们可以对元组使用某些运算符,比如:
a=(2, [3, 5], 10);
a+2;
// output: (4,[5,7],12)
a pow 3;
// output: (8,[27,125],1000)
对多个元组赋值
x,y=(1 2 3, 2:5);
x;
// output: [1,2,3]
y;
// output: 2 : 5
元组用于函数返回值,返回多个值
def foo(a,b){return a+b, a-b};
x = foo(15,10);
x;
// output: (25,5)
typestr x;
// output: 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;
/* output:
0->[1,2]
1->[2,3]
2->[3]
3->[1,2]
4->[2,3]
*/
d.dictUpdate!(append!,1,0)
d;
/* output:
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;
/* output:
0->[1,2]
1->[2,3]
2->[3]
3->[1,2]
4->[2,3]
*/
d.dictUpdate!(append!,1,0)
d;
/* output:
0->[1,2]
1->[2,3,0]
2->[3]
3->[1,2]
4->[2,3]
*/