---
title: "Don't Repeat Yourself"
output:
  rmarkdown::html_vignette:
    toc: true
    number_sections: true
vignette: >
  %\VignetteIndexEntry{Don't Repeat Yourself}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

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

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


# The transform_if function

"Don't Repeat Yourself", sometimes abbreviated as "DRY", is the coding principle that you should try to reduce repeating patterns in your code (within reason).

Consider the following code:


```{r echo=TRUE}
object <- matrix(c(-9:8, NA, NA) , ncol=2)
y <- 0
z <- 1000
ifelse(
  is.na(object>y), -z,
  ifelse(
    object>y,  log(object), object^2
  )
)
```


Here a conditional subset of the object `object` is transformed where the condition is using a function referring to `object` itself. Consequently, reference to `object` is written 4 times! This can become cumbersome quickly. Notice also that the above code gives an unnecessary warning, due to `ifelse()` requiring the entirety of `log(object)`.


The `tinycodet` package therefore adds the `transform_if(x, cond, yes, no, other)` function, which will "dry" this up.
Here, in argument `cond` a function must be given that returns a logical vector. For every value where `cond(x)==TRUE`, function `yes(x)` is run, for every value where `cond(x)==FALSE`, function `no(x)` is run, and for every value where `cond(x)==NA`, function `other` is run. Because a function-based approach is used instead of directly supplying vectors, unnecessary warnings and annoying errors are avoided (unlike the above code).

The above code can now be re-written in a less warning/error prone and more compact manner as:

```{r echo=TRUE}
object |> transform_if(\(x)x>y, log, \(x)x^2, \(x) -z)
```


Instead of supplying a function for `cond`, one can also directly supply a logical vector to argument `cond`. Moreover, when the transformed value is an atomic scalar, you don't really need a function; you can just fill in the scalar (vectors are not allowed though, as that will lead the same unnecessary warnings or even annoying errors as occur with `ifelse()`).

So one can thus also re-write the original code (without warnings/errors and more compact) as:

```{r echo=TRUE}
object |> transform_if(object > y, log, \(x)x^2, -z)
```


&nbsp;

# Matrix re-ordering operators

The `tinycodet` R package adds 2 additional matrix operators:

 - The `x %row~% mat` operator  re-orders the elements of every row, each row ordered independently from the other rows, of matrix `x`, by the ordering ranks given in matrix `mat`.
 - The `x %col~% mat` operator re-orders the elements of every column, each column ordered independently from the other columns, of matrix `x`, by the ordering ranks given in matrix `mat`.


&nbsp;

Examples with a numeric matrix:

```{r}
x <- matrix(sample(1:25), nrow = 5L)
print(x)
x %row~% x # sort elements of every row
x %row~% -x # reverse-sort elements of every row
x %col~% x # sort elements of every column
x %col~% -x # reverse-sort elements of every column

x <- matrix(sample(1:25), nrow = 5L)
print(x)
rand <- sample(1:length(x)) |> matrix(ncol=ncol(x)) # matrix of random integers
x %row~% rand # random shuffle every row independent of other rows
x %col~% rand # random shuffle every column independent of other columns
```


Examples with a character matrix:

```{r}
x <- matrix(sample(letters, 25L), nrow = 5L)
print(x)
mat <- stringi::stri_rank(as.vector(x)) |>
  matrix(ncol = ncol(x)) # matrix of ordering ranks
x %row~% mat # sort elements of every row
x %row~% -mat # reverse-sort elements of every row
x %col~% mat # sort elements of every column
x %col~% -mat # reverse-sort elements of every column

x <- matrix(sample(letters, 25L), nrow = 5L)
print(x)
rand <- sample(1:length(x)) |> matrix(ncol=ncol(x)) # matrix of random integers
x %row~% rand # random shuffle every row independent of other rows
x %col~% rand # random shuffle every column independent of other columns
```



&nbsp;

