5.2 列表

不严格地说,列表是一个向量,其中每个元素的类型可以不同。本节谈一谈如何创建、索引和操作列表。

5.2.1 创建列表

列表由list函数创建,且能像c函数那样指定内容。你只需简单地用逗号分隔每个参数即可指定列表中的内容。列表中的元素变量的类型不限,可以是向量、矩阵,甚至函数:

  1. (a_list <- list(
  2. c(1, 1, 2, 5, 14, 42), #请查看http://oeis.org/A000108
  3. month.abb,
  4. matrix(c(3, -8, 1, -3), nrow = 2),
  5. asin
  6. ))
  7. ##[[1]]
  8. ##[1] 1 1 2 5 14 42
  9. ##
  10. ##[[2]]
  11. ## [1] "Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec"
  12. ##
  13. ##[[3]]
  14. ## [,1] [,2]
  15. ##[1,] 3 1
  16. ##[2,] -8 -3
  17. ##
  18. ##[[4]]
  19. ##function (x) .Primitive("asin")

与向量的命名类似,你可以在构造列表时就给元素命名,或在构造之后使用names函数命名:

  1. names(a_list) <- c("catalan", "months", "involutary", "arcsin")
  2. a_list
  3. ##$catalan
  4. ##[1] 1 1 2 5 14 42
  5. ##
  6. ##$months
  7. ## [1] "Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec"
  8. ##
  9. ##$involutary
  10. ## [,1] [,2]
  11. ##[1,] 3 1
  12. ##[2,] -8 -3
  13. ##
  14. ##$arcsin
  15. ##function (x) .Primitive("asin")
  16. (the_same_list <- list(
  17. catalan = c(1, 1, 2, 5, 14, 42),
  18. months = month.abb,
  19. involutary = matrix(c(3, -8, 1, -3), nrow = 2),
  20. arcsin = asin
  21. ))
  22. ##$catalan
  23. ##[1] 1 1 2 5 14 42
  24. ##
  25. ##$months
  26. ## [1] "Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec"
  27. ##
  28. ##$involutary
  29. ## [,1] [,2]
  30. ##[1,] 3 1
  31. ##[2,] -8 -3
  32. ##
  33. ##$arcsin
  34. ##function (x) .Primitive("asin")

尽管这并非是强制性的,但你在给元素命名时最好使用有效的变量名。

你甚至可以把列表作为一个列表的元素:

  1. (main_list <- list(
  2. middle_list = list(
  3. element_in_middle_list = diag(3),
  4. inner_list = list(
  5. element_in_inner_list = pi ^ 1:4,
  6. another_element_in_inner_list = "a"
  7. )
  8. ),
  9. element_in_main_list = log10(1:10)
  10. ))
  11. ##$middle_list
  12. ##$middle_list$element_in_middle_list
  13. ## [,1] [,2] [,3]
  14. ##[1,] 1 0 0
  15. ##[2,] 0 1 0
  16. ##[3,] 0 0 1
  17. ##
  18. ##$middle_list$inner_list
  19. ##$middle_list$inner_list$element_in_inner_list
  20. ##[1] 3.141593
  21. ##
  22. ##$middle_list$inner_list$another_element_in_inner_list
  23. ##[1] "a"
  24. ##
  25. ##
  26. ##
  27. ##$element_in_main_list
  28. ## [1] 0.0000000 0.3010300 0.4771213 0.6020600 0.6989700 0.7781513 0.8450980
  29. ## [8] 0.9030900 0.9542425 1.0000000

从理论上讲,你可以一直这样嵌套下去。实际上,一旦嵌套列表达到成千上万层(确切的数字在不同机器上有所不同),当前版本的R会抛出一个错误。幸好,在现实中这不成问题,因为嵌套超过三、四层的代码是极少见的。

5.2.2 原子变量和递归变量

由于列表具有这种把其他列表包含在内的能力,它被认为是递归变量。与之相对,向量、矩阵和数组则是原子变量(变量或是递归的,或是原子的,从不两者兼具。附录A的表解释了哪些变量类型是原子的,哪些是递归的)。函数is.recursiveis.atomic可测试它们的类型:

  1. is.atomic(list())
  2. ## [1] FALSE
  3. is.recursive(list())
  4. ## [1] TRUE
  5. is.atomic(numeric())
  6. ## [1] TRUE
  7. is.recursive(numeric())
  8. ## [1] FALSE

5.2.3 列表的维度和算术运算

