---
title: "String related functions"
output:
  rmarkdown::html_vignette:
    toc: true
    number_sections: true
vignette: >
  %\VignetteIndexEntry{String related functions}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
```

```{r setup}
library(tinycodet)
```

&nbsp;

# Introduction

R's numerical functions are generally very fast. But R's native string functions are somewhat slow, do not have a unified naming scheme, and are not as comprehensive as R's impressive numerical functions.
The primary R-package that fixes this is 'stringi', which many, if not most, string related packages depend on (see the list of reverse-dependencies on CRAN).
The 'stringr' package, for example, is merely a thin wrapper around 'stringi'.
As string manipulation is important to programming languages, even those primarily focused on mathematics, 'tinycodet' adds a little bit new functionality to 'stringi'.


&nbsp;

# stri_locate_ith

Suppose one wants to transform the **first** vowels in the strings of a character vector `str`, such that all upper case vowels become lower case, and vice-versa.
One can do that completely in `stringi` + base R as follows:

```{r}

x <- c("HELLO WORLD", "goodbye world")
loc <- stringi::stri_locate_first(x, regex="a|e|i|o|u", case_insensitive=TRUE)
extr <- stringi::stri_sub(x, from=loc)
repl <- chartr(extr, old = "a-zA-Z", new = "A-Za-z")
stringi::stri_sub_replace(x, loc, replacement=repl)
```

But now suppose one wants to transform the **second-last** vowel. How are you going to do that in an efficient way? For clear and efficient code, `stringi` really needs some kind of "stri_locate_ith" function. And, of course, the `tinycodet` package provides just that.

The `stri_locate_ith(str, i, ...)` function locates for every element/string in character vector `str`, the $i^\textrm{th}$ occurrence of some (regex/fixed/etc) pattern. When `i` is positive, the occurrence is counted from left to right. Negative values for `i` are also allowed, in which case the occurrence is counted from the right to left. But `i=0` is not allowed though. Thus, to get the **second** occurrence of some pattern, use `i=2`, and to get the **second-last** occurrence, use `i=-2`.

The `stri_locate_ith(str, i, ...)` function uses the exact same argument and naming convention as `stringi`, to keep your code consistent. And just like `stringi::stri_locate_first/last`, the `stri_locate_ith(str, i, ...)` function is a vectorized function: `str` and  `i` as well as the pattern (`regex, fixed, coll, charclass`) can all be different-valued vectors.


&nbsp;

To transform the **second-last** occurrence, one can now use `stri_locate_ith()` in a very similar way as was done with `stri_locate_first/last`:

```{r}
x <- c("HELLO WORLD", "goodbye world")

loc <- stri_locate_ith( # this part is the key-difference
  x, -2, regex="a|e|i|o|u", case_insensitive=TRUE
)

extr <- stringi::stri_sub(x, from=loc)
repl <- chartr(extr, old = "a-zA-Z", new = "A-Za-z")
stringi::stri_sub_replace(x, loc, replacement=repl)
```

Notice that the code is virtually equivalent. We *only* need to change the locate function.

&nbsp;

There is also the `stri_locate_ith_boundaries()` function, which of course locates the $i^\textrm{th}$ text boundary.

&nbsp;

# strcut_ - functions

The `tinycodet` R package adds 2 `strcut` functions: `strcut_loc()` and `strcut_brk()`.

The `strcut_loc()`function
cuts every string in a character vector around a location range `loc`,
such that every string is cut into the following parts:

 - the sub-string **before** `loc`;
 - the sub-string at `loc` itself;
 - the sub-string **after** `loc`.
 
The location range `loc` would usually be matrix with 2 columns, giving the start and end points of some pattern match.


The `strcut_brk()` function is basically a wrapper around `stringi::stri_split_boundaries(..., simplify=NA)`, and with some more conveniently named arguments.

Examples:

```{r}

x <- rep(paste0(1:10, collapse=""), 10)
print(x)
loc <- stri_locate_ith(x, 1:10, fixed = as.character(1:10))
strcut_loc(x, loc)
strcut_loc(x, c(5,5))


test <- c("The above-mentioned    features are very useful. ",
"Spam, spam, eggs, bacon, and spam. 123 456 789")
strcut_brk(test, "line")
strcut_brk(test, "word")
strcut_brk(test, "sentence")
strcut_brk(test, "character")

```


&nbsp;

# Matrix joining

The `tinycodet` package adds a tiny additional function to `stringi`:

`stri_join_mat` (and their aliases `stri_c_mat` and `stri_paste_mat`).

As the name suggests, these functions perform row-wise (`margin=1`; the default) or column-wise (`margin=2`) joining of a matrix of strings, thereby transforming it to a vector of strings. You can do this already in base R, but it requires converting the matrix to a data.frame or list, and then calling `stri_join` inside `do.call()`, which to me just seems too much trouble for something *soooo* abysmally simple.

Here is an example of their usage when re-ordering strings, words, or sentences :

```{r}

# sorting characters in strings:
x <- c(paste(sample(letters), collapse = ""), paste(sample(letters), collapse = ""))
print(x)
mat <- strcut_brk(x)
rank <- stringi::stri_rank(as.vector(mat)) |>  matrix(ncol=ncol(mat))
sorted <- mat %row~% rank
print(sorted)
stri_join_mat(sorted, margin=1)
stri_join_mat(sorted, margin=2)

# sorting words:
x <- c("2nd 3rd 1st", "Goodbye everyone")
print(x)
mat <- strcut_brk(x, "word")
rank <- stringi::stri_rank(as.vector(mat)) |> matrix(ncol=ncol(mat))
sorted <- mat %row~% rank
sorted[is.na(sorted)] <- ""
stri_c_mat(sorted, margin=1, sep = " ") # <- alias for stri_join_mat
stri_c_mat(sorted, margin=2, sep = " ")

# randomly shuffle sentences:
x <- c("Hello, who are you? Oh, really?! Cool!", "I don't care. But I really don't.")
print(x)
mat <- strcut_brk(x, "sentence")
rank <- sample(1:length(mat)) |> matrix(ncol = ncol(mat))
sorted <- mat %row~% rank
sorted[is.na(sorted)] <- ""
stri_paste_mat(sorted, margin=1) # <- another alias for stri_join_mat
stri_paste_mat(sorted, margin=2)

```


&nbsp;
