48

I want to create a sequence between two letters let's say "b" and "f". So the output is

"b" "c" "d" "e" "f"

For numbers, we can do

2:6 #which gives output as 
[1] 2 3 4 5 6

Is there an easy way to do this with letters as well?

I have gone through Generate a sequence of characters from 'A'-'Z' but this produces all the letters and not sequence between specific letters.

My current solution is,

indx <- which(letters %in% c("b", "f")); 
letters[indx[1] : indx[2]]

#[1] "b" "c" "d" "e" "f"

This works but I am curious if there is an easy way to do this or a function in any of the package that I have missed?

Note: I do not want letters[2:6] as I do not know 2 and 6 beforehand. It could be between any two letters.

zx8754
  • 52,746
  • 12
  • 114
  • 209
Ronak Shah
  • 377,200
  • 20
  • 156
  • 213
  • 2
    What defines your set of "letters"? Do you want the 26 lower-case letters of the Latin alphabet, or do you want the set of letters in the users current locale? Which could be the french, greek, russian, arabic or other alphabet? – Spacedman Nov 26 '18 at 08:33
  • @Spacedman yes, currently looking only for 26 letters from Latin alphabet. – Ronak Shah Nov 26 '18 at 08:50
  • "I do not want letters[2:6] as I do not know 2 and 6 beforehand." So I take it the reason you don't want to do `letters[begin:end]` is that you want to generate it based on the limits being given as letters rather than numbers? – Acccumulation Nov 26 '18 at 23:18
  • @Acccumulation correct. I have input as letters and not numbers. – Ronak Shah Nov 27 '18 at 01:12

10 Answers10

45

This would be another base R option:

letters[(letters >= "b") & (letters <= "f")]
# [1] "b" "c" "d" "e" "f"
r.user.05apr
  • 5,356
  • 3
  • 22
  • 39
28

You can create your own function:

`%:%` <- function(l, r) {
    intToUtf8(seq(utf8ToInt(l), utf8ToInt(r)), multiple = TRUE)
}

Usage:

"b" %:% "f"
# [1] "b" "c" "d" "e" "f"

"f" %:% "b"
# [1] "f" "e" "d" "c" "b"

"A" %:% "D"
# [1] "A" "B" "C" "D"
zx8754
  • 52,746
  • 12
  • 114
  • 209
Sven Hohenstein
  • 80,497
  • 17
  • 145
  • 168
17

Another option with match, seq and do.call:

letters[do.call(seq, as.list(match(c("b","f"), letters)))]

which gives:

[1] "b" "c" "d" "e" "f"

Making a function of this such that it works with both lower-case and upper-case letters:

char_seq <- function(lets) {
  switch(all(grepl("[[:upper:]]", lets)) + 1L,
         letters[do.call(seq, as.list(match(lets, letters)))],
         LETTERS[do.call(seq, as.list(match(lets, LETTERS)))])
}

the output of this:

> char_seq(c("b","f"))
[1] "b" "c" "d" "e" "f"

> char_seq(c("B","F"))
[1] "B" "C" "D" "E" "F"

This function can be extended with checks on the correctness of the input:

char_seq <- function(lets) {
  g <- grepl("[[:upper:]]", lets)
  if(length(g) != 2) stop("Input is not of length 2")
  if(sum(g) == 1) stop("Input does not have all lower-case or all upper-case letters")
  switch(all(g) + 1L,
         letters[do.call(seq, as.list(match(lets, letters)))],
         LETTERS[do.call(seq, as.list(match(lets, LETTERS)))])
}

resulting in proper error-messages when the input is not correct:

> char_seq(c("B"))
Error in char_seq(c("B")) : Input is not of length 2

> char_seq(c("b","F"))
Error in char_seq(c("b", "F")) : 
  Input does not have all lower-case or all upper-case letters
Jaap
  • 81,064
  • 34
  • 182
  • 193
15

Playing with UTF, something like:

intToUtf8(utf8ToInt("b"):utf8ToInt("f"), multiple = TRUE)
# [1] "b" "c" "d" "e" "f"
zx8754
  • 52,746
  • 12
  • 114
  • 209
