An open API service indexing awesome lists of open source software.

https://github.com/chrismuir/anagrams

R pkg for detecting anagrams
https://github.com/chrismuir/anagrams

anagram anagrams rcpp rstats

Last synced: over 1 year ago
JSON representation

R pkg for detecting anagrams

Awesome Lists containing this project

README

          

---
output:
github_document: default
html_document: default
---

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

```{r message=FALSE, warning=FALSE, error=FALSE, include=FALSE}
options(width = 120)
```

# anagrams

[![Travis-CI Build Status](https://travis-ci.org/ChrisMuir/anagrams.svg?branch=master)](https://travis-ci.org/ChrisMuir/anagrams)

`anagrams` is a super simple R package providing a function for comparing character vectors and determining if strings are [anagrams](https://en.wikipedia.org/wiki/Anagram) of one another. The project was inspired by [this](http://www.programmingr.com/content/simple-anagram-finder-using-r/) blog post on finding anagrams in R. The package uses C++ and [Rcpp](https://CRAN.R-project.org/package=Rcpp) for speed.

If you're interested in anagrams and R, be sure to check out [Romain Francois'](https://github.com/romainfrancois) package [anagram](https://github.com/romainfrancois/anagram) (and h/t to Romain for [pointing out](https://twitter.com/romain_francois/status/972754279164514304) some bottle-necks in my cpp code).

Also, thank you to [Mark van der Loo](https://github.com/markvanderloo) for pointing out that the [stringdist](https://github.com/markvanderloo/stringdist) package can be used to concisely determine same-length anagrams (and it's faster than the base R function I defined below!) I included the `stringdist` solution in the benchmarks below.

## Installation

Install from github with:
```{r eval = FALSE}
# install.packages("devtools")
devtools::install_github("ChrisMuir/anagrams")
```

## Example Usage

The exported function `is_anagram` takes as input a string and a character vector, and looks for anagrams of the string in the character vector.

```{r}
library(anagrams)

# Test for anagrams that are the same length as the input string.
is_anagram("stac", c("cats are great", "tacs", "frogs", "cats", "ts"))

# Use arg "value" to return the values that are anagrams.
is_anagram("stac", c("cats are great", "tacs", "frogs", "cats", "ts"), value = TRUE)

# Set arg "any_len" to TRUE to test for anagrams that are any length (either same length or sub-string).
is_anagram("stac", c("cats are great", "tacs", "frogs", "cats", "ts"), any_len = TRUE)

# Use arg "ignore_spaces" to make anagram searching insensitive to spaces.
is_anagram("s t a c", c("cats are great", "t acs", "frogs", "ca ts", "ts"), ignore_spaces = TRUE)

# Use arg "ignore_case" to make anagram searching insensitive to lower/upper case.
is_anagram("STAc", c("catS are great", "tacs", "frogs", "CaTS", "ts"), ignore_case = TRUE)
```

## Benchmarks

Let's create a simple, base R version of `is_anagram` that searches for same-length anagrams, and compare speeds.
```{r}
r_is_anagram <- function(string, terms) {
out <- rep(FALSE, length(terms))
terms_to_insp <- which(nchar(terms) == nchar(string))
if (length(terms_to_insp) == 0) {
return(out)
}

string_spl <- unlist(strsplit(string, "", fixed = TRUE), FALSE, FALSE)
str_counts <- vapply(string_spl, function(x) sum(string_spl == x), integer(1))
terms_spl <- strsplit(terms, "", fixed = TRUE)

out[terms_to_insp] <- vapply(terms_spl[terms_to_insp], function(x) {
anagram <- TRUE
for (char in string_spl) {
if (str_counts[char] != sum(x == char)) {
anagram <- FALSE
break
}
}
anagram
}, logical(1), USE.NAMES = FALSE)

out
}

# Test to make sure its output is identical to that of pkg function is_anagram.
identical(
r_is_anagram("stac", c("cats are great", "tacs", "frogs", "cats", "ts")),
is_anagram("stac", c("cats are great", "tacs", "frogs", "cats", "ts"))
)
```
And for completeness, here is a similar function that uses `stringdist::stringdist()` to find same-length anagrams.
```{r}
library(stringdist)

sd_is_anagram <- function(string, terms) {
out <- rep(FALSE, length(terms))
terms_to_insp <- which(nchar(terms) == nchar(string))
if (length(terms_to_insp) == 0) {
return(out)
}

out[terms_to_insp] <- stringdist(string, terms[terms_to_insp], method="qgram", q=1) == 0

out
}

# Test to make sure its output is identical to that of pkg function is_anagram.
identical(
sd_is_anagram("stac", c("cats are great", "tacs", "frogs", "cats", "ts")),
is_anagram("stac", c("cats are great", "tacs", "frogs", "cats", "ts"))
)
```

Now we'll compare speeds.

```{r message=FALSE, warning=FALSE, error=FALSE}
library(microbenchmark)
library(stringi)

# Test in which each element is shorter than the input string.

test_vect <- stringi::stri_rand_strings(100000, 3)
microbenchmark(
anagrams_pkg = is_anagram("cats", test_vect),
stringdist_pkg = sd_is_anagram("cats", test_vect),
base_r = r_is_anagram("cats", test_vect)
) -> mb

print(mb)

autoplot.microbenchmark(mb)

# Test in which each element is the same length as the input string.

test_vect <- stringi::stri_rand_strings(100000, 4)
microbenchmark(
anagrams_pkg = is_anagram("cats", test_vect),
stringdist_pkg = sd_is_anagram("cats", test_vect),
base_r = r_is_anagram("cats", test_vect)
) -> mb

print(mb)

autoplot.microbenchmark(mb)

# Test in which each element is an anagram of the input string.

test_vect <- rep("tacs", 100000)
microbenchmark(
anagrams_pkg = is_anagram("cats", test_vect),
stringdist_pkg = sd_is_anagram("cats", test_vect),
base_r = r_is_anagram("cats", test_vect)
) -> mb

print(mb)

autoplot.microbenchmark(mb)

# Test in which each element is a string with length between two and six chars.

test_vect <- stringi::stri_rand_strings(100000, 2:6)
microbenchmark(
anagrams_pkg = is_anagram("cats", test_vect),
stringdist_pkg = sd_is_anagram("cats", test_vect),
base_r = r_is_anagram("cats", test_vect)
) -> mb

print(mb)

autoplot.microbenchmark(mb)

# Test in which each element is a long string (nchar == 1000).

test_str <- stringi::stri_rand_strings(1, 1000)
test_vect <- stringi::stri_rand_strings(100000, 1000)
microbenchmark(
anagrams_pkg = is_anagram(test_str, test_vect),
stringdist_pkg = sd_is_anagram(test_str, test_vect),
base_r = r_is_anagram(test_str, test_vect)
) -> mb

print(mb)

autoplot.microbenchmark(mb)
```