Forum: PC-Programmierung C++: Element in std::set finden?


von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Hi, steh grad auf dem Schlauch:

Wie finde ich ein Element in std::set ?
1
#include <set>
2
#include <iostream>
3
4
using S = std::set<int>;
5
using std::cout;
6
using std::endl;
7
8
int main()
9
{
10
    S s = { 1, 2 };
11
    
12
    for (int i = 0; i < 3; ++i)
13
    {
14
        auto f = s.find (i);
15
        if (???)
16
            cout << "found " << *f << endl;
17
        else
18
            cout << "not found" << endl;
19
    }
20
21
    return 0;
22
}

Sprache ist C++11.

Was kommt an Stelle von ???

Und im Endeffekt brauche ist einen Zugriff, der erlaubt, das gefundene 
Element zu ändern (Sortierreihenfolge bzw. search key wird sich dadurch 
nicht ändern).

von cppbert (Gast)


Lesenswert?


von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Funktioniert nicht:
1
error: 'struct std::_Rb_tree_const_iterator<int>' has no member named 'end'

von nfet (Gast)


Lesenswert?

??? --> f != s.end()

von cppbert (Gast)


Lesenswert?

Johann L. schrieb:
> Funktioniert nicht:
> error: 'struct std::_Rb_tree_const_iterator<int>' has no member named
> 'end'

Dann solltest du das Beispiel eben richtig kopieren, bei 3 Zeilen sollte 
das klappen :)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

nfet schrieb:
> ??? --> f != s.end()

D.h ich muss immer den Container mit rumschleppen?

von Carl D. (jcw2)


Lesenswert?

Johann L. schrieb:
> nfet schrieb:
>> ??? --> f != s.end()
>
> D.h ich muss immer den Container mit rumschleppen?

Wenn du den nicht mehr "mit rumschleppst", dann ist auch der Iterator, 
den find liefert, nicht mehr gültig.

: Bearbeitet durch User
von Thomas W. (goaty)


Lesenswert?

Geht ein Std:find_if aus Algorithm library, mit einem Lambda drin, denke 
ich.

std:find_if(s.begin(), s.end(), []( ... ){ Return ... });

So etwa

: Bearbeitet durch User
von Carl D. (jcw2)


Lesenswert?

Thomas W. schrieb:
> Geht ein Std:find_if aus Algorithm library, mit einem Lambda drin, denke
> ich.
>
> std:find_if(s.begin(), s.end(), []( ... ){ Return ... });
>
> So etwa

set.find() dürfte schneller sein, da es binäre Suche macht.
std::find() hangelt sich an begin() entlang.

von cppbert (Gast)


Lesenswert?

Johann L. schrieb:
> nfet schrieb:
>> ??? --> f != s.end()
>
> D.h ich muss immer den Container mit rumschleppen?

Egal in welcher Sprache du in einem Container suchst brauchst du den 
Container und warum oder wofuer willst du die .end pruefung 
hinauszoegern?

So lange sich dein Container nicht aendert bleibt der Iterator valide

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Carl D. schrieb:
> Johann L. schrieb:
>> nfet schrieb:
>>> ??? --> f != s.end()
>>
>> D.h ich muss immer den Container mit rumschleppen?
>
> Wenn du den nicht mehr "mit rumschleppst", dann ist auch der Iterator,
> den find liefert, nicht mehr gültig.

Wieso dass denn?  Kann man keine Funktion damit aufrufen?  Kann ja auch 
eine Funktion mit nem Zeiger auf ne lokale Variable aufrufen.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

cppbert schrieb:
> Egal in welcher Sprache du in einem Container suchst brauchst du den
> Container und warum oder wofuer willst du die .end pruefung
> hinauszoegern?

Hat mich nur gewundert, dass das gefundene Objekt es nicht selbst weiss 
und immer noch was externes braucht.


Und wie überprüft man, ob der Iterator das letzte Element ist?
1
using S = std::set<int>;
2
using std::cout;
3
using std::endl;
4
5
int main()
6
{
7
    S s = { 1, 2 };
8
9
    for (auto i : s)
10
    {
11
        if (i == s.end())
12
            cout << i << " last element" << endl;
13
    }
14
15
    return 0;
16
}

von Carl D. (jcw2)


Lesenswert?

Johann L. schrieb:
> cppbert schrieb:
>> Egal in welcher Sprache du in einem Container suchst brauchst du den
>> Container und warum oder wofuer willst du die .end pruefung
>> hinauszoegern?
>
> Hat mich nur gewundert, dass das gefundene Objekt es nicht selbst weiss
> und immer noch was externes braucht.
>
>
> Und wie überprüft man, ob der Iterator das letzte Element ist?
>
>
1
> using S = std::set<int>;
2
> using std::cout;
3
> using std::endl;
4
> 
5
> int main()
6
> {
7
>     S s = { 1, 2 };
8
> 
9
>     for (auto i : s)
10
>     {
11
>         if (i == s.end())
12
>             cout << i << " last element" << endl;
13
>     }
14
> 
15
>     return 0;
16
> }

