jueves, 25 de febrero de 2021

Caracteres UNICODE PQ vs Fórmula

Recientemente tuve que realizar una serie de búsquedas exactas y el problema me exigió emplear en la comparativa la función UNICODE, que devuelve el valor numérico Unicode asociado al caracter indicado...

Aunque una manera muy sencilla de realizar esta búsqueda exacta, discriminando mayúsculas de minúsculas, podría haber sido alguno de los método descritos aquí.
O incluso aplicando celda a celda un fórmula matricial del estilo:
=UNIRCADENAS("";0;UNICODE(EXTRAE(TblBUSCAR[@codigo];SECUENCIA(LARGO(TblBUSCAR[@codigo]));1)))
Caracteres UNICODE PQ vs Fórmula

Para luego sobre el rango auxiliar realizar la búsqueda según esa combinación numérica concatenada de caracteres Unicode...

Sin embargo decidí probar a aplicar una fórmula desbordada/dynamic array y verificar su comportamiento...
Caracteres UNICODE PQ vs Fórmula

Así pues monté la siguiente fórmula, en la celda F12, compuesta de otras funciones (LET, UNIRCADENAS, XMLFILTRO, SECUENCIA, etc):
=SI.ERROR(XMLFILTRO("<inicio><fila><dato>"&SUSTITUIR(
LET(rng;LET(
codigo;TblBUSCAR[codigo];
arr;LET(n;MAX(LARGO(codigo));
SI.ERROR(UNICODE(EXTRAE(codigo;SECUENCIA(1;n);1));""));
arr);
Cols1;SECUENCIA(1;COLUMNAS(rng));
Cols2;SECUENCIA(1;COLUMNAS({1}));
NCols1;COLUMNAS(rng);
NCols2;COLUMNAS({1});
TotalFilas;SECUENCIA(FILAS(rng));
TotalCols;SECUENCIA(1;NCols1+NCols2);

dispo;MULTIPLO.SUPERIOR.MAT(SECUENCIA(6;NCols1+1);NCols1+1)/(NCols1+1);
matriz;SI(TotalCols<=NCols1;INDICE(rng;TotalFilas;TotalCols);INDICE({"|";"|";"|";"|";"|";"|"};TotalFilas;TotalCols-NCols1));
fila;SECUENCIA(6);
arr;INDICE(dispo;fila;SECUENCIA(1;TotalCols));
IZQUIERDA(UNIRCADENAS("";0;SI(arr=fila;matriz;""));LARGO(UNIRCADENAS("";0;SI(arr=fila;matriz;"")))-1));

"|";"</dato><dato>")&"</dato></fila></inicio>";"//fila/dato");"")
Fórmula preparada para trabajar sobre los seís código de la TblBUSCAR!!.
La limitación que surgió al aplicar esta 'formulita' fue que XMLFILTRO convertía a valor numérico cada uno de las secuencias Unicode de cada dato!! :O
Lo que genera un gran perjuicio cuando el valor devuelto en Unicode supera la precisión de Excel de los 15 dígitos!!.
Fíjate en la imagen anterior en las celdas en amarillo... los últimos dígitos se han perdido en las celdas:
8510551455098202 -- 8510551455098200
2507351585022466 -- 2507351585022460

Por lo que probé con otra de las herramientas de Excel: Power Query
Como siempre cargamos la tabla 'TblBUSCAR' al editor de Power Query, y desde el editor avanzado la query quedó:
let
    Origen = Excel.CurrentWorkbook(){[Name="TblBUSCAR"]}[Content],
    //convertimos cada caracter a su valor UNICODE
    AddCol_Caracteres  = Table.AddColumn(Origen, "unicode", 
    						each List.Accumulate(
                                   Text.ToList([codigo])
                                   , ""
                                   , (state, current)=>
                                      	state
                                        &
                                        Number.ToText(Character.ToNumber(current), "0")))
in
    AddCol_Caracteres

Caracteres UNICODE PQ vs Fórmula

En este caso, empleando funciones M ya vistas en el blog
Text.ToList leer aquí
y List.Accumulate leer aquí
Conseguimos descomponer en caracteres individuales cada 'código' (con 'Text.ToList'), para que luego 'List.Accumulate' procese caracter a caracter recuperando su valor Unicode (Character.ToNumber), darle finalmente formato de texto a cada valor... concatenándolas paso a paso.

El resultado final es una cadena de texto compuesto por todos los valores Unicode de cada caracter, como necesitábamos...

Un ejercicio que demuestra que debemos saber discernir entre los caminos que nos ofrece Excel, cuál es el óptimo y eficaz ;-)

martes, 23 de febrero de 2021

Reemplazar TODOS los caracteres especiales en un paso

Días atrás, con un compañero de un grupo de Excel (Frèdèric LE GUEN - Excel MVP francés ), estuvimos viendo diferentes formas de forzar un reemplazamiento maivo de todos los posibles caracteres especiales que existían en los distintos idiomas... y con una única fórmula, sin programación!!.

LLegamos a soluciones algo diferentes, pero igualmente válidas... Os muestro mi solución.

Emplearemos funciones clásicas ya conocidas de todos: INDICE, BUSCARV, SI.ERROR, TRUNCAR, RESIDUO, CONTARA, COLUMNAS, EXTRAE, LARGO, CARACTER, UNICODE
y otras más novedosas como: UNIRCADENAS, SECUENCIA o LET

Por suerte se emplearán métodos ya conocidos y expuestos en este blog ;-)
Reemplazar TODOS los caracteres especiales en un paso

