Как разобрать текстовый файл и использовать файл ввода в конструкторах, чтобы создать контейнер объектов - PullRequest
0 голосов
/ 17 февраля 2019

У меня есть программа, которая построчно читает текстовый файл для имен и сохраняет эти имена как объекты, используя конструктор.Конструктор используется для создания вектора из всех имен.Тем не менее, моя проблема в том, что мне нужно, чтобы мои имена имели привязанные к ним атрибуты, у меня есть конструктор для атрибутов, но я понятия не имею, как разобрать текстовый файл, чтобы отделить имена от атрибутов, а затем как хранить атрибуты.с именами.

Мой код работает только для имен в файле, и я не могу просто использовать разделитель в этом случае, так как мне нужно искать «Имя», а затем атрибут атрибут атрибута.

Пример:

"Baron Samedi" Mage Magical Ranged

имя должно быть сохранено без включенных кавычек, а затем атрибуты должны быть построены в контейнере, который соответствует именам, чтобы при вызове.getAttackType для определенного имени (объекта) возвращает соответствующий тип.

#include <string>
#include <vector>
#include <iostream>
#include <fstream>
#include <exception>
#include <sstream>
#include <ctime>
#include <random>


enum class AttackType {
    MELEE,
    RANGE
};

enum class DamageType {
    MAGICAL,
    PHYSICAL
};

enum class AbilityType {
    Mage,
    Guardian,
    Warrior,
    Hunter,
    Assassin
};

struct EntityAttributes {

    AttackType attackType;
    DamageType damageType;
    AbilityType abilityType;
};

class Entity {
private:
    std::string name_;
    EntityAttributes attribs_;

public:
    Entity() = default;
    explicit Entity(const std::string& name) :
        name_(name)
    {}
    Entity(const std::string& name, EntityAttributes attribs) :
        name_(name),
        attribs_(attribs)
    {}

    void assignAttributes(EntityAttributes attribs) {
        attribs_ = attribs;
    }

    std::string getName() const { return name_; }

    AbilityType getClassType() const { return attribs_.abilityType; }
    AttackType getAttackType() const { return attribs_.attackType; }
    DamageType getDamageType() const { return attribs_.damageType; }
};

void getAllLinesFromFile(const char* filename, std::vector<std::string>& output) {
    std::ifstream file(filename);
    if (!file) 
    {
        std::stringstream stream;
        stream << "failed to open file " << filename << '\n';
        throw std::runtime_error(stream.str());
    }

    std::string line;
    while (std::getline(file, line)) {
        if (line.size() > 0)
            output.push_back(line);
    }
    file.close();
}

