问题
I have declared this STL multiset
:
multiset<IMidiMsgExt, IMidiMsgExtComp> playingNotes;
and my comparator is:
struct IMidiMsgExtComp {
bool operator()(const IMidiMsgExt& lhs, const IMidiMsgExt& rhs) {
return lhs.mTick < rhs.mTick;
}
};
and this serves to me well on .insert
:
playingNotes.insert(midiMessage);
it inserts (and than orders) the item having the min mTick
at the top and the max mTick
in the bottom of the list. So its ordered by mTick
, and every .begin()
will return a IMidiMsgExt
object with the min mTick
value.
Now, I'd like to find inside this list the first element that have, on another field called mNote
(which is int
), the value 60
, and than remove it:
auto iteratorItemFound = playingNotes.find(60);
playingNotes.erase(iteratorItemFound );
but how can I define on which field the list should search? Another comparator?
回答1:
Use std::find_if.
int value = 60;
auto iteratorItemFound = std::find_if(std::begin(playingNotes), std::end(playingNotes), [value](const IMidiMsgExt& msg)
{
return msg.mNote == value;
});
回答2:
I'd agree with Mohamad Elghawi's answer. But it is incomplete.
Your actual code will use a find_if, just like this:
const auto it = find_if(cbegin(playingNotes), cend(playingNotes), [value = int{60}](const auto& i){return i.mNote == value;});
if(it != cend(playingNotes)) {
playingNotes.erase(it);
}
This will remove the IMidiMsgExt
with the lowest mTick
value which has an mNote
of 60 (or whatever value
was initialized to.) If there are multiple IMidiMsgExt
s in playingNotes
that tie for the lowest, the IMidiMsgExt
that has been in playingNotes
the longest will be removed.
You're explanation of the problem was a little sparse, so I've created and solved a Minimal, Complete, Verifiable Example here: http://ideone.com/oFQ4rS
回答3:
What you want to do is not possible (while keeping the logarithmic search properties of the map). The reason is that the find
method takes an instance of key_type
, so even writing a custom comparator which has overloads for comparing your key_type against ints will not work.
The idea of a key_type
is that it's an immutable and cheap object to construct - i.e. you should be unafraid to treat it as a value that can be copied.
Edit:
Here's how I may approach it.
Synopsis:
- use a sorted vector (sorted by timestamp) as my 'set'.
- this allows me to search in logarithmic time, same as a set
- but I can now search across the set quickly (no pointer de-referencing and good cache locality)
code:
#include <vector>
#include <algorithm>
struct midi_message
{
midi_message(int timestamp, int note)
: _timestamp(timestamp)
, _note(note)
{}
int timestamp() const { return _timestamp; }
int note() const { return _note; }
private:
int _timestamp, _note;
};
struct earlier
{
bool operator()(const midi_message& l, const midi_message& r) const {
return l.timestamp() < r.timestamp();
}
bool operator()(const midi_message& l, const int& r) const {
return l.timestamp() < r;
}
};
struct midi_messages
{
// insert messages, keeping the map sorted by timestamp
void add(midi_message m) {
auto i = std::lower_bound(std::begin(_data),
std::end(_data),
m.timestamp(),
earlier());
_data.insert(i, std::move(m));
}
bool remove_first_note_like(int note)
{
auto i = std::find_if(std::begin(_data),
std::end(_data),
[note](auto& msg)
{ return msg.note() == note; });
if (i != std::end(_data)) {
_data.erase(i);
return true;
}
return false;
}
std::size_t remove_all_before(int timestamp)
{
auto new_begin = std::lower_bound(std::begin(_data),
std::end(_data),
timestamp,
[](auto& msg, auto& timestamp) {
return msg.timestamp() < timestamp;
});
_data.erase(std::begin(_data), new_begin);
}
private:
std::vector<midi_message> _data;
};
int main()
{
midi_messages messages;
messages.add(midi_message(1000, 60));
messages.add(midi_message(1000, 60));
messages.add(midi_message(1000, 60));
messages.add(midi_message(1001, 60));
messages.add(midi_message(1002, 70));
messages.add(midi_message(1002, 60));
messages.remove_first_note_like(60);
messages.remove_all_before(1001);
}
回答4:
Solution with Boost.MultiIndex:
Live Coliru Demo
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/member.hpp>
using namespace boost::multi_index;
struct IMidiMsgExt
{
int mTick;
int mTone;
};
using MidiSet=multi_index_container<
IMidiMsgExt,
indexed_by<
ordered_unique<member<IMidiMsgExt,int,&IMidiMsgExt::mTick>>,
hashed_non_unique<member<IMidiMsgExt,int,&IMidiMsgExt::mTone>>
>
>;
#include <iostream>
int main()
{
MidiSet m={{0,100},{2,60},{3,150},{5,60},{1,200},{4,90}};
std::cout<<"before erasing:\n";
for(const auto& msg:m)std::cout<<"["<<msg.mTick<<","<<msg.mTone<<"]";
std::cout<<"\n";
m.get<1>().erase(60);
std::cout<<"after erasing:\n";
for(const auto& msg:m)std::cout<<"["<<msg.mTick<<","<<msg.mTone<<"]";
std::cout<<"\n";
}
Output
before erasing: [0,100][1,200][2,60][3,150][4,90][5,60] after erasing: [0,100][1,200][3,150][4,90]
来源:https://stackoverflow.com/questions/36918760/how-can-i-set-two-kind-of-comparator-one-for-insert-one-for-find-on-this-mult