Y la fórmula 'fabricada' sería:
=UNIRCADENAS("";0;
LET(reference;A1;
rpl_a; {"à"\"á"\"â"\"ã"\"ä"\"å"};
rpl_e; {"é"\"è"\"ê"\"ë"};
rpl_i; {"ì"\"í"\"î"\"ï"};
rpl_o; {"ò"\"ó"\"ô"\"õ"\"ö"\"ø"};
rpl_u; {"ù"\"ú"\"û"\"ü"\"ů"};
rpl_y; {"Ý"\"Ÿ"};
rpl_c; {"ç"\"ć"};
rpl_l; {"ł"};
rpl_n; {"ñ"\"ń"};
rpl_r; {"ř"};
rpl_s; {"š"\"ś"};
rpl_z; {"ž"\"ż"\"ź"};
rpl_aa; {"À"\"Á"\"Â"\"Ã"\"Ä"\"Å"\"Ą"};
rpl_ee; {"È"\"É"\"Ê"\"Ë"\"Ę"};
rpl_ii; {"Ì"\"Í"\"Î"};
rpl_oo; {"Ò"\"Ó"\"Ô"\"Õ"\"Ö"\"Ø"};
rpl_uu; {"Ù"\"Ú"\"Û"\"Ü"\"Ů"};
rpl_yy; {"Ý"\"Ÿ"};
rpl_cc; {"Ç"\"Ć"};
rpl_ll; {"Ł"};
rpl_nn; {"Ñ"\"Ń"};
rpl_rr; {"Ř"};
rpl_ss; {"Š"\"Ś"};
rpl_zz; {"Ž"\"Ż"\"Ź"};
arrRpl;SI.ERROR(ELEGIR(SECUENCIA(24);rpl_a; rpl_e; rpl_i; rpl_o; rpl_u; rpl_y; rpl_c; rpl_l; rpl_n; rpl_r; rpl_s; rpl_z; rpl_aa; rpl_ee; rpl_ii; rpl_oo; rpl_uu; rpl_yy; rpl_cc; rpl_ll; rpl_nn; rpl_rr; rpl_ss; rpl_zz);"");

arrRpla;INDICE(arrRpl;TRUNCAR(SECUENCIA(CONTARA(arrRpl);1;0;1/COLUMNAS(arrRpl)))+1;RESIDUO(SECUENCIA(CONTARA(arrRpl);1;0;1);COLUMNAS(arrRpl))+1);
arrBy;ELEGIR(SECUENCIA(24);"a";"e";"i";"o";"u";"y";"c";"l";"n";"r";"s";"z";"A";"E";"I";"O";"U";"Y";"C";"L";"N";"R";"S";"Z");
arrPor;INDICE(arrBy;1+(MULTIPLO.SUPERIOR.MAT(SECUENCIA(CONTARA(arrRpl));COLUMNAS(arrRpl))-COLUMNAS(arrRpl))/COLUMNAS(arrRpl));
array;ELEGIR({1\2};arrRpla;arrPor);
txt;LET(
n;SECUENCIA(LARGO(reference));
chr;EXTRAE(reference;n;1);
SI.ERROR(CARACTER(BUSCARV(UNICODE(chr);UNICODE(array);2;0));chr));
txt))
Fórmula monstruosa seguramente mejorable... ¿te atrevés? ;-)

En líneas generales, antes de entrar en detalle, lo que hace la fórmula (ayudado por LET) es identificar grupos o matrices de caracteres especiales a ser reemplazados (las 24 matrices 'rpl_').
A continuación unificamos dichas 24 matrices en una sola:
arrRpl;SI.ERROR(ELEGIR(SECUENCIA(24);rpl_a; rpl_e; rpl_i; rpl_o; rpl_u; rpl_y; rpl_c; rpl_l; rpl_n; rpl_r; rpl_s; rpl_z; rpl_aa; rpl_ee; rpl_ii; rpl_oo; rpl_uu; rpl_yy; rpl_cc; rpl_ll; rpl_nn; rpl_rr; rpl_ss; rpl_zz);"");
Esto nos generaría una matriz bidimensional de 24 filas x 7 columnas (dependerá del número de matrices indicadas y del tamaño de la mayor de ellas).
Reemplazar TODOS los caracteres especiales en un paso


Igualmente generamos una matriz de 24 filas x 1 columna de manera manual... debiendo corresponder el orden con el de las matrices previas... tal y como se ve en la imagen anterior.
arrBy;ELEGIR(SECUENCIA(24);"a";"e";"i";"o";"u";"y";"c";"l";"n";"r";"s";"z";"A";"E";"I";"O";"U";"Y";"C";"L";"N";"R";"S";"Z");
En los pasos siguientes pasamos estas matrices anteriores (arrRpl y arrBy) de matrices bidimensionales a vectores de una sola columna (lee aquí el proceso)
arrRpla;INDICE(arrRpl;TRUNCAR(SECUENCIA(CONTARA(arrRpl);1;0;1/COLUMNAS(arrRpl)))+1;RESIDUO(SECUENCIA(CONTARA(arrRpl);1;0;1);COLUMNAS(arrRpl))+1);
y
arrPor;INDICE(arrBy;1+(MULTIPLO.SUPERIOR.MAT(SECUENCIA(CONTARA(arrRpl));COLUMNAS(arrRpl))-COLUMNAS(arrRpl))/COLUMNAS(arrRpl));
Consiguiendo algo como esto (dentro de la fórmula, de forma virtual!!), dos vectores de 168 filas x 1 columnas:
Reemplazar TODOS los caracteres especiales en un paso


En el siguiente paso unificamos en una sola matriz los dos vectores (arrRpla y arrPor):
array;ELEGIR({1\2};arrRpla;arrPor);

La parte final del proceso o tratamiento de esa matriz 'array' se consigue con un nuevo LET anidado, que se encarga de forzar ese reemplazamiento buscado.
txt;LET(
n;SECUENCIA(LARGO(reference));
chr;EXTRAE(reference;n;1);
SI.ERROR(CARACTER(BUSCARV(UNICODE(chr);UNICODE(array);2;0));chr));
txt)

Donde la cadena de texto de referencia se descompone caracter a caracter, realizando la búsqueda individualmente, de cada uno de ellos, convertidos previamente a codificación ANSI para ser precisos en la sustitución.
La fórmula, para aquellos casos que no encuentra coincidencia devuelve el caracter inicial...
Dicho de otro modo, solo se reemplazarán los caracteres especiales listados...

El resultado es el esperado. Cualquiera de los caracteres desplegados en la fórmula será reemplazado por su correspondiente caracter 'limpio' de signos especiales de puntuación: acentos, tildes, virgulillas, dieresis, etc.

jueves, 18 de febrero de 2021

Bucles con la función LET

Al hilo de la entrada anterior del blog, sobre cómo convertir un número a letra (ver aquí); me han llegado varios comentarios o peticiones para aclarar el método aplicado...

Ciertamente el método es muy potente y merece la pena verlo y comprenderlo con un ejemplo más sencillo de interpretar ;-)

Podríamos decir que el método o algoritmo aplicado, con una anidación de funciones LET, nos permite, de alguna manera, realizar un bucle por distintos elementos, aplicádoles un proceso o cálculo a cada uno de ellos, para finalmente realizar, si procede, un cálculo final con los resultados obtenidos para cada elemento.

Veamos la siguiente imagen:
Bucles con la función LET

Tenemos tres elementos sobre los que trabajar (celdas A1, A2 y A3), para aplicar a cada uno de ellos una operación (en mi ejemplo elevarlos al cuadrado), y finalmente sumar los resultados parciales de todos ellos.

Sería lo que nos devolvería la función estándar de Excel SUMA.CUADRADOS.

En C1 la fórmula construida es:
=LET(
a;A1;
b;A2;
c;A3;
matriz;ELEGIR({1;2;3};a;b;c);

cálculo;LET(importe;matriz;
importe^2);

calcFinal;INDICE(cálculo;1)+INDICE(cálculo;2)+INDICE(cálculo;3);
calcFinal)

