Dividir uma coluna de string em várias variáveis ​​dummy

Como um usuário relativamente inexperiente do pacote data.table em R, tenho tentado processar uma coluna de texto em um grande número de colunas de indicadores (variáveis ​​dummy), com um 1 em cada coluna indicando que uma subcadeia específica era encontrado dentro da coluna de string. Por exemplo, quero processar isso:

ID     String  
1       a$b  
2       b$c  
3       c  

nisso:

ID     String     a     b     c  
1       a$b       1     1     0  
2       b$c       0     1     1  
3        c        0     0     1  

Eu descobri como fazer o processamento, mas leva mais tempo para executar do que eu gostaria, e eu suspeito que meu código é ineficiente. Uma versão reproduzível do meu código com dados fictícios está abaixo. Observe que nos dados reais, existem mais de 2000 substrings para procurar, cada substring tem aproximadamente 30 caracteres e pode haver alguns milhões de linhas. Se necessário, posso paralelizar e lançar muitos recursos no problema, mas quero otimizar o código o máximo possível. Eu tentei executar o Rprof, o que não sugeriu melhorias óbvias (para mim).

set.seed(10)  
elements_list <- c(outer(letters, letters, FUN = paste, sep = ""))  
random_string <- function(min_length, max_length, separator) {  
    selection <- paste(sample(elements_list, ceiling(runif(1, min_length, max_length))), collapse = separator)  
    return(selection)  
}  
dt <- data.table(id = c(1:1000), messy_string = "")  
dt[ , messy_string := random_string(2, 5, "$"), by = id]  
create_indicators <- function(search_list, searched_string) {  
    y <- rep(0, length(search_list))  
    for(j in 1:length(search_list)) {  
        x <- regexpr(search_list[j], searched_string)  
        x <- x[1]  
        y[j] <- ifelse(x > 0, 1, 0)  
    }  
    return(y)  
}  
timer <- proc.time()  
indicators <- matrix(0, nrow = nrow(dt), ncol = length(elements_list))  
for(n in 1:nrow(dt)) {  
    indicators[n, ] <- dt[n, create_indicators(elements_list, messy_string)]  
}  
indicators <- data.table(indicators)  
setnames(indicators, elements_list)  
dt <- cbind(dt, indicators)  
proc.time() - timer  

user  system elapsed 
13.17    0.08   13.29 

EDITAR

Obrigado pelas ótimas respostas - todas muito superiores ao meu método. Os resultados de alguns testes de velocidade abaixo, com pequenas modificações em cada função para usar 0L e 1L em meu próprio código, para armazenar os resultados em tabelas separadas por método e para padronizar a ordenação. Estes são tempos decorridos de testes de velocidade única (em vez de medianas de muitos testes), mas as corridas maiores demoram muito tempo.

Number of rows in dt     2K      10K      50K     250K      1M   
OP                       28.6    149.2    717.0   
eddi                     5.1     24.6     144.8   1950.3  
RS                       1.8     6.7      29.7    171.9     702.5  
Original GT              1.4     7.4      57.5    809.4   
Modified GT              0.7     3.9      18.1    115.2     473.9  
GT4                      0.1     0.4      2.26    16.9      86.9

Muito claramente, a versão modificada da abordagem do GeekTrader é a melhor. Eu ainda sou um pouco vago sobre o que cada passo está fazendo, mas eu posso passar por isso no meu tempo livre. Embora um pouco fora dos limites da questão original, se alguém quiser explicar o que os métodos GeekTrader e Ricardo Saporta estão fazendo de forma mais eficiente, seria apreciado por mim e provavelmente por qualquer pessoa que visite esta página no futuro. Estou particularmente interessado em entender por que alguns métodos são melhores que outros.

***** EDIT # 2 *****

Eu tentei editar a resposta do GeekTrader com este comentário, mas isso parece não funcionar. Fiz duas pequenas modificações na função GT3, a) ordenei as colunas, o que adiciona um pouco de tempo, eb) substituo 0 e 1 por 0L e 1L, o que acelera um pouco as coisas. Chame a função resultante GT4. Tabela acima editada para adicionar tempos para o GT4 em diferentes tamanhos de tabela. Claramente o vencedor por uma milha, e tem a vantagem de ser intuitivo.

questionAnswers(6)

yourAnswerToTheQuestion