int main() {

    srand(time(NULL));
    try {
        // This will store all of the names in from the text file.
        std::vector<std::string> names;
        getAllLinesFromFile("Names.txt", names);

        // This will give us a container of all of our entities with a provided name
        // after this container is filled you can go back later and add the addition
        // properties, or if you read the properties in from a file as well you can use
        // the other Entity constructor to generate all of them with their names and properties
        std::vector<Entity> entities;
        for (auto& n : names) {
            Entity e(n);
            entities.push_back(e);
        }

        // Check array of Entities
        std::cout << "There are " << entities.size() << " entities\n";
        for (auto& e : entities) {
            std::cout << e.getName() << '\n';
        }
}
    catch (std::runtime_error& e) {
        std::cerr << e.what() << std::endl;
        return EXIT_FAILURE;
    }
    system("pause");
    return EXIT_SUCCESS;
}```


Ответы [ 2 ]

0 голосов
/ 17 февраля 2019

Исходный формат файла:

#include <string>       // std::string
#include <vector>       // std::vector<>
#include <iostream>     // std::cin, std::cout, std::cerr
#include <fstream>      // std::ifstream
#include <ctime>        // std::time()
#include <cstdlib>      // std::rand(), EXIT_FAILURE
#include <iterator>     // std::istream_iterator<>
#include <limits>       // std::numeric_limits<>
#include <algorithm>    // std::find()

char const *AbilityTypeStrings[]{ "Mage", "Guardian", "Warrior", "Hunter", "Assassin" };
enum class AbilityType {
    Mage,
    Guardian,
    Warrior,
    Hunter,
    Assassin
};

char const *DamageTypeStrings[]{ "Magical", "Physical" };
enum class DamageType {
    MAGICAL,
    PHYSICAL
};

char const *AttackTypeStrings[]{ "Melee", "Range" };
enum class AttackType {
    MELEE,
    RANGE
};

struct EntityAttributes {
    AttackType attackType;
    DamageType damageType;
    AbilityType abilityType;
};

class Entity {
    std::string name;
    EntityAttributes attributes;

public:
    Entity(std::string const &name = {}, EntityAttributes const &attributes = {}) :
        name(name),
        attributes(attributes)
    {}

    friend std::istream& operator>>(std::istream &is, Entity &entity)
    {
        // ignore everything up to the first '"'.
        is.ignore(std::numeric_limits<std::streamsize>::max(), '\"');

        // try to read the entities name
        std::string name;
        if (!std::getline(is, name, '\"')) {
            return is;
        }

        // try to read its abilities
        std::string abilities;
        if (!(is >> abilities)) {
            return is;
        }

        EntityAttributes attributes{};
        auto ability_type{ std::find(std::begin(AbilityTypeStrings), std::end(AbilityTypeStrings), abilities) };

        if (ability_type == std::end(AbilityTypeStrings)) {
            is.setstate(std::ios::failbit);
            return is;
        }

        attributes.abilityType = static_cast<AbilityType>(ability_type - std::begin(AbilityTypeStrings));

        std::string damage;
        if (!(is >> damage)) {
            return is;
        }

        auto damage_type{ std::find(std::begin(DamageTypeStrings), std::end(DamageTypeStrings), damage) };

        if (damage_type == std::end(DamageTypeStrings)) {
            is.setstate(std::ios::failbit);
            return is;
        }

        attributes.damageType = static_cast<DamageType>(damage_type - std::begin(DamageTypeStrings));

        std::string attack;
        if (!(is >> attack)) {
            return is;
        }

        auto attack_type{ std::find(std::begin(AttackTypeStrings), std::end(AttackTypeStrings), attack) };

        if (attack_type == std::end(AttackTypeStrings)) {
            is.setstate(std::ios::failbit);
            return is;
        }

        attributes.attackType = static_cast<AttackType>(attack_type - std::begin(AttackTypeStrings));


        entity.name = name;
        entity.attributes = attributes;

        return is;
    }

    friend std::ostream& operator<<(std::ostream &os, Entity const &entity)
    {
        os << '\"' << entity.name << "\"\n" << DamageTypeStrings[static_cast<std::size_t>(entity.attributes.damageType)] << '\n'
           << AbilityTypeStrings[static_cast<std::size_t>(entity.attributes.abilityType)] << '\n'
           << AttackTypeStrings[static_cast<std::size_t>(entity.attributes.attackType)] << '\n';
        return os;
    }
};

int main()
{
    std::srand(static_cast<unsigned>(std::time(nullptr)));  // why do you include <random> when
                                                            // you're using the old old C stuff?
    char const *filename{ "test.txt" };
    std::ifstream is{ filename };
    if (!is.is_open()) {
        std::cerr << "Couldn't open \"" << filename << "\" for reading :(\n\n";
        return EXIT_FAILURE;
    }

    std::vector<Entity> entities{ std::istream_iterator<Entity>{ is }, std::istream_iterator<Entity>{} };

    for (auto const &e : entities)
        std::cout << e << '\n';
}
0 голосов
/ 17 февраля 2019

То, как вы анализируете свои данные, зависит от вашей файловой структуры, и помните, что вы можете структурировать свой файл в соответствии с вашим собственным конкретным форматом, если вы будете придерживаться своего соглашения.

Вы можете делать это, как пытались:

SingleWordName Class DamageType AttackType
"Multiple Word Name" Class DamageType AttackType

Тогда вам придется анализировать каждую строку (строку) текста по отдельности, но вы также можете упростить ее, изменив структуру вашего текстового файла.Если вы знаете, что повсюду будет похожий шаблон, который не изменится, то что-то подобное может сделать его более легким для вас.

SingleWordName or Multiple Word Name
AbilityType
AttackType
DamageType

NextName
AbilityType
AttackType
DamageType

Тогда, если вы структурируете его таким образом, вы знаете, что каждая строка содержитстрока, первая строка в наборе будет переменной имени вашего класса Entity, а следующие три будут заполнять структуру атрибутов в этом классе.Затем пустая строка, которую можно игнорировать.Эта пустая строка предназначена только для визуальных ссылок на чтение, чтобы читатель-человек мог легко отличить одну сущность от другой.

Вы даже можете структурировать свой файл следующим образом:

Entity Name Single Or Multiple Words
AbilityType AttackType DamageType

Next Entity
AbilityType AttackType DamageType

Такая структурабудет принимать первую строку текста или строки и устанавливает имя объекта, а затем вторая строка содержит все поля для структуры атрибутов.Этот случай будет работать достаточно легко, если все ваши атрибуты будут только одним словом.Если у вас есть атрибуты, состоящие из нескольких слов, и вам не нравится идея заключать их в кавычки, скобки, скобки и т. Д., Вы можете просто использовать подчеркивание между каждым словом, например:

Baron_Samedi

Затем один разу вас есть это слово, вы можете найти любой _ в этом слове, удалить его из строки и заменить его на ' '.

Существует несколько способов анализа строковых данных, и все это зависит от двух основных факторов: во-первых, ваших данных или структур классов, а затем - вашей файловой структуры, которую вы используете для представления этой структуры данных.Если у вас есть эти два на месте, у вас есть ваш фундамент, тогда это вопрос использования этой информации и построения вашей функции синтаксического анализа из нее.


Редактировать - следить заОП комментируют путаницу разбора строки между кавычками:

Если у вас есть строка в кавычках " ";Проблема здесь в том, что вам нужно выполнить более одного поиска по этой строке для одного символа ", и вам нужно сохранить индекс того, где вы нашли first_occurence_of ", а также next_occurence_of ".После нахождения первого вхождения и сохранения его местоположения индекса вам придется перебирать эту последовательность символов, например массив, до тех пор, пока вы не найдете следующий ", и снова вам также потребуется сохранить это местоположение индекса.Затем вы должны получить разницу между ними.

Что касается простого примера, мы будем использовать "Hello" с кавычками, являющимися нашей строкой, которая имеет всего 7 символов.Первый " имеет индекс 0, а следующий - индекс 6.Затем вам потребуется подстрока [(first + 1), (next-1)] из этой исходной строки.

[0][1][2][3][4][5][6]
["][H][e][l][l][o]["]

Как вы можете видеть выше, первая " имеет индекс 0 иследующий по индексу 6.Общая длина строки равна 7. Мы можем использовать эту информацию и предоставленные строковые функции из stl для создания подстроки из этой исходной строки.Но нам нужно искать строку от начала и до конца, чтобы найти оба местоположения нашего начального и конечного разделителей.

// Pseudo Code
substring = [(original.firstIndexFound(") + 1) to (original.nextIndexFound(")-1)];
// Where the [] indicates inclusive at that position...
substring = [(0+1) to (6-1)] = [1,2,3,4,5]
// broken down into individual array indices..
substring[0] = original[1]
substring[1] = original[2]
substring[2] = original[3]
substring[3] = original[4]
substring[4] = original[5] 

// Visual Translation:

// Original:
[0][1][2][3][4][5][6]
["][H][e][l][l][o]["]

// Sub
[0][1][2][3][4]
[H][e][l][l][o]

Это все равно будет работать, если между словами есть пробелы, потому что функция, которую вы вызываете с разделителемне ищет , но " или любой другой символ, который вы определяете в качестве разделителя.


Вот простая программа для демонстрации разбора строки между " ".

#include <string>
#include <iostream>

int main() {
    std::string str = { "\"Hello\"" }; // need the \" for quote in the string
    std::cout << "Original String = " << str << '\n';

    size_t first = str.find_first_of('"');
    size_t next = str.find_first_of('"', first + 1);
    std::cout << "First index at: " << first << "\nNext index at: " << next << '\n';

    std::string sub = str.substr(first + 1, next - 1);
    std::cout << "Substring = " << sub << '\n';

    return 0;
}

-выход-

Original String = "Hello"
First index at: 0
Next index at: 6
Substring = Hello

-Note-

В приведенном выше описании нет проверки работоспособности, чтобы определить, не было ли вообще никакого символа " в строке.Это достаточно просто сделать, все, что вам нужно сделать, это сначала проверить, не находится ли индекс или итератор строки в конечной позиции, если он находится в конечной позиции, то просто вернуть обратно исходную строку, в противном случае просто выполните описанное выше.расчеты без изменений.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...