Analicemos qué está ocurriendo...
En los primeros pasos asignamos variables a las distintas celdas sobre las que trabajar...
=LET(
a;A1;
b;A2;
c;A3;
Nada impresionante ;-)

El siguiente paso es fundamental.. ya que construimos una matriz de esas variables!!
Esto se consigue con la función ELEGIR
matriz;ELEGIR({1;2;3};a;b;c);
Hemos asignado a una variable una matriz de nuestros tres elementos previamente definido...
Si en una celda cualquiera escribiáramos: =ELEGIR({1;2;3};A1;A2;A3) obtendríamos la matriz ={2;5;6}

El caso es que con ese 'movimiento' hemos asignado a la variable 'matriz' cada uno de esos tres elementos...

LLegamos a la función LET anidada:
cálculo;LET(importe;matriz;
importe^2);
que aplica elemento a elemento el cálculo indicado (elevar al cuadrado), y vincularlo a una nueva matriz llamada 'cálculo'.
Es decir, la variable 'cálculo' se ha cargado con tres elementos: {4;25;36} (en nuestro ejemplo).
Y como con cualquier matriz, podemos trabajar empleando, por ejemplo, las funciones INDICE, COINCIDIR, etc...

Que será nuestro último paso. Una vez dejado atrás la operación aplicada a cada elemento, procedemos a operar sobre resultados parciales:
calcFinal;INDICE(cálculo;1)+INDICE(cálculo;2)+INDICE(cálculo;3);
calcFinal
En este ejemplo, sumo o acumulo cada resultado parcial, recuperando del vector/matriz anterior 'cálculo' cada elemento (={4;25;36}).

Como apunte final mencionar la importancia de asociar un nombre de variable a la función anidada LET, ya que así podremos posteriormente recuperar sus elementos o componentes.

Una variante de este ejemplo, abierto a un rango cualquiera o incluso de una tabla!! (en lugar de celdas determinadas) podría ser:
=LET(
rng;Tabla1[Datos];
matriz;INDICE(rng;SECUENCIA(CONTARA(rng)));

cálculo;LET(importe;matriz;
importe^2);

calcFinal;SUMA(INDICE(cálculo;SECUENCIA(CONTARA(rng))));
calcFinal)

Fantástico!! :OO

martes, 16 de febrero de 2021

Convertir un número a letras con una sola fórmula

Una cuestión planteada en multitud de ocasiones es la forma de convertir un número a letras; lo que se puede (y siempre se ha podido) hacer bien empleando algo de programación en VBA (ver ejemplo aquí) o bien con fórmulas dispuestas a lo largo de diferentes celdas con 'retorcidas' búsquedas y anidaciones...

Hoy veremos como con una única fórmula, en una sola celda, podemos realizar la conversión deseada: transformar un número a letras.
La idea surgió al ver a algunos compañeros del programa Microsoft MVP (Sergei Baklan, Frédéric LE GUEN y algún otro que aportó su granito) elaborar una fórmula para dicha conversión a letras en inglés y francés (puedes ver la publicación de Frédéric LE GUEN aquí).

El problema es que a la hora de leer los número en los diferentes idiomas el algoritmo de lectura es muy diferente, i.e., el patrón en inglés es más homogeneo que en español, de forma similar si comparamos francés y español... por suerte o desgracia los números leídos en castellano tienen bastante más casuística que otras lenguas :'(

Esto me hizo repensar la fórmula propuesta por estos gurús de Excel y generar la fórmula para los números en español.
Y basándome en el algoritmo descrito en este artículo, y haciendo uso de la función LET (de uso para suscriptores de Microsoft 365 y parece que a fecha de hoy también en Excel 2019 y 2019. Leer más aquí) construí la fórmula, capaz de transformar a letras números de 0 hasta 999.999.999

Anticipo que la fórmula, no puede ser de otra forma, es larga y se anida una función LET dentro de otra... empleando además matrices de constantes, y por supuesto el concepto de matrices.

Disponemos en la celda A1 del número que deseamos transformar a letras.
Y en B1 añadimos la fórmula buscada (que pasaré a explicar posteriormente):
=LET(importe;A1;
QMillones;SI(importe>999999;ENTERO(importe/1000000));
QMiles;SI(importe>999;ENTERO(importe/1000)-QMillones*1000);
QCentenas;importe-ENTERO(importe/1000)*1000;
QEntera;ENTERO(importe);
QDecimal;REDONDEAR((importe-ENTERO(importe))*100;0);

centfin;SI(QDecimal=1;" céntimo";" céntimos");
eurfin;SI(QEntera=1;" euro";" euros");

matriz;ELEGIR({1;2;3;4};QMillones;QMiles;QCentenas;QDecimal);
calculo;LET(
centena2;ENTERO(matriz/100);
decena2;ENTERO((matriz-centena2*100)/10);
unidad2;ENTERO((matriz-centena2*100-decena2*10));

centenas;{""\"cien"\"doscientos"\"trescientos"\"cuatrocientos"\"quinientos"\"seiscientos"\"setecientos"\"ochocientos"\"novecientos"};
decenas;{""\" diez"\" veinte"\" treinta"\" cuarenta"\" cincuenta"\" sesenta"\" setenta"\" ochenta"\" noventa"};
unidades;{""\" un"\" dos"\" tres"\" cuatro"\" cinco"\" seis"\" siete"\" ocho"\" nueve"};
dieces;{"diez"\"once"\"doce"\"trece"\"catorce"\"quince"\"dieciseis"\"diecisiete"\"dieciocho"\"diecinueve"};
veintes;{"veinte"\"veintiuno"\"veintidos"\"veintitres"\"veinticuatro"\"veinticinco"\"veintiseis"\"veintisiete"\"veintiocho"\"veintinueve"};
tatantos;{""\" y un"\" y dos"\" y tres"\" y cuatro"\" y cinco"\" y seis"\" y siete"\" y ocho"\" y nueve"};

num_letra1;INDICE(centenas;1;centena2+1);
num_letra2;SI(centena2=1;num_letra1&"to";num_letra1);
num_letra3;num_letra2&INDICE(decenas;1;decena2+1);
num_letra4;SI(decena2=0;INDICE(unidades;1;unidad2+1);"");
num_letra5;SI(decena2=1;EXTRAE(num_letra3;1;LARGO(num_letra3)-4)&INDICE(dieces;1;unidad2+1);"");
num_letra6;SI(decena2=2;EXTRAE(num_letra3;1;LARGO(num_letra3)-6)&INDICE(veintes;1;unidad2+1);"");
num_letra7;SI(decena2>2;INDICE(tatantos;1;unidad2+1);"");
num_letra8;SI(decena2=1;num_letra5;SI(decena2=2;num_letra6;num_letra3&num_letra4&num_letra7));
SI(matriz=100;"cien";num_letra8));

txtMillones;SI(importe>999999;SI(QMillones=1;"un millón ";INDICE(calculo;1)&" millones ");"");
txtMil;SI(importe>999;SI(QMiles=1;"mil ";SI(QMiles=0;"";INDICE(calculo;2)&" mil "));"");
txtCent;SI(QEntera=0;"";SI(INDICE(calculo;3)="";"";INDICE(calculo;3))&eurfin);
txtDecimal;SI(INDICE(calculo;4)="";"";SI(QEntera=0;"";" con ")&INDICE(calculo;4)&centfin);

SI(importe=0;"cero euros";ESPACIOS(txtMillones&txtMil&txtCent&txtDecimal)))


