Для того чтобы генерить осознаные названия или имена неплохо бы иметь исходные данные. Я собрал базу данных с прилагательными, именами собственными и фамилиями. Скачать базу данных
Если подключить эту базу, то из нее можно вытащить данные то можно их потом использовать для генерации.
Но давайте сначала просто взглянем как добавить запись в таблицу. Для этого существует команда INSERT
. Синтаксис такой
INSERT INTO название_таблицы(поле1, поле2, ...)
VALUES(значение_для_поля1, значение_для_поля2, ...)
если у таблицы есть суррогатный ключ id, то его при добавлении записи указывать не надо. СУБД сгенерирует значение автоматически.
Так, только чет у меня не очень интересно, добавлю-ка я еще поле с породой кота
А теперь попробую добавить кошку:
INSERT INTO Cats(name)
VALUES('Царапка')
запускаем
Обработано строк: 1 – означает что добавилась одна запись.
Теперь запросим данные:
так как мы не указали значение для породы, то туда было подставлено значение NULL. Кстати еще смотрите у меня id идет не по порядку. То есть 1, 2, а потом раз и 4 – это потому что я добавил запись, а потом решил ее удалить. Но идентификаторы в базе всегда возрастают и поэтому удаленную запись с id = 3
ни как на увеличение значение идентификатора не повлияло.
Попробуем теперь добавить сразу несколько полей
INSERT INTO Cats(name, breed)
VALUES('Клеопатра', 'Египетская Мау')
глянем данные
во теперь все заполнено.
Вообще в жизни как правило вставки просто значений не достаточно и хочется еще и получить идентификатор ново-добавленной записи, чтобы с этой записью потом еще что-нибудь и сделать.
Для того команду INSERT
можно усложнить и добавить в него инструкцию OUTPUT в которой через специальный объект inserted
можно получить доступ к только что вставленной строке, например, мы можем вывести все поля сразу после добавления если напишем такой запрос:
INSERT INTO Cats(name, breed)
OUTPUT inserted.id, inserted.name, inserted.breed
VALUES('Тимофей', 'Сибирский')
то получим в ответ результат как будто мы делали запрос SELECT:
Вообще вставка значений по одно записи не самая эффективная операция, намного эффективнее вставлять сразу несколько записей, просто добавляя список кортежей через запятую
INSERT INTO Cats(name, breed)
OUTPUT inserted.id, inserted.name, inserted.breed
VALUES('Забияка', 'Сиамская'),
('Бусинка', 'Бенгальская'),
('Мята', 'Мейн-Кун')
ну и благодаря OUTPUT
в ответ получим сразу все новодобавленные записи
Делаем интерфейс для генератора кошек
Так как у нас будет несколько всяких генераторов. То я буду использовать компоненту с табами, чтобы можно было быстро между разными генераторами переключаться.
Но, чтобы мои поля для выполнения произвольных запросов не пропали я их перетяну прям во вкладку
получится вот так:
теперь накидаем компоненты
Идея у меня такая. В самом верху у меня TrackBar по которому я буду определять процентное соотношение кошек или котов. Если слайдер находится в крайнем левом положении, то буду только Коты, если в крайнем правом – только Кошки, если по центру, то примерно одинаковое количество.
Затем идут слайдеры пород. У меня будет восемь пород
Каждый слайдер будет определять долю котяр той или иной породы. Максимальное значение слайдера – 10.
При запуске программы я буду складывать выбранные значения всех слайдеров. А вероятность того что я сгенерю ту или иную породу будет определятся по формуле.
Значения слайдера пароды / Сумма значений слайдеров всех парод
Плюс таким образом если слайдер пароды находится в крайнем левом положении, то порода генериться не будет.
Пробуем сгенерить одну запись
У нас самое сложное в нашей задачи будет это формировать имена, для этого мы как раз будем использовать ту базу которую кидал выше.
Но прежде чем озаботиться вопросом создания имен. Попробуем погенерить хотя бы что-нибудь.
Тыкаем два раза на кнопку запустить:
private void btnGenerateCats_Click(object sender, EventArgs e)
{
// пока пусто
}
Так как мы часто будем работать с соединением, а вы в прошлой задачке уже написали код для соединения с базой данных, то возьмите этот код и создайте метод следующего вида
и теперь можно использовать этот метод GetConnection, пишем в кнопке:
private void btnGenerateCats_Click(object sender, EventArgs e)
{
using (var connection = GetConnection())
{
connection.Open();
SqlCommand command = new("INSERT INTO Cats(name, breed) VALUES('Просто кот', 'Уличный')", connection);
command.ExecuteNonQuery(); // выполняем запрос, без необходимости получить что-нибудь в ответ
}
}
вообще, когда запросы становятся длинные то их как правило выносят в отдельный файл, либо как вариант, можно добавлять @ строке перед кавычкой и тогда запрос можно будет писать в несколько строк.
Это может выглядеть сначала жутковато, но такой код проще читать чем однострочник с запросом:
private void btnGenerateCats_Click(object sender, EventArgs e)
{
using (var connection = GetConnection())
{
connection.Open();
SqlCommand command = new(@"
INSERT INTO Cats(name, breed)
VALUES('Просто кот', 'Уличный')"
, connection);
command.ExecuteNonQuery();
}
}
давайте теперь попробуем запустить приложение тыкнуть на кнопку и глянуть что произойдет.
то есть наш запрос реально дошел до базы =О
Заполняем поле породы
Для выбора породы у нас будет немного хитрая логика. Для выбора значения мы будет использовать класс Random.
Выбирать будем по такому принципу.
- есть шкала от 0 до 1
- Каждая парода попадает в некоторый промежуток на это шкале. Например бенгальская в промежутке от 0.25 до 0.5.
- Мы бросаем с помощью Random случайное число от 0 до 1, и если число попадет в промежуток от 0.25 до 0.5 то будем использовать “Бенагльскую” породу.
Вот пример шкалы:
public partial class Form1 : Form
{
// добавляем структуру под промежуток
struct Range {
public double min;
public double max;
public bool CheckContains(double value)
{
return min <= value && value <= max;
}
}
public Form1()
{
// ...
}
// ...
private void btnGenerateCats_Click(object sender, EventArgs e)
{
using (var connection = GetConnection())
{
connection.Open();
// создаем словарик в котором сопоставляем название породы с трекбаром на форме
var breeds = new Dictionary<string, TrackBar>
{
["Сибирская"] = tbSiberian,
["Бенгальская"] = tbBengalskaya,
["Сиамская"] = tbSiamskaya,
["Мейн-кун"] = tbMainKun,
["Египетская"] = tbEgipetskaya,
["Персидская"] = tbPersidkaya,
["Манчкин"] = tbManchkin,
["Шоколадный йорк"] = tbShocoladniyYork,
};
// рассчитываем суммарное значение на трекбарах
int totalBreed = breeds.Values.Sum(tb => tb.Value);
// создаем словарик для сопоставления порода -- промежуток
Dictionary<string, Range> breedsProbablity = new();
double previousProbabilty = 0;
// ну и последовательно заполняем
foreach(var item in breeds)
{
double k = (double)breeds[item.Key].Value / totalBreed;
if (k > 0)
{
breedsProbablity[item.Key] = new Range
{
min = previousProbabilty,
max = previousProbabilty + k
};
previousProbabilty += k;
}
}
// создаем генератор случайных чисел
Random rnd = new();
// и начинаем в цикле создавать записи в БД,
// в цикле повторяем по количеству записей указанных в поле udCount
for (var i = 0; i<udCount.Value; ++i)
{
string breed = null;
var k = rnd.NextDouble(); // бросаем случайное число от 0 до 1
// проходим по шкале вероятностей
foreach(var item in breedsProbablity)
{
// если значение попадает в промежуток
if (item.Value.CheckContains(k))
{
breed = item.Key; // то фиксируем значение породы
break; // и выходим из цикла
}
}
if (breed == null)
continue;
// передаем породу в breed
SqlCommand command = new($@"
INSERT INTO Cats(name, breed)
VALUES('Просто кот', '{breed}')"
, connection);
command.ExecuteNonQuery();
}
}
}
}
попробуем запустить
Сейчас еще не очень хорошо получается, не совсем понятно сработало ли что или нет. Давайте сделаем, чтобы наш прогресс бар показывал прогресс по добавлению. Для этого добавим изменение значений ProgressBar по мере добавления новых записей ну и естественно надо сбрасывать в начале
private void btnGenerateCats_Click(object sender, EventArgs e)
{
using (var connection = GetConnection())
{
connection.Open();
pbCatGenerator.Value = 0; // сбрасываем значение
pbCatGenerator.Maximum = (int)udCount.Value; // ставим максимальное
var breeds = new Dictionary<string, TrackBar>
{
// ...
};
// ...
for (var i = 0; i < udCount.Value; ++i)
{
// ...
pbCatGenerator.Value++; // увеличиваем значение на 1
}
}
}
пробуем:
ну вот в принципе какой-то прогресс теперь виден. На самом деле у нас сейчас не самый грамотный способ информирования о прогрессе так как у нас происходит блокировка интерфейса. Так что мы как-нибудь попозже рассмотрим улучшенный способ, которые позволит не блокировать интерфейс.
Подключаем основу для имен
И так подходим к самом интересному к генерации имен. Я планирую генерить имя по схеме
Прилагательное1 Прилагательное2 Имя
причем я хочу, чтобы часть имен было в женском роде, а часть в мужском. То есть таким образом, чтобы прилагательные были согласованы по роду надо как-то сопоставлять.
Давайте глянем что у нас есть в базе Скачать базу данных которую вы возможно уже скачали подключили выше. Первая таблица которая будет нас интересовать это adjectives
то есть прилагательные
Столбцы следующие:
- IID – порядковый номер
- word – само прилагательное
- code – идентификатор
- code_parent – идентификатор производного прилагательного
- type_sub – склоняется ли форма, если не склоняется там будет
неизм
- plural – множественное ли число
- gender – род прилагательного
- wcase – падеж
- comp – является ли сравнительным
- short – является ли сокращеной формы, типа
вкусен
для слова вкусный
Мне надо вытащить прилагательные именительного падежа, мужского и женского рода не находящихся в сокращённой форме и не находящиеся в форме множественного числа.
Я думаю вы уже сможете сами написать такой запрос, получилось у меня 61751 строк в ответ
теперь сохраним эти данные в файлик
в папку с проектом
получится как-то так
теперь делаем тоже самое, но уже с именами. Тут только важно чтобы поле пол выдавало такие же значения, как и в прилагательных, чтобы было проще сопоставлять, так что не забываем в запрос запихать конструкцию CASE
и тоже сохраняем в папку с проектом, получится так
у каждого файлика ставим свойство
теперь давайте добавим структуру под строку в наших файлах и два поля под имена и прилагательные
Ну и реализуем загрузку прилагательных в конструкторе формы
можете попробовать запустить программу. Если все ок, значит вы все сделали правильно.
Генерируем имя кота
Чтобы быть более гибкими, давайте создадим метод GenreateCatName
и пусть он нам выдает имя, причем пусть он учитывает род прилагательных.
теперь давайте подключим этот метод в методе генератора
private void btnGenerateCats_Click(object sender, EventArgs e)
{
using (var connection = GetConnection())
{
//...
// и начинаем в цикле создавать записи в БД,
// в цикле повторяем по количеству записей указаных в поле udCount
for (var i = 0; i < udCount.Value; ++i)
{
// ...
if (breed == null)
continue;
// сначала определяем род который будем использовать
var isMale = ((double)tbCatMaleFemale.Value / 100) < rnd.NextDouble();
var gender = isMale ? "муж" : "жен";
var name = GenerateCatName(gender); // ну и генерим имя на основании рода
// ну и сюда имя теперь запихиваем через '{name}'
SqlCommand command = new($@"
INSERT INTO Cats(name, breed)
VALUES('{name}', '{breed}')"
, connection);
command.ExecuteNonQuery();
pbCatGenerator.Value++;
}
}
}
запускаем
Вроде ничего не упало, глянем что там негенерилось
а что вполне убедительные имена, плюс и почитать забавно =)
Единственное мне не нравится, что прилагательные с маленькой буквы пишутся.
Давайте сделаем функцию которая в любом слове делает букву большой. В C# есть механизм расширений который позволяет добавть метод к любому уже существующему классу. Давайте добавим метод к класс string
и теперь идем в метод генерации имен, и в самом конце добавляем:
private string GenerateCatName(string gender)
{
var genderAdjectives = this.adjectives.Where(x => x.gender == gender).ToList();
// ...
// старый return заменяем на новый
//return $"{firstAdjective.word} {secondAdjective.word} {name.word}";
// после каждого word добавляем Capitalize
return $"{firstAdjective.word.Capitalize()} {secondAdjective.word.Capitalize()} {name.word}";
}
запускаем еще раз
смотрим:
вот теперь все симпотишно =)