Genau so, wie es bei dir steht, z.B.
1
for(auto it = s.begin(); it!=s.end(); it++) 
2
    mach_was_mit(*it);
3
4
// oder kurz:
5
  for(auto& e: s)
6
    mach_was_mit(e);

: Bearbeitet durch User
von nfet (Gast)


Lesenswert?

Johann L. schrieb:
> Hat mich nur gewundert, dass das gefundene Objekt es nicht selbst weiss
> und immer noch was externes braucht.

Dinge wie std::optional gibt es (leider) erst seit neuestem. Daher 
"weiß" der Rückgabe Zeiger nicht, obs was geworden ist.

> Und wie überprüft man, ob der Iterator das letzte Element ist?

Nicht geprüft aber: f == (s.rend())++) sollte auf das letzte Element 
prüfen. Aber du solltest dir überlegen, ob das hier überhaupt sinnvoll 
ist. Denn das was du aus meinen Code bekommst ist eventuell nicht das 
Element mit dem größten Key. Dieses Element zu bekommen ginge eventuell 
irgendwie mit lower bound oder so.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Ok, danke für die Tipps.  Ich kopiers einfach in ein Array und iteriere 
über den Index.  Old School.

von Carl D. (jcw2)


Lesenswert?

Johann L. schrieb:
> Ok, danke für die Tipps.  Ich kopiers einfach in ein Array und iteriere
> über den Index.  Old School.

Also wenn es um das letzte Element im Set geht, dann ist rbegin() ein 
Iterator auf das letzte Element. Und da ein Set sortiert ist (im 
Gegensatz zum std::unordered_set), ist "Letztes" auch im Sinne der 
gewählten Sortierung das "Größte".
Range-based for loop braucht übrigens keine Endeerkennung, das macht der 
Compiler selber. Er braucht dazu einen Container, der begin() und end() 
implementiert, die jeweils was zurückgeben, was die Operatoren * ++ und 
!= unterstützt.

von Rolf M. (rmagnus)


Lesenswert?

Mir ist nicht so ganz klar, was du nun eigentlich willst. Erst suchst du 
ein Element mit einem bestimmten Wert (was genau der Sinn eine set ist), 
dann willst du auf einmal das letzte Element des set haben. Was willst 
du eigentlich erzielen?

von M.K. B. (mkbit)


Lesenswert?

In C++ bedeuten die Iteratoren folgendes:
begin(): zeigt auf das erste Element
end(): zeigt HINTER das letzte Element (hier kannst du auch keinen Wert 
auslesen)
Wenn der Container leer ist, dann ist begin() == end().
Bei Suchfunktionen verwendet man außerdem end() als Return, wenn nichts 
gefunden wurde.

Durch die Verwendung von auto sind bei dir die Typen nicht gut 
ersichtlich. Versuche deinen Code mal ohne auto direkt mit den Typen zu 
schreiben. Wenn du es verstanden hast, kannst du auto ja wieder 
verwenden.
1
for (int i = 0; i < 3; ++i)
2
{
3
    std::set<int>::iterator f = s.find (i);
4
}
5
6
for (int i : s)
7
{
8
    if (i == s.end()) // iterator mit integer vergleichen???
9
         cout << i << " last element" << endl;
10
}

nfet schrieb:
> Nicht geprüft aber: f == (s.rend())++) sollte auf das letzte Element
> prüfen.
Das gilt aber nur, wenn das set nicht leer ist. Sonst ist es undefined 
behaviour, weil rend() == begin() und begin nicht dekrementiert werden 
darf.

Johann L. schrieb:
> Ok, danke für die Tipps.  Ich kopiers einfach in ein Array und iteriere
> über den Index.  Old School.
Das klingt für mich nach fehlendem Verständnis (nicht böse gemeint).

Erklär uns doch vielleicht mal, was dein Problem genau ist. Für mich 
sieht es so aus, als ob du versuchst den ungeeignenten Lösungsansatz 
hinzubiegen.

Johann L. schrieb:
> Und im Endeffekt brauche ist einen Zugriff, der erlaubt, das gefundene
> Element zu ändern (Sortierreihenfolge bzw. search key wird sich dadurch
> nicht ändern).
Beim set ist der Key und das Element das selbe. Deshalb ist auch der 
Iterator beim set const, damit du die Elemente nicht ändern kannst.
Du suchst vermutlich eine map. Dort kannst du dann auch den Value 
ändern, ohne den Key zu ändern. Wenn deine map aber immer die Keys von 0 
bis N hat, dann ist es im Prinzip ein array oder vector.

In C++20 hat man übrigens eine Funktion für diesen Fall eingebaut:
1
 s.contains(i);

von Ex C++ Programmierer (Gast)


Lesenswert?

M.K. B. schrieb:
> In C++20 hat man übrigens eine Funktion für diesen Fall eingebaut:

Puh, ganz schön innovativ, im Jahre 2020 eine Testfunktion ob eine 
Element in einem Set vorhanden ist, zu planen... Denn veröffentlicht ist 
C++20 ja noch nicht...

