So I have created a binary search tree that is stored within an array. This binary search tree (BST) stores a user input ID, Age, and Name, then places it within the array sorted in ascending order by the ID.
I'm attempting to write a delete function that takes a user input ID, loops through the vector, and if it finds a matching ID, it deletes it.
However, I can't seem to get BinaryTree.erase() to work due to an error.
Severity Code Description Project File Line Suppression State
Error (active) E0304 no instance of overloaded function "std::vector<_Ty, _Alloc>::erase [with _Ty=Node,
_Alloc=std::allocator]" matches the argument list Project_4CS210
After erasing, I plan on organizing the vector so that there aren't any open spots, however, I want to make sure that I can delete the node first. Any suggestions? I am a beginner, so most of this is rather new to me.
Here's my delete function.
#include "BinaryTree.h"
#include <string>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int index;
struct Node
{
int ID;
int age;
string name;
Node()
{
}
Node(int id, int Age, string nm)
{
this->ID = id;
this->age = Age;
this->name = nm;
}
};
vector<Node> binaryTree;
void BST::Delete()
{
int input_id;
cout << "What is the ID of the person to be deleted" << endl;
cin >> input_id;
for (unsigned int i = 0; i < binaryTree.size(); i++)
{
if (input_id == binaryTree.at(i).ID)
binaryTree.erase(i);
}
}
std::vector::erase takes an iterator, not an index.
You can use binaryTree.begin() + i to get the iterator for the i-th element of a vector (note that some containers have iterator types without +, so you'd need to use std::advance there).
Overall, binaryTree.erase(binaryTree.begin() + i) should do the job you want, but for what you are trying to do you could also look into std::remove_if.
To expand on this last point (because someone in the comments mentioned the remove/erase idiom):
vector.erase(std::remove_if(vector.begin(),
vector.end(),
[](const auto& element) { return condition(element); }),
vector.end());
This will remove all elements from the vector where condition(element) returns true, and is generally better than iterating over the vector by hand:
If you want to remove more than one element with your method, you might miss some. Take a vector where the elements you want to remove are at indices i and i + 1. Then, you remove the element at index i normally, and the element at i + 1 moves to index i - however, in the next iteration you already check index i + 1, so you miss the item that you wanted to remove
Each time you remove an element from the vector, all the elements behind that are moved around in memory. If your items are fairly big, that can be expensive. Using remove_if should ensure that each element isn't moved more than necessary, which can be more efficient especially if you remove many elements.
You shouldn't remove elements from vector while iterating over it, you can miss some indexes while iterating, use std::remove_if to send elements needed to remove at the end of vector and then erase them from vector with erase() method:
auto It = std::remove_if(binaryTree.begin(), binaryTree.end(), [input](const Node& node)
{
if( node.ID == input )
{
return true;
}
return false;
});
binaryTree.erase(It,binaryTree.end());
Method erase() accepts iterators to remove range or one iterator to remove one element. std::remove_if will return iterator for the first elements pushed at the end to remove.
Considering you wish to remove all the items in the vector or a specific item. You can actually erase items from a vector using erase while iterating. Keep in mind the erase actually provides you with the iterator to the next item. So you may not need to increment the iterator unless it fulfills the condition.
Here is a sample code:
for (std::vector<Node>::iterator it = binaryTree.begin(); it != binaryTree.end();)
{
if(it == yourItem) //Conditional check if it matches with your object.
{
it = binaryTree.erase(it);
}
else
{
++it;
}
}
You can see that i am actually getting the iterator to the next item in the list without incrementing it. You can process it in whatever way you may like.
Related
I have a class called mapTriple that has a method that takes an integer vector and multiplies all the values in vector by a private function of the mapTriple class (the function takes an int, and returns the int *3)
I have already set up the classes and the function that triples the integer. I am stuck on the mapTriple method. The method cannot be iterative, it must be recursive.
vector<int> MapTriple::map(vector<int> myVector)
{
if(myVector.size() == 1)
{
myVector[0] = f(myVector[0]);
return myVector;
}
else
{
map(myVector.erase(myVector.begin()+myVector.size()-1));
myVector[myVector.size()-1] = f(myVector[myVector.size()-1]);
return myVector;
}
}
int f (int a)
{
return (a*3);
}
It currently isnt compiling, it is say there is no matching call to map. I have all the .h files and main files etc
erase does not return the modified vector. It returns an iterator after the removed element (which will be end in your case, so you don't need that). Just pass the modified vector itself.
You currently don't re-add the erased element, so even if your code compiled, you would always be returning a vector of length 1 (and the remaining element would be tripled n times if the vector was originally size n).
The correct else branch should be:
else
{
// Store and remove the last element.
int currentElement = myVector.back();
myVector.erase(myVector.end()-1);
// Recursively process the remaining elements.
map(myVector);
// Process and re-add the above element.
myVector.push_back(f(currentElement));
return myVector;
}
However, instead of erasing elements and re-adding them, you could work with the iterators.
using Iterator = std::vector<int>::iterator;
void MapTriple::map(Iterator start, Iterator end)
{
// No elements remaining?
if (start == end)
return;
// Process first element.
*start = f(*start);
// Process remaining elements recursively.
map(start+1, end);
}
While this is pretty elegant, it would of course be even simpler to do this with a simple for loop:
for (auto& e : myVector) e = f(e);
or std::transform:
std::transform(myVector.begin(), myVector.end(), myVector.begin(),
[this](int e) -> { return f(e); });`
It should also be noted that map is probably a mediocre name for this method, if you did using namespace std; as seems to be the case (see also Why is "using namespace std" considered bad practice?).
I have 7x20 2D Vector of an "Entry" object. The Entry object has an ID. I want to iterate through my vector and do:
if( idToDelete == entry.getID() )
//Delete entry
Currently the code looks like this:
for(auto day : entryRegister)
{
for(auto entry : day)
{
if(entryID == entry.getID())
{
//Delete Entry
}
}
}
I'm unsure how to formulate the expression to actually delete an item. I've tried doing entryRegister[day].erase(entry) but I get an error because there is no operator found that takes an iterator in the square brackets.
I saw the request for the declaration of EntryRegister. The relevant information is just that it is declared like this: vector< vector<Entry> > and has 7 colums, each with a maximum of 20 rows.
getID returns an unsigned char that is some value between 1 and 70.
This is part of a function where I pass in an ID that i wish to delete. I then iterate through the register, and delete any entry with that ID.
C++ does not have a function "delete a value" in a vector, since it can be done in two more fundamental steps, commonly known as the erase-remove idiom:
Find all items in the vector equal to the value, and swap them to the end of the vector, using std::remove or remove_if
Resize the vector to delete those found items, using std::vector::erase
Note that erase takes one or two iterators. It only supports erasing items where you already knew the position.
for (auto& day : entryRegister) {
auto erase_begin = std::remove_if(
day.begin(),
day.end(),
[=](const Entry& entry) { return entry.EntryID() == entryID; }
);
day.erase(erase_begin, day.end());
}
Note that I changed the entryRegister loop to get day by reference, so the changes actually affect the real day instead of a copy.
To summarize my problem, in an "unordered_map" I will be adding a pair, name with a number, and sending it to a function. (function doesn't matter what it does.) So then I will be adding another thing to the graph map, but I want to be able to traverse to the next thing I added to the list because I want to throw mark's name into the function next.
Here is some code
unordered_map<string,int> graph;
unordered_map<string,int>:: iterator it;
using namespace std;
int main(){
string name;
graph.insert(pair<string,int>("Sue",4));
it=graph.begin();
name = it -> first; //name is equal to "Sue"
function(name);
graph.insert(pair<string,int>("Mark",83));
it++ // this will not work
name = it -> first; //this will not end up equaling to "Mark"
function(name);
}
Doing ++it does not work,going backwards --it, doesn't work too.
So does anyone have a solution how I can go backwards to obtain the name = it -> first to be Mark?
Note: (I will be doing this method multiple times too because it'll be going into a loop.)
For unordered_map, it's not guaranteed that an item inserted after another item, will also be stored after that item. This is what is meant with an unordered map.
In this case, you are out of luck: the std::hash<std::string> conspires to let "Sue" come after "Mark" and it++ points to graph.end() and it->first is dereferencing this, with a memory error.
If you would use an ordered map<string, int> and insert "Sue" after "Mark" (or you would use std::greater as the map comparison for your original order of "Mark" after "Sue"), then the order of elements is what you expect.
#include <unordered_map>
#include <map>
#include <iostream>
using namespace std;
map<string,int> graph;
map<string,int>:: iterator it;
int main()
{
string name;
graph.insert(pair<string,int>("Mark",4));
it=graph.begin();
name = it -> first; //name is equal to "Sue"
std::cout << name;
graph.insert(pair<string,int>("Sue",83));
it++ ;// this will not work
name = it -> first; //this will not end up equaling to "Mark"
std::cout << name;
}
Live Example.
I think this is because the iterator values are generated when you call Begin().
In some other languages (C#) if a IEnumerable (e.g. List) changes while an IEnumerator (aka. iterator) is active, the Enumerator becomes invalid
and the following IEnumerator::MoveNext() will throw an InvalidOperationException.
see MSDN Article on IEnumerator.MoveNext Method()
Also This article on CPPReference states:
If rehashing occurs due to the insertion, all iterators are invalidated. Otherwise iterators are not affected. References are not invalidated. Rehashing occurs only if the new number of elements is equal to or greater than max_load_factor()*bucket_count().
I am trying to delete an element from a list of objects if one of the object's properties matches a condition. This is my function to do so, however, after performing this operation and then printing the contents, the erase() seems to have no effect. What am I doing wrong here?
void FileReader::DeleteProcess(int id, list<Process> listToDeleteFrom)
{
list<Process>::iterator process;
for(process = listToDeleteFrom.begin(); process != listToDeleteFrom.end(); process++)
{
if (process -> ID == id)
{
listToDeleteFrom.erase(process);
}
}
}
First, you need to pass the list by reference; your code is working on a copy, so changes it makes won't affect the caller's list:
void FileReader::DeleteProcess(int id, list<Process> & listToDeleteFrom)
^
Second, erasing a list element invalidates any iterator that refers to that element, so attempting to carry on iterating afterwards will cause undefined behaviour. If there will only be one element to remove, then return from the function straight after the call to erase; otherwise, the loop needs to be structured something like:
for (auto it = list.begin(); it != list.end(); /* don't increment here */) {
if (it->ID == id) {
it = list.erase(it);
} else {
++it;
}
}
Calling erase() when an iterator is iterating over the list invalidates the iterator. Add the elements to erase to a second list then remove them afterwards.
Also note that you are passing the list by value rather than using a reference or a pointer. Did you mean to use list<Process>& listToDeleteFrom or list<Process>* listToDeleteFrom?
The reason you see no changes reflected is that your list is not being passed by reference, so you are only removing elements from a copy of the list.
Change it to this:
void FileReader::DeleteProcess(int id, list<Process> &listToDeleteFrom) //note &
This will keep the same syntax in the function and modify the original.
However, the way you're deleting the elements is a bit sub-optimal. If you have C++11, the following will remove your invalidation problem, and is more idiomatic, using an existing algorithm designed for the job:
listToDeleteFrom.erase ( //erase matching elements returned from remove_if
std::remove_if(
std::begin(listToDeleteFrom),
std::end(listToDeleteFrom),
[](const Process &p) { //lambda that matches based on id
return p->ID == id;
}
),
std::end(listToDeleteFrom) //to the end of the list
);
Note the keeping of std::list<>::erase in there to actually erase the elements that match. This is known as the erase-remove idiom.
In C++, how can I delete an element from a vector?
Delete it right from where it is, i.e. let the vector resize
Swap the element to be deleted with the last element s.t. pop_back() can be used (which I hope doesn't involve copying everything around...)
For (1), I've tried the following, but I'm not quite sure if it does what it is supposed to do (remove the item passed to removeItem() ), and it doesn't seem very elegant:
vector<Item*> items;
// fill vector with lots of pointers to item objects (...)
void removeItem(Item * item) {
// release item from memory
if (int i = getItemIdIfExists(item) != -1) {
items.erase (items.begin()+i);
}
}
int getItemIdIfExists(Item * item) {
// Get id of passed-in Item in collection
for (unsigned int i=0; i<items.size(); i++) {
// if match found
if (items[i] == item) return i;
}
// if no match found
return -1;
}
The standard remove+erase idiom removes elements by value:
#include <vector>
#include <algorithm>
std::vector<int> v;
v.erase(std::remove(v.begin(), v.end(), 12), v.end());
remove reorders the elements so that all the erasees are at the end and returns an iterator to the beginning of the erasee range, and erase actually removes the elements from the container.
This is as efficient as you can get with a contiguous-storage container like vector, especially if you have multiple elements of the same value that all get erased in one wash.
void removeItem(Item*item){
for(int i=0; i<items.size(); i++){
if (items[i]==item){
swap(items[i], items.back());
items.pop_back();
return;
}
}
}
Though, if the order doesn't matter, why not just use a std::set?
Delete it right from where it is, i.e. let the vector resize
That's what erase does.
Swap the element to be deleted with the last element s.t. pop_back() can be used (which I hope doesn't involve copying everything around...)
That's what remove does, except that it preserves the order of the remaining objects so it does involve copying everything around.
What you've done could be written as:
items.erase(
std::remove(
items.begin(), items.end()
, item
)
, items.end()
);
The difference with your code being that this will actually remove all items valued item, instead of just the first one.