Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch Bitset from an R6 class to a named list. #186

Merged
merged 6 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
338 changes: 219 additions & 119 deletions R/bitset.R
Original file line number Diff line number Diff line change
@@ -1,130 +1,230 @@
#' 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{<hr>}}")
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
#' 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.
#' @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
Expand Down
Loading
Loading