forked from mystiq/hydrogen-web
work on sorted list from map
This commit is contained in:
parent
5bff41c1ee
commit
1441abbf7e
4 changed files with 126 additions and 54 deletions
30
src/observable/BaseObservableCollection.js
Normal file
30
src/observable/BaseObservableCollection.js
Normal file
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
37
src/observable/list/BaseObservableList.js
Normal file
37
src/observable/list/BaseObservableList.js
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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<key,value> -> 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]() {
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue