Ir al contenido principal

Sistema factorádico de numeración


El sistema factorádico es un sistema numérico basado en factoriales en el que el n-ésimo dígito, empezando desde la derecha, debe ser multiplicado por n! Por ejemplo, el número "341010" en el sistema factorádico es 463 en el sistema decimal ya que

3×5! + 4×4! + 1×3! + 0×2! + 1×1! + 0×0! = 463

En este sistema numérico, el dígito de más a la derecha es siempre 0, el segundo 0 o 1, el tercero 0,1 o 2 y así sucesivamente.

Con los dígitos del 0 al 9 el mayor número que podemos codificar es el 10!-1 = 3628799. En cambio, si lo ampliamos con las letras A a Z podemos codificar hasta 36!-1 = 37199332678990121746799944815083519999999910.

Definir las funciones

factoradicoAdecimal :: String -> Integer
decimalAfactoradico :: Integer -> String

tales que

  • (factoradicoAdecimal cs) es el número decimal correspondiente al número factorádico cs. Por ejemplo,
λ> factoradicoAdecimal "341010"
463
λ> factoradicoAdecimal "2441000"
2022
λ> factoradicoAdecimal "A0000000000"
36288000
λ> map factoradicoAdecimal ["10","100","110","200","210","1000","1010","1100","1110","1200"]
[1,2,3,4,5,6,7,8,9,10]
λ> factoradicoAdecimal "3KXWVUTSRQPONMLKJIHGFEDCBA9876543210"
37199332678990121746799944815083519999999
  • (decimalAfactoradico n) es el número factorádico correpondiente al número decimal n. Por ejemplo,
λ> decimalAfactoradico 463
"341010"
λ> decimalAfactoradico 2022
"2441000"
λ> decimalAfactoradico 36288000
"A0000000000"
λ> map decimalAfactoradico [1..10]
["10","100","110","200","210","1000","1010","1100","1110","1200"]
λ> decimalAfactoradico 37199332678990121746799944815083519999999
"3KXWVUTSRQPONMLKJIHGFEDCBA9876543210"

Comprobar con QuickCheck que, para cualquier entero positivo n,

factoradicoAdecimal (decimalAfactoradico n) == n

Nota: Escribir las soluciones en Haskell, en Python y en Common Lisp.

Soluciones

A continuación se muestran

1. Soluciones en Haskell

module Sistema_factoradico_de_numeracion where

import Data.List (genericIndex, genericLength)
import qualified Data.Map as M ((!), Map, fromList)
import Test.Hspec (Spec, describe, hspec, it, shouldBe)
import Test.QuickCheck

-- 1ª definición de factoradicoAdecimal
-- ====================================

factoradicoAdecimal1 :: String -> Integer
factoradicoAdecimal1 cs = sum (zipWith (*) xs ys)
  where xs = map caracterAentero cs
        n  = length cs
        ys = reverse (take n facts)

-- (caracterAentero c) es la posición del carácter c en la lista de
-- caracteres ['0', '1',..., '9', 'A', 'B',..., 'Z']. Por ejemplo,
--    caracterAentero '0'  ==  0
--    caracterAentero '1'  ==  1
--    caracterAentero '9'  ==  9
--    caracterAentero 'A'  ==  10
--    caracterAentero 'B'  ==  11
--    caracterAentero 'Z'  ==  35
caracterAentero :: Char -> Integer
caracterAentero c =
  head [n | (n,x) <- zip [0..] caracteres, x == c]

-- caracteres es la lista de caracteres
-- ['0', '1',..., '9', 'A', 'B',..., 'Z']
caracteres :: String
caracteres = ['0'..'9'] ++ ['A'..'Z']

-- facts es la lista de los factoriales. Por ejemplo,
--    λ> take 12 facts
--    [1,1,2,6,24,120,720,5040,40320,362880,3628800,39916800]
facts :: [Integer]
facts = scanl (*) 1 [1..]

-- 2ª definición de factoradicoAdecimal
-- ====================================

factoradicoAdecimal2 :: String -> Integer
factoradicoAdecimal2 cs = sum (zipWith (*) xs ys)
    where xs = map caracterAentero2 cs
          n  = length cs
          ys = reverse (take n facts)

-- (caracterAentero2 c) es la posición del carácter c en la lista de
-- caracteres ['0', '1',..., '9', 'A', 'B',..., 'Z']. Por ejemplo,
--    caracterAentero2 '0'  ==  0
--    caracterAentero2 '1'  ==  1
--    caracterAentero2 '9'  ==  9
--    caracterAentero2 'A'  ==  10
--    caracterAentero2 'B'  ==  11
--    caracterAentero2 'Z'  ==  35
caracterAentero2 :: Char -> Integer
caracterAentero2 c = caracteresEnteros M.! c

-- caracteresEnteros es el diccionario cuyas claves son los caracteres y
-- las claves son los números de 0 a 35.
caracteresEnteros :: M.Map Char Integer
caracteresEnteros = M.fromList (zip (['0'..'9'] ++ ['A'..'Z']) [0..])

-- 3ª definición de factoradicoAdecimal
-- ====================================

factoradicoAdecimal3 :: String -> Integer
factoradicoAdecimal3 cs =
  sum (zipWith (*) facts (reverse (map caracterAentero3 cs)))

-- (caracterAentero3 c) es la posición del carácter c en la lista de
-- caracteres ['0', '1',..., '9', 'A', 'B',..., 'Z']. Por ejemplo,
--    caracterAentero3 '0'  ==  0
--    caracterAentero3 '1'  ==  1
--    caracterAentero3 '9'  ==  9
--    caracterAentero3 'A'  ==  10
--    caracterAentero3 'B'  ==  11
--    caracterAentero3 'Z'  ==  35
caracterAentero3 :: Char -> Integer
caracterAentero3 c =
  genericLength (takeWhile (/= c) caracteres)

-- 4ª definición de factoradicoAdecimal
-- ====================================

factoradicoAdecimal4 :: String -> Integer
factoradicoAdecimal4 =
  sum . zipWith (*) facts . reverse . map caracterAentero4

-- (caracterAentero4 c) es la posición del carácter c en la lista de
-- caracteres ['0', '1',..., '9', 'A', 'B',..., 'Z']. Por ejemplo,
--    caracterAentero4 '0'  ==  0
--    caracterAentero4 '1'  ==  1
--    caracterAentero4 '9'  ==  9
--    caracterAentero4 'A'  ==  10
--    caracterAentero4 'B'  ==  11
--    caracterAentero4 'Z'  ==  35
caracterAentero4 :: Char -> Integer
caracterAentero4 =
  genericLength . flip takeWhile caracteres . (/=)

-- 1ª definición de decimalAfactoradico
-- ====================================

decimalAfactoradico1 :: Integer -> String
decimalAfactoradico1 n = aux n (reverse (takeWhile (<=n) facts))
  where aux 0 xs     = ['0' | _ <- xs]
        aux m (x:xs) = enteroAcaracter (m `div` x) : aux (m `mod` x) xs

-- (enteroAcaracter k) es el k-ésimo elemento de la lista
-- ['0', '1',..., '9', 'A', 'B',..., 'Z']. . Por ejemplo,
--    enteroAcaracter 0   ==  '0'
--    enteroAcaracter 1   ==  '1'
--    enteroAcaracter 9   ==  '9'
--    enteroAcaracter 10  ==  'A'
--    enteroAcaracter 11  ==  'B'
--    enteroAcaracter 35  ==  'Z'
enteroAcaracter :: Integer -> Char
enteroAcaracter k = caracteres `genericIndex` k

-- 2ª definición de decimalAfactoradico
-- ====================================

decimalAfactoradico2 :: Integer -> String
decimalAfactoradico2 n = aux n (reverse (takeWhile (<=n) facts))
  where aux 0 xs     = ['0' | _ <- xs]
        aux m (x:xs) = enteroAcaracter2 (m `div` x) : aux (m `mod` x) xs

-- (enteroAcaracter2 k) es el k-ésimo elemento de la lista
-- ['0', '1',..., '9', 'A', 'B',..., 'Z']. . Por ejemplo,
--    enteroAcaracter2 0   ==  '0'
--    enteroAcaracter2 1   ==  '1'
--    enteroAcaracter2 9   ==  '9'
--    enteroAcaracter2 10  ==  'A'
--    enteroAcaracter2 11  ==  'B'
--    enteroAcaracter2 35  ==  'Z'
enteroAcaracter2 :: Integer -> Char
enteroAcaracter2 k = enterosCaracteres M.! k

-- enterosCaracteres es el diccionario cuyas claves son los número de 0
-- a 35 y las claves son los caracteres.
enterosCaracteres :: M.Map Integer Char
enterosCaracteres = M.fromList (zip [0..] caracteres)

-- 3ª definición de decimalAfactoradico
-- ====================================

decimalAfactoradico3 :: Integer -> String
decimalAfactoradico3 n = aux "" 2 (n, 0)
  where aux s _ (0, 0) = s
        aux s m (d, r) = aux (enteroAcaracter3 r: s) (m + 1) (d `divMod` m)

-- (enteroAcaracter3 k) es el k-ésimo elemento de la lista
-- ['0', '1',..., '9', 'A', 'B',..., 'Z']. . Por ejemplo,
--    enteroAcaracter3 0   ==  '0'
--    enteroAcaracter3 1   ==  '1'
--    enteroAcaracter3 9   ==  '9'
--    enteroAcaracter3 10  ==  'A'
--    enteroAcaracter3 11  ==  'B'
--    enteroAcaracter3 35  ==  'Z'
enteroAcaracter3 :: Integer -> Char
enteroAcaracter3 n =
  caracteres !! fromInteger n

-- 4ª definición de decimalAfactoradico
-- ====================================

decimalAfactoradico4 :: Integer -> String
decimalAfactoradico4 = f "" 2 . (, 0)
  where f s _ (0, 0) = s
        f s n (d, r) = f (enteroAcaracter4 r: s) (n + 1) (d `divMod` n)

-- (enteroAcaracter4 k) es el k-ésimo elemento de la lista
-- ['0', '1',..., '9', 'A', 'B',..., 'Z']. . Por ejemplo,
--    enteroAcaracter4 0   ==  '0'
--    enteroAcaracter4 1   ==  '1'
--    enteroAcaracter4 9   ==  '9'
--    enteroAcaracter4 10  ==  'A'
--    enteroAcaracter4 11  ==  'B'
--    enteroAcaracter4 35  ==  'Z'
enteroAcaracter4 :: Integer -> Char
enteroAcaracter4 = (caracteres `genericIndex`)

-- Verificación
-- ============

verifica :: IO ()
verifica = hspec spec

specG1 :: (String -> Integer) -> Spec
specG1 factoradicoAdecimal = do
  it "e1" $
    factoradicoAdecimal "341010" `shouldBe` 463
  it "e2" $
    factoradicoAdecimal "2441000" `shouldBe` 2022
  it "e3" $
    factoradicoAdecimal "A0000000000" `shouldBe` 36288000

specG2 :: (Integer -> String) -> Spec
specG2 decimalAfactoradico = do
  it "e1" $
    decimalAfactoradico 463 `shouldBe` "341010"
  it "e2" $
    decimalAfactoradico 2022 `shouldBe` "2441000"
  it "e3" $
    decimalAfactoradico 36288000 `shouldBe` "A0000000000"

spec :: Spec
spec = do
  describe "def. 1" $ specG1 factoradicoAdecimal1
  describe "def. 2" $ specG1 factoradicoAdecimal2
  describe "def. 3" $ specG1 factoradicoAdecimal3
  describe "def. 4" $ specG1 factoradicoAdecimal4
  describe "def. 1" $ specG2 decimalAfactoradico1
  describe "def. 2" $ specG2 decimalAfactoradico2
  describe "def. 3" $ specG2 decimalAfactoradico3
  describe "def. 4" $ specG2 decimalAfactoradico4

-- La verificación es
--    λ> verifica
--
--    24 examples, 0 failures

-- Propiedad de inverso
-- ====================

prop_factoradico :: Integer -> Property
prop_factoradico n =
  n >= 0 ==>
  factoradicoAdecimal1 (decimalAfactoradico1 n) == n &&
  factoradicoAdecimal2 (decimalAfactoradico2 n) == n &&
  factoradicoAdecimal3 (decimalAfactoradico3 n) == n &&
  factoradicoAdecimal4 (decimalAfactoradico4 n) == n

