diff --git a/R/bitset.R b/R/bitset.R index e600680..556c560 100644 --- a/R/bitset.R +++ b/R/bitset.R @@ -1,3 +1,41 @@ +#' Generate documentation for an R6-like method +#' +#' The Bitset class is implemented as a named list with closures that capture +#' the environment. By default, roxygen2 generates terrible documentation for +#' it since it isn't a typical way of doing things. +#' +#' This method generates a snippet of Rd code for a method, in a way that +#' resembles the code roxygen2 generates for R6 methods. +#' +#' @noRd +bitset_method_doc <- function(name, description, static = FALSE, ...) { + # nocov start: this is only used for documentation generation + lines <- character() + push <- function(...) lines <<- c(lines, ...) + + arguments <- list(...) + argnames <- paste(names(arguments), collapse=", ") + receiver <- if (static) "Bitset" else "b" + + push("\\if{html}{\\out{
}}") + push(paste0("\\subsection{Method \\code{", name, "()}}{")) + push(description) + push("\\subsection{Usage}{") + push(sprintf("\\preformatted{%s$%s(%s)}", receiver, name, argnames)) + push("}") + if (length(arguments) > 0) { + push("\\subsection{Arguments}{") + push("\\describe{") + push(sprintf("\\item{\\code{%s}}{%s}", names(arguments), arguments)) + push("}") + push("}") + } + push("}") + + cat(paste(lines, collapse="\n")) + # nocov end +} + #' @title A Bitset Class #' @description This is a data structure that compactly stores the presence of #' integers in some finite set (\code{max_size}), and can @@ -5,126 +43,188 @@ #' difference, set difference). #' WARNING: All operations are in-place so please use \code{$copy} #' if you would like to perform an operation without destroying your current bitset. -#' @importFrom R6 R6Class +#' +#' This class is defined as a named list for performance reasons, but for most +#' intents and purposes it behaves just like an R6 class. +#' @format NULL +#' @usage NULL +#' @docType NULL +#' @keywords NULL #' @export -Bitset <- R6Class( - 'Bitset', - public = list( - #' @field .bitset a pointer to the underlying IterableBitset. - .bitset = NULL, - - #' @field max_size the maximum size of the bitset. - max_size = 0, - - #' @description create a bitset. - #' @param size the size of the bitset. - #' @param from pointer to an existing IterableBitset to use; if \code{NULL} - #' make empty bitset, otherwise copy existing bitset. - initialize = function(size, from = NULL) { - if (is.null(from)) { - self$.bitset <- create_bitset(size) - } else { - stopifnot(inherits(from, "externalptr")) - self$.bitset <- from - } - self$max_size <- bitset_max_size(self$.bitset) - }, - - #' @description insert into the bitset. - #' @param v an integer vector of elements to insert. - insert = function(v) { - bitset_insert(self$.bitset, v) - self - }, - - #' @description remove from the bitset. - #' @param v an integer vector of elements (not indices) to remove. - remove = function(v) { - bitset_remove(self$.bitset, v) - self - }, - - #' @description clear the bitset. - clear = function() { - bitset_clear(self$.bitset) - self - }, - - #' @description get the number of elements in the set. - size = function() bitset_size(self$.bitset), - - #' @description to "bitwise or" or union two bitsets. - #' @param other the other bitset. - or = function(other) { - bitset_or(self$.bitset, other$.bitset) - self - }, - - #' @description to "bitwise and" or intersect two bitsets. - #' @param other the other bitset. - and = function(other) { - bitset_and(self$.bitset, other$.bitset) - self - }, - - #' @description to "bitwise not" or complement a bitset. - #' @param inplace whether to overwrite the current bitset, default = TRUE - not = function(inplace = TRUE) { - Bitset$new(from = bitset_not(self$.bitset, inplace)) - }, - - #' @description to "bitwise xor" or get the symmetric difference of two bitset - #' (keep elements in either bitset but not in their intersection). - #' @param other the other bitset. - xor = function(other){ - bitset_xor(self$.bitset, other$.bitset) - self - }, - - #' @description Take the set difference of this bitset with another - #' (keep elements of this bitset which are not in \code{other}). - #' @param other the other bitset. - set_difference = function(other){ - bitset_set_difference(self$.bitset, other$.bitset) - self - }, - - #' @description sample a bitset. - #' @param rate the success probability for keeping each element, can be - #' a single value for all elements or a vector of unique - #' probabilities for keeping each element. - sample = function(rate) { - stopifnot(is.finite(rate), !is.null(rate)) - if (length(rate) == 1) { - bitset_sample(self$.bitset, rate) - } else { - bitset_sample_vector(self$.bitset, rate) - } - self - }, - - #' @description choose k random items in the bitset - #' @param k the number of items in the bitset to keep. The selection of - #' these k items from N total items in the bitset is random, and - #' k should be chosen such that \eqn{0 \le k \le N}. - choose = function(k) { - stopifnot(is.finite(k)) - stopifnot(k <= bitset_size(self$.bitset)) - stopifnot(k >= 0) - if (k < self$max_size) { - bitset_choose(self$.bitset, as.integer(k)) - } - self - }, - - #' @description returns a copy the bitset. - copy = function() Bitset$new(from = bitset_copy(self$.bitset)), - - #' @description return an integer vector of the elements - #' stored in this bitset. - to_vector = function() bitset_to_vector(self$.bitset) - - ) +#' @section Methods: +Bitset <- list( + #' ```{r echo=FALSE, results="asis"} + #' bitset_method_doc( + #' "new", + #' "create a bitset.", + #' static = TRUE, + #' size = "the size of the bitset.", + #' from = "pointer to an existing IterableBitset to use; if \\code{NULL} + #' make empty bitset, otherwise copy existing bitset." + #' ) + #' ``` + new = function(size, from = NULL) { + if (is.null(from)) { + bitset <- create_bitset(size) + } else { + stopifnot(inherits(from, "externalptr")) + bitset <- from + } + max_size <- bitset_max_size(bitset) + + self <- list( + .bitset = bitset, + max_size = max_size, + + #' ```{r echo=FALSE, results="asis"} + #' bitset_method_doc( + #' "insert", + #' "insert into the bitset.", + #' v = "an integer vector of elements to insert.") + #' ``` + insert = function(v) { + bitset_insert(self$.bitset, v) + self + }, + + #' ```{r echo=FALSE, results="asis"} + #' bitset_method_doc( + #' "remove", + #' "remove from the bitset.", + #' v = "an integer vector of elements (not indices) to remove.") + #' ``` + remove = function(v) { + bitset_remove(self$.bitset, v) + self + }, + + #' ```{r echo=FALSE, results="asis"} + #' bitset_method_doc( + #' "clear", + #' "clear the bitset.") + #' ``` + clear = function() { + bitset_clear(self$.bitset) + self + }, + + #' ```{r echo=FALSE, results="asis"} + #' bitset_method_doc( + #' "size", + #' "get the number of elements in the set.") + #' ``` + size = function() bitset_size(self$.bitset), + + #' ```{r echo=FALSE, results="asis"} + #' bitset_method_doc( + #' "or", + #' "to \"bitwise or\" or union two bitsets.", + #' other = "the other bitset.") + #' ``` + or = function(other) { + bitset_or(self$.bitset, other$.bitset) + self + }, + + #' ```{r echo=FALSE, results="asis"} + #' bitset_method_doc( + #' "and", + #' "to \"bitwise and\" or intersect two bitsets.", + #' other = "the other bitset.") + #' ``` + and = function(other) { + bitset_and(self$.bitset, other$.bitset) + self + }, + + #' ```{r echo=FALSE, results="asis"} + #' bitset_method_doc( + #' "not", + #' "to \"bitwise not\" or complement a bitset.", + #' inplace = "whether to overwrite the current bitset, default = TRUE") + #' ``` + not = function(inplace = TRUE) { + Bitset$new(from = bitset_not(self$.bitset, inplace)) + }, + + #' ```{r echo=FALSE, results="asis"} + #' bitset_method_doc( + #' "xor", + #' "to \"bitwise xor\" get the symmetric difference of two bitset + #' (keep elements in either bitset but not in their intersection).", + #' other = "the other bitset.") + #' ``` + xor = function(other){ + bitset_xor(self$.bitset, other$.bitset) + self + }, + + #' ```{r echo=FALSE, results="asis"} + #' bitset_method_doc( + #' "set_difference", + #' "Take the set difference of this bitset with another + #' (keep elements of this bitset which are not in \\code{other})", + #' other = "the other bitset.") + #' ``` + set_difference = function(other){ + bitset_set_difference(self$.bitset, other$.bitset) + self + }, + + #' ```{r echo=FALSE, results="asis"} + #' bitset_method_doc( + #' "sample", + #' "sample a bitset.", + #' rate = "the success probability for keeping each element, can be + #' a single value for all elements or a vector of unique + #' probabilities for keeping each element.") + #' ``` + sample = function(rate) { + stopifnot(is.finite(rate), !is.null(rate)) + if (length(rate) == 1) { + bitset_sample(self$.bitset, rate) + } else { + bitset_sample_vector(self$.bitset, rate) + } + self + }, + + #' ```{r echo=FALSE, results="asis"} + #' bitset_method_doc( + #' "choose", + #' "choose k random items in the bitset.", + #' k = "the number of items in the bitset to keep. The selection of + #' these k items from N total items in the bitset is random, and + #' k should be chosen such that \\eqn{0 \\le k \\le N}.") + #' ``` + choose = function(k) { + stopifnot(is.finite(k)) + stopifnot(k <= bitset_size(self$.bitset)) + stopifnot(k >= 0) + if (k < self$max_size) { + bitset_choose(self$.bitset, as.integer(k)) + } + self + }, + + #' ```{r echo=FALSE, results="asis"} + #' bitset_method_doc( + #' "copy", + #' "returns a copy of the bitset.") + #' ``` + copy = function() Bitset$new(from = bitset_copy(self$.bitset)), + + #' ```{r echo=FALSE, results="asis"} + #' bitset_method_doc( + #' "to_vector", + #' "return an integer vector of the elements stored in this bitset.") + #' ``` + to_vector = function() bitset_to_vector(self$.bitset) + ) + + class(self) <- 'Bitset' + self + } ) #' @title Filter a bitset diff --git a/man/Bitset.Rd b/man/Bitset.Rd index cf9a03e..9955742 100644 --- a/man/Bitset.Rd +++ b/man/Bitset.Rd @@ -10,271 +10,166 @@ efficiently perform set operations (union, intersection, complement, symmetric difference, set difference). WARNING: All operations are in-place so please use \code{$copy} if you would like to perform an operation without destroying your current bitset. -} -\section{Public fields}{ -\if{html}{\out{
}} -\describe{ -\item{\code{.bitset}}{a pointer to the underlying IterableBitset.} -\item{\code{max_size}}{the maximum size of the bitset.} -} -\if{html}{\out{
}} +This class is defined as a named list for performance reasons, but for most +intents and purposes it behaves just like an R6 class. } \section{Methods}{ -\subsection{Public methods}{ -\itemize{ -\item \href{#method-Bitset-new}{\code{Bitset$new()}} -\item \href{#method-Bitset-insert}{\code{Bitset$insert()}} -\item \href{#method-Bitset-remove}{\code{Bitset$remove()}} -\item \href{#method-Bitset-clear}{\code{Bitset$clear()}} -\item \href{#method-Bitset-size}{\code{Bitset$size()}} -\item \href{#method-Bitset-or}{\code{Bitset$or()}} -\item \href{#method-Bitset-and}{\code{Bitset$and()}} -\item \href{#method-Bitset-not}{\code{Bitset$not()}} -\item \href{#method-Bitset-xor}{\code{Bitset$xor()}} -\item \href{#method-Bitset-set_difference}{\code{Bitset$set_difference()}} -\item \href{#method-Bitset-sample}{\code{Bitset$sample()}} -\item \href{#method-Bitset-choose}{\code{Bitset$choose()}} -\item \href{#method-Bitset-copy}{\code{Bitset$copy()}} -\item \href{#method-Bitset-to_vector}{\code{Bitset$to_vector()}} -\item \href{#method-Bitset-clone}{\code{Bitset$clone()}} -} -} \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-Bitset-new}{}}} \subsection{Method \code{new()}}{ create a bitset. \subsection{Usage}{ -\if{html}{\out{
}}\preformatted{Bitset$new(size, from = NULL)}\if{html}{\out{
}} +\preformatted{Bitset$new(size, from)} } - \subsection{Arguments}{ -\if{html}{\out{
}} \describe{ \item{\code{size}}{the size of the bitset.} - \item{\code{from}}{pointer to an existing IterableBitset to use; if \code{NULL} make empty bitset, otherwise copy existing bitset.} } -\if{html}{\out{
}} } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-Bitset-insert}{}}} \subsection{Method \code{insert()}}{ insert into the bitset. \subsection{Usage}{ -\if{html}{\out{
}}\preformatted{Bitset$insert(v)}\if{html}{\out{
}} +\preformatted{b$insert(v)} } - \subsection{Arguments}{ -\if{html}{\out{
}} \describe{ \item{\code{v}}{an integer vector of elements to insert.} } -\if{html}{\out{
}} } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-Bitset-remove}{}}} \subsection{Method \code{remove()}}{ remove from the bitset. \subsection{Usage}{ -\if{html}{\out{
}}\preformatted{Bitset$remove(v)}\if{html}{\out{
}} +\preformatted{b$remove(v)} } - \subsection{Arguments}{ -\if{html}{\out{
}} \describe{ \item{\code{v}}{an integer vector of elements (not indices) to remove.} } -\if{html}{\out{
}} } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-Bitset-clear}{}}} \subsection{Method \code{clear()}}{ clear the bitset. \subsection{Usage}{ -\if{html}{\out{
}}\preformatted{Bitset$clear()}\if{html}{\out{
}} +\preformatted{b$clear()} } - } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-Bitset-size}{}}} \subsection{Method \code{size()}}{ get the number of elements in the set. \subsection{Usage}{ -\if{html}{\out{
}}\preformatted{Bitset$size()}\if{html}{\out{
}} +\preformatted{b$size()} } - } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-Bitset-or}{}}} \subsection{Method \code{or()}}{ to "bitwise or" or union two bitsets. \subsection{Usage}{ -\if{html}{\out{
}}\preformatted{Bitset$or(other)}\if{html}{\out{
}} +\preformatted{b$or(other)} } - \subsection{Arguments}{ -\if{html}{\out{
}} \describe{ \item{\code{other}}{the other bitset.} } -\if{html}{\out{
}} } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-Bitset-and}{}}} \subsection{Method \code{and()}}{ to "bitwise and" or intersect two bitsets. \subsection{Usage}{ -\if{html}{\out{
}}\preformatted{Bitset$and(other)}\if{html}{\out{
}} +\preformatted{b$and(other)} } - \subsection{Arguments}{ -\if{html}{\out{
}} \describe{ \item{\code{other}}{the other bitset.} } -\if{html}{\out{
}} } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-Bitset-not}{}}} \subsection{Method \code{not()}}{ to "bitwise not" or complement a bitset. \subsection{Usage}{ -\if{html}{\out{
}}\preformatted{Bitset$not(inplace = TRUE)}\if{html}{\out{
}} +\preformatted{b$not(inplace)} } - \subsection{Arguments}{ -\if{html}{\out{
}} \describe{ \item{\code{inplace}}{whether to overwrite the current bitset, default = TRUE} } -\if{html}{\out{
}} } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-Bitset-xor}{}}} \subsection{Method \code{xor()}}{ -to "bitwise xor" or get the symmetric difference of two bitset +to "bitwise xor" get the symmetric difference of two bitset (keep elements in either bitset but not in their intersection). \subsection{Usage}{ -\if{html}{\out{
}}\preformatted{Bitset$xor(other)}\if{html}{\out{
}} +\preformatted{b$xor(other)} } - \subsection{Arguments}{ -\if{html}{\out{
}} \describe{ \item{\code{other}}{the other bitset.} } -\if{html}{\out{
}} } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-Bitset-set_difference}{}}} \subsection{Method \code{set_difference()}}{ Take the set difference of this bitset with another -(keep elements of this bitset which are not in \code{other}). +(keep elements of this bitset which are not in \code{other}) \subsection{Usage}{ -\if{html}{\out{
}}\preformatted{Bitset$set_difference(other)}\if{html}{\out{
}} +\preformatted{b$set_difference(other)} } - \subsection{Arguments}{ -\if{html}{\out{
}} \describe{ \item{\code{other}}{the other bitset.} } -\if{html}{\out{
}} } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-Bitset-sample}{}}} \subsection{Method \code{sample()}}{ sample a bitset. \subsection{Usage}{ -\if{html}{\out{
}}\preformatted{Bitset$sample(rate)}\if{html}{\out{
}} +\preformatted{b$sample(rate)} } - \subsection{Arguments}{ -\if{html}{\out{
}} \describe{ \item{\code{rate}}{the success probability for keeping each element, can be a single value for all elements or a vector of unique probabilities for keeping each element.} } -\if{html}{\out{
}} } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-Bitset-choose}{}}} \subsection{Method \code{choose()}}{ -choose k random items in the bitset +choose k random items in the bitset. \subsection{Usage}{ -\if{html}{\out{
}}\preformatted{Bitset$choose(k)}\if{html}{\out{
}} +\preformatted{b$choose(k)} } - \subsection{Arguments}{ -\if{html}{\out{
}} \describe{ \item{\code{k}}{the number of items in the bitset to keep. The selection of these k items from N total items in the bitset is random, and k should be chosen such that \eqn{0 \le k \le N}.} } -\if{html}{\out{
}} } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-Bitset-copy}{}}} \subsection{Method \code{copy()}}{ -returns a copy the bitset. +returns a copy of the bitset. \subsection{Usage}{ -\if{html}{\out{
}}\preformatted{Bitset$copy()}\if{html}{\out{
}} +\preformatted{b$copy()} } - } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-Bitset-to_vector}{}}} \subsection{Method \code{to_vector()}}{ -return an integer vector of the elements -stored in this bitset. +return an integer vector of the elements stored in this bitset. \subsection{Usage}{ -\if{html}{\out{
}}\preformatted{Bitset$to_vector()}\if{html}{\out{
}} +\preformatted{b$to_vector()} } - } -\if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-Bitset-clone}{}}} -\subsection{Method \code{clone()}}{ -The objects of this class are cloneable with this method. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{Bitset$clone(deep = FALSE)}\if{html}{\out{
}} } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{deep}}{Whether to make a deep clone.} -} -\if{html}{\out{
}} -} -} -} diff --git a/tests/performance/bench-bitset.R b/tests/performance/bench-bitset.R index 87899a1..1f2abff 100644 --- a/tests/performance/bench-bitset.R +++ b/tests/performance/bench-bitset.R @@ -88,6 +88,20 @@ ggplot(data = core_ops_bset) + limit_args_grid <- data.frame(limit = 10^(3:8)) +create_bset <- bench::press( + { + bench::mark( + min_iterations = 100, + check = FALSE, + filter_gc = TRUE, + {Bitset$new(size = limit)} + ) + }, + .grid = limit_args_grid +) + +create_bset <- simplify_bench_output(create_bset) + # clear clear_bset <- bench::press( { @@ -119,6 +133,12 @@ not_bset <- bench::press( not_bset <- simplify_bench_output(out = not_bset) +ggplot(data = create_bset) + + geom_violin(aes(x = expression, y = time, color = expression, fill = expression)) + + facet_wrap(. ~ limit, scales = "free") + + coord_flip() + + ggtitle("Create benchmark") + ggplot(data = clear_bset) + geom_violin(aes(x = expression, y = time, color = expression, fill = expression)) + facet_wrap(. ~ limit, scales = "free") + @@ -257,4 +277,4 @@ filter_bset <- simplify_bench_output(filter_bset) ggplot(data = filter_bset) + geom_violin(aes(x = as.factor(expression), y = time, color = expression, fill = expression)) + facet_wrap(size ~ limit, scales = "free") + - ggtitle("Sampling operations benchmark: filter") \ No newline at end of file + ggtitle("Sampling operations benchmark: filter")