Trie -> SegmentTrie

This commit is contained in:
Tommi Reiman 2019-01-13 16:45:43 +02:00
parent 54aded4442
commit 93bcc5dad8
6 changed files with 51 additions and 52 deletions

View file

@ -6,7 +6,7 @@ import java.io.UnsupportedEncodingException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.util.*; import java.util.*;
public class Trie { public class SegmentTrie {
public static ArrayList<String> split(final String path) { public static ArrayList<String> split(final String path) {
final ArrayList<String> segments = new ArrayList<>(4); final ArrayList<String> segments = new ArrayList<>(4);
@ -50,35 +50,35 @@ public class Trie {
} }
} }
private Map<String, Trie> childs = new HashMap<>(); private Map<String, SegmentTrie> childs = new HashMap<>();
private Map<Keyword, Trie> wilds = new HashMap<>(); private Map<Keyword, SegmentTrie> wilds = new HashMap<>();
private Map<Keyword, Trie> catchAll = new HashMap<>(); private Map<Keyword, SegmentTrie> catchAll = new HashMap<>();
private Object data; private Object data;
public Trie add(String path, Object data) { public SegmentTrie add(String path, Object data) {
List<String> paths = split(path); List<String> paths = split(path);
Trie pointer = this; SegmentTrie pointer = this;
for (String p : paths) { for (String p : paths) {
if (p.startsWith(":")) { if (p.startsWith(":")) {
Keyword k = Keyword.intern(p.substring(1)); Keyword k = Keyword.intern(p.substring(1));
Trie s = pointer.wilds.get(k); SegmentTrie s = pointer.wilds.get(k);
if (s == null) { if (s == null) {
s = new Trie(); s = new SegmentTrie();
pointer.wilds.put(k, s); pointer.wilds.put(k, s);
} }
pointer = s; pointer = s;
} else if (p.startsWith("*")) { } else if (p.startsWith("*")) {
Keyword k = Keyword.intern(p.substring(1)); Keyword k = Keyword.intern(p.substring(1));
Trie s = pointer.catchAll.get(k); SegmentTrie s = pointer.catchAll.get(k);
if (s == null) { if (s == null) {
s = new Trie(); s = new SegmentTrie();
pointer.catchAll.put(k, s); pointer.catchAll.put(k, s);
} }
break; break;
} else { } else {
Trie s = pointer.childs.get(p); SegmentTrie s = pointer.childs.get(p);
if (s == null) { if (s == null) {
s = new Trie(); s = new SegmentTrie();
pointer.childs.put(p, s); pointer.childs.put(p, s);
} }
pointer = s; pointer = s;
@ -93,7 +93,7 @@ public class Trie {
return new StaticMatcher(childs.keySet().iterator().next(), childs.values().iterator().next().matcher()); return new StaticMatcher(childs.keySet().iterator().next(), childs.values().iterator().next().matcher());
} else { } else {
Map<String, Matcher> m = new HashMap<>(); Map<String, Matcher> m = new HashMap<>();
for (Map.Entry<String, Trie> e : childs.entrySet()) { for (Map.Entry<String, SegmentTrie> e : childs.entrySet()) {
m.put(e.getKey(), e.getValue().matcher()); m.put(e.getKey(), e.getValue().matcher());
} }
return new StaticMapMatcher(m); return new StaticMapMatcher(m);
@ -112,7 +112,7 @@ public class Trie {
if (!childs.isEmpty()) { if (!childs.isEmpty()) {
matchers.add(staticMatcher()); matchers.add(staticMatcher());
} }
for (Map.Entry<Keyword, Trie> e : wilds.entrySet()) { for (Map.Entry<Keyword, SegmentTrie> e : wilds.entrySet()) {
matchers.add(new WildMatcher(e.getKey(), e.getValue().matcher())); matchers.add(new WildMatcher(e.getKey(), e.getValue().matcher()));
} }
m = new LinearMatcher(matchers); m = new LinearMatcher(matchers);
@ -301,7 +301,7 @@ public class Trie {
public static void main(String[] args) { public static void main(String[] args) {
Trie trie = new Trie(); SegmentTrie trie = new SegmentTrie();
//trie.add("/kikka/:id/permissions", 1); //trie.add("/kikka/:id/permissions", 1);
trie.add("/kikka/:id", 2); trie.add("/kikka/:id", 2);
trie.add("/kakka/ping", 3); trie.add("/kakka/ping", 3);
@ -311,7 +311,7 @@ public class Trie {
System.out.println(lookup(m, "/kikka/1")); System.out.println(lookup(m, "/kikka/1"));
/* /*
Trie trie = new Trie(); SegmentTrie trie = new SegmentTrie();
trie.add("/user/:id/profile/:type", 1); trie.add("/user/:id/profile/:type", 1);
trie.add("/user/:id/permissions", 2); trie.add("/user/:id/permissions", 2);
trie.add("/company/:cid/dept/:did", 3); trie.add("/company/:cid/dept/:did", 3);

View file

@ -6,7 +6,7 @@
(:import (java.util.regex Pattern) (:import (java.util.regex Pattern)
(java.util HashMap Map) (java.util HashMap Map)
(java.net URLEncoder URLDecoder) (java.net URLEncoder URLDecoder)
(reitit Trie)))) (reitit SegmentTrie))))
(defn maybe-map-values (defn maybe-map-values
"Applies a function to every value of a map, updates the value if not nil. "Applies a function to every value of a map, updates the value if not nil.
@ -21,7 +21,7 @@
coll)) coll))
(defn segments [path] (defn segments [path]
#?(:clj (Trie/split ^String path) #?(:clj (SegmentTrie/split ^String path)
:cljs (.split path #"/" 666))) :cljs (.split path #"/" 666)))
;; ;;

View file

@ -2,7 +2,7 @@
(:refer-clojure :exclude [-lookup compile]) (:refer-clojure :exclude [-lookup compile])
(:require [reitit.impl :as impl] (:require [reitit.impl :as impl]
[clojure.string :as str]) [clojure.string :as str])
#?(:clj (:import (reitit Trie Trie$Match)))) #?(:clj (:import (reitit SegmentTrie Trie$Match))))
(defrecord Match [data path-params]) (defrecord Match [data path-params])
@ -50,13 +50,13 @@
(defn insert [root path data] (defn insert [root path data]
#?(:cljs (-insert (or root (segment)) (impl/segments path) (map->Match {:data data})) #?(:cljs (-insert (or root (segment)) (impl/segments path) (map->Match {:data data}))
:clj (.add (or ^Trie root ^Trie (Trie.)) ^String path data))) :clj (.add (or ^SegmentTrie root ^SegmentTrie (SegmentTrie.)) ^String path data)))
(defn compile [segment] (defn compile [segment]
#?(:cljs segment #?(:cljs segment
:clj (.matcher ^Trie segment))) :clj (.matcher ^SegmentTrie segment)))
(defn lookup [segment path] (defn lookup [segment path]
#?(:cljs (-lookup segment (impl/segments path) {}) #?(:cljs (-lookup segment (impl/segments path) {})
:clj (if-let [match ^Trie$Match (Trie/lookup segment path)] :clj (if-let [match ^Trie$Match (SegmentTrie/lookup segment path)]
(->Match (.data match) (clojure.lang.PersistentHashMap/create (.params match)))))) (->Match (.data match) (clojure.lang.PersistentHashMap/create (.params match))))))

View file

@ -7,7 +7,8 @@
[reitit.impl :as impl] [reitit.impl :as impl]
[reitit.ring :as ring] [reitit.ring :as ring]
[reitit.core :as r]) [reitit.core :as r])
(:import (reitit Trie Trie$Matcher))) (:import (reitit SegmentTrie Trie$Matcher)
(calfpath Util)))
;; ;;
;; start repl with `lein perf repl` ;; start repl with `lein perf repl`
@ -109,20 +110,20 @@
(impl/segments "/user/1234/profile/compact"))) (impl/segments "/user/1234/profile/compact")))
(comment (comment
(Trie/split "/user/1234/profile/compact") (SegmentTrie/split "/user/1234/profile/compact")
;; 91ns ;; 91ns
(cc/quick-bench (cc/quick-bench
(Trie/split "/user/1234/profile/compact"))) (SegmentTrie/split "/user/1234/profile/compact")))
(comment (comment
(let [router (r/router ["/user/:id/profile/:type"])] (let [router (r/router ["/user/:id/profile/:type"])]
(cc/quick-bench (cc/quick-bench
(r/match-by-path router "/user/1234/profile/compact")))) (r/match-by-path router "/user/1234/profile/compact"))))
(let [lookup ^Trie$Matcher (Trie/sample)] (let [lookup ^Trie$Matcher (SegmentTrie/sample)]
(Trie/lookup lookup "/user/1234/profile/compact") (SegmentTrie/lookup lookup "/user/1234/profile/compact")
#_(cc/quick-bench #_(cc/quick-bench
(Trie/lookup lookup "/user/1234/profile/compact"))) (SegmentTrie/lookup lookup "/user/1234/profile/compact")))
(let [router (r/router [["/user/:id" ::1] (let [router (r/router [["/user/:id" ::1]
["/user/:id/permissions" ::2] ["/user/:id/permissions" ::2]
@ -144,50 +145,50 @@
(read-string (read-string
(str (str
(.matcher (.matcher
(doto (Trie.) (doto (SegmentTrie.)
(.add "/user" 1) (.add "/user" 1)
#_(.add "/user/id/permissions" 2) #_(.add "/user/id/permissions" 2)
(.add "/user/id/permissions2" 3))))) (.add "/user/id/permissions2" 3)))))
(Trie/lookup (SegmentTrie/lookup
(.matcher (.matcher
(doto (Trie.) (doto (SegmentTrie.)
(.add "/user/1" 1) (.add "/user/1" 1)
(.add "/user/1/permissions" 2))) (.add "/user/1/permissions" 2)))
"/user/1") "/user/1")
(.matcher (.matcher
(doto (Trie.) (doto (SegmentTrie.)
(.add "/user/1" 1) (.add "/user/1" 1)
(.add "/user/1/permissions" 2))) (.add "/user/1/permissions" 2)))
;; 137ns ;; 137ns
(let [m (.matcher (let [m (.matcher
(doto (Trie.) (doto (SegmentTrie.)
(.add "/user/:id/profile/:type" 1)))] (.add "/user/:id/profile/:type" 1)))]
#_(cc/quick-bench #_(cc/quick-bench
(Trie/lookup m "/user/1234/profile/compact")) (SegmentTrie/lookup m "/user/1234/profile/compact"))
(Trie/lookup m "/user/1234/profile/compact")) (SegmentTrie/lookup m "/user/1234/profile/compact"))
(comment (comment
(let [matcher ^Trie$Matcher (Trie/sample)] (let [matcher ^Trie$Matcher (SegmentTrie/sample)]
(Trie/lookup matcher "/user/1234/profile/compact") (SegmentTrie/lookup matcher "/user/1234/profile/compact")
(cc/quick-bench (cc/quick-bench
(Trie/lookup matcher "/user/1234/profile/compact"))) (SegmentTrie/lookup matcher "/user/1234/profile/compact")))
;; 173ns ;; 173ns
(let [lookup ^Trie$Matcher (Trie/tree2)] (let [lookup ^Trie$Matcher (SegmentTrie/tree2)]
(Trie/lookup lookup "/user/1234/profile/compact") (SegmentTrie/lookup lookup "/user/1234/profile/compact")
(cc/quick-bench (cc/quick-bench
(Trie/lookup lookup "/user/1234/profile/compact"))) (SegmentTrie/lookup lookup "/user/1234/profile/compact")))
;; 140ns ;; 140ns
(let [lookup ^Trie$Matcher (Trie/tree1)] (let [lookup ^Trie$Matcher (SegmentTrie/tree1)]
(Trie/lookup lookup "/user/1234/profile/compact") (SegmentTrie/lookup lookup "/user/1234/profile/compact")
(cc/quick-bench (cc/quick-bench
(Trie/lookup lookup "/user/1234/profile/compact"))) (SegmentTrie/lookup lookup "/user/1234/profile/compact")))
;; 849ns (clojure, original) ;; 849ns (clojure, original)
;; 599ns (java, initial) ;; 599ns (java, initial)
@ -225,15 +226,13 @@
(dotimes [_ 1000] (dotimes [_ 1000]
(handler-reitit request))))) (handler-reitit request)))))
(import '[reitit Util])
(comment (comment
(Util/matchURI "/user/1234/profile/compact/" ["/user/" :id "/profile/" :type "/"]) (Util/matchURI "/user/1234/profile/compact/" ["/user/" :id "/profile/" :type "/"])
(cc/quick-bench (cc/quick-bench
(Util/matchURI "/user/1234/profile/compact/" ["/user/" :id "/profile/" :type "/"])) (Util/matchURI "/user/1234/profile/compact/" ["/user/" :id "/profile/" :type "/"]))
(cc/quick-bench (cc/quick-bench
(Trie/split "/user/1234/profile/compact/")) (SegmentTrie/split "/user/1234/profile/compact/"))
(cc/quick-bench (cc/quick-bench
(.split "/user/1234/profile/compact/" "/" 666))) (.split "/user/1234/profile/compact/" "/" 666)))
@ -265,9 +264,9 @@
(segment/lookup segment "/user/1/permissions/")))) (segment/lookup segment "/user/1/permissions/"))))
#_(cc/quick-bench #_(cc/quick-bench
(Trie/split "/user/1/profile/compat")) (SegmentTrie/split "/user/1/profile/compat"))
#_(Trie/split "/user/1/profile/compat") #_(SegmentTrie/split "/user/1/profile/compat")
#_(cc/quick-bench #_(cc/quick-bench
(Segment2/hashLookup h "abba")) (Segment2/hashLookup h "abba"))

View file

@ -190,7 +190,7 @@
(suite "split") (suite "split")
;; 114ns (String/split) ;; 114ns (String/split)
;; 82ns (Trie/split) ;; 82ns (SegmentTrie/split)
(test "Splitting a String") (test "Splitting a String")
(test! impl/segments "/olipa/kerran/:avaruus")) (test! impl/segments "/olipa/kerran/:avaruus"))

View file

@ -3,7 +3,7 @@
[io.pedestal.http.route.prefix-tree :as p] [io.pedestal.http.route.prefix-tree :as p]
[reitit.segment :as segment] [reitit.segment :as segment]
[criterium.core :as cc]) [criterium.core :as cc])
(:import (reitit Trie))) (:import (reitit SegmentTrie)))
;; ;;
;; testing ;; testing
@ -72,7 +72,7 @@
(def matcher (def matcher
(.matcher (.matcher
^Trie ^SegmentTrie
(reduce (reduce
(fn [acc [p d]] (fn [acc [p d]]
(segment/insert acc p d)) (segment/insert acc p d))