-- La comprobación es
--    λ> quickCheck prop_factoradico
--    +++ OK, passed 100 tests; 101 discarded.

-- Comparación de eficiencia
-- =========================

-- La comparación es
--    λ> length (decimalAfactoradico1 (10^300000))
--    68191
--    (2.46 secs, 9,088,634,744 bytes)
--    λ> length (decimalAfactoradico2 (10^300000))
--    68191
--    (2.36 secs, 9,088,634,800 bytes)
--    λ> length (decimalAfactoradico3 (10^300000))
--    68191
--    (2.18 secs, 4,490,856,416 bytes)
--    λ> length (decimalAfactoradico4 (10^300000))
--    68191
--    (1.98 secs, 4,490,311,536 bytes)
--
--    λ> length (show (factoradicoAdecimal1 (show (10^50000))))
--    213237
--    (0.93 secs, 2,654,156,680 bytes)
--    λ> length (show (factoradicoAdecimal2 (show (10^50000))))
--    213237
--    (0.51 secs, 2,633,367,168 bytes)
--    λ> length (show (factoradicoAdecimal3 (show (10^50000))))
--    213237
--    (0.93 secs, 2,635,792,192 bytes)
--    λ> length (show (factoradicoAdecimal4 (show (10^50000))))
--    213237
--    (0.43 secs, 2,636,996,848 bytes)

2. Soluciones en Python

from itertools import count, takewhile
from math import factorial
from typing import Iterator

from hypothesis import given
from hypothesis import strategies as st

# 1ª definición de factoradicoAdecimal
# ====================================

# caracterAentero(c) es la posición del carácter c en la lista de
# caracteres ['0', '1',..., '9', 'A', 'B',..., 'Z']. Por ejemplo,
#    caracterAentero('0')  ==  0
#    caracterAentero('1')  ==  1
#    caracterAentero('9')  ==  9
#    caracterAentero('A')  ==  10
#    caracterAentero('B')  ==  11
#    caracterAentero('Z')  ==  35
def caracterAentero(c: str) -> int:
    if c.isdigit():
        return int(c)
    return ord(c) - ord('A') +  10

def factoradicoAdecimal1(cs: str) -> int:
    xs = map(caracterAentero, cs)
    n  = len(cs)
    ys = reversed([factorial(k) for k in range(n)])
    return sum((x * y for (x, y) in zip(xs, ys)))

# 2ª definición de factoradicoAdecimal
# ====================================

# caracteres es la cadena de los caracteres.
caracteres: str = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'

# caracteresEnteros es el diccionario cuyas claves son los caracteres y
# los valores son los números de 0 a 35.
caracteresEnteros: dict[str, int] = {c: i for i, c in enumerate(caracteres)}

# caracterAentero2(c) es la posición del carácter c en la lista de
# caracteres ['0', '1',..., '9', 'A', 'B',..., 'Z']. Por ejemplo,
#    caracterAentero2('0')  ==  0
#    caracterAentero2('1')  ==  1
#    caracterAentero2('9')  ==  9
#    caracterAentero2('A')  ==  10
#    caracterAentero2('B')  ==  11
#    caracterAentero2('Z')  ==  35
def caracterAentero2(c: str) -> int:
    return caracteresEnteros[c]

def factoradicoAdecimal2(cs: str) -> int:
    xs = map(caracterAentero2, cs)
    n  = len(cs)
    ys = reversed([factorial(k) for k in range(n)])
    return sum((x * y for (x, y) in zip(xs, ys)))

# 3ª definición de factoradicoAdecimal
# ====================================

# caracterAentero3(c) es la posición del carácter c en la lista de
# caracteres ['0', '1',..., '9', 'A', 'B',..., 'Z']. Por ejemplo,
#    caracterAentero3('0')  ==  0
#    caracterAentero3('1')  ==  1
#    caracterAentero3('9')  ==  9
#    caracterAentero3('A')  ==  10
#    caracterAentero3('B')  ==  11
#    caracterAentero3('Z')  ==  35
def caracterAentero3(c: str) -> int:
    return len(list(takewhile(lambda x: x != c, caracteres)))

