rewrite access to collection to avoid "double" finding - c++

I have such code:
std::unordered_map<int64_t /*id_ord*/, LimitOrder> futOrders;
auto i = futOrders.find(orderId);
if (i == futOrders.end()) {
LimitOrder& newOrder = futOrders[orderId];
// work
} else {
LimitOrder& futOrder = i->second;
// another work
}
Here I execute "find" twice:
first time: auto i = futOrders.find(orderId);
second time: LimitOrder& newOrder = futOrders[orderId];
Can i rewrite it somehow to avoid "double find"?

You can perform an emplace, and check the return value to know whether the item was inserted or not:
std::unordered_map<int64_t /*id_ord*/, LimitOrder> futOrders;
auto i = futOrders.emplace(
std::piecewise_construct, std::tie(orderId), std::make_tuple());
if (i.second) {
LimitOrder& newOrder = i.first->second;
// work
} else {
LimitOrder& futOrder = i.first->second;
// another work
}

How about using size() to realize if an element was inserted, like this:
auto old_size = futOrders.size();
LimitOrder& order = futOrders[orderId];
if (old_size < futOrders.size()) {
LimitOrder& newOrder = order;
// work
} else {
LimitOrder& futOrder = order;
// another work
}

Assuming there is a way to "determine if an order is empty", you could do:
LimitOrder& anOrder = futOrders[orderId];
if (anOrder.empty())
{
// New order, do stuff that only new orders need.
}
else
{
// Old order, update it.
}
The empty method could of course be something like if (anOrder.name == "") or if (anOrder.orderId == 0), etc.

You can use this overload of insert instead:
std::pair<iterator,bool> insert( const value_type& value );
Example:
std::unordered_map<int, std::string> m { {0, "A"}, {1, "B"}, {2, "C"} };
int orderId = 1;
// attempt to insert with key you have and default constructed value type
auto p = m.insert( std::make_pair(orderId, std::string()) );
if (p.second) {
// the element was inserted
} else {
// the element was not inserted
std::cout << p.first->second; // will print "B"
}
In both cases, p.first is the iterator to the element you search for (or just got inserted).

Related

What's the problem of saving pointer of vector inside std::map

I am trying to find the best sum with memorization, but when saving the vector pointer inside a map the values keep appending inside the vector and getting the wrong vector.
if I commented out the map insertion it works properly.
saving nullptr is not possible in case trying to save vector inside the map by reference.
std::vector<int> *bestSumV(int target, int nums[], int size) {
static std::map<int, std::vector<int> *> memo;
if (memo.find(target) != memo.end())
return memo.at(target);
if (target == 0)
return new std::vector<int>();
if (target < 0)
return NULL;
std::vector<int> *bestCom = nullptr;
for (int i = 0; i < size; i++) {
int reminder = target - nums[i];
std::vector<int> *reminderResult = bestSumV(reminder, nums, size);
if (reminderResult != NULL) {
reminderResult->push_back(nums[i]);
if (bestCom == nullptr || reminderResult->size() < bestCom->size()) {
bestCom = static_cast<std::vector<int> *>(reminderResult);
}
}
}
// if i commented out the map insertion i am getting the correct value
// and getting a vector of 5 items
memo.insert(std::make_pair(target, std::move(bestCom)));
return bestCom;
}
void runHowbestTest() {
int testArray[] = {5, 4, 2};
std::vector<int> *bestSum25 = bestSumV(25, testArray, 3);
for (int i = 0; i < bestSum25->size(); i++) {
std::cout << "the items " << bestSum25->at(i) << std::endl;
}
}
bestCom is a std::vector<int> * don't std::move it. It's pointless, and makes the code hard to read.
reminderResult is already a std::vector<int> *, no need to static_cast<std::vector<int> *> it.
On this line
memo.insert(std::make_pair(target, std::move(bestCom)));
bestCom might be null. When that happens,
if (memo.find(target) != memo.end())
return memo.at(target);
will return and bypass the function logic. You need:
if (memo.find(target) != memo.end() && memo.at(target))
return memo.at(target);
And, most likely, the real issue:
// returns a memoized value
std::vector<int> *reminderResult = bestSumV(reminder, nums, size);
...
// modifies it.
reminderResult->push_back(nums[i]);
You cannot modify a memoized value and expect it to be valid.
Using objects instead of pointers fixes the issue: https://godbolt.org/z/v35oT4 . But I make no claims about what it does to performance.

