List mapping

List mapping is to evaluate an expression for each list member. It is the fundamental operation in rlist. Almost all functions in this package that work with expressions are using mapping but in different ways. The following examples demonstrate several types of mapping.

library(rlist)
devs <- 
  list(
    p1=list(name="Ken",age=24,
      interest=c("reading","music","movies"),
      lang=list(r=2,csharp=4,python=3)),
    p2=list(name="James",age=25,
      interest=c("sports","music"),
      lang=list(r=3,java=2,cpp=5)),
    p3=list(name="Penny",age=24,
      interest=c("movies","reading"),
      lang=list(r=1,cpp=4,python=2)))

list.map

The simplest way of mapping is provided by list.map. Basically, it evaluates an expression for each list member.

The function makes it easier to query a list by putting all fields of the list member in mapping to the environment where the expression is evaluated. In other words, the expression is evaluated in the context of one list member each time.

For example, the following code maps each list member in devs by expression age. Therefore, it results in a list where each item becomes the value of that expression for each member of devs.

list.map(devs, age)
# $p1
# [1] 24
# 
# $p2
# [1] 25
# 
# $p3
# [1] 24

Since the expression does not have to be a field name of the list member, we can evaluate whatever we want in the context of a list member.

The following code maps each list member to the sum of years of they use the programming languages they know.

list.map(devs, sum(as.numeric(lang)))
# $p1
# [1] 9
# 
# $p2
# [1] 10
# 
# $p3
# [1] 7

If we need more than one values for each member, we can evaluate a vector or list expression.

The following code maps each list member to a new list of his or her age and range of number of years using a programming language.

list.map(devs, list(age=age,range=range(as.numeric(lang))))
# $p1
# $p1$age
# [1] 24
# 
# $p1$range
# [1] 2 4
# 
# 
# $p2
# $p2$age
# [1] 25
# 
# $p2$range
# [1] 2 5
# 
# 
# $p3
# $p3$age
# [1] 24
# 
# $p3$range
# [1] 1 4

list.mapv

If we want to get the mapping results as a vector rather than a list, we can use list.mapv, which basically calls unlist to the list resulted from list.map.

list.mapv(devs, age)
# p1 p2 p3 
# 24 25 24
list.mapv(devs, sum(as.numeric(lang)))
# p1 p2 p3 
#  9 10  7
list.mapv(devs, range(as.numeric(lang)))
# p11 p12 p21 p22 p31 p32 
#   2   4   2   5   1   4

list.select

In contrast to list.map, list.select provides an easier way to map each list member to a new list. This functions basically evaluates all given expressions and put the results into a list.

If a field name a list member is selected, its name will automatically preserved. If a list item evaluated from other expression is selected, we may better give it a name, or otherwise it will only have an index.

list.select(devs, name, age)
# $p1
# $p1$name
# [1] "Ken"
# 
# $p1$age
# [1] 24
# 
# 
# $p2
# $p2$name
# [1] "James"
# 
# $p2$age
# [1] 25
# 
# 
# $p3
# $p3$name
# [1] "Penny"
# 
# $p3$age
# [1] 24
list.select(devs, name, age, nlang=length(lang))
# $p1
# $p1$name
# [1] "Ken"
# 
# $p1$age
# [1] 24
# 
# $p1$nlang
# [1] 3
# 
# 
# $p2
# $p2$name
# [1] "James"
# 
# $p2$age
# [1] 25
# 
# $p2$nlang
# [1] 3
# 
# 
# $p3
# $p3$name
# [1] "Penny"
# 
# $p3$age
# [1] 24
# 
# $p3$nlang
# [1] 3

list.iter

Sometimes we don't really need the result of a mapping but its side effects. For example, if we only need to print out something about each list member, we don't need to carry on the output of mapping.

list.iter performs iterations over a list and returns nothing.

list.iter(devs, cat(name,":",age,"\n"))
# Ken : 24 
# James : 25 
# Penny : 24

list.maps

All the previous functions work with a single list. There are scenarios where mapping multiple lists is needed. list.maps evaluates an expression with multiple lists each of which is represented by a user-defined symbol at the function call.

l1 <- list(p1=list(x=1,y=2), p2=list(x=3,y=4), p3=list(x=1,y=3))
l2 <- list(2,3,5)
list.maps(a$x*b+a$y,a=l1,b=l2)
# $p1
# [1] 4
# 
# $p2
# [1] 13
# 
# $p3
# [1] 8

list.maps does not follow the conventions of many other functions like list.map and list.iter where the data comes first and expression comes the second. Since list.maps supports multi-mapping with a group of lists, only implicit lambda expression is supported to avoid ambiguity. After that the function still allows users to define the symbol that represents each list being mapped in the ....

In the example above, ... means a = l1, b = l2, so that a and b are meaningful in the first expression a$x*b+a$y where a and b mean the current element of each list, respectively.

list.maps(.i + a$x*b+a$y,a=l1,b=l2)
# $p1
# [1] 5
# 
# $p2
# [1] 15
# 
# $p3
# [1] 11

The expression is evaluated in an environment where .i and .name are defined as the index and name of the element for the first list, respectively.