1

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?

Kate Gregory
  • 18,808
  • 8
  • 56
  • 85
markzzz
  • 47,390
  • 120
  • 299
  • 507

4 Answers4

3

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;
});
Mohamad Elghawi
  • 2,071
  • 10
  • 14
  • find_if has linear search time. it defeats the object of using a map. – Richard Hodges Apr 28 '16 at 15:24
  • How can I pass that `60` value? Its in a variable outside that scope. – markzzz Apr 28 '16 at 15:28
  • I've updated my answer to address your follow up question but I have to agree with @Richard Hodges. – Mohamad Elghawi Apr 28 '16 at 15:30
  • @RichardHodges `multiset` is ordered. You're thinking of `unordered_multiset`. – Jonathan Mee Apr 28 '16 at 15:35
  • @JonathanMee; what if I copy elements I need on another list and I change its Comparator? Such as struct IMidiMsgExtCompByNote { bool operator()(const IMidiMsgExt& lhs, const IMidiMsgExt& rhs) { return lhs.mNote() < rhs.mNote(); } }; – markzzz Apr 28 '16 at 15:39
  • You cannot change a `multiset`'s comparator, that's part of it's type. But if you were to copy the elements to a new `multiset` which used a different comparator that would be legal. – Jonathan Mee Apr 28 '16 at 15:45
  • Yes, I can insert "temporanely" elements that I'll to search on another multiset. How would you define that comp? So that I can search on it by `mNote`. – markzzz Apr 28 '16 at 15:51
  • @JonathanMee his search predicate is based on a different property of the midi message to the sorting. He's sorting by time, but deleting by note value. – Richard Hodges Apr 28 '16 at 15:55
  • @RichardHodges: yes. But I can have a list that insert by time, and a new one (which I populate dynamically) that could be by note value. How would you set this new one multiset/comparator? Example? If I set the new Comparator I've write in the comment above, when I use `playingNotes.find(60)` it says `'std::_Tree_const_iterator>> std::_Tree>::find(const IMidiMsgExt &) const': cannot convert argument 1 from 'int' to 'const IMidiMsgExt &'` – markzzz Apr 28 '16 at 15:58
  • @RichardHodges Perhaps the question needs clarification. I took that to mean he wanted to delete the `MidiMsgExt` with the smallest `mTime` that had a `mNote` of 60. – Jonathan Mee Apr 28 '16 at 16:01
  • @JonathanMee: no. I want to delete the first note that has `mNote` of 60. No matter the `mTime` when deleting. That's why I could use two list. – markzzz Apr 28 '16 at 16:04
  • @paizza So you cannot use `playingNotes` ordering to search for the `MidiMsgExt`. What then are you calling the "first note that has `mNote` of 60"? Are you creating another `multiset` that sorts on another criteria? If so what's that criteria? – Jonathan Mee Apr 28 '16 at 16:08
  • That's what I'm trying to ask :) If I can set two different comparator (one for inserting, which is `mTick`, and one for findm which is `mNote`) than I'll use one list. Else, on the first list (where I add) I can use the comparator I've posted. On another one (which I'll populate with MidiMsgExt objects extracted from the first list) I could use `mNote` as key? So `.find` acts on key? – markzzz Apr 28 '16 at 16:40
  • I mean: this `struct IMidiMsgExtCompByNoteNumber { bool operator()(const IMidiMsgExt& lhs, const IMidiMsgExt& rhs) { return lhs.mNote < rhs.mNote; } };` will set as key `mNote` right? And find will search for that key? I've tried but it doesn't works. Maybe I add another question. – markzzz Apr 28 '16 at 16:45
  • @paizza So you're saying that you want to erase the first `IMidiMsgExt` that was inserted? To do that you'll need a member of `IMidiMsgExt` which keeps track of the order in which the elements were created. – Jonathan Mee Apr 28 '16 at 17:59
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/110564/discussion-between-paizza-and-jonathan-mee). – markzzz Apr 28 '16 at 18:17
0

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);
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • How would you set the key so? Can't `mNote` become the key? (even if I can have objects with the same key at that point). – markzzz Apr 28 '16 at 15:29
  • I must be misunderstanding something here. How is `midi_messages` different from a `multiset` They both sort their contents based on a comparator? – Jonathan Mee Apr 28 '16 at 16:16
  • @JonathanMee it's offering the same functionality with (most likely) better performance. In all the tests I have ever run, a sorted vector easily outperforms set/map/multiset/multimap – Richard Hodges Apr 28 '16 at 16:22
0

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

Community
  • 1
  • 1
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
0

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]
Joaquín M López Muñoz
  • 5,243
  • 1
  • 15
  • 20