Los números romanos, históricamente significativos, se convierten a arábigos con herramientas modernas como Power Query, fusionando tradición y tecnología
Convertir de Número Romano a Arábigo con Power Query, una función personalizada de Power Query que desglosamos a continuación:
Paso 1: Definición de Variables
En este paso, establecemos las bases para nuestro código definiendo las variables esenciales que usaremos en los pasos posteriores.
1 | let2 | NumeroRomano = 3 | "CMXXXVII",4 | 5 | CodificacionRomana = 6 | [ I = 1, V = 5, X = 10, L = 50, C = 100, D = 500, M = 1000 ]7 | 8 | in9 | CodificacionRomana
Número Romano
En nuestro código, la primera variable se llama NumeroRomano
y almacenamos en ella un valor de tipo texto que representa el número romano CMXXXVII
.
Codificación Romana
En nuestro código la variable CodifcacionRomana
almacena un valor de registro. En el Lenguaje M, un registro es similar a un diccionario en otros lenguajes de programación, ya que asocia claves (en este caso, símbolos romanos) con valores (en este caso, sus equivalentes numéricos).
La CodificacionRomana
mapea cada símbolo romano a su valor numérico correspondiente. Concretamente: “I” se asigna a 1, “V” se asigna a 5, “X” se asigna a 10 y así sucesivamente.
Paso 2: Validación de Símbolos
El propósito de este paso es garantizar que cada carácter en la cadena NumeroRomano
sea un símbolo romano válido. Los símbolos romanos válidos incluyen “I”, “V”, “X”, “L”, “C”, “D” y “M”. Esto nos permite identificar de antemano cualquier entrada inválida, como por ejemplo “AVII“, donde la letra “A” no es un carácter válido dentro de la notación romana. La validación inicial es esencial para asegurarnos de que el número romano proporcionado sea correcto antes de continuar con cualquier proceso adicional.
Para lograrlo, primero:
Convertir Número Romano en Lista
Queremos que cada letra del número romano proporcionado sea un elemento en una lista. Esto lo conseguimos con la función Text.ToList
. El resultado lo guardaremos en la variable NumeroRomanoEnLista
, así: NumeroRomanoEnLista = Text.ToList ( NumeroRomano )
Validación de Caracteres Romanos
Luego de esto, procederemos a crear una expresión en un paso llamado EsValido
que nos permitirá determinar si la lista NumeroRomanoEnLista
contiene exclusivamente caracteres romanos válidos. Comenzamos utilizando la función List.Distinct
aplicada a la variable NumeroRomanoEnLista
, de manera que obtengamos una lista con valores únicos. A continuación, empleamos la función List.Difference
para comparar esta lista de valores únicos con una lista predefinida de caracteres romanos válidos, que incluye: { "I", "V", "X", "L", "C", "D", "M" }
.
Mediante esta expresión, logramos que la lista quede vacía si todos los elementos son caracteres romanos válidos; de lo contrario, si algún carácter no es válido (por ejemplo, una "A"
), dicho carácter se mantendrá en la lista.
Para determinar si la lista resultante de la diferencia está vacía, empleamos la función List.IsEmpty
. Si la lista está vacía, esto indica que todos los elementos en NumeroRomanoEnLista
son caracteres romanos válidos, y la expresión devolverá true
. Si la lista no está vacía, esto señala que al menos un elemento en NumeroRomanoEnLista
no es un carácter romano válido, y la expresión devolverá false
.
1 | let 2 | NumeroRomano = 3 | "CMXXXVII", 4 | 5 | CodificacionRomana = 6 | [ I = 1, V = 5, X = 10, L = 50, C = 100, D = 500, M = 1000 ], 7 | 8 | NumeroRomanoEnLista = 9 | Text.ToList ( NumeroRomano ),10 | 11 | EsValido =12 | List.IsEmpty (13 | List.Difference (14 | List.Distinct ( NumeroRomanoEnLista ),15 | { "I", "V", "X", "L", "C", "D", "M" }16 | )17 | )18 | 19 | in20 | EsValido
Paso 3: Validación de Secuencia
Los números romanos tienen reglas específicas sobre cómo los símbolos individuales pueden aparecer en secuencia. Por ejemplo, el símbolo “I” puede repetirse hasta tres veces consecutivas (como en “III” para representar el número 3), pero nunca cuatro veces seguidas. De manera similar, los símbolos “V”, “L” y “D” tienen la restricción de que no deben repetirse en absoluto.
Enseguida crearemos una variable llamada EsSecuenciaValida
, la cual utiliza La función List.Select
para filtrar una lista predefinida de secuencias inválidas: { "IIII", "VV", "XXXX", "LL", "CCCC", "DD" }
y selecciona solo aquellas secuencias que están presentes en NumeroRomano
utilizando la función Text.Contains
. El resultado es una lista de secuencias inválidas encontradas en NumeroRomano
. Finalmente, la función List.IsEmpty
verifica si esta lista resultante está vacía. Si la lista está vacía, significa que NumeroRomano
no contiene ninguna secuencia inválida y la expresión devuelve true
. Si la lista no está vacía, indica que hay al menos una secuencia inválida en NumeroRomano
y la expresión devuelve false
.
1 | let 2 | NumeroRomano = 3 | "CMXXXVII", 4 | 5 | CodificacionRomana = 6 | [ I = 1, V = 5, X = 10, L = 50, C = 100, D = 500, M = 1000 ], 7 | 8 | NumeroRomanoEnLista = 9 | Text.ToList ( NumeroRomano ),10 | 11 | EsValido =12 | List.IsEmpty (13 | List.Difference (14 | List.Distinct ( NumeroRomanoEnLista ),15 | { "I", "V", "X", "L", "C", "D", "M" }16 | )17 | ),18 | 19 | EsSecuenciaValida = 20 | List.IsEmpty (21 | List.Select (22 | { "IIII", "VV", "XXXX", "LL", "CCCC", "DD" },23 | each 24 | Text.Contains (25 | NumeroRomano, 26 | _27 | )28 | )29 | )30 | 31 | in32 | EsSecuenciaValida
Paso 4: Conversión de Romano a Arábigo
Ahora, vamos a convertir el número romano en su equivalente decimal. La idea es recorrer cada símbolo y, dependiendo del símbolo que le sigue, decidir si debemos sumar o restar su valor.
El fragmento de código EquivalenciaNumerica
se encarga de convertir una lista de símbolos romanos en su equivalente numérico arábigo. Comienza verificando dos condiciones con EsValido and EsSecuenciaValida
. Si ambas son verdaderas, procede a la conversión; de lo contrario, devuelve null
.
La función List.Transform
se utiliza para recorrer cada símbolo en NumeroRomanoEnLista
. Para cada símbolo, determina su valor numérico (ValorActual
) consultando el registro CodificacionRomana
. Luego, identifica el símbolo siguiente (SimboloAdyacente
) y su valor (ValorAdyacente
). Si no hay un símbolo siguiente, se asigna un valor de -#infinity
.
La lógica clave aquí es el cálculo de ValorConSigno
. En la numeración romana, si un símbolo de menor valor precede a uno de mayor valor (como “IV” para 4), el valor del símbolo menor se resta del mayor. Por lo tanto, si ValorActual
es menor que ValorAdyacente
, se le asigna un signo negativo; de lo contrario, se mantiene su valor positivo.
El resultado es una lista de números que, cuando se suman, proporcionan el valor arábigo total del número romano.
1 | let 2 | NumeroRomano = 3 | "CMXXXVII", 4 | 5 | CodificacionRomana = 6 | [ I = 1, V = 5, X = 10, L = 50, C = 100, D = 500, M = 1000 ], 7 | 8 | NumeroRomanoEnLista = 9 | Text.ToList ( NumeroRomano ),10 | 11 | EsValido =12 | List.IsEmpty (13 | List.Difference (14 | List.Distinct ( NumeroRomanoEnLista ),15 | { "I", "V", "X", "L", "C", "D", "M" }16 | )17 | ),18 | 19 | EsSecuenciaValida = 20 | List.IsEmpty (21 | List.Select (22 | { "IIII", "VV", "XXXX", "LL", "CCCC", "DD" },23 | each 24 | Text.Contains (25 | NumeroRomano, 26 | _27 | )28 | )29 | ),30 | 31 | EquivalenciaNumerica = 32 | if 33 | EsValido and EsSecuenciaValida 34 | then35 | List.Transform (36 | 37 | NumeroRomanoEnLista,38 | 39 | ( Simbolo ) => 40 | let41 | ValorActual = 42 | Record.Field ( 43 | CodificacionRomana, 44 | Simbolo45 | ),46 | 47 | PosicionAdyacente = 48 | List.PositionOf ( 49 | NumeroRomanoEnLista, 50 | Simbolo51 | ) + 1,52 | 53 | SimboloAdyacente = 54 | NumeroRomanoEnLista { PosicionAdyacente }?,55 | 56 | ValorAdyacente = 57 | try 58 | Record.Field ( 59 | CodificacionRomana, 60 | SimboloAdyacente61 | ) 62 | otherwise 63 | -#infinity,64 | 65 | ValorConSigno = 66 | if 67 | ValorActual < ValorAdyacente 68 | then 69 | -ValorActual 70 | else 71 | ValorActual72 | in73 | ValorConSigno74 | )75 | else76 | null77 | 78 | in79 | EsSecuenciaValida
Paso 5: Cálculo Final
Finalmente, sumamos todos los valores numéricos para obtener el valor decimal del número romano.
Si EquivalenciaNumerica
no es null
(lo que significa que el número romano es válido), sumamos todos los valores para obtener el resultado final. De lo contrario, devolvemos null
para indicar un número romano inválido.
Con estos pasos, hemos construido un código que no solo convierte números romanos a decimales, sino que también valida la entrada para asegurarse de que sea un número romano válido.
El código final:
1 | let 2 | NumeroRomano = 3 | "CMXXXVII", 4 | 5 | CodificacionRomana = 6 | [ I = 1, V = 5, X = 10, L = 50, C = 100, D = 500, M = 1000 ], 7 | 8 | NumeroRomanoEnLista = 9 | Text.ToList ( NumeroRomano ),10 | 11 | EsValido =12 | List.IsEmpty (13 | List.Difference (14 | List.Distinct ( NumeroRomanoEnLista ),15 | { "I", "V", "X", "L", "C", "D", "M" }16 | )17 | ),18 | 19 | EsSecuenciaValida = 20 | List.IsEmpty (21 | List.Select (22 | { "IIII", "VV", "XXXX", "LL", "CCCC", "DD" },23 | each 24 | Text.Contains (25 | NumeroRomano, 26 | _27 | )28 | )29 | ),30 | 31 | EquivalenciaNumerica = 32 | if 33 | EsValido and EsSecuenciaValida 34 | then35 | List.Transform (36 | 37 | NumeroRomanoEnLista,38 | 39 | ( Simbolo ) => 40 | let41 | ValorActual = 42 | Record.Field ( 43 | CodificacionRomana, 44 | Simbolo45 | ),46 | 47 | PosicionAdyacente = 48 | List.PositionOf ( 49 | NumeroRomanoEnLista, 50 | Simbolo51 | ) + 1,52 | 53 | SimboloAdyacente = 54 | NumeroRomanoEnLista { PosicionAdyacente }?,55 | 56 | ValorAdyacente = 57 | try 58 | Record.Field ( 59 | CodificacionRomana, 60 | SimboloAdyacente61 | ) 62 | otherwise 63 | -#infinity,64 | 65 | ValorConSigno = 66 | if 67 | ValorActual < ValorAdyacente 68 | then 69 | -ValorActual 70 | else 71 | ValorActual72 | in73 | ValorConSigno74 | )75 | else76 | null,77 | 78 | CalculoDelValor = 79 | if 80 | EquivalenciaNumerica <> null 81 | then 82 | List.Sum ( EquivalenciaNumerica ) 83 | else 84 | null 85 | 86 | in87 | CalculoDelValor
Y aquí lo tenemos la solución complete para Convertir de Número Romano a Arábigo con Power Query.
Conclusiones
La conversión de números romanos a arábigos, aunque puede parecer una tarea arcaica, sigue siendo relevante en la era digital, especialmente cuando se trata de procesar y analizar datos en la parte de derecho, recursos humanos y sus formatos, documentos históricos y literarios. El fragmento de código que hemos explorado demuestra la potencia y flexibilidad de Power Query para abordar este desafío.
Si deseas aprender Power Query y Lenguaje M y crear soluciones como Convertir de Número Romano a Arábigo con Power Query, el curso de Grado Experto: Experto en Power Query y Lenguaje M es la mejor opción.