The following should read a hex string such as "ece998a2fb" into vector 236 233 152 162 251. I was unable to find any built in method for doing this.

as.byte.vector <- function(hex.string) {
  vapply(seq(to=nchar(hex.string), by=2), function(idx) {
    as.numeric(paste("0x", substr(hex.string, idx, idx+1), sep=''))
  }, FUN.VALUE = double(1))

2 Answers 2


One improvement is you can use the vectorized function substring rather than substr. This way, you can precompute all hexadecimal pairs and feed them directly into paste and as.numeric which are also vectorized. This removes the need for vapply (a disguised and slowish for loop).

You could also use paste0 as a shortcut for paste(, sep = ""). paste0 has been around for what seems like a couple years. If you are concerned about compatibility with older versions of R, then yes, it is safer to stick with paste.

I could be wrong but I see no reason not to output a vector of integers instead of numerics. That integers use less memory and are not subject to floating point errors are two strong reasons to prefer them over numerics.

It is also a good idea to add input checks to test your assumptions. Here it seems that the input must be a character vector of length 1.

This comment is more subjective. You have crammed a lot of operations into a single statement. I can count 8 functions including the anonymous one you created. That really makes your code hard to understand.

In all, I would have written the function like this:

as.byte.vector <- function(hex.string) {
   stopifnot(is.character(hex.string), length(hex.string) == 1L)
   start.idx <- seq(from = 1L, to = nchar(hex.string), by = 2L)
   end.idx <- start.idx + 1L
   hex.pairs <- substring(hex.string, start.idx, end.idx)
   prefixed.pairs <- paste0("0x", hex.pairs)

I hope you will agree that it is easier to read when the code is broken out this way, with each statement using a very few functions, and where the names of the variables holding intermediate results help understand the process.

This should already be faster than your version, thanks to the replacement of vapply. If speed is a real concern, you could make significant improvement by replacing substring with the following regular expression trick:

hex.pairs <- strsplit(gsub("(..)", "\\1 ", hex.string), " ")[[1]]

(We insert a space every two characters, then split on white space.)

  • \$\begingroup\$ I'm very new to R and appreciate the feedback. So far the only style/code guidelines I've been given are to indent code. Creating the corresponding end.idx sequence and vectorizing the substring call seem obvious, now that I've seen it, but things I hadn't happened upon yet. Thanks! I am quite curious: How would the string manipulation be faster than using substring? \$\endgroup\$
    – psaxton
    Nov 5, 2015 at 17:32
  • \$\begingroup\$ You would have to look at how the functions are implemented to tell for sure, but my guess is that every time you call substring, it is scanning the whole string from the left. Whereas gsub and strsplit only scan it once. \$\endgroup\$
    – flodel
    Nov 5, 2015 at 22:36
  • \$\begingroup\$ @flodel, just checked: gsub + strsplit is slower than substring. (Possibly because the additional spaces imply copying?) \$\endgroup\$ Nov 8, 2015 at 21:38
  • 1
    \$\begingroup\$ More likely because of overheads, as you tested with extremely small inputs. You should see that gsub + strsplit is a lot faster as you increase the input size. \$\endgroup\$
    – flodel
    Nov 9, 2015 at 0:57

In addition to @flodel's nice answer, hex.pairs can directly be converted:

strtoi (hex.pairs, base = 16L)

Not prefixing with 0x will shave off another 10% or so of the runtime.

The gsub / strsplit combination is somewhat slower (according to microbenchmark of the provided string:

Unit: microseconds
      expr    min     lq     mean  median      uq     max neval  cld
    prefix 44.628 46.445 49.27500 48.4000 49.1685  88.628   100  b  
    strtoi 39.181 41.905 45.15926 43.5110 45.2570  97.149   100 a   
gsubstrtoi 47.772 51.159 55.23254 52.5910 54.7905 138.565   100   c 
      orig 74.870 76.790 84.32210 79.9685 90.3745 171.809   100    d

prefix is @flodel's version, strtoi skips the prefixing and directly uses strtoi, gsubstrtoi splits by gsub + strsplit; orig is the OP's version.

  • \$\begingroup\$ +1 for the solution but I cringe a little at the speed tests and the hasty conclusions. Speed tests are meaningful when you specify the size of the inputs and test with various input sizes. Here, I imagine that you tested with the OP's example which is extremely small. With such sizes, we could argue that 5~40 microseconds are not going to make a big difference. And the differences will likely come from meaningless overheads the various approaches include (e.g. input checking, handling of NAs, etc.) \$\endgroup\$
    – flodel
    Nov 9, 2015 at 0:16
  • 1
    \$\begingroup\$ I suggested gsub + strsplit as an alternative to substring if "speed is a real concern". In my mind, that's if the user plan to use much larger input strings, the only situation where differences will create meaningful differences. I'd encourage you to add a test with hex.string <- paste(sample(c(0:9, LETTERS[1:6]), N, TRUE), collapse = "") for much larger values of N if you have the time. gsubstrtoi is a clear/visible winner with N=1e4 and frankly the only one that will scale nicely if you go over. \$\endgroup\$
    – flodel
    Nov 9, 2015 at 0:25
  • \$\begingroup\$ @flodel: fair enough - I tested (and had in mind) lots of calls with small inputs (played only with N up to around 50). More speedup may anyways be gained by thinking about where the strings come from and whether they could be processed in a way that chops them immediately into 2 char groups. (or move to Rcpp) \$\endgroup\$ Nov 9, 2015 at 12:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.