Skip to content

Commit 1508915

Browse files
tstrsplit: add rev argument for reverse indexing (#6341) (#7838)
adds implementation, tests, documentation and NEWS --------- Co-authored-by: Benjamin Schwendinger <52290390+ben-schwen@users.noreply.github.com> Co-authored-by: Benjamin Schwendinger <benjaminschwe@gmail.com>
1 parent 2f88b3e commit 1508915

4 files changed

Lines changed: 22 additions & 4 deletions

File tree

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636

3737
7. Rows can now be deleted by reference using `DT[i, .ROW := NULL]`, avoiding a full copy of the table for large row-removal operations, [#635](https://github.com/Rdatatable/data.table/issues/635). This has been one of data.table's most requested features. Target rows must be selected with the `i` expression, `by`/`keyby` are not supported, and keys/indices are cleared after deletion. The new experimental helper `setallocrow()` prepares columns for by-reference row operations. Thanks @arunsrinivasan for the feature request, @ben-schwen for the implementation, and @aitap for review and assistance.
3838

39+
8. `tstrsplit()` gains a `rev` argument to facilitate extracting elements anchored from the end of the string, [#6341](https://github.com/Rdatatable/data.table/issues/6341). This is especially useful when strings have a varying number of components and you only want to extract the last or second-to-last element. Thanks to @JBrownArcGen for the suggestion and @venom1204 for the implementation.
40+
3941
### BUG FIXES
4042

4143
1. `fread()` with `skip=0` and `(header=TRUE|FALSE)` no longer skips the first row when it has fewer fields than subsequent rows, [#7463](https://github.com/Rdatatable/data.table/issues/7463). Thanks @emayerhofer for the report and @ben-schwen for the fix.

R/transpose.R

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,15 @@ transpose = function(l, fill=NA, ignore.empty=FALSE, keep.names=NULL, make.names
2323
ans[]
2424
}
2525

26-
tstrsplit = function(x, ..., fill=NA, type.convert=FALSE, keep, names=FALSE) {
26+
tstrsplit = function(x, ..., fill=NA, type.convert=FALSE, keep, names=FALSE, rev=FALSE) {
2727
if (!isTRUEorFALSE(names) && !is.character(names))
2828
stopf("'names' must be TRUE/FALSE or a character vector.")
29-
ans = transpose(strsplit(as.character(x), ...), fill=fill, ignore.empty=FALSE)
29+
if (!isTRUEorFALSE(rev))
30+
stopf("'rev' must be TRUE or FALSE.")
31+
ans = strsplit(as.character(x), ...)
32+
if (rev) ans = lapply(ans, base::rev)
33+
ans = transpose(ans, fill=fill, ignore.empty=FALSE)
34+
3035
if (!missing(keep)) {
3136
keep = suppressWarnings(as.integer(keep))
3237
chk = min(keep) >= min(1L, length(ans)) & max(keep) <= length(ans)

inst/tests/tests.Rraw

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21850,3 +21850,8 @@ a = c(a=1L)
2185021850
test(2379.12, as.IDate(d) + 1L, as.IDate(as.Date(d) + 1L))
2185121851
test(2379.13, as.IDate("2020-01-01") + a, as.IDate(as.Date("2020-01-01") + a))
2185221852
test(2379.14, as.IDate(d) - 1L, as.IDate(as.Date(d) - 1L))
21853+
21854+
# #6341: tstrsplit() supports reverse indexing
21855+
test(2380.01, tstrsplit(c("ABC-DEF", "ABC-DEF-GHI", "ABC-DEF-GHI-JKL", "ABC-DEF-GHI-JKL-MNO"), "-", fixed=TRUE, keep=1:3, rev=TRUE), list(c("DEF", "GHI", "JKL", "MNO"), c("ABC", "DEF", "GHI", "JKL"), c(NA_character_, "ABC", "DEF", "GHI")))
21856+
test(2380.02, tstrsplit(c("A-B", "A-B-C"), "-", fixed=TRUE, rev=TRUE), list(c("B", "C"), c("A", "B"), c(NA_character_, "A")))
21857+
test(2380.03, tstrsplit("A-B", "-", rev=NA), error="'rev' must be TRUE or FALSE.")

man/tstrsplit.Rd

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
}
99

1010
\usage{
11-
tstrsplit(x, \dots, fill=NA, type.convert=FALSE, keep, names=FALSE)
11+
tstrsplit(x, \dots, fill=NA, type.convert=FALSE, keep, names=FALSE, rev=FALSE)
1212
}
1313
\arguments{
1414
\item{x}{The vector to split (and transpose).}
@@ -17,9 +17,10 @@ tstrsplit(x, \dots, fill=NA, type.convert=FALSE, keep, names=FALSE)
1717
\item{type.convert}{\code{TRUE} calls \code{\link{type.convert}} with \code{as.is=TRUE} on the columns. May also be a function, list of functions, or named list of functions to apply to each part; see examples. }
1818
\item{keep}{Specify indices corresponding to just those list elements to retain in the transposed result. Default is to return all.}
1919
\item{names}{\code{TRUE} auto names the list with \code{V1, V2} etc. Default (\code{FALSE}) is to return an unnamed list.}
20+
\item{rev}{logical. If \code{TRUE}, each split result is reversed before transposing, so \code{keep} indices count from the end of the string. This is useful when strings have varying numbers of components and extraction should be anchored to the end.}
2021
}
2122
\details{
22-
It internally calls \code{strsplit} first, and then \code{\link{transpose}} on the result.
23+
It internally calls \code{strsplit} first, optionally reverses each split result, and then calls \code{\link{transpose}}.
2324

2425
\code{names} argument can be used to return an auto named list, although this argument does not have any effect when used with \code{:=}, which requires names to be provided explicitly. It might be useful in other scenarios.
2526
}
@@ -62,6 +63,11 @@ DT[, tstrsplit(z, "/", type.convert=list(as.factor=1L, as.numeric))]
6263

6364
# convert the remaining using 'type.convert(x, as.is=TRUE)' (i.e. what type.convert=TRUE does)
6465
DT[, tstrsplit(v, " ", type.convert=list(as.IDate=4L, function(x) type.convert(x, as.is=TRUE)))]
66+
67+
# using rev to anchor from the end of the string
68+
x = c("ABC-DEF", "ABC-DEF-GHI", "ABC-DEF-GHI-JKL")
69+
tstrsplit(x, "-", fixed=TRUE, keep=1:2, rev=TRUE) # returns the last two elements
70+
6571
}
6672
\seealso{
6773
\code{\link{data.table}}, \code{\link{transpose}}, \code{\link[utils]{type.convert}}

0 commit comments

Comments
 (0)