13

Why not?

letters[which(letters == 'b') : which(letters == 'f')]
8

Perhaps using the raw versions of letters and then converting back to character could be used to define an infix function analogous to ":"

 `%c:%` <- function(x,y) { strsplit( rawToChar(as.raw(
     seq(as.numeric(charToRaw(x)), as.numeric(charToRaw(y))))), "" )[[1]]}
>  'a' %c:% 'g'
[1] "a" "b" "c" "d" "e" "f" "g"

I'm certainly not claiming this satisfies the request for "an easy way to do this" and I'm not even certain it would be more efficient, but it does introduce a couple of potentially useful functions.

IRTFM
  • 258,963
  • 21
  • 364
  • 487
  • "it does introduce a couple of potentially useful functions" Can you develop? – ARandomUser Dec 11 '18 at 14:02
  • 1
    I often find the process of locating conversion functions like `charToRaw` and `rawToChar` difficult. Also in the list of functions I have trouble remembering are: `intToUtf8` and `chartr`, `sfsmisc::AsciiToInt`, `stringi::stri_enc_isascii`, `stringi::stri_enc_toascii`. The last few I located by using `??ascii`. I think there are a few other utility functions that I sometimes can locate, but not at this moment cannot. I had a lsit in my `.Rprofile` file on my "regular" computer but I'm now converting from Mac to Linux and don't have it running. – IRTFM Dec 11 '18 at 18:20
  • I see your point, having myself difficulties to remember functions that I've used months ago. Thanks! – ARandomUser Dec 12 '18 at 08:52
6

Iknow it is frowned upon, but here is an eval(parse(...)) solution

LETTERS[eval(parse(text = paste(which(LETTERS %in% c('B', 'F')), collapse = ':')))]
#[1] "B" "C" "D" "E" "F"
Sotos
  • 51,121
  • 6
  • 32
  • 66
  • Using `eval` and `parse` here is blatant abuse, sorry. You can implement the same logic without (e.g. with `do.call`), although the logic itself is also needlessly convoluted. – Konrad Rudolph Nov 27 '18 at 10:28
4

First things first: your code

which(letters %in% c("b", "f"))

Is a valid but convoluted way of writing

match(c('b', 'f'), letters)

(Why “convoluted”? Because %in% is a wrapper around match for a specific use-case, which explicitly turns the numeric index into a logical value, i.e. the inverse operation of which.)

Next, you can of course use the result and convert it into a range via idx[1L] : idx[2L] and there’s nothing wrong with that in this case. But R has an idiomatic way of expressing the concept of calling a function using a vector as its parameters: do.call:

do.call(`:`, as.list(match(c('b', 'f'), letters)))

Or, equivalently:

do.call(seq, as.list(match(c('b', 'f'), letters)))

{purrr} allows us to do the same without the as.list:

purrr::invoke(seq, match(c('b', 'f'), letters))

And, finally, we subset:

letters[purrr::invoke(seq, match(c('b', 'f'), letters))]
Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
4

You can use grep and letters and the pattern [b-f].

grep("[b-f]", letters, value = TRUE)
#[1] "b" "c" "d" "e" "f"

letters[grep("[b-f]", letters)]
#[1] "b" "c" "d" "e" "f"

letters[grepl("[b-f]", letters)]
#[1] "b" "c" "d" "e" "f"

And for a decreasing sequence you can use in addition rev

rev(grep("[b-f]", letters, value = TRUE))
#[1] "f" "e" "d" "c" "b"

or using match.

letters[match("b", letters) : match("f", letters)]
#[1] "b" "c" "d" "e" "f"
GKi
  • 37,245
  • 2
  • 26
  • 48
1

A compact solution using match

library(magrittr)

a <- "b"
b <- "f"

letters %>% .[match(a,.):match(b,.)]
#> [1] "b" "c" "d" "e" "f"

Created on 2021-12-30 by the reprex package (v2.0.1)

Dan Adams
  • 4,971
  • 9
  • 28