Man, was bin ich froh diesen Quark hinter mir gelassen zu haben. 
Inzwischen ist mir es es peinlich, ein C++-Fanboy gewesen zu sein und 
anderen C++ auch empfohlen zu haben...

von zitter_ned_aso (Gast)


Lesenswert?

Ex C++ Programmierer schrieb:
> Man, was bin ich froh diesen Quark hinter mir gelassen zu haben.
Bei welcher PRogrammiersprache bist du jetzt gelandet?

von Carl D. (jcw2)


Lesenswert?

Ex C++ Programmierer schrieb:
> M.K. B. schrieb:
>> In C++20 hat man übrigens eine Funktion für diesen Fall eingebaut:
>
> Puh, ganz schön innovativ, im Jahre 2020 eine Testfunktion ob eine
> Element in einem Set vorhanden ist, zu planen... Denn veröffentlicht ist
> C++20 ja noch nicht...

Eine Abkürzung für die, die kein "s.find(was)!=s.end()" hinbekommen.

> Man, was bin ich froh diesen Quark hinter mir gelassen zu haben.
> Inzwischen ist mir es es peinlich, ein C++-Fanboy gewesen zu sein und
> anderen C++ auch empfohlen zu haben...

Und jetzt? Fe2O3?

: Bearbeitet durch User
von Jemand (Gast)


Lesenswert?

Carl D. schrieb:
> Eine Abkürzung für die, die kein "s.find(was)!=s.end()" hinbekommen.

Ballerst du wirklich alles mit linearen Suchen voll?

von Carl D. (jcw2)


Lesenswert?

Jemand schrieb:
> Carl D. schrieb:
>> Eine Abkürzung für die, die kein "s.find(was)!=s.end()" hinbekommen.
>
> Ballerst du wirklich alles mit linearen Suchen voll?

Es ging um ein std::set. Das find'et binär. Auch wenn man's nicht weiß.

von Jemand (Gast)


Lesenswert?

Carl D. schrieb:
> Auch wenn man's nicht weiß.

Ja, habe die falsche Dokumentation erwischt.

von Wilhelm M. (wimalopaan)


Lesenswert?

Johann L. schrieb:
> Ok, danke für die Tipps.  Ich kopiers einfach in ein Array und iteriere
> über den Index.  Old School.

DAS ist lin. Suche. Kann aber ggf. schneller sein ...

Allerdings: warum willst Du das machen. Was ist Dein Problem mit dem 
Iterator?

von Carl D. (jcw2)


Lesenswert?

Wilhelm M. schrieb:
> Johann L. schrieb:
>> Ok, danke für die Tipps.  Ich kopiers einfach in ein Array und iteriere
>> über den Index.  Old School.
>
> DAS ist lin. Suche. Kann aber ggf. schneller sein ...

Lineare Suche kann schneller sein, da moderne (PC-)HW sehr gut im 
vorauslesen sequenziell gelesener Daten ist. Wenn man aber vorher das 
std::set in ein C-Array kopiert, dann sicher nicht.

> Allerdings: warum willst Du das machen. Was ist Dein Problem mit dem
> Iterator?

Johann ist nach eigener Aussage (von vor einiger Zeit, und sehr frei 
übersetzt) noch auf dem Weg. Das wird noch ;-)

von Dirk K. (merciless)


Lesenswert?

Man sollte std:: - Funktionen nur dann benutzen,
wenn der STL-Container keine eigene Methode dafür
anbietet. Weil wenn das der Fall ist, ist diese
Funktion für den jeweiligen Container optimiert
(bestes Bsp: set::find() == binäre Suche,
std::find() == lineare Suche).

merciless

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Hier einetwas größeres Beispiel:
1
#include <set>
2
#include <iostream>
3
4
struct PQ
5
{
6
    int p;
7
    mutable int q;
8
    PQ (int p = 0, int q = 0) : p(p), q(q) {}
9
10
    bool operator () (const PQ& a, const PQ& b)
11
    {
12
        return a.p < b.p;
13
    }
14
15
    void count () const
16
    {
17
        ++q;
18
    }
19
};
20
21
struct B
22
{
23
    typedef std::set<PQ, PQ> PQs;
24
    PQs pqs;
25
26
    void ping (int p)
27
    {
28
        const auto& pq = pqs.find (p);
29
30
        if (pq != pqs.end())
31
            pq->count();
32
        else
33
            pqs.insert (PQ (p, 1));
34
    }
35
};
36
37
std::ostream& operator << (std::ostream& ost, const PQ& pq)
38
{
39
    return ost << "(" << pq.p << ", " << pq.q << ")";
40
}
41
42
std::ostream& operator << (std::ostream& ost, const B& b)
43
{
44
    size_t n_pq = b.pqs.size();
45
    for (const PQ& pq : b.pqs)
46
        ost << pq << (--n_pq ? ", " : "");
47
    return ost;
48
}
49
50
51
int main()
52
{
53
    B b;
54
55
    b.ping (0);
56
    b.ping (2);
57
    b.ping (2);
58
59
    std::cout << b << std::endl;
60
61
    return 0;
62
}

Zum einen ist das mutable hässlich in
1
    mutable int q;
