wenn meine Struktur
ist %Vor%Ich möchte eine Funktion namens keys-in erhalten, die etwas wie folgt zurückgibt:
%Vor%Also kann ich etwas tun wie:
(not-any? nil? (Karte # (meine-andere-Karte% 1) (Schlüssel-in meine-Karte)))
Damit kann ich sicher sein, dass my-other-map die gleichen Schlüssel wie my-map
hatSie können dies relativ einfach mit clojure.zip oder tree-seq erstellen, obwohl ich die prismatic.schema Bibliothek zur Überprüfung sehr bevorzuge die Struktur von verschachtelten Karten
%Vor% Wenn Sie kein faules Ergebnis benötigen und nur schnell sein wollen, versuchen Sie reduce-kv
.
Wenn Sie auch Vektorindizes unterstützen möchten (wie bei get-in
oder update-in
), testen Sie mit associative?
anstelle von map?
. Wenn Sie Zwischenpfade möchten, können Sie diese auch verbinden. Hier ist eine Variante:
Diese Antwort von mir ist nur um zu veranschaulichen, wie es NICHT zu tun ist, da es noch prozedural ist.
%Vor%Hier ist eine Implementierung, die alle Schlüssel (nicht nur die Terminal-Schlüssel) basierend auf lazy-seq zurückgibt:
%Vor%Hier sind Lösungen (ohne Zwischenpfade) mit Spectre . Sie stammen von Nathan Marz, dem Autor von Spectre, aus einer Konversation im Spectre Slack Channel (mit seiner Erlaubnis). Ich beanspruche diese Definitionen nicht.
Einfache Version:
%Vor%Effizientere Version:
%Vor%Meine informelle Erklärung, was in diesen Definitionen passiert:
Da in der einfachen Version das Top-Level-Argument eine Map ist, übergibt if-path map?
es an die erste Sammlung von Navigatoren in Klammern. Diese beginnen mit ALL
, was hier heißt, den Rest für jedes Element in der Map zu machen. Dann sagt MapEntry
für jedes (collect-one FIRST)
in der Map, dass sein erstes Element (key) dem Ergebnis der letzten Übergabe seines letzten Elements (val) an if-path
hinzugefügt wird. p
wurde durch recursive-path
als Verweis auf denselben recursive-path
-Ausdruck gebunden. Durch diesen Prozess gelangen wir schließlich zu einer Nicht-Map. Gebe es zurück und beende die Verarbeitung in diesem Zweig; das ist, was STAY
bedeutet. Diese letzte Sache ist jedoch nicht einer der Schlüssel; es ist das Terminal val. Also enden wir mit den Blattwerten in jeder Sequenz. Um sie zu entfernen, maskiere butlast
über das gesamte Ergebnis.
Die zweite Version vermeidet diesen letzten Schritt, indem sie nur in den Wert in MapEntry
rekurriert, wenn dieses val selbst eine Karte ist. Das macht die innere if-path
: [LAST map?]
erhält das letzte Element, d. H. Den Wert des aktuellen MapEntry
, der von ALL
generiert wurde, und übergibt ihn an map?
.
Ich habe Criterium verwendet, um alle Schlüsselpfadfunktionen auf dieser Seite zu testen, die keine Zwischenpfade zurückliefern, plus einen von geräuschverursachenden, der Teil eines Beantworte eine andere Frage . Für eine 3-Level-Karte mit 3 Tasten pro Level und für eine 6-Level-Karte mit 6 Tasten pro Level haben die Version von miner49r und die zweite, schnellere Spectre-Version ähnliche Geschwindigkeiten und sind viel schneller als alle anderen Versionen.
Timings auf einer 3-Level, 3 Schlüssel pro Level (27 Pfade) map, in der Reihenfolge:
vec
): 243.756275 μs Timings auf einer 6-Level, 6 Schlüssel pro Level (6 ^ 6 = 46656 Pfade) Karte in der Reihenfolge:
vec
): 839.266448 ms Alle Aufrufe wurden in doall
eingeschlossen, damit faule Ergebnisse erzielt werden. Da ich doall
ing war, habe ich vec
wrapper in Alex Millers Definition herausgenommen.
Ausführliche Informationen zu den Timings finden Sie hier . Der Testcode ist hier .
(Die einfache Spectre-Version ist langsamer als die schnellere Version, da map butlast
zum Entfernen der Blattwerte verwendet wird. Wenn dieser Schritt entfernt wird, entsprechen die Zeiten der einfachen Spectre-Definition denen der zweiten Definition. )
Ich arbeite an etwas ähnlichem für ein persönliches Projekt und das ist meine naive Umsetzung:
%Vor%Benutze es aus dem repl:
%Vor%Schicker Weg:
%Vor%Tags und Links clojure map functional-programming recursion