3

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?

vonjd
  • 4,202
  • 3
  • 44
  • 68
  • `sapply` can coerce based on the output. You may add `simplify = FALSE` or better use `lapply` instead of `sapply` – akrun Sep 06 '21 at 19:07
  • I tried that already. Question is why does the behaviour change with different arguments for 'mods'. – vonjd Sep 06 '21 at 19:10
  • Also, the function doesn't need vectorization as `%%` is already vectorized – akrun Sep 06 '21 at 19:10
  • Ah, ok... Vetorize seems to break something... still, why does the behaviour change?!? – vonjd Sep 06 '21 at 19:11
  • By default it is `SIMPLIFY = TRUE` in `Vectorize` which also does some lapply/mapply inside – akrun Sep 06 '21 at 19:13
  • 1
    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. ` i.e. you may need to modify the function as ` crt <- function(x, mods) { + sapply(mods, \(y) x %% y) + }` and now it have the same behavior – akrun Sep 06 '21 at 19:28
  • 1
    That is really helpful, thank you!! – vonjd Sep 06 '21 at 19:29

2 Answers2

3

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.

akrun
  • 874,273
  • 37
  • 540
  • 662
  • @vonjd I mentioned in the post about the `simplify` `SIMPLIFY` option – akrun Sep 06 '21 at 19:32
  • I think the trick is to not use the Vectorize function at all... then everything works fine – vonjd Sep 06 '21 at 19:33
  • 1
    @vonjd Yes, Here the function you defined is already using `sapply` and there is noot much further vectorizeation is needed as `%%` is vectorized – akrun Sep 06 '21 at 19:34
  • I think this should be included in the answer. Then I will accept it - Thank you! – vonjd Sep 06 '21 at 19:35
0

If you adjust your code slightly

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

and call it with a different mods argument like

crt(1:100, mods = list(c(6, 12)))

the output looks like expected:

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