简要背景:许多(大多数?)当代广泛使用的编程语言至少有一些共同的adt[抽象数据类型],特别是,
字符串(由字符组成的序列)
List(值的有序集合)和
基于映射的类型(将键映射到值的无序数组)
在R编程语言中,前两者分别实现为字符和向量。
当我开始学习R时,有两件事几乎从一开始就很明显:list是R中最重要的数据类型(因为它是R data.frame的父类),其次,我就是不理解它们是如何工作的,至少在我的代码中不能正确地使用它们。
首先,在我看来,R的列表数据类型是映射ADT的直接实现(Python中的字典,Objective C中的NSMutableDictionary, Perl和Ruby中的散列,Javascript中的对象文字,等等)。
例如,创建它们就像创建Python字典一样,通过将键值对传递给构造函数(在Python中是dict而不是list):
x = list("ev1"=10, "ev2"=15, "rv"="Group 1")
访问R List中的项就像访问Python字典中的项一样,例如x['ev1']。同样,你可以通过以下方法检索“键”或“值”:
names(x) # fetch just the 'keys' of an R list
# [1] "ev1" "ev2" "rv"
unlist(x) # fetch just the 'values' of an R list
# ev1 ev2 rv
# "10" "15" "Group 1"
x = list("a"=6, "b"=9, "c"=3)
sum(unlist(x))
# [1] 18
但R列表也不同于其他地图类型的adt(从我学过的语言中)。我猜这是S的初始规范的结果,也就是说,打算从头开始设计一种数据/统计DSL[领域特定语言]。
R列表与其他广泛使用的语言中的映射类型之间的三个显著区别(例如:Python, Perl, JavaScript):
首先,R中的列表是一个有序的集合,就像向量一样,即使值是键(即键可以是任何可哈希的值,而不仅仅是连续的整数)。其他语言中的映射数据类型几乎总是无序的。
其次,即使在调用函数时没有传入列表,即使返回列表的函数不包含(显式)列表构造函数,也可以从函数返回列表(当然,在实践中可以通过调用unlist来包装返回的结果来处理这个问题):
x = strsplit(LETTERS[1:10], "") # passing in an object of type 'character'
class(x) # returns 'list', not a vector of length 2
# [1] list
R列表的第三个特殊特性:它们似乎不能成为另一个ADT的成员,如果您尝试这样做,那么主容器将被强制转换为列表。例如,
x = c(0.5, 0.8, 0.23, list(0.5, 0.2, 0.9), recursive=TRUE)
class(x)
# [1] list
我在这里的意图不是批评这种语言或它是如何被记录的;同样,我并不是说列表数据结构或它的行为有任何问题。我所追求的是纠正我对它们如何工作的理解,这样我就可以在我的代码中正确地使用它们。
以下是我想更好地理解的事情:
What are the rules which determine when a function call will return a list (e.g., strsplit expression recited above)?
If I don't explicitly assign names to a list (e.g., list(10,20,30,40)) are the default names just sequential integers beginning with 1? (I assume, but I am far from certain that the answer is yes, otherwise we wouldn't be able to coerce this type of list to a vector w/ a call to unlist.)
Why do these two different operators, [], and [[]], return the same result?
x = list(1, 2, 3, 4)
both expressions return "1":
x[1]
x[[1]]
why do these two expressions not return the same result?
x = list(1, 2, 3, 4)
x2 = list(1:4)
请不要给我指R文档(?我已经仔细阅读了它,它并不能帮助我回答我刚才背诵的那种问题。
(最后,我最近学习并开始使用一个名为hash的R包(在CRAN上可用),它通过S4类实现了传统的映射类型行为;我当然可以推荐这套套餐。)
只是为了解决你问题的最后一部分,因为这确实指出了R中列表和向量之间的区别:
为什么这两个表达式不返回相同的结果?
X = list(1,2,3,4);X2 = list(1:4)
列表可以包含任何其他类作为每个元素。所以你可以有一个列表,其中第一个元素是一个字符向量,第二个是一个数据帧,等等。在本例中,您创建了两个不同的列表。X有四个向量,每个向量的长度都是1。X2有一个长度为4的向量:
> length(x[[1]])
[1] 1
> length(x2[[1]])
[1] 4
这是完全不同的列表。
R列表非常类似于哈希映射数据结构,因为每个索引值都可以与任何对象相关联。下面是一个简单的列表示例,它包含3个不同的类(包括一个函数):
> complicated.list <- list("a"=1:4, "b"=1:3, "c"=matrix(1:4, nrow=2), "d"=search)
> lapply(complicated.list, class)
$a
[1] "integer"
$b
[1] "integer"
$c
[1] "matrix"
$d
[1] "function"
假设最后一个元素是搜索函数,我可以这样调用它:
> complicated.list[["d"]]()
[1] ".GlobalEnv" ...
作为最后的评论:应该注意data.frame实际上是一个列表(来自data.frame文档):
数据帧是一组行数相同且行名唯一的变量,给定类"data.frame"
这就是为什么data.frame中的列可以有不同的数据类型,而矩阵中的列则不能。举个例子,在这里我尝试用数字和字符创建一个矩阵:
> a <- 1:4
> class(a)
[1] "integer"
> b <- c("a","b","c","d")
> d <- cbind(a, b)
> d
a b
[1,] "1" "a"
[2,] "2" "b"
[3,] "3" "c"
[4,] "4" "d"
> class(d[,1])
[1] "character"
请注意,我无法将第一列的数据类型更改为数字,因为第二列有字符:
> d[,1] <- as.numeric(d[,1])
> class(d[,1])
[1] "character"
虽然这是一个相当老的问题,但我必须说,它正好触及了我在R的第一步中所缺失的知识——即如何将我手中的数据表示为R中的对象,或者如何从现有的对象中进行选择。对于一个R新手来说,从一开始就“在R盒子里”思考并不容易。
所以我自己开始使用下面的拐杖,这帮助我找到什么对象使用什么数据,基本上想象现实世界的用法。
虽然我没有给出问题的确切答案,但下面的简短文本可能会帮助那些刚刚以R开头并提出类似问题的读者。
Atomic vector ... I called that "sequence" for myself, no direction, just sequence of same types. [ subsets.
Vector ... the sequence with one direction from 2D, [ subsets.
Matrix ... bunch of vectors with the same length forming rows or columns, [ subsets by rows and columns, or by sequence.
Arrays ... layered matrices forming 3D
Dataframe ... a 2D table like in excel, where I can sort, add or remove rows or columns or make arit. operations with them, only after some time I truly recognized that data frame is a clever implementation of list where I can subset using [ by rows and columns, but even using [[.
List ... to help myself I thought about the list as of tree structure where [i] selects and returns whole branches and [[i]] returns item from the branch. And because it is tree like structure, you can even use an index sequence to address every single leaf on a very complex list using its [[index_vector]]. Lists can be simple or very complex and can mix together various types of objects into one.
所以对于列表,你可以根据情况选择更多的方法,就像下面的例子。
l <- list("aaa",5,list(1:3),LETTERS[1:4],matrix(1:9,3,3))
l[[c(5,4)]] # selects 4 from matrix using [[index_vector]] in list
l[[5]][4] # selects 4 from matrix using sequential index in matrix
l[[5]][1,2] # selects 4 from matrix using row and column in matrix
这种思维方式对我帮助很大。
这是一个非常老的问题,但我认为一个新的答案可能会增加一些价值,因为在我看来,没有人直接解决OP中的一些问题。
不管接受的答案是什么,R中的列表对象不是哈希映射。如果你想与python并行,列表更像是python列表(实际上是元组)。
最好描述大多数R对象是如何内部存储的(R对象的C类型是SEXP)。它们主要由三部分组成:
一个头,声明对象的R类型,长度和其他一些元数据;
数据部分,这是一个标准的C堆分配数组(连续的内存块);
属性,它是指向其他R对象的指针的命名链表(如果对象没有属性,则为NULL)。
从内部的角度来看,例如,列表和数字向量之间几乎没有区别。它们存储的值是不同的。让我们把两个对象分解到之前描述的范例中:
x <- runif(10)
y <- list(runif(10), runif(3))
x:
头文件会说类型是数字(c端是REALSXP),长度是10等等。
数据部分将是一个包含10个双精度值的数组。
属性为NULL,因为对象没有任何属性。
y:
头文件会说类型是list (c端是VECSXP),长度是2等等。
数据部分将是一个包含两个指向两种SEXP类型的指针的数组,分别指向由runif(10)和runif(3)获得的值。
属性是NULL,就像x一样。
所以数字向量和列表之间的唯一区别是数字数据部分由双值组成,而对于列表,数据部分是指向其他R对象的指针数组。
名字会怎么样呢?名字只是你可以分配给对象的一些属性。让我们看看下面的对象:
z <- list(a=1:3, b=LETTERS)
头文件会说类型是list (c端是VECSXP),长度是2等等。
数据部分将是一个数组,包含2个指向两种SEXP类型的指针,分别指向通过1:3和字母获得的值。
属性现在出现了,是一个名称组件,它是一个字符R对象,值为c("a","b")。
在R级别,您可以使用attributes函数检索对象的属性。
R中典型哈希映射的键-值只是一种假象。当你说:
z[["a"]]
事情是这样的:
the [[ subset function is called;
the argument of the function ("a") is of type character, so the method is instructed to search such value from the names attribute (if present) of the object z;
if the names attribute isn't there, NULL is returned;
if present, the "a" value is searched in it. If "a" is not a name of the object, NULL is returned;
if present, the position of the first occurence is determined (1 in the example). So the first element of the list is returned, i.e. the equivalent of z[[1]].
键值搜索是相当间接的,并且总是位置搜索。另外,记住:
在哈希映射中,键必须具有的唯一限制是它必须是可哈希的。R中的名称必须是字符串(字符向量);
在哈希映射中,不能有两个相同的键。在R中,你可以用重复的值给一个对象分配名称。例如:
name (y) <- c("same", "same")
在r中完全有效。当您尝试y[["same"]]时,会检索到第一个值。现在你应该知道原因了。
总之,给对象赋予任意属性的能力会让您从外部角度看到不同的外观。但是R列表并不是哈希映射。