Small improvement

* Sort linear routes secondary with static path length
* Unwrap data-matchers from linear-router
* Simplify StaticMatcher impl
This commit is contained in:
Tommi Reiman 2019-02-09 11:51:15 +02:00
parent c302f795ab
commit 81b9bdceef
3 changed files with 88 additions and 50 deletions

View file

@ -63,6 +63,8 @@ public class Trie {
Match match(int i, int max, char[] path, Match match); Match match(int i, int max, char[] path, Match match);
int depth(); int depth();
int length();
} }
public static StaticMatcher staticMatcher(String path, Matcher child) { public static StaticMatcher staticMatcher(String path, Matcher child) {
@ -98,6 +100,11 @@ public class Trie {
return child.depth() + 1; return child.depth() + 1;
} }
@Override
public int length() {
return path.length;
}
@Override @Override
public String toString() { public String toString() {
return "[\"" + new String(path) + "\" " + child + "]"; return "[\"" + new String(path) + "\" " + child + "]";
@ -129,52 +136,50 @@ public class Trie {
return 1; return 1;
} }
@Override
public int length() {
return 0;
}
@Override @Override
public String toString() { public String toString() {
return (data != null ? data.toString() : "nil"); return (data != null ? data.toString() : "nil");
} }
} }
public static WildMatcher wildMatcher(Keyword parameter, Matcher child) { public static WildMatcher wildMatcher(Keyword parameter, char end, Matcher child) {
return new WildMatcher(parameter, child); return new WildMatcher(parameter, end, child);
} }
static final class WildMatcher implements Matcher { static final class WildMatcher implements Matcher {
private final Keyword key; private final Keyword key;
private final char end;
private final Matcher child; private final Matcher child;
WildMatcher(Keyword key, Matcher child) { WildMatcher(Keyword key, char end, Matcher child) {
this.key = key; this.key = key;
this.end = end;
this.child = child; this.child = child;
} }
@Override @Override
public Match match(int i, int max, char[] path, Match match) { public Match match(int i, int max, char[] path, Match match) {
if (i < max && path[i] != '/') { if (i < max && path[i] != end) {
boolean hasPercent = false; boolean hasPercent = false;
boolean hasPlus = false; boolean hasPlus = false;
int stop = max;
for (int j = i; j < max; j++) { for (int j = i; j < max; j++) {
final char c = path[j]; final char c = path[j];
switch (c) { if (c == end) {
case '/': stop = j;
final Match m = child.match(j, max, path, match); break;
if (m != null) {
m.params.assoc(key, decode(path, i, j, hasPercent, hasPlus));
}
return m;
case '%':
hasPercent = true;
break;
case '+':
hasPlus = true;
break;
default:
break;
} }
hasPercent = hasPercent || c == '%';
hasPlus = hasPlus || c == '+';
} }
final Match m = child.match(max, max, path, match); final Match m = child.match(stop, max, path, match);
if (m != null) { if (m != null) {
m.params.assoc(key, decode(path, i, max, hasPercent, hasPlus)); m.params.assoc(key, decode(path, i, stop, hasPercent, hasPlus));
} }
return m; return m;
} }
@ -186,6 +191,11 @@ public class Trie {
return child.depth() + 1; return child.depth() + 1;
} }
@Override
public int length() {
return 0;
}
@Override @Override
public String toString() { public String toString() {
return "[" + key + " " + child + "]"; return "[" + key + " " + child + "]";
@ -220,6 +230,11 @@ public class Trie {
return 1; return 1;
} }
@Override
public int length() {
return 0;
}
@Override @Override
public String toString() { public String toString() {
return "[" + parameter + " " + new DataMatcher(data) + "]"; return "[" + parameter + " " + new DataMatcher(data) + "]";
@ -237,7 +252,7 @@ public class Trie {
LinearMatcher(List<Matcher> childs) { LinearMatcher(List<Matcher> childs) {
this.childs = childs.toArray(new Matcher[0]); this.childs = childs.toArray(new Matcher[0]);
Arrays.sort(this.childs, Comparator.comparing(Matcher::depth).reversed()); Arrays.sort(this.childs, Comparator.comparing(Matcher::depth).thenComparing(Matcher::length).reversed());
this.size = childs.size(); this.size = childs.size();
} }
@ -257,6 +272,11 @@ public class Trie {
return Arrays.stream(childs).mapToInt(Matcher::depth).max().orElseThrow(NoSuchElementException::new); return Arrays.stream(childs).mapToInt(Matcher::depth).max().orElseThrow(NoSuchElementException::new);
} }
@Override
public int length() {
return 0;
}
@Override @Override
public String toString() { public String toString() {
return Arrays.toString(childs); return Arrays.toString(childs);

View file

@ -15,7 +15,8 @@
(defprotocol Matcher (defprotocol Matcher
(match [this i max path]) (match [this i max path])
(view [this]) (view [this])
(depth [this])) (depth [this])
(length [this]))
(defn -assoc! [match k v] (defn -assoc! [match k v]
(let [params (or (:path-params match) (transient {}))] (let [params (or (:path-params match) (transient {}))]
@ -33,8 +34,7 @@
(not= (get s1 i) (get s2 i)) (not= (get s1 i) (get s2 i))
(if-not (zero? i) (subs s1 0 i)) (if-not (zero? i) (subs s1 0 i))
;; recur ;; recur
:else :else (recur (inc i))))))
(recur (inc i))))))
(defn- -keyword [s] (defn- -keyword [s]
(if-let [i (str/index-of s "/")] (if-let [i (str/index-of s "/")]
@ -81,10 +81,13 @@
(assoc node :data data) (assoc node :data data)
(instance? Wild path) (instance? Wild path)
(update-in node [:wilds (:value path)] (fn [n] (-insert (or n (-node {})) ps data))) (let [next (first ps)]
(if (or (instance? Wild next) (instance? CatchAll next))
(throw (ex-info (str "Two following wilds: " path ", " next) {}))
(update-in node [:wilds path] (fn [n] (-insert (or n (-node {})) ps data)))))
(instance? CatchAll path) (instance? CatchAll path)
(assoc-in node [:catch-all (:value path)] (-node {:data data})) (assoc-in node [:catch-all path] (-node {:data data}))
(str/blank? path) (str/blank? path)
(-insert node ps data) (-insert node ps data)
@ -118,8 +121,7 @@
#?(:cljs #?(:cljs
(defn decode! [path start end percent?] (defn decode! [path start end percent?]
(let [path (subs path start end)] (if percent? (js/decodeURIComponent (subs path start end)) path)))
(if percent? (js/decodeURIComponent path) path))))
(defn data-matcher [data] (defn data-matcher [data]
#?(:clj (Trie/dataMatcher data) #?(:clj (Trie/dataMatcher data)
@ -129,7 +131,8 @@
(if (= i max) (if (= i max)
match)) match))
(view [_] data) (view [_] data)
(depth [_] 1))))) (depth [_] 1)
(length [_])))))
(defn static-matcher [path matcher] (defn static-matcher [path matcher]
#?(:clj (Trie/staticMatcher ^String path ^Trie$Matcher matcher) #?(:clj (Trie/staticMatcher ^String path ^Trie$Matcher matcher)
@ -143,25 +146,27 @@
(if (= (get p (+ i j)) (get path j)) (if (= (get p (+ i j)) (get path j))
(recur (inc j))))))) (recur (inc j)))))))
(view [_] [path (view matcher)]) (view [_] [path (view matcher)])
(depth [_] (inc (depth matcher))))))) (depth [_] (inc (depth matcher)))
(length [_] (count path))))))
(defn wild-matcher [key matcher] (defn wild-matcher [key end matcher]
#?(:clj (Trie/wildMatcher key matcher) #?(:clj (Trie/wildMatcher key (if end (Character. end)) matcher)
:cljs (reify Matcher :cljs (reify Matcher
(match [_ i max path] (match [_ i max path]
(if (and (< i max) (not= (get path i) \/)) (if (and (< i max) (not= (get path i) end))
(loop [percent? false, j i] (loop [percent? false, j i]
(if (= max j) (if (= max j)
(if-let [match (match matcher max max path)] (if-let [match (match matcher max max path)]
(-assoc! match key (decode! path i max percent?))) (-assoc! match key (decode! path i max percent?)))
(let [c ^char (get path j)] (let [c ^char (get path j)]
(case c (condp = c
\/ (if-let [match (match matcher j max path)] end (if-let [match (match matcher j max path)]
(-assoc! match key (decode! path i j percent?))) (-assoc! match key (decode! path i j percent?)))
\% (recur true (inc j)) \% (recur true (inc j))
(recur percent? (inc j)))))))) (recur percent? (inc j))))))))
(view [_] [key (view matcher)]) (view [_] [key (view matcher)])
(depth [_] (inc (depth matcher)))))) (depth [_] (inc (depth matcher)))
(length [_]))))
(defn catch-all-matcher [key data] (defn catch-all-matcher [key data]
#?(:clj (Trie/catchAllMatcher key data) #?(:clj (Trie/catchAllMatcher key data)
@ -170,11 +175,12 @@
(match [_ i max path] (match [_ i max path]
(if (< i max) (-assoc! match key (decode! path i max true)))) (if (< i max) (-assoc! match key (decode! path i max true))))
(view [_] [key [data]]) (view [_] [key [data]])
(depth [_] 1))))) (depth [_] 1)
(length [_])))))
(defn linear-matcher [matchers] (defn linear-matcher [matchers]
#?(:clj (Trie/linearMatcher matchers) #?(:clj (Trie/linearMatcher matchers)
:cljs (let [matchers (vec (reverse (sort-by depth matchers))) :cljs (let [matchers (vec (reverse (sort-by (juxt depth length) matchers)))
size (count matchers)] size (count matchers)]
(reify Matcher (reify Matcher
(match [_ i max path] (match [_ i max path]
@ -183,7 +189,8 @@
(or (match (get matchers j) i max path) (or (match (get matchers j) i max path)
(recur (inc j)))))) (recur (inc j))))))
(view [_] (mapv view matchers)) (view [_] (mapv view matchers))
(depth [_] (apply max (map depth matchers))))))) (depth [_] (apply max 0 (map depth matchers)))
(length [_])))))
;; ;;
;; public api ;; public api
@ -201,14 +208,17 @@
(-insert (or node (-node {})) (split-path path) data))) (-insert (or node (-node {})) (split-path path) data)))
(defn compile [{:keys [data children wilds catch-all]}] (defn compile [{:keys [data children wilds catch-all]}]
(let [matchers (cond-> [] (let [ends (fn [{:keys [children]}] (or (keys children) ["/"]))
data (conj (data-matcher data)) matchers (-> []
children (into (for [[p c] children] (static-matcher p (compile c)))) (cond-> data (conj (data-matcher data)))
wilds (into (for [[p c] wilds] (wild-matcher p (compile c)))) (into (for [[p c] children] (static-matcher p (compile c))))
catch-all (into (for [[p c] catch-all] (catch-all-matcher p (:data c)))))] (into (for [[p c] wilds, end (ends c)]
(if (rest matchers) (wild-matcher (:value p) (first end) (compile (update c :children select-keys [end])))))
(linear-matcher matchers) (into (for [[p c] catch-all] (catch-all-matcher (:value p) (:data c)))))]
(first matchers)))) (cond
(> (count matchers) 1) (linear-matcher matchers)
(= (count matchers) 1) (first matchers)
:else (data-matcher nil))))
(defn pretty [matcher] (defn pretty [matcher]
#?(:clj (-> matcher str read-string eval) #?(:clj (-> matcher str read-string eval)
@ -284,7 +294,13 @@
["/v1/orgs/:org-id/topics" 57]] ["/v1/orgs/:org-id/topics" 57]]
(insert) (insert)
(compile) (compile)
(pretty)) (pretty)
(./aprint))
(-> [["/{a}/2"]
["/{a}.2"]]
(insert)
(compile))
(-> [["/kikka" 2] (-> [["/kikka" 2]
["/kikka/kakka/kukka" 3] ["/kikka/kakka/kukka" 3]

View file

@ -332,6 +332,7 @@
;; 305ns (trie-router, no injects) ;; 305ns (trie-router, no injects)
;; 281ns (trie-router, no injects, optimized) ;; 281ns (trie-router, no injects, optimized)
;; 277ns (trie-router, no injects, switch-case) - 690ns clojure ;; 277ns (trie-router, no injects, switch-case) - 690ns clojure
;; 273ns (trie-router, no injects, direct-data)
(let [req (map->Req {:request-method :get, :uri "/repos/julienschmidt/httprouter/stargazers"})] (let [req (map->Req {:request-method :get, :uri "/repos/julienschmidt/httprouter/stargazers"})]
(title "param") (title "param")
(assert (= {:status 200, :body "/repos/:owner/:repo/stargazers"} (app req))) (assert (= {:status 200, :body "/repos/:owner/:repo/stargazers"} (app req)))
@ -346,6 +347,7 @@
;; 66µs (trie-router, no injects) ;; 66µs (trie-router, no injects)
;; 64µs (trie-router, no injects, optimized) - 124µs (clojure) ;; 64µs (trie-router, no injects, optimized) - 124µs (clojure)
;; 63µs (trie-router, no injects, switch-case) - 124µs (clojure) ;; 63µs (trie-router, no injects, switch-case) - 124µs (clojure)
;; 63ns (trie-router, no injects, direct-data)
(let [requests (mapv route->req routes)] (let [requests (mapv route->req routes)]
(title "all") (title "all")
(cc/quick-bench (cc/quick-bench