英文:
Removing element from vector based on value of the next element
问题
我正在编写一个程序,该程序将读取一个字符串向量,如果遇到值为“..”的元素,它将删除该元素以及前一个元素。(例如:{ "1", "2", "..", "3" } 将被转换为 { "1", "3" })
我明白我可以编写代码来使用循环和 erase()
等来实现这一点,但我想知道是否可能使用标准库的容器函数(如 remove_if()
、for_each()
等)来实现这一目标,因为这样会使我的代码看起来更清晰。
我已经查了一下,但没有找到标准库中以这种方式移除/删除元素的函数。
英文:
I'm writing a program that will read over a vector of strings, and if it encounters an element with the value "..", it will erase itself and the previous element. (eg. { "1", "2", "..", "3" } would be converted to { "1", "3" })
I understand that I could write code to do this using loops, and erase()
etc, but I was wondering
if it was possible to achieve this using the standard libraries container functions (like remove_if()
, for_each()
etc), as this would make my code look a lot cleaner.
I've had a look around, but not managed to find anything in the standard library that removes/erases elements in this style.
答案1
得分: 0
TLDR: 是的,可以使用向量函数来完成,但由于性能和可读性的原因,不建议在您的情况下使用。下面是按照您的要求编写的代码,以及我建议的方法。
让我们首先讨论您提到的函数:
remove_if()
:该函数删除满足谓词的所有元素,并返回新的向量范围的迭代器。它不返回已删除元素的各个迭代器,因此无法根据它们来删除元素。for_each()
:此函数允许您对向量中包含的每个元素运行一个函数。然而,如此所示(链接:https://en.cppreference.com/w/cpp/algorithm/for_each#Example),将针对向量的每个元素的函数是它们的直接值,而不是迭代器。因此,您无法根据正在迭代的元素来删除元素。而且,使用此函数在迭代过程中从向量中删除元素可能会导致问题。
我找到的与您的用例最接近的函数是 find_if()
。它允许您在向量中查找满足谓词的第一个迭代器,并在找不到时返回您的向量的结束迭代器。
这个函数利用了它:
#include <string>
#include <vector>
#include <algorithm>
void processvec(std::vector<std::string>& vec) {
std::vector<std::string>::iterator found;
while ((found = std::find_if(vec.begin(), vec.end(), [](std::string str) { return str == ".."; })) != vec.end()) {
vec.erase(vec.erase(found - 1));
}
}
简要解释一下:
- 我们声明了一个 "found" 迭代器
- 然后,我们进入一个 while 循环,每次迭代都会找到与我们的向量中的元素相对应的第一个迭代器,该元素等于 ".."
- 如果找到的迭代器等于向量的结束迭代器,我们就退出了 while 循环
- 如果 while 循环没有被打破,那么我们就从向量中删除了找到的迭代器之前的元素
- erase 函数返回我们刚刚删除的迭代器之后的下一个有效迭代器
- 因此,我们也删除了新返回的迭代器。
- 重复 while 循环
请注意两件事:
- 您的输入向量必须有效:如果您输入例如
{ "..", "1", "2" }
,则会发生错误,因为第一个 ".." 之前没有元素。 - 尽管这符合您的要求“使用标准库容器函数”,但它效率非常低下,因为
find_if()
函数每次调用时都会遍历您的向量。因此,在具有数千个元素但只有少数最后几个元素实际上是 ".." 的大规模向量上,将多次遍历数百个元素,这样做毫无意义。
因此,我认为在您的情况下,坚持使用常规的迭代器循环将是一个更好的主意。
以下是一个可以有效地完成任务的函数:
#include <string>
#include <vector>
#include <algorithm>
void processvec(std::vector<std::string>& vec) {
for (std::vector<std::string>::iterator it = vec.begin(); it != vec.end();) {
if (*it == ".." && it != vec.begin())
it = vec.erase(vec.erase(it - 1));
else
++it;
}
}
我不确定这里可读性有多客观,但我发现前一个函数比后一个函数更难阅读,因为前者有许多内容堆叠在一起。
英文:
TLDR: Yes it can be done using vector functions, but isn't recommended in your case due to performance and readability. Below is both the code following what you asked, and what I'd recommend.
Let's first talk about the functions you mentioned:
remove_if()
: This function removes all elements that satisfy a predicate, and returns the iterator for the new vector range. It does not return the individual iterators of the removed elements, which therefore doesn't allow you to remove elements depending of them.for_each()
: This function lets you run a function for each elements contained within a vector. However, as shown here, the function that will target each element of your vector are their direct values, not the iterators. Therefore, you can't erase elements depending of the ones you're iterating through. Plus, it could likely cause issues to delete elements from the vector while iterating through it using this function.
The closest function I could find for your usage is find_if()
. It lets you find the first iterator that satisfy a predicate in a vector, and returns your vector's end iterator if none could be found.
This function takes advantage of it:
#include <string>
#include <vector>
#include <algorithm>
void processvec(std::vector<std::string>& vec) {
std::vector<std::string>::iterator found;
while ((found = std::find_if(vec.begin(), vec.end(), [](std::string str) {return str == ".."; })) != vec.end()) {
vec.erase(vec.erase(found - 1));
}
}
To explain it a little bit:
- We declare a "found" iterator
- We then enter a while loop that will, for each iteration, find the first iterator that corresponds to an element of our vector that is equal to ".."
- If the found iterator equals the vector's end iterator, we break the while loop
- If the while loop wasn't broken, then we erase from the vector the element prior to the found iterator
- The erase function returns the next valid operator of our vector after the one we just erase
- We therefore erase the newly returned iterator as well.
- Repeat the while loop
Do note two things though:
- Your input vector must be valid: if you input
{"..", "1", "2"}
for instance, an error will occur because there is no element prior to the first "..". - While this answers your request of "using the standard libraries container functions", it is terribly inefficient, as the
find_if()
function will iterate through your vector every time it is called. So on a large scale vector with a thousand elements, but only the few last one actually are "..", then hundreds of elements will be iterated through multiple times for nothing.
As a result, I think that sticking with usual iterator for loops would be a better idea in your case.
Here is a function that should do the job efficiently enough:
#include <string>
#include <vector>
#include <algorithm>
void processvec(std::vector<std::string>& vec) {
for (std::vector<std::string>::iterator it = vec.begin(); it != vec.end();) {
if (*it == ".." && it != vec.begin())
it = vec.erase(vec.erase(it - 1));
else
++it;
}
}
I'm not sure how objective readability is here, but I find the former function much harder to read than the latter, due to so many things being stacked on the former.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论