2010-10-05 42 views
5

Hay una lista de palabras prohibidas (o cadenas para ser más generales) y otra lista con, por ejemplo, correos de usuarios. Me gustaría suprimir todas las palabras prohibidas de todos los correos.Cómo cortar palabras especificadas de la cadena

ejemplo trivial:

foreach(string word in wordsList) 
{ 
    foreach(string mail in mailList) 
    { 
     mail.Replace(word,String.Empty); 
    } 
} 

¿Cómo puedo mejorar este algoritmo?


Gracias por los consejos. He votado algunas respuestas pero no marqué ninguna como respuesta, ya que fue más como una discusión que una solución. Algunas personas se perdieron las palabras prohibidas con malas palabras. En mi caso, no tengo que molestarme en reconocer 'sh1t' o algo así.

+10

¿Estás teniendo problemas de rendimiento con esto? No optimices hasta que sea necesario. – Oded

+1

No tengo problemas de rendimiento. Solo quiero aprender y mejorar mis habilidades :-) – zgorawski

Respuesta

2

Se puede usar expresiones regulares para hacer las cosas un poco más limpio:

var bannedWords = @"\b(this|is|the|list|of|banned|words)\b"; 

foreach(mail in mailList) 
    var clean = Regex.Replace(mail, bannedWords, "", RegexOptions.IgnoreCase); 

Incluso que, sin embargo, está lejos de ser perfecto ya que la gente siempre va a encontrar una manera alrededor de cualquier tipo de filtro.

+0

Esto no elimina palabras prohibidas, está eliminando subcadenas prohibidas.Por ejemplo, esto cambiaría la palabra "a menudo" en una cadena a "diez". –

+0

@Michael - Obviamente mi RegEx-Fu no está a la altura. Agregué lo que pensé que era la forma correcta de limitar los límites de las palabras. ¿Alguna corrección? –

+0

Eso se ve mejor, gracias. Aunque mencionaré nuevamente (como a continuación) que probablemente no sea ideal hacer una Regex como esta si su lista es más que unas pocas decenas de palabras. –

5

Los enfoques simples para el filtrado de blasfemias no funcionarán; los enfoques complejos tampoco funcionan, en su mayor parte.

¿Qué sucede cuando obtiene un trabajo como 'contraseña' y quiere filtrar 'culo'? ¿Qué sucede cuando una persona inteligente escribe 'a $$' en su lugar, la intención sigue siendo clara, ¿verdad?

Consulte How do you implement a good profanity filter? para una extensa discusión.

+0

"¿Qué sucede cuando recibes un trabajo como 'contraseña' y quieres filtrar 'culo'?" - Entonces tu algoritmo apesta. –

+1

"¿Qué sucede cuando una persona inteligente escribe 'a $$' en su lugar, la intención sigue siendo clara, ¿verdad?" - Muy a menudo, reducir un problema tiene valor, no siempre se necesita una solución al problema del 100%. –

+0

@Brian - de acuerdo, estoy leyendo entre líneas aquí. Si OP solo quiere construir el código de 'mejor esfuerzo', entonces los ajustes al reemplazo de cadenas están bien. Si él/ella se inscribió para construir un filtro de blasfemia confiable, entonces el alcance del esfuerzo debe ser claro, o él/ella podría estar en problemas cuando demora más de lo esperado. –

2

Obtendrá el mejor rendimiento dibujando un finite state machine (FSM) (o genere uno) y luego analizará su entrada de 1 carácter a la vez y recorriendo los estados.

Puede hacerlo fácilmente con una función que toma su siguiente char de entrada y su estado actual y que devuelve el siguiente estado, también crea salida al recorrer los caracteres del mensaje de correo. Dibuja el FSM en un papel.

Alternativamente, podría mirar en el Windows Workflow Foundation: State Machine Workflows.

De esta forma, solo necesita recorrer cada mensaje una sola vez.

+0

A menos que malinterprete su sugerencia, tengo ganas de usar un Windows Workflow State Machine en este problema para analizar un carácter de cadena por carácter, es un poco exagerado. –

+0

Eso depende de lo que sea el software. Si la persona está tratando de construir un software de filtrado profano, entonces no lo creo. –

0

Puede considerar el uso de Regex en lugar de simples coincidencias de cadena, para evitar reemplazar el contenido parcial dentro de las palabras. Un Regex le permitiría asegurar que solo obtiene palabras completas que coincidan. Se puede usar un patrón de esta manera:

"\bBADWORD\b" 

Además, es posible que desee iterar sobre la lista de correo en el exterior, y la lista de palabras en el bucle interno.

1

construir una expresión normal de las palabras (word1|word2|word3|...) y utilizar esto en vez del bucle externo podría ser más rápido, desde entonces, cada correo electrónico sólo tiene que ser analizado una vez. Además, el uso de expresiones regulares le permitiría eliminar solo "palabras completas" utilizando los marcadores de límite de palabras (\b(word1|word2|word3|...)\b).

En general, yo no creo que se encontrará una solución que es varios órdenes de magnitud más rápido que el actual uno: Usted se tiene que recorrer todos los correos y se tiene que buscar todas las palabras , no hay una manera fácil de evitar eso.

1

Un algoritmo general sería:

  1. generar una lista de fichas basado en la cadena de entrada
  2. comparar cada señal a una lista de palabras prohibidas
  3. (es decir, mediante el tratamiento de los espacios en blanco como separadores de fichas.)
  4. fichas Reemplazar emparejados

una expresión regular es conveniente para la identificación de fichas, y un HashSet proporcionaría búsquedas rápidas para su lista de palabras prohibidas. Hay un método Replace sobrecargado en la clase Regex que toma una función, donde puede controlar el comportamiento de reemplazo en función de su búsqueda.

