One way is using data.table and fmatch, where data.table could use multiple cores.
library(data.table)
library(fastmatch)
setDTthreads(1)
fmatch(x, data.table(x)[,logical(1), keyby = x]$x)
another using kit::funique and fmatch optionally with kit::psort, where you can use multiple cores.
library(kit)
library(fastmatch)
fmatch(x, sort(kit::funique(x)))
fmatch(x, kit::psort(kit::funique(x), nThread=1L))
or using collapse::funique.
library(collapse)
library(fastmatch)
fmatch(x, sort(collapse::funique(x)))
fmatch(x, collapse::funique(x, sort=TRUE))
Benchmark based on @thomasiscoding:
library(fastmatch)
library(data.table)
setDTthreads(1)
set.seed(42)
x <- sample(letters, 1e6, replace = T)
index <- function(x) as.numeric(as.factor(x))
index2 <- function(x) match(x, sort(unique(x)))
index3 <- function(x) chmatch(x, sort(unique(x)))
index4 <- function(x) fmatch(x, sort(unique(x)))
index5 <- function(x) fmatch(x, data.table(x)[,logical(1), keyby = x]$x)
index6 <- function(x) fmatch(x, sort(collapse::funique(x)))
index7 <- function(x) fmatch(x, collapse::funique(x, TRUE))
index8 <- function(x) fmatch(x, sort(kit::funique(x)))
index9 <- function(x) fmatch(x, kit::psort(kit::funique(x), nThread=1L))
bench::mark(index(x), index2(x), index3(x), index4(x), index5(x), index6(x),
index7(x), index8(x), index9(x))
# expression min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc total_time
# <bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl> <int> <dbl> <bch:tm>
#1 index(x) 29.95ms 30.11ms 32.1 30.89MB 88.4 4 11 124ms
#2 index2(x) 28.6ms 28.67ms 34.8 23.26MB 39.1 8 9 230ms
#3 index3(x) 20.46ms 21.13ms 46.1 15.63MB 20.2 16 7 347ms
#4 index4(x) 18.71ms 18.88ms 52.1 15.63MB 23.2 18 8 345ms
#5 index5(x) 20.42ms 20.81ms 46.7 15.46MB 16.5 17 6 364ms
#6 index6(x) 10.22ms 10.33ms 96.1 3.82MB 11.2 43 5 448ms
#7 index7(x) 10.11ms 10.26ms 95.1 3.82MB 6.34 45 3 473ms
#8 index8(x) 8.69ms 9.28ms 107. 3.81MB 6.41 50 3 468ms
#9 index9(x) 8.62ms 9.22ms 108. 3.81MB 6.36 51 3 472ms