Skip to content

Commit 61368bf

Browse files
committed
Merge remote-tracking branch 'origin/master' into issue6663
2 parents e697d45 + fc60d3c commit 61368bf

9 files changed

Lines changed: 334 additions & 3 deletions

File tree

NAMESPACE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export(tstrsplit)
3636
export(frank)
3737
export(frankv)
3838
export(address)
39-
export(.SD,.N,.I,.GRP,.NGRP,.BY,.EACHI, measure, measurev, patterns)
39+
export(.SD,.N,.I,.GRP,.NGRP,.BY,.EACHI,.ROW, measure, measurev, patterns)
4040
# TODO(#6197): Export these.
4141
# export(., J)
4242
export(rleid)

R/data.table.R

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ methods::setPackageName("data.table",.global)
1111
# (1) add to man/special-symbols.Rd
1212
# (2) export() in NAMESPACE
1313
# (3) add to vignettes/datatable-importing.Rmd#globals section
14-
.SD = .N = .I = .GRP = .NGRP = .BY = .EACHI = NULL
14+
.SD = .N = .I = .GRP = .NGRP = .BY = .EACHI = .ROW = NULL
1515
# These are exported to prevent NOTEs from R CMD check, and checkUsage via compiler.
1616
# But also exporting them makes it clear (to users and other packages) that data.table uses these as symbols.
1717
# And NULL makes it clear (to the R's mask check on loading) that they're variables not functions.
@@ -1560,6 +1560,18 @@ replace_dot_alias = function(e) {
15601560
names(jsub)=""
15611561
jsub[[1L]]=as.name("list")
15621562
}
1563+
1564+
# Check for .ROW := NULL pattern (delete rows by reference)
1565+
if (identical(lhs, ".ROW") || identical(lhs, quote(.ROW))) {
1566+
if (!is.null(jsub) && !identical(jsub, quote(NULL)))
1567+
stopf(".ROW can only be used with := NULL to delete rows")
1568+
if (is.null(irows))
1569+
stopf(".ROW := NULL requires i= condition to specify rows to delete")
1570+
if (!missingby)
1571+
stopf(".ROW := NULL does not support 'by' or 'keyby'. To delete rows using by grouping, first compute the row indices (e.g. rows = DT[, .I[cond], by=grp]$V1) and then delete them DT[rows, .ROW := NULL].")
1572+
.Call(CdeleteRows, x, irows)
1573+
return(suppPrint(x))
1574+
}
15631575
av = all.vars(jsub,TRUE)
15641576
if (!is.atomic(lhs)) stopf("LHS of := must be a symbol, or an atomic vector (column names or positions).")
15651577
if (is.character(lhs)) {

inst/tests/tests.Rraw

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21684,6 +21684,98 @@ x = as.data.table(as.list(1:max_ppsize))
2168421684
test(2376, rbindlist(list(x)), x)
2168521685
rm(x, max_ppsize)
2168621686

21687+
# delete rows by reference #635
21688+
# atomic types and list columns
21689+
dt = data.table(
21690+
int = 1:5,
21691+
real = c(1.1, 2.2, 3.3, 4.4, 5.5),
21692+
char = letters[1:5],
21693+
lgl = c(TRUE, FALSE, TRUE, FALSE, TRUE),
21694+
cplx = as.complex(1:5),
21695+
raw_col = as.raw(1:5),
21696+
list_col = list(1L, 1:2, 1:3, 1:4, 1:5)
21697+
)
21698+
test(2377.01, copy(dt)[1L, .ROW := NULL], dt[-1])
21699+
test(2377.02, copy(dt)[1, .ROW := NULL], dt[-1])
21700+
test(2377.03, copy(dt)[c(TRUE, FALSE, FALSE, TRUE, FALSE), .ROW := NULL], dt[-c(1,4)])
21701+
test(2377.04, copy(dt)[int==1L, .ROW := NULL], dt[-1])
21702+
test(2377.05, copy(dt)[int<2L, .ROW := NULL], dt[-1])
21703+
test(2377.06, copy(dt)[-1, .ROW := NULL], dt[1])
21704+
# zero row or empty data.tables
21705+
dt = data.table()
21706+
test(2377.07, dt[logical(0), .ROW := NULL], dt)
21707+
dt = data.table(a=integer(0), b=character(0))
21708+
test(2377.08, dt[logical(0), .ROW := NULL], dt)
21709+
# multirow
21710+
dt = data.table(a=1:5, b=letters[1:5])
21711+
test(2377.09, copy(dt)[c(1L, 3L), .ROW := NULL], dt[c(2,4,5)])
21712+
test(2377.11, copy(dt)[1:2, .ROW := NULL], dt[3:5])
21713+
test(2377.12, copy(dt)[1:5, .ROW := NULL], dt[0])
21714+
# NA handling and edges case
21715+
dt = data.table(a=1:5, b=letters[1:5])
21716+
test(2377.13, copy(dt)[c(1L, NA_integer_, 3L), .ROW := NULL], dt[c(2,4,5)])
21717+
test(2377.14, copy(dt)[c(NA_integer_, NA_integer_), .ROW := NULL], dt)
21718+
test(2377.15, copy(dt)[c(TRUE, NA, FALSE, NA, TRUE), .ROW := NULL], dt[c(2,3,4)])
21719+
test(2377.16, copy(dt)[integer(0), .ROW := NULL], dt)
21720+
test(2377.17, copy(dt)[logical(0), .ROW := NULL], dt)
21721+
test(2377.18, copy(dt)[c(FALSE, FALSE, FALSE, FALSE, FALSE), .ROW := NULL], dt)
21722+
test(2377.19, copy(dt)[a > 100, .ROW := NULL], dt) # no matches
21723+
# Duplicate indices
21724+
dt = data.table(a=1:5, b=letters[1:5])
21725+
test(2377.20, copy(dt)[c(1L, 1L), .ROW := NULL], dt[-1])
21726+
test(2377.21, copy(dt)[c(1L, 1L, 2L, 2L), .ROW := NULL], dt[3:5])
21727+
test(2377.22, copy(dt)[c(3L, 1L, 3L, 1L), .ROW := NULL], dt[c(2,4,5)])
21728+
# integer64
21729+
if (test_bit64) {
21730+
dt = data.table(a=1:5, b=as.integer64(11:15))
21731+
test(2377.23, copy(dt)[c(1L, 3L), .ROW := NULL], dt[-c(1L,3L)])
21732+
test(2377.24, copy(dt)[1:5, .ROW := NULL], data.table(a=integer(0), b=integer64(0)))
21733+
}
21734+
# Date/IDate/ITime columns
21735+
dt = data.table(a=1:5, d=as.Date("2024-01-01") + 0:4, t=as.ITime(paste0(10:14, ":00:00")), dt=as.POSIXct("2024-01-01 12:00:00") + 3600*0:4)
21736+
test(2377.25, copy(dt)[c(1L, 3L), .ROW := NULL], dt[c(2,4,5)])
21737+
test(2377.26, copy(dt)[c(2L, 4L), .ROW := NULL]$d, as.Date("2024-01-01") + c(0,2,4))
21738+
# Factor columns
21739+
dt = data.table(a=1:5, f=factor(letters[1:5], levels=letters[1:10]))
21740+
test(2377.27, copy(dt)[c(1L, 3L), .ROW := NULL], dt[-c(1L,3L)])
21741+
test(2377.28, levels(copy(dt)[c(1L, 3L), .ROW := NULL]$f), letters[1:10])
21742+
dt = data.table(a=1:5, of=ordered(letters[1:5], levels=letters[5:1]))
21743+
test(2377.29, copy(dt)[c(2L, 4L), .ROW := NULL], dt[-c(2L,4L)])
21744+
test(2377.30, is.ordered(copy(dt)[c(2, 4L), .ROW := NULL]$of))
21745+
# Keys - should be cleared after deletion
21746+
dt = data.table(a=5:1, b=letters[1:5], key="a")
21747+
test(2377.31, key(copy(dt)[1L, .ROW := NULL]), NULL)
21748+
test(2377.32, haskey(copy(dt)[1L, .ROW := NULL]), FALSE)
21749+
# Indices - should be cleared after deletion
21750+
dt = data.table(a=1:5, b=letters[1:5], c=5:1)
21751+
setindex(dt, b)
21752+
test(2377.33, indices(copy(dt)[1L, .ROW := NULL]), NULL)
21753+
# row names
21754+
dt = data.table(a=1:5, b=letters[1:5])
21755+
test(2377.34, attr(copy(dt)[c(1L, 3L), .ROW := NULL], "row.names"), 1:3)
21756+
# selfref check
21757+
test(2377.35, selfrefok(copy(dt)[1L, .ROW := NULL]), 1L)
21758+
# errors
21759+
dt = data.table(a=1:4, g=1:2)
21760+
test(2377.36, dt[1L, .ROW := 1L], error=".ROW can only be used with := NULL")
21761+
test(2377.37, dt[1L, .ROW := "delete"], error=".ROW can only be used with := NULL")
21762+
test(2377.38, dt[1L, .ROW := FALSE], error=".ROW can only be used with := NULL")
21763+
test(2377.39, dt[, .ROW := NULL], error=".ROW := NULL requires i= condition")
21764+
test(2377.40, dt[1L, .ROW := NULL, by=g], error=".ROW := NULL does not support 'by' or 'keyby'")
21765+
# large table
21766+
dt = data.table(a=1:20000, b=rep(letters, length.out=20000))
21767+
idx = seq(1L, 20000L, by=2L)
21768+
test(2377.41, copy(dt)[idx, .ROW := NULL], dt[-idx])
21769+
# Chaining and complexer i expressions
21770+
dt = data.table(a=1:10, b=letters[1:10])
21771+
test(2377.42, copy(dt)[a>2, .ROW := NULL][b=="a"], data.table(a=1L, b="a"))
21772+
test(2377.43, copy(dt)[a %% 2 == 0, .ROW := NULL], dt[a %% 2 != 0])
21773+
test(2377.44, copy(dt)[!(a < 5 & b != "d"), .ROW := NULL], dt[1:3])
21774+
# make columns resizable
21775+
dt = data.table(a=1:3)
21776+
test(2377.91, truelength(dt$a), 0L)
21777+
test(2377.92, {setallocrow(dt); truelength(dt$a)}, 3L)
21778+
2168721779
#6663 Option to print the number of columns
2168821780
test(2377.1, capture.output(print(as.data.table(iris)))[1L], "Number of columns: 5", options=list(datatable.show.ncols=TRUE))
2168921781
test(2377.2, any(grepl("^Number of columns: 5$", capture.output(print(setkey(as.data.table(iris), Sepal.Length))))), TRUE, options=list(datatable.show.ncols=TRUE))

man/assign.Rd

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,16 @@ set(x, i = NULL, j, value)
5757
DT[, colD := if (.N > 2) sum(v) else integer(0), by = g] # zero-length RHS is treated as a no-op for groups with <= 2 rows
5858
DT[,`:=`(new1 = sum(colB), new2 = sum(colC))] # Functional form
5959
DT[, let(new1 = sum(colB), new2 = sum(colC))] # New alias for functional form.
60+
DT[i, .ROW := NULL] # delete rows by reference.
6061
}
6162

