Search code examples
rvectorizationmodular-arithmetic

Unexpected behaviour of sapply() within vectorized function


I wanted to play around with modular arithmetic and programmed some innocently looking function... but got totally surprised by the following unexpected behaviour:

crt <- function(x, mods = c(5, 7)) {
  sapply(mods, \(y) x %% y)
}
crt <- Vectorize(crt)

crt(20)
##      [,1]
## [1,]    0
## [2,]    6

crt(55)
##      [,1]
## [1,]    0
## [2,]    6

crt(1:100)
##      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12] [,13] [,14]
## [1,]    1    2    3    4    0    1    2    3    4     0     1     2     3     4
## [2,]    1    2    3    4    5    6    0    1    2     3     4     5     6     0
##      [,15] [,16] [,17] [,18] [,19] [,20] [,21] [,22] [,23] [,24] [,25] [,26]
## [1,]     0     1     2     3     4     0     1     2     3     4     0     1
## [2,]     1     2     3     4     5     6     0     1     2     3     4     5
##      [,27] [,28] [,29] [,30] [,31] [,32] [,33] [,34] [,35] [,36] [,37] [,38]
## [1,]     2     3     4     0     1     2     3     4     0     1     2     3
## [2,]     6     0     1     2     3     4     5     6     0     1     2     3
##      [,39] [,40] [,41] [,42] [,43] [,44] [,45] [,46] [,47] [,48] [,49] [,50]
## [1,]     4     0     1     2     3     4     0     1     2     3     4     0
## [2,]     4     5     6     0     1     2     3     4     5     6     0     1
##      [,51] [,52] [,53] [,54] [,55] [,56] [,57] [,58] [,59] [,60] [,61] [,62]
## [1,]     1     2     3     4     0     1     2     3     4     0     1     2
## [2,]     2     3     4     5     6     0     1     2     3     4     5     6
##      [,63] [,64] [,65] [,66] [,67] [,68] [,69] [,70] [,71] [,72] [,73] [,74]
## [1,]     3     4     0     1     2     3     4     0     1     2     3     4
## [2,]     0     1     2     3     4     5     6     0     1     2     3     4
##      [,75] [,76] [,77] [,78] [,79] [,80] [,81] [,82] [,83] [,84] [,85] [,86]
## [1,]     0     1     2     3     4     0     1     2     3     4     0     1
## [2,]     5     6     0     1     2     3     4     5     6     0     1     2
##      [,87] [,88] [,89] [,90] [,91] [,92] [,93] [,94] [,95] [,96] [,97] [,98]
## [1,]     2     3     4     0     1     2     3     4     0     1     2     3
## [2,]     3     4     5     6     0     1     2     3     4     5     6     0
##      [,99] [,100]
## [1,]     4      0
## [2,]     1      2

crt(x = 1:100, mods = c(12, 60))
##   [1]  1  2  3  4  5  6  7  8  9 10 11 12  1 14  3 16  5 18  7 20  9 22 11 24  1
##  [26] 26  3 28  5 30  7 32  9 34 11 36  1 38  3 40  5 42  7 44  9 46 11 48  1 50
##  [51]  3 52  5 54  7 56  9 58 11  0  1  2  3  4  5  6  7  8  9 10 11 12  1 14  3
##  [76] 16  5 18  7 20  9 22 11 24  1 26  3 28  5 30  7 32  9 34 11 36  1 38  3 40

Why is the last function call crt(x = 1:100, mods = c(12, 60)) giving a totally different output? The first vectorized output crt(1:100) is what I wanted and expected, the last one doesn't seem structurally different but the result is... why? And how do I fix this to get the same output as the first?


Solution

  • According to ?Vectorize

    The arguments named in the vectorize.args argument to Vectorize are the arguments passed in the ... list to mapply. Only those that are actually passed will be vectorized; default values will not.

    Here, in the OP's function, there is default value for 'mods'. If we remove it

    crt <- function(x, mods) {
       sapply(mods, \(y) x %% y)
     }
    crt <- Vectorize(crt)
    

    -testing

    > crt(1:100, mods = c(5, 7))
      [1] 1 2 3 4 0 6 2 1 4 3 1 5 3 0 0 2 2 4 4 6 1 1 3 3 0 5 2 0 4 2 1 4 3 6 0 1 2 3 4 5 1 0 3 2 0 4 2 6 4 1 1 3 3 5 0 0 2 2 4 4 1 6 3 1 0 3 2 5 4 0 1 2 3 4
     [75] 0 6 2 1 4 3 1 5 3 0 0 2 2 4 4 6 1 1 3 3 0 5 2 0 4 2
    > crt(1:100, mods = c(12, 60))
      [1]  1  2  3  4  5  6  7  8  9 10 11 12  1 14  3 16  5 18  7 20  9 22 11 24  1 26  3 28  5 30  7 32  9 34 11 36  1 38  3 40  5 42  7 44  9 46 11 48  1
     [50] 50  3 52  5 54  7 56  9 58 11  0  1  2  3  4  5  6  7  8  9 10 11 12  1 14  3 16  5 18  7 20  9 22 11 24  1 26  3 28  5 30  7 32  9 34 11 36  1 38
     [99]  3 40
    

    The output format is determined at two levels here - 1) sapply which by default uses simplify = TRUE and Vectorize which also by default have SIMPLIFY = TRUE

    Also, based on the function defined, Vectorize is not really needed as internally, it does the looping with *apply functions and we already have the crt defined with sapply which loop over the 'mods'. The function applied %% on those parameters is %% which is already a vectorized function by default.