¿Larga, verdad?... Normal por la cantidad de casuística a evaluar...
Convertir un número a letras con una sola fórmula
Para descomponer y explicar la fórmula empezaríamos por el LET anidado:
LET(
centena2;ENTERO(matriz/100);
decena2;ENTERO((matriz-centena2*100)/10);
unidad2;ENTERO((matriz-centena2*100-decena2*10));

centenas;{""\"cien"\"doscientos"\"trescientos"\"cuatrocientos"\"quinientos"\"seiscientos"\"setecientos"\"ochocientos"\"novecientos"};
decenas;{""\" diez"\" veinte"\" treinta"\" cuarenta"\" cincuenta"\" sesenta"\" setenta"\" ochenta"\" noventa"};
unidades;{""\" un"\" dos"\" tres"\" cuatro"\" cinco"\" seis"\" siete"\" ocho"\" nueve"};
dieces;{"diez"\"once"\"doce"\"trece"\"catorce"\"quince"\"dieciseis"\"diecisiete"\"dieciocho"\"diecinueve"};
veintes;{"veinte"\"veintiuno"\"veintidos"\"veintitres"\"veinticuatro"\"veinticinco"\"veintiseis"\"veintisiete"\"veintiocho"\"veintinueve"};
tatantos;{""\" y un"\" y dos"\" y tres"\" y cuatro"\" y cinco"\" y seis"\" y siete"\" y ocho"\" y nueve"};

num_letra1;INDICE(centenas;1;centena2+1);
num_letra2;SI(centena2=1;num_letra1&"to";num_letra1);
num_letra3;num_letra2&INDICE(decenas;1;decena2+1);
num_letra4;SI(decena2=0;INDICE(unidades;1;unidad2+1);"");
num_letra5;SI(decena2=1;EXTRAE(num_letra3;1;LARGO(num_letra3)-4)&INDICE(dieces;1;unidad2+1);"");
num_letra6;SI(decena2=2;EXTRAE(num_letra3;1;LARGO(num_letra3)-6)&INDICE(veintes;1;unidad2+1);"");
num_letra7;SI(decena2>2;INDICE(tatantos;1;unidad2+1);"");
num_letra8;SI(decena2=1;num_letra5;SI(decena2=2;num_letra6;num_letra3&num_letra4&num_letra7));
SI(matriz=100;"cien";num_letra8))


Este primer LET realiza la conversión de número a letra para valores entre 0 y 999, que nos servirá para transformar decimales, centenas, miles o millones.
Es decir, la idea general es descompone el importe inicial en los tramos normales, y pasarlo por el proceso de esta función LET, donde obtenemos el valor de las posiciones de U, D y C (unidades, decenas y centenas)... extrapolables al resto de tramos de cualquier número (miles, millones, etc...)
Muy importantes son las matrices de constantes descritas; en mi caso he preferido desarrollarlas horizontálmente, i.e., por columnas, pero igualmente podrían haberse implantado verticalmente, por filas. Basta cambiar la barra invertida \ por punto y coma ;
centenas;{""\"cien"\"doscientos"\"trescientos"\"cuatrocientos"\"quinientos"\"seiscientos"\"setecientos"\"ochocientos"\"novecientos"};
decenas;{""\" diez"\" veinte"\" treinta"\" cuarenta"\" cincuenta"\" sesenta"\" setenta"\" ochenta"\" noventa"};
unidades;{""\" un"\" dos"\" tres"\" cuatro"\" cinco"\" seis"\" siete"\" ocho"\" nueve"};
dieces;{"diez"\"once"\"doce"\"trece"\"catorce"\"quince"\"dieciseis"\"diecisiete"\"dieciocho"\"diecinueve"};
veintes;{"veinte"\"veintiuno"\"veintidos"\"veintitres"\"veinticuatro"\"veinticinco"\"veintiseis"\"veintisiete"\"veintiocho"\"veintinueve"};
tatantos;{""\" y un"\" y dos"\" y tres"\" y cuatro"\" y cinco"\" y seis"\" y siete"\" y ocho"\" y nueve"};

Estas matrices recogen los casos generales y especiales de los número leídos en castellano.

Sobre estas matrices, o a partir de ellas, comenzamos un proceso (basado en la programación en VBA del artículo comentado más arriba), que nos permite identificar qué textos corresponden a cada número:
num_letra1;INDICE(centenas;1;centena2+1);
num_letra2;SI(centena2=1;num_letra1&"to";num_letra1);
num_letra3;num_letra2&INDICE(decenas;1;decena2+1);
num_letra4;SI(decena2=0;INDICE(unidades;1;unidad2+1);"");
num_letra5;SI(decena2=1;EXTRAE(num_letra3;1;LARGO(num_letra3)-4)&INDICE(dieces;1;unidad2+1);"");
num_letra6;SI(decena2=2;EXTRAE(num_letra3;1;LARGO(num_letra3)-6)&INDICE(veintes;1;unidad2+1);"");
num_letra7;SI(decena2>2;INDICE(tatantos;1;unidad2+1);"");
num_letra8;SI(decena2=1;num_letra5;SI(decena2=2;num_letra6;num_letra3&num_letra4&num_letra7));
SI(matriz=100;"cien";num_letra8))

Si formulamos estos cálculos en distintas celdas veríamos el proceso (seguro mejorable!!):
Convertir un número a letras con una sola fórmula
Este proceso de cálculo va concatenando, según cumplimos las condiciones o casuísticas, los distintos componentes o partes del número en letras... ¡¡de cualquier número entre 0 y 999!!!; que sirve para tramos de centenas, miles, millones y por supuesto parte decimal ;-)

