简要背景:许多(大多数?)当代广泛使用的编程语言至少有一些共同的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类实现了传统的映射类型行为;我当然可以推荐这套套餐。)


当前回答

这是一个非常老的问题,但我认为一个新的答案可能会增加一些价值,因为在我看来,没有人直接解决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列表并不是哈希映射。

其他回答

就拿你们的一部分问题来说

这篇关于索引的文章解决了[]和[[]]之间的区别问题。

简而言之,[[]]从列表中选择单个项目,[]返回所选项目的列表。在你的例子中,x = list(1,2,3,4)'项目1是一个整数,但x[[1]]返回一个1,而x[1]返回一个只有一个值的列表。

> x = list(1, 2, 3, 4)
> x[1]
[[1]]
[1] 1

> x[[1]]
[1] 1

关于其他语言中的向量和哈希/数组概念:

Vectors are the atoms of R. Eg, rpois(1e4,5) (5 random numbers), numeric(55) (length-55 zero vector over doubles), and character(12) (12 empty strings), are all "basic". Either lists or vectors can have names. > n = numeric(10) > n [1] 0 0 0 0 0 0 0 0 0 0 > names(n) NULL > names(n) = LETTERS[1:10] > n A B C D E F G H I J 0 0 0 0 0 0 0 0 0 0 Vectors require everything to be the same data type. Watch this: > i = integer(5) > v = c(n,i) > v A B C D E F G H I J 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 > class(v) [1] "numeric" > i = complex(5) > v = c(n,i) > class(v) [1] "complex" > v A B C D E F G H I J 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i Lists can contain varying data types, as seen in other answers and the OP's question itself.

我见过一些语言(ruby, javascript),其中的“数组”可能包含变量数据类型,但例如在c++中,“数组”必须是相同的数据类型。我相信这是一个速度/效率的问题:如果你有一个数字(1e6),你可以先验地知道它的大小和每个元素的位置;如果某个未知片段中可能包含“紫飞食人”,那么你就必须真正分析这些内容来了解它的基本事实。

当类型得到保证时,某些标准R操作也更有意义。例如cumsum(1:9)是有意义的,而cumsum(list(1,2,3,4,5,'a',6,7,8,9)则没有意义,因为不能保证类型为double。


关于你的第二个问题:

即使在调用函数时从未传入List,也可以从函数返回List

函数返回的数据类型总是与输入的数据类型不同。Plot返回一个Plot,即使它不接受一个Plot作为输入。Arg返回一个数字,即使它接受了复数。等。

(至于strsplit:源代码在这里。)

这是一个非常老的问题,但我认为一个新的答案可能会增加一些价值,因为在我看来,没有人直接解决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列表并不是哈希映射。

如果有帮助的话,我倾向于把R中的“列表”想象成其他前oo语言中的“记录”:

它们没有对总体类型做任何假设(或者说,所有可能记录的任何arity和字段名的类型都是可用的)。 它们的字段可以是匿名的(然后按照严格的定义顺序访问它们)。

“record”这个名字会与数据库术语中“记录”(又名行)的标准含义相冲突,这可能就是为什么它们的名字本身就暗示了:作为列表(字段)。

再补充一点:

R在散列包中确实有一个与Python dict等效的数据结构。你可以在这篇来自开放数据组的博客文章中读到它。这里有一个简单的例子:

> library(hash)
> h <- hash( keys=c('foo','bar','baz'), values=1:3 )
> h[c('foo','bar')]
<hash> containing 2 key-value pairs.
  bar : 2
  foo : 1

就可用性而言,哈希类非常类似于列表。但对于大型数据集,性能更好。