Zum anderen der Test auf's letzte Element in
1
    size_t n_pq = b.pqs.size();
2
    for (const PQ& pq : b.pqs)
3
        ... --n_pq ? ...;

: Bearbeitet durch User
von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Warum benutzt du da überhaupt ein set? Wäre ein map da nicht viel 
treffender? Dann hättest du auch nicht das Problem mit "mutable", was 
hier eigentlich etwas deplatziert ist. Die Klasse als ihren eigenen 
Vergleichsoperator definieren ist auch etwas kurios. So ginge es:
1
#include <map>
2
#include <iostream>
3
#include <iterator>
4
5
struct PQ
6
{
7
  int p, q;
8
  PQ (int p = 0, int q = 0) : p(p), q(q) {}
9
10
  void count () {
11
    ++q;
12
  }
13
};
14
15
bool operator < (const PQ& a, const PQ& b) {
16
  return a.p < b.p;
17
}
18
19
struct B
20
{
21
  using PQs = std::map<int, PQ>;
22
  PQs pqs;
23
24
  void ping (int p)
25
  {
26
    auto pq = pqs.find (p);
27
28
    if (pq != pqs.end())
29
      pq->second.count();
30
    else
31
      pqs.emplace (p, PQ {p, 1});
32
  }
33
};
34
35
std::ostream& operator << (std::ostream& ost, const PQ& pq)
36
{
37
  return ost << "(" << pq.p << ", " << pq.q << ")";
38
}
39
40
std::ostream& operator << (std::ostream& ost, const B& b) {
41
  for (auto iter = b.pqs.begin (), next = std::next (iter); ; iter = next, std::advance (next, 1)) {
42
    ost << iter->second << (next != b.pqs.end () ? ", " : "");
43
    if (next == b.pqs.end ())
44
      break;
45
  }
46
  return ost;
47
}
48
49
50
int main()
51
{
52
  B b;
53
54
  b.ping (0);
55
  b.ping (2);
56
  b.ping (2);
57
58
  std::cout << b << std::endl;
59
60
  return 0;
61
}

Die Ausgabe ist so zwar auch nicht so richtig schön, aber etwas direkter 
ohne die Größe abzufragen. Iterator-basierte Algorithmen sind immer 
etwas umständlich, aber so würde das praktisch mit jedem Container 
funktionieren.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Ich kann schon nachvollziehen, warum er ein set verwenden will. Das kann 
schon mal die erste Idee sein, denn bei der map hat man oft wie hier den 
Nachteil, dass man den Wert, nach dem nachher gesucht werden muss, immer 
duplizieren muss. Zusätzlich zum Element des Value ist es auch nochmal 
als Key enthalten. Die selben Daten doppelt zu halten, ist immer 
irgenwie unschön und birgt die Gefahr, dass sie ggf. nicht mehr 
konsistent sind.
Das Problem ist allerdings, dass bei den Stanard-Containern und 
-Smartpointern immer alles non-intrusiv ist, wodurch solche Dinge 
unschön, aber nicht vermeidbar werden.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Niklas G. schrieb:
> Warum benutzt du da überhaupt ein set?
> Wäre ein map da nicht viel treffender?

Die Dinge haben eine totale Ordnung.  Da ist ein RB-Tree doch besser al 
mit Hashing anzufangen.  Außerden wüsste ich incht, wie ich ein Hash 
berechnen soll.

> Dann hättest du auch nicht das Problem mit "mutable",
> was hier eigentlich etwas deplatziert ist.

"mutable" ist immer deplaziert irgendwie...

> Die Klasse als ihren eigenen Vergleichsoperator definieren ist auch etwas 
kurios. So ginge es:

> bool operator < (const PQ& a, const PQ& b) {
>   return a.p < b.p;
> }

Funktioniert nicht:
1
In file included from .../include/c++/bits/stl_tree.h:65:0,
2
                 from .../include/c++/set:60,
3
                 from ...:
4
.../include/c++/bits/stl_function.h: In instantiation of 'bool std::less<_Tp>::operator()(const _Tp&, const _Tp&) const [with _Tp = PQ]':
5
.../include/c++/bits/stl_tree.h:1547:38:   required from 'std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::iterator std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::find(const _Key&) [with _Key = PQ; _Val = PQ; _KeyOfValue = std::_Identity<PQ>; _Compare = std::less<PQ>; _Alloc = std::allocator<PQ>; std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::iterator = std::_Rb_tree_iterator<PQ>]'
6
.../include/c++/bits/stl_set.h:613:29:   required from 'std::set<_Key, _Compare, _Alloc>::iterator std::set<_Key, _Compare, _Alloc>::find(const key_type&) [with _Key = PQ; _Compare = std::less<PQ>; _Alloc = std::allocator<PQ>; std::set<_Key, _Compare, _Alloc>::iterator = std::_Rb_tree_const_iterator<PQ>; std::set<_Key, _Compare, _Alloc>::key_type = PQ]'
7
required from here
8
.../include/c++/bits/stl_function.h:237:22: error: ambiguous overload for 'operator<' in '__x < __y'
9
.../include/c++/bits/stl_function.h:237:22: note: candidates are:
10
note: bool PQ::operator<(const PQ&) const
11
note: bool operator<(const PQ&, const PQ&)