Con este complejo proceso construido, solo nos queda ir pasándole las partes del importe de la celda A1. Por ejemplo, si tenemos el número
123.056.709,03
Nuestra función LET principal comienza separando las partes:
=LET(importe;A1;
QMillones;SI(importe>999999;ENTERO(importe/1000000));
QMiles;SI(importe>999;ENTERO(importe/1000)-QMillones*1000);
QCentenas;importe-ENTERO(importe/1000)*1000;
QEntera;ENTERO(importe);
QDecimal;REDONDEAR((importe-ENTERO(importe))*100;0);

centfin;SI(QDecimal=1;" céntimo";" céntimos");
eurfin;SI(QEntera=1;" euro";" euros");
Podemos ver esos cálculos, si fuera necesario para la explicación, sobre las celdas de nuestra hoja:
Convertir un número a letras con una sola fórmula

Aprovechando la ocasión para definir los sufijos o coletillas para la parte entera y decimal del número: euro/s y céntimo/s

Como paso previo a procesar y convertir cada tramo del importe de la celda A1 generamos una matriz con los importes obtenidos de cada parte (millones, miles, centenas o decimales):
matriz;ELEGIR({1;2;3;4};QMillones;QMiles;QCentenas;QDecimal);
La función ELEGIR es la encargada de crear esa matriz de elementos variables...

Nuestra fórmula pasa cada elemento de esta 'matriz' por el proceso de conversión a letras, de acuerdo a lo anteriormente definido (con el LET anidado)... para finalmente, una vez 'convertido a letras' cada parte por separado... unificarlo y concatenarlo con los prefijos, sufijos, separadores y concatenadores correspondientes.
Lo que vemos en la última parte de nuestra fórmula:
txtMillones;SI(importe>999999;SI(QMillones=1;"un millón ";INDICE(calculo;1)&" millones ");"");
txtMil;SI(importe>999;SI(QMiles=1;"mil ";SI(QMiles=0;"";INDICE(calculo;2)&" mil "));"");
txtCent;SI(QEntera=0;"";SI(INDICE(calculo;3)="";"";INDICE(calculo;3))&eurfin);
txtDecimal;SI(INDICE(calculo;4)="";"";SI(QEntera=0;"";" con ")&INDICE(calculo;4)&centfin);

SI(importe=0;"cero euros";ESPACIOS(txtMillones&txtMil&txtCent&txtDecimal))

Donde con la función INDICE recuperamos cada elemento de nuestra 'matriz' tratada y convertida a letras.
Los condicionales aplicados en cada caso terminan discriminando las especificaciones concretas para los casos 'especiales' del castellano.
El último condicional se encarga de salvar el último caso 'raro' y de concatenar y unir las partes finales obtenidas... llegando al resultado esperado!!

Lo más importante de este ejercicio es que se nos permite, como usuarios 'normales' (sin conocimientos de programación en VBA), con funciones y fórmulas habituales (SI, INDICE, ELEGIR, ENTERO, ...), replicar algoritmos y procesos de cálculos complejos...
Basta tener claridad mental de cuál es nuestro objetivo, y conocer todas las opciones, caminos y variables con las que nos toparemos...
Espero disfrutes este artículo tanto como yo lo he hecho.

jueves, 11 de febrero de 2021

Power Query: Renombrar Todas las Columnas de una vez

Una tarea árdua cuando trabajamos con Power Query sobre distintos orígenes, cada uno con encabezados o nombres de columnas diferentes, es renombrar dichas columnas...
Hoy aprenderemos en un único movimiento a renombrar todas las columnas de nuestra consulta.

Partiremos de un ejemplo descrito en este artículo del blog; y que también puedes ver en YouTube.
Partiremos de tres tablas de datos ('Tbl_2019','Tbl_2020' y 'Tbl_2021'), cada una con tres columnas... pero todas ellas con nombres distintos.
Y además la celda F2 con un nombre definido asignado ('ndAño') que nos permitirá obtener los datos de uno de nuestros tres orígenes...
Power Query: Renombrar Todas las Columnas de una vez.


Los primeros pasos se describieron en el artículo anterior, basicamente consiste en cargar solo como conexión las tres tablas ('Tbl_2019','Tbl_2020' y 'Tbl_2021')...

Con las tres tablas ya cargadas podemos crear una Consulta en blanco que renombraremos como 'TblFINAL' donde incluiremos el siguiente código M:
let
    pAño=Excel.CurrentWorkbook(){[Name="ndAño"]}[Content]{0}[Column1],
    Origen = if pAño=2019 then Tbl_2019 else 
                if pAño=2020 then Tbl_2020 else 
                Tbl_2021,

    //Renombramos columnas_______________________________//
    //Recuperamos una Lista los Nombres de las columnas actuales
    NombresColsOriginales = Table.ColumnNames(Origen),
    //Definimos una Lista con los Futuros Nombres de las tres columnas
    NombresColsNuevos={"Date","Product name","Units"},
    
    //Componemos una Lista 'doble' con las dos anteriores usando List.Zip
    RenombramosListado = List.Zip({NombresColsOriginales,NombresColsNuevos}),
    //Finalmente con Table.RenameColumns asignamos los nombres nuevos...
    RenombradoColumnas = Table.RenameColumns(Origen, RenombramosListado)

in
    RenombradoColumnas

Power Query: Renombrar Todas las Columnas de una vez

El resultado es que, independientemente del origen de datos (cualquiera de las tres tablas), los nombres de las columnas serán siempre los descritos: 'Date', 'Product name' y 'Units'
Power Query: Renombrar Todas las Columnas de una vez


Pero, ¿cuál es la técnica empleada?.
El uso de dos funciones importantes:
Table.RenameColumns(table as table, renames as list, optional missingField as nullable number) as table
Función importante que es capaz de renombrar las columnas de nuestra 'tabla'. Este reemplazamiento se da a partir de una lista de dos valores: {Nombre anterior, Nuevo nombre}
Si el nombre de la columna no existe, es posible controlarlo con el tercer argumento de la función (opcional): missingField con dos posibilidades
MissingField.UseNull
MissingField.Ignore
Por ejemplo, sobre nuestra Tbl_2019, podríamos aplicar individualmente el siguiente código para renombrar sus columnas:
let
    Origen = Excel.CurrentWorkbook(){[Name="Tbl_2019"]}[Content],
    RenombraColumnas=Table.RenameColumns(Origen,
            {{"Fecha","Date"},
            {"Cantidad","Units"},
            {"Nombre","Product name"},
            {"Campo NO existe","---"}},
            MissingField.Ignore)
in
    RenombraColumnas

Power Query: Renombrar Todas las Columnas de una vez


Clave para nuestro objetivo es la lista de dos valores {Nombre anterior, Nuevo nombre}:
{{"Fecha","Date"},{"Cantidad","Units"},{"Nombre","Product name"}}

Si somos capaces de 'montar' una lista como la anterior automáticamente, nuestro trabajo estará hecho... y aquí aparece la siguiente función M:
List.Zip(lists as list) as list
la cual conforma una lista de listas, combinando los elementos de dichas listas situados en las mismas posiciones.
Por ejemplo:
let
    Origen = List.Zip({{"a","b","c"},{1,22,333}})
in
    Origen

Lo que crearía una lista con la siguiente forma:
{{"a",1},{"b",22},{"c",333}}
esto es, una lista de doble valor, como la que requiere Table.RenameColumns.
Power Query: Renombrar Todas las Columnas de una vez


Así pues, con List.Zip construiremos nuestro listado de reemplazamiento... pero antes necesitamos saber cuáles son los nombres de las columnas actuales, lo que haremos con:
Table.ColumnNames(table as table) as list
que nos devuelve precisamente esto, una lista con los nombres de las columnas existentes.
Solo nos queda definir los nuevos nombres de las columnas, lo que conseguimos con una lista personalizada:
NombresColsNuevos={"Date","Product name","Units"}

Con lo que la parte importante, que es capaz de renombrar nuestras columnas quedaría:
//Renombramos columnas_______________________________//
//Recuperamos una Lista los Nombres de las columnas actuales
NombresColsOriginales = Table.ColumnNames(Origen),
//Definimos una Lista con los Futuros Nombres de las tres columnas
NombresColsNuevos={"Date","Product name","Units"},

//Componemos una Lista 'doble' con las dos anteriores usando List.Zip
RenombramosListado = List.Zip({NombresColsOriginales,NombresColsNuevos}),
//Finalmente con Table.RenameColumns asignamos los nombres nuevos...
RenombradoColumnas = Table.RenameColumns(Origen, RenombramosListado)


Y nuestra meta alcanzada...

martes, 9 de febrero de 2021

Separar Nombres y Apellidos solo con fórmulas

Un tema que siempre me ha interesado es cómo separar eficazmente los nombres y apellidos compuestos en las tres partes correspondientes: Nombre + Apellido 1 + Apellido 2
Creo que de manera bastante exitosa lo conseguí con macros aquí y con Power Query aquí.

En el día de hoy conseguiremos lo mismo con la tercera pata de Excel, vamos a a separar nombre y apellidos compuesto solo con fórmulas!!.
Separar Nombres y Apellidos solo con fórmulas

El algortimo de cálculo empleado ha sido el mismo que el usado en programación o con Power query, i.e., identificar y separar las partes con algún separador (la barra vertical | en mi caso), para después aplicar y separar por dichos separadores...
Puedes releer los links a los artículos comentados para aclarar el concepto.

Para ello emplearemos la función web XMLFILTRO, vista en los artículos del blog previos a este. Y en especial haremos uso de la técnica expuesta en este post, donde explico cómo realizar sustituciones múltiples con fórmulas.
Nuevamente el uso desbordado de nuestras funciones matriciales, en versiones modernas de Excel (especialmente Microsoft 365) nos permitirá completar con éxito esta tarea.

Habitualmente en mis posts expongo en primer lugar la fórmula completa para luego ir descomponiendola... hoy lo haré a la inversa, mostraré los cálculos celda a celda, paso a paso... para finalmente recomponer todas ellas en un único cálculo.

Empecemos...
En primer lugar tenemos nuestro nombre compuesto (o no) completo en la celda A2.
Y nuestro primer paso, nuestra primera fórmula en D2 sería:
=SUSTITUIR(NOMPROPIO(A2);" ";"| ")
Donde homogeneizamos el texto del nombre con la función NOMPROPIO, esto es, cada primera letra de cada palabra en mayúscula... para luego cambiar los espacios intermedios por una barra vertical seguida de otro espacio en blanco...
Separar Nombres y Apellidos solo con fórmulas


En el paso siguiente aplicamos el proceso de sustitución múltiple comentado en un post previo.
Así en la celda D3 insertamos:
=UNIRCADENAS(" ";0;
SI.ERROR(BUSCARV(XMLFILTRO("<ini><fila><dato>"&SUSTITUIR(D2;" ";"</dato><dato>")&"</dato></fila></ini>";"//ini/fila/dato");TblReemp;2;0);
XMLFILTRO("<ini><fila><dato>"&SUSTITUIR(D2;" ";"</dato><dato>")&"</dato></fila></ini>";"//ini/fila/dato")))

donde se procede a sustituir cada elemento o palabra del nombre por su equivalencia de la tabla de reemplazos... si no existiera equivalencia nos quedamos con la palabra original.
Separar Nombres y Apellidos solo con fórmulas


El siguiente punto consiste en un simple conteo de partes del resultado anterior, en D4 aplicamos la fórmula:
=LARGO(D3)-LARGO(SUSTITUIR(D3;"|";""))+1

Si todo va bien hasta ahora, como máximo podremos tener cuatro partes...
Por tanto lo que haremos será recuperar, por separado, cada una de ellas. Para lo cual haremos uso, de nuevo, de la función XMLFILTRO con un uso curioso de su segundo argumento, donde indicaremos qué elemento/posición queremos obtener...
Entonces, como pasos siguientes tendremos en D5:
=TRANSPONER(XMLFILTRO("<ini><fila><dato>"&SUSTITUIR(D$3;"|";"</dato><dato>")&"</dato></fila></ini>";"//ini/fila/dato[1]"))
Notemos como recuperamos el primer elemento con //ini/fila/dato[1]
En D6 para el segundo elemento:
=TRANSPONER(XMLFILTRO("<ini><fila><dato>"&SUSTITUIR(D$3;"|";"</dato><dato>")&"</dato></fila></ini>";"//ini/fila/dato[2]"))
En D7:
=TRANSPONER(XMLFILTRO("<ini><fila><dato>"&SUSTITUIR(D$3;"|";"</dato><dato>")&"</dato></fila></ini>";"//ini/fila/dato[3]"))
Y en D8 para el último:
=TRANSPONER(XMLFILTRO("<ini><fila><dato>"&SUSTITUIR(D$3;"|";"</dato><dato>")&"</dato></fila></ini>";"//ini/fila/dato[4]"))
Separar Nombres y Apellidos solo con fórmulas

Y llegamos al resultado final, empleando los pasos recorridos, en D9 insertamos:
=SI(D4>3;
TRANSPONER(XMLFILTRO("<ini><fila><dato>"&UNIRCADENAS("</dato><dato>";FALSO;D5&" "&D6;SI.ERROR(D7;"");SI.ERROR(D8;""))&"</dato></fila></ini>";"//ini/fila/dato"));
TRANSPONER(XMLFILTRO("<ini><fila><dato>"&UNIRCADENAS("</dato><dato>";FALSO;D5;SI.ERROR(D6;"");SI.ERROR(D7;""))&"</dato></fila></ini>";"//ini/fila/dato")))

Donde controlamos y decimos que si hay cuatro partes, necesariamente es por que el nombre es compuesto, y concatenamos las partes/elementos de las celdas D5,D6,D7 y D8 de acuerdo a esas partes.
Para evitar posibles fallos, en caso de que no existan tantas partes (por ejemplo un nombre y un solo apellido, o algo similar), aplicamos la función SI.ERROR a estas partes...
Separar Nombres y Apellidos solo con fórmulas

Con lo que concluiríamos nuestro trabajo... podemos probar diferentes combinaciones de nombre y comprobaríamos que el éxito esta garantizado en un muy elevado porcentaje, cercano al 100%.

Pero... ¿y si quiero trabajar todo esto en una sola fórmula?
No te preocupes... si has comprendido los pasos explicados, entenderás enseguida la fórmula siguiente.
Emplearemos la función LET (ver aquí), que nos permite construir fórmulas 'monstruosas' agilmente, y fáciles de seguir...

Veamos nuestra 'megafórmula' aplicada sobre diferentes combinaciones de nombres compuestos o simples...
Separar Nombres y Apellidos solo con fórmulas

En la celda D15 añadimos:
=SI.ERROR(LET(Paso0;SUSTITUIR(NOMPROPIO(A15);" ";"| ");
Paso1;UNIRCADENAS(" ";0;
SI.ERROR(BUSCARV(XMLFILTRO("<ini><fila><dato>"&SUSTITUIR(Paso0;" ";"</dato><dato>")&"</dato></fila></ini>";"//ini/fila/dato");TblReemp;2;0);
XMLFILTRO("<ini><fila><dato>"&SUSTITUIR(Paso0;" ";"</dato><dato>")&"</dato></fila></ini>";"//ini/fila/dato")));
Partes;LARGO(Paso1)-LARGO(SUSTITUIR(Paso1;"|";""))+1;
part1;TRANSPONER(XMLFILTRO("<ini><fila><dato>"&SUSTITUIR(Paso1;"|";"</dato><dato>")&"</dato></fila></ini>";"//ini/fila/dato[1]"));
part2;TRANSPONER(XMLFILTRO("<ini><fila><dato>"&SUSTITUIR(Paso1;"|";"</dato><dato>")&"</dato></fila></ini>";"//ini/fila/dato[2]"));
part3;TRANSPONER(XMLFILTRO("<ini><fila><dato>"&SUSTITUIR(Paso1;"|";"</dato><dato>")&"</dato></fila></ini>";"//ini/fila/dato[3]"));
part4;TRANSPONER(XMLFILTRO("<ini><fila><dato>"&SUSTITUIR(Paso1;"|";"</dato><dato>")&"</dato></fila></ini>";"//ini/fila/dato[4]"));
SI(Partes>3;
TRANSPONER(XMLFILTRO("<ini><fila><dato>"&UNIRCADENAS("</dato><dato>";FALSO;part1&" "&part2;part3;SI.ERROR(part4;""))&"</dato></fila></ini>";"//ini/fila/dato"));
TRANSPONER(XMLFILTRO("<ini><fila><dato>"&UNIRCADENAS("</dato><dato>";FALSO;part1;SI.ERROR(part2;"");SI.ERROR(part3;""))&"</dato></fila></ini>";"//ini/fila/dato"))));"")

Separar Nombres y Apellidos solo con fórmulas

Puedes copiar o arrastrar la fórmula al resto de nombres y comprobar su correcto funcionamiento.

jueves, 4 de febrero de 2021

Sustitución múltiple con fórmulas en Excel

Una cuestión planteada recurrentemente en diferentes foros de Exce es la posibilidad de realizar múltiples sustituciones con fórmulas de una sola vez:
Sustitución múltiple con fórmulas en Excel


Esta sustitución múltiple es posible empleando la función XMLFILTRO (ya vista en articulos previos de este blog), y alguna función desbordada como SECUENCIA, y otras funciones clásicas como UNIRCADENAS, INDICE, BUSCARV o SI.ERROR.
Recomendado por tanto emplear versión de Microsoft 365!!.

Veamos lo que necesitamos...
En un rango A2:A6 las cadenas de texto donde aplicar nuestra sustitución múltiple.
Una tabla 'TblREEMPLAZO' donde tengamos qué deseamos reemplazar y por qué... No importa cuan larga sea la tabla!.
Y finalmente nuestra fórmula en B2:
=UNIRCADENAS(" ";0;
SI.ERROR(BUSCARV(XMLFILTRO("<ini><fila><dato>"&SUSTITUIR(A2;" ";"</dato><dato>")&"</dato></fila></ini>";"//ini/fila/dato");TblREEMPLAZO;2;0);
INDICE(XMLFILTRO("<ini><fila><dato>"&SUSTITUIR(A2;" ";"</dato><dato>")&"</dato></fila></ini>";"//ini/fila/dato");
SECUENCIA(LARGO(A2)-LARGO(SUSTITUIR(A2;" ";""))+1))))


O una versión más 'light':
=UNIRCADENAS(" ";0;
SI.ERROR(BUSCARV(XMLFILTRO("<ini><fila><dato>"&SUSTITUIR(A2;" ";"</dato><dato>")&"</dato></fila></ini>";"//ini/fila/dato");TblREEMPLAZO;2;0);
XMLFILTRO("<ini><fila><dato>"&SUSTITUIR(A2;" ";"</dato><dato>")&"</dato></fila></ini>";"//ini/fila/dato")))


¿Un poco larga??... Pero muy potente!

Descompongámosla...

La clave como puedes comprobar es nuevamente el uso de XMLFILTRO, función que apareció en la versión 2013, y de poco conocimiento general... pero que a dia de hoy, con el comportamiento desbordado, con las fórmulas desbordadas, ha subido a un alto status entre las funciones.

Empezaremos analizando la fórmula:
XMLFILTRO("<ini><fila><dato>"&SUSTITUIR(A2;" ";"</dato><dato>")&"</dato></fila></ini>";"//ini/fila/dato")
que nos retornaría una vector/matriz vertical de elementos, entendiendo elementos para nuestro caso por palabras:
Sustitución múltiple con fórmulas en Excel

En un segundo paso cada elemento o palabra recuperada la pasamos con un BUSCARV sobre nuestra TblREEMPLAZO:
BUSCARV(XMLFILTRO("<ini><fila><dato>"&SUSTITUIR(A2;" ";"</dato><dato>")&"</dato></fila></ini>";"//ini/fila/dato");TblREEMPLAZO;2;0)
Sustitución múltiple con fórmulas en Excel

En este paso hemos identificado cualquier palabra dentro de nuestra lista de reemplazos, y a la vez han sido sustituidas... Las palabras no encontradas devuelven lógicamente un #N/D.

Situación que controlamos en el siguiente paso con la función SI.ERROR:
SI.ERROR(BUSCARV(XMLFILTRO("<ini><fila><dato>"&SUSTITUIR(A2;" ";"</dato><dato>")&"</dato></fila></ini>";"//ini/fila/dato");TblREEMPLAZO;2;0);
INDICE(XMLFILTRO("<ini><fila><dato>"&SUSTITUIR(A2;" ";"</dato><dato>")&"</dato></fila></ini>";"//ini/fila/dato");
SECUENCIA(LARGO(A2)-LARGO(SUSTITUIR(A2;" ";""))+1)))

Sustitución múltiple con fórmulas en Excel

La idea de este paso es muy sencilla. Buscamos palabras a sustituir, y si no está en la lista nos quedamos con la palabra original.
La función INDICE combinada con SECUENCIA se encarga de retornar precisamente esa palabra no encontrada en la 'TblREEMPLAZO':
INDICE(XMLFILTRO("<ini><fila><dato>"&SUSTITUIR(A2;" ";"</dato><dato>")&"</dato></fila></ini>";"//ini/fila/dato");
SECUENCIA(LARGO(A2)-LARGO(SUSTITUIR(A2;" ";""))+1))


Recuerda que, en general, también se podría emplear simplemente:
XMLFILTRO("<ini><fila><dato>"&SUSTITUIR(A2;" ";"</dato><dato>")&"</dato></fila></ini>";"//ini/fila/dato")

El último paso es el más obvio de todos, con UNIRCADENAS, juntamos cada palabra final obtenida con las fórmulas previas, y como vimos en una versión más abreviada:
=UNIRCADENAS(" ";0;
SI.ERROR(BUSCARV(XMLFILTRO("<ini><fila><dato>"&SUSTITUIR(A2;" ";"</dato><dato>")&"</dato></fila></ini>";"//ini/fila/dato");TblREEMPLAZO;2;0);
XMLFILTRO("<ini><fila><dato>"&SUSTITUIR(A2;" ";"</dato><dato>")&"</dato></fila></ini>";"//ini/fila/dato")))


Y con la fórmula acabada podemos arrastrar en B2:B6 como necesitábamos.
Todo un éxito!!.

En definitiva hemos conseguido una búsqueda y sustitución 'recursiva', evitando el uso de anidaciones de SUSTITUIR.

martes, 2 de febrero de 2021

Alternativas a TEXTSPLIT en Excel

Hoy toca algo más de formulación... con un objetivo claro, replicar el comportamiento de TEXTSPLIT; esto es, conseguir con una fórmula dividir una cadena de texto en una celda que incluya separadores (como ; , - | etc).
Veremos un par de posibilidades... Una mejor que otra ;-)
Alternativas a TEXTSPLIT

