Apellidos aleatorios (y falsos)

Published 103 weeks, 3 days ago
Fri Jan 12 2007
Respondiendo al desafío planteado por Jon Galloway en su excelente blog, aquí está mi solución para generar apellidos falsos y aleatorios. El código está repleto de comentarios, eso sí en inglés de momento, los traduciré en cuanto pueda. Estás invitado, por supuesto, a hacer preguntas, críticas, o incluso mejor un buen refactorizado! =)

class Program
{
    
//Un arraylist que contiene todos los posibles digramas (ver más abajo)
    //(Nota: digram en inglés es la combinación de dos caracteres, no sé si en español será sílaba)
    private static ArrayList AllDigrams = new ArrayList();
    
//Un arraylist que contiene todas las letras posibles (ver más abajo)
    
private static ArrayList AllLetters = new ArrayList();
    
//Una semilla aleatoria para el generador aleatorio principal
    
private static Random rSeed = new Random()
    
//Un array multidimensional que especifica la estructura principal de un apellido. 
    // "A" representa una vocal, "B" una consonante
    //"AB representa un digrama de vocal + consonante, etc...
    //Estas combinaciones las he hecho a mano, puedes añadir, modificar o quitar 
    //combinaciones para ajustar el tipo de apellidos que se obtengan
    
private static string[,] SurnameRandomTypes = new string[106]
            {
                {
"A",  "BA""B",  "A",  "BA"""},
                {
"B",  "A",  "BA""",   "",   ""},
                {
"A",  "BA""A",  "B",  "A",  "BB"},
                {
"A",  "B",  "B",  "A",  "BA"""},
                {
"A",  "BB""AB""A",  "",   ""},
                {
"BA""B""B",  "A",  "B",  "A"},
                {
"B",  "AB""A",  "BB""A",  "AB"},
                {
"B",  "AB""B",  "A",  "BB""AB"},
                {
"A",  "BB""A",  "B",  "AB"""},
                {
"AB""AA""B",  "A",  "B",  "A"},
            }
;

    static void Main()
    {
        
bool exit = false;
        
//Cargamos TODOS los posibles digrams en memoria, 
        //en el arraylist AllDigrams
        
LoadDigrams();
        
//Lo mismo para las letras posibles, 
        //en el arraylist AllLetters
        
LoadLetters();
        do
        
{
            
for (int 0i <20i++)
            {
                Console.WriteLine(
"Apellido Aleatorio:    {0}"
                    GenerateRandomSurname())
;
            
}
            Console.WriteLine(
"Pulsa ENTER para generar otro lote, X para salir");
            string 
sInput Console.ReadLine();
            if 
(sInput.ToString() == "x" || sInput.ToString() == "X")
                exit 
= true;
        
while (exit == false);
        
Console.ReadLine();
    
}

    
/// <summary>
    /// Éste método genera un apellido aleatorio y lo devuelve.
    /// </summary>
    /// <returns></returns>
    
static string GenerateRandomSurname()
    {
        
string sRet "";
        
Random r;
        
//Nueva semilla aleatoria para el generador de números
        
= new Random(rSeed.Next(11000));
        
//y obtenemos un índice al azar
        //para el array multidimensional SurnameRandomTypes
        
int rndIndex r.Next(09);
        
//Entonces leemos las distintas "columnas" de  
        //esa "fila" y decidimos si necesitamos una vocal, 
        //una consonante, un digrama vocal-consonante, 
        //o lo que sea
        
for (int 0i < 6i++)
        {
            
switch(SurnameRandomTypes[rndIndex, i])
            {
                
case "AA":
                    sRet +
getDigram(DigramType.TwoVowels);
                    break;
                case 
"AB":
                    sRet +
getDigram(DigramType.VowelAndConsonant);
                    break;
                case 
"BA":
                    sRet +
getDigram(DigramType.ConsonantAndVowel);
                    break;
                case 
"BB":
                    sRet +
getDigram(DigramType.TwoConsonants);
                    break;
                case 
"A":
                    sRet +
getLetter(LetterType.Vowel);
                    break;
                case 
"B":
                    sRet +
getLetter(LetterType.Consonant);
                    break;
                default
:
                    
break;
            
}
        }
        
//Y devolvemos el apellido generado 
        //convirtiendo a mayúsculas la primera letra
        
return sRet.Substring(0,1).ToUpper() + 
            sRet.Substring(
1);
    
}

    
#region Letters and Digrams
    
    
#region Structs and Enums
    
/// <summary>
    /// Con esta enumeración sabremos de qué 
    /// está compuesto el digrama
    /// </summary>
    
private enum DigramType
    { TwoVowels, VowelAndConsonant, ConsonantAndVowel, TwoConsonants }

    
/// <summary>
    /// Con esta enumeración sabremos si una letra es una
    /// vocal o una consonante
    /// </summary>
    
private enum LetterType { Vowel, Consonant }

    
/// <summary>
    /// Un objeto digrama. Además del propio digrama ("th", por ejemplo)
    /// guardamos el rango de frecuencia en el que el digrama aparece 
    /// en el lenguaje inglés y su tipo
    /// </summary>
    
private struct Digram 
    {
        
public int iniRange;        //El principio de su rango.
        
public int endRange;        //El final de su rango.
        
public string Value;        //Su valor
        
public DigramType Type;     //Su tipo, según la enumeración DigramType
    
}

    
/// <summary>
    /// Una letra. Además de la propia letra ("a", por ejemplo)
    /// guardamos el rango de frecuencia en la que aparece  
    /// en el lenguaje inglés y su tipo
    /// </summary>
    
private struct Letter
    {
        
public int iniRange;        //El principio de su rango.
        
public int endRange;        //El final de su rango.
        
public string Value;        //Su valor.
        
public LetterType Type;     //Su tipo, según la enumeración LetterType
    
}
    
#endregion

    
/// <summary>
    /// Este método devuelve el valor  
    /// de un digrama aleatorio del tipo especificado
    /// </summary>
    /// <param name="type">El tipo de digrama que queremos obtener</param>
    /// <returns>El VALOR del digrama ("th", por ejemplo)</returns>
    
static string getDigram(DigramType type)
    {
        Random r 
= new Random(rSeed.Next(01000));
        
Digram nuDigram = new Digram();
        do
        
{
            
//Obtenemos un número aleatorio dentro del rango
            //TOTAL de TODOS los digramas
            
int freq r.Next(05548)//Ver LoadDigrams()
            
foreach (Digram digram in AllDigrams)
            {
                
//Y si el número aleatorio está dentro del  
                //rango de frecuencia del digrama actual, 
                //tenemos un ganador!
                
if (freq >digram.iniRange && freq <digram.endRange) 
                        nuDigram 
digram;
            
}
            
//Pero sólo si es del tipo que necesitamos
        
while (nuDigram.Type !type);
        return 
nuDigram.Value;
    
}

    
/// <summary>
    /// Obtenemos el VALOR de una sola letra del tipo especificado
    /// </summary>
    /// <param name="type">El tipo de letra que queremos obtener</param>
    /// <returns>El VALOR de la letra</returns>
    
static string getLetter(LetterType type) 
    {
        Random r 
= new Random(rSeed.Next(01000));
        
Letter nuLetter = new Letter();
        do
        
{
            
//Obtenemos un número aleatorio dentro del rango 
            //TOTAL de TODAS las letras
            
int freq r.Next(010025)//Ver LoadLetters()
            
foreach (Letter letter in AllLetters)
            {
                
//Y si el número aleatorio
                //está dentro del rango de frecuencias de
                //la letra actual, tenemos un ganador!
                
if (freq >letter.iniRange && freq <letter.endRange)
                        nuLetter 
letter;
            
}
            
//Pero sólo si es del tipo que necesitamos  
        
while (nuLetter.Type !type);
        return 
nuLetter.Value;
    
}

    
/// <summary>
    /// Simple comprobación booleana para ver si una letra determinada es
    /// una vocal o una consonante
    /// </summary>
    /// <param name="checkLetter">La letra a comprobar</param>
    /// <returns>True si es una vocal, false en cualquier otro caso</returns>
    
static bool IsAVowel(string checkLetter)
    {
        
switch(checkLetter.ToLower())
        {
            
case "a":
            
case "e":
            
case "i":
            
case "o":
            
case "u":
                
return true;
            default
:
                
return false;
        
}
    }

    
/// <summary>
    /// Gracias del departamento de Informática de la Universidad de Bristol
    /// (y a Google) he encontrado una lista de todas las letras del
    /// alfabeto inglés, con su frecuencia de uso.
    /// Como Random maneja enteros, he multiplicado todos estos valores * 100
    /// La suma de todos ellos debería ser 10.000, lo que hace que cuando calculamos
    /// el rango de números debería ser 10.000 + la cantidad de letras posibles;  
    /// porque el rango de la primera letra es de 0 a 1.231, y el siguiente  
    /// rango de letra empezará en 1.232 (el fin del anterior rango +1)
    /// En el caso de letras solas esto es correcto, y los rangos van desde
    /// 0 a 10.025. 
    /// 
    /// Pero, no sé porqué, en el caso de los digramas el rango va  
    /// de 0 a 5.548. El porcentaje de frecuencia de todos los digramas disponibles,
    /// todos ellos multiplicados por 100 y sumados NO da 10.000 como debiera.
    /// ¿Alguien sabe porqué? A no ser que la lista de digramas que tengo NO esté completa.
    /// </summary>
    
static void LoadLetters()
    {
        
// http://www.cs.bris.ac.uk/Teaching/Resources/COMS30124/Labs/freq.html
        // Porcentaje de frecuencia de letras
        //
        //  E 12.31      L 4.03     B 1.62
        //  T  9.59      D 3.65     G 1.61
        //  A  8.05      C 3.20     V 0.93
        //  O  7.94      U 3.10     K 0.52
        //  N  7.19      P 2.29     Q 0.20
        //  I  7.18      F 2.28     X 0.20
        //  S  6.59      M 2.25     J 0.10
        //  R  6.03      W 2.03     Z 0.09
        //  H  5.14      Y 1.88      

        
string[] _letterValue 
        
{
            
"e""l""b""t""d""g""a""c""v",
            
"o""u""k""n""p""q""i""f""x",
            
"s""m""j""r""w""z""h""y"
        
};
        int
[] _letterFreq 
        
{
            
123140316295936516180532093
            
794310527192292071822820,
            
659225106032039514188
        
};
        int 
lastEndRange 0;
        
//Recorriendo los arrays rellenados con esos datos
        //guardamos una colección de objetos Letra llamada AllLetters, 
        //asignando a cada Letra su rango, valor y tipo
        
for (int 0i < _letterFreq.Lengthi++)
        {
            Letter nuLetter 
= new Letter();
            
nuLetter.iniRange lastEndRange;
            
nuLetter.endRange lastEndRange + _letterFreq[i];
            
lastEndRange nuLetter.endRange + 1;
            
nuLetter.Value _letterValue[i];
            
nuLetter.Type IsAVowel(_letterValue[i]) ? 
                LetterType.Vowel : LetterType.Consonant
;
            
AllLetters.Add(nuLetter);
        
}
    }
     
    
/// <summary>
    /// Ver el comentario de LoadLetters
    /// </summary>
    
static void LoadDigrams()
    {
        
// http://www.cs.bris.ac.uk/Teaching/Resources/COMS30124/Labs/freq.html
        // Digramas ingleses y su porcentaje de frecuencia de uso
        //  TH  3.15   TO  1.11   SA  0.75   MA  0.56 
        //  HE  2.51   NT  1.10   HI  0.72   TA  0.56
        //  AN  1.72   ED  1.07   LE  0.72   CE  0.55
        //  IN  1.69   IS  1.06   SO  0.71   IC  0.55
        //  ER  1.54   AR  1.01   AS  0.67   LL  0.55
        //  RE  1.48   OU  0.96   NO  0.65   NA  0.54
        //  ES  1.45   TE  0.94   NE  0.64   RO  0.54
        //  ON  1.45   OF  0.94   EC  0.64   OT  0.53
        //  EA  1.31   IT  0.88   IO  0.63   TT  0.53
        //  TI  1.28   HA  0.84   RT  0.63   VE  0.53
        //  AT  1.24   SE  0.84   CO  0.59   NS  0.51
        //  ST  1.21   ET  0.80   BE  0.58   UR  0.49
        //  EN  1.20   AL  0.77   DI  0.57   ME  0.48
        //  ND  1.18   RI  0.77   LI  0.57   WH  0.48
        //  OR  1.13   NG  0.75   RA  0.57   LY  0.47 

        
string[] _digramValue 
        
{   
            
"th""to""sa""ma""he""nt""hi""ta",
            
"an""ed""le""ce""in""is""so""ic",
            
"er""ar""as""ll""re""ou""no""na",
            
"es""te""ne""ro""on""of""ec""ot",
            
"ea""it""io""tt""ti""ha""rt""ve",
            
"at""se""co""ns""st""et""be""ur",
            
"en""al""di""me""nd""ri""li""wh",
            
"or""ng""ra""ly"
        
};
        int
[] _digramFreq 
        
{
            
31511175562511107256,
            
17210772551691067155
            
1541016755148966554
            
145946454145946453
            
131886353128846353
            
124845951121805849
            
120775748118775748
            
113755747
        
};
        int 
lastEndRange 0;
        
//Recorremos los arrays de datos para rellenar
        //la colección de objetos Digram AllDigrams
        //asignándole a cada uno su rango,
        //valor y tipo de digrama
        
for (int 0i < _digramFreq.Lengthi++)
        {
            Digram nuDigram 
= new Digram();
            
nuDigram.iniRange lastEndRange;
            
nuDigram.endRange lastEndRange + 
                _digramFreq[i]
;
            
lastEndRange nuDigram.endRange + 1;
            
nuDigram.Value _digramValue[i];
            if 
(IsAVowel(_digramValue[i].Substring(01)) && 
                IsAVowel(_digramValue[i].Substring(
11)))
                    nuDigram.Type 
DigramType.TwoVowels;
            else if 
(!IsAVowel(_digramValue[i].Substring(01)) && 
                IsAVowel(_digramValue[i].Substring(
11)))
                    nuDigram.Type 
DigramType.ConsonantAndVowel;
            else if 
(IsAVowel(_digramValue[i].Substring(01)) && 
                !IsAVowel(_digramValue[i].Substring(
11)))
                    nuDigram.Type 
DigramType.VowelAndConsonant;
            else 
nuDigram.Type DigramType.TwoConsonants;
            
AllDigrams.Add(nuDigram);
        
}
    }
#endregion
}

Colorized by: CarlosAg.CodeColorizer

Actualización 13/01/2007.- Traducidos los comentarios del código fuente al español. Éste es un ejemplo de los apellidos que se generan:
  • Andaso
  • Estecon
  • Ovloso
  • Eleasawh
  • Athene
  • Tertore
  • Setolyiis
  • Face
  • Lirsuco
  • Rofsender
Tuesday, January 16, 2007 8:18:15 AM (Romance Standard Time, UTC+01:00)
Me gustaría saber la frecuencia de letras en castellano :-)
espinete
Comments are closed.