martes, 8 de junio de 2021

Power Query: Reemplazamiento Masivo Recursivo

Al hilo de las entradas anteriores donde hablabamos de funciones recursivas o de sus alternativas (List.Generate o List.Accumulate) encontré un fantástico artículo (leer) de mi colega John MacDougall (también Microsoft MVP desde 2017) donde expone como ejemplo de recursividad un ejercicio de reemplazamiento múltiple masivo.

Me permito compartirlo y explicarlo por su utilidad...

La base de su propuesta reside en dos 'patas':
-la función Table.ReplaceValues:
Table.ReplaceValue(table as table, oldValue as any, newValue as any, replacer as function, columnsToSearch as list) as table
-y por otro lado en la aplicación de la recursividad usando el operador de ámbito (@)

Partimos de dos tablas, una con una serie de cadenas de texto sobre la que queremos aplicar una serie de sustituciones; y una segunda tabla con el listado propuesto de reemplazamientos.
Power Query: Reemplazamiento Masivo Recursivo

Cargamos al editor ambas tablas solo como conexión.
Y crearemos una consulta en blanco con el siguiente código:
let 
    ListaReempl=Table.ToRows(TblREEMP), //obtenemos una Lista de Listas (con la dupla 'esto'- 'por esto')
    Contador=Table.RowCount(TblREEMP),  //contamos el número de reemplazamientos posibles

    //definimos una Función Recursiva que recorra toda la Tabla de reemplazamientos
    ReemplazamientoValores=(TablaTemporal,n) =>
        let
            //aplicamos una función Table.ReplaceValue para reemplazar las partes de texto que localice
            //en cualquier parte de la Tabla....
            TablaFinal=Table.ReplaceValue(
                TablaTemporal,
                ListaReempl{n}{0},   // esto...
                ListaReempl{n}{1},   // por esto
                Replacer.ReplaceText,   // tipo de reemplazamiento
                {"textos"}      // nombre de la columna
                )
        in 
            //controlamos la finalización del proceso cuando lleguemos al último reemplazamiento
            if n+1=Contador then TablaFinal // terminamos
                else @ReemplazamientoValores(TablaFinal,n+1),   // mantenemos el ciclo de sustitución
            

    //aplicamos nuestra función recursiva anterior sobre la Tabla con los textos originales
    Resultado=ReemplazamientoValores(TblTEXTOS, 0)
in 
    Resultado


El resultado es el esperado... Se ha conseguido recorrer cada fila de la Tabla con los reemplazamientos y aplicarlo a la función Table.ReplaceValue, y sobre el resultado del primer reemplazamiento, aplicamos el segundo y sucesivamente.
Finalmente, por tanto, tenemos una Tabla final donde se han aplicado la totalidad de los reemplazamientos como se pretendía.

Se reunen aquí varios de los conceptos relevantes de la recursividad, en especial el que controla una salida del bucle (con un if...then...else...), fundamental para no entrar en bucles infinitos.
Otro aspecto es el uso del operador de ámbito, que permite referirse una función a si misma. Nota que dentro de la definición de 'ReemplazamientoValores' llamamos a ella misma '@ReemplazamientoValores'
Otro punto importante en la recursividad es el empleo de una variable (la 'n' en el ejemplo) que permite avanzar por el bucle de filas de la Lista de elementos a reemplazar.

E igualmente curioso es la forma en que recuperamos cada elemento de la tabla de reemplazamiento ('esto' y 'por esto'):
ListaReempl{n}{0}, // esto...
ListaReempl{n}{1}, // por esto

donde de la lista de listas que devuelve Table.ToRows, vamos una por una recupernando sus partes:
con ListaReempl{n} recuperamos una de esas sublista (que contiene una dupla esto-por esto-)
Para luego identificar uno y otro tomando cada elemento de dicha sublista:
ListaReempl{n}{0}, // esto...
ListaReempl{n}{1}, // por esto


El último paso de la query:
Resultado=ReemplazamientoValores(TblTEXTOS, 0)
donde se procesa la Tabla de los contenidos originales con todos los reemplazamientos a aplicar. Y llamamos a la primera ya que es esta en la que podemos ver la totalidad de las sustituciones aplicadas...
Puedes comprobar que si en tu última fila de la query tuvieras:
Resultado=ReemplazamientoValores(TblTEXTOS, Contador-1)
solo veríamos la última sustitución aplicada...
;-)
Sin duda un ejercicio fantástico y sobre todo muy útil.

Por aportar otro punto de vista a la idea de John mostraré una alternativa con List.Generate:
let 
    ListaReempl=Table.ToRows(TblREEMP), //obtenemos una Lista de Listas (con la dupla 'esto'- 'por esto')
    Contador=Table.RowCount(TblREEMP),  //contamos el número de reemplazamientos posibles

    ReemplazamientoValores= 
        List.Generate(
                ()=> [x=0, TablaTemporal=TblTEXTOS], //marcará la primera fila de los reemplazamientos
                each [x]<=Contador,   //condición de salida - cuando superemos el número de filas de la tabla de reemplazos
                //siguiente valor, esto es, siguiente dupla/Registro
                each [x =[x]+1,       
                            TablaTemporal=Table.ReplaceValue(
                                [TablaTemporal],
                                ListaReempl{[x]}{0},   // esto...
                                ListaReempl{[x]}{1},   // por esto
                                Replacer.ReplaceText,   // tipo de reemplazamiento
                                {"textos"}      // nombre de la columna
                                )], 
                each [TablaTemporal]),
      
    //De la Lista de Tablas generada nos quedamos con la último, que recoge todas las sustituciones
    Resultado=ReemplazamientoValores{Contador}
in 
    Resultado

Power Query: Reemplazamiento Masivo Recursivo

Lo interesante de este método es que hemos generado una Lista de Tablas, donde cada nuevo reemplazamiento se aplica a la Tabla anterior...
En el primer argumento de List.Generate:
()=> [x=0, TablaTemporal=TblTEXTOS],
definimos nuestro punto de partida.
Una dupla compuesto por un Registro de dos valores:
- un contador x que nos permitirá controlar el paso por cada fila de la Tabla de reemplazamientos
- y la Tabla con los textos a reemplazar... la que tenemos en el momento cero, previa a cualquier reemplazo.
En el segundo argumento:
each [x]<=Contador,
limita el recorrido de nuestro contador x, y la salida por tanto de nuestro 'bucle', asignando nuevos valores a x mientras sean menores al número de filas de la Tabla de reemplazamientos.
En el tercer argumento:
each [x =[x]+1,
TablaTemporal=Table.ReplaceValue(
[TablaTemporal],
ListaReempl{[x]}{0}, // esto...
ListaReempl{[x]}{1}, // por esto
Replacer.ReplaceText, // tipo de reemplazamiento
{"textos"} // nombre de la columna
)],

donde por un lado incrementamos nuestro contador x; y por otro aplicamos la función Table.ReplaceValue a la Tabla resultante de cada reemplazamiento previo.
Power Query: Reemplazamiento Masivo Recursivo
Y con el cuarto argumento:
each [TablaTemporal]
indicamos qué queremos que nos devuelva la función List.Generate, en el ejemplo, la Tabla con el reemplazamiento aplicado.

La query finaliza recuperando el último elemento de la lista generada, es decir, la Tabla con todos los reemplazamientos aplicados...

No hay comentarios:

Publicar un comentario

Nota: solo los miembros de este blog pueden publicar comentarios.