提问



简要背景:广泛使用的许多(大多数?)当代编程语言至少有一些共同的ADT [[抽象数据类型]],特别是



  • 字符串(由字符组成的序列)

  • 列表(有序的值集合)和

  • 基于地图的类型(将键映射到值的无序数组)



在R编程语言中,前两个分别实现为charactervector


当我开始学习R时,几乎从一开始就有两件事情是显而易见的:list是R中最重要的数据类型(因为它是R data.frame的父类),其次,我只是无法理解它们是如何工作的,至少不能很好地在我的代码中正确使用它们。


首先,在我看来,Rs list数据类型是地图ADT的简单实现([[dictionary在Python中,NSMutableDictionary在目标C中,hash在Perl和Ruby中,object literal在Javascript中,等等。


例如,您可以像创建Python字典一样创建它们,方法是将键值对传递给构造函数(在Python中dict而不是list):


x = list("ev1"=10, "ev2"=15, "rv"="Group 1")


并且您可以像访问Python字典那样访问R List的项目,例如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 list s也 不像 其他地图类型的ADT(从我学到的语言中)。我的猜测是这是一个结果S的初始规范,即打算从头开始设计数据/统计DSL [[特定领域语言]]。


三个 R list与广泛使用的其他语言中的映射类型之间的显着差异(例如,Python,Perl,JavaScript):


R中的第一个,list s是一个有序集合,就像向量一样,即使值是键控的(即,键可以是任何可清除值)只是顺序整数)。几乎总是,其他语言中的映射数据类型是无序。


second ,list s可以从函数返回,即使你在调用函数时从未传入list,而即使函数也是如此返回list并不包含(显式)list构造函数(当然,你可以通过将返回的结果包装到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 [[s list的一个第三特有的特征:看起来它们似乎不能成为另一个ADT的成员,如果你试图这样做,那么主容器就会被强制转换为a list。例如。,


x = c(0.5, 0.8, 0.23, list(0.5, 0.2, 0.9), recursive=TRUE)

class(x)
# [1] list


我的意图不是批评语言或如何记录;同样,我并不是说list数据结构或它的行为方式有什么问题。我所追求的是我对它们如何工作的理解,所以我可以在我的代码中正确使用它们。


以下是我想要更好理解的各种事情:



  • 确定何时函数调用将返回list(例如,上述strsplit表达式)的规则是什么?

  • 如果我没有明确地为list指定名称(例如,list(10,20,30,40))是默认名称只是从1开始的连续整数?(我假设,但我很不确定答案是肯定的,否则我们就不能将这种类型的list强制转换为调用unlist的向量。)

  • 为什么这两个不同的运算符[][****]会返回相同的结果?


    x = list(1, 2, 3, 4)


    两个表达式都返回1:


    x[1]


    x[**1**]

  • 为什么这两个表达式会返回相同的结果?


    x = list(1, 2, 3, 4)


    x2 = list(1:4)



请不要指向R文档(?listR-intro) - 我仔细阅读了它,但它无法帮我回答上面所述的问题类型。[132]]] [133]


(最后,我最近了解并开始使用名为hash的R包(在CRAN上可用),它通过S4类实现传统的映射类型行为;我当然可以推荐这个包。 )[134]

最佳参考


只是为了解决你问题的最后一部分,因为这确实指出了R中listvector之间的区别:



  为什么这两个表达式不会返回相同的结果?

  
  x =列表(1,2,3,4); x2 =清单(1:4)



列表可以包含任何其他类作为每个元素。因此,您可以拥有一个列表,其中第一个元素是字符向量,第二个元素是数据框等。在这种情况下,您创建了两个不同的列表。 x有四个向量,每个长度为1. x2有1个长度为4的向量:


> length(x[**1**])
[1] 1
> length(x2[**1**])
[1] 4


所以这些是完全不同的列表。


R列表非常类似于散列映射数据结构,因为每个索引值可以与任何对象相关联。这是一个包含3个不同类(包括函数)的列表的简单示例:[135]


> 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"

其它参考1


关于你的问题,让我按顺序解决它们并给出一些例子:


1 )如果return语句添加一个列表,则返回一个列表。考虑


 R> retList <- function() return(list(1,2,3,4)); class(retList())
 [1] "list"
 R> notList <- function() return(c(1,2,3,4)); class(notList())
 [1] "numeric"
 R> 


2 )名称根本没有设置:


R> retList <- function() return(list(1,2,3,4)); names(retList())
NULL
R> 


3 )他们不会返回相同的东西。你的例子给出了


R> x <- list(1,2,3,4)
R> x[1]
[**1**]
[1] 1
R> x[**1**]
[1] 1


其中x[1]返回x的第一个元素 - 与x相同。每个标量都是长度为1的向量。另一方面x[**1**]返回列表的第一个元素。


4 )最后,两者在它们分别创建包含四个标量的列表和包含单个元素的列表(恰好是四个元素的向量)之间是不同的。

其它参考2


只是为了解答你的一小部分问题:


这篇关于索引的文章解决了[][****]之间差异的问题。[136]


简而言之[[]]从列表中选择单个项目,[]返回所选项目的列表。在您的示例中,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

其它参考3


列出工作原因(有序)的一个原因是解决了对任何节点可以包含任何类型的有序容器的需求,这些向量不能执行。列表在R中被重复用于各种目的,包括形成data.frame的基数,data.frame是任意类型(但长度相同)的向量列表。


为什么这两个表达式不会返回相同的结果?


x = list(1, 2, 3, 4); x2 = list(1:4)


要添加@Shane的答案,如果您想获得相同的结果,请尝试:


x3 = as.list(1:4)


将矢量1:4强制转换为列表。

其它参考4


只是为此添加一点:


R确实具有与hash包中的Python dict相同的数据结构。您可以在Open Data Group的博客文章中阅读相关内容。这是一个简单的例子:[137] [138]


> 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


在可用性方面,hash类与列表非常相似。但是对于大型数据集,性能更好。

其它参考5


你说:



  另一方面,可以返回列表
  从功能,即使你从来没有
  当你打电话给我时,在列表中传递
  功能,即使功能
  不包含List构造函数,
  例如。,



x = strsplit(LETTERS[1:10], "") # passing in an object of type 'character'
class(x)
# => 'list'


我猜你认为这是一个问题(?)。我在这里告诉你为什么它不是问题:-)。您的示例有点简单,因为当您执行字符串拆分时,您有一个包含1个元素长的元素的列表,因此您知道x[**1**]unlist(x)[1]相同。但是如果strsplit的结果返回每个bin中不同长度的结果会怎样。简单地返回一个向量(对比一个列表)根本不会做。


例如:


stuff <- c("You, me, and dupree",  "You me, and dupree",
           "He ran away, but not very far, and not very fast")
x <- strsplit(stuff, ",")
xx <- unlist(strsplit(stuff, ","))


在第一种情况下(x:返回一个列表),你可以知道第三个字符串的第二个部分是什么,例如:x[**3**][2]。既然结果已被解开(unlist - ed,你怎么能用xx做同样的事?

其它参考6


x = list(1, 2, 3, 4)
x2 = list(1:4)
all.equal(x,x2)


是不一样的,因为1:4与c(1,2,3,4)相同。
如果你想要它们是相同的那么:


x = list(c(1,2,3,4))
x2 = list(1:4)
all.equal(x,x2)

其它参考7


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



  1. 向量是R的原子。例如,rpois(1e4,5)(5个随机数),numeric(55)(长度-55零向量超过双倍)和character(12)(12个空字符串),都是基本的。

  2. 列表或向量可以有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
    

  3. 向量要求所有内容都是相同的数据类型。看这个:


    > 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
    

  4. 列表可以包含不同的数据类型,如其他答案和OP的问题本身所示。



我已经看过语言(ruby,javascript),其中数组可能包含可变数据类型,但是例如在C ++中数组必须是完全相同的数据类型。我相信这是速度/效率的事情:如果你有[[numeric(1e6)你知道它的大小和每个元素先验的位置;如果这个东西可能在某个未知切片中包含"Flying Purple People Eaters",那么你必须实际解析东西才能知道基本事实关于它。


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





至于你的第二个问题:



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



函数返回的数据类型不同于一直重新输入。plot返回一个绘图,即使它没有将绘图作为输入。 Arg即使接受了complex,也会返回numeric。等等。


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

其它参考8


如果它有帮助,我倾向于将R中的列表设想为其他前OO语言中的记录:



  • 他们不会对总体类型做出任何假设(或者更确切地说,任何arity和字段名称的所有可能记录的类型都可用)。

  • 他们的字段可以是匿名的(然后您可以通过严格的定义顺序访问它们。)



记录这个名称会与数据库用语中记录(又名行)的标准含义冲突,这也就是他们的名字建议的原因:作为列表(字段)。

其它参考9


尽管这是一个非常古老的问题,但我必须说它完全触及了我在R的第一步中所缺少的知识 - 即如何将我手中的数据表示为R中的对象或如何从现有对象中进行选择。 R新手从一开始就认为在R盒子里是不容易的。


所以我自己开始使用下面的拐杖帮助我找到了什么对象用于什么数据,并基本上想象真实世界的用法。


虽然我没有给出问题的确切答案,但下面的简短文字可能会帮助那些刚刚开始使用R并且正在提出类似问题的读者。



  • 原子载体......我称自己为序列,没有方向,只是相同类型的序列。 [子集。

  • Vector ...来自2D,[子集的一个方向的序列。

  • 矩阵...具有相同长度的矢量束形成行或列,[子行按行和列,或按顺序。

  • 阵列...形成3D的分层矩阵

  • Dataframe ...像excel一样的2D表格,我可以在其中排序,添加或删除行或列或制作arit。与它们一起操作,只是经过一段时间我才真正认识到数据帧是list的巧妙实现,我可以按行和列使用[进行子集,但即使使用[**

  • 列表...帮助自己我想到了tree structure的列表,[i]选择并返回整个分支,[**i**]从分支返回项目。因为它是tree like structure,你甚至可以使用index sequence使用[**index_vector**]来处理非常复杂的list上的每一片叶子。列表可以简单或非常复杂,可以将各种类型的对象混合在一起。



因此,对于lists,您可以根据情况选择leaf的更多方法,如下例所示。


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


这种思维方式对我帮助很大。

其它参考10


为什么这两个不同的运算符[ ][** **]会返回相同的结果?


x = list(1, 2, 3, 4)



  1. [ ]提供子设置操作。一般来说任何对象的子集
    将与原始对象具有相同的类型。因此,x[1]
    提供一份清单。类似地x[1:2]是原始列表的子集,
    因此它是一个清单。防爆。


    x[1:2]
    
    [**1**] [1] 1
    
    [**2**] [1] 2
    

  2. [** **]用于从列表中提取元素。 x[**1**]有效
    并从列表中提取第一个元素。 x[**1:2**]无效[** **]
    不提供像[ ]这样的子设置。


     x[**2**] [1] 2 
    
    > x[**2:3**] Error in x[**2:3**] : subscript out of bounds