How well do you know your fundamental operators in different languages? ‘Easy’ examples help to fortify that knowledge, and comparing across languages makes for some neat implementation detail discoveries.
I saw this toot from @gregeganSF
(mathstodon.xyz) on Mastodon
which says
To rotate a j-digit number n by k digits, if n≠10^j-1 there is a simple formula:
rot(n,j,k) = (n*10^k) mod (10^j-1)
e.g. 1234 * 100 mod 9999 = 3412
Why? The mod subtracts (10^j-1) times the leftmost k digits of n*10^k, removing them from the left and adding them on the right.
and my first thought was “ooh, that’s cool”, but my second thought was “I’m going to implement this in a bunch of languages!”. Sure, it’s a very small bit of math to implement without any particular sharp edges of iteration/recursion, but that means I’ll be working with some basic functionality and I believe it’s very important to have that locked in comfortably. Let’s see how it goes!
I’m not aware of a “name” for this as such. Writing it out a little more styled
it looks like I’ll just need a ‘power’ and a ‘modulo’. The there is the number of digits in and sure, we could count that ourselves and pass it as an argument, but even better might be to calculate it as well. That means figuring out how many digits are in a number.
As always, my go-to is R, so let’s start there.
R
In R the power operator is ^
. Also **
, but that’s almost never used -
there’s even a note about that in the documentation
**
is translated in the parser to^
, but this was undocumented for many years.
Modulo is %%
which I can never remember because it’s similar to integer
division which is %/%
.
To get the number of digits we can use the fact that nchar()
will first convert
its input into a character vector, so 12345
becomes "12345"
and thus the
number of characters of that is the number of digits. If that wasn’t the case
I could still do
ceiling(log10(314159))
## [1] 6
but the nchar()
approach seems fine. Putting those pieces together into a
function which takes the number and how many places to move it, I get
rot <- function(n, k) {
(n*10^k) %% (10^nchar(n)-1)
}
rot(12345, 3)
## [1] 45123
You can see that the values are ‘rotated’ cyclically by 3 places (to the left).
R doesn’t have a built-in way to achieve this even for a vector of values (rotating ‘digits’ of an integer is a toy problem that is unlikely to actually come up in real situations). One solution is my {vec} (github.com) package which does implement a ring-buffer sort of effect for vectors
# remotes::install_github("jonocarroll/vec")
library(vec)
v <- as_vec(1:5)
rotate(v, 3)
## [1] 4 5 1 2 3
Under the hood this uses modulo on the indices
x <- 1:5; n <- 3
x[(((n - 1 + seq_along(x)) %% length(x))) + 1]
## [1] 4 5 1 2 3
One extra feature of this is it also takes negative values to shift the other way
rotate(v, -3)
## [1] 3 4 5 1 2
whereas the rot()
implementation above can’t.
Julia
As is almost always the case, the Julia functionality looks closer to what one
might “expect” from translating maths or stats; the power operator remains ^
,
but the modulo operator is a more familiar %
. There is an actual ndigits()
function to get the number of digits which, as far as I can tell from the source,
doesn’t first convert to character. The examples for that function do highlight
a failing of the R approach - if a value is negative then as.character()
will
produce the wrong number of digits. In R:
as.character(-1234)
## [1] "-1234"
nchar(-1234)
## [1] 5
while in Julia
ndigits(-1234)
## 4
We’re not dealing with negatives here, but that’s certainly a gotcha.
Implementing the Julia function can be done in a single line so no need for a
function
keyword
rot(n,k) = (n*10^k) % (10^ndigits(n)-1)
## rot (generic function with 1 method)
rot(12345, 3)
## 45123
If we were working with vectors, Julia also has a built-in way to do the cyclic rotation, though it seems to shift in the opposite direction
x = [1, 2, 3, 4, 5]
## 5-element Vector{Int64}:
## 1
## 2
## 3
## 4
## 5
circshift(x, -3)
## 5-element Vector{Int64}:
## 4
## 5
## 1
## 2
## 3
circshift(x, 3)
## 5-element Vector{Int64}:
## 3
## 4
## 5
## 1
## 2
Here ends the Algol-language portion of the post, and I’ll move on to some languages where these operations are even more fundamental…
APL
When I see ‘straightforward’ math operations on numbers I now think APL because that’s what it was originally built for and it works so very well; each math operation you could perform on an array of values has a ‘glyph’ representing it so should be a better ‘translation’ of the math on a blackboard directly to code.
One thing R users will immediately recognise is the assignment glyph ←
; yes
that’s a single glyph, not R’s <-
, but it works the same as in R.
You’re probably familiar with the multiplication glyph ×
and addition +
.
The power/exponentiation glyph is *
. Nothing too surprising there, I hope.
Because -
is the ‘subtract’ operation, there’s a distinct glyph for ‘negative’
¯
(a raised hyphen) so it isn’t confused with subtraction. Modulo takes some
more inspiration from math and is |
. The only other potentially confusing
glyphs are length ≢
and format ⍕
which, when combined, do something very
similar to R’s “how many characters does this use?”. Again the ‘negative number’
problem is here, but we’re not worried about that in this case.
Putting those pieces together requires knowing that APL evaluates right-to-left,
with the argument to the right of an operator in a “function” being denoted by
omega (⍵
) and the one to the left being alpha (⍺
). The function I came up
with looks like this
rot←{(¯1+10*≢⍕⍵)|⍵×10*⍺}
It’s entirely possible that it can be shortened or improved; I have a tendency to overlook where parentheses are really required and opportunities for simplification. Nonetheless, if you read from right-to-left it spells out the calculation we want. Applying it to some value means placing it between its two arguments
3 rot 12345
45123
Try it out yourself! (tryapl.org)
Most of the difficulty I faced when building this was dealing with order-of-operations which need to be right-to-left. There are ways to ‘swap’ the order of arguments to a function (such as modulo) to make it read more similarly to the hand-written expression, but I both couldn’t get that to produce the right answer and didn’t feel it was necessary.
In terms of working with vectors, that’s where APL shines. There is a rotate
glyph ⌽
which when given just one argument reverses a vector, but with a second
argument does exactly what we want; rotates by that many places
x←1 2 3 4 5
3 ⌽ x
4 5 1 2 3
If you don’t look too closely at the type of the data, we can use this to rotate
a string made of character digits by again using format ⍕
to make a vector of
characters from the number
3 ⌽ ⍕12345
45123
(the whitespace here is purely for demonstration; 3⌽⍕12345
works just the same).
Uiua
Uiua is a much newer language that has a lot of support for operating on data, but it behaves differently to all of the above; it’s a stack-based language so you work with data on a stack, not as variables. I’ve played around with it and really enjoy working with it - there’s even an Exercism track (exercism.org) now - but in trying to write this solution I realised that I’d only ever worked with one ‘thing’ on the stack, even if that was an entire vector of values. This problem invites us to work with a value to be rotated and how many places to rotate it; that meant I learned a lot about figuring out which value from the stack I want.
Entering operators into Uiua is greatly eased by having translation and
auto-complete; you don’t have to figure out how to type ◿
you can start typing
mod
and as long as it’s a unique completion, Uiua will convert it to the
appropriate glyph. Additionally, there are some formatting tricks such as taking
a double-underscore suffix to a function to make a combined glyph with a preset
value; log__10
translates to ₙ₁₀
.
The operators I need here are modulo ◿
, multiply ×
, power ⁿ
, log10 ₙ₁₀
,
ceiling ⌈
, and subtract -
, with the additional dip ⊙
to use the second
value on the stack, and backward ˜
to swap the arguments of modulo. I couldn’t
immediately think of a way to cleanly get the number of digits of a value (I did
later, which I’ll come back to) so I went the log10
route and my solution is
(again, right-to-left)
3 12345
˜◿⊙(-1˜ⁿ10⌈ₙ₁₀)⊸טⁿ10
45123
Try it out yourself! (uiua.org)
Working with vectors is much cleaner here, too, and there’s a simple rotate ↻
that does the same as APL
↻ 3 [1 2 3 4 5]
[4 5 1 2 3]
Reading this, the vector is placed on the stack, then the value 3 is put on the top of the stack, then rotate takes two values from the stack and performs that operation, leaving one value (the result) on the stack.
Uiua also has a really cool feature of “undoing” an operation, where the inverse
can be calculated. If I wanted to turn a string into a number I would use parse
⋕
and I can do the opposite by prefixing it with un °
to make “unparse”
which converts a number to a string. Since a string is just a vector of
characters (in this case digits) the rotate works naturally, albeit returning a
string
↻ 3 °⋕ 12345
"45123"
Uiua goes one step further with an under ⍜
which takes some value, performs
some transformation, applies a function, then undoes that transformation. In my
case, I want to “unparse then re-parse” which seems like a great fit for this
⍜°⋕(↻ 3) 12345
45123
and returns a number again, because under applies the back-transformation of parse.
The unparse °⋕
is recognised as a compound and is passed as the first argument
to under, while I need the rotate and 3 to go together with parentheses. If I
always wanted to shift by 3 places I could use the double underscore to ‘attach’
the 3
to the rotate producing rotate__3
↻₃
but what I have above allows for
changing that number.
Looking into it this way, it’s more obvious that I could get the number of digits
with lengthunparse
as ⧻°⋕
, but exploring how to go the long way around was
entirely worthwhile, and not necessarily longer with ceilinglog__10
as ⌈ₙ₁₀
.
I knew I’d find lots of interesting things when I saw this and I was right - just spending the time going through the documentation of these ‘basic’ functions reminded me of things I’ve forgotten and some new things I don’t think I knew before.
I’d love to hear what people think about these comparisons - are there points I’ve overlooked? Better ways to do it? Different functions in some other languages? Considerations I’ve missed? As always, I can be found on Mastodon (fosstodon.org) and the comment section below.
devtools::session_info()
## ─ Session info ───────────────────────────────────────────────────────────────
## setting value
## version R version 4.4.1 (2024-06-14)
## os macOS 15.4.1
## system aarch64, darwin20
## ui X11
## language (EN)
## collate en_US.UTF-8
## ctype en_US.UTF-8
## tz Australia/Adelaide
## date 2025-05-03
## pandoc 3.4 @ /Applications/RStudio.app/Contents/Resources/app/quarto/bin/tools/aarch64/ (via rmarkdown)
##
## ─ Packages ───────────────────────────────────────────────────────────────────
## package * version date (UTC) lib source
## blogdown 1.19 2024-02-01 [1] CRAN (R 4.4.0)
## bookdown 0.41 2024-10-16 [1] CRAN (R 4.4.1)
## bslib 0.8.0 2024-07-29 [1] CRAN (R 4.4.0)
## cachem 1.1.0 2024-05-16 [1] CRAN (R 4.4.0)
## cli 3.6.4 2025-02-13 [1] CRAN (R 4.4.1)
## devtools 2.4.5 2022-10-11 [1] CRAN (R 4.4.0)
## digest 0.6.37 2024-08-19 [1] CRAN (R 4.4.1)
## ellipsis 0.3.2 2021-04-29 [1] CRAN (R 4.4.0)
## evaluate 1.0.3 2025-01-10 [1] CRAN (R 4.4.1)
## fastmap 1.2.0 2024-05-15 [1] CRAN (R 4.4.0)
## fs 1.6.5 2024-10-30 [1] CRAN (R 4.4.1)
## glue 1.8.0 2024-09-30 [1] CRAN (R 4.4.1)
## htmltools 0.5.8.1 2024-04-04 [1] CRAN (R 4.4.0)
## htmlwidgets 1.6.4 2023-12-06 [1] CRAN (R 4.4.0)
## httpuv 1.6.15 2024-03-26 [1] CRAN (R 4.4.0)
## jquerylib 0.1.4 2021-04-26 [1] CRAN (R 4.4.0)
## jsonlite 2.0.0 2025-03-27 [1] CRAN (R 4.4.1)
## JuliaCall 0.17.6 2024-12-07 [1] CRAN (R 4.4.1)
## knitr 1.48 2024-07-07 [1] CRAN (R 4.4.0)
## later 1.4.1 2024-11-27 [1] CRAN (R 4.4.1)
## lifecycle 1.0.4 2023-11-07 [1] CRAN (R 4.4.0)
## magrittr 2.0.3 2022-03-30 [1] CRAN (R 4.4.0)
## memoise 2.0.1 2021-11-26 [1] CRAN (R 4.4.0)
## mime 0.12 2021-09-28 [1] CRAN (R 4.4.0)
## miniUI 0.1.1.1 2018-05-18 [1] CRAN (R 4.4.0)
## pkgbuild 1.4.7 2025-03-24 [1] CRAN (R 4.4.1)
## pkgload 1.4.0 2024-06-28 [1] CRAN (R 4.4.0)
## profvis 0.4.0 2024-09-20 [1] CRAN (R 4.4.1)
## promises 1.3.2 2024-11-28 [1] CRAN (R 4.4.1)
## purrr 1.0.4 2025-02-05 [1] CRAN (R 4.4.1)
## R6 2.6.1 2025-02-15 [1] CRAN (R 4.4.1)
## Rcpp 1.0.14 2025-01-12 [1] CRAN (R 4.4.1)
## remotes 2.5.0 2024-03-17 [1] CRAN (R 4.4.1)
## rlang 1.1.5 2025-01-17 [1] CRAN (R 4.4.1)
## rmarkdown 2.28 2024-08-17 [1] CRAN (R 4.4.0)
## rstudioapi 0.17.1 2024-10-22 [1] CRAN (R 4.4.1)
## sass 0.4.9 2024-03-15 [1] CRAN (R 4.4.0)
## sessioninfo 1.2.2 2021-12-06 [1] CRAN (R 4.4.0)
## shiny 1.9.1 2024-08-01 [1] CRAN (R 4.4.0)
## urlchecker 1.0.1 2021-11-30 [1] CRAN (R 4.4.0)
## usethis 3.1.0.9000 2025-03-31 [1] Github (r-lib/usethis@a653d6e)
## vctrs 0.6.5 2023-12-01 [1] CRAN (R 4.4.0)
## vec * 0.1.0 2025-01-07 [1] Github (jonocarroll/vec@595b07e)
## xfun 0.51 2025-02-19 [1] CRAN (R 4.4.1)
## xtable 1.8-4 2019-04-21 [1] CRAN (R 4.4.0)
## yaml 2.3.10 2024-07-26 [1] CRAN (R 4.4.0)
##
## [1] /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/library
##
## ──────────────────────────────────────────────────────────────────────────────