Search code examples
c++algorithmvectorstlc++17

How to remove duplicates in std::vector of user defined class?


I have a list of Text Editors that is to be rendered on the application whenever the user selected a file. It works well however when I started to delete elements on the list to signify that they were closed by the user and not to be rendered again, the problem starts to arise when I selected that same file that was deleted earlier to have a duplicate. So, I was trying to remove the duplicates by first sorting the list and then use std::unique before rendering.

Here's the part where I render the text editors

if(!Opened_TextEditors.empty())
{   
   std::sort(Opened_TextEditors.begin(), Opened_TextEditors.end());
   auto last = std::unique(Opened_TextEditors.begin(), Opened_TextEditors.end());
   Opened_TextEditors.erase(last, Opened_TextEditors.end());

   auto it = Opened_TextEditors.begin();
   while(it != Opened_TextEditors.end())
   {   
        if(it->IsWindowVisible())
        {   
            it->Render();
            if(it->IsWindowFocused()){
                char buffer[255];
                selected_window_path = it->GetPath(); // determines which window is active or currently selected. For writing contents on status bar
                selected_editor_path = it->GetPath(); // This is to deterimine which window is focused or currently selected. For determining where to render the next selected window. This is to reduce reordering during rendering.
                auto cpos = it->GetCursorPosition();

                snprintf(buffer, sizeof(buffer), "Ln %d, Col %-6d %6d lines  | %s | %s | %s | %s ", cpos.mLine + 1, cpos.mColumn + 1, it->GetTotalLines(),
                        it->IsOverwrite() ? "Ovr" : "Ins",
                        it->CanUndo() ? "*" : " ",
                        it->GetFileExtension().c_str(), 
                        it->GetFileName().c_str()
                 );
                 current_editor = std::string(buffer);
            }
            ++it;
        }
        else {//Bug: When the editor was deleted and is selected again to be rendered, causes some sort of anomalies where it tends not being docked and somtimes when it was rendered and tried to close any window, every window will be deleted.
             it = Opened_TextEditors.erase(it);
             }
           }
        }

And here's the part where I add the selected text editor

auto it = std::find(Opened_TextEditors.begin(), Opened_TextEditors.end(), parentNode.FullPath);
if(it == Opened_TextEditors.end())
{   
     static unsigned int id = 0;
     ArmSimPro::TextEditor editor(parentNode.FullPath, id,bg_col.GetCol());
     auto programming_lang = ArmSimPro::TextEditor::LanguageDefinition::CPlusPlus();
     ++id;
     for (int i = 0; i < sizeof(ppnames) / sizeof(ppnames[0]); ++i)
           std::future<void> PreprocIdentifier = std::async(std::launch::async, SetupPreprocIdentifiers, programming_lang, ppvalues[i]);
     for (int i = 0; i < sizeof(identifiers) / sizeof(identifiers[0]); ++i)
            std::future<void> Identifiers = std::async(std::launch::async, SetupIdentifiers, programming_lang, identifiers[i], idecls[i]);
                
    editor.SetLanguageDefinition(programming_lang);

    std::ifstream t(parentNode.FullPath.c_str());
    if (t.good())
    {
        std::string str;
        t.seekg(0, std::ios::end);
        str.reserve(t.tellg());
        t.seekg(0, std::ios::beg);
        str.assign((std::istreambuf_iterator<char>(t)), std::istreambuf_iterator<char>());

        editor.SetText(str);
     }

     if(!selected_editor_path.empty()){
         int index = GetTextEditorIndex(selected_editor_path) + 1;
         Opened_TextEditors.insert(Opened_TextEditors.begin() + index, editor);
     }
     if(Opened_TextEditors.empty())
         Opened_TextEditors.push_back(editor);
     }

When I run it, it gives an error message:

call to object of class type 'std::less<void>': no matching call operator found
call to object of class type 'std::less<void>': no matching call operator found 

So, I added an function on my class that overloads the "==" operator:

class TextEditor
{
 public:
        .....
        bool operator==(TextEditor& other) { return this->path == other.path; }
        bool operator==(const TextEditor& other) { return this->path == other.path; }
        bool operator==(const std::string& full_path) const { return this->path == full_path; }
        .....
};

I don't have much of experience in <algorithm> since I was just starting to learn for a few months but I suspect the error was coming from std::unique. I've found this similar problem but still get the same error. Is there anything that I could add on my TextEditor class to satisfy the parameters on the std::unique?

At this problem I've seen people saying to use std::set to avoid duplicates. However, to allow adding text editors next to the current selected text editor need an ability to insert at a particular index. This is done by searching through the list and finds the editor that has the specified file path and then get its position.


Solution

  • The error more than likely is coming from std::sort and not std::unique. The 2-argument std::sort requires that the type can be compared with <, which TextEditor does not have.

    I would use the 3-argument std::sort that allows a predicate to be used, instead of having to provide an overloaded operator < to the TextEditor class.

    Here is a full example:

    #include <vector>
    #include <iostream>
    #include <algorithm>
    
    class TextEditor
    {
        std::string path;
        public:
            TextEditor(const char * p) : path(p) {}
            const std::string& getPath() const { return path;}
    };
    
    int main()
    {
        std::vector<TextEditor> v;
        v.push_back("path1");
        v.push_back("path2");
        v.push_back("path1");  // Duplicate
    
        std::sort(v.begin(), v.end(), [](const TextEditor& e1, const TextEditor& e2)
        { return e1.getPath() < e2.getPath(); });
    
        auto it = std::unique(v.begin(), v.end(), [](const TextEditor& e1, const TextEditor& e2)
        { return e1.getPath() == e2.getPath(); });
    
        v.erase(it, v.end());
    
        for (auto& te : v)
        {
            std::cout << te.getPath() << "\n";
        }
    }
    

    Output:

    path1
    path2
    

    Note that the 3-argument std::unique call was made, thus not needing to write an overloaded operator==.