none
Comparar dos listas ignorando los acentos

    Pregunta

  • Hola.
    He llenado una lista, (palabras = new List(Of String)), con algunas palabras acentuada y otras que no lo están.
    además
    ademas
    adémas
    cónclave
    conclave
    conclavé
    métal
    metal
    metál
    En otra lista, (buscar = new List(Of String)), hay otras palabras y ninguna esta con acento.
    ademas
    conclave
    metal
    Lo que intento es leer todas las líneas de la lista palabras partiendo de la lista buscar
    Gracias por su ayuda.

    He editado el Título   "Comparar dos listas genéricas ignorando los acentos"

    Lo he cambiado por: "Comparar dos Listas ignorando los acentos"



    • Editado Rafael F.M miércoles, 17 de abril de 2019 21:18
    miércoles, 17 de abril de 2019 1:14

Todas las respuestas

  • Hola  

    Gracias por levantar tu consulta en los foros de MSDN. Con respecto a la misma, te hago la recomendación de ingresar al siguiente enlace en donde puedes encontrar una posible solución para tu problema.

    https://social.msdn.microsoft.com/Forums/es-ES/e1bca596-ea1f-4212-9dca-6e7bd8c5dd3a/comparar-dos-listas-genericas-con-linq?forum=vcses

    https://social.msdn.microsoft.com/Forums/es-ES/cad28955-0073-4a3d-847a-50da39da7405/bsqueda-ignorando-acentos?forum=vbes

    https://social.msdn.microsoft.com/Forums/es-ES/3371ca9d-4495-4e32-8bee-087b67114966/comparar-dos-listas?forum=vbes

    Gracias por usar los foros de MSDN.

    Carlos Ruiz
     ____

    Por favor recuerde "Marcar como respuesta" las respuestas que hayan resuelto su problema, es una forma común de reconocer a aquellos que han ayudado, y hace que sea más fácil para los otros visitantes encontrar la solución más tarde. 

    Microsoft ofrece este servicio de forma gratuita, con la finalidad de ayudar a los usuarios y la ampliación de la base de datos de conocimientos relacionados con los productos y tecnologías de Microsoft.  

    Este contenido es proporcionado "tal cual" y no implica ninguna responsabilidad de parte de Microsoft.
    • Propuesto como respuesta Pablo Rubio jueves, 2 de mayo de 2019 16:55
    miércoles, 17 de abril de 2019 14:31
  • Hola  

    Gracias por levantar tu consulta en los foros de MSDN....

    Gracias por la respuesta. Pero no he obtenido conclusiones para resolver mi problema.

    • Editado Rafael F.M viernes, 19 de abril de 2019 1:13
    miércoles, 17 de abril de 2019 21:21
  • Esto es lo que he conseguido hasta ahora.

        ReadOnly palabras As New List(Of String)
        ReadOnly buscar As New StringBuilder()
        Sub MeLoad() Handles Me.Load
            Dim plbrs() As String = File.ReadAllLines("pruebas\palabras.txt")
            For Each line As String In plbrs
                palabras.Add(line.Trim)
            Next
            Dim bscr As New StreamReader("pruebas\buscar.txt")
            buscar.Append(bscr.ReadToEnd)
        End Sub
    
        Sub Boton1() Handles Button1.Click
            CompararIgnorandoAcentos(palabras, buscar)
        End Sub
    
        Private Function CompararIgnorandoAcentos(ByVal lista1 As List(Of String), ByVal lista2 As StringBuilder) As Boolean
            Return String.Compare(EliminarAcentos(lista1.ToString), EliminarAcentos(lista2.ToString), StringComparison.InvariantCultureIgnoreCase) = 0
        End Function
        Private Function EliminarAcentos(ByVal strng As String) As String
            Dim descodificar As Encoding = Encoding.GetEncoding("iso-8859-8")
            RtbCoincidencias.Text = descodificar.GetString(Encoding.Convert(Encoding.UTF8, descodificar, Encoding.UTF8.GetBytes(strng)))
            'Return descodificar.GetString(Encoding.Convert(Encoding.UTF8, descodificar, Encoding.UTF8.GetBytes(s)))
            Return Nothing
        End Function
    

    Encuentra en palabras(Of String) todas las palabras que contiene la StringBuilde, estas acentuadas o no. Pero las devuelve sin acentos.

    gracias de nuevo

    miércoles, 17 de abril de 2019 22:16
  • Y, con lo siguiente, solo puedo buscar una palabra a la vez.

        Sub Boton1() Handles Button1.Click
            Dim palabras As New List(Of String)
            Dim buscar As New StringBuilder()
            Dim plbrs() As String = File.ReadAllLines("pruebas\palabras.txt")
            For Each line As String In plbrs
                palabras.Add(line.Trim)
            Next
            Dim bscr As New StreamReader("pruebas\buscar.txt")
            buscar.Append(bscr.ReadToEnd)
    
            For Each strng In palabras
                Dim tempBytes() As Byte
                tempBytes = Encoding.GetEncoding("ISO-8859-8").GetBytes(strng)
                Dim asciiStr As String = Encoding.UTF8.GetString(tempBytes)
    
                Dim match As Match = Regex.Match(asciiStr, "CODICE") ' Solo puedo buscar de una en una palabras
                If match.Success Then
                    RtbCoincidencias.Text = (strng)
                    'ListBox1.Items.Add(strng)
                End If
            Next
            'Resultado
            ' CÓDICE
        End Sub
    

    miércoles, 17 de abril de 2019 23:51
  • Existe en el Framework una forma bastante sencilla de realizar comparaciones despreciando los acentos:

    string.Compare(s1, s2, CultureInfo.CurrentCulture, CompareOptions.IgnoreNonSpace)


    Aunque suene raro, la opcion "IgnoreNonSpace" en realidad significa "ignorar acentos" aunque el nombre no parezca indicarlo:

    https://docs.microsoft.com/en-us/dotnet/api/system.globalization.compareoptions?view=netframework-4.7.2

    Esto es bastante mas simple (y eficiente en tiempo de ejecucion) que el RegEx o la funcion EliminarAcentos que estabas usando en tus ejemplos anteriores.

    Editado: Si lo quieres usar con sencillez para comparar dos listas, simplemente usa la funcion Intersect de LINQ: primeraLista.Intersect(segundaLista, comparador) y en el comparador utiliza el string.Compare indicado antes.

    https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.intersect?view=netframework-4.7.2


    jueves, 18 de abril de 2019 8:16
  • Gracias por las respuestas.

            Dim palabras As New List(Of String)
            Dim plbrs() As String = File.ReadAllLines("pruebas\palabras.txt")
            For Each line As String In plbrs
                palabras.Add(line.Trim)
            Next
    
            'Dim buscar As New StringBuilder
            'Dim bscr As New StreamReader("pruebas\buscar.txt")
            'buscar.Append(bscr.ReadToEnd)
    
            For Each strng In palabras
                Dim bts() As Byte
                bts = Encoding.GetEncoding("ISO-8859-8").GetBytes(strng)
                Dim encdg As String = Encoding.UTF8.GetString(bts)
    
                Dim valor As String = encdg.ToArray
                Dim matchs As MatchCollection = Regex.Matches(valor, "(^" & "CODI" & ".*)") 'No se como adaptar aquí para buscar secuencias
                For Each mtchs As Match In matchs
                    For Each cptrs As Capture In mtchs.Captures
                        RichTextBox1.AppendText(strng & vbLf)
                    Next
                Next
            Next
            'Resultado
            'CODICE
            'CÓDICE
            'CÓDIGO
            'CODILLERA
            'CODILLO
            'CODÍN

     Dim matchs As MatchCollection = Regex.Matches(valor, "(^" & "CODI" & ".*)") 'No se como buscar varias palabras contenidas en un StringBuilder.

    EDITO

    También lo siguiente,

      Dim palabras As New List(Of String)
            Dim plbrs() As String = File.ReadAllLines("pruebas\palabras.txt")
            For Each line As String In plbrs
                palabras.Add(line)
            Next
            Dim bscrz As New List(Of String)
            Dim buscars() As String = File.ReadAllLines("pruebas\buscar.txt")
            For Each line As String In buscars
                bscrz.Add(line.Trim)
            Next
    
            'Dim buscar As New StringBuilder
            'Dim bscr As New StreamReader("pruebas\buscar.txt")
            'buscar.Append(bscr.ReadToEnd)
    
            For Each strng In palabras
                Dim bts() As Byte
                bts = Encoding.GetEncoding("ISO-8859-8").GetBytes(strng)
                Dim encdg As String = Encoding.UTF8.GetString(bts)
    
                Dim valor As String = encdg.ToArray
                Dim matchs As MatchCollection
    
                Dim str As String
                For Each str In bscrz
                    matchs = Regex.Matches(valor, "(^" & str & ".*)", RegexOptions.Multiline) ' "(^" & "CODI" & ".*)") 'No se como adaptar aquí para buscar secuencia
                Next
    
                For Each mtchs As Match In matchs
                    For Each cptrs As Capture In mtchs.Captures
                        RichTextBox1.AppendText(strng & vbLf)
                    Next
                Next
            Next
    Pero solo obtengo el resultado de la última palabra de la List Buscar

    • Editado Rafael F.M jueves, 18 de abril de 2019 15:38
    • Propuesto como respuesta Pablo Rubio jueves, 2 de mayo de 2019 16:55
    jueves, 18 de abril de 2019 14:18
  • Existe en el Framework una forma bastante sencilla de realizar comparaciones despreciando los acentos:

    string.Compare(s1, s2, CultureInfo.CurrentCulture, CompareOptions.IgnoreNonSpace)

    Aunque suene raro, la opcion "IgnoreNonSpace" en realidad significa "ignorar acentos" aunque el nombre no parezca indicarlo:

    Editado: Si lo quieres usar con sencillez para comparar dos listas, simplemente usa la funcion Intersect de LINQ: primeraLista.Intersect(segundaLista, comparador) y en el comparador utiliza el string.Compare indicado antes.

    Gracias Alberto. Pero no lo consigo. A lo máximo que he llegado es lo siguiente,

    Dim obtacentos As IEnumerable(Of String) = palabras.Intersect(busqueda, StringComparer.InvariantCultureIgnoreCase)
    RichTextBox1.Lines = obtacentos.ToArray

     y no funciona.

    Solo devuelve palabras que no llevan acento.

    Edito:

    palabras y busqueda son List(Of String)

    • Editado Rafael F.M viernes, 19 de abril de 2019 4:28
    viernes, 19 de abril de 2019 4:17
  •  A lo máximo que he llegado es lo siguiente,

    Dim obtacentos As IEnumerable(Of String) = palabras.Intersect(busqueda, StringComparer.InvariantCultureIgnoreCase)
    RichTextBox1.Lines = obtacentos.ToArray

     y no funciona.

    No, claro que no funciona. El "InvariantCultureIgnoreCase" solo ignora las mayúsculas y minúsculas, pero no los acentos. No vale esa técnica, tienes que usar la que yo te he dicho usando el string.Compare. Para podérselo pasar al Intersect tienes que meterlo dentro de una clase que implemente la interfaz necesaria y pasarle al intersect una instancia de la clase. En el enlace que te puse hay un ejemplo de cómo se hace esto.
    • Propuesto como respuesta Carlos_Ruiz_M martes, 23 de abril de 2019 15:07
    viernes, 19 de abril de 2019 8:36
  •         'palabras = List(Of String) Intersect busqueda = List(Of String)
            Dim intersection = palabras.Intersect(busqueda) 'No consigo meter String.Compare(str1, str2, CultureInfo.CurrentCulture, CompareOptions.IgnoreNonSpace)
            Dim output As New StringBuilder
            For Each id As String In intersection
                output.AppendLine(id)
            Next
            RichTextBox1.Text = output.ToString

    viernes, 19 de abril de 2019 11:24
  • 'No consigo meter String.Compare

    Claro, no consigues meterlo porque no se mete ahí. Hay que meterlo en una clase. Después se crea una instancia de la clase. Y después, esa instancia es la que metes en el Intersect. Viene un ejemplo hecho en el enlace que te pasé:

    https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.intersect?view=netframework-4.7.2

    Fíjate en donde viene el ProductComarer, cómo lo instancia y se lo pasa al Intersect.

    Adaptándolo a tu caso, quedaría aproximadamente como te pongo a continuación. Hago énfasis en "aproximadamente". Lo he escrito de memoria, sin probarlo. Lo importante es que entiendas el objetivo y lo adaptes a tus necesidades, no que lo copies literalmente sin cambiar ni una sola coma, porque es probable que no funcione a la primera.

    Public Class MiComparador
        Implements IEqualityComparer(Of String)
    
        Public Function Equals1(
            ByVal x As String, 
            ByVal y As String
            ) As Boolean Implements IEqualityComparer(Of String).Equals
            Return String.Compare(x, y, CultureInfo.CurrentCulture, CompareOptions.IgnoreNonSpace)
        End Function
    
        Public Function GetHashCode1(
            ByVal s As String
            ) As Integer Implements IEqualityComparer(Of String).GetHashCode
            Return 0 'No será muy eficiente pero en tu caso concreto no te afectará
        End Function
    End Class
    
    ...
    
            Dim intersection = palabras.Intersect(busqueda, new MiComparador())
    
    ...

    • Marcado como respuesta Rafael F.M sábado, 20 de abril de 2019 3:54
    • Desmarcado como respuesta Rafael F.M sábado, 20 de abril de 2019 4:05
    • Propuesto como respuesta Carlos_Ruiz_M lunes, 22 de abril de 2019 14:37
    • Votado como útil Carlos_Ruiz_M martes, 23 de abril de 2019 15:07
    • Propuesto como respuesta Carlos_Ruiz_M martes, 23 de abril de 2019 15:07
    viernes, 19 de abril de 2019 18:49
  • Claro, no consigues meterlo porque no se mete ahí. Hay que meterlo en una clase. Después se crea una instancia de la clase. Y después, esa instancia es la que metes en el Intersect. Viene un ejemplo hecho en el enlace que te pasé:

    Voy a probar. A ver si lo consigo de una vez.

    Mi problema es que el inglés que conozco, es el que voy aprendiendo leyendo código. Puedo hablarlo, pero muy poco y bastante mal. La verdad, me cuesta bastante retener el lenguaje de programación. Si éste estuviese escrito en castellano...

    Gracias de nuevo.

    sábado, 20 de abril de 2019 1:33
  • Gracias Alberto.

    Pero, para mi, ha estado bastante difícil.

        Public Class MiComparador
            Implements IEqualityComparer(Of String)
            Public Function Equals1(ByVal x As String, ByVal y As String) As Boolean Implements IEqualityComparer(Of String).Equals
                Return (x = y = String.Compare(x, y, CultureInfo.CurrentCulture, CompareOptions.IgnoreNonSpace))
            End Function
            Public Function GetHashCode1(ByVal s As String) As Integer Implements IEqualityComparer(Of String).GetHashCode
                Return Nothing  'No será muy eficiente pero en tu caso concreto no te afectará
            End Function
        End Class

    Pero ahora no se obtienen, también, las palabras que no tienen acentos.

    La lista búsqueda contiene:

    ACIDO

    La lista palabras contiene:

    ACIDO
    ÁCIDO

    El resultado es: ÁCIDO

    Falta ACIDO

    ...

    EDITO: Solucionado

            Public Function Equals1(ByVal x As String, ByVal y As String) As Boolean Implements IEqualityComparer(Of String).Equals
                Return (x = y Or x = y = String.Compare(x, y, CultureInfo.CurrentCulture, CompareOptions.IgnoreCase Or CompareOptions.IgnoreNonSpace))
            End Function

    EDITO:

    Perdón por anular la marca de respuesta.

    Falta lo de al revés. Buscar palabras con acentos y sin acentos, desde palabras con acentos o sin acentos.

    Además! Para lo que intento va lento. Tengo que implementar una lista de anagramas de 9 letras, validando éstos desde otra lista con unas 600.000 palabras, y compararlos con esta última lista para obtener palabras acentuadas y palabras átonas.



    • Editado Rafael F.M sábado, 20 de abril de 2019 8:20
    sábado, 20 de abril de 2019 3:23
  • Para lo que intento va lento. Tengo [...] lista con unas 600.000 palabras [...]

    Ah, eso es distinto. Basado en los ejemplos que pusiste, pensé que las listas eran pequeñas. Pero si son muy grandes, es importante que funcione bien la tabla de Hash interna que usa el Intersects. Eso significa que no vale con que el GetHashCode retorne cero como te puse, sino que hay que devolver un código hash con una distribución aceptable. Se me ocurre algo como esto:

    Return Encoding.ASCII.GetString(Encoding.ASCII.GetBytes(s)).GetHashCode()

    Eso hará que se pierdan los caracteres acentuados y luego se tome el hash del string resultante. Debería tener una distribución bastante decente suponiendo que los caracteres acentuados son proporcionalmente pocos en comparación con el resto de caracteres.

    Para esos volúmenes de datos (600000 registros) también puede ser que te interese usar una base de datos. SQL Server se puede configurar con una comparación insensible a acentos, y puede indexar los datos de manera también insensible. Debería en principio ser rapidísimo para este tipo de búsqueda.

    sábado, 20 de abril de 2019 8:41
  • Para lo que intento va lento. Tengo [...] lista con unas 600.000 palabras […]

    Return Encoding.ASCII.GetString(Encoding.ASCII.GetBytes(s)).GetHashCode()

    Para esos volúmenes de datos (600000 registros) también puede ser que te interese usar una base de datos. SQL Server se puede configurar con una comparación insensible a acentos, y puede indexar los datos de manera también insensible. Debería en principio ser rapidísimo para este tipo de búsqueda.

    Voy a intentarlo...
    sábado, 20 de abril de 2019 9:01
  • Para lo que intento va lento. Tengo [...] lista con unas 600.000 palabras […]

    Return Encoding.ASCII.GetString(Encoding.ASCII.GetBytes(s)).GetHashCode()

    Voy a intentarlo...

    Por ahora he logrado losiguiente:

    Return Encoding.ASCII.GetString(Encoding.ASCII.GetBytes(s.Count)).GetHashCode()

    Sin tornillos no hay molinillos

    EDITO: Mejor!. Creo!

    Public Function GetHashCode1(ByVal s As String) As Integer Implements IEqualityComparer(Of String).GetHashCode
        Return s.Length
    End Function


    • Editado Rafael F.M sábado, 20 de abril de 2019 12:06
    sábado, 20 de abril de 2019 9:17
  • Ambas funciones te darán un código de Hash muy malo. Si devuelves la longitud del string como hash, habrá muy poca variación ya que tendrás muy pocas longitudes distintas. Casi todos tus strings caerán en media docena de buckets de la tabla de hash, y funcionará casi tan mal como la primera opción que devolvía cero.

    La otra opción, donde has añadido .Count detrás del string, es igual de mala. El count te devolverá la longitud, y al convertirla en string y tomar los bytes te saldrá igualmente solo media docena de valores distintos. Es necesario que la dejes como yo la tenía, poniendo s y no s.Count.

    La función "GetBytes" a la que está llamando es esta:

    https://docs.microsoft.com/en-us/dotnet/api/system.text.encoding.getbytes?view=netframework-4.8#System_Text_Encoding_GetBytes_System_String_

    que como ves recibe un string (la s) y no un número (s.Count). En VB no te da error el s.Count porque no tienes puesto el "Option Strict On" (deberías ponerlo, te evitará muchos errores) y entonces hace una conversión implícita de integer a string. Pero aunque el VB te quite el error, no deja de ser una pésima función de Hash.

    • Propuesto como respuesta Carlos_Ruiz_M lunes, 22 de abril de 2019 14:37
    sábado, 20 de abril de 2019 19:30