blob: 13f43589581826a4cbda7b9ddd8b25b8498ac71d [file] [log] [blame]
# File src/library/utils/R/roman.R
# Part of the R package, https://www.R-project.org
#
# Copyright (C) 1995-2019 The R Core Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# A copy of the GNU General Public License is available at
# https://www.R-project.org/Licenses/
.as.roman <- function(x, check.range=TRUE)
{
if(is.integer(x)) { }
else if(is.double(x) || is.logical(x)) # <- as.roman(NA)
x <- as.integer(x)
else if(is.character(x)) {
x <- if(all(dig.x <- !nzchar(x) | is.na(x) | grepl("^[[:digit:]]+$", x)))
as.integer(x)
else if(any(dig.x)) {
r <- suppressWarnings(as.integer(x))# NAs for all non-dig
r[!dig.x] <- .roman2numeric(x[!dig.x])
r
}
else ## no digits -- assume all roman characters
.roman2numeric(x)
}
else
stop("cannot coerce 'x' to roman")
if(check.range) x[x <= 0L | x >= 3900L] <- NA
class(x) <- "roman"
x
}
as.roman <- function(x) .as.roman(x, check.range=TRUE)
as.character.roman <- function(x, ...) .numeric2roman(x)
format.roman <- function(x, ...) format(as.character.roman(x), ...)
print.roman <- function(x, ...)
{
print(noquote(as.character.roman(x)), ...)
invisible(x)
}
`[.roman` <-
function(x, i)
{
cl <- oldClass(x)
y <- NextMethod("[")
oldClass(y) <- cl
y
}
Ops.roman <- function(e1, e2) {
if(.Generic %in% c("+", "-", "*", "^", "%%", "%/%", "/")) { # "Arith" in S4 parlance:
e1 <- .as.roman(e1, check.range=FALSE)
e2 <- .as.roman(e2, check.range=FALSE)
as.roman(NextMethod(.Generic))
}
else # "Compare" and "Logic" in S4 parlance; just work with integer:
NextMethod(.Generic)
}
Summary.roman <- function(x, ..., na.rm=TRUE) {
if(.Generic %in% c("any", "all"))
NextMethod(.Generic)
else # max, min, .. sum
as.roman(NextMethod(.Generic))
}
## for recycling etc
rep.roman <- function(x, ...) structure(rep(unclass(x), ...), class = class(x))
## romans: used in both utility functions, and not unuseful in general:
.romans <-
c(1000L, 900L, 500L, 400L, 100L, 90L, 50L, 40L, 10L, 9L, 5L, 4L, 1L)
names(.romans) <-
c("M", "CM", "D", "CD", "C", "XC", "L", "XL","X","IX","V","IV","I")
## Can *not* use stats {dependency cycle at build time} -- hence need our own:
## .setNames <- function (object = nm, nm) {
## names(object) <- nm
## object
## }
## .romans <- .setNames(
## c(1000L, 900L, 500L, 400L, 100L, 90L, 50L, 40L, 10L, 9L, 5L, 4L, 1L),
## c("M", "CM", "D", "CD", "C", "XC", "L", "XL","X","IX","V","IV","I"))
.numeric2roman <-
function(x) {
romaNs <- names(.romans)
n2r <- function(z) {
y <- character()
for(i in seq_along(.romans)) {
d <- .romans[[i]]
while(z >= d) {
z <- z - d
y <- c(y, romaNs[i])
}
}
paste(y, collapse = "")
}
x <- as.integer(x)
ind <- is.na(x) | (x <= 0L) | (x >= 3900L)
out <- character(length(x))
out[ind] <- NA
out[!ind] <- vapply(x[!ind], n2r, "")
out
}
.roman2numeric <- function(x)
{
out <- integer(length(x))
out[ina <- is.na(x) | !nzchar(x)] <- NA
if(any(ind <- !ina)) {
y <- toupper(x[ind])
y <- gsub("CM", "DCCCC", y)
y <- gsub("CD", "CCCC", y)
y <- gsub("XC", "LXXXX", y)
y <- gsub("XL", "XXXX", y)
y <- gsub("IX", "VIIII", y)
y <- gsub("IV", "IIII", y)
ok <- grepl("^M{,3}D?C{,4}L?X{,4}V?I{,4}$", y)
if(any(!ok)) {
warning(sprintf(ngettext(sum(!ok),
"invalid roman numeral: %s",
"invalid roman numerals: %s"),
paste(x[ind][!ok], collapse = " ")),
domain = NA)
out[ind][!ok] <- NA
}
out[ind][ok] <-
vapply(strsplit(y[ok], ""),
function(z)
as.integer(sum(.romans[match(z, names(.romans))])),
integer(1L))
}
out
}