Range-based for loop with special case for the first item

I find myself often with code that looks like this:
bool isFirst = true;
for(const auto &item: items)
{
if(!isFirst)
{
// Do something
}
// Normal processing
isFirst = false;
}
It seems that there ought to be a better way to express this, since it's a common pattern in functions that act like a "join".
Maybe a for_first_then_each is what you're looking for? It takes your range in terms of iterators and applies the first function to the first element and the second function to the rest.
#include <iostream>
#include <vector>
template<typename BeginIt, typename EndIt, typename FirstFun, typename OthersFun>
void for_first_then_each(BeginIt begin, EndIt end, FirstFun firstFun, OthersFun othersFun) {
if(begin == end) return;
firstFun(*begin);
for(auto it = std::next(begin); it != end; ++it) {
othersFun(*it);
};
}
int main() {
std::vector<int> v = {0, 1, 2, 3};
for_first_then_each(v.begin(), v.end(),
[](auto first) { std::cout << first + 42 << '\n'; },
[](auto other) { std::cout << other - 42 << '\n'; }
);
// Outputs 42, -41, -40, -39
return 0;
}
You can't know which element you are visiting in a range based for loop unless you are looping over a container like an array or vector where you can take the address of the object and compare it to the address of the first item to figure out where in the container you are. You can also do this if the container provides lookup by value, you can see if the iterator returned from the find operation is the same as the begin iterator.
If you need special handling for the first element then you can fall back to a traditional for loop like
for (auto it = std::begin(items), first = it, end = std::end(items); it != end; ++it)
{
if (it == first)
{
// do something
}
// Normal processing
}
If what you need to do can be factored out of the loop then you could use a range based for loop and just put the processing before the loop like
// do something
for(const auto &item: items)
{
// Normal processing
}
With Ranges coming in C++20, you can split this in two loops:
for (auto const& item : items | view::take(1)) {
// first element only (or never executed if items is empty)
}
for (auto const& item : items | view::drop(1)) {
// all after the first (or never executed if items has 1 item or fewer)
}
If you don't want to wait for C++20, check out range-v3 which supports both of these operations.
This won't work like this with an Input range (like if items is really a range that reads from cin) but will work just fine with any range that is Forward or better (I'm guessing items is a container here, so that should be fine).
A more straightforward version is actually to use enumerate (which only exists in range-v3, not in C++20):
for (auto const& [idx, item] : view::enumerate(items)) {
if (idx == 0) {
// first element only
}
// all elements
}
A fun alternative solution, that I would not use in production without great care, would be to use custom iterator.
int main() {
std::vector<int> v{1,2,3,4};
for (const auto & [is_first,b] : wrap(v)) {
if (is_first) {
std::cout << "First: ";
}
std::cout << b << std::endl;
}
}
A toy implementation could look like this:
template<typename T>
struct collection_wrap {
collection_wrap(T &c): c_(c) {}
struct magic_iterator {
bool is_first = false;
typename T::iterator itr;
auto operator*() {
return std::make_tuple(is_first, *itr);
}
magic_iterator operator++() {
magic_iterator self = *this;
itr++;
//only works for forward
is_first = false;
return self;
}
bool operator!=(const magic_iterator &o) {
return itr != o.itr;
}
};
magic_iterator begin() {
magic_iterator itr;
itr.is_first = true;
itr.itr = c_.begin();
return itr;
}
magic_iterator end() {
magic_iterator itr;
itr.is_first = false;
itr.itr = c_.end();
return itr;
}
T &c_;
};
template<typename Collection>
collection_wrap<Collection>
wrap(Collection &vec) {
return collection_wrap(vec);
}
Check the object address to see if it's the first item:
for(const auto &item: items)
{
if (&item != &(*items.begin())
{
// do something for all but the first
}
// Normal processing
}
An approach still valid in C++ is to use a macro:
#include <iostream>
#include <vector>
#define FOR(index, element, collection, body) { \
auto &&col = collection; \
typeof(col.size()) index = 0; \
for(auto it=col.begin(); it!=col.end(); index++, it++) { \
const auto &element = *it; \
body; \
} \
}
using namespace std;
int main() {
vector<int> a{0, 1, 2, 3};
FOR(i, e, a, {
if(i) cout << ", ";
cout << e;
})
cout << endl;
FOR(i, e, vector<int>({0, 1, 2, 3}), {
if(i) cout << ", ";
cout << e;
})
cout << endl;
return 0;
}
Prints:
0, 1, 2, 3
0, 1, 2, 3
This solution is succinct compared to alternative options. On the downside, index is being tested and incremented on each iteration of the loop - this can be avoided by increasing the complexity of the macro and by using bool first instead of index, but using index in the macro covers more use cases than bool first.
Since C++20, you can slightly improve your range-based for loop by using an init-statement. The init-statement allows you to move your isFirst flag into the scope of the loop so that this flag is no longer visible outside the loop:
std::vector<int> items { 1, 2, 3 };
for(bool isFirst(true); const auto &item: items) {
if(!isFirst) {
std::cout << "Do something with: " << item << std::endl;
}
std::cout << "Normal processing: " << item << std::endl;
isFirst = false;
}
Output:
Normal processing: 1
Do something with: 2
Normal processing: 2
Do something with: 3
Normal processing: 3
Code on Wandbox
I assume you want to know how to retrieve the first element, you could do this with an array and a vector.
I'm going to show the array here.
First include this in your code:
#include <array>
Then convert your array accordingly:
std::array<std::string, 4> items={"test1", "test2", "test3", "test4"};
for(const auto &item: items)
{
if(item == items.front()){
// do something
printf("\nFirst: %s\n", item.c_str()); //or simply printf("\nFirst:"); since you gonna output a double element
}
// Normal processing
printf("Item: %s\n", item.c_str());
}
return 0;
}