列表与向量一样也有长度,其长度是它顶层元素的数目:

  1. length(a_list)
  2. ## [1] 4
  3. length(main_list) #不包括嵌套列表的长度
  4. ## [1] 2

仍与向量类似但异于矩阵的是,列表没有维度。因此,把dim函数作用于列表上会返回NULL

  1. dim(a_list)
  2. ## NULL

nrowNROW及相应的列函数作用于列表时,与作用于矢量上的原理基本相同:

  1. nrow(a_list)
  2. ## NULL
  3. ncol(a_list)
  4. ## NULL
  5. NROW(a_list)
  6. ## [1] 4
  7. NCOL(a_list)
  8. ## [1] 1

与向量不同的是,算术运算对列表不起作用。由于每个元素的类型可以不同,将两个列表相加或相乘没有任何意义。然而,如果两个列表中的元素类型相同,则可对它们进行算术运算。在这种情况下,一般的算术运算法都适用。例如:

  1. l1 <- list(1:5)
  2. l2 <- list(6:10)
  3. l1[[1]] + l2[[1]]
  4. ## [1] 7 9 11 13 15

更常见的是,你可能要对列表中的每一个元素执行算术运算(或其他操作)。这需要使用循环,第8章中详细介绍。

5.2.4 索引列表

考虑这个测试列表:

  1. l <- list(
  2. first = 1,
  3. second = 2,
  4. third = list(
  5. alpha = 3.1,
  6. beta = 3.2
  7. )
  8. )

与向量类似,我们可通过方括号[]、正或负的下标数字、元素名称或逻辑索引这四种方法访问列表中的元素。以下四行代码的结果相同:

  1. l[1:2]
  2. ## $first
  3. ## [1] 1
  4. ##
  5. ## $second
  6. ## [1] 2
  7. l[-3]
  8. ## $first
  9. ## [1] 1
  10. ##
  11. ## $second
  12. ## [1] 2
  13. l[c("first", "second")]
  14. ## $first
  15. ## [1] 1
  16. ##
  17. ## $second
  18. ## [1] 2
  19. l[c(TRUE, TRUE, FALSE)]
  20. ## $first
  21. ## [1] 1
  22. ##
  23. ## $second
  24. ## [1] 2

这些索引操作催生了另一个列表。有时,我们要访问的是列表元素中的内容。有两个操作符能帮助我们做到这一点:给双方括号[[ ]]传入正整数,它代表返回的索引下标,或指定该元素的名称字符串:

  1. l[[1]]
  2. ## [1] 1
  3. l[["first"]]
  4. ## [1] 1

如果输入的是一个列表,is.list函数将返回TRUE,否则将返回FALSE。作为比较,观察以下两个索引操作符:

  1. is.list(l[1])
  2. ## [1] TRUE
  3. is.list(l[[1]])
  4. ## [1] FALSE

对于列表中的命名元素,我们也可以使用美元符号运算符$。这与向双方括号传入一个命名字符串的工作方式几乎一样,且另有两个好处。首先,许多IDE能自动补全名字(在R的GUI下,按下Tab键可实现此功能)。其次,R能接受部分匹配的元素名称:

  1. l$first
  2. ## [1] 1
  3. l$f # 部分匹配将“f”解释为“first”
  4. ## [1] 1

我们可以通过嵌套方括号或传入向量来访问嵌套元素,尽管后者不常用且往往难以阅读:

  1. l[["third"]]["beta"]
  2. ## $beta
  3. ## [1] 3.2
  4. l[["third"]][["beta"]]
  5. ## [1] 3.2
  6. l[[c("third", "beta")]]
  7. ## [1] 3.2

当尝试访问不存在的元素列表时,根据你所使用的索引类型,行为会有所不同。在下例中,回想一下列表l,它只有三个元素。

如果我们使用单方括号的索引,那么将返回只带一个NULL元素的列表(且名称为NA,如原列表有名字的话)。与之相比,对于一个无效的向量索引,其返回值将是NA

  1. l[c(4, 2, 5)]
  2. ## $<NA>
  3. ## NULL
  4. ##
  5. ## $second
  6. ## [1] 2
  7. ##
  8. ## $<NA>
  9. ## NULL
  10. l[c("fourth", "second", "fifth")]
  11. ## $<NA>
  12. ## NULL
  13. ##
  14. ## $second
  15. ## [1] 2
  16. ##
  17. ## $<NA>
  18. ## NULL