6263
The \code{\link{.Last.updated}} variable contains the number of rows updated by the most recent \code{:=} or \code{set} calls, which may be useful, for example, in production settings for testing assumptions about the number of rows affected by a statement; see \code{\link{.Last.updated}} for details.
6364

6465
Note that for efficiency no check is performed for duplicate assignments, i.e. if multiple values are passed for assignment to the same index, assignment to this index will occur repeatedly and sequentially; for a given use case, consider whether it makes sense to create your own test for duplicates, e.g. in production code.
6566

67+
Note that \code{.ROW := NULL} is a special case used to delete rows by reference. Unlike column assignment, this requires an \code{i} expression to specify which rows to delete, and does not support \code{by} or \code{keyby}.
68+
To delete rows using a per-group condition, first compute the indices (\code{rows = DT[, .I[cond], by=grp]$V1}) then delete \code{DT[rows, .ROW := NULL]}. See \code{\link{.ROW}} or \code{\link{special-symbols}} for details.
69+
6670
All of the following result in a friendly error (by design) :
6771

6872
\preformatted{
@@ -159,6 +163,13 @@ set(DT, j = c("b", "d"), value = list(200L, 300L))
159163
## Set values for multiple columns with multiple specified rows.
160164
set(DT, c(1L, 3L), c("b", "d"), value = list(500L, 800L))
161165

166+
# Delete rows by reference
167+
DT = data.table(a=1:10, b=letters[1:10])
168+
DT[c(2,4,6), .ROW := NULL] # delete rows 2, 4, and 6
169+
DT
170+
DT[a>5, .ROW := NULL] # delete rows where a>5
171+
DT
172+
162173
\dontrun{
163174
# Speed example:
164175

man/special-symbols.Rd

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99
\alias{.EACHI}
1010
\alias{.NGRP}
1111
\alias{.NATURAL}
12+
\alias{.ROW}
1213
\title{ Special symbols }
1314
\description{
1415
\code{.SD}, \code{.BY}, \code{.N}, \code{.I}, \code{.GRP}, and \code{.NGRP} are \emph{read-only} symbols for use in \code{j}. \code{.N} can be used in \code{i} as well. \code{.I} can be used in \code{by} as well. See the vignettes, Details and Examples here and in \code{\link{data.table}}.
1516
\code{.EACHI} is a symbol passed to \code{by}; i.e. \code{by=.EACHI}, \code{.NATURAL} is a symbol passed to \code{on}; i.e. \code{on=.NATURAL}
17+
\code{.ROW} is a symbol used with \code{:= NULL} to delete rows by reference. See also \code{\link{assign}}.
1618
}
1719
\details{
1820
The bindings of these variables are locked and attempting to assign to them will generate an error. If you wish to manipulate \code{.SD} before returning it, take a \code{\link{copy}(.SD)} first (see FAQ 4.5). Using \code{:=} in the \code{j} of \code{.SD} is reserved for future use as a (tortuously) flexible way to update \code{DT} by reference by group (even when groups are not contiguous in an ad hoc by).
@@ -32,6 +34,8 @@
3234

3335
\code{.NATURAL} is defined as \code{NULL} but its value is not used. Its usage is \code{on=.NATURAL} (alternative of \code{X[on=Y]}) which joins two tables on their common column names, performing a natural join; see \code{\link{data.table}}'s \code{on} argument for more details.
3436
37+
\code{.ROW} is a symbol that can only be used with \code{:= NULL} to delete rows by reference. When you use \code{DT[i, .ROW := NULL]}, the rows matching the \code{i} expression are removed from \code{DT} in-place. This is an efficient way to delete rows without copying the entire data.table. The \code{i} argument is required and \code{by}/\code{keyby} are not supported. After deletion, any keys and indices on \code{DT} are cleared. See \code{\link{:=}} for more on reference semantics.
38+
3539
Note that \code{.N} in \code{i} is computed up-front, while that in \code{j} applies \emph{after filtering in \code{i}}. That means that even absent grouping, \code{.N} in \code{i} can be different from \code{.N} in \code{j}. See Examples.
3640
3741
Note also that you should consider these symbols read-only and of limited scope -- internal data.table code might manipulate them in unexpected ways, and as such their bindings are locked. There are subtle ways to wind up with the wrong object, especially when attempting to copy their values outside a grouping context. See examples; when in doubt, \code{copy()} is your friend.
@@ -72,5 +76,12 @@ DT[, .(min(.SD[,-1])), by=.I]
7276
# Do not expect this to correctly append the value of .BY in each group; copy(.BY) will work.
7377
by_tracker = list()
7478
DT[, { append(by_tracker, .BY); sum(v) }, by=x]
79+
80+
# .ROW to delete rows by reference
81+
DT = data.table(a=1:5, b=letters[1:5])
82+
DT[c(2,4), .ROW := NULL]
83+
DT
84+
DT[a>2, .ROW := NULL]
85+
DT
7586
}
7687
\keyword{ data }

src/data.table.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,9 @@ void copyVectorElements(SEXP dst, SEXP src, R_xlen_t n, bool deep_copy, const ch
334334
SEXP copyAsPlain(SEXP x, R_xlen_t overalloc);
335335
SEXP allocrow(SEXP dt, R_xlen_t n);
336336
void copySharedColumns(SEXP x);
337+
338+
// deleterows.c
339+
SEXP deleteRows(SEXP dt, SEXP rows_to_delete);
337340
SEXP lock(SEXP x);
338341
SEXP unlock(SEXP x);
339342
bool islocked(SEXP x);

src/deleterows.c

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
#include "data.table.h"
2+
3+
static void computePrefixSum(const int *keep, int *dest, R_xlen_t n, int nthreads);
4+
static void compactVectorRaw(SEXP col, const int *dest, const int *keep, R_xlen_t new_nrow, R_xlen_t old_nrow);
5+
6+
SEXP deleteRows(SEXP dt, SEXP rows_to_delete) {
7+
if (!isNewList(dt))
8+
internal_error(__func__, "deleteRows received non-list dt"); // #nocov
9+
if (!xlength(dt)) return dt; // zero-column data.table
10+
11+
const R_xlen_t ncol = length(dt);
12+
const R_xlen_t old_nrow = length(VECTOR_ELT(dt, 0));
13+
int nprotect = 0;
14+
15+
if (old_nrow == 0) return dt;
16+
17+
if (!isInteger(rows_to_delete) && !isLogical(rows_to_delete))
18+
internal_error(__func__, "rows_to_delete must be logical, integer, or numeric"); // #nocov
19+
20+
int *keep = (int *)R_alloc(old_nrow, sizeof(int));
21+
const R_xlen_t n = length(rows_to_delete);
22+
for (R_xlen_t i = 0; i < old_nrow; i++) keep[i] = 1;
23+
int *idx = INTEGER(rows_to_delete);
24+
for (R_xlen_t j = 0; j < n; j++) {
25+
if (idx[j] == NA_INTEGER) continue;
26+
// should be checked from irows in [
27+
if (idx[j] < 1 || idx[j] > old_nrow) internal_error(__func__, "Row index %d out of range [1, %lld]", idx[j], (long long)old_nrow); //# nocov
28+
keep[idx[j] - 1] = 0;
29+
}
30+
31+
R_xlen_t new_nrow = 0;
32+
for (R_xlen_t i = 0; i < old_nrow; i++) new_nrow += keep[i];
33+
if (new_nrow == old_nrow) return dt;
34+
35+
int *dest = (int *)R_alloc(old_nrow, sizeof(int));
36+
const int nthreads = getDTthreads(old_nrow, true);
37+
computePrefixSum(keep, dest, old_nrow, nthreads);
38+
39+
// Compact each column
40+
for (R_xlen_t j = 0; j < ncol; j++) {
41+
SEXP col = VECTOR_ELT(dt, j);
42+
if (!R_isResizable(col)) {
43+
// catered for ALTREP above
44+
SEXP newcol = PROTECT(copyAsPlain(col, 0)); nprotect++;
45+
SET_VECTOR_ELT(dt, j, newcol);
46+
col = newcol;
47+
}
48+
compactVectorRaw(col, dest, keep, new_nrow, old_nrow);
49+
R_resizeVector(col, new_nrow);
50+
SET_VECTOR_ELT(dt, j, col);
51+
}
52+
53+
SEXP rownames = PROTECT(getAttrib(dt, R_RowNamesSymbol)); nprotect++;
54+
if (!isNull(rownames)) {
55+
// create them from scratch like in dogroups or subset to avoid R internal issues
56+
SEXP rn = PROTECT(allocVector(INTSXP, 2)); nprotect++;
57+
INTEGER(rn)[0] = NA_INTEGER;
58+
INTEGER(rn)[1] = -(int)new_nrow;
59+
setAttrib(dt, R_RowNamesSymbol, rn);
60+
}
61+
62+
// Clear key and indices
63+
setAttrib(dt, install("sorted"), R_NilValue);
64+
setAttrib(dt, install("index"), R_NilValue);
65+
66+
UNPROTECT(nprotect);
67+
return dt;
68+
}
69+
70+
// Parallel prefix sum (exclusive scan)
71+
// Two-pass algorithm: first count per thread, then scan, then local prefix sum
72+
static void computePrefixSum(const int *keep, int *dest, R_xlen_t n, int nthreads) {
73+
if (nthreads == 1) {
74+
// Sequential version
75+
int sum = 0;
76+
for (R_xlen_t i = 0; i < n; i++) {
77+
dest[i] = sum;
78+
sum += keep[i];
79+
}
80+
return;
81+
}
82+
83+
// Parallel version with two passes
84+
int *thread_counts = (int *)R_alloc(nthreads, sizeof(int));
85+
86+
// Pass 1: Count keeps per thread
87+
#pragma omp parallel num_threads(nthreads)
88+
{
89+
const int tid = omp_get_thread_num();
90+
const R_xlen_t chunk_size = (n + nthreads - 1) / nthreads;
91+
const R_xlen_t start = tid * chunk_size;
92+
const R_xlen_t end = (start + chunk_size > n) ? n : start + chunk_size;
93+
94+
int local_count = 0;
95+
for (R_xlen_t i = start; i < end; i++) {
96+
local_count += keep[i];
97+
}
98+
thread_counts[tid] = local_count;
99+
}
100+
101+
// Sequential scan of thread counts to get offsets
102+
int *thread_offsets = (int *)R_alloc(nthreads, sizeof(int));
103+
thread_offsets[0] = 0;
104+
for (int t = 1; t < nthreads; t++) {
105+
thread_offsets[t] = thread_offsets[t-1] + thread_counts[t-1];
106+
}
107+
108+
// Pass 2: Compute local prefix sum with offset
109+
#pragma omp parallel num_threads(nthreads)
110+
{
111+
const int tid = omp_get_thread_num();
112+
const R_xlen_t chunk_size = (n + nthreads - 1) / nthreads;
113+
const R_xlen_t start = tid * chunk_size;
114+
const R_xlen_t end = (start + chunk_size > n) ? n : start + chunk_size;
115+
116+
int local_sum = thread_offsets[tid];
117+
for (R_xlen_t i = start; i < end; i++) {
118+
dest[i] = local_sum;
119+
local_sum += keep[i];
120+
}
121+
}
122+
}
123+
124+
#define COMPACT(CTYPE, ACCESSOR) { \
125+
CTYPE *p = ACCESSOR(col); \
126+
R_xlen_t i = 0; \
127+
while (i < old_nrow) { \
128+
if (!keep[i]) { \
129+
i++; \
130+
continue; \
131+
} \
132+
R_xlen_t run_start = i; \
133+
int target_idx = dest[i]; \
134+
while (i < old_nrow && keep[i]) i++; \
135+
size_t run_len = i - run_start; \
136+
if (target_idx != run_start) { \
137+
memmove(p + target_idx, p + run_start, run_len * sizeof(CTYPE)); \
138+
} \
139+
} \
140+
}
141+
142+
143+
// Type-specific stream compaction
144+
static void compactVectorRaw(SEXP col, const int *dest, const int *keep,
145+
R_xlen_t new_nrow, R_xlen_t old_nrow) {
146+
switch(TYPEOF(col)) {
147+
case INTSXP:
148+
case LGLSXP: {
149+
COMPACT(int, INTEGER);
150+
break;
151+
}
152+
case REALSXP: {
153+
COMPACT(double, REAL);
154+
break;
155+
}
156+
case CPLXSXP: {
157+
COMPACT(Rcomplex, COMPLEX);
158+
break;
159+
}
160+
case RAWSXP: {
161+
COMPACT(Rbyte, RAW);
162+
break;
163+
}
164+
case STRSXP: {
165+
for (R_xlen_t i = 0; i < old_nrow; i++) {
166+
if (keep[i]) SET_STRING_ELT(col, dest[i], STRING_ELT(col, i));
167+
}
168+
break;
169+
}
170+
case VECSXP: {
171+
for (R_xlen_t i = 0; i < old_nrow; i++) {
172+
if (keep[i]) SET_VECTOR_ELT(col, dest[i], VECTOR_ELT(col, i));
173+
}
174+
break;
175+
}
176+
default: // #nocov
177+
error("Unsupported column type %s", type2char(TYPEOF(col))); // #nocov
178+
}
179+
}

src/init.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ static const R_CallMethodDef callMethods[] = {
9797
{"Cfrank", (DL_FUNC)&frank, -1},
9898
{"Cdt_na", (DL_FUNC)&dt_na, -1},
9999
{"Callocrowwrapper", (DL_FUNC)&allocrowwrapper, 2},
100+
{"CdeleteRows", (DL_FUNC)&deleteRows, 2},
100101
{"Clookup", (DL_FUNC)&lookup, -1},
101102
{"Coverlaps", (DL_FUNC)&overlaps, -1},
102103
{"Cwhichwrapper", (DL_FUNC)&whichwrapper, -1},

0 commit comments

Comments
 (0)