Deleting element in row of 2D Vector - With Iterators - c++

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.

Related

C++ How to delete nodes in a vector

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.

Sorting 2d vector passed by reference

I implemented Prim's algorithm. It returns a std::vector<std::vector<int>> (which is a vector of vectors : [vertice, vertice, weight]). The results are correct, but when I compare it with my lab guys results, sometimes there is a difference like:
I have an edge 4-1 with weight 2, and he has an edge 1-4 with weight 2 + his results are like 0-something, 0-something, 1-something, 2-something, and mine are 4-something, 1-something, 2-something.
I know that it's not an error but he is soo much nitpicking, so I thought I will sort this vector.
First of all I would sort inner vector by swapping those 2 vertice labels if the [0] one is bigger than the [1] one.
Then I would sort them all by checking if [0] in some vector is bigger than [0] in other vector. I've got this code:
void undirected_graph::mySort(std::vector<std::vector<int>>& input)
{
for (auto row = input.begin(); row != input.end(); ++row)
{
if (row[0] > row[1])
{
std::swap(row[0], row[1]);
}
}
std::sort(input.begin(), input.end(), [](const std::vector< int >& a, const std::vector< int >& b) { return a[0] < b[0]; });
}
I call that method with mySort(mst) (which is of type std:vector<std::vector<int>>)
The error when it attemtps to std::swap I get is: vector iterator not dereferencable
Should I pass the vector somehow different, or should I change something in this method code?
auto row is an iterator. That iterator's operator[] does not call the according operator on the pointed-to element but returns the element that the iterator would point to if it were incremented by the given operand. Hence, the statement row[1] may try to dereference the end() iterator of the vector, which is not possible. What you want to do is this:
if ((*row)[0] > (*row)[1])
{
std::swap((*row)[0], (*row)[1]);
}
You should also write your comparison predicate in a way that uses the second entry whenever the first entries are equal.

What is the std::vector::iterator's index in the vector?

I have an std::vector<Bullet> bullets and in the for-loop below I want to remove a bullet from the vector if it's not alive anymore.
My plan is to remove the element with pop_back(). If there are more than one element in the vector I want to first swap the element that is to be removed with the last element in the vector, and then call pop_back().
for (std::vector<Bullet>::iterator b = bullets.begin(); b != bullets.end(); ++b) {
if(!b->isAlive()) {
if (bullets.size() > 1) {
std::iter_swap(bullets + ..., bullets.end());
}
bullets.pop_back();
}
}
The problem is the first parameter in iter_swap. I looked up http://www.cplusplus.com/reference/algorithm/iter_swap/ and the syntax for the first parameter is the vector + the position of the element.
How do I find out b's index in the vector?
If the condition governing whether an element is to be removed or not is :
object->isAlive()
Then you should use an STL way to do the removal, namely the erase-remove idiom :
bullets.erase(std::remove_if(bullets.begin(), bullets.end(),
[](Bullet const& b) {
return !b.isAlive();
}), bullets.end());
Now, to answer your particular question an iterator's it index in a vector v can be obtained like so :
auto indx = std::distance(v.begin(), it);
There's an easier way to filter a std::vector.
#include <algorithm>
auto part = std::remove_if(
bullets_.begin(),
bullets_.end(),
[](const Bullet &bullet) { return !bullet.isAlive(); });
bullets_.erase(part, bullets_.end());
This will partition the vector into the alive and dead bullets, and then you delete the segment with the dead bullets.
The std::remove_if() function is like partition() but only the order of the first partition is preserved.

std::list::erase not working

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.

Erasing element from Vector

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.