Comencemos con la primera fórmula, válida solo cuando los datos están distribuidos uniformemente en la celda, con igual número de caracteres entre el separador.
Muy limitada, sin duda, pero útil y simple si nos sentimos cómodos con las funciones desbordadas.

Vemos unos datos en una celda separados por la barra vertical |, y que además el número de caracteres entre separadores es siempre igual a tres. En B1:B3:
Q-1|Q-2|Q-3|Q-4|
100|200|321|425|
351|589|654|133|
Así pues en D1 insertamos la siguiente fórmula desbordada:
=EXTRAE(B1;
FILTRAR((EXTRAE(B1;SECUENCIA(1;LARGO(B1));1)="|")*(SECUENCIA(1;LARGO(B1)));
(EXTRAE(B1;SECUENCIA(1;LARGO(B1));1)="|")*(SECUENCIA(1;LARGO(B1)))<>0)-3;
3)

Alternativas a TEXTSPLIT

Fórmula basada, y que mejora, una fórmula expuesta en este blog hace años (ver aquí).
La idea fundamental es que se recorre cada caracter de la celda en busca de las posiciones donde se ubiquen los separadores, lo que se obtiene con la parte:
FILTRAR((EXTRAE(B1;SECUENCIA(1;LARGO(B1));1)="|")*(SECUENCIA(1;LARGO(B1)));
(EXTRAE(B1;SECUENCIA(1;LARGO(B1));1)="|")*(SECUENCIA(1;LARGO(B1)))<>0)-3;