HashSet<string> BannedWords = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase) 
{ 
    "bad", 
}; 

string Input = "this is some bad text."; 

string Output = Regex.Replace(Input, @"\b\w+\b", (Match m) => BannedWords.Contains(m.Value) ? new string('x', m.Value.Length) : m.Value); 
+0

Sin embargo, esto no hace uso de la potencia de Regex. Simplemente abstrae el bucle de reemplazo. Ver [la respuesta de Justin] (http://stackoverflow.com/questions/3864678/how-to-cut-specified-words-from-string/3864743#3864743) para lo que quiero decir. –

+0

@Ahmad Mageed: estoy usando una expresión regular simple (y rápida) para producir una secuencia de tokens a partir de una cadena. ¿Qué más energía necesito? Tampoco creo que sea ideal (o realizador) tomar cientos de palabras prohibidas y construir una gran expresión regular como en la solución de Justin. –

0

¿No sería más fácil (y más eficiente) a redactar simplemente cambiando todos ellos sus personajes a * o algo? De esta forma, no es necesario cambiar el tamaño de una cadena grande o moverla, y las recitivas se vuelven más conscientes de lo que sucedió, en lugar de obtener oraciones sin sentido con palabras faltantes.

+0

¿Por qué sería esto más eficiente? – Heinzi

+0

@Heinzi - Editado para incluir esa información. Básicamente, Replace tendrá que mover los datos después de la cadena reemplazada, a menos que lo reemplaces con la misma cantidad de caracteres. –

+0

'Reemplazar 'creará una instancia de cadena completamente nueva de todos modos, ya que las cadenas son inmutables. ¡Estoy de acuerdo con tu punto de usabilidad, sin embargo! – Heinzi

1

Reemplazarlo con * es molesto, pero menos molesto que algo que elimina el contexto de su intención quitando la palabra y dejando una oración mal formada. Al hablar sobre la Batalla de Hastings, me irritaría si viera a William con el título "Grand ******* of Normandy", pero al menos sabría que estaba jugando en el patio de juegos para niños pequeños, mientras que él tiene el título de" Gran de Normandía " simplemente parece un error, o (peor) podría pensar que ese era realmente su título.

No intentes reemplazar palabras con palabras más inocuas a menos que sea gracioso. La gente tiene la broma en 4chan, pero los grupos de yahoo sobre la historia tenían personas confundidas porque los períodos medireview y mediareview estaban siendo discutidos cuando eval (no blasfemias, pero se usa en algunos ataques XSS que yahoo había sido golpeado por) fue reemplazado con revisión en medieval y medieval (aparentemente, ¡medireview es la ortografía estadounidense de la revisión de medios!).

+0

Esto es más o menos lo mismo que mi respuesta, y se envió aproximadamente al mismo tiempo. Cuando eso sucede, mi política general es que el remitente es claramente un genio y merece un +1. :-) –

0

Bueno, ciertamente No quiero cometer el error clásico de una cadena ingenua. Reemplazar() para hacerlo. La solución de expresiones regulares podría funcionar, aunque podría iterar o usar el alternador de tuberías (y no sé si/cuánto eso desaceleraría su operación, particularmente para una gran lista de palabras prohibidas). Siempre se puede simplemente ... no hacerlo, ya que es completamente inútil, no importa qué; hay formas de hacer que sus palabras intencionadas sean bastante claras incluso sin utilizar las letras exactas.

Eso, y es ridículo tener una lista de palabras que "las personas consideran ofensivas" en primer lugar.Hay alguien que va a ser ofendido por casi cualquier palabra

/censura es una mierda diatriba

1

En algunas circunstancias es posible mejorarla: Sólo por diversión:

u puede utilizar SortedList, si ur correo la lista es la lista de correo (porque tiene un delimitador como ";") puede hacer lo siguiente:

primero calcule su algoritmo de tiempo de ejecución: Palabras: n elemento. (cada elemento tiene una longitud O (1)). lista de correo: K artículo. cada artículo en la lista de correos tiene una longitud promedio de Z. cada subelemento en la longitud promedio del artículo de la lista de correo de Y por lo que el número promedio de subelementos en los artículos de la lista de correo es m = Z/Y.

tu algoritmo toma O (n * K * Z). // la mejor manera con el algoritmo knut

1. ahora si ordena la lista de palabras en O (n log n).

2.1- use mailingListItem.Split (";". ToCharArray()) para cada elemento de la lista de correo: O (Z). 2.2- ordenar los artículos en la lista de correo: O (m * log m) clasificación total toma O (K * Z) en el caso de valor con respecto a (m logm < < Z).

3- algoritmo uso de combinación para combinar elementos de mala palabra y lista de correo específica: O ((M + n) * k)

tiempo total es O ((m + n) * K + m * Z + n^2) con respecto a m < < n, el tiempo total de ejecución del algoritmo es O (n^2 + Z * K) en caso de valor, que es menor que O (n * K * Z) si n < K * Z (creo que si).

Por lo tanto, si el rendimiento es muy, muy importante, puede hacerlo.

0

Supongo que quiere detectar solo palabras completas (separadas por caracteres que no sean letras) e ignorar palabras con una subcadena de palabra de filtro (como un ejemplo de palabra p [ass]). En ese caso, debe construir un HashSet de palabras de filtro, escanear el texto en busca de palabras y, para cada palabra, verificar su existencia en HashSet. Si se trata de una palabra de filtro, compila el objeto StringBuilder resultante sin él (o con el mismo número de asteriscos).

Cuestiones relacionadas