blob: f30eed6ad06cf2698b85f46d079774b3735215bf [file] [log] [blame]
# File src/library/base/R/sort.R
# Part of the R package, https://www.R-project.org
#
# Copyright (C) 1995-2018 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/
.doSortWrap <- local({
## this matches the enum in Rinternals.h
INCR_NA_1ST <- 2
INCR <- 1
DECR <- -1
DECR_NA_1ST <- -2
UNSORTED <- 0
UNKNOWN <- NA_integer_
.makeSortEnum <- function(decr, na.last) {
if(decr) {
if (is.na(na.last) || na.last) DECR
else DECR_NA_1ST
} else {
if (is.na(na.last) || na.last) INCR
else INCR_NA_1ST
}
}
function(vec, decr, nalast, noNA = NA) {
if (length(vec) > 0 && is.numeric(vec)) {
sorted <- .makeSortEnum(decr, nalast)
if (is.na(noNA)) {
if(is.na(nalast)) ## NAs were removed
noNA <- TRUE
else if(nalast) ## NAs are last
noNA <- !is.na(vec[length(vec)])
else ## NAs are first
noNA <- !is.na(vec[1L])
}
.Internal(wrap_meta(vec, sorted, noNA))
}
else vec
}
})
## temporary, for sort.int and sort.list captured as S4 default methods
.doWrap <- .doSortWrap
sort <- function(x, decreasing = FALSE, ...)
{
if(!is.logical(decreasing) || length(decreasing) != 1L)
stop("'decreasing' must be a length-1 logical vector.\nDid you intend to set 'partial'?")
UseMethod("sort")
}
sort.default <- function(x, decreasing = FALSE, na.last = NA, ...)
{
## The first case includes factors.
## no wrapping/altrep fastpass here because sortedness may not correspond
## to what other code assumes. ie for factors the vector itself is
## not guaranteed to be sorted in numeric order, since it goes by level
## values
if(is.object(x))
x[order(x, na.last = na.last, decreasing = decreasing)]
else
sort.int(x, na.last = na.last, decreasing = decreasing, ...)
}
sort.int <-
function(x, partial = NULL, na.last = NA, decreasing = FALSE,
method = c("auto", "shell", "quick", "radix"),
index.return = FALSE)
{
## fastpass
decreasing <- as.logical(decreasing)
if (is.null(partial) && !index.return && is.numeric(x)) {
if (.Internal(sorted_fpass(x, decreasing, na.last))) {
## strip attributes other than 'names'
attr <- attributes(x)
if (! is.null(attr) && ! identical(names(attr), "names"))
attributes(x) <- list(names = names(x))
return(x)
}
}
method <- match.arg(method)
if (method == "auto" && is.null(partial) &&
(is.numeric(x) || is.factor(x) || is.logical(x)) &&
is.integer(length(x)))
method <- "radix"
if (method == "radix") {
if (!is.null(partial)) {
stop("'partial' sorting not supported by radix method")
}
if (index.return && is.na(na.last)) {
x <- x[!is.na(x)]
na.last <- TRUE
}
o <- order(x, na.last = na.last, decreasing = decreasing,
method = "radix")
y <- x[o]
y <- .doSortWrap(y, decreasing, na.last)
return(if (index.return) list(x = y, ix = o) else y)
}
else if (method == "auto" || !is.numeric(x))
method <- "shell" # explicitly prevent 'quick' for non-numeric data
if(isfact <- is.factor(x)) {
if(index.return) stop("'index.return' only for non-factors")
lev <- levels(x)
nlev <- nlevels(x)
isord <- is.ordered(x)
x <- c(x) # drop attributes
} else if(!is.atomic(x))
stop("'x' must be atomic")
if(has.na <- any(ina <- is.na(x))) {
nas <- x[ina]
x <- x[!ina]
}
if(index.return && !is.na(na.last))
stop("'index.return' only for 'na.last = NA'")
if(!is.null(partial)) {
if(index.return || decreasing || isfact || method != "shell")
stop("unsupported options for partial sorting")
if(!all(is.finite(partial))) stop("non-finite 'partial'")
y <- if(length(partial) <= 10L) {
partial <- .Internal(qsort(partial, FALSE))
.Internal(psort(x, partial))
} else if (is.double(x)) .Internal(qsort(x, FALSE))
else .Internal(sort(x, FALSE))
} else {
nms <- names(x)
switch(method,
"quick" = {
if(!is.null(nms)) {
if(decreasing) x <- -x
y <- .Internal(qsort(x, TRUE))
if(decreasing) y$x <- -y$x
names(y$x) <- nms[y$ix]
if (!index.return) y <- y$x
} else {
if(decreasing) x <- -x
y <- .Internal(qsort(x, index.return))
if(decreasing)
if(index.return) y$x <- -y$x else y <- -y
}
},
"shell" = {
if(index.return || !is.null(nms)) {
o <- sort.list(x, decreasing = decreasing)
y <- if (index.return) list(x = x[o], ix = o) else x[o]
}
else
y <- .Internal(sort(x, decreasing))
})
}
if (!is.na(na.last) && has.na)
y <- if (!na.last) c(nas, y) else c(y, nas)
if (isfact)
y <- (if (isord) ordered else factor)(y, levels = seq_len(nlev),
labels = lev)
if (is.null(partial)) {
y <- .doSortWrap(y, decreasing, na.last)
}
y
}
order <- function(..., na.last = TRUE, decreasing = FALSE,
method = c("auto", "shell", "radix"))
{
z <- list(...)
## fastpass, take advantage of ALTREP metadata
decreasing <- as.logical(decreasing)
if (length(z) == 1L && is.numeric(x <- z[[1L]]) && !is.object(x) && length(x) > 0) {
if (.Internal(sorted_fpass(x, decreasing, na.last)))
return(seq_along(x))
}
method <- match.arg(method)
if(any(vapply(z, is.object, logical(1L)))) {
z <- lapply(z, function(x) if(is.object(x)) as.vector(xtfrm(x)) else x)
return(do.call("order", c(z, list(na.last = na.last, decreasing = decreasing,
method = method))))
}
if (method == "auto") {
useRadix <- all(vapply(z, function(x) {
(is.numeric(x) || is.factor(x) || is.logical(x)) &&
is.integer(length(x))
}, logical(1L)))
method <- if (useRadix) "radix" else "shell"
}
if(method != "radix" && !is.na(na.last)) {
return(.Internal(order(na.last, decreasing, ...)))
}
if (method == "radix") {
decreasing <- rep_len(as.logical(decreasing), length(z))
return(.Internal(radixsort(na.last, decreasing, FALSE, TRUE, ...)))
}
## na.last = NA case: remove nas
if(any(diff((l.z <- lengths(z)) != 0L)))
stop("argument lengths differ")
na <- vapply(z, is.na, rep.int(NA, l.z[1L]))
ok <- if(is.matrix(na)) rowSums(na) == 0L else !any(na)
if(all(!ok)) return(integer())
z[[1L]][!ok] <- NA
ans <- do.call("order", c(z, list(decreasing = decreasing)))
ans[ok[ans]]
}
sort.list <- function(x, partial = NULL, na.last = TRUE, decreasing = FALSE,
method = c("auto", "shell", "quick", "radix"))
{
## fastpass, take advantage of ALTREP metadata
decreasing <- as.logical(decreasing)
if(is.null(partial) && is.numeric(x) && !is.object(x) && length(x) > 0) {
if (.Internal(sorted_fpass(x, decreasing, na.last)))
return(seq_along(x))
}
method <- match.arg(method)
if (method == "auto" && (is.numeric(x) || is.factor(x) || is.logical(x)) &&
is.integer(length(x)))
method <- "radix"
if(!is.atomic(x))
stop("'x' must be atomic for 'sort.list'\nHave you called 'sort' on a list?")
if(!is.null(partial))
.NotYetUsed("partial != NULL")
if(method == "quick") {
if(is.factor(x)) x <- as.integer(x) # sort the internal codes
if(is.numeric(x))
return(sort(x, na.last = na.last, decreasing = decreasing,
method = "quick", index.return = TRUE)$ix)
else stop("method = \"quick\" is only for numeric 'x'")
}
if (is.na(na.last)) {
x <- x[!is.na(x)]
na.last <- TRUE
}
if(method == "radix") {
return(order(x, na.last=na.last, decreasing=decreasing, method="radix"))
}
## method == "shell"
.Internal(order(na.last, decreasing, x))
}
## xtfrm is now primitive
## xtfrm <- function(x) UseMethod("xtfrm")
xtfrm.default <- function(x)
if(is.numeric(x)) unclass(x) else as.vector(rank(x, ties.method = "min",
na.last = "keep"))
xtfrm.factor <- function(x) as.integer(x) # primitive, so needs a wrapper
## ## Moved to package survival
## xtfrm.Surv <- function(x)
## order(if(ncol(x) == 2L) order(x[,1L], x[,2L]) else order(x[,1L], x[,2L], x[,3L])) # needed by 'party'
xtfrm.AsIs <- function(x)
{
if(length(cl <- class(x)) > 1) oldClass(x) <- cl[-1L]
NextMethod("xtfrm")
}
## callback from C code for rank/order
.gt <- function(x, i, j)
{
xi <- x[i]; xj <- x[j]
if (xi == xj) 0L else if(xi > xj) 1L else -1L;
}
## callback for C code for is.unsorted, hence negation.
.gtn <- function(x, strictly)
{
n <- length(x)
if(strictly) !all(x[-1L] > x[-n]) else !all(x[-1L] >= x[-n])
}
grouping <- function(...) {
z <- list(...)
if(any(vapply(z, is.object, logical(1L)))) {
z <- lapply(z, function(x) if(is.object(x)) as.vector(xtfrm(x)) else x)
return(do.call("grouping", z))
}
nalast <- TRUE
decreasing <- rep_len(FALSE, length(z))
group <- TRUE
sortStr <- FALSE
return(.Internal(radixsort(nalast, decreasing, group, sortStr, ...)))
}