Grandamakulo   17-10-2023, 20:43
#1
Hola, compañeros. Debido a un programa con el que me estoy quebrando la cabeza he estado elucubrando con los algoritmos de búsqueda. Y, como soy de reinventar la pólvora, aunque existe ya una función muy buena para la búsqueda de elementos en matrices, pues me he dicho «vamos a comparar C++ compilado con gambas3». ¿Por qué? Pues porque yo lo valgo. En fin, al grano que me disperso.
Con el método MiMatriz.Find(MiTexto) se obtiene el índice que la primera cadena MiTexto tiene dentro de la matriz de cadenas MiMatriz. Hasta aquí, todo perfecto. Pero este método se emplea con matrices que están desordenadas y que pueden contener duplicidades. Pero, ¿y si no es así? ¿Y si la matriz está ordenada? ¿Existe alguna manera de acelerar la búsqueda aprovechando esa propiedad? Pues sí, existen varios métodos, por ejemplo el de Bisección o el de la Secante. Sí, ambos se emplean para resolver ecuaciones, pero es que el problema es similar.
Para comparar métodos he cargado una matriz enorme de valores únicos, en este caso el CREA de la RAE — CREA —, con todas las palabras en español del corpus de la RAE. Sobre esa matriz de unos 740.000 elementos —de los que realmente válidos me parecen sólo unos 400.000, por cierto—, he creado otra aleatoria con un número a elegir de elementos para buscar con todos los métodos, para que así sean comparables. Además he generado una función llamada Vacío que directamente devuelve «-1» para poder comparar el tiempo de llamada de funciones, por si fuese relevante.
En primer lugar, ejecuto el método directo, siendo sLista la matriz con todas las palabras y sPrueba la matriz con las palabras a buscar.
Código:
 '' Directa
  Tic = Now
  For k = 0 To sPrueba.Max
    j = sLista.Find(sPrueba[k])
  Next
  Toc = Now
  Print "Prueba Directa:  ", (Toc - Tic) * 24 * 3600
Este esquema es el que uso para evaluar cada método, es decir, Tic y Toc, o mejor, su diferencia, es lo que se tarda en ejecutar cada método.
Después, intento una búsqueda a lo bestia similar a la que hace este método, pero en Gambas3 sin compilar, claro:
Código:
, sTexto As String) As Integer
  ' **** Búsqueda de una cadena en una matriz de cadenas
  ' <<<< Devuelve el índice de la coincidencia
  ' >>>> sMatriz es una matriz de cadenas en la que buscar
  ' >>>> sTexto es la cadena a buscar
  
  ' Busca los valores uno por uno hasta que encuentra el primero coincidente.
  ' No importa que la matriz esté desordenada. Puede tener valores repetidos.
  ' Si no encuentra ninguno, devuelve «-1»

  Dim k As Integer                'Contador genérico

  For k = 0 To sMatriz.Max
    If sMatriz[k] = sTexto Then 
      Return k
    Endif
  Next
  Return -1

End

Por último, probemos con el método de bisección:
Código:
, sTexto As String) As Integer

  ' **** Búsqueda de una cadena en una matriz de cadenas ORDENADA
  ' <<<< Devuelve el índice de la coincidencia
  ' >>>> sMatriz: es una matriz de cadenas en la que buscar
  ' >>>> sTexto: es la cadena a buscar
  
  ' A partir de los valores extremos de la matriz, busca un valor intermedio. En
  ' función de si ese valor es mayor o menor que el de referencia, cambia los 
  ' extremos y vuelve a buscar un valor intermedio. Para cuando encuentra el valor
  ' verdadero o cuando la diferencia de los extremos es uno o menor.
  ' La matriz tiene que estar ordenada ascendentemente. Si no es así, podría entrar
  ' en un bucle infinito.Puede tener valores repetidos, pero el valor devuelto será
  ' un índice al azar de entre todos los del
  ' texto repetido.
  ' Si no encuentra ninguno, devuelve «-1»

  Dim iA, iB, iC As Integer                ' Índices de trabajo
  
  ' Valores de punto inicial los índices extremos de la matriz
  iA = 0                 ' Indice menor = 0, se devuelve si es el buscado.
  If sMatriz[iA] = sTexto Then Return iA
  iB = sMatriz.Max       ' Índice máximo, se devuelve si es el buscado.
  If sMatriz[iB] = sTexto Then Return iB
  
  Do Until iB - iA <= 1   ' Hasta que la diferencia de los extremos sea menor o uno
    iC = (iB + iA) \ 2     ' Índice intermedio, se devuelve si es el buscado.
    If sMatriz[iC] = sTexto Then Return iC
    ' Se cambian los extremos, según de cuál esté más cerca el valor intermedio.
    If sTexto < sMatriz[iC] Then
        iB = iC
      Else 
        iA = iC
    Endif
  Loop 
  Return -1             ' Si no lo encuentra, devuelve «-1»

