Is there something similar to Rust pattern syntax in R?

Viewed 168

I know of the switch statement in R, but I'm curious if there's a way to assign the same action/value to multiple patterns in the same arm, similar to how it's in Rust:

let x = 1;
match x {
    1 | 2 => println!("one or two"),
    3 => println!("three"),
    _ => println!("anything"),
}

I don't need to write two separate cases for 1 & 2, I can just combine them into one with '|'. It would be also helpful if I could define the default case ("_") if no pattern before was matched.

1 Answers

Preceding values with no assignment carry forward until an assigned value is found.

switch(
  as.character(x),
  "1"=,
  "2"="one or two",
  "3"="three",
  "anything"
)

I use as.character(x) instead of just x because EXPR (the first argument) may be interpreted as positional instead of equality. From ?switch:

     If the value of 'EXPR' is not a character string it is coerced to
     integer.  Note that this also happens for 'factor's, with a
     warning, as typically the character level is meant.  If the
     integer is between 1 and 'nargs()-1' then the corresponding
     element of '...' is evaluated and the result returned: thus if the
     first argument is '3' then the fourth argument is evaluated and
     returned.

So if x is an integer between 1 and the number of other arguments, then it is interpreted as a positional indicator, as in

switch(3, 'a','z','y','f')
# [1] "y"

which means that the named arguments are effectively ignored, as in this very confusing example

switch(3, '1'='a','3'='z','2'='y','4'='f')
# [1] "y"

Note that the help does not reference non-strings that are greater than nargs()-1 ... those integers return null:

(switch(9, '1'='a','3'='z','2'='y','4'='f'))
# NULL

Since it is the value of the integer you're looking to match, you need to confusingly convert to string:

switch(as.character(3), '1'='a','3'='z','2'='y','4'='f')
# [1] "z"

Alternatively,

dplyr::case_when(
  x %in% 1:2 ~ "one or two",
  x == 3     ~ "three",
  TRUE       ~ "anything"
)
# [1] "one or two"

or

data.table::fcase(
  x %in% 1:2          , "one or two",
  x == 3              , "three",
  rep(TRUE, length(x)), "anything"
)

(The need for rep(TRUE,length(x)) is because fcase requires all arguments to be exactly the same length, i.e., it allows no recycling as many R functions allow. I personally would prefer that they allow 1 or N recycling instead of only N, but that isn't the way at the moment.)

This has an advantage that it is naturally vectorized.

switch is only length-1 friendly. A workaround for a vector x could be

sapply(x, switch, '1'='a', '3'='z', '2'='y', '4'='f')

(or, better yet, vapply enforcing the return class).

Related