From 1441abbf7ec7dbfd075d3a871a9c9aeae0eb6df5 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 21 Feb 2019 23:08:23 +0100 Subject: [PATCH] work on sorted list from map --- src/observable/BaseObservableCollection.js | 30 +++++++ src/observable/list/BaseObservableList.js | 37 +++++++++ .../{map/operators => list}/SortOperator.js | 81 +++++++++++++------ src/observable/map/BaseObservableMap.js | 32 +------- 4 files changed, 126 insertions(+), 54 deletions(-) create mode 100644 src/observable/BaseObservableCollection.js create mode 100644 src/observable/list/BaseObservableList.js rename src/observable/{map/operators => list}/SortOperator.js (50%) diff --git a/src/observable/BaseObservableCollection.js b/src/observable/BaseObservableCollection.js new file mode 100644 index 00000000..cfa1b4c7 --- /dev/null +++ b/src/observable/BaseObservableCollection.js @@ -0,0 +1,30 @@ +export default class BaseObservableCollection { + constructor() { + this._handlers = new Set(); + } + + onSubscribeFirst() { + + } + + onUnsubscribeLast() { + + } + + subscribe(handler) { + this._handlers.add(handler); + if (this._handlers.length === 1) { + this.onSubscribeFirst(); + } + return () => { + if (handler) { + this._handlers.delete(this._handler); + if (this._handlers.length === 0) { + this.onUnsubscribeLast(); + } + handler = null; + } + return null; + }; + } +} diff --git a/src/observable/list/BaseObservableList.js b/src/observable/list/BaseObservableList.js new file mode 100644 index 00000000..7980f4f2 --- /dev/null +++ b/src/observable/list/BaseObservableList.js @@ -0,0 +1,37 @@ +import BaseObservableCollection from "../BaseObservableCollection.js"; + +export default class BaseObservableList extends BaseObservableCollection { + emitReset() { + for(let h of this._handlers) { + h.onReset(); + } + } + // we need batch events, mostly on index based collection though? + // maybe we should get started without? + emitAdd(index, value) { + for(let h of this._handlers) { + h.onAdd(index, value); + } + } + + emitChange(index, value, params) { + for(let h of this._handlers) { + h.onChange(index, value, params); + } + } + + emitRemove(index, value) { + for(let h of this._handlers) { + h.onRemove(index, value); + } + } + + // toIdx assumes the item has already + // been removed from its fromIdx + emitMove(fromIdx, toIdx, value) { + for(let h of this._handlers) { + h.onMove(fromIdx, toIdx, value); + } + } + +} diff --git a/src/observable/map/operators/SortOperator.js b/src/observable/list/SortOperator.js similarity index 50% rename from src/observable/map/operators/SortOperator.js rename to src/observable/list/SortOperator.js index aacc29e2..c8a6e51f 100644 --- a/src/observable/map/operators/SortOperator.js +++ b/src/observable/list/SortOperator.js @@ -1,4 +1,30 @@ -import Operator from "../Operator.js"; +import BaseObservableList from "../BaseObservableList.js"; + + +/* + +when a value changes, it sorting order can change. It would still be at the old index prior to firing an onChange event. +So how do you know where it was before it changed, if not by going over all values? + +how to make this fast? + +seems hard to solve with an array, because you need to map the key to it's previous location somehow, to efficiently find it, +and move it. + +I wonder if we could do better with a binary search tree (BST). +The tree has a value with {key, value}. There is a plain Map mapping keys to this tuple, +for easy lookup. Now how do we find the index of this tuple in the BST? + +either we store in every node the amount of nodes on the left and right, or we decend into the part +of the tree preceding the node we want to know about. Updating the counts upwards would probably be fine as this is log2 of +the size of the container. + +to be able to go from a key to an index, the value would have the have a link with the tree node though + +so key -> Map -> value -> node -> *parentNode -> rootNode +with a node containing {value, leftCount, rightCount, leftNode, rightNode, parentNode} +*/ + /** * @license @@ -26,44 +52,54 @@ function sortedIndex(array, value, comparator) { } return high; } - -// TODO: this should not inherit from an BaseObservableMap, as it's a list -export default class SortOperator extends Operator { - constructor(sourceMap, comparator) { - super(sourceMap); +// no duplicates allowed for now +export default class SortOperator extends BaseObservableList { + constructor(sourceMap, comparator, getKey) { + super(); + this._sourceMap = sourceMap; this._comparator = comparator; + this._getKey = getKey; this._sortedValues = []; - this._keyIndex = new Map(); } onAdd(key, value) { const idx = sortedIndex(this._sortedValues, value, this._comparator); this._sortedValues.splice(idx, 0, value); - this._keyIndex.set(key, idx); this.emitAdd(idx, value); } - onRemove(key, _value) { + onRemove(key, value) { const idx = sortedIndex(this._sortedValues, value, this._comparator); - this._sortedValues.splice(idx, 0, value); - this._keyIndex.set(key, idx); - this.emitAdd(idx, value); + this._sortedValues.splice(idx, 1); + this.emitRemove(idx, value); } onChange(key, value, params) { - // index could have moved if other items got added in the meantime - const oldIdx = this._keyIndex.get(key); - this._sortedValues.splice(oldIdx, 1); - const idx = sortedIndex(this._sortedValues, value, this._comparator); + const idxIfSortUnchanged = sortedIndex(this._sortedValues, value, this._comparator); + if (this._getKey(this._sortedValues[idxIfSortUnchanged]) === key) { + this.emitChange(idxIfSortUnchanged, value, params); + } else { + // TODO: slow!? See above for idea with BST to speed this up if we need to + const oldIdx = this._sortedValues.find(v => this._getKey(v) === key); + this._sortedValues.splice(oldIdx, 1); + const newIdx = sortedIndex(this._sortedValues, value, this._comparator); + this._sortedValues.splice(newIdx, 0, value); + this.emitMove(oldIdx, newIdx, value); + this.emitChange(newIdx, value, params); + } + } + onReset() { + this._sortedValues = []; + this.emitReset(); } onSubscribeFirst() { - this._sortedValues = new Array(this._source.size); + this._mapSubscription = this._sourceMap.subscribe(this); + this._sortedValues = new Array(this._sourceMap.size); let i = 0; - for (let [key, value] of this._source) { + for (let [, value] of this._sourceMap) { this._sortedValues[i] = value; - this._keyIndex.set(key, i); ++i; } this._sortedValues.sort(this._comparator); @@ -73,15 +109,12 @@ export default class SortOperator extends Operator { onUnsubscribeLast() { super.onUnsubscribeLast(); this._sortedValues = null; + this._mapSubscription = this._mapSubscription(); } - onReset() { - this._sortedValues = []; - this.emitReset(); - } get length() { - return this._source.size; + return this._sourceMap.size; } [Symbol.iterator]() { diff --git a/src/observable/map/BaseObservableMap.js b/src/observable/map/BaseObservableMap.js index dc7c7199..7d4f0ce1 100644 --- a/src/observable/map/BaseObservableMap.js +++ b/src/observable/map/BaseObservableMap.js @@ -1,11 +1,8 @@ import MapOperator from "./operators/MapOperator.js"; import SortOperator from "./operators/SortOperator.js"; +import BaseObservableCollection from "../BaseObservableCollection.js"; -export default class BaseObservableMap { - constructor() { - this._handlers = new Set(); - } - +export default class BaseObservableMap extends BaseObservableCollection { emitReset() { for(let h of this._handlers) { h.onReset(); @@ -31,31 +28,6 @@ export default class BaseObservableMap { } } - onSubscribeFirst() { - - } - - onUnsubscribeLast() { - - } - - subscribe(handler) { - this._handlers.add(handler); - if (this._handlers.length === 1) { - this.onSubscribeFirst(); - } - return () => { - if (handler) { - this._handlers.delete(this._handler); - if (this._handlers.length === 0) { - this.onUnsubscribeLast(); - } - handler = null; - } - return null; - }; - } - map(mapper, updater) { return new MapOperator(this, mapper, updater); }