How to delete an object from a map which contains a vector as value in C++

I have a map which contains a of vector of type Messages.
std::map<std::string, std::vector<Message>> storage;
class Message has 3 member variables.
class Message
{
private:
std::string msg;
std::string msg_type;
int priority;
}
Now i am trying to delete an object which has priority(say 3) from the map. i am using the following function for it. But it doesn't work.
void deleteByMessagePriority(int priority)
{
if (checkPriorityOfMessage(priority))
{
for (std::map<std::string, std::vector<Message>>::iterator it = storage.begin(); it != storage.end(); it++)
{
std::vector<Message> listOfMsgs = it->second;
for (std::vector<Message>::iterator vec_it = listOfMsgs.begin(); vec_it != listOfMsgs.end(); vec_it++)
//for(int index = 0;index < listOfMsgs.size();index++)
{
if (vec_it->getPriority() == priority)
{
listOfMsgs.pop_back();
}
}
}
}
}
Look carefully at this:
if (vec_it->getPriority() == priority)
{
listOfMsgs.pop_back();
}
You're looking at the priority of one message (the one referred to by vec_it), but then what are you deleting if it matches?
Instead of writing your own loop, I'd use erase and std::remove_if to remove all the items you care about in that vector at once.
for (auto & item : storage) {
auto &vec = item.second;
auto start_junk = std::remove_if(
vec.begin(), vec.end(),
[=](Message const &m) { return m.priority == priority; });
vec.erase(start_junk, vec.end());
}
if (vec_it->getPriority() == priority)
{
listOfMsgs.pop_back();
pop_back() removes the last element of the vector which you don't want.You want to check erase
Also remember erase() invalidates the iterators so you need iterator to the next element after a deleted element for which we can fortunately use return value of erase
if (vec_it->getPriority() == priority)
{
vec_it = listOfMsgs.erase(vec_it); //returns iterator to the element after vec_it which can also be `listOfMsgs.end()`
std::vector<Message> listOfMsgs = it->second;
.
.
.
listOfMsgs.pop_back();
You're copying the list, only to modify the copy. What you meant is:
std::vector<Message>& listOfMsgs = it->second;
Then you can proceed erasing elements. As Gaurav Sehgal says, use the return value of erase:
std::vector<Message>::iterator vec_it = listOfMsgs.begin();
while (vec_it != listOfMsgs.end())
{
if (vec_it->getPriority() == priority)
{
vec_it = listOfMsgs.erase(vec_it);
}
else
{
++vec_it;
}
}

STL map doesn't add a pair after removing the first pairs

In this chunk of code I add a pair on a map and everything is fine but when I delete a pair that isn't the last one the map doesn't add any more pairs. What I'm Doing wrong??
SomeClass::add(Object object)
if (!object.empty())
{
ObjectList::iterator result = find(object.name());
if (result == ObjectList.end())
{
object.order(size() + 1);
ObjectList.insert(orderedObject(object.order(), object));
}
else
{
ObjectList[result->first] = object;
}
}
ObjectList and orderedObject are declared as follows:
typedef std::pair<int, Object> orderedObject;
typedef std::map<int, Object> ObjectList;
This is the deletion code:
SomeClass::eraseNamed(std::string aName)
{
if (!isEmpty())
{
ObjectList::iterator result;
result = find(aName);
if (result != ObjectList.end())
{
ObjectList.erase(result);
reorgObjectList();
return true;
}
}
return false;
}
For find method:
ObjectList::iterator SomeClass::find(std::string aName)
{
ObjectList::iterator result = ObjectList.begin();
while (result != ObjectList.end())
{
if (aName == result->second.name())
return result;
result++;
}
return result;
}
and for reorgObjectList:
bool SomeClass::reorgObjectList()
{
ObjectList::iterator i=ObjectList.begin();
int j=1;
for (i = ObjectList.begin(); i != ObjectList.end(); ++i)
{
if(j!=i->second.order())
i->second.order(j);
j++;
}
return true;
}
Any suggestions???
Well you are keying on the size of the map, this seems like it may cause your problems.
So if you have 3 things in the map you will have
1 => Obj1
2 => Obj2
3 => Obj3
if you remove one of these elements, say at 1, you will have
2 => Obj2
3 => Obj3
then later you go to insert, and set the key to "size() + 1", size will return 2, and you will try to insert at key 2 + 1 == 3. 3 is already taken. So it will either get overriden or fail (not sure how your find is working above).
Instead of inserting at the size + 1, I would check the last key and increment by 1 if thats how you want to manage the key.

Can you remove elements from a std::list while iterating through it?

I've got code that looks like this:
for (std::list<item*>::iterator i=items.begin();i!=items.end();i++)
{
bool isActive = (*i)->update();
//if (!isActive)
// items.remove(*i);
//else
other_code_involving(*i);
}
items.remove_if(CheckItemNotActive);
I'd like remove inactive items immediately after update them, inorder to avoid walking the list again. But if I add the commented-out lines, I get an error when I get to i++: "List iterator not incrementable". I tried some alternates which didn't increment in the for statement, but I couldn't get anything to work.
What's the best way to remove items as you are walking a std::list?
You have to increment the iterator first (with i++) and then remove the previous element (e.g., by using the returned value from i++). You can change the code to a while loop like so:
std::list<item*>::iterator i = items.begin();
while (i != items.end())
{
bool isActive = (*i)->update();
if (!isActive)
{
items.erase(i++); // alternatively, i = items.erase(i);
}
else
{
other_code_involving(*i);
++i;
}
}
You want to do:
i= items.erase(i);
That will correctly update the iterator to point to the location after the iterator you removed.
You need to do the combination of Kristo's answer and MSN's:
// Note: Using the pre-increment operator is preferred for iterators because
// there can be a performance gain.
//
// Note: As long as you are iterating from beginning to end, without inserting
// along the way you can safely save end once; otherwise get it at the
// top of each loop.
std::list< item * >::iterator iter = items.begin();
std::list< item * >::iterator end = items.end();
while (iter != end)
{
item * pItem = *iter;
if (pItem->update() == true)
{
other_code_involving(pItem);
++iter;
}
else
{
// BTW, who is deleting pItem, a.k.a. (*iter)?
iter = items.erase(iter);
}
}
Of course, the most efficient and SuperCool® STL savy thing would be something like this:
// This implementation of update executes other_code_involving(Item *) if
// this instance needs updating.
//
// This method returns true if this still needs future updates.
//
bool Item::update(void)
{
if (m_needsUpdates == true)
{
m_needsUpdates = other_code_involving(this);
}
return (m_needsUpdates);
}
// This call does everything the previous loop did!!! (Including the fact
// that it isn't deleting the items that are erased!)
items.remove_if(std::not1(std::mem_fun(&Item::update)));
I have sumup it, here is the three method with example:
1. using while loop
list<int> lst{4, 1, 2, 3, 5};
auto it = lst.begin();
while (it != lst.end()){
if((*it % 2) == 1){
it = lst.erase(it);// erase and go to next
} else{
++it; // go to next
}
}
for(auto it:lst)cout<<it<<" ";
cout<<endl; //4 2
2. using remove_if member funtion in list:
list<int> lst{4, 1, 2, 3, 5};
lst.remove_if([](int a){return a % 2 == 1;});
for(auto it:lst)cout<<it<<" ";
cout<<endl; //4 2
3. using std::remove_if funtion combining with erase member function:
list<int> lst{4, 1, 2, 3, 5};
lst.erase(std::remove_if(lst.begin(), lst.end(), [](int a){
return a % 2 == 1;
}), lst.end());
for(auto it:lst)cout<<it<<" ";
cout<<endl; //4 2
4. using for loop , should note update the iterator:
list<int> lst{4, 1, 2, 3, 5};
for(auto it = lst.begin(); it != lst.end();++it){
if ((*it % 2) == 1){
it = lst.erase(it); erase and go to next(erase will return the next iterator)
--it; // as it will be add again in for, so we go back one step
}
}
for(auto it:lst)cout<<it<<" ";
cout<<endl; //4 2
Use std::remove_if algorithm.
Edit:
Work with collections should be like:
prepare collection.
process collection.
Life will be easier if you won't mix this steps.
std::remove_if. or list::remove_if ( if you know that you work with list and not with the TCollection )
std::for_each
The alternative for loop version to Kristo's answer.
You lose some efficiency, you go backwards and then forward again when deleting but in exchange for the extra iterator increment you can have the iterator declared in the loop scope and the code looking a bit cleaner. What to choose depends on priorities of the moment.
The answer was totally out of time, I know...
typedef std::list<item*>::iterator item_iterator;
for(item_iterator i = items.begin(); i != items.end(); ++i)
{
bool isActive = (*i)->update();
if (!isActive)
{
items.erase(i--);
}
else
{
other_code_involving(*i);
}
}
Here's an example using a for loop that iterates the list and increments or revalidates the iterator in the event of an item being removed during traversal of the list.
for(auto i = items.begin(); i != items.end();)
{
if(bool isActive = (*i)->update())
{
other_code_involving(*i);
++i;
}
else
{
i = items.erase(i);
}
}
items.remove_if(CheckItemNotActive);
Removal invalidates only the iterators that point to the elements that are removed.
So in this case after removing *i , i is invalidated and you cannot do increment on it.
What you can do is first save the iterator of element that is to be removed , then increment the iterator and then remove the saved one.
If you think of the std::list like a queue, then you can dequeue and enqueue all the items that you want to keep, but only dequeue (and not enqueue) the item you want to remove. Here's an example where I want to remove 5 from a list containing the numbers 1-10...
std::list<int> myList;
int size = myList.size(); // The size needs to be saved to iterate through the whole thing
for (int i = 0; i < size; ++i)
{
int val = myList.back()
myList.pop_back() // dequeue
if (val != 5)
{
myList.push_front(val) // enqueue if not 5
}
}
myList will now only have numbers 1-4 and 6-10.
Iterating backwards avoids the effect of erasing an element on the remaining elements to be traversed:
typedef list<item*> list_t;
for ( list_t::iterator it = items.end() ; it != items.begin() ; ) {
--it;
bool remove = <determine whether to remove>
if ( remove ) {
items.erase( it );
}
}
PS: see this, e.g., regarding backward iteration.
PS2: I did not thoroughly tested if it handles well erasing elements at the ends.
You can write
std::list<item*>::iterator i = items.begin();
while (i != items.end())
{
bool isActive = (*i)->update();
if (!isActive) {
i = items.erase(i);
} else {
other_code_involving(*i);
i++;
}
}
You can write equivalent code with std::list::remove_if, which is less verbose and more explicit
items.remove_if([] (item*i) {
bool isActive = (*i)->update();
if (!isActive)
return true;
other_code_involving(*i);
return false;
});
The std::vector::erase std::remove_if idiom should be used when items is a vector instead of a list to keep compexity at O(n) - or in case you write generic code and items might be a container with no effective way to erase single items (like a vector)
items.erase(std::remove_if(begin(items), end(items), [] (item*i) {
bool isActive = (*i)->update();
if (!isActive)
return true;
other_code_involving(*i);
return false;
}));
do while loop, it's flexable and fast and easy to read and write.
auto textRegion = m_pdfTextRegions.begin();
while(textRegion != m_pdfTextRegions.end())
{
if ((*textRegion)->glyphs.empty())
{
m_pdfTextRegions.erase(textRegion);
textRegion = m_pdfTextRegions.begin();
}
else
textRegion++;
}
I'd like to share my method. This method also allows the insertion of the element to the back of the list during iteration
#include <iostream>
#include <list>
int main(int argc, char **argv) {
std::list<int> d;
for (int i = 0; i < 12; ++i) {
d.push_back(i);
}
auto it = d.begin();
int nelem = d.size(); // number of current elements
for (int ielem = 0; ielem < nelem; ++ielem) {
auto &i = *it;
if (i % 2 == 0) {
it = d.erase(it);
} else {
if (i % 3 == 0) {
d.push_back(3*i);
}
++it;
}
}
for (auto i : d) {
std::cout << i << ", ";
}
std::cout << std::endl;
// result should be: 1, 3, 5, 7, 9, 11, 9, 27,
return 0;
}
I think you have a bug there, I code this way:
for (std::list<CAudioChannel *>::iterator itAudioChannel = audioChannels.begin();
itAudioChannel != audioChannels.end(); )
{
CAudioChannel *audioChannel = *itAudioChannel;
std::list<CAudioChannel *>::iterator itCurrentAudioChannel = itAudioChannel;
itAudioChannel++;
if (audioChannel->destroyMe)
{
audioChannels.erase(itCurrentAudioChannel);
delete audioChannel;
continue;
}
audioChannel->Mix(outBuffer, numSamples);
}