End

Empleando las nuevas características de «tablas» implementadas en el foro,  Tongue  —gracias, maestro— muestro los resultados para varias pruebas, según el número de palabras buscadas:
                                                                                                                                                   
Tipo PruebaNúmero de pruebas
110100100010000100000
Prueba Vacío 00000,0030174851417540,047998130321503
Prueba Directa 0,011989474296570,0390261411666870,3540113568305973,6289885640144434,9390178918839460,84501594305
Prueba Bestia 0,0770062208175660,2999782562255862,6849985122680727,3050218820572260,1239964365963800,15601217747
Prueba Bisección000,0010058283805850,007000565528870,0569701194763180,678008794784546

El método de Bisección es ¡¡500!! veces más rápido que la función interna. También podríamos implementar el método de la secante, pero requiere la medición de distancias entre cadenas de gb.Util que es especialmente lenta, por lo que, de momento, paso.
En fin, a continuación dejo el código completo. Eso sí, el archivo «CREA_total.TXT» tiene que estar en el directorio de la aplicación:
Código:
' Gambas module file

Public Sub Main()

  Dim sTotal As String[]              ' Archivo CREA entero, separado por líneas
  Dim Tic, Toc As Date                ' Dos fechas/horas para medir intervalos de tiempo
  Dim sPaso As String[]               ' Cada línea de sTotal, separada por tabulaciones
  Dim sLista As New String[]          ' Lista total de palabras de CREA (segunda columna)
  Dim sPrueba As New String[10]       ' Lista de palabras para test. nº elementos=nº de pruebas.
  Dim k As Integer                    ' Contador genérico
  Dim j As Integer                    '    "       "
  
  Tic = Now
  sTotal = Split(File.Load("CREA_total.TXT"), "\n")
  Toc = Now
  Print "Tiempo de lectura: ", sTotal.Count, (Toc - Tic) * 24 * 3600
  Tic = Now
  'Fila 0 es el encabezado
  For k = 1 To sTotal.Max
    sPaso = Split(sTotal[k], "\t")
    Try sLista.Add(Trim(sPaso[1]))  ' Palabra en 2ª columna (comienza en 0)
  Next
  sLista.Sort()
  Toc = Now
  Print "Tiempo ordenando: ", sLista.Count, (Toc - Tic) * 24 * 3600
  
  ' Preparar una lista de palabras aleatorias para buscar como prueba común
  For k = 0 To sPrueba.Max
    sPrueba[k] = sLista[CInt(Rnd(0, sLista.Max))]
  Next
  
  '' Vacío : como referencia, cúanto tarda en llamar a la función
  Tic = Now
  For k = 0 To sPrueba.Max
    j = Vacio(sLista, sPrueba[k])
  Next
  Toc = Now
  Print "Prueba Vacío"; " :"; (Toc - Tic) * 24 * 3600
  
  '' Directa
  Tic = Now
  For k = 0 To sPrueba.Max
    j = sLista.Find(sPrueba[k])
  Next
  Toc = Now
  Print "Prueba Directa"; " :"; (Toc - Tic) * 24 * 3600
  
  '' Bestia
  Tic = Now
  For k = 0 To sPrueba.Max
    j = Bestia(sLista, sPrueba[k])
  Next
  Toc = Now
  Print "Prueba Bestia"; " :"; (Toc - Tic) * 24 * 3600
  
   '' Bisección
  Tic = Now
  For k = 0 To sPrueba.Max
    j = Buscar_Ordenada(sLista, sPrueba[k])
  Next
  Toc = Now
  Print "Prueba Bisección"; " :"; (Toc - Tic) * 24 * 3600

