Eu tenho um requisito para filtrar palavrões dos envios de usuários em um aplicativo Web baseado em Java. O cliente conhece os problemas de Scunthorpe e Clbuttic e aceitou as consequências. Por favor, não desejo um debate sobre o mérito da falta de censura.
Existem dois bits de dados:
- O envio do usuário, que pode conter 500 palavras ou mais;
- Uma tabela de banco de dados de coluna única contendo palavras que não são permitidas. Pode haver muitos milhares de registros nesta tabela.
A presente solução parece errada para mim:
- A tabela inteira é carregada em uma String estática [] na inicialização em um Singleton (residindo assim na memória).
- Para cada envio de usuário, percorremos o array e executamos um .indexOf () para ver se alguma palavra na String [] aparece no envio.
- Se aparecer, substituímos por caracteres no estilo% $ # @%. Isso é feito tokenizando o envio do usuário, fazendo um loop por todo o envio do usuário como tokens (novamente) e substituindo cada instância da palavra encontrada.
Pode haver brilho nessa solução, mas sou cético. E tendo olhado para ele por um tempo, não consigo encontrar o meu caminho.
A pergunta é: qual é a solução que fornecerá bom desempenho e, com sorte, será razoavelmente sensata para futuros desenvolvedores manterem depois que eu for demitido por não filtrar alguma palavra obscura da qual nunca ouvi falar?
Respostas:
A única maneira de fazer um filtro de palavras de maneira inteligente é usar um sistema de correspondência fônica. Escrevi um filtro de palavrões muito eficaz para um jogo online multijogador muito popular para adolescentes e adolescentes há alguns anos em Java.
Ele foi baseado em um algoritmo Double MetaPhone altamente modificado , que foi ajustado para ser mais preciso, em vez do padrão, que deve corresponder ao máximo de coisas possível. Foi tão extremamente eficaz, pois captou erros de ortografia e fonética da mesma forma que as palavras reais. Eu adicionei
l33t
falar etxt
falar com o algoritmo metaphone, bem como, tornando-o mais de um algoritmo Triplo / Quad Metaphone.Ele apresentava um pré-processador que comprimia as letras em execução e detectava coisas como as crianças juntando coisas
w o r d s
, inteligentemente comprimindo as letras e eliminando as duplicatas em execuçãowwoorrddss
, como era muito especializado apenas em inglês.Foi rápido o suficiente há 8 anos para ser usado em um fluxo de sistema de bate-papo em tempo real sem latência perceptível com dezenas de milhares de usuários em um sistema de CPU de núcleo único.
Tínhamos uma lista de palavras codificadas pelo Metaphone em uma tabela no banco de dados, e ela era carregada em um Mapa estático surpreendentemente pequeno e nunca tivemos que fazer nada de especial para acessar a lista de palavras proibidas. Pude adicionar detecção de frase usando as mesmas técnicas quase de graça.
É claro que eu tinha um registro de todas as conversas de milhares de crianças tentando quebrar o sistema em tempo real, então eu tinha um conjunto bastante abrangente de dados para trabalhar. A maneira como fiz o registro foi quando alguém acionou o filtro com um positivo, registrei as próximas mensagens de bate-papo que não acionavam o filtro , assim, se encontrassem uma maneira de contornar uma palavra ou frase específica, eu poderia adaptar meu sistema e pegar isso. Eu era bastante à prova de balas depois de apenas algumas semanas.
fonte
Se você deseja fazer a correspondência com eficiência, o algoritmo Aho Corasick é uma opção muito boa (tenho certeza de que você pode encontrar uma implementação Java flutuando).
É claro que você provavelmente desejará pré-processar o envio para substituir qualquer irregularidade ortográfica ('$' -> 's', '@' -> 'a', '| <' -> 'k' etc.)
fonte
Em vez de carregar em uma String estática [], utilize o HashMap [] ou algum outro tipo de árvore binária (se você quiser melhorar a pesquisa), transformando a string em sua chave no hash. Divida sua String por espaços e remova a pontuação. Em seguida, você pode consultar o HashMap para cada palavra na sua divisão de cadeia; se o hashmap voltar com nulo, você saberá que tem uma palavra ruim.
O que falha aqui é o problema de Clbuttic, no qual alguém adiciona caracteres aleatórios em torno da palavra ex.
bhassda
fonte
Usar um sistema fônico não é a única solução, mas pode ser a mais simples, já que existem muitas bibliotecas de código aberto que fazem esse tipo de coisa.
A parte difícil sempre será a parte correspondente de qualquer algoritmo e parece que sua correspondência é bem lenta e ingênua. Você não pode supor que o indexOf corresponderá corretamente sem alguma forma de verificação auxiliar.
Além disso, você terminará repetidamente o tempo N da cadeia N, onde N é o número de palavras na sua lista negra. As sugestões para usar Set ou HashMap definitivamente vão melhorar um pouco as coisas.
Na maioria dos casos, um algoritmo linear baseado em estado é melhor e mais rápido. Eu escrevi a solução para o Clean Speak e ele usa esse tipo de algoritmo com um sistema de correspondência fônica pré-processo. Essa foi a única solução que não ficou complicada quando os palavrões foram incorporados (se foo é palavrões, incorporar é palavrões) e conseguiu manter um alto nível de desempenho. Ele também se adapta bem a outros idiomas sem a implementação de novos codexes.
Por fim, o pré-processamento de qualquer formulário geralmente é algo a ser evitado. Na maioria dos casos, você pode fazer a mesma coisa de maneira linear ao lidar com cada um dos caracteres da sequência.
Obviamente, eu sugiro procurar outras soluções a longo prazo, porque na maioria dos aplicativos que lidam com conteúdo gerado pelo usuário é mais complexo do que apenas a filtragem de palavrões. Muitas vezes, você também deseja filtrar informações pessoais, como e-mails e números de previdência social, e às vezes coisas como URLs. Além disso, descobrimos que a maioria dos aplicativos precisa de alguma forma de sistema de moderação e pesquisa de conteúdo. Isso aumentam consideravelmente a complexidade.
fonte
O que você deseja fazer em um caso como esse é determinar qual das duas listas de palavras é a menor. Digamos que sua lista "verboten" contenha 2000 palavras e o envio máximo de usuários seja 500 palavras. Nesse caso, você percorrerá a lista de palavras no envio do usuário e as pesquisará uma a uma na lista de palavras proibidas e vice-versa.
A outra mudança que eu faria é que você não mantém a lista de palavras proibidas em uma String [] - se você pesquisar na matriz, terá uma pesquisa de O (n) por palavra no envio do usuário. Isso é muito ruim. Eu tentaria colocar a estrutura de dados em que você está procurando em algum tipo de estrutura associativa de contêiner ou árvore que tenha um melhor desempenho de pesquisa (log n em vez de n). O desafio aqui seria que, se você colocar o envio do usuário nesse contêiner, terá que acompanhar a posição da palavra para poder reconstruir a entrada ou atualizar a string de entrada se você tiver uma ocorrência de pesquisa.
fonte