Como encontrar onde um caractere ou substring está dentro de uma palavra
Às vezes você precisa saber exatamente em que parte de uma palavra algo aparece — seja um caractere específico, um prefixo, um sufixo. Parece simples até você se deparar com strings que têm acentos, espaços duplos ou codificação incorreta e o resultado sai errado.
em que parte de uma palavra: o básico
O conceito central é muito direto. Você pega uma string e busca por um alvo dentro dela. A diferença fica por conta da linguagem e do que você considera como "parte". Posição absoluta? Índice zero-based ou um-based? Primeira ocorrência ou todas? No Python, por exemplo, você usa o método find() ou index(). A diferença entre os dois é que find devolve -1 quando não encontra nada, enquanto index levanta uma exceção.
palavra = "programação"
palavra.find("ra") retorna 4
palavra.find("z") retorna -1
Em JavaScript, você tem o indexOf, que funciona de forma similar.
let palavra = "programação";
palavra.indexOf("ra"); // retorna 4
Em ambas as situações, a contagem começa do zero. O "p" está na posição 0, o "r" em 1, e assim por diante. Muita gente confunde isso na primeira vez que usa. No PHP, a função strpos é a opção padrão. Note que ela também retorna -1 quando falha, mas há uma pegadinha: se o alvo estiver na posição 0, a função devolve 0, que é falsy em PHP. Se você usar um teste condicional simples como if (strpos($str, 'a')), vai dar falso positivo para string que começa com "a". A correção é comparar estritamente com !== false.
edge case que me custou duas horas
Num projeto recente, eu precisava extrair a posição de cada vogal em strings vindas de um export de dados legados. Os dados vinham com encoding inconsistente — alguns com acentos em UTF-8, outros em Latin-1, e uma parte com caracteres Unicode decompostos (NFD em vez de NFC). O resultado era imprevisível. O mesmo texto "edição" às vezes dava posição 2 para o "e" e outras vezes 3, porque o acento agudo era tratado como um caractere separado após a conversão.
A solução foi normalizar tudo com Unicode NFC antes de qualquer busca:
import unicodedata
texto = unicodedata.normalize('NFC', texto_original)
posicao = texto.find('ç')
Depois da normalização, o resultado ficou estável. O custo de normalização é baixo — em torno de 2 a 3ms por string de 500 caracteres em hardware comum — mas vale a pena fazer logo no início do pipeline, antes de qualquer outro processamento.
buscando todas as ocorrências, não só a primeira
O problema de find e indexOf é que eles param na primeira match. Quando você precisa de todas as posições, precisa loopar. Um padrão que eu uso frequentemente é este em Python:
👉 Clique no botão abaixo para saber mais sobre o assunto!
def todas_ocorrencias(texto, alvo):
posicoes = []
inicio = 0
while True:
pos = texto.find(alvo, inicio)
if pos == -1:
break
posicoes.append(pos)
inicio = pos + 1
return posicoes
Repare que o incremento é pos + 1, não pos + len(alvo). Se você quiser evitar sobreposição, aí sim soma o tamanho do alvo. Se a string alvo for "ana" e o texto for "ananana", com pos + 1 você pega todas as ocorrências incluindo sobrepostas: posições 0, 2 e 4. Com pos + len(alvo) você pega apenas 0 e 4. Em JavaScript o mesmo padrão se aplica, mas existe uma alternativa com expressões regulares usando a flag g e o método matchAll:
const texto = "ananana";
[...texto.matchAll(/ana/g)].map(m => m.index); // [0, 2, 4]
Expressões regulares são mais flexíveis quando você precisa de padrões complexos — por exemplo, encontrar todas as ocorrências de vogais, ou qualquer dígito. Mas têm um custo maior. Para strings curtas e padrões fixos, o loop com find/indexOf costuma ser mais rápido e previsível.
pitfalls que ninguémConta
Existem algumas armadilhas que aparecem com frequência e quase ninguém prevê antes de errar. Case sensitivity. A maioria das funções de busca é sensível a maiúsculas e minúsculas por padrão. "Programação" e "programação" não são a mesma coisa para indexOf. A correção é normalizar ambas as strings para o mesmo case antes de buscar, ou usar opções de case-insensitive quando disponíveis. No JavaScript, não há parâmetro de ignore-case no indexOf, então você precisa converter para lower case primeiro. Em Python, find também não aceita flags, então o mesmo truque se aplica.
Unicode e grafemas. Caracteres como "ñ", "ç", "á" podem ser representados de formas diferentes no código. O "á" pode ser um único code point U+00E1 ou a sequência U+0041 + U+0301 (A + acento agudo). Se seu dado vier de fontes diferentes, você vai ter strings que parecem iguais mas são diferentes bytes. A normalização Unicode resolve isso na maioria dos casos. Posição vs. visibilidade. Em strings que contêm emojis compostos ou caracteres com combining marks, a posição em code points nem sempre corresponde à posição visual. Um emoji como "" ocupa múltiplos code points, mas é exibido como um único glifo. Se você está construindo um editor de texto ou uma ferramenta de edição caractere por caractere, considere usar bibliotecas como grapheme-splitter (JavaScript) ou o módulo regex com o flag u em Python, que respeitam boundaries de grafema.
quando buscar em parte de uma palavra é mais útil do que buscar na palavra inteira
Às vezes o objetivo real não é achar uma substring inteira, mas sim entender em que parte da palavra ela aparece. Isso é comum em análise morfológica, detecção de prefixos/sufixos, ou validação de formato. Por exemplo, digamos que você quer separar o radical de um verbo português do sufixo conjugado. "Correndo" tem o radical "corr" e o sufixo "endo". Você pode isolar o sufixo verificando se a string termina com ele:
def analisar_verbo(palavra):
sufixos = {
'endo': 'gerúndio',
'ando': 'gerúndio',
'ido': 'particípio',
'aria': 'futuro do pretérito',
'eria': 'condicional'
}
for sufixo, tempo in sufixos.items():
if palavra.endswith(sufixo):
radical = palavra[:-len(sufixo)]
return radical, tempo
return palavra, 'infinitivo'
analisar_verbo("correndo") ('corr', 'gerúndio')
analisar_verbo("amará") ('ama', 'futuro do pretérito')/code
Isso é basicamente em que parte de uma palavra o sufixo aparece — no final, claro — mas a lógica de verificar a posição relativa é o que permite classificar a forma verbal.
resumo prático
Aqui estão os pontos que valem a pena guardar:
- Sempre normaliza o encoding e a forma Unicode antes de buscar. Perde 2ms e evita horas de debugging.
- Se estiver em PHP, use
!== false em vez de != false ao testar strpos. A diferença é a igualdade estrita.
- Para encontrar todas as ocorrências, prefira o loop com índice inicial atualizado do que expressões regulares, a menos que o padrão seja complexo.
- Emoji e caracteres compostidos quebram a lógica de posição baseada em code points. Use bibliotecas de grapheme cluster se a precisão visual importar.
- Verificar em que parte de uma palavra algo aparece (início, meio, fim) é frequentemente mais produtivo do que só obter o índice bruto. Use
startswith, endswith e fatiamento antes de recorrer a buscas por índice.
Se você está lidando com grandes volumes de texto e performance importa, considere usar bibliotecas especializadas como o Rust crate memchr ou o módulo re2 do Python. Eles usam algoritmos como Boyer-Moore-Horspool que são significativamente mais rápidos para buscas repetidas em strings longas. Em benchmarks típicos, passam de dezenas de milhares de buscas por segundo em strings de megabytes, contra algumas centenas com a abordagem ingênua.