Мне 16 и я школьник. Не так уж давно меня посетила идея написать бота… Нет, не PHP-поделие, уныло висящее на никому не нужном сайте. И даже не бесполезный ответчик на фразы типа "! Погода".
Бот задумывался для развлечения как «говорилка» на десктоп. Ужасно, правда? Но мне хочется узнать свои ошибки, ведь я ни разу не показывал свой код кому-либо, в школе только паскаль. Итак, следуя ненавидимому некоторыми чистому структурному подходу я написал первоначальный вариант на C++.
Задумка такова. Бот берет фразу из консоли, вычленяет слова, проверяет каждое по словарю, расположенному в файле «Memory.txt», и возвращает найденный ответ к каждому слову; если ни на одно слово ответ не найден, то он возвращает оговоренную фразу (не принципиально).
Словарь в файле «Memory.txt» структурирован простейшим образом:
слово=ответ
Пример:
яблоко=яблоки вкусные
Bot.h- заголовочный файл, о нем позже. Основные функции будут располагаться в файле Bot.cpp:
Определим имя для словаря в этом же файле:
«Основа основ» бота — это функция, выделяющая слова из одной строки в массив строк и возвращающая указатель на массив.
Следующая функция получает строку для поиска и ищет ее по словарю, если ответ не найден, возвращает пустую строку "". Хочу обратить внимание на то, что если слово будет найдено внутри другого слова в файле ассоциаций, то ответ будет засчитан.
Теперь можно подумать о необязательное мелочи- ужасной пародии на обучение- добавление новых ассоциаций при нахождении в строке символа '-'.
Пример:
Зло- это добро наоборот
В словарь идет:
Зло= это добро наоборот
Не забываем о том, что при нахождении слова внутри другого, ответ засчитывается, так что результат может быть интересным.
В моем представлении структурный подход не отменяет инкапсуляцию, поэтому мы добавим анонимное пространство имен — для банальной инкапсуляции включающее в себя все предыдущие функции.
Таким образом предыдущие функции уже не будут доступны при подключении заголовочного файла «Bot.h». Позволю себе сослаться на гуру:
Вот, все вместе:
И наконец, функция для связи с внешним миром, разумеется вне пространства имен, но в той же единице компиляции. Принимает фразу, вычленяет слова, получает ассоциации по каждому, при нахождении символа равно добавляет новую ассоциацию при помощи предыдущих функций:
А теперь подведем итог — файл «Bot.cpp» полностью:
Вот и все с файлом «Bot.cpp» мы закончили, теперь быстро набросаем заголовочный файл «Bot.h»:
С основной частью мы закончили, дело за малостью — функцией main(). Она у нас будет располагаться в файле Cbot.cpp. Cbot — звучит невероятно оригинально, ведь правда?
Все, бот готов, собираем вместе, получаем Cbot.exe, сохраняем файл Memory.txt в кодировке OEM 866 и кладем в одну директорию программой. Ссылка на сборку: spaces.ru/files/?r=main/view&Read=58688510
Ожидаю конструктивный поток критики показывающий на очевидные ошибки в коде.
Бот задумывался для развлечения как «говорилка» на десктоп. Ужасно, правда? Но мне хочется узнать свои ошибки, ведь я ни разу не показывал свой код кому-либо, в школе только паскаль. Итак, следуя ненавидимому некоторыми чистому структурному подходу я написал первоначальный вариант на C++.
Задумка такова. Бот берет фразу из консоли, вычленяет слова, проверяет каждое по словарю, расположенному в файле «Memory.txt», и возвращает найденный ответ к каждому слову; если ни на одно слово ответ не найден, то он возвращает оговоренную фразу (не принципиально).
Словарь в файле «Memory.txt» структурирован простейшим образом:
слово=ответ
Пример:
яблоко=яблоки вкусные
Bot.h- заголовочный файл, о нем позже. Основные функции будут располагаться в файле Bot.cpp:
/**
Даниил Демидко, 2015
Основные функции Cbot
*/
#include"Bot.h"
Определим имя для словаря в этом же файле:
///Имя тектового файла- памяти бота
const char *const MemoryPath="Memory.txt";
«Основа основ» бота — это функция, выделяющая слова из одной строки в массив строк и возвращающая указатель на массив.
const std::string *const GetWords(const std::string &Word)
///Количество возвращаемых слов в массиве-глобальная переменная, надеюсь всем понятно почему...
int MaxIndex=0;
///Функция выделяет слова из строки
const std::string *const GetWords(const std::string &Word)
{
///Резервируется массив в куче на 256 слов
std::string *const PtrWords=new std::string[256];
///Сбрасываем предидущее значение
MaxIndex=0;
///Фиксирует наличие искомого символа в предидущих циклах
bool Fix=false;
///Последний символ- служебный, поэтому не учитыватся
for(int i=0; i<Word.size(); ++i)
{
///Символы- разделители слов
if(Word[i]==' '||Word[i]=='.'||Word[i]==','||Word[i]=='!'||Word[i]=='?'||Word[i]=='='||Word[i]=='/')
{
///При нахождении разделителя, фиксируем это и пропускаем один цикл
Fix=true;
continue;
}
///Если в предидущих циклах зафиксирован разделитель, то переходим на одну ячейку
if(Fix)
{
Fix=false;
++MaxIndex;
}
PtrWords[MaxIndex]+=Word[i];
}
return PtrWords;
}
Следующая функция получает строку для поиска и ищет ее по словарю, если ответ не найден, возвращает пустую строку "". Хочу обратить внимание на то, что если слово будет найдено внутри другого слова в файле ассоциаций, то ответ будет засчитан.
const std::string GetAssociation(const std::string &Word)
///Функция возвращает ассоциацию
const std::string GetAssociation(const std::string &Word)
{
std::ifstream Memory(MemoryPath, std::ios::in);
if(!Memory)
{
std::ofstream NewMemory(MemoryPath);
NewMemory.close();
Memory.open(MemoryPath);
return "";
}
while(!Memory.eof())
{
std::string Buffer="";
std::getline(Memory, Buffer);
if(Buffer.find(Word)!=-1)
{
std::string Result[2];
for(int i=0, Index=0; i<Buffer.size(); ++i)
{
if(Buffer[i]=='=')
{
///Символы после второго знака '=' включительно- игнорируются
if(Index==1)
{
break;
}
++Index;
continue;
}
Result[Index]+=Buffer[i];
}
if(Result[0].find(Word)!=-1)
{
Memory.close();
return Result[1];
}
}
}
Memory.close();
return "";
}
Теперь можно подумать о необязательное мелочи- ужасной пародии на обучение- добавление новых ассоциаций при нахождении в строке символа '-'.
Пример:
Зло- это добро наоборот
В словарь идет:
Зло= это добро наоборот
Не забываем о том, что при нахождении слова внутри другого, ответ засчитывается, так что результат может быть интересным.
void PutAssociation(const std::string &Left, const std::string &Right)
///Функция добавляет ассоциацию
void PutAssociation(const std::string &Left, const std::string &Right)
{
std::ofstream Memory(MemoryPath, std::ios::app);
Memory<<Left<<'='<<Right<<std::endl;
Memory.close();
}
В моем представлении структурный подход не отменяет инкапсуляцию, поэтому мы добавим анонимное пространство имен — для банальной инкапсуляции включающее в себя все предыдущие функции.
Таким образом предыдущие функции уже не будут доступны при подключении заголовочного файла «Bot.h». Позволю себе сослаться на гуру:
Это самый передовой способ избежать объявления функций и переменных со статическим связыванием.
Доступ может быть осуществлен только в рамках единицы
трансляции (т. е. в полученном после предварительной обработки файле),
в которой они находятся, точно так же, как к статическим переменным.
Стивен С. Дьюрхест, «C++. Священные знания»
Вот, все вместе:
namespace
namespace
{
///Имя тектового файла- памяти бота
const char *const MemoryPath="Memory.txt";
///Количество возвращаемых слов в массиве
int MaxIndex=0;
///Функция выделяет слова из строки
const std::string *const GetWords(const std::string &Word)
{
///Резервируется массив на 256 слов
std::string *const PtrWords=new std::string[256];
///Сбрасываем предидущее значение
MaxIndex=0;
///Фиксирует наличие искомого символа в предидущих циклах
bool Fix=false;
///Последний символ- служебный, поэтому не учитыватся
for(int i=0; i<Word.size(); ++i)
{
///Символы- разделители слов
if(Word[i]==' '||Word[i]=='.'||Word[i]==','||Word[i]=='!'||Word[i]=='?'||Word[i]=='='||Word[i]=='/')
{
///При нахождении разделителя, фиксируем это и пропускаем один цикл
Fix=true;
continue;
}
///Если в предидущих циклах зафиксирован разделитель, то переходим на одну ячейку
if(Fix)
{
Fix=false;
++MaxIndex;
}
PtrWords[MaxIndex]+=Word[i];
}
return PtrWords;
}
///Функция добавляет ассоциацию
void PutAssociation(const std::string &Left, const std::string &Right)
{
std::ofstream Memory(MemoryPath, std::ios::app);
Memory<<Left<<'='<<Right<<std::endl;
Memory.close();
}
}
И наконец, функция для связи с внешним миром, разумеется вне пространства имен, но в той же единице компиляции. Принимает фразу, вычленяет слова, получает ассоциации по каждому, при нахождении символа равно добавляет новую ассоциацию при помощи предыдущих функций:
const std::string GetFullAssociation(const std::string &Word)
///Возвращает ассоциации по всем словам фразы
const std::string GetFullAssociation(const std::string &Word)
{
const std::string *const Words=GetWords(Word);
std::string Result="";
for(int i=0; i<=MaxIndex; ++i)
{
const std::string Buffer=GetAssociation(Words[i]);
if(Buffer!="")
{
Result+=Buffer+' ';
}
}
delete[] Words;
if(Word.find('-')!=-1)
{
std::string NewAssociations[2];
for(int i=0, Index=0; i<Word.size(); ++i)
{
if(Word[i]=='-')
{
if(Index==1)
{
break;
}
++Index;
continue;
}
if(Word[i]=='=')
{
continue;
}
NewAssociations[Index]+=Word[i];
}
PutAssociation(NewAssociations[0], NewAssociations[1]);
}
return Result;
}
А теперь подведем итог — файл «Bot.cpp» полностью:
Bot.cpp
/**
Даниил Демидко, 2015
Основные функции Cbot
*/
#include"Bot.h"
///Анонимное пространство имен инкапсулирует функции за пределами этой единицы компиляции
namespace
{
///Имя тектового файла- памяти бота
const char *const MemoryPath="Memory.txt";
///Количество возвращаемых слов в массиве
int MaxIndex=0;
///Функция выделяет слова из строки
const std::string *const GetWords(const std::string &Word)
{
///Резервируется массив на 256 слов
std::string *const PtrWords=new std::string[256];
///Сбрасываем предидущее значение
MaxIndex=0;
///Фиксирует наличие искомого символа в предидущих циклах
bool Fix=false;
///Последний символ- служебный, поэтому не учитыватся
for(int i=0; i<Word.size(); ++i)
{
///Символы- разделители слов
if(Word[i]==' '||Word[i]=='.'||Word[i]==','||Word[i]=='!'||Word[i]=='?'||Word[i]=='='||Word[i]=='/')
{
///При нахождении разделителя, фиксируем это и пропускаем один цикл
Fix=true;
continue;
}
///Если в предидущих циклах зафиксирован разделитель, то переходим на одну ячейку
if(Fix)
{
Fix=false;
++MaxIndex;
}
PtrWords[MaxIndex]+=Word[i];
}
return PtrWords;
}
///Функция возвращает ассоциацию
const std::string GetAssociation(const std::string &Word)
{
std::ifstream Memory(MemoryPath, std::ios::in);
if(!Memory)
{
std::ofstream NewMemory(MemoryPath);
NewMemory.close();
Memory.open(MemoryPath);
return "";
}
while(!Memory.eof())
{
std::string Buffer="";
std::getline(Memory, Buffer);
if(Buffer.find(Word)!=-1)
{
std::string Result[2];
for(int i=0, Index=0; i<Buffer.size(); ++i)
{
if(Buffer[i]=='=')
{
///Символы после второго знака '=' включительно- игнорируются
if(Index==1)
{
break;
}
++Index;
continue;
}
Result[Index]+=Buffer[i];
}
if(Result[0].find(Word)!=-1)
{
Memory.close();
return Result[1];
}
}
}
Memory.close();
return "";
}
///Функция добавляет ассоциацию
void PutAssociation(const std::string &Left, const std::string &Right)
{
std::ofstream Memory(MemoryPath, std::ios::app);
Memory<<Left<<'='<<Right<<std::endl;
Memory.close();
}
}
///Возвращает ассоциации по всем словам фразы
const std::string GetFullAssociation(const std::string &Word)
{
const std::string *const Words=GetWords(Word);
std::string Result="";
for(int i=0; i<=MaxIndex; ++i)
{
const std::string Buffer=GetAssociation(Words[i]);
if(Buffer!="")
{
Result+=Buffer+' ';
}
}
delete[] Words;
if(Word.find('-')!=-1)
{
std::string NewAssociations[2];
for(int i=0, Index=0; i<Word.size(); ++i)
{
if(Word[i]=='-')
{
if(Index==1)
{
break;
}
++Index;
continue;
}
if(Word[i]=='=')
{
continue;
}
NewAssociations[Index]+=Word[i];
}
PutAssociation(NewAssociations[0], NewAssociations[1]);
}
return Result;
}
Вот и все с файлом «Bot.cpp» мы закончили, теперь быстро набросаем заголовочный файл «Bot.h»:
Bot.h
#ifndef BOT
#define BOT
///На всякий случай проверяем, подключен ли уже iostream
#ifndef _GLIBCXX_IOSTREAM
#include<iostream>
#endif //_GLIBCXX_IOSTREAM
///Проверяем fstream
#ifndef _GLIBCXX_FSTREAM
#include<fstream>
#endif //_GLIBCXX_FSTREAM
///Наша уже описанная функция для связи с миром
extern const std::string GetFullAssociation(const std::string&);
#endif //BOT
С основной частью мы закончили, дело за малостью — функцией main(). Она у нас будет располагаться в файле Cbot.cpp. Cbot — звучит невероятно оригинально, ведь правда?
Cbot.cpp
#include"Bot.h"
int main()
{
///Файл кодировки 866 OEM (русская), файл "Memory.txt" должен быть в ней же
setlocale(LC_ALL, ".866");
std::wcout<<"Cbot 2.0\nАвтор: Даниил Демидко\nE-Mail: DDemidko1@gmail.com"<<std::endl;
while(true)
{
std::wcout<<"Я: ";
std::string Buffer="";
std::getline(std::cin, Buffer);
const std::string Association=GetFullAssociation(Buffer);
/**
Почему такая конструкция? Казалось, ведь можно было бы проще-
if(Association=="")
{
Association="Bot: Не распознана ключевая последовательность!";
}
std::cout<<Association<<std::endl;
Но мы не должны забывать, что работаем с 866 OEM-
с ней не получится корректно присвоить объекту std::string строку кириллических символов прямо из кода-
такое возможно (с 866 OEM) только при вводе из консоли.
*/
if(Association=="")
{
std::wcout<<"Bot: Не распознана ключевая последовательность!"<<std::endl;
}
else
{
std::cout<<"Bot: "<<Association<<std::endl;
}
}
}
Все, бот готов, собираем вместе, получаем Cbot.exe, сохраняем файл Memory.txt в кодировке OEM 866 и кладем в одну директорию программой. Ссылка на сборку: spaces.ru/files/?r=main/view&Read=58688510
Ожидаю конструктивный поток критики показывающий на очевидные ошибки в коде.