End

Private Function Bestia(sMatriz As String[], sTexto As String) As Integer
  ' **** Búsqueda de una cadena en una matriz de cadenas
  ' <<<< Devuelve el índice de la coincidencia
  ' >>>> sMatriz es una matriz de cadenas en la que buscar
  '      sTexto es la cadena a buscar
  
  ' Busca los valores uno por uno hasta que encuentra el primero coincidente.
  ' No importa que la matriz esté desordenada. Puede tener valores repetidos.
  ' Si no encuentra ninguno, devuelve «-1»

  Dim k As Integer                'Contador genérico

  For k = 0 To sMatriz.Max
    If sMatriz[k] = sTexto Then 
      Return k
    Endif
  Next
  Return -1

End

Private Function Buscar_Ordenada(sMatriz As String[], sTexto As String) As Integer

  ' **** Búsqueda de una cadena en una matriz de cadenas ORDENADA
  ' <<<< Devuelve el índice de la coincidencia
  ' >>>> sMatriz: es una matriz de cadenas en la que buscar
  ' >>>> sTexto: es la cadena a buscar
  
  ' A partir de los valores extremos de la matriz, busca un valor intermedio. En
  ' función de si ese valor es mayor o menor que el de referencia, cambia los 
  ' extremos y vuelve a buscar un valor intermedio. Para cuando encuentra el valor
  ' verdadero o cuando la diferencia de los extremos es uno o menor.
  ' La matriz tiene que estar ordenada ascendentemente. Si no es así, podría entrar
  ' en un bucle infinito.Puede tener valores repetidos, pero el valor devuelto será
  ' un índice al azar de entre todos los del
  ' texto repetido.
  ' Si no encuentra ninguno, devuelve «-1»

  Dim iA, iB, iC As Integer                ' Índices de trabajo
  
  ' Valores de punto inicial los índices extremos de la matriz
  iA = 0                 ' Indice menor = 0, se devuelve si es el buscado.
  If sMatriz[iA] = sTexto Then Return iA
  iB = sMatriz.Max       ' Índice máximo, se devuelve si es el buscado.
  If sMatriz[iB] = sTexto Then Return iB
  
  Do Until iB - iA <= 1   ' Hasta que la diferencia de los extremos sea menor o uno
    iC = (iB + iA) \ 2     ' Índice intermedio, se devuelve si es el buscado.
    If sMatriz[iC] = sTexto Then Return iC
    ' Se cambian los extremos, según de cuál esté más cerca el valor intermedio.
    If sTexto < sMatriz[iC] Then
        iB = iC
      Else 
        iA = iC
    Endif
  Loop 
  Return -1             ' Si no lo encuentra, devuelve «-1»

End

Private Function Vacio(sMatriz As String[], sTexto As String) As Integer

  Return -1

End


PS.—El «orden» del método directo o secuencial —o «bestia», Wink— es O(n). Esto es, al duplicarse la cantidad de elementos de la lista, se duplica el tiempo. El en caso de la «bisección», el orden es log(n).
Última modificación: 18-10-2023, 18:05 por Grandamakulo.

En un lugar de La Mancha de cuyo nombre me acuerdo perfectamente...
Grandamakulo   18-10-2023, 18:02
#2
Actualizado, con el código comentado, la tabla con más pruebas y, sobre todo, eliminado del código los  «[ i ]» o los «[ B ]», que aunque estén en el código, los interpreta como BBCode Big Grin .

En un lugar de La Mancha de cuyo nombre me acuerdo perfectamente...
tincho   19-10-2023, 10:08
#3
(17-10-2023, 20:43)Grandamakulo escribió: También podríamos implementar el método de la secante, pero requiere la medición de distancias entre cadenas de gb.Util que es especialmente lenta, por lo que, de momento, paso.

Por favor haz un ejemplo con esto cuando puedas, no conozco ese método. Lo de la medición de distancia entre cadenas qué es exactamente?

1 Saludo.
Grandamakulo   21-10-2023, 21:02
#4
 
