Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
abb5cf0
Test whether functions preserve names (cf #575).
jonovik Sep 4, 2025
e352c08
Preserve names where sensible.
jonovik Sep 4, 2025
c1d2da4
Remove unnecessary check for if (length(out) == length(string)).
jonovik Sep 5, 2025
587a7da
Add tests for dropping names if string is scalar and pattern/replacem…
jonovik Sep 5, 2025
f2a5ff2
Add helper copy_names(to, from) and use where it makes the code more …
jonovik Sep 8, 2025
e8350be
Remove redundant comments.
jonovik Sep 8, 2025
bf3056e
Remove unnecessary guards.
jonovik Sep 8, 2025
0f005d2
Rewrite to keep below 80 character line length.
jonovik Sep 8, 2025
6efaf97
Restore check for length of pattern vs result in str_extract_all().
jonovik Sep 8, 2025
a0025ea
Move tests from test-preserve-names.R into each function's test file.
jonovik Sep 8, 2025
aa98d94
Switch argument order to copy_names(from, to).
jonovik Sep 8, 2025
32ee982
Revert compat-purrr.R to ad7fe11.
jonovik Sep 8, 2025
195159c
Add keep_names() to preserve names from `string` when suitable.
jonovik Sep 8, 2025
ec51727
Have str_subset() drop NAs from string.
jonovik Sep 9, 2025
982ff70
Refactor keep_names() and copy_names().
jonovik Sep 10, 2025
9471b98
Ensure str_unique() always returns character.
jonovik Sep 10, 2025
7a12f4d
Don't special-case if string is logical NA. Restore switch() in str_s…
jonovik Sep 11, 2025
463bb58
Merge commit 'fc4e4940f21f63a6bbe55a01ee11f9800c99af73'
hadley Sep 15, 2025
669a0e7
Explain condition for copy_names() in str_sub() and str_pad().
jonovik Sep 15, 2025
8d73956
Merged origin/main into jonovik-575-preserve-names
hadley Sep 22, 2025
19b9575
Add news bullet
hadley Sep 22, 2025
b9b36fc
Reduce comments slightly
hadley Sep 22, 2025
47c92cd
Add helper
hadley Sep 22, 2025
7264eb3
Marginally simplify tests
hadley Sep 22, 2025
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
15 changes: 7 additions & 8 deletions R/case.R
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,29 @@ NULL
#' @rdname case
str_to_upper <- function(string, locale = "en") {
check_string(locale)

stri_trans_toupper(string, locale = locale)
copy_names(string, stri_trans_toupper(string, locale = locale))
}
#' @export
#' @rdname case
str_to_lower <- function(string, locale = "en") {
check_string(locale)

stri_trans_tolower(string, locale = locale)
copy_names(string, stri_trans_tolower(string, locale = locale))
}
#' @export
#' @rdname case
str_to_title <- function(string, locale = "en") {
check_string(locale)

stri_trans_totitle(string, opts_brkiter = stri_opts_brkiter(locale = locale))
out <- stri_trans_totitle(string,
opts_brkiter = stri_opts_brkiter(locale = locale))
copy_names(string, out)
}
#' @export
#' @rdname case
str_to_sentence <- function(string, locale = "en") {
check_string(locale)

stri_trans_totitle(
out <- stri_trans_totitle(
string,
opts_brkiter = stri_opts_brkiter(type = "sentence", locale = locale)
)
copy_names(string, out)
}
2 changes: 1 addition & 1 deletion R/conv.R
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@
str_conv <- function(string, encoding) {
check_string(encoding)

stri_conv(string, encoding, "UTF-8")
copy_names(string, stri_conv(string, encoding, "UTF-8"))
}
3 changes: 2 additions & 1 deletion R/count.R
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,12 @@
str_count <- function(string, pattern = "") {
check_lengths(string, pattern)

switch(type(pattern),
out <- switch(type(pattern),
empty = ,
bound = stri_count_boundaries(string, opts_brkiter = opts(pattern)),
fixed = stri_count_fixed(string, pattern, opts_fixed = opts(pattern)),
coll = stri_count_coll(string, pattern, opts_collator = opts(pattern)),
regex = stri_count_regex(string, pattern, opts_regex = opts(pattern))
)
if (keep_names(string, pattern)) copy_names(string, out) else out
}
16 changes: 11 additions & 5 deletions R/detect.R
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,15 @@ str_detect <- function(string, pattern, negate = FALSE) {
check_lengths(string, pattern)
check_bool(negate)

switch(type(pattern),
out <- switch(type(pattern),
empty = no_empty(),
bound = no_boundary(),
fixed = stri_detect_fixed(string, pattern, negate = negate, opts_fixed = opts(pattern)),
coll = stri_detect_coll(string, pattern, negate = negate, opts_collator = opts(pattern)),
regex = stri_detect_regex(string, pattern, negate = negate, opts_regex = opts(pattern))
)

if (keep_names(string, pattern)) copy_names(string, out) else out
}

#' Detect the presence/absence of a match at the start/end
Expand Down Expand Up @@ -78,7 +80,7 @@ str_starts <- function(string, pattern, negate = FALSE) {
check_lengths(string, pattern)
check_bool(negate)

switch(type(pattern),
out <- switch(type(pattern),
empty = no_empty(),
bound = no_boundary(),
fixed = stri_startswith_fixed(string, pattern, negate = negate, opts_fixed = opts(pattern)),
Expand All @@ -88,6 +90,7 @@ str_starts <- function(string, pattern, negate = FALSE) {
stri_detect_regex(string, pattern2, negate = negate, opts_regex = opts(pattern))
}
)
if (keep_names(string, pattern)) copy_names(string, out) else out
}

#' @rdname str_starts
Expand All @@ -96,7 +99,7 @@ str_ends <- function(string, pattern, negate = FALSE) {
check_lengths(string, pattern)
check_bool(negate)

switch(type(pattern),
out <- switch(type(pattern),
empty = no_empty(),
bound = no_boundary(),
fixed = stri_endswith_fixed(string, pattern, negate = negate, opts_fixed = opts(pattern)),
Expand All @@ -106,6 +109,7 @@ str_ends <- function(string, pattern, negate = FALSE) {
stri_detect_regex(string, pattern2, negate = negate, opts_regex = opts(pattern))
}
)
if (keep_names(string, pattern)) copy_names(string, out) else out
}

#' Detect a pattern in the same way as `SQL`'s `LIKE` and `ILIKE` operators
Expand Down Expand Up @@ -166,7 +170,8 @@ str_like <- function(string, pattern, ignore_case = deprecated()) {
}

pattern <- regex(like_to_regex(pattern), ignore_case = FALSE)
stri_detect_regex(string, pattern, opts_regex = opts(pattern))
out <- stri_detect_regex(string, pattern, opts_regex = opts(pattern))
if (keep_names(string, pattern)) copy_names(string, out) else out
}

#' @export
Expand All @@ -179,7 +184,8 @@ str_ilike <- function(string, pattern) {
}

pattern <- regex(like_to_regex(pattern), ignore_case = TRUE)
stri_detect_regex(string, pattern, opts_regex = opts(pattern))
out <- stri_detect_regex(string, pattern, opts_regex = opts(pattern))
if (keep_names(string, pattern)) copy_names(string, out) else out
}

like_to_regex <- function(pattern) {
Expand Down
6 changes: 4 additions & 2 deletions R/dup.R
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ str_dup <- function(string, times, sep = NULL) {
check_string(sep, allow_null = TRUE)

if (is.null(sep)) {
stri_dup(input$string, input$times)
out <- stri_dup(input$string, input$times)
} else {
map_chr(seq_along(input$string), function(i) {
out <- map_chr(seq_along(input$string), function(i) {
paste(rep(string[[i]], input$times[[i]]), collapse = sep)
})
}
names(out) <- names(input$string)
out
}
3 changes: 2 additions & 1 deletion R/escape.R
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@
#' str_detect(c("a", "."), ".")
#' str_detect(c("a", "."), str_escape("."))
str_escape <- function(string) {
str_replace_all(string, "([.^$\\\\|*+?{}\\[\\]()])", "\\\\\\1")
out <- str_replace_all(string, "([.^$\\\\|*+?{}\\[\\]()])", "\\\\\\1")
copy_names(string, out)
}
31 changes: 18 additions & 13 deletions R/extract.R
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,20 @@
#' str_extract_all("This is, suprisingly, a sentence.", boundary("word"))
str_extract <- function(string, pattern, group = NULL) {
if (!is.null(group)) {
return(str_match(string, pattern)[, group + 1])
out <- str_match(string, pattern)[, group + 1]
return(if (keep_names(string, pattern)) copy_names(string, out) else out)
}

check_lengths(string, pattern)
switch(type(pattern),
empty = stri_extract_first_boundaries(string, opts_brkiter = opts(pattern)),
bound = stri_extract_first_boundaries(string, opts_brkiter = opts(pattern)),
fixed = stri_extract_first_fixed(string, pattern, opts_fixed = opts(pattern)),
coll = stri_extract_first_coll(string, pattern, opts_collator = opts(pattern)),
regex = stri_extract_first_regex(string, pattern, opts_regex = opts(pattern))
opt <- opts(pattern)
out <- switch(type(pattern),
empty = stri_extract_first_boundaries(string, opts_brkiter = opt),
bound = stri_extract_first_boundaries(string, opts_brkiter = opt),
fixed = stri_extract_first_fixed(string, pattern, opts_fixed = opt),
coll = stri_extract_first_coll(string, pattern, opts_collator = opt),
regex = stri_extract_first_regex(string, pattern, opts_regex = opt)
)
if (keep_names(string, pattern)) copy_names(string, out) else out
}

#' @rdname str_extract
Expand All @@ -59,16 +62,18 @@ str_extract_all <- function(string, pattern, simplify = FALSE) {
check_lengths(string, pattern)
check_bool(simplify)

switch(type(pattern),
opt <- opts(pattern)
out <- switch(type(pattern),
empty = stri_extract_all_boundaries(string,
simplify = simplify, omit_no_match = TRUE, opts_brkiter = opts(pattern)),
simplify = simplify, omit_no_match = TRUE, opts_brkiter = opt),
bound = stri_extract_all_boundaries(string,
simplify = simplify, omit_no_match = TRUE, opts_brkiter = opts(pattern)),
simplify = simplify, omit_no_match = TRUE, opts_brkiter = opt),
fixed = stri_extract_all_fixed(string, pattern,
simplify = simplify, omit_no_match = TRUE, opts_fixed = opts(pattern)),
simplify = simplify, omit_no_match = TRUE, opts_fixed = opt),
coll = stri_extract_all_coll(string, pattern,
simplify = simplify, omit_no_match = TRUE, opts_collator = opts(pattern)),
simplify = simplify, omit_no_match = TRUE, opts_collator = opt),
regex = stri_extract_all_regex(string, pattern,
simplify = simplify, omit_no_match = TRUE, opts_regex = opts(pattern))
simplify = simplify, omit_no_match = TRUE, opts_regex = opt)
)
if (keep_names(string, pattern)) copy_names(string, out) else out
}
4 changes: 2 additions & 2 deletions R/length.R
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@
#' # Because the second element is made up of a u + an accent
#' str_sub(u, 1, 1)
str_length <- function(string) {
stri_length(string)
copy_names(string, stri_length(string))
}

#' @export
#' @rdname str_length
str_width <- function(string) {
stri_width(string)
copy_names(string, stri_width(string))
}
6 changes: 4 additions & 2 deletions R/locate.R
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,14 @@
str_locate <- function(string, pattern) {
check_lengths(string, pattern)

switch(type(pattern),
out <- switch(type(pattern),
empty = ,
bound = stri_locate_first_boundaries(string, opts_brkiter = opts(pattern)),
fixed = stri_locate_first_fixed(string, pattern, opts_fixed = opts(pattern)),
coll = stri_locate_first_coll(string, pattern, opts_collator = opts(pattern)),
regex = stri_locate_first_regex(string, pattern, opts_regex = opts(pattern))
)
if (keep_names(string, pattern)) copy_names(string, out) else out
}

#' @rdname str_locate
Expand All @@ -52,13 +53,14 @@ str_locate_all <- function(string, pattern) {
check_lengths(string, pattern)
opts <- opts(pattern)

switch(type(pattern),
out <- switch(type(pattern),
empty = ,
bound = stri_locate_all_boundaries(string, omit_no_match = TRUE, opts_brkiter = opts),
fixed = stri_locate_all_fixed(string, pattern, omit_no_match = TRUE, opts_fixed = opts),
regex = stri_locate_all_regex(string, pattern, omit_no_match = TRUE, opts_regex = opts),
coll = stri_locate_all_coll(string, pattern, omit_no_match = TRUE, opts_collator = opts)
)
if (keep_names(string, pattern)) copy_names(string, out) else out
}


Expand Down
6 changes: 4 additions & 2 deletions R/match.R
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,11 @@ str_match <- function(string, pattern) {
cli::cli_abort(tr_("{.arg pattern} must be a regular expression."))
}

stri_match_first_regex(string,
out <- stri_match_first_regex(string,
pattern,
opts_regex = opts(pattern)
)
if (keep_names(string, pattern)) copy_names(string, out) else out
}

#' @rdname str_match
Expand All @@ -68,9 +69,10 @@ str_match_all <- function(string, pattern) {
cli::cli_abort(tr_("{.arg pattern} must be a regular expression."))
}

stri_match_all_regex(string,
out <- stri_match_all_regex(string,
pattern,
omit_no_match = TRUE,
opts_regex = opts(pattern)
)
if (keep_names(string, pattern)) copy_names(string, out) else out
}
3 changes: 2 additions & 1 deletion R/pad.R
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ str_pad <- function(string, width, side = c("left", "right", "both"), pad = " ",
side <- arg_match(side)
check_bool(use_width)

switch(side,
out <- switch(side,
left = stri_pad_left(string, width, pad = pad, use_length = !use_width),
right = stri_pad_right(string, width, pad = pad, use_length = !use_width),
both = stri_pad_both(string, width, pad = pad, use_length = !use_width)
)
if (length(out) == length(string)) copy_names(string, out) else out
}
9 changes: 5 additions & 4 deletions R/replace.R
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ str_replace <- function(string, pattern, replacement) {

check_lengths(string, pattern, replacement)

switch(type(pattern),
out <- switch(type(pattern),
empty = no_empty(),
bound = no_boundary(),
fixed = stri_replace_first_fixed(string, pattern, replacement,
Expand All @@ -86,6 +86,7 @@ str_replace <- function(string, pattern, replacement) {
regex = stri_replace_first_regex(string, pattern, fix_replacement(replacement),
opts_regex = opts(pattern))
)
if (keep_names(string, pattern)) copy_names(string, out) else out
}

#' @export
Expand All @@ -107,7 +108,7 @@ str_replace_all <- function(string, pattern, replacement) {
}


switch(type(pattern),
out <- switch(type(pattern),
empty = no_empty(),
bound = no_boundary(),
fixed = stri_replace_all_fixed(string, pattern, replacement,
Expand All @@ -117,6 +118,7 @@ str_replace_all <- function(string, pattern, replacement) {
regex = stri_replace_all_regex(string, pattern, fix_replacement(replacement),
vectorize_all = vec, opts_regex = opts(pattern))
)
if (keep_names(string, pattern)) copy_names(string, out) else out
}

is_replacement_fun <- function(x) {
Expand Down Expand Up @@ -178,10 +180,9 @@ fix_replacement_one <- function(x) {
#' str_replace_na(c(NA, "abc", "def"))
str_replace_na <- function(string, replacement = "NA") {
check_string(replacement)
stri_replace_na(string, replacement)
copy_names(string, stri_replace_na(string, replacement))
}


str_transform <- function(string, pattern, replacement) {
loc <- str_locate(string, pattern)
new <- replacement(str_sub(string, loc))
Expand Down
4 changes: 3 additions & 1 deletion R/sort.R
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,11 @@ str_sort <- function(x,
check_bool(numeric)

opts <- stri_opts_collator(locale, numeric = numeric, ...)
stri_sort(x,
idx <- stri_order(
x,
decreasing = decreasing,
na_last = na_last,
opts_collator = opts
)
x[idx]
}
10 changes: 7 additions & 3 deletions R/split.R
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,15 @@ str_split <- function(string, pattern, n = Inf, simplify = FALSE) {
n <- -1L
}

switch(type(pattern),
out <- switch(type(pattern),
empty = stri_split_boundaries(string, n = n, simplify = simplify, opts_brkiter = opts(pattern)),
bound = stri_split_boundaries(string, n = n, simplify = simplify, opts_brkiter = opts(pattern)),
fixed = stri_split_fixed(string, pattern, n = n, simplify = simplify, opts_fixed = opts(pattern)),
regex = stri_split_regex(string, pattern, n = n, simplify = simplify, opts_regex = opts(pattern)),
coll = stri_split_coll(string, pattern, n = n, simplify = simplify, opts_collator = opts(pattern))
)

if (keep_names(string, pattern)) copy_names(string, out) else out
}

#' @export
Expand Down Expand Up @@ -104,7 +106,8 @@ str_split_i <- function(string, pattern, i) {

if (i > 0) {
out <- str_split(string, pattern, simplify = NA, n = i + 1)
out[, i]
col <- out[, i]
if (keep_names(string, pattern)) copy_names(string, col) else col
} else if (i < 0) {
i <- abs(i)
pieces <- str_split(string, pattern)
Expand All @@ -116,7 +119,8 @@ str_split_i <- function(string, pattern, i) {
x[[n + 1 - i]]
}
}
map_chr(pieces, last)
out <- map_chr(pieces, last)
if (keep_names(string, pattern)) copy_names(string, out) else out
} else {
cli::cli_abort(tr_("{.arg i} must not be 0."))
}
Expand Down
Loading
Loading