How can I set two kind of comparator (one for insert, one for find) on this multiset?

蹲街弑〆低调 提交于 2019-12-12 06:36:17

问题


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 IMidiMsgExts 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:

  1. use a sorted vector (sorted by timestamp) as my 'set'.
  2. this allows me to search in logarithmic time, same as a set
  3. 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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!