c++optimization

Is there a better way to keep ids in a vector sequential in C++?


I have a simple C++ program that acts as a todo list, it contains a vector that holds class type of Item to track each todo and their ID.

Is there a more efficient way to make sure that the list of IDs are sequential even after removing an element?

I'm looking for an answer that is objectively more efficient while still following these constraints of being based on vector, using class Item, and keeping track of IDs. Note that this isn't a question on why are you setting IDs in the first place, I want to know what would be the best method while following the constraints.

Example:

Imagine we have an initialized list called todos that is an instance of class Todo

Todo todos = Todo();

todos.add("Take out the trash.");
todos.add("Do the laundry.");
todos.add("Read a book.");
todos.add("Make dinner.");

Our list looks like this when we call list():

{Todo[{Item(id=0, desc="Take out the trash.")}, {Item(id=1, desc="Do the laundry.")}, {Item(id=2, desc="Read a book.")}, {Item(id=3, desc="Make dinner.")}]}

If I then remove the element at ID 2:

todos.remove(2);

The result would be:

{Todo[{Item(id=0, desc="Take out the trash.")}, {Item(id=1, desc="Do the laundry.")}, {Item(id=3, desc="Make dinner.")}]}

Now we have removed the element at ID 2, but, I want to keep the IDs sequential. I'm looking for 0, 1, 2.

If I call my fix() function, it will iterate through each index of our todos, check if the elements ID matches the expected next ID, if it doesn't, set the elements ID to the current iteration index (i)

void Todo::fix() {
    for (int i = 0; i < static_cast<int>(todos.size()); i++) {
        if (todos[i].getId() != i) {
            todos[i].setId(i);
        }
    }
}

Here is my code:

todos.add("Take out the trash.");
todos.add("Do the laundry.");
todos.add("Read a book.");
todos.add("Make dinner.");
todos.remove(2);
todos.fix();

If we run this, our todos will now be:

{Todo[{Item(id=0, desc="Take out the trash.")}, {Item(id=1, desc="Do the laundry.")}, {Item(id=2, desc="Make dinner.")}]}

Great! I got the result I wanted, but here is the question:

Is this the most efficient way to do this task in C++? It seems that if this were a bigger list, it could become resource-intensive and slow. Is there any faster/more efficient way to do this?

Here is the code:

#include <iostream>
#include <vector>

class Item {
    private:
        int id;
        std::string description;
    public:
        Item() : id(0), description("") {}
        Item(int id, std::string description) : id(id), description(description) {}
        int getId() const { return id; }
        void setId(int value) { id = value; }
        std::string getDescription() const { return description; }
        void setDescription(std::string value) { description = value; }
};

class Todo {
    private:
        std::vector<Item> todos;
    public:
        Todo() = default;
        Todo(std::vector<Item> todos) : todos(todos) {};
        bool add(std::string description);
        bool remove(int id);
        bool edit(int id, std::string description);
        void list();
        void fix();
};

bool Todo::add(std::string description) {
    if (description.empty()) {
        return false;
    }

    todos.emplace_back(todos.size(), description);
    return true;
}

bool Todo::remove(int id) {
    if (id < 0 || id >= static_cast<int>(todos.size())) {
        return false;
    }

    todos.erase(todos.begin() + id);
    return true;
}

bool Todo::edit(int id, std::string description) {
    if (id < 0 || id >= static_cast<int>(todos.size()) || description.empty()) {
        return false;
    }

    todos[id].setDescription(description);
    return true;
}

void Todo::list() {
    std::cout << "{Todo[";
    for (size_t i = 0; i < todos.size(); ++i) {
        const auto& item = todos[i];
        std::cout << "{Item(id=" << item.getId() << ", desc=\"" << item.getDescription() << "\")}";
        if (i != todos.size() - 1) std::cout << ", ";
    }
    std::cout << "]}" << std::endl;
}

void Todo::fix() {
    for (int i = 0; i < static_cast<int>(todos.size()); i++) {
        if (todos[i].getId() != i) {
            todos[i].setId(i);
        }
    }
}

int main() {
    Todo todos = Todo();
    todos.add("Take out the trash.");
    todos.add("Do the laundry.");
    todos.add("Read a book.");
    todos.add("Make dinner.");

    todos.remove(2);

    todos.fix();

    todos.list();
    return 0;
}

Solution

  • I agree with Christophe that you are using the wrong container for what it seems you want to do - but - since you're asking about how to keep using a std::vector<Item> but make it more efficient, here are some ways: