diff --git a/book.iml b/book.iml
index 4da9514..32c73e0 100644
--- a/book.iml
+++ b/book.iml
@@ -6,6 +6,7 @@
+
diff --git a/project.clj b/project.clj
index 99670f9..ca08dd6 100644
--- a/project.clj
+++ b/project.clj
@@ -4,6 +4,7 @@
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.9.0"]]
+ :java-source-paths ["src/java"]
:main ^:skip-aot book.core
:target-path "target/%s"
:profiles {:uberjar {:aot :all}})
diff --git a/src/book/core.clj b/src/book/core.clj
index 5043080..2417f2e 100644
--- a/src/book/core.clj
+++ b/src/book/core.clj
@@ -1,10 +1,17 @@
(ns book.core
+ (:import [book.PersistentSortedList])
(:require [book.order]
[clojure.spec.alpha :as s]
[clojure.edn :as edn])
(:gen-class))
+(defn sorted-list [& keys]
+ (book.PersistentSortedList/create (seq keys)))
+
+(defn sorted-list-by [comparator & keys]
+ (book.PersistentSortedList/create comparator (seq keys)))
+
;; Let say a book is two collections. The first called
;; sell-book contains all the sell orders. The second
@@ -12,7 +19,14 @@
(defn create-book [& args]
(let [cmp #(< (:price %1) (:price %2))]
- (apply sorted-set-by cmp args)))
+ (apply sorted-list-by cmp args)))
+
+
+(defn sorted-set-rest [s]
+ (print "=>" s)
+ (if (empty? s)
+ s
+ (disj s (first s))))
(def book {:sell (create-book) :buy (create-book)})
@@ -65,12 +79,18 @@
(rest new-book))))
+(defn leftover-order [order trades]
+ (let [traded-quantity (reduce + (map :book.order/quantity trades))
+ leftover-quantity (- (:quantity order) traded-quantity)]
+ (when (pos? leftover-quantity)
+ (assoc order :quantity leftover-quantity))))
+
(defn match-buy-order-in-sell-book [sell-book order]
- (let [trades-1 [(create-trade order (first sell-book))]
- trades (create-sell-trades order sell-book)
- new-book (compute-new-sell-book-after-trades sell-book trades)]
- [trades new-book]))
+ (let [trades (create-sell-trades order sell-book)
+ new-book (compute-new-sell-book-after-trades sell-book trades)
+ new-buy-order (leftover-order order trades)]
+ [trades new-book new-buy-order]))
(defn place-buy-order [book order]
diff --git a/src/java/book/PersistentSortedList.java b/src/java/book/PersistentSortedList.java
new file mode 100644
index 0000000..e52f1e8
--- /dev/null
+++ b/src/java/book/PersistentSortedList.java
@@ -0,0 +1,120 @@
+package book;
+
+import clojure.lang.*;
+import java.util.*;
+
+public class PersistentSortedList extends ASeq implements IPersistentList, IReduce, List, Counted{
+ final private PersistentTreeMap impl;
+ final private int _count;
+ static public final PersistentSortedList EMPTY = new PersistentSortedList(null, PersistentTreeMap.EMPTY, 0);
+
+ public static PersistentSortedList create(ISeq items){
+ PersistentSortedList ret = EMPTY;
+ for(; items != null; items = items.next()){
+ ret = ret.cons(items.first());
+ }
+ return ret;
+ }
+
+ public static PersistentSortedList create(Comparator comp, ISeq items){
+ PersistentSortedList ret = new PersistentSortedList(null, new PersistentTreeMap(null, comp), 0);
+ for(; items != null; items = items.next()){
+ ret = ret.cons(items.first());
+ }
+ return ret;
+ }
+
+ PersistentSortedList(IPersistentMap meta, PersistentTreeMap impl, int count){
+ super(meta);
+ this.impl = impl;
+ this._count = count;
+ }
+
+ @Override
+ public int count(){
+ return _count;
+ }
+
+ @Override
+ public PersistentSortedList cons(final Object o){
+ final IPersistentCollection f = (IPersistentCollection) impl.get(o);
+ IPersistentCollection newList = f == null ? new PersistentList(o) : f.cons(o);
+ return new PersistentSortedList(meta(), impl.assoc(o, newList), _count + 1);
+ }
+
+ @Override
+ public Object peek() {
+ if(_count == 0){
+ return null;
+ }
+ final PersistentTreeMap.NodeIterator ite = this.impl.iterator();
+ final AMapEntry node = (AMapEntry) ite.next();
+ return ((IPersistentCollection) node.val()).seq().first();
+ }
+
+ @Override
+ public IPersistentStack pop() {
+ if(_count == 0){
+ throw new IllegalStateException("Can't pop empty list");
+ }
+ final PersistentTreeMap.NodeIterator ite = this.impl.iterator();
+ final AMapEntry node = (AMapEntry) ite.next();
+ if(((IPersistentCollection) node.val()).count() == 1){
+ final PersistentTreeMap nextImpl = impl.without(node.getKey());
+ return new PersistentSortedList(meta(), nextImpl, _count - 1);
+ }
+ final PersistentTreeMap nextImpl = impl.assoc(node.getKey(), ((IPersistentStack) node.getValue()).pop());
+ return new PersistentSortedList(meta(), nextImpl, _count -1);
+ }
+
+ @Override
+ public Object reduce(IFn f) {
+ Object ret = first();
+ for(ISeq s = next(); s != null; s = s.next()) {
+ ret = f.invoke(ret, s.first());
+ if (RT.isReduced(ret)) return ((IDeref)ret).deref();
+ }
+ return ret;
+ }
+
+ @Override
+ public Object reduce(IFn f, Object start) {
+ Object ret = f.invoke(start, first());
+ for(ISeq s = next(); s != null; s = s.next()) {
+ if (RT.isReduced(ret)) return ((IDeref)ret).deref();
+ ret = f.invoke(ret, s.first());
+ }
+ if (RT.isReduced(ret)) return ((IDeref)ret).deref();
+ return ret;
+ }
+
+ @Override
+ public Object first() {
+ return peek();
+ }
+
+ @Override
+ public PersistentSortedList next() {
+ if(_count == 0){
+ return null;
+ }
+ final PersistentTreeMap.NodeIterator ite = this.impl.iterator();
+ final AMapEntry node = (AMapEntry) ite.next();
+ final ISeq nextValue = ((IPersistentCollection) node.val()).seq().next();
+ if(nextValue == null){
+ final PersistentTreeMap nextImpl = impl.without(node.getKey());
+ return impl.count() > 1 ? new PersistentSortedList(meta(), nextImpl, _count - 1) : null;
+ }
+ final PersistentTreeMap nextImpl = impl.assoc(node.getKey(), nextValue);
+ return new PersistentSortedList(meta(), nextImpl, _count - 1);
+ }
+
+ @Override
+ public Obj withMeta(IPersistentMap meta) {
+ if(meta != meta()){
+ return new PersistentSortedList(meta, impl, _count);
+ }
+ return this;
+ }
+}
+
diff --git a/test/book/core_test.clj b/test/book/core_test.clj
index accc385..4c79272 100644
--- a/test/book/core_test.clj
+++ b/test/book/core_test.clj
@@ -3,10 +3,8 @@
[book.core :refer :all]))
-
-
(deftest test-match-buy-order-in-sell-book
- (testing "Simplest case."
+ (testing "Simplest case I."
(let [sell-book (create-book {:type :sell :quantity 10 :price 2 :book.order/id 101})
order {:type :buy :quantity 10 :price 2 :book.order/id 102}
[trades new-book] (match-buy-order-in-sell-book sell-book order)]
@@ -33,4 +31,47 @@
[trades new-book] (match-buy-order-in-sell-book sell-book order)]
(is (= new-book []))
(is (= trades [#:book.order{:quantity 2 :price 12 :book-id 107 :order-id 109 :type :sell}
- #:book.order{:quantity 8 :price 13 :book-id 108 :order-id 109 :type :sell}])))))
+ #:book.order{:quantity 8 :price 13 :book-id 108 :order-id 109 :type :sell}]))))
+
+ (testing "Simplest case V."
+ (let [sell-book (create-book {:type :sell :quantity 2 :price 12 :book.order/id 110} {:type :sell :quantity 8 :price 13 :book.order/id 111})
+ order {:type :buy :quantity 10 :price 13 :book.order/id 112}
+ [trades new-book] (match-buy-order-in-sell-book sell-book order)]
+ (is (empty? new-book))
+ (is (= trades [#:book.order{:quantity 2 :price 12 :book-id 110 :order-id 112 :type :sell}
+ #:book.order{:quantity 8 :price 13 :book-id 111 :order-id 112 :type :sell}]))))
+
+ (testing "Simplest case VI."
+ (let [sell-book (create-book {:type :sell :quantity 2 :price 12 :book.order/id 113} {:type :sell :quantity 8 :price 13 :book.order/id 114})
+ order {:type :buy :quantity 9 :price 13 :book.order/id 115}
+ [trades new-book] (match-buy-order-in-sell-book sell-book order)]
+ (is (= new-book (create-book {:type :sell :quantity 1 :price 13 :book.order/id 114})))
+ (is (= trades [#:book.order{:quantity 2 :price 12 :book-id 113 :order-id 115 :type :sell}
+ #:book.order{:quantity 7 :price 13 :book-id 114 :order-id 115 :type :sell}]))))
+
+ (testing "Simplest case VII."
+ (let [sell-book (create-book {:type :sell :quantity 2 :price 12 :book.order/id 116} {:type :sell :quantity 8 :price 13 :book.order/id 117})
+ order {:type :buy :quantity 21 :price 13 :book.order/id 118}
+ [trades new-book leftover-order] (match-buy-order-in-sell-book sell-book order)]
+ (is (empty? new-book))
+ (is (= trades [#:book.order{:quantity 2 :price 12 :book-id 116 :order-id 118 :type :sell}
+ #:book.order{:quantity 8 :price 13 :book-id 117 :order-id 118 :type :sell}]))
+ (is (= leftover-order {:type :buy :quantity 11 :price 13 :book.order/id 118}))))
+
+ (testing "Simplest case IIX."
+ (let [sell-book (create-book {:type :sell :quantity 2 :price 12 :book.order/id 116} {:type :sell :quantity 8 :price 13 :book.order/id 117})
+ order {:type :buy :quantity 21 :price 10 :book.order/id 118}
+ [trades new-book leftover-order] (match-buy-order-in-sell-book sell-book order)]
+ (is (= new-book sell-book))
+ (is (= trades []))
+ (is (= leftover-order order)))))
+
+
+;
+;(deftest test-match-sell-order-in-buy-book
+; (testing "Simplest case I."
+; (let [sell-book (create-book {:type :sell :quantity 10 :price 2 :book.order/id 101})
+; order {:type :buy :quantity 10 :price 2 :book.order/id 102}
+; [trades new-book] (match-sell-order-in-buy-book sell-book order)]
+; (is (= new-book []))
+; (is (= trades [#:book.order{:quantity 10 :price 2 :book-id 101 :order-id 102 :type :sell}])))))
diff --git a/test/book/test_PersistentSortedList.clj b/test/book/test_PersistentSortedList.clj
new file mode 100644
index 0000000..cf30676
--- /dev/null
+++ b/test/book/test_PersistentSortedList.clj
@@ -0,0 +1,47 @@
+(ns book.test-PersistentSortedList
+ (:require [clojure.test :refer :all]
+ [book.core :refer [sorted-list sorted-list-by]]))
+
+
+
+(deftest test-match-buy-order-in-sell-book
+ (testing "Test ordering."
+ (is (= (sorted-list 1 2 3) (sorted-list 3 2 1)))
+ (is (= (sorted-list 1 2 2 3) (sorted-list 3 2 1 2))))
+ (testing "count"
+ (is (= (count (sorted-list)) 0))
+ (is (= (count (sorted-list 1 2 3)) 3))
+ (is (= (count (sorted-list 1 2 3 2)) 4)))
+ (testing "rest"
+ (is (= (rest (sorted-list 1 2 3)) (sorted-list 2 3)))
+ (is (= (rest (sorted-list 1 2 3 2)) (sorted-list 2 2 3)))
+ (is (= (rest (sorted-list 1)) (list)))
+ (is (= (rest (sorted-list)) (list))))
+ (testing "next"
+ (is (= (next (sorted-list 1 2 3)) (sorted-list 2 3)))
+ (is (= (next (sorted-list 1 2 3 2)) (sorted-list 2 2 3)))
+ (is (= (next (sorted-list 1)) nil))
+ (is (= (next (sorted-list)) nil)))
+ (testing "drop"
+ (is (= (drop 2 (sorted-list 1 2 3)) (sorted-list 3)))
+ (is (= (drop 3 (sorted-list 1 2 3)) (list))))
+ (testing "reduce"
+ (is (= (reduce + (sorted-list 1 2 3)) 6))
+ (is (= (reduce + 10 (sorted-list 1 2 3)) 16)))
+ (testing "peek/pop"
+ (is (= (peek (sorted-list 1 2 3)) 1))
+ (is (= (peek (sorted-list 1 1 1 2 3)) 1))
+ (is (= (peek (sorted-list)) nil))
+ (is (= (pop (sorted-list 1 2 3)) (sorted-list 2 3)))
+ (is (= (pop (sorted-list 1 1 2 3)) (sorted-list 1 2 3)))
+ (is (= (pop (sorted-list 1 1 1 2 3)) (sorted-list 1 1 2 3)))
+ (is (thrown? IllegalStateException (pop (sorted-list)) nil)))
+ (testing "count after change"
+ (let [l (sorted-list 1 2 3)]
+ (is (= (count (conj l 4)) 4))
+ (is (= (count (conj l 3)) 4))
+ (is (= (count (cons 3 l)) 4))
+ (is (= (count (rest l)) 2))
+ (is (= (count (pop l)) 2))))
+ (testing "sorted-list-by"
+ (is (= (sorted-list-by > 1 2 3) [3 2 1]))))