Podemos dividir o algoritmo em dois casos: se os caracteres devem estar na mesma ordem da palavra (ou na ordem inversa), ou se podem estar embaralhados. Cada um dos casos leva a um algoritmo diferente.
De qualquer forma, primeiro concentre-se em fazer o algoritmo para uma palavra, depois vc consegue aplicá-lo para todas as palavras da lista.
Caracteres em ordem
Suponha que a palavra seja "house". Então vc pega a string que o usuário digitou e procura pela primeira posição em que ocorre o "h".
Se tiver "h", vc procura pela próxima letra (no caso, "o"), mas faz esta busca a partir da posição em que o "h" foi encontrado.
Se encontrar, vai pra próxima letra (no caso, "u"), buscando-a a partir da posição em que a letra "o" foi encontrada. E assim por diante.
Se chegar no final da string antes de encontrar o último caractere da palavra, é porque ela não ocorre. Se encontrou a última letra da palavra, é porque ocorre.
Em JavaScript seria algo assim:
// procura pela palavra dentro da string "str"
function procura(palavra, str) {
// se a palavra é maior que a string, com certeza não ocorre
if (palavra.length > str.length) {
return false;
}
// converte ambas para minúsculo
palavra = palavra.toLowerCase();
str = str.toLowerCase();
var posicaoAtual = -1; // posição a partir da qual será feita a busca na string
for (var i = 0; i < palavra.length; i++) {
// Verifica se o caractere da palavra (na posição "i") ocorre na string
// Busca a partir da última posição em que um caractere foi encontrado
posicaoAtual = str.indexOf(palavra[i], posicaoAtual + 1);
if (posicaoAtual === -1) { // não encontrou, então não ocorre
return false;
}
}
// se chegou aqui, é porque encontrou todos os caracteres da palavra
return true;
}
console.log(procura('kill', 'aK8dI3Lc7L')); // true
console.log(procura('kill', 'dki7hjl5fhjl')); // true
console.log(procura('kill', 'dki7hjl5')); // false
// assim, tanto faz se os caracteres são letras ou não, funciona do mesmo jeito
console.log(procura('123', 'abc_1 2xxxx3.')); // true
Repare que desta forma, tanto faz se os caracteres envolvidos são letras ou não. Claro que se quiser, pode fazer uma validação extra para verificar se a string só tem letras, mas pelo que entendi, não existe esta restrição.
Agora, para a lista, basta usar a função acima em um loop:
// procura pela palavra dentro da string "str"
function procura(palavra, str) {
// se a palavra é maior que a string, com certeza não ocorre
if (palavra.length > str.length) {
return false;
}
// converte ambas para minúsculo
palavra = palavra.toLowerCase();
str = str.toLowerCase();
var posicaoAtual = -1; // posição a partir da qual será feita a busca na string
for (var i = 0; i < palavra.length; i++) {
// Verifica se o caractere da palavra (na posição "i") ocorre na string
// Busca a partir da última posição em que um caractere foi encontrado
posicaoAtual = str.indexOf(palavra[i], posicaoAtual + 1);
if (posicaoAtual === -1) { // não encontrou, então não ocorre
return false;
}
}
// se chegou aqui, é porque encontrou
return true;
}
// inverte uma string
function reverse(s) {
return s.split('').reverse().join('');
}
// verifica se alguma das palavras ocorre na string "str" ou no inverso dela
function procuraLista(palavras, str) {
var inverso = reverse(str);
for (var palavra of palavras) {
if (procura(palavra, str)) {
console.log(`"${palavra}" encontrada`);
return; // se encontrou, pode sair
} else if (procura(palavra, inverso)) {
console.log(`"${palavra}" encontrada de trás pra frente`);
return; // se encontrou, pode sair
}
}
// se chegou aqui, é porque percorreu todas as palavras e não encontrou nenhuma
console.log('Nenhuma das palavras foi encontrada');
}
var palavras = ['house', 'secret', 'gun', 'kill'];
procuraLista(palavras, 'aK8dI3Lc7L'); // "kill" encontrada
procuraLista(palavras, '68sl6hslgsji68hsk'); // "kill" encontrada de trás pra frente
procuraLista(palavras, '123 G_xyzU...nabc!'); // "gun" encontrada
procuraLista(palavras, '123 nxU__Gabc!'); // "gun" encontrada de trás pra frente
procuraLista(palavras, 'abcdefghi'); // Nenhuma das palavras foi encontrada
Claro que a função procuraLista
, em vez de imprimir a mensagem, poderia retornar o resultado - por exemplo, algo como { palavraEncontrada: 'kill', invertido: false }
ou algo do tipo (e null
caso não encontre nada). Mas aí fica a seu critério, pois o algoritmo básico está aí.
Caracteres em qualquer ordem
Neste caso, não precisa gerar todas as permutações possíveis, pois além de desnecessário, é uma solução que não escala. Por exemplo, se a string tiver apenas 10 caracteres, serão mais de 3 milhões de permutações para testar. Com 20 caracteres, são mais de 2 quintilhões de possibilidades. Não precisa de nada disso.
Basta que vc verifique quais caracteres estão na palavra, e quantas vezes cada um ocorre. Depois, basta comparar esta contagem com as ocorrências na string.
Por exemplo, "kill" tem uma letra "k", uma letra "i" e duas letras "l". Basta que a string tenha no mínimo estas quantidades de cada uma destas letras, para que vc considere a ocorrência.
Então a ideia é primeiro construir um objeto Map
com a contagem de todos os caracteres da palavra.
Depois, para cada caractere da string, basta ver se ele está na contagem (e se estiver, subtraio 1). Se a contagem zerar, é porque todos os caracteres foram encontrados na quantidade em que ocorrem na palavra.
Seria algo assim:
// procura pela palavra dentro da string "str"
// mas os caracteres não precisam estar na mesma ordem
function procuraQualquerOrdem(palavra, str) {
// se a palavra é maior que a string, com certeza não ocorre
if (palavra.length > str.length) {
return false;
}
// obtém a contagem dos caracteres da palavra
var contPalavra = new Map();
for (var char of palavra.toLowerCase()) {
var n = contPalavra.get(char) ?? 0;
contPalavra.set(char, n + 1);
}
// para cada caractere da string, verifica se ele está na contagem da palavra
for (var char of str.toLowerCase()) {
// se o caractere está na contagem, diminui 1
if (contPalavra.has(char)) {
var n = contPalavra.get(char);
if (n == 1) { // se ocorre apenas uma vez, pode remover
contPalavra.delete(char);
if (contPalavra.size == 0) {
// se a contagem esvaziou, é porque todos os caracteres foram encontrados
return true;
}
} else { // senão, diminui 1 ocorrência
contPalavra.set(char, n - 1);
}
}
}
// se chegou aqui, é porque a contagem não esvaziou (ou seja, faltou caracteres, então a palavra não ocorre)
return false;
}
// verifica se alguma das palavras ocorre na string "str"
function procuraLista(palavras, str) {
for (var palavra of palavras) {
if (procuraQualquerOrdem(palavra, str)) {
console.log(`"${palavra}" encontrada`);
return;
}
}
console.log('Nenhuma das palavras foi encontrada');
}
var palavras = ['house', 'secret', 'gun', 'kill'];
// testando os casos anteriores (letras na ordem)
procuraLista(palavras, 'aK8dI3Lc7L'); // "kill" encontrada
procuraLista(palavras, '68sl6hslgsji68hsk'); // "kill" encontrada
procuraLista(palavras, '123 G_xyzU...nabc!'); // "gun" encontrada
procuraLista(palavras, '123 nxU__Gabc!'); // "gun" encontrada
procuraLista(palavras, 'abcdefghi'); // Nenhuma das palavras foi encontrada
// testando letras fora de ordem
procuraLista(palavras, 'xL_Kalxi123'); // "kill" encontrada
procuraLista(palavras, 'o1haxs95465Uabce_la'); // "house" encontrada
Porém, veja que agora não tem como saber se os caracteres estavam invertidos ou não, pois agora estou verificando-os em qualquer ordem. Por isso, não preciso mais inverter a string e fazer outra busca.
Outro detalhe é que, se uma palavra for encontrada, a função procuraLista
para de procurar as demais.
Mas se quiser que ele procure por todas, bastaria mudar a função para retornar um array com todas as palavras encontradas:
function procuraLista(palavras, str) {
var encontradas = [];
for (var palavra of palavras) {
if (procuraQualquerOrdem(palavra, str)) {
encontradas.push(palavra);
}
}
return encontradas;
}
var palavras = ['house', 'secret', 'gun', 'kill'];
console.log(procuraLista(palavras, 'lkhogulisne')); // [ 'house', 'gun', 'kill' ]
console.log(procuraLista(palavras, 'housecret')); // [ 'house', 'secret' ]
Ou ainda:
function procuraLista(palavras, str) {
return palavras.filter(palavra => procuraQualquerOrdem(palavra, str));
}
Mas agora surgiu outro caso que deve ser pensado o que fazer. Repare que "housecret" encontrou tanto "house" quando "secret", afinal cada busca é feita de forma isolada. Pode-se até argumentar que se "house" já foi encontrada, então a busca deveria considerar apenas os caracteres restantes (no caso, "cret") e aí o algoritmo ficaria mais complexo (precisaria remover os caracteres das palavras já encontradas). Mas acho que estou indo além, pois a ideia básica está aí pra vc aprimorar.