work on sorted list from map

This commit is contained in:
Bruno Windels 2019-02-21 23:08:23 +01:00
parent 5bff41c1ee
commit 1441abbf7e
4 changed files with 126 additions and 54 deletions

View 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;
};
}
}

View 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);
}
}
}

View file

@ -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]() {

View file

@ -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);
}