PQ hat bereits eine andere (algebraische) Ordnung, die aber weder eine 
Sortierung erlaubt noch gie gewünschte Reihenfolge liefern würde:
1
struct PQ
2
{
3
    ...
4
5
    bool operator < (const PQ& y) const
6
    {
7
        return p + q > y.p + y.q;
8
    }
9
};

Ist wie gesagt nur ein vereinfachtes einfaches Beispiel, das Programm 
ist komplexer.

> struct B
> {
>   using PQs = std::map<int, PQ>;

Ich will nicht die Komponenten doppeln.  Die sind nicht trivial 
kopierbar und belegen u.U. einiges an dynamisch allokiertem Speicher. 
Oder nimmt sich map Adresse oder Referenz des Keys?

Das eigentliche Programm / Datenstrukturen sid komplexer und nicht 
trivial.

>   for (auto iter = b.pqs.begin (), next = std::next (iter); ; iter =
> next, std::advance (next, 1)) {
>     ost << iter->second << (next != b.pqs.end () ? ", " : "");
>     if (next == b.pqs.end ())
>       break;

Also da finde ich meins wesentlich besser lesbar. Und weniger zu tippen.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Johann L. schrieb:
> Da ist ein RB-Tree doch besser al
> mit Hashing anzufangen.  Außerden wüsste ich incht, wie ich ein Hash
> berechnen soll.

std::map verwendet einen RB-Tree und keine Hashes und ist sortiert. 
std::unordered_map verwendet Hashes.

Johann L. schrieb:
> PQ hat bereits eine andere (algebraische) Ordnung, die aber weder eine
> Sortierung erlaubt noch gie gewünschte Reihenfolge liefern würde:

Achso, dann würde ich den Vergleichs-Operator aber in eine eigene Klasse 
packen:
1
struct ComparePQ{
2
   bool operator () (const PQ& a, const PQ& b)
3
    {
4
        return a.p < b.p;
5
    }
6
};
7
struct B
8
{
9
    typedef std::set<PQ, ComparePQ> PQs;
10
};

Johann L. schrieb:
> Ich will nicht die Komponenten doppeln.  Die sind nicht trivial
> kopierbar und belegen u.U. einiges an dynamisch allokiertem Speicher.

Welche jetzt? Hier wird nur der "int" Key gedoppelt. Die "int p" 
Variable könnte man aber auch entfernen und stattdessen halt immer auf 
den Key zugreifen.

Johann L. schrieb:
> Also da finde ich meins wesentlich besser lesbar. Und weniger zu tippen.

Das schon. Dafür weniger allgemein, ist in dem Fall aber egal.

von Peterchen (Gast)


Lesenswert?

Niklas G. schrieb:
> std::map verwendet einen RB-Tree

Genau genommen kann es verwenden, was es will, solange es die 
Spezifikationen einhält. Man sollte vielleicht eher sagen "die (zur 
Zeit) üblichen Implementationen verwenden ...".

von Rolf M. (rmagnus)


Lesenswert?

Peterchen schrieb:
> Niklas G. schrieb:
>> std::map verwendet einen RB-Tree
>
> Genau genommen kann es verwenden, was es will, solange es die
> Spezifikationen einhält.

Es ist zwar so, dass nicht festgelegt ist, wie es implementiert sein 
muss, aber meistens ist die Spezifikation so, dass es eigentlich kaum 
eine andere sinnvolle Möglichkeit gibt.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Niklas G. schrieb:
> Johann L. schrieb:
>> Ich will nicht die Komponenten doppeln.  Die sind nicht trivial
>> kopierbar und belegen u.U. einiges an dynamisch allokiertem Speicher.
>
> Welche jetzt? Hier wird nur der "int" Key gedoppelt. Die "int p"

Ja, in dem simplen Beispiel.  In echt ist es aber eben kein "int" 
sondern was anderes.  Ich weiß nicht, ob es hier hilft, 1000e Zeilen 
Code zu posten die noch nicht mal übersetzbar sind weil euch 
Bibliotheken dazu fehlen.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Johann L. schrieb:
> In echt ist es aber eben kein "int"
> sondern was anderes.

In dem Fall ist ein intrusiver Container sinnvoll, z.B. Boost.Intrusive 
set. Eine intrusive map macht keinen Sinn. Man muss den Speicher dann 
aber selbst verwalten, denn der intrusive Container sorgt nur für die 
Verlinkung. Das könne z.B. so aussehen (mit C++17 kompilieren):
1
#include <iostream>
2
#include <iterator>
3
#include <utility>
4
#include <memory>
5
#include <vector>
6
#include <boost/intrusive/set.hpp>
7
8
struct PQ : boost::intrusive::set_base_hook<> {
9
  int p, q;
10
  PQ (int p = 0, int q = 0) : p(p), q(q) {}
11
12
  void count () {
13
    ++q;
14
  }
15
};
16
17
struct PQComparator {
18
  bool operator () (const int& a, const int& b) const {
19
    return a < b;
20
  }
21
};
22
23
struct PQ_Get_P {
24
  using type = int;
25
  int operator () (const PQ& e) const { return e.p; }
26
};
27
28
struct B {
29
  using PQSet = boost::intrusive::set<PQ, boost::intrusive::key_of_value<PQ_Get_P>, boost::intrusive::compare <PQComparator>>;
30
  std::vector<std::unique_ptr<PQ>> pqMem;
31
  PQSet pqSet;
32
33
  void ping (int p)
34
  {
35
    auto pq = pqSet.find (p);
36
37
    if (pq != pqSet.end())
38
      pq->count();
39
    else {
40
      pqSet.insert (*pqMem.emplace_back (std::make_unique<PQ> (p, 1)));
41
    }
42
  }
43
};
44
45
std::ostream& operator << (std::ostream& ost, const PQ& pq) {
46
  return ost << "(" << pq.p << ", " << pq.q << ")";
47
}
48
49
template <typename Iter, typename F>
50
void foreach_checklast (Iter begin, Iter end, F&& f) {
51
  if (begin == end) return;
52
  
53
  for (auto next = std::next (begin); ; begin = next, std::advance (next, 1)) {
54
    std::forward<F> (f) (*begin, next == end);
55
    if (next == end)
56
      break;
57
  }
58
}
59
60
template <typename Container, typename F>
61
void foreach_checklast (const Container& cont, F&& f) {
62
  foreach_checklast (cont.cbegin (), cont.cend (), std::forward<F> (f));
63
}
64
65
std::ostream& operator << (std::ostream& ost, const B& b) {
66
  foreach_checklast (b.pqSet, [&] (const PQ& pq, bool last) {
67
    ost << pq << (!last ? ", " : "");
68
  });
69
  return ost;
70
}
71
72
int main () {
73
  B b;
74
75
  b.ping (0);
76
  b.ping (2);
77
  b.ping (2);
78
  b.ping (3);
79
  b.ping (3);
80
  b.ping (3);
81
  b.ping (3);
82
83
  std::cout << b << std::endl;
84
85
  return 0;
86
}

Dadurch braucht es den Speicher für den Key, also "int p", nur 1x. Den 
Algorithmus zum Iterieren mit Testen aufs letzte Element hab ich in 
einer Funktion gekapselt, sodass man ihn komfortabel nutzen kann.

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Niklas G. schrieb:
> mit C++17 kompilieren

Projekt ist in C++11.

Was ist an meiner std::set Lösung falsch?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Johann L. schrieb:
> Projekt ist in C++11.

Dann so:
1
pqMem.emplace_back (new PQ (p, 1));
2
pqSet.insert (*pqMem.back ());

Johann L. schrieb:
> Was ist an meiner std::set Lösung falsch?

Dass du die Elemente "in-place" änderst, so ist das bei std::set halt 
nicht gedacht. Das ist eine Ansammlung nicht-verändlicher Objekte. Dass 
sich das mit mutable "hacken" lässt ändert nichts daran.

https://stackoverflow.com/a/2217889/4730685

Die korrekte Standard-Lösung wäre std::map, aber wenn der Key dafür zu 
groß ist hilft eben boost::intrusive::set.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Niklas G. schrieb:
> Dass du die Elemente "in-place" änderst, so ist das bei std::set halt
> nicht gedacht.

D.h. das ist Undefined Behavior oder Ill Formed?

StackOverflow nennt u.a. mutable und const-cast als Lösung, falls Key 
nicht geändert wird.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Johann L. schrieb:
> D.h. das ist Undefined Behavior oder Ill Formed?

Weder noch, es ist zwar nicht verboten, aber nicht ganz im Sinne des 
std::set-Erfinders - wozu sonst gibt std::set nur konstante Referenzen 
heraus, und wozu sonst gibt es std::map? Eigentlich™ sollte der 
Vergleichsoperator für die set-Elemente alle Datenelemente mit 
einbeziehen, also auch q, was aber dessen Modifizierung verhindert. 
Nicht alles was in C++ möglich ist, ist auch sinnvoll... 
boost::intrusive::set ist genau was du brauchst.
const_cast braucht man eigentlich nie und ist ein Zeichen für verkehrte 
Architektur. "mutable" sollte das nach außen sichtbare Verhalten einer 
Klasse nicht verändern - tut es aber bei dir, denn das nach außen 
sichtbare "q" ist dadurch veränderlich.

von Carsten Sch. (Gast)


Lesenswert?

Carl D. schrieb:
> Thomas W. schrieb:
>> Geht ein Std:find_if aus Algorithm library, mit einem Lambda drin, denke
>> ich.
>>
>> std:find_if(s.begin(), s.end(), []( ... ){ Return ... });
>>
>> So etwa
>
> set.find() dürfte schneller sein, da es binäre Suche macht.
> std::find() hangelt sich an begin() entlang.

Lasst euch nicht von binärer Suche irritieren Bei wenigen Elementen kann 
eine lineare Suche schneller sein.

von Oliver S. (oliverso)


Lesenswert?

Deshalb sucht find auch bei wenigen Elementen linear.

Oliver

von Carl D. (jcw2)


Lesenswert?

Carsten Sch. schrieb:
> Carl D. schrieb:
>> Thomas W. schrieb:
>>> Geht ein Std:find_if aus Algorithm library, mit einem Lambda drin, denke
>>> ich.
>>>
>>> std:find_if(s.begin(), s.end(), []( ... ){ Return ... });
>>>
>>> So etwa
>>
>> set.find() dürfte schneller sein, da es binäre Suche macht.
>> std::find() hangelt sich an begin() entlang.
>
> Lasst euch nicht von binärer Suche irritieren Bei wenigen Elementen kann
> eine lineare Suche schneller sein.

Ja das ist mir klar, wobei diese dann auch in einem "zusammenhängenden 
Speicherbereich" gespeichert sein müssen. Das ist aus "lineare Suche" 
nicht erkennbar.

Oder der Lib-Developper das optimiert hat -> siehe Oliver S.

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Niklas G. schrieb:
> Johann L. schrieb:
>> D.h. das ist Undefined Behavior oder Ill Formed?
>
> Weder noch, es ist zwar nicht verboten, aber nicht ganz im Sinne des
> std::set-Erfinders

Es ist in meinem Sinne.  Das genügt mir.

> Eigentlich™ sollte der Vergleichsoperator für die set-Elemente
> alle Datenelemente mit einbeziehen,

Warum sollte man das tun? Die Äquivalenzklasse eines Elements wird 
eineindeutig durch p repräsentiert.  Warum sollte q einbezogen werden, 
was die Darstellung nicht-eindeutig machen würde, nur um irgendwann q 
wieder rauszufaktorisieren, um 'ne eindeutige Darstellung der 
Äquivalenzklassen zu erhalten?

> boost::intrusive::set ist genau was du brauchst.

Boost gibt's nicht (Coding-Rules).

Die C++ Fehlermeldungen bei dem kleinen Fehler sind schon tausende 
Zeilen lange, das ist echt was für Leute, die ihre Schwiegermutter 
gemeuchelt haben.  Mit Boost würde das kaum besser.

Von der Usability ist C++ echt der totale Kramp, momentan verwende ich 
das eh nur für Glue-Code.  Bei Gelegenheit werd ich mir mal anschauen 
wie man Python-Bindings bastelt.  Geht garantiert schneller als sich 
durch diesen C++ Krampf durchzubeißen.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Johann L. schrieb:
> Warum sollte man das tun? Die Äquivalenzklasse eines Elements wird
> eineindeutig durch p repräsentiert.

Und warum existiert q dann überhaupt? Wenn p alles relevante enthält, 
ist q überflüssig. Vielleicht sollte das q ja in eine eigene Klasse 
ausgelagert werden, und PQ enthält eine Referenz darauf.

Johann L. schrieb:
> Boost gibt's nicht (Coding-Rules).

Uff. Aber die Python Standard Bibliothek wäre erlaubt...? Boost ist ja 
praktisch die erweiterte C++ Standard Bibliothek, wer darauf verzichtet 
schränkt sich doch ziemlich ein.

Johann L. schrieb:
> Bei Gelegenheit werd ich mir mal anschauen
> wie man Python-Bindings bastelt.  Geht garantiert schneller als sich
> durch diesen C++ Krampf durchzubeißen.

Ja natürlich. C++ hat einen ganz anderen Zweck als Python. Wenn Python 
für die Aufgabe geeignet ist, ist es sinnlos, C++ zu nutzen. Wobei 
dynamisch typisierte Sprachen wieder andere Probleme haben. Ich 
persönlich bevorzuge (wenn auch kryptische) Compiler-Fehler über selten 
auftretende Laufzeit-Probleme, die nur auftreten wenn ein ganz 
bestimmter Codepfad durchlaufen wird in welchem an eine Funktion ein 
falscher Typ übergeben wird, der dann in eine bestimmte Variable 
geschrieben wird, irgendwann später wieder abgerufen, und sich dann erst 
als falsch herausstellt... Clang++ gibt übrigens verständlichere 
Fehlermeldungen aus.

von zer0@away (Gast)


Lesenswert?

Niklas G. schrieb:
> Clang++ gibt übrigens verständlichere Fehlermeldungen aus.

Bezeichnend ist, dass es anscheinend keine einzige IDE gibt, die einfach 
die ganzen Fehler in std:: und sonstwas hinter die Fehler in 
"User-Dateien" sortiert.
Typischerweise muss man einfach die ersten paar hundert Zeilen des 
Fehlertraces ignorieren und nach einem relevanten Dateinamen Ausschau 
halten. Der Rest (oder alte Anfang) sind nur Debugging-Details.

von Niklas Gürtler (Gast)


Lesenswert?

zer0@away schrieb:
> Typischerweise muss man einfach die ersten paar hundert Zeilen des
> Fehlertraces ignorieren und nach einem relevanten Dateinamen Ausschau
> halten.

Das reicht oft nicht, man muss oft schon schauen was man genau falsch 
gemacht hat. z.B. eine Klasse ohne Move+Copy Constructor an std::vector 
übergeben; was an "std::vector<MyClass> v;" falsch ist kann man sonst 
nicht so leicht erkennen.

von Heiko L. (zer0)


Lesenswert?

Niklas Gürtler schrieb:
> zer0@away schrieb:
>> Typischerweise muss man einfach die ersten paar hundert Zeilen des
>> Fehlertraces ignorieren und nach einem relevanten Dateinamen Ausschau
>> halten.
>
> Das reicht oft nicht, man muss oft schon schauen was man genau falsch
> gemacht hat. z.B. eine Klasse ohne Move+Copy Constructor an std::vector
> übergeben; was an "std::vector<MyClass> v;" falsch ist kann man sonst
> nicht so leicht erkennen.

https://godbolt.org/z/cGiXfq
Also für mich finge der relevante Part beim "required from here" an.
Was darüber steht könnte gut nach hinten wandern (ggf in umgekehrter 
Reihenfolge).

: Bearbeitet durch User
von Niklas Gürtler (Gast)


Lesenswert?

Heiko L. schrieb:
> Also für mich finge der relevante Part beim "required from here" an.

Für mich nicht. Ich wäre doch ziemlich verwirrt wenn ich nicht wüsste 
woran sich der vector stört. Wenn man gerade nicht an die Konstruktoren 
denkt...

von Heiko L. (zer0)


Lesenswert?

Niklas Gürtler schrieb:
> Heiko L. schrieb:
>> Also für mich finge der relevante Part beim "required from here" an.
>
> Für mich nicht. Ich wäre doch ziemlich verwirrt wenn ich nicht wüsste
> woran sich der vector stört. Wenn man gerade nicht an die Konstruktoren
> denkt...

Steht da doch. Außerdem war das nicht mein Beispiel.

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

Niklas G. schrieb:
> Clang++ gibt übrigens verständlichere
> Fehlermeldungen aus.

Wenn man die gleiche Compilergenration von gcc und clang vergleicht, tun 
die sich nicht viel. Bei den Template-Fehlermldungen hat sich ja in den 
neueren Compilerversionen eine Menge verbessert.

Wer noch gcc 4.6 oder noch was älteres einsetzt, muß halt mit den 
länglichen Fehlermeldungen leben.

Da hilft dann die einfache Grundregel: Erste und letzte Zeile der 
Fehlermeldung lesen, die weisen zumindest etwas auf das was und wo hin.

Oliver

von Rolf M. (rmagnus)


Lesenswert?

Oliver S. schrieb:
> Da hilft dann die einfache Grundregel: Erste und letzte Zeile der
> Fehlermeldung lesen, die weisen zumindest etwas auf das was und wo hin.

Das Problem ist, die zu finden, wenn du mehr als einen Fehler hast und 
nicht nach der Behebung jedes einzelnen Fehlers neu compilieren willst. 
Wie findest du das Ende der Meldungsreihe zum ersten Fehler?

von Oliver S. (oliverso)


Lesenswert?

Na ja, einen Fehler nach dem anderen zu beheben ist eigentlich eine ganz 
sinnvolle Strategie. Andernfalls muß man auch noch vorsortieren, was 
Folgefehler, und was eigenständige Probleme sind.

Und in der Regel reicht es ja, dafür zunächst nur das betroffene 
Sourcefile zu kompilieren. Das sollte zeitlich machbar sein.

Oliver

von Rolf M. (rmagnus)


Lesenswert?

Oliver S. schrieb:
> Na ja, einen Fehler nach dem anderen zu beheben ist eigentlich eine ganz
> sinnvolle Strategie. Andernfalls muß man auch noch vorsortieren, was
> Folgefehler, und was eigenständige Probleme sind.

Also ohne diesen riesigen Template-Rattenschwanz ist das nicht übermäßig 
schwer. Das mache ich öfters. Mit etwas Erfahrung sieht man auch recht 
schnell, ob die nächste Meldung ein eigenständiger Fehler ist oder nur 
ein Folgefehler und man besser nochmal neu baut.

> Und in der Regel reicht es ja, dafür zunächst nur das betroffene
> Sourcefile zu kompilieren. Das sollte zeitlich machbar sein

Zeitlich machbar schon. Aber wenn man es vermeiden kann, ist man 
trotzdem schneller.

: Bearbeitet durch User
von Heiko L. (zer0)


Lesenswert?

Rolf M. schrieb:
> Oliver S. schrieb:
>> Da hilft dann die einfache Grundregel: Erste und letzte Zeile der
>> Fehlermeldung lesen, die weisen zumindest etwas auf das was und wo hin.
>
> Das Problem ist, die zu finden, wenn du mehr als einen Fehler hast und
> nicht nach der Behebung jedes einzelnen Fehlers neu compilieren willst.
> Wie findest du das Ende der Meldungsreihe zum ersten Fehler?

Man sollte meinen, dass man in einer IDE da einen Tree-View oder sowas 
anzeigen könnte...

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.