Skip to content

Commit 7e7b00d

Browse files
author
maechler
committed
sequence.default(..., recycle = ..getenv("..")): step #1 towards fully recycling w/ some back-compatibility
git-svn-id: https://svn.r-project.org/R/trunk@89122 00db46b3-68df-0310-9c12-caf00c1e9a41
1 parent 7f42857 commit 7e7b00d

File tree

6 files changed

+148
-44
lines changed

6 files changed

+148
-44
lines changed

doc/NEWS.Rd

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,11 @@
147147
convenience and code readability, from an R-devel suggestion, after
148148
many years of private definitions mostly hidden in packages,
149149
including in \R's \pkg{tools} package.
150+
151+
\item The default method of \code{sequence()} gets a new option
152+
\code{recycle} to swap between current (not fully recycling) and
153+
future behaviour where all three of \code{(nvec, from, by)} are
154+
jointly recycled.
150155
}
151156
}
152157

@@ -427,6 +432,13 @@
427432
\item Values returned by active binding functions are now marked as
428433
not mutable to prevent unintended mutation in complex assignment
429434
operations.
435+
436+
\item The default method of \code{sequence()} now allows to
437+
fully recycle all three of \code{(nvec, from, by)} as always
438+
documented; with thanks to \I{Mikael Jagan}'s report and patch in
439+
\PR{18304}. Its behaviour remains back compatible for now, but can
440+
be switched to future behaviour via enviroment variable
441+
\env{R_sequence_recycle}, or explicit \code{recycle = TRUE}.
430442
}
431443
}
432444
}

src/library/base/R/seq.R

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,8 @@ seq.default <-
172172
## In reverence to the very first versions of R which already had sequence():
173173
sequence <- function(nvec, ...) UseMethod("sequence")
174174