Finalmente, identificadas cuantas posiciones existan del separador, con la función EXTRAE recuperamos los tres caracteres siguientes... es decir, los caracteres que responden a los elementos buscados.
Una ventaja de esta fórmula mejorada frente a la del post comentado es que no estamos limitados a un número finito y determinado de elementos...

Como alternativa a esta, y de éxito seguro, y sin limitaciones de ningún tipo.
Para esta fórmula emplearemos la ya conocida función web XMLFILTRO.
Esta fórmula ha sido bastante comentada ya en diferentes foros y grupos de Excel, sin saber exactamente quién fue su creador... pero sin duda de una aplicación real.
Si lees los artículos del blog previos a este, donde se explica la función XMLFILTRO, y algunas otras aplicaciones, entenderás mucho mejor el uso de hoy.

Disponemos de los siguientes datos en B6:B16:
año|pais|uds|ventas|comentario
2020|ES|47|1575
2024|FR|44|1128
2021|IT|26|2069|pendiente
2024|DE|42|1537
2024|ES|16|4866
2024|FR|20|2477
2021|IT|39|1182|riesgo
2022|DE|38|1509
2021|ES|26|3312|ojo!
2025|FR|14|3117|evaluar
como vemos con distinto número de separadores, distinto número de caracteres entre éstos e incluso podría haber diferentes longitudes en cada celda...
En D6 añadimos nuestra fórmula:
=TRANSPONER(XMLFILTRO("<inicio><fila><dato>"&SUSTITUIR(B6;"|";"</dato><dato>")&"</dato></fila></inicio>";"//fila/dato"))
Alternativas a TEXTSPLIT

Como expliqué en entradas previas del blog, lo que se persigue en esta fórmula en concatenar una cadena de texto que imite el código de una tabla en XML.
No se hace necesario emplear la notación real de una tabla (etiquetas table - tr - td), pueden ser inventadas siempre y cuando se embeban en los niveles adecuados, y se abran y cierren correctamente las etiquetas inventadas.

Si nos fijamos con el primer argumento de XMLFILTRO:
"<inicio><fila><dato>"&SUSTITUIR(B6;"|";"</dato><dato>")&"</dato></fila></inicio>"
conseguiríamos el código XML siguiente:
  
      
         añopaisudsventascomentario
      
  

que sería el equivalente a una especie de tabla con tantos elementos como 'dato' hayamos logrado con la fórmula.
El concatenado se consigue con una sencilla SUSTITUCION del separador por las etiquetas adecuadas...
Finalemente es XMLFILTRO la que se encarga de recuperar cada 'dato':
XMLFILTRO(cód_XML;"//fila/dato")
Empleamos la función TRANSPONER porque XMLFILTRO retorna siempre un vector vertical de los elementos... y en este caso nos interesa el vector horizontal, como se veía en la imagen.