Bei fast dem gesamten Code, den ich schreibe, habe ich häufig Probleme mit der Reduzierung von Sätzen in Sammlungen, die letztendlich zu naiven "Wenn" -Bedingungen führen. Hier ist ein einfaches Beispiel:
for(int i=0; i<myCollection.size(); i++)
{
if (myCollection[i] == SOMETHING)
{
DoStuff();
}
}
Mit funktionalen Sprachen kann ich das Problem lösen, indem ich die Sammlung (einfach) auf eine andere Sammlung reduziere und dann alle Operationen an meinem reduzierten Satz ausführe. Im Pseudocode:
newCollection <- myCollection where <x=true
map DoStuff newCollection
Und in anderen C-Varianten wie C # könnte ich mit einer where-Klausel wie reduzieren
foreach (var x in myCollection.Where(c=> c == SOMETHING))
{
DoStuff();
}
Oder besser (zumindest für meine Augen)
myCollection.Where(c=>c == Something).ToList().ForEach(d=> DoStuff(d));
Zugegeben, ich mache viel Paradigmenmischung und subjektiven / meinungsbasierten Stil, aber ich kann nicht anders, als das Gefühl zu haben, dass mir etwas wirklich Grundlegendes fehlt, das es mir ermöglichen könnte, diese bevorzugte Technik mit C ++ zu verwenden. Könnte mich jemand aufklären?
std::copy_if
, aber die Auswahl ist nicht faulif
Innere , dasfor
Sie erwähnen, nicht nur den anderen Beispielen funktional ziemlich äquivalent ist, sondern in vielen Fällen wahrscheinlich auch schneller wäre. Auch für jemanden, der behauptet, funktionalen Stil zu mögen, scheint das, was Sie fördern, gegen das geliebte Konzept der Reinheit der funktionalen Programmierung zuDoStuff
verstoßen, da es eindeutig Nebenwirkungen hat.Antworten:
IMHO ist es einfacher und lesbarer, eine for-Schleife mit einem if darin zu verwenden. Wenn dies jedoch für Sie ärgerlich ist, können Sie
for_each_if
Folgendes verwenden:Anwendungsfall:
Live-Demo
quelle
find_if
undfind
ob sie auf Bereichen oder Iteratorpaaren funktionieren. (Es gibt einige Ausnahmen wiefor_each
undfor_each_n
). Die Art und Weise neue algos für jeden Nieser Schreiben zu vermeiden , ist verschiedene Operationen mit der bestehenden algos zu verwenden, zB stattfor_each_if
einbetten der Bedingung in den vergangen aufrufbarfor_each
, zBfor_each(first, last, [&](auto& x) { if (cond(x)) f(x); });
Boost bietet Bereiche, für die w / range-based verwendet werden kann. Bereiche haben den Vorteil , dass sie nicht kopieren , die zugrunde liegende Datenstruktur, sie nur bieten eine ‚Aussicht‘ (das heißt,
begin()
,end()
für den Bereich undoperator++()
,operator==()
für den Iterator). Dies könnte von Ihrem Interesse sein: http://www.boost.org/libs/range/doc/html/range/reference/adaptors/reference/filtered.htmlquelle
is_even
=>condition
,input
=>myCollection
usw.filtered()
für Sie finden - das heißt, es ist besser, es zu verwenden eine unterstützte Bibliothek als irgendein Ad-hoc-Code.Anstatt wie bei der akzeptierten Antwort einen neuen Algorithmus zu erstellen, können Sie einen vorhandenen Algorithmus mit einer Funktion verwenden, die die Bedingung anwendet:
Oder wenn Sie wirklich einen neuen Algorithmus wollen, verwenden Sie ihn dort zumindest wieder,
for_each
anstatt die Iterationslogik zu duplizieren:quelle
std::for-each(first, last, [&](auto& x) {if (p(x)) op(x); });
ist total einfacher alsfor (Iter x = first; x != last; x++) if (p(x)) op(x);}
?std::for_each(std::execution::par, first, last, ...);
Wie einfach ist es, diese Dinge einer handschriftlichen Schleife hinzuzufügen?Die Idee zu vermeiden
Konstrukte als Antimuster sind zu breit.
Es ist völlig in Ordnung, mehrere Elemente, die einem bestimmten Ausdruck entsprechen, innerhalb einer Schleife zu verarbeiten, und der Code kann nicht viel klarer werden. Wenn die Verarbeitung zu groß wird, um auf den Bildschirm zu passen, ist dies ein guter Grund, eine Unterroutine zu verwenden, aber die Bedingung wird am besten innerhalb der Schleife platziert, d. H.
ist weitaus vorzuziehen
Es wird zu einem Antimuster, wenn nur ein Element übereinstimmt, da es dann klarer wäre, zuerst nach dem Element zu suchen und die Verarbeitung außerhalb der Schleife durchzuführen.
ist ein extremes und offensichtliches Beispiel dafür. Subtiler und damit häufiger ist ein Fabrikmuster wie
Dies ist schwer zu lesen, da es nicht offensichtlich ist, dass der Body-Code nur einmal ausgeführt wird. In diesem Fall ist es besser, die Suche zu trennen:
Es gibt immer noch ein
if
innerhalb von afor
, aber aus dem Kontext wird klar, was es tut, es besteht keine Notwendigkeit, diesen Code zu ändern, es sei denn, die Suche ändert sich (z. B. zu amap
), und es ist sofort klar, dasscreate_object()
nur einmal aufgerufen wird, weil es so ist nicht in einer Schleife.quelle
for( range ){ if( condition ){ action } }
Stil es einfach macht, Dinge einzeln zu lesen, und nur Kenntnisse über die grundlegenden Sprachkonstrukte verwendet.for(...) if(...) { ... }
oft die beste Wahl ist (deshalb habe ich die Empfehlung qualifiziert, die Aktion in eine Unterroutine aufzuteilen).for(…)if(…)…
korrigieren, wenn es der einzige Ort wäre, an dem Lookup stattgefunden hat.Hier ist eine schnelle, relativ minimale
filter
Funktion.Es braucht ein Prädikat. Es gibt ein Funktionsobjekt zurück, das eine Iterierbarkeit annimmt.
Es gibt eine Iterable zurück, die in einer
for(:)
Schleife verwendet werden kann.Ich habe Abkürzungen genommen. Eine echte Bibliothek sollte echte Iteratoren sein, nicht die
for(:)
qualifizierenden Pseudofasken, die ich gemacht habe.Am Verwendungsort sieht es so aus:
Das ist ziemlich schön und druckt
Live-Beispiel .
Es gibt eine vorgeschlagene Ergänzung zu C ++ namens Rangesv3, die so etwas und mehr macht.
boost
hat auch Filterbereiche / Iteratoren zur Verfügung. Boost hat auch Helfer, die das Schreiben der oben genannten viel kürzer machen.quelle
Ein Stil, der genug verwendet wird, um zu erwähnen, aber noch nicht erwähnt wurde, ist:
Vorteile:
DoStuff();
wenn die Komplexität der Bedingungen zunimmt. LogischerweiseDoStuff();
sollte es sich auf der obersten Ebene derfor
Schleife befinden, und das ist es auch.SOMETHING
s der Sammlung iteriert , ohne dass der Leser überprüfen muss, ob nach dem Schließen}
desif
Blocks nichts mehr vorhanden ist .Nachteile:
continue
Wie andere Anweisungen zur Flusskontrolle wird sie auf eine Weise missbraucht, die zu schwer zu befolgendem Code führt, so dass einige Leute gegen jede Verwendung von Code sind : Es gibt einen gültigen Codierungsstil, den einige befolgen, der vermeidetcontinue
, der vermeidetbreak
andere als andere in aswitch
vermeidet dasreturn
andere als am Ende einer Funktion.quelle
for
Schleife, die zu vielen Zeilen führt, eine zweizeilige Zeile "Wenn nicht, weiter" viel klarer, logischer und lesbarer ist. Sagen Sie sofort "Überspringen Sie dies, wenn", nachdem diefor
Anweisung gut gelesen wurde, und rücken Sie, wie Sie sagten, die verbleibenden funktionalen Aspekte der Schleife nicht ein. Wenn dascontinue
weiter unten ist, wird jedoch eine gewisse Klarheit geopfert (dh wenn eine Operation immer vor derif
Anweisung ausgeführt wird).Sieht ziemlich nach C ++ aus
for
für mich Verständnis aus. Für dich?quelle
auto const
hat keinerlei Einfluss auf die Iterationsreihenfolge. Wenn Sie nach Fernkampf suchenfor
, werden Sie feststellen, dass im Grunde eine Standardschleife vonbegin()
bisend()
mit impliziter Dereferenzierung ausgeführt wird. Es gibt keine Möglichkeit, die Bestellgarantien (falls vorhanden) des zu iterierenden Containers zu brechen. es wäre vom Erdboden gelacht wordenstd::future
s,std::function
s, selbst diese anonymen Verschlüsse sehr gut C ++ in der Syntax sind; Jede Sprache hat ihre eigene Sprache, und wenn neue Funktionen integriert werden, versucht sie, die alte bekannte Syntax nachzuahmen.Wenn DoStuff () in Zukunft irgendwie von i abhängig wäre, würde ich diese garantierte verzweigungsfreie Bitmaskierungsvariante vorschlagen.
Wobei popcount eine Funktion ist, die eine Populationszählung durchführt (Anzahl der Bits = 1). Es wird einige Freiheiten geben, um i und ihren Nachbarn fortgeschrittenere Einschränkungen aufzuerlegen. Wenn dies nicht benötigt wird, können wir die innere Schleife entfernen und die äußere Schleife neu erstellen
gefolgt von einem
quelle
Wenn Sie die Sammlung nicht neu anordnen möchten, ist std :: partition auch günstig.
quelle
std::partition
ordnet den Container neu.Ich bin beeindruckt von der Komplexität der oben genannten Lösungen. Ich wollte ein einfaches vorschlagen,
#define foreach(a,b,c,d) for(a; b; c)if(d)
aber es weist einige offensichtliche Defizite auf. Sie müssen beispielsweise daran denken, in Ihrer Schleife Kommas anstelle von Semikolons zu verwenden, und Sie können den Kommaoperator ina
oder nicht verwendenc
.quelle
Eine andere Lösung für den Fall, dass die i: s wichtig sind. Dieser erstellt eine Liste, die die Indizes ausfüllt, für die doStuff () aufgerufen werden soll. Auch hier geht es vor allem darum, die Verzweigung zu vermeiden und gegen pipelinierbare Rechenkosten einzutauschen.
Die "magische" Linie ist die Pufferladelinie, die arithmetisch berechnet, ob der Wert beibehalten und in Position bleiben oder die Position hochgezählt und der Wert erhöht werden soll. Also tauschen wir einen potenziellen Zweig gegen Logik und Arithmetik und vielleicht gegen Cache-Treffer aus. Ein typisches Szenario, in dem dies nützlich wäre, ist, wenn doStuff () eine kleine Anzahl von Pipeline-Berechnungen durchführt und jede Verzweigung zwischen Aufrufen diese Pipelines unterbrechen könnte.
Dann durchlaufen Sie einfach den Puffer und führen doStuff () aus, bis wir cnt erreichen. Dieses Mal wird der aktuelle i im Puffer gespeichert, damit wir ihn bei Bedarf im Aufruf von doStuff () verwenden können.
quelle
Man kann Ihr Codemuster als Anwenden einer Funktion auf eine Teilmenge eines Bereichs beschreiben, oder mit anderen Worten: Anwenden auf das Ergebnis der Anwendung eines Filters auf den gesamten Bereich.
Dies ist auf einfachste Weise mit der Range-v3-Bibliothek von Eric Neibler möglich . obwohl es ein bisschen nervig ist, weil Sie mit Indizes arbeiten möchten:
Aber wenn Sie bereit sind, auf Indizes zu verzichten, erhalten Sie:
Das ist meiner Meinung nach schöner.
PS - Die Bereichsbibliothek wird hauptsächlich in den C ++ - Standard in C ++ 20 übernommen.
quelle
Ich werde nur Mike Acton erwähnen, er würde definitiv sagen:
Wenn Sie das tun müssen, haben Sie ein Problem mit Ihren Daten. Sortieren Sie Ihre Daten!
quelle