175-
sequence.default <- function(nvec, from = 1L, by = 1L, ...) {
176-
.Internal(sequence(as.integer(nvec), as.integer(from), as.integer(by)))
175+
sequence.default <- function(nvec, from = 1L, by = 1L,
176+
recycle = Sys.getenv("R_sequence_recycle", "false"), ...) {
177+
.Internal(sequence(as.integer(nvec), as.integer(from), as.integer(by),
178+
if(missing(recycle)) 0L else as.logical(recycle)))
177179
}

src/library/base/man/sequence.Rd

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
% File src/library/base/man/sequence.Rd
22
% Part of the R package, https://www.R-project.org
3-
% Copyright 1995-2007 R Core Team
3+
% Copyright 1995-2025 R Core Team
44
% Distributed under GPL 2 or later
55

66
\name{sequence}
77
\title{Create A Vector of Sequences}
88
\usage{
99
sequence(nvec, \dots)
10-
\method{sequence}{default}(nvec, from = 1L, by = 1L, \dots)
10+
\method{sequence}{default}(nvec, from = 1L, by = 1L,
11+
recycle = Sys.getenv("R_sequence_recycle", "false"), \dots)
1112
}
1213
\alias{sequence}
1314
\alias{sequence.default}
@@ -18,37 +19,59 @@ sequence(nvec, \dots)
1819
specifies the first element of a sequence.}
1920
\item{by}{coerced to an integer vector each element of which
2021
specifies the step size between elements of a sequence.}
22+
\item{recycle}{\code{\link{logical}} or coercible to it, indicating if
23+
\code{nvec} is recycled, as it has always been documented to.
24+
We recommend to set this to \code{TRUE} explicitly, as that will become
25+
default, see \sQuote{Details}.}% also \PR{18304} for background
2126
\item{\dots}{additional arguments passed to methods.}
2227
}
2328
\description{
2429
The default method for \code{sequence} generates the sequence
2530
\code{\link{seq}(from[i], by = by[i], length.out = nvec[i])} for each
2631
element \code{i} in the parallel (and recycled) vectors \code{from},
27-
\code{by} and \code{nvec}. It then returns the result of concatenating
28-
those sequences.
32+
\code{by} and \code{nvec}.
33+
Note that \code{nvec} is \emph{not} recycled unless \code{recycle}
34+
is true, see \sQuote{Details}.
35+
It then returns the result of concatenating those sequences.
2936
}
3037
\details{
3138
Negative values are supported for \code{from} and
3239
\code{by}. \code{sequence(nvec, from, by=0L)} is equivalent to
3340
\code{rep(from, each=nvec)}.
3441

35-
This function was originally implemented in R with fewer features, but
42+
This function was originally implemented in \R with fewer features, but
3643
it has since become more flexible, and the default method is
3744
implemented in C for speed.
45+
46+
Argument \code{recycle} is new since \R 4.6.0;
47+
currently, the default is \code{FALSE} unless the environment variable
48+
\env{R_sequence_recycle} is set to a true value. \cr
49+
This provides back compatibility with \R <= 4.5.z, where \code{from} and
50+
\code{by} are recycled or shortened to length \code{length(nvec)} in case
51+
that is shorter than the maximal length. \cr
52+
The plan is to replace the environment variable with an option
53+
(\code{\link{getOption}}) defaulting to \code{TRUE} and later to
54+
\code{TRUE} without a global option, to use \R's usual recycling semantic
55+
for all three arguments \code{nvec, from, by}.
3856
}
3957
\seealso{
4058
\code{\link{gl}}, \code{\link{seq}}, \code{\link{rep}}.
4159
}
4260
\author{Of the current version, Michael Lawrence based on code from the
43-
S4Vectors Bioconductor package}
61+
\pkg{S4Vectors} Bioconductor package}
4462
\examples{
4563
sequence(c(3, 2)) # the concatenated sequences 1:3 and 1:2.
46-
#> [1] 1 2 3 1 2
47-
sequence(c(3, 2), from=2L)
48-
#> [1] 2 3 4 2 3
49-
sequence(c(3, 2), from=2L, by=2L)
50-
#> [1] 2 4 6 2 4
51-
sequence(c(3, 2), by=c(-1L, 1L))
52-
#> [1] 1 0 -1 1 2
64+
#> [1] 1 2 3 ' 1 2 (using ' to visualize the sub-sequences)
65+
sequence(c(3, 2), from=2L) #> [1] 2 3 4 ' 2 3
66+
sequence(c(3, 2), from=2L, by=2L) #> [1] 2 4 6 ' 2 4
67+
sequence(c(3, 2), by=c(-1L, 1L)) #> [1] 1 0 -1 ' 1 2
68+
69+
## Cases where 'recycle' makes a difference:
70+
sequence(3, 1:3) #> 1 2 3 (_currently_ -- will change in future!)
71+
sequence(3, 1:3, recycle = FALSE)# back compatible: 1 2 3
72+
sequence(3, 1:3, recycle = TRUE) # future default -- use in new code!
73+
# --> 1 2 3 ' 2 3 4 ' 3 4 5
74+
try(sequence(3, 1:3, 1[0], recycle = FALSE)) # an Error; default will be "empty":
75+
sequence(3, 1:3, 1[0], recycle = TRUE) # |--> integer(0)
5376
}
5477
\keyword{manip}

src/main/names.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ FUNTAB R_FunTab[] =
269269
{"tcrossprod", do_matprod, 2, 1, 1, {PP_FUNCALL, PREC_FN, 0}},
270270
{"asplit", do_asplit, 0, 11, 8, {PP_FUNCALL, PREC_FN, 0}},
271271
{"lengths", do_lengths, 0, 11, 2, {PP_FUNCALL, PREC_FN, 0}},
272-
{"sequence", do_sequence, 0, 11, 3, {PP_FUNCALL, PREC_FN, 0}},
272+
{"sequence", do_sequence, 0, 11, 4, {PP_FUNCALL, PREC_FN, 0}},
273273

274274
{"vhash", do_vhash, 0, 11, -1, {PP_FUNCALL, PREC_FN, 0}},
275275

src/main/seq.c

Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1122,57 +1122,83 @@ attribute_hidden SEXP do_seq_len(SEXP call, SEXP op, SEXP args, SEXP rho)
11221122