(19-10-2023, 10:08)tincho escribió:
(17-10-2023, 20:43)Grandamakulo escribió: También podríamos implementar el método de la secante, pero requiere la medición de distancias entre cadenas de gb.Util que es especialmente lenta, por lo que, de momento, paso.

Por favor haz un ejemplo con esto cuando puedas, no conozco ese método. Lo de la medición de distancia entre cadenas qué es exactamente?



En cuanto a la distancia, se pueden emplear varias. En el caso de Util.gb, la medida se hace por el algoritmo de Damerau-Levenshtein —creo—, que se basa en el número de pasos que hay que hacer para llegar de una a otra —traslación, cambio, etc.—. Requiere crear una matriz enoooooorme y luego resumir; por eso, por generar esa matriz y su análisis, resulta tan sumamente lento. No tiene signo, en principio, pero eso se soluciona con una simple comparación mayor o menor.
En cuanto al ejemplo, me pongo a ello, pero no me pongo plazo, jajaja Wink
El método de la secante, si las cadenas tuviesen un valor fácil de calcular en UTF-8 —a ver si me apetece y pongo por aquí una idea que tengo— o, al menos, una distancia, sería muy parecido al método de la secante para calcular  soluciones de ecuaciones. Pongo un ejemplo para solucionar una ecuación:
Código:
Function Wat_o(th As Float, hr As Float, pt As Float) As Float
    ' Cálculo de la humedad absoluta (kga/kgas) a partir de:
    ' th: temperatura húmeda (ºC)
    ' hr: Humedad relativa (tpu)
    ' pt: Presión absoluta (Pa)
    
    Dim A As Float
    Dim FA As Float
    Dim B As Float
    Dim FB As Float
    Dim C As Float
    Dim FC As Float
    
    Dim E As Float
    Dim N As Long
    
    Dim TK As Float
    Dim WE As Float
    Dim PS As Float
    Dim TE As Float
    
    Dim WH As Float
    Dim PH As Float
                    
    TK = th + 273.159
    PH = 10 ^ (2.7858 + ((7.5 * (TK - 273.159) / (TK - 35.85))))
    WH = 0.621945 * PH / (pt - PH)
    
    ' Valores iniciales de iteración
    
    A = 0.05
    B = 0
    E = Abs(A - B)
    
    'Primer cálculo
    
    'FA
    TE = (th * (1 + 4.186 * A - 2.381 * WH) - 2501 * (A - WH)) / (1 + 1.86 * A)
    TK = TE + 273.159
    PS = 10 ^ (2.7858 + ((7.5 * (TK - 273.159) / (TK - 35.85))))
    WE = hr * 0.621945 * PH / (pt - PH)
    FA = WE - A
           
    'FB
    TE = (th * (1 + 4.186 * B - 2.381 * WH) - 2501 * (B - WH)) / (1 + 1.86 * B)
    TK = TE + 273.159
    PS = 10 ^ (2.7858 + ((7.5 * (TK - 273.159) / (TK - 35.85))))
    WE = hr * 0.621945 * PH / (pt - PH)
    FB = WE - B
    
    'Iteración
    N = 0
    Do Until E < 0.000000001 Or N > 20
    
        'C
        C = B - FB / (FB - FA) * (B - A)
        'FC
        TE = (th * (1 + 4.186 * C - 2.381 * WH) - 2501 * (C - WH)) / (1 + 1.86 * C)
        TK = TE + 273.159
        PS = 10 ^ (2.7858 + ((7.5 * (TK - 273.159) / (TK - 35.85))))
        WE = hr * 0.621945 * PH / (pt - PH)
        FC = WE - C
        
        'cambio de variables
        If Abs(FA) < Abs(FB) Then
            B = C
            FB = FC
        Else
            A = C
            FA = FC
        End If
        E = Abs(A - B)
        N = N + 1
    Loop
    If N > 20 Then
        Return ""
    Else
        Return C
    End If

End

En un lugar de La Mancha de cuyo nombre me acuerdo perfectamente...
  
Usuarios navegando en este tema: 1 invitado(s)
Powered By MyBB, © 2002-2024 MyBB Group.
Made with by Curves UI.