无论是以双方括号还是美元符号来访问元素的内容,如果名字错误,都将返回NULL

  1. l[["fourth"]]
  2. ## NULL
  3. l$fourth
  4. ## NULL

最后,如果以错误的数字索引访问元素内容,则会抛出一个错误,说明下标越界。对于这种不一致的行为,你只能接受,而最好的防范措施是确保在使用前对索引作相应的检查:

  1. l[[4]] #这里会抛出一个错误

5.2.5 向量和列表之间的转换

向量可使用as.list函数来转换成列表。所创建的可见列表与向量中元素的值一一对应:

  1. busy_beaver <- c(1, 6, 21, 107) #请查看http://oeis.org/A060843
  2. as.list(busy_beaver)
  3. ## [[1]]
  4. ## [1] 1
  5. ##
  6. ## [[2]]
  7. ## [1] 6
  8. ##
  9. ## [[3]]
  10. ## [1] 21
  11. ##
  12. ## [[4]]
  13. ## [1] 107

如果列表中每个元素都是标量值,则亦可使用之前出现过的函数(as.numericas.character等)将其转换成向量:

  1. as.numeric(list(1, 6, 21, 107))
  2. ## [1] 1 6 21 107

如果列表中包含非标量元素,这种方法将不起作用。这真的是一个问题,因为列表既能存储不同类型的数据亦可存储相同类型的数据,但后者不是存储成矩阵的形式:

  1. (prime_factors <- list( two = 2,
  2. three = 3,
  3. four = c(2, 2),
  4. five = 5,
  5. six = c(2, 3),
  6. seven = 7,
  7. eight = c(2, 2, 2),
  8. nine = c(3, 3),
  9. ten = c(2, 5)
  10. ))
  11. ## $two
  12. ## [1] 2
  13. ##
  14. ## $three
  15. ## [1] 3
  16. ##
  17. ## $four
  18. ## [1] 2 2
  19. ##
  20. ## $five
  21. ## [1] 5
  22. ##
  23. ## $six
  24. ## [1] 2 3
  25. ##
  26. ## $seven
  27. ## [1] 7
  28. ##
  29. ## $eight
  30. ## [1] 2 2 2
  31. ##
  32. ## $nine
  33. ## [1] 3 3
  34. ##
  35. ## $ten
  36. ## [1] 2 5

对于这样的列表,可用unlist函数将其转换为向量(对于混合类型的列表,有时技术上可行,但没什么用):

  1. unlist(prime_factors)
  2. ## two three four1 four2 five six1 six2 seven eight1 eight2 eight3
  3. ## 2 3 2 2 5 2 3 7 2 2 2
  4. ## nine1 nine2 ten1 ten2
  5. ## 3 3 2 5

5.2.6 组合列表

c函数既能用于拼接向量,亦能用于拼接列表:

  1. c(list(a = 1, b = 2), list(3))
  2. ## $a
  3. ## [1] 1
  4. ##
  5. ## $b
  6. ## [1] 2
  7. ##
  8. ## [[3]]
  9. ## [1] 3

如果我们用它来拼接列表和向量,向量在拼接之前将被转换为列表(就像调用了as.list函数):

  1. c(list(a = 1, b = 2), 3)
  2. ## $a
  3. ## [1] 1
  4. ##
  5. ## $b
  6. ## [1] 2
  7. ##
  8. ## [[3]]
  9. ## [1] 3

亦可对列表使用cbindrbind函数,但结果中出现的对象相当奇怪。它们或是含有可能为非标量元素的矩阵,或是有维度的列表,这取决于你看它们的方式:

  1. (matrix_list_hybrid <- cbind(
  2. list(a = 1, b = 2),
  3. list(c = 3, list(d = 4))
  4. ))
  5. ## [,1] [,2]
  6. ##a 1 3
  7. ##b 2 List,1
  8. str(matrix_list_hybrid)
  9. ## List of 4
  10. ## $ : num 1
  11. ## $ : num 2
  12. ## $ : num 3
  13. ## $ :List of 1
  14. ## ..$ d: num 4
  15. ## - attr(*, "dim")= int [1:2] 2 2
  16. ## - attr(*, "dimnames")=List of 2
  17. ## ..$ : chr [1:2] "a" "b"
  18. ## ..$ : NULL

在此,你应避免常用cbindrbind,最好完全不用它们。这再次证明R有点过于灵活和包容了,否则它会直接抛出一个错误表明你干了蠢事。