Desempenho do filtro de palavrões em Java

9

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:

  1. O envio do usuário, que pode conter 500 palavras ou mais;
  2. 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:

  1. A tabela inteira é carregada em uma String estática [] na inicialização em um Singleton (residindo assim na memória).
  2. Para cada envio de usuário, percorremos o array e executamos um .indexOf () para ver se alguma palavra na String [] aparece no envio.
  3. 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?

peixe dourado
fonte
Você diz que parece errado para você, sem nos dizer por que acha que está errado. Em seguida, você solicita uma solução com bom desempenho, sem nos informar, de que maneira a solução atual não é suficiente. Quantos textos por segundo você recebe, quantos deles você pode processar?
usuário desconhecido
Eu pensei que a solução estava errada, principalmente porque a base de código em que estou trabalhando é inadequada e desleixada. Dado o meu preconceito, não confiava na minha desconfiança. Eu senti que a opinião dos outros seria benéfica. O que despertou alarmes para mim foi o String [] (o que é este ano de 1999?), Fazendo um loop sobre o String [] muito grande, em vez do conjunto de dados muito menor que o usuário envia, aninhando um loop dentro do loop String [] com envio de usuário tokenizado e assim por diante. A utilização esperada não é especificada; idealmente, uma solução elegante com desempenho razoável seria adorável.
blueishgoldfish
2
'Desempenho razoável' pode significar qualquer coisa. Se você não tem uma meta concreta, não pode saber se a alcançou. Se você acelerar um processo, seja 100 vezes mais rápido - esse é um objetivo? Se o usuário estiver aguardando 1ms ou 1 / 10s? O usuário não se beneficiará do seu trabalho.
usuário desconhecido

Respostas:

18

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 l33tfalar e txtfalar 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ção wwoorrddss, 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
3
Esta solução parece ser a melhor. O problema é (ou estava nesse ponto) que eu tive que resolvê-lo em uma tarde. Se houver tempo suficiente, adotarei a abordagem Double MetaPhone ou a contratarei para fazê-lo. :-)
blueishgoldfish
Então, acho que metade das pessoas vai parar de jogar o jogo agora: D
Davor Dralral
2

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.)

Dmitri
fonte
Precisamente o que eu estava procurando, obrigado! Aqui é uma implementação Java: hkn.eecs.berkeley.edu/~dyoo/java
Remi Mélisson
0

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

Suroot
fonte
Penso que a última advertência é o que torna essa solução praticamente inútil - não há como estendê-la a nada além de correspondências de palavras inteiras.
Essa é uma afirmação justa; mas fica difícil capturar tudo o que a mente humana pode inventar para escapar de um filtro de palavrões. Você sempre pode criar uma expressão regular enorme com instruções OR para combinar todas as opções e depois corresponder o regex à entrada. OU você pode fazer uma seleção no banco de dados com o "campo de palavrões" do banco de dados com um RLIKE na entrada. Retorno indica palavrão e também retornará o palavrão.
@Suroot, não é difícil capturar praticamente qualquer palavra ou frase com correspondência fonética, como a minha pergunta fala. As correspondências absolutas nunca funcionarão ou serão dimensionadas, mas a correspondência fonética funcionará quase 100% das vezes depois que você ajustar o máximo possível.
-1

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.

Brian Pontarelli
fonte
-2

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.

Timo Geusch
fonte