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

Consider adding str_sandwich() #557

Closed
allenbaron opened this issue Jul 26, 2024 · 1 comment
Closed

Consider adding str_sandwich() #557

allenbaron opened this issue Jul 26, 2024 · 1 comment

Comments

@allenbaron
Copy link
Contributor

I frequently have to sandwich/surround a string with either one (e.g. making the string hello into "hello") or two different placeholders (e.g. making the string hello into <hello>).

Would you consider adding a function that does this to stringr? It could possibly be called str_sandwich(). I imagine you can make a faster version and probably come up with a better name.

Here are two different approaches I've used to accomplish this with slightly different properties for reference.

Approach 1

Approach 1 uses paste. It is faster and always adds the placeholders to the strings, even when they are already there. It can optionally collapse output like paste (but I think it probably shouldn't be able to).

Function

#' Sandwich Text Between Placeholders
#'
#' Sandwiches strings between one or two placeholders.
#'
#' @param x A string or character vector.
#' @param placeholder One or two placeholders to sandwich each element of `x`
#'     between. When two placeholders are provided, `x` will be sandwiched
#'     between them with the first on the left and second on the right.
#'     Otherwise, `x` will be sandwiched on both sides by the same placeholder.
#' @inheritDotParams base::paste0
#'
#' @examples
#' str_sandwich("a", placeholder = "h")
#' str_sandwich("a", placeholder = c("b", "h"))
#'
#' @family general utilities
#' @export
str_sandwich <- function(x, placeholder, ...) {
    if (length(placeholder) < 1 ||
        length(placeholder) > 2 ||
        !is.character(placeholder)
    ) {
        stop("`placeholder` must be a length-1 or -2 character vector.")
    }

    if (length(placeholder) == 1) {
        out <- paste0(placeholder, x, placeholder, ...)
    } else {
        out <- paste0(placeholder[1], x, placeholder[2], ...)
    }

    if (!is.null(names(x))) {
        names(out) <- names(x)
    }

    out
}

Tests

test_that("str_sandwich() works", {
    expect_equal(str_sandwich("a", "h"), "hah")
    expect_equal(str_sandwich("a", c("b", "h")), "bah")
    expect_equal(
        str_sandwich("testing a sentence", c("I'm ", ".")),
        "I'm testing a sentence."
    )
    expect_equal(
        str_sandwich(c("ally", "ail"), "s"),
        c("sallys", "sails")
    )
    expect_equal(
        str_sandwich(c("es", "a", "oo"), "t", collapse = ", "),
        "test, tat, toot"
    )
    expect_error(str_sandwich("a", 1))
})

Approach 2

Approach 2 includes the option to avoid adding placeholders if they already exist at the start/end, as specified. It is slower because it uses stringr::str_replace() twice (stringr::str_replace_all() doesn't work for the optional case).

Function

#' Sandwich Text Between Placeholders
#'
#' Sandwiches strings between one or two placeholders.
#'
#' @param x A character vector.
#' @param placeholder One or two placeholders to sandwich each element of `x`
#'     between. When two placeholders are provided, `x` will be sandwiched
#'     between them with the first at the start and second at the end.
#'     Otherwise, `x` will be sandwiched at both start and end by the same
#'     placeholder.
#' @param add_dup Whether to add placeholders even if the same character is
#'     already found in that position, as a boolean (default: `TRUE`).
#'
#' @examples
#' str_sandwich("a", placeholder = "h")
#' str_sandwich("a", placeholder = c("b", "h"))
#' str_sandwich("bah", placeholder = c("b", "h"), add_dup = TRUE)
#' str_sandwich("bah", placeholder = c("b", "h"), add_dup = FALSE)
#' str_sandwich("bah", placeholder = "h", add_dup = FALSE)
#'
#' @export
#' Sandwich Text Between Placeholders
#'
#' Sandwiches strings between one or two placeholders.
#'
#' @param x A string or character vector.
#' @param placeholder One or two placeholders to sandwich each element of `x`
#'     between. When two placeholders are provided, `x` will be sandwiched
#'     between them with the first at the start and second at the end.
#'     Otherwise, `x` will be sandwiched at both start and end by the same
#'     placeholder.
#' @param add_dup Whether to add placeholders even if the same character is
#'     already found in that position, as a boolean (default: `TRUE`).
#'
#' @examples
#' str_sandwich("a", placeholder = "h")
#' str_sandwich("a", placeholder = c("b", "h"))
#' str_sandwich("bah", placeholder = c("b", "h"), add_dup = TRUE)
#' str_sandwich("bah", placeholder = c("b", "h"), add_dup = FALSE)
#' str_sandwich("bah", placeholder = "h", add_dup = FALSE)
#'
#' @family general utilities
#' @export
str_sandwich <- function(x, placeholder, add_dup = TRUE) {
    if (length(placeholder) < 1 ||
        length(placeholder) > 2 ||
        !is.character(placeholder)
    ) {
        stop("`placeholder` must be a length-1 or -2 character vector.")
    }

    placeholder2 <- rep(placeholder, length(placeholder) %% 2 + 1)
    if (add_dup) {
        pattern <- c("^", "$")
    } else {
        opt_placeholder <- paste0(placeholder2, "?")
        pattern <- paste0(c("^", opt_placeholder[2]), c(opt_placeholder[1], "$"))
    }

    out <- stringr::str_replace(x, pattern[1], placeholder2[1])
    out <- stringr::str_replace(out, pattern[2], placeholder2[2])

    if (!is.null(names(x))) {
        names(out) <- names(x)
    }

    out
}

Tests

test_that("str_sandwich() works", {
    expect_equal(str_sandwich("a", "h"), "hah")
    expect_equal(str_sandwich("a", c("b", "h")), "bah")
    expect_equal(
        str_sandwich("testing a sentence", c("I'm ", ".")),
        "I'm testing a sentence."
    )
    expect_equal(
        str_sandwich(c("ally", "ail"), "s"),
        c("sallys", "sails")
    )
    expect_error(str_sandwich("a", 1))
})

test_that("str_sandwich() add_dup argument works", {
    expect_equal(
        str_sandwich("bah", placeholder = c("b", "h"), add_dup = TRUE),
        "bbahh"
    )
    expect_equal(
        str_sandwich("bah", placeholder = c("b", "h"), add_dup = FALSE),
        "bah"
    )
    expect_equal(
        str_sandwich("bah", placeholder = "h", add_dup = FALSE),
        "hbah"
    )
})
@hadley
Copy link
Member

hadley commented Aug 20, 2024

Thanks for filing this issue! Unfortunately, I think it's a bit too special purpose for stringr since it doesn't reduce code that much compared to use paste() directly, and developing good software requires relentless focus, which means that we have to say no to many good ideas. Even though I'm closing this issue, I really appreciate the feedback, and hope you'll continue to contribute in the future 😄

@hadley hadley closed this as completed Aug 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants