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[10, 6]
{
{"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 i = 0; i <= 20; i++)
{
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
r = new Random(rSeed.Next(1, 1000));
//y obtenemos un índice al azar
//para el array multidimensional SurnameRandomTypes
int rndIndex = r.Next(0, 9);
//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 i = 0; i < 6; i++)
{
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(0, 1000));
Digram nuDigram = new Digram();
do
{
//Obtenemos un número aleatorio dentro del rango
//TOTAL de TODOS los digramas
int freq = r.Next(0, 5548); //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(0, 1000));
Letter nuLetter = new Letter();
do
{
//Obtenemos un número aleatorio dentro del rango
//TOTAL de TODAS las letras
int freq = r.Next(0, 10025); //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 =
{
1231, 403, 162, 959, 365, 161, 805, 320, 93,
794, 310, 52, 719, 229, 20, 718, 228, 20,
659, 225, 10, 603, 203, 9, 514, 188
};
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 i = 0; i < _letterFreq.Length; i++)
{
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 =
{
315, 111, 75, 56, 251, 110, 72, 56,
172, 107, 72, 55, 169, 106, 71, 55,
154, 101, 67, 55, 148, 96, 65, 54,
145, 94, 64, 54, 145, 94, 64, 53,
131, 88, 63, 53, 128, 84, 63, 53,
124, 84, 59, 51, 121, 80, 58, 49,
120, 77, 57, 48, 118, 77, 57, 48,
113, 75, 57, 47
};
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 i = 0; i < _digramFreq.Length; i++)
{
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(0, 1)) &&
IsAVowel(_digramValue[i].Substring(1, 1)))
nuDigram.Type = DigramType.TwoVowels;
else if (!IsAVowel(_digramValue[i].Substring(0, 1)) &&
IsAVowel(_digramValue[i].Substring(1, 1)))
nuDigram.Type = DigramType.ConsonantAndVowel;
else if (IsAVowel(_digramValue[i].Substring(0, 1)) &&
!IsAVowel(_digramValue[i].Substring(1, 1)))
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