def factoradicoAdecimal3(cs: str) -> int:
    return sum(x * y for x, y in zip([factorial(k) for k in range(len(cs))],
                                     reversed(list(map(caracterAentero3, cs)))))

# 1ª definición de decimalAfactoradico
# ====================================

# enteroAcaracter(k) es el k-ésimo elemento de la lista
# ['0', '1',..., '9', 'A', 'B',..., 'Z']. . Por ejemplo,
#    enteroAcaracter(0)   ==  '0'
#    enteroAcaracter(1)   ==  '1'
#    enteroAcaracter(9)   ==  '9'
#    enteroAcaracter(10)  ==  'A'
#    enteroAcaracter(11)  ==  'B'
#    enteroAcaracter(35)  ==  'Z'
def enteroAcaracter(k: int) -> str:
    return caracteres[k]

# facts() es la lista de los factoriales. Por ejemplo,
#    >>> list(takewhile(lambda x : x < 900, facts()))
#    [1, 1, 2, 6, 24, 120, 720]
def facts() -> Iterator[int]:
    return (factorial(n) for n in count())

def decimalAfactoradico1(n: int) -> str:
    def aux(m: int, xs: list[int]) -> str:
        if m == 0:
            return "0" * len(xs)
        y, *ys = xs
        print(m, y, m // y)
        return enteroAcaracter(m // y) + aux(m % y, ys)
    return aux(n, list(reversed(list(takewhile(lambda x : x <= n, facts())))))

# 2ª definición de decimalAfactoradico
# ====================================

# enterosCaracteres es el diccionario cuyas claves son los número de 0
# a 35 y las claves son los caracteres.
enterosCaracteres: dict[int, str] = dict(enumerate(caracteres))

# enteroAcaracter2(k) es el k-ésimo elemento de la lista
# ['0', '1',..., '9', 'A', 'B',..., 'Z']. . Por ejemplo,
#    enteroAcaracter2(0)   ==  '0'
#    enteroAcaracter2(1)   ==  '1'
#    enteroAcaracter2(9)   ==  '9'
#    enteroAcaracter2(10)  ==  'A'
#    enteroAcaracter2(11)  ==  'B'
#    enteroAcaracter2(35)  ==  'Z'
def enteroAcaracter2(k: int) -> str:
    return enterosCaracteres[k]

def decimalAfactoradico2(n: int) -> str:
    def aux(m: int, xs: list[int]) -> str:
        if m == 0:
            return "0" * len(xs)
        y, *ys = xs
        return enteroAcaracter2(m // y) + aux(m % y, ys)
    return aux(n, list(reversed(list(takewhile(lambda x : x <= n, facts())))))

# 3ª definición de decimalAfactoradico
# ====================================

# enteroAcaracter3(k) es el k-ésimo elemento de la lista
# ['0', '1',..., '9', 'A', 'B',..., 'Z']. . Por ejemplo,
#    enteroAcaracter3(0)   ==  '0'
#    enteroAcaracter3(1)   ==  '1'
#    enteroAcaracter3(9)   ==  '9'
#    enteroAcaracter3(10)  ==  'A'
#    enteroAcaracter3(11)  ==  'B'
#    enteroAcaracter3(35)  ==  'Z'
def enteroAcaracter3(n: int) -> str:
    return caracteres[n]

def decimalAfactoradico3(n: int) -> str:
    def aux(m: int, xs: list[int]) -> str:
        if m == 0:
            return "0" * len(xs)
        y, *ys = xs
        return enteroAcaracter3(m // y) + aux(m % y, ys)
    return aux(n, list(reversed(list(takewhile(lambda x : x <= n, facts())))))

# Verificación
# ============

def test_factoradico() -> None:
    for factoradicoAdecimal in [factoradicoAdecimal1,
                                factoradicoAdecimal2,
                                factoradicoAdecimal3]:
        assert factoradicoAdecimal("341010") == 463
        assert factoradicoAdecimal("2441000") == 2022
        assert factoradicoAdecimal("A0000000000") == 36288000
    for decimalAfactoradico in [decimalAfactoradico1,
                                decimalAfactoradico2,
                                decimalAfactoradico3]:
        assert decimalAfactoradico(463) == "341010"
        assert decimalAfactoradico(2022) == "2441000"
        assert decimalAfactoradico(36288000) == "A0000000000"
    print("Verificado")

# La verificación es
#    >>> test_factoradico()
#    Verificado

# Propiedad de inverso
# ====================

@given(st.integers(min_value=0, max_value=1000))
def test_factoradico_equiv(n: int) -> None:
    assert factoradicoAdecimal1(decimalAfactoradico1(n)) == n
    assert factoradicoAdecimal2(decimalAfactoradico3(n)) == n
    assert factoradicoAdecimal3(decimalAfactoradico3(n)) == n

# La comprobación es
#    >>> test_factoradico_equiv()
#    >>>

3. Soluciones en Common Lisp

(ql:quickload "fiveam" :silent t)

(defpackage :sistema-factoradico-de-numeracion
  (:use :cl
        :fiveam))

(in-package :sistema-factoradico-de-numeracion)

;;; Listas infinitas
;;; ================

;;; En las soluciones se usarán las siguientes funciones para trabajar
;;; con listas infinitas (ver "Streams in Common Lisp"
;;; https://tinyurl.com/266685bz).

;;; (delay e) retrasa la evaluación de la función e. Por ejemplo,
;;;    > (delay (+ 2 3))
;;;    #<FUNCTION (LAMBDA ()) {5363B46B}>
(defmacro delay (e)
  `(lambda () ,e))

;;; (force e) evalúa la expresión retrasada retrasada e. Por ejemplo,
;;;    > (force (retrasa (+ 2 3)))
;;;    5
(defun force (e)
  (funcall e))

;;; (s-cons x y) crea una lista perezosa cuyo primer elemento es x
;;; y el resto es la expresión y retrasada. Por ejemplo,
;;;    > (setf ej-lp (s-cons 1 (s-cons 2 nil)))
;;;    (1 . #<FUNCTION (LAMBDA ()) {5368A37B}>)
(defmacro s-cons (x y)
  `(cons ,x (delay ,y)))

;;; (s-first s) es el primer elemento de la lista perezosa s. Por
;;; ejemplo,
;;;    > (s-first (s-cons 1 (s-cons 2 nil)))
;;;    1
(defun s-first (s)
  (car s))

;;; (s-rest s) es el resto de la lista perezosa s. Por ejmplo,
;;;    > (s-rest (s-cons 1 (s-cons 2 nil)))
;;;    (2 . #<FUNCTION (LAMBDA ()) {5363B00B}>)
(defun s-rest (s)
  (force (cdr s)))

;;; (s-take n s) es la lista de los n primeros elementos de la lista
;;; infinita s.
;;;    > (s-take 10 (factoriales))
;;;    (1 1 2 6 24)
(defun s-take (n s)
  (if (zerop n)
      nil
      (cons (s-first s)
            (s-take (1- n) (s-rest s)))))

;;; (factoriales) es la lista de los factoriales. Por ejemplo,
;;;    > (s-take 10 (factoriales))
;;;    (1 1 2 6 24 120 720 5040 40320 362880)
(defun factoriales (&optional (n 0) (f 1))
  (s-cons f (factoriales (1+ n) (* (1+ n) f))))

;;; 1ª definición de factoradico-a-decimal
;;; ======================================

;;; caracteres es la lista de caracteres
(defparameter *caracteres*
  (concatenate 'string
               "0123456789"
               "ABCDEFGHIJKLMNOPQRSTUVWXYZ"))

;;; (caracter-a-entero c) es la posición del carácter c en la lista de
;;; caracteres. Por ejemplo,
;;;    (caracter-a-entero #\0)  ==  0
;;;    (caracter-a-entero #\1)  ==  1
;;;    (caracter-a-entero #\9)  ==  9
;;;    (caracter-a-entero #\A)  ==  10
;;;    (caracter-a-entero #\B)  ==  11
;;;    (caracter-a-entero #\Z)  ==  35
(defun caracter-a-entero (c)
  (position c *caracteres*))

(defun factoradico-a-decimal-1 (cs)
  (let* ((xs (map 'list #'caracter-a-entero cs))
         (n (length cs))
         (ys (reverse (s-take n (factoriales)))))
    (reduce #'+ (mapcar #'* xs ys))))

;;; 2ª definición de factoradico-a-decimal
;;; ======================================

;;; *caracteres-enteros* es la tabla que asigna a cada carácter su
;;; posición correspondiente, desde 0 hasta 35. Por ejemplo,
;;;      > (gethash #\A *caracteres-enteros*)
;;;      10
(defparameter *caracteres-enteros*
  (let ((tabla (make-hash-table)))
    (loop for i from 0 to 35
          for c across *caracteres*
          do (setf (gethash c tabla) i))
    tabla))

;;; (caracter-a-entero-2 c) es la posición del carácter c en la lista de
;;; caracteres. Por ejemplo,
;;;    (caracter-a-entero-2 #\0)  ==  0
;;;    (caracter-a-entero-2 #\1)  ==  1
;;;    (caracter-a-entero-2 #\9)  ==  9
;;;    (caracter-a-entero-2 #\A)  ==  10
;;;    (caracter-a-entero-2 #\B)  ==  11
;;;    (caracter-a-entero-2 #\Z)  ==  35
(defun caracter-a-entero-2 (c)
  (gethash c *caracteres-enteros*))

(defun factoradico-a-decimal-2 (cs)
  (let* ((xs (map 'list #'caracter-a-entero-2 cs))
         (n (length cs))
         (ys (reverse (s-take n (factoriales)))))
    (reduce #'+ (mapcar #'* xs ys))))

;;; 3ª definición de factoradico-a-decimal
;;; ======================================

;;; (caracter-a-entero-3 c) es la posición del carácter c en la lista de
;;; caracteres. Por ejemplo,
;;;    (caracter-a-entero-3 #\0)  ==  0
;;;    (caracter-a-entero-3 #\1)  ==  1
;;;    (caracter-a-entero-3 #\9)  ==  9
;;;    (caracter-a-entero-3 #\A)  ==  10
;;;    (caracter-a-entero-3 #\B)  ==  11
;;;    (caracter-a-entero-3 #\Z)  ==  35
(defun caracter-a-entero-3 (c)
    (loop for x across *caracteres*
          for i from 0
          when (char= x c) return i))

(defun factoradico-a-decimal-3 (cs)
  (reduce #'+
          (mapcar #'*
                  (s-take (length cs) (factoriales))
                  (reverse (map 'list #'caracter-a-entero-3 cs)))))

;;; 1ª definición de decimalAfactoradico
;;; ====================================

;;; (entero-a-caracter k) es el k-ésimo elemento de la lista
;;; de los caracteres. Por ejemplo,
;;;    (entero-a-caracter 0)   ==  #\0
;;;    (entero-a-caracter 1)   ==  #\1
;;;    (entero-a-caracter 9)   ==  #\9
;;;    (entero-a-caracter 10)  ==  #\A
;;;    (entero-a-caracter 11)  ==  #\B
;;;    (entero-a-caracter 35)  ==  #\Z
(defun entero-a-caracter (k)
  (char *caracteres* k))

;;; (s-take-while p s) es la lista de los primeros elementos de la lista
;;; infinita s que verifica el predicado p. Por ejemplo,
;;;    > (s-take-while (lambda (x) (< x 30)) (factoriales))
;;;    (0 1 1 2 3 5 8 13 21)
(defun s-take-while (p s)
  (if (not (funcall p (s-first s)))
      nil
      (cons (s-first s)
            (s-take-while p (s-rest s)))))

(defun decimal-a-factoradico-1 (n)
  (labels ((aux (m xs)
             (if (= m 0)
                 (mapcar (constantly #\0) xs)
                 (let ((x (first xs)))
                   (cons (entero-a-caracter (truncate m x))
                         (aux (mod m x) (rest xs)))))))
    (coerce (aux n (reverse (s-take-while (lambda (x) (<= x n))
                                          (factoriales))))
            'string)))

;;; 2ª definición de decimalAfactoradico
;;; ====================================

;;; *enteros-caracteres* es la tabla cuyas claves son los número de 0
;;; a 35 y las claves son los caracteres. Por ejemplo,
;;;    > (gethash 10 *enteros-caracteres*)
;;;    #\A
(defparameter *enteros-caracteres*
  (let ((hash (make-hash-table)))
    (loop for i from 0 to 35
          for c across *caracteres*
          do (setf (gethash i hash) c))
    hash))

;;; (entero-a-caracter-2 k) es el k-ésimo elemento de la lista
;;; ['0', '1',..., '9', 'A', 'B',..., 'Z']. . Por ejemplo,
;;;    (entero-a-caracter-2 0)   ==  #\0
;;;    (entero-a-caracter-2 1)   ==  #\1
;;;    (entero-a-caracter-2 9)   ==  #\9
;;;    (entero-a-caracter-2 10)  ==  #\A
;;;    (entero-a-caracter-2 11)  ==  #\B
;;;    (entero-a-caracter-2 35)  ==  #\Z
(defun entero-a-caracter-2 (k)
  (gethash k *enteros-caracteres*))

(defun decimal-a-factoradico-2 (n)
  (labels ((aux (m xs)
             (if (= m 0)
                 (mapcar (constantly #\0) xs)
                 (let ((x (first xs)))
                   (cons (entero-a-caracter-2 (truncate m x))
                         (aux (mod m x) (rest xs)))))))
    (coerce (aux n (reverse (s-take-while (lambda (x) (<= x n))
                                          (factoriales))))
            'string)))

;;; 3ª definición de decimalAfactoradico
;;; ====================================

;;; (entero-a-caracter-3 k) es el k-ésimo elemento de la lista de los
;;; caracteres. Por ejemplo,
;;;    (entero-a-caracter-3 0)   ==  #\0
;;;    (entero-a-caracter-3 1)   ==  #\1
;;;    (entero-a-caracter-3 9)   ==  #\9
;;;    (entero-a-caracter-3 10)  ==  #\A
;;;    (entero-a-caracter-3 11)  ==  #\B
;;;    (entero-a-caracter-3 35)  ==  #\Z
(defun entero-a-caracter-3 (k)
  (char *caracteres* k))

(defun decimal-a-factoradico-3 (n)
  (labels ((aux (m xs)
             (if (= m 0)
                 (mapcar (constantly #\0) xs)
                 (let ((x (first xs)))
                   (cons (entero-a-caracter-3 (truncate m x))
                         (aux (mod m x) (rest xs)))))))
    (coerce (aux n (reverse (s-take-while (lambda (x) (<= x n))
                                          (factoriales))))
            'string)))

;;; Verificación
;;; ============

(test factoradico-a-decimal
  (mapc (lambda (factoradico-a-decimal)
          (is (= (funcall factoradico-a-decimal "341010") 463))
          (is (= (funcall factoradico-a-decimal "2441000") 2022))
          (is (= (funcall factoradico-a-decimal "A0000000000") 36288000)))
        '(factoradico-a-decimal-1
          factoradico-a-decimal-2
          factoradico-a-decimal-3)))

(test decimal-a-factoradico
  (mapc (lambda (decimal-a-factoradico)
          (is (equal (funcall decimal-a-factoradico 463) "341010"))
          (is (equal (funcall decimal-a-factoradico 2022) "2441000"))
          (is (equal (funcall decimal-a-factoradico 36288000) "A0000000000")))
        '(decimal-a-factoradico-1
          decimal-a-factoradico-2
          decimal-a-factoradico-3
          )))

(defun verifica ()
  (run 'factoradico-a-decimal)
  (run 'decimal-a-factoradico))

;;; La verificación es
;;;    (verifica)
;;;
;;;    Running test FACTORADICO-A-DECIMAL ........


;;; Propiedad de inverso
;;; ====================

;;; La propiedad es
(test factoradico
  (for-all ((n (gen-integer :min 0)))
    (is (= (factoradico-a-decimal-1 (decimal-a-factoradico-1 n)) n))
    (is (= (factoradico-a-decimal-2 (decimal-a-factoradico-2 n)) n))
    (is (= (factoradico-a-decimal-3 (decimal-a-factoradico-3 n)) n))))

(defun comprueba ()
  (run 'factoradico))

;;; La comprobación es
;;;    > (comprueba)
;;;
;;;    Running test SUMA-CADENAS-EQUIV ...