11231123
attribute_hidden SEXP do_sequence(SEXP call, SEXP op, SEXP args, SEXP rho)
11241124
{
1125-
R_xlen_t lengths_len, from_len, by_len, ans_len, i, i2, i3;
1126-
int from_elt, by_elt, length, j, k, *ans_elt;
1127-
const int *lengths_elt;
1128-
SEXP ans, lengths, from, by;
1129-
11301125
checkArity(op, args);
11311126

1132-
lengths = CAR(args);
1127+
SEXP lengths = CAR(args);
11331128
if (!isInteger(lengths))
11341129
error(_("'nvec' is not of mode integer"));
1135-
from = CADR(args);
1130+
SEXP from = CADR(args);
11361131
if (!isInteger(from))
11371132
error(_("'from' is not of mode integer"));
1138-
by = CADDR(args);
1133+
SEXP by = CADDR(args);
11391134
if (!isInteger(by))
11401135
error(_("'by' is not of mode integer"));
1136+
SEXP recycle_1st_ = CADDDR(args);
1137+
bool maybe_warn = (bool) isInteger(recycle_1st_); // when missing(recycle) in R
1138+
bool recycle_1st = asBool2(recycle_1st_, call);
1139+
R_xlen_t
1140+
lengths_len = xlength(lengths);
1141+
if(lengths_len == 0)
1142+
return allocVector(INTSXP, 0);
1143+
R_xlen_t
1144+
from_len = xlength(from),
1145+
by_len = xlength(by);
11411146

1142-
lengths_len = length(lengths);
1143-
from_len = length(from);
1144-
by_len = length(by);
1145-
if (lengths_len != 0) {
1147+
if (!recycle_1st && lengths_len != 0) {
11461148
if (from_len == 0)
1147-
error(_("'from' has length 0, but not 'nvec'"));
1149+
error(_("'%s' has length 0, but not 'nvec'; 'recycle = TRUE' returns empty here"), "from");
11481150
if (by_len == 0)
1149-
error(_("'by' has length 0, but not 'nvec'"));
1151+
error(_("'%s' has length 0, but not 'nvec'; 'recycle = TRUE' returns empty here"), "by");
1152+
} else { // recycle_1st
1153+
if(from_len == 0 || by_len == 0)
1154+
return allocVector(INTSXP, 0);
1155+
}
1156+
R_xlen_t ans_len,
1157+
max_len = ((lengths_len > from_len) ?
1158+
(lengths_len > by_len ? lengths_len : by_len) :
1159+
(from_len > by_len ? from_len : by_len)),
1160+
i, i1, i2, i3;
1161+
if(!recycle_1st && lengths_len < max_len) {
1162+
/* warn that this will change, if arg was missing, at most *once* per R session */
1163+
static bool warn_1st = true;
1164+
if(warn_1st && maybe_warn) {
1165+
char msg[99];
1166+
snprintf(msg, 99, "length(nvec) %ld < %ld = max(length(from), length(by))",
1167+
lengths_len, max_len);
1168+
warning(_("%s -- future R`s default 'recycle = TRUE' will recycle 'nvec'"), msg);
1169+
warn_1st = false;
1170+
}
1171+
max_len = lengths_len;
11501172
}
1151-
ans_len = 0;
1152-
lengths_elt = INTEGER(lengths);
1153-
for (i = 0; i < lengths_len; i++, lengths_elt++) {
1154-
length = *lengths_elt;
1155-
if (length == NA_INTEGER || length < 0)
1173+
const int *lengths_elt = INTEGER(lengths);
1174+
for (i = i1 = ans_len = 0; i < max_len; i++, i1++) {
1175+
if (i1 >= lengths_len)
1176+
i1 = 0; /* recycle */
1177+
int len = lengths_elt[i1];
1178+
if (len == NA_INTEGER || len < 0)
11561179
error(_("'nvec' must be a vector of non-negative integers"));
1157-
ans_len += length;
1180+
ans_len += len;
11581181
}
1159-
ans = allocVector(INTSXP, ans_len);
1160-
ans_elt = INTEGER(ans);
1161-
lengths_elt = INTEGER(lengths);
1162-
for (i = i2 = i3 = 0; i < lengths_len; i++, i2++, i3++, lengths_elt++) {
1182+
SEXP ans = allocVector(INTSXP, ans_len);
1183+
int *ans_elt = INTEGER(ans),
1184+
*pfrom = INTEGER(from),
1185+
*pby = INTEGER(by);
1186+
for (i = i1 = i2 = i3 = 0; i < max_len; i++, i1++, i2++, i3++) {
1187+
if (recycle_1st && i1 >= lengths_len)
1188+
i1 = 0; /* recycle */
11631189
if (i2 >= from_len)
11641190
i2 = 0; /* recycle */
11651191
if (i3 >= by_len)
11661192
i3 = 0; /* recycle */
1167-
length = *lengths_elt;
1168-
from_elt = INTEGER(from)[i2];
1193+
int length = lengths_elt[i1],
1194+
from_elt = pfrom[i2];
11691195
if (length != 0 && from_elt == NA_INTEGER)
11701196
error(_("'from' contains NAs"));
1171-
by_elt = INTEGER(by)[i3];
1197+
int by_elt = pby[i3];
11721198
if (length >= 2 && by_elt == NA_INTEGER)
11731199
error(_("'by' contains NAs"));
11741200
// int to = from_elt + (length - 1) * by_elt;
1175-
for (k = 0, j = from_elt; k < length; j += by_elt, k++)
1201+
for (int k = 0, j = from_elt; k < length; j += by_elt, k++)
11761202
*(ans_elt++) = j;
11771203
}
11781204
return ans;

tests/reg-tests-1e.R

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2408,6 +2408,47 @@ x[1] <- 2
24082408
stopifnot(y == 1)
24092409
rm(x)
24102410

2411+
## PR#18304 -- recycling `nvec` argument of sequence.default()
2412+
chkS <- function(n, nvec, recyc=FALSE) {
2413+
lxn <- rep.int(1L, n)
2414+
stopifnot(exprs = {
2415+
identical(sequence.default(from = lxn, by = 1L, nvec = nvec, recycle=recyc),
2416+
sequence.default(from = 1L, by = lxn, nvec = nvec, recycle=recyc) -> s1)
2417+
is.integer(s2 <- unlist(mapply(seq, from = lxn, by = 1L,
2418+
length.out = rep_len(nvec, n),# <- to avoid warning: longer argument
2419+
# not a multiple of length of shorter
2420+
SIMPLIFY=FALSE, USE.NAMES=FALSE)))
2421+
identical(s1, if(recyc || n <= length(nvec)) s2 else s2[seq_along(s1)])
2422+
})
2423+
s1
2424+
}
2425+
for(recycl in c(FALSE, TRUE)) withAutoprint({
2426+
cat("\n>>>> recycl: ", recycl, "-----\n",strrep("-", 25),"\n", sep="")
2427+
## These all worked identically previously:
2428+
chkS(1, 1, recyc = recycl) # 1
2429+
chkS(2, 1:2, recyc = recycl) # 1, 1 2
2430+
chkS(3, 1:3, recyc = recycl) # 1, 1 2, 1 2 3
2431+
chkS(3, 3:1, recyc = recycl) # 1 2 3, 1 2, 1
2432+
chkS(4, 1:4, recyc = recycl) # 1, 1 2, 1 2 3, 1 2 3 4
2433+
chkS(4, 4:1, recyc = recycl) # 1 2 3 4, 1 2 3, 1 2, 1
2434+
chkS(5, 1:5, recyc = recycl) # 1, 1 2, 1 2 3, 1 2 3 4, 1 2 3 4 5
2435+
## These did not: length(nvec) < n :
2436+
if(recycl) chkS(3, 2:3, recyc = TRUE)
2437+
else { rF <- getVaW( chkS(3, 2:3, recyc = FALSE) )
2438+
## the very first produces a __once per R session__ warning:
2439+
if(!is.null(wrn <- attr(rF, "warning"))) {
2440+
cat("Caught warning: ")
2441+
writeLines(wrn) } # recycl: FALSE || TRUE
2442+
as.vector(rF) } # 1 2, 1 2 3 || 1 2, 1 2 3, 1 2
2443+
chkS(5, 2:3, recyc = recycl) # 1 2, 1 2 3 || 1 2, 1 2 3, 1 2, 1 2 3, 1 2
2444+
chkS(6, 2:3, recyc = recycl) # 1 2, 1 2 3 || 1 2, 1 2 3, 1 2, 1 2 3, 1 2, 1 2 3
2445+
chkS(4, 2:1, recyc = recycl) # 1 2, 1 || 1 2, 1, 1 2, 1
2446+
chkS(4, 5:6, recyc = recycl) # 1 2 3 4 5, 1 2 3 4 5 6 || 1 2 3 4 5, 1 2 3 4 5 6, 1 2 3 4 5, 1 2 3 4 5 6
2447+
chkS(5, 1:4, recyc = recycl) # 1, 1 2, 1 2 3, 1 2 3 4 || 1, 1 2, 1 2 3, 1 2 3 4, 1
2448+
## the last 6 cases all failed chkS() for recycle = TRUE
2449+
})
2450+
2451+
24112452

24122453
## keep at end
24132454
rbind(last = proc.time() - .pt,

0 commit comments

Comments
 (0)