4

I have two large, ordered lists of integers. I would like the integer length at [i]position of hello to be equal to the length at [i] position of bye.

Here is a simplified reproducible example to simulate my dataset :

c1<- c(0,1,0,1,1,1,0,0) |> as.integer()
c2<- c(0,1,0) |> as.integer()
c3<- c(1,1,1,0,0,0,0,0,0) |> as.integer()
c4<- c(0,1,0,1,0,0,0,1,0,1) |> as.integer()
c5<- c(0,1,0,1,0,0,0,0) |> as.integer()
c6 <-  c(1,1,1,0,0) |> as.integer()

hello<- list(c1, c2, c3)
bye<- list(c4,c5,c6)

Lists output:

hello
[[1]]
[1] 0 1 0 1 1 1 0 0

[[2]]
[1] 0 1 0

[[3]]
[1] 1 1 1 0

bye
[[1]]
 [1] 0 1 0 1 0 0 0 1 0 1

[[2]]
[1] 0 1 0 1 0 0 0 0

[[3]]
[1] 1 1 1 0 0

I would like the values of the expanded relevant list element to be appended with values of 0. The desired output of an individual list element would look like this:

hello[[2]]
[1] 0 1 0 0 0 0 0 0

bye[[2]]
[1] 0 1 0 1 0 0 0 0

So far, I have tried a for loop with an append statement which I just couldn't get to work.

I assume that lapply or purrr::map would provide a neater solution but I am still trying to get my head around functional programming in R. Any help would be greatly appreciated.

AR459
  • 63
  • 3

5 Answers5

5

You could try:

Map(\(x, y) c(x, integer(y))[1L:y], hello, pmax(lengths(hello), lengths(bye)))

Or a slight variation:

Map(\(x, y, z) c(x, integer(max(0L, z-y))), hello, lengths(hello), lengths(bye))

[[1]]
 [1] 0 1 0 1 1 1 0 0 0 0

[[2]]
[1] 0 1 0 0 0 0 0 0

[[3]]
[1] 1 1 1 0 0 0 0 0 0
Ritchie Sacramento
  • 29,890
  • 4
  • 48
  • 56
2

Other variants of Map

Map(
    \(x, y) c(x, rep(0,y)), 
    hello, 
    pmax(lengths(bye) - lengths(hello), 0)
)

or

Map(
    \(x, y) replace(d <- `length<-`(x, y), is.na(d), 0), 
    hello, 
    pmax(lengths(bye), lengths(hello))
)

gives

[[1]]
 [1] 0 1 0 1 1 1 0 0 0 0

[[2]]
[1] 0 1 0 0 0 0 0 0

[[3]]
[1] 1 1 1 0 0 0 0 0 0
ThomasIsCoding
  • 96,636
  • 9
  • 24
  • 81
1

Another way, using imap:

imap(hello, \(h, i) if (length(h) > length(bye[[i]])) h[1:length(bye[[i]])] else c(bye[[i]], rep(0, length(bye[[i]]) - length(h))))

It's unclear to me what you want to happen in the case when hello is already longer than bye. I shortened it to the first n characters of hello, where n is the length of bye.

Mark
  • 7,785
  • 2
  • 14
  • 34
1

Utilizing length(x) <- value:

# template list:
str(bye)
#> List of 3
#>  $ : int [1:10] 0 1 0 1 0 0 0 1 0 1
#>  $ : int [1:8] 0 1 0 1 0 0 0 0
#>  $ : int [1:5] 1 1 1 0 0

# if padding with NA values is OK, this would be enough:
hello_2 <- mapply(`length<-`, hello, lengths(bye))
str(hello_2)
#> List of 3
#>  $ : int [1:10] 0 1 0 1 1 1 0 0 NA NA
#>  $ : int [1:8] 0 1 0 NA NA NA NA NA
#>  $ : int [1:5] 1 1 1 0 0

# to replace NAs with 0:
hello_3 <- hello_2 |> lapply(\(v) `[<-`(v, is.na(v), 0))
str(hello_3)
#> List of 3
#>  $ : num [1:10] 0 1 0 1 1 1 0 0 0 0
#>  $ : num [1:8] 0 1 0 0 0 0 0 0
#>  $ : num [1:5] 1 1 1 0 0

Created on 2023-08-21 with reprex v2.0.2

margusl
  • 7,804
  • 2
  • 16
  • 20
  • 1
    This is really nice! I had no idea that you could use `\`length<-\`` like this. Minor point - I would prefer `Map()` to `mapply()` here (and almost always) - no difference to the output in this case but I have been tripped up when my inputs changed and `mapply()` decided it could simplify my result into a matrix. – SamR Aug 21 '23 at 11:19
0

For a one way padding (length of hello item adjusted according to length of related bye item) you can use

library(purrr)

hello2 <- map2(hello, bye, function(h,b) {
  if(length(b)>length(h)) {
    h <- append(h, rep(0, length(b)-length(h)))
  } else {
    h
  }
})

map2 iterate pairwise on both lists and calls given function for each pair. Here we detect if length adjustment is needed then append needed number of zeroes.

Billy34
  • 1,777
  • 11
  • 11