@rimbu/hashed
Version:
Immutable HashMap and HashSet implementations for TypeScript
441 lines • 18.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HashMapCollisionBuilder = exports.HashMapBlockBuilder = void 0;
var tslib_1 = require("tslib");
var base_1 = require("@rimbu/base");
var common_1 = require("@rimbu/common");
var list_1 = require("@rimbu/list");
var stream_1 = require("@rimbu/stream");
var custom_1 = require("@rimbu/stream/custom");
var common_2 = require("@rimbu/hashed/common");
var HashMapBlockBuilder = /** @class */ (function (_super) {
tslib_1.__extends(HashMapBlockBuilder, _super);
function HashMapBlockBuilder(context, source, _entries, _entrySets, size, level) {
if (size === void 0) { size = (_a = source === null || source === void 0 ? void 0 : source.size) !== null && _a !== void 0 ? _a : 0; }
if (level === void 0) { level = (_b = source === null || source === void 0 ? void 0 : source.level) !== null && _b !== void 0 ? _b : 0; }
var _a, _b;
var _this = _super.call(this) || this;
_this.context = context;
_this.source = source;
_this._entries = _entries;
_this._entrySets = _entrySets;
_this.size = size;
_this.level = level;
_this._lock = 0;
_this.get = function (key, otherwise, hash) {
if (undefined !== _this.source)
return _this.source.get(key, otherwise);
if (!_this.context.hasher.isValid(key))
return (0, common_1.OptLazy)(otherwise);
var keyHash = hash !== null && hash !== void 0 ? hash : _this.context.hash(key);
var keyIndex = _this.context.getKeyIndex(_this.level, keyHash);
if (keyIndex in _this.entries) {
var currentEntry = _this.entries[keyIndex];
if (_this.context.eq(key, currentEntry[0]))
return currentEntry[1];
return (0, common_1.OptLazy)(otherwise);
}
if (keyIndex in _this.entrySets) {
var currentEntrySet = _this.entrySets[keyIndex];
return currentEntrySet.get(key, otherwise, keyHash);
}
return (0, common_1.OptLazy)(otherwise);
};
// prettier-ignore
_this.hasKey = function (key) {
var token = Symbol();
return token !== _this.get(key, token);
};
_this.addEntry = function (entry) {
_this.checkLock();
return _this.addEntryInternal(entry);
};
_this.addEntries = function (source) {
_this.checkLock();
if ((0, custom_1.isEmptyStreamSourceInstance)(source))
return false;
return stream_1.Stream.from(source).filterPure({ pred: _this.addEntry }).count() > 0;
};
_this.set = function (key, value) {
_this.checkLock();
return _this.addEntryInternal([key, value]);
};
_this.modifyAt = function (key, options, keyHash) {
if (keyHash === void 0) { keyHash = _this.context.hash(key); }
_this.checkLock();
var keyIndex = _this.context.getKeyIndex(_this.level, keyHash);
if (keyIndex in _this.entries) {
// potential match in entries
var currentEntry = _this.entries[keyIndex];
var _a = tslib_1.__read(currentEntry, 2), currentKey = _a[0], currentValue = _a[1];
if (_this.context.eq(key, currentKey)) {
// exact match
if (undefined === options.ifExists)
return false;
var newValue_1 = options.ifExists instanceof Function
? options.ifExists(currentValue, base_1.Token)
: options.ifExists;
if (Object.is(newValue_1, currentValue)) {
return false;
}
_this.source = undefined;
if (base_1.Token === newValue_1) {
_this.size--;
delete _this.entries[keyIndex];
return true;
}
// replace current value
var newEntry = [key, newValue_1];
_this.entries[keyIndex] = newEntry;
return true;
}
if (undefined === options.ifNew)
return false;
// no match, replace entry with entryset containing both entries
var newValue_2 = (0, common_1.OptLazyOr)(options.ifNew, base_1.Token);
if (base_1.Token === newValue_2)
return false;
_this.source = undefined;
_this.size++;
delete _this.entries[keyIndex];
var newEntrySet = _this.level < _this.context.maxDepth
? new HashMapBlockBuilder(_this.context, undefined, undefined, undefined, 0, _this.level + 1)
: new HashMapCollisionBuilder(_this.context);
newEntrySet.addEntryInternal(currentEntry);
newEntrySet.addEntryInternal([key, newValue_2], keyHash);
_this.entrySets[keyIndex] = newEntrySet;
return true;
}
if (keyIndex in _this.entrySets) {
// potential match in entrysets
var entrySet = _this.entrySets[keyIndex];
var preSize = entrySet.size;
var result = entrySet.modifyAt(key, options, keyHash);
if (result)
_this.source = undefined;
_this.size += entrySet.size - preSize;
if (entrySet.size > 1)
return result;
// single entry needs to be pulled up
var first = undefined;
if (_this.context.isHashMapBlockBuilder(entrySet)) {
for (var index in entrySet.entries) {
first = entrySet.entries[index];
break;
}
}
else {
first = entrySet.entries.get(0, base_1.RimbuError.throwInvalidStateError);
}
delete _this.entrySets[keyIndex];
_this.entries[keyIndex] = first;
return true;
}
if (undefined === options.ifNew)
return false;
// no matching entry or entrySet
var newValue = (0, common_1.OptLazyOr)(options.ifNew, base_1.Token);
if (base_1.Token === newValue)
return false;
_this.source = undefined;
_this.size++;
_this.entries[keyIndex] = [key, newValue];
return true;
};
// prettier-ignore
_this.updateAt = function (key, update, otherwise) {
var result;
var found = false;
_this.modifyAt(key, {
ifExists: function (value) {
result = value;
found = true;
return (0, common_1.Update)(value, update);
},
});
if (!found)
return (0, common_1.OptLazy)(otherwise);
return result;
};
_this.removeKey = function (key, otherwise) {
_this.checkLock();
if (!_this.context.hasher.isValid(key))
return (0, common_1.OptLazy)(otherwise);
var removedValue;
var found = false;
_this.modifyAt(key, {
ifExists: function (currentValue, remove) {
removedValue = currentValue;
found = true;
return remove;
},
});
if (!found)
return (0, common_1.OptLazy)(otherwise);
return removedValue;
};
// prettier-ignore
_this.removeKeys = function (keys) {
_this.checkLock();
if ((0, custom_1.isEmptyStreamSourceInstance)(keys))
return false;
var notFound = Symbol();
return (stream_1.Stream.from(keys)
.mapPure(_this.removeKey, notFound)
.countElement(notFound, { negate: true }) > 0);
};
_this.forEach = function (f, options) {
if (options === void 0) { options = {}; }
var _a = options.state, state = _a === void 0 ? (0, common_1.TraverseState)() : _a;
_this._lock++;
_super.prototype.forEach.call(_this, f, { state: state });
_this._lock--;
};
_this.build = function () {
if (_this.size === 0)
return _this.context.empty();
return _this.buildNE();
};
// prettier-ignore
_this.buildMapValues = function (f) {
if (_this.size === 0) {
return _this.context.empty();
}
if (undefined !== _this.source)
return _this.source.mapValues(f);
var entries = _this.entries.length === 0
? null
: base_1.Arr.mapSparse(_this.entries, function (e) { return [
e[0],
f(e[1], e[0]),
]; });
var entrySets = _this.entrySets.length === 0
? null
: base_1.Arr.mapSparse(_this.entrySets, function (entrySet) {
return entrySet.buildMapValues(f);
});
return _this.context.block(entries, entrySets, _this.size, _this.level);
};
return _this;
}
HashMapBlockBuilder.prototype.checkLock = function () {
if (this._lock)
base_1.RimbuError.throwModifiedBuilderWhileLoopingOverItError();
};
HashMapBlockBuilder.prototype.prepareMutate = function () {
var _this = this;
var _a;
if (undefined === this._entries) {
if (undefined !== this.source) {
this._entries =
null === this.source.entries
? []
: base_1.Arr.copySparse(this.source.entries);
}
else {
this._entries = [];
}
}
if (undefined === this._entrySets) {
if (undefined !== this.source) {
this._entrySets =
null === this.source.entrySets
? []
: ((_a = base_1.Arr.mapSparse(this.source.entrySets, function (entrySet) {
if (_this.context.isHashMapBlock(entrySet)) {
return new HashMapBlockBuilder(_this.context, entrySet);
}
return new HashMapCollisionBuilder(_this.context, entrySet);
})) !== null && _a !== void 0 ? _a : []);
}
else {
this._entrySets = [];
}
}
};
Object.defineProperty(HashMapBlockBuilder.prototype, "entries", {
get: function () {
this.prepareMutate();
return this._entries;
},
enumerable: false,
configurable: true
});
Object.defineProperty(HashMapBlockBuilder.prototype, "entrySets", {
get: function () {
this.prepareMutate();
return this._entrySets;
},
enumerable: false,
configurable: true
});
HashMapBlockBuilder.prototype.addEntryInternal = function (entry, hash) {
if (hash === void 0) { hash = this.context.hash(entry[0]); }
var keyIndex = this.context.getKeyIndex(this.level, hash);
if (keyIndex in this.entries) {
var currentEntry = this.entries[keyIndex];
if (this.context.eq(entry[0], currentEntry[0])) {
if (Object.is(entry[1], currentEntry[1]))
return false;
this.source = undefined;
this.entries[keyIndex] = entry;
return true;
}
this.source = undefined;
this.size++;
delete this.entries[keyIndex];
if (this.level < this.context.maxDepth) {
var newEntrySet_1 = new HashMapBlockBuilder(this.context, undefined, undefined, undefined, 0, this.level + 1);
newEntrySet_1.addEntryInternal(currentEntry);
newEntrySet_1.addEntryInternal(entry, hash);
this.entrySets[keyIndex] = newEntrySet_1;
return true;
}
var newEntries = list_1.List.builder();
newEntries.append(currentEntry);
newEntries.append(entry);
var newEntrySet = new HashMapCollisionBuilder(this.context, undefined, newEntries);
this.entrySets[keyIndex] = newEntrySet;
return true;
}
if (keyIndex in this.entrySets) {
var currentEntrySet = this.entrySets[keyIndex];
var preSize = currentEntrySet.size;
var changed = currentEntrySet.addEntryInternal(entry, hash);
if (changed)
this.source = undefined;
this.size += currentEntrySet.size - preSize;
return changed;
}
this.source = undefined;
this.size++;
this.entries[keyIndex] = entry;
return true;
};
HashMapBlockBuilder.prototype.buildNE = function () {
if (undefined !== this.source)
return this.source;
var entries = this.entries.length === 0 ? null : base_1.Arr.copySparse(this.entries);
var entrySets = this.entrySets.length === 0
? null
: base_1.Arr.mapSparse(this.entrySets, function (entrySet) { return entrySet.buildNE(); });
return this.context.block(entries, entrySets, this.size, this.level);
};
return HashMapBlockBuilder;
}(common_2.BlockBuilderBase));
exports.HashMapBlockBuilder = HashMapBlockBuilder;
var HashMapCollisionBuilder = /** @class */ (function (_super) {
tslib_1.__extends(HashMapCollisionBuilder, _super);
function HashMapCollisionBuilder(context, source, _entries) {
var _this = _super.call(this) || this;
_this.context = context;
_this.source = source;
_this._entries = _entries;
return _this;
}
HashMapCollisionBuilder.prototype.get = function (key, otherwise, hash) {
var _this = this;
if (!this.context.hasher.isValid(key))
return (0, common_1.OptLazy)(otherwise);
if (undefined !== this.source)
return this.source.get(key, otherwise);
var token = Symbol();
var result = token;
this.entries.forEach(function (e, _, halt) {
if (_this.context.eq(key, e[0])) {
result = e[1];
halt();
}
});
if (token === result)
return (0, common_1.OptLazy)(otherwise);
return result;
};
HashMapCollisionBuilder.prototype.addEntryInternal = function (entry) {
var _this = this;
var index = -1;
this.entries.forEach(function (e, i, halt) {
if (_this.context.eq(e[0], entry[0])) {
index = i;
halt();
}
});
if (index < 0) {
this.source = undefined;
this.entries.append(entry);
return true;
}
var oldEntry = this.entries.updateAt(index, function (currentEntry) {
if (Object.is(currentEntry[1], entry[1]))
return currentEntry;
return entry;
});
var changed = undefined === oldEntry ||
!Object.is(oldEntry[1], entry[1]) ||
!Object.is(oldEntry[0], entry[0]);
if (changed) {
this.source = undefined;
}
return changed;
};
HashMapCollisionBuilder.prototype.set = function (key, value) {
return this.addEntryInternal([key, value]);
};
HashMapCollisionBuilder.prototype.modifyAt = function (atKey, options) {
var _this = this;
var index = -1;
var foundEntry = undefined;
this.entries.forEach(function (e, i, halt) {
if (_this.context.eq(e[0], atKey)) {
index = i;
foundEntry = e;
halt();
}
});
if (undefined === foundEntry) {
if (undefined === options.ifNew)
return false;
var newValue_3 = (0, common_1.OptLazyOr)(options.ifNew, base_1.Token);
if (base_1.Token === newValue_3)
return false;
this.source = undefined;
this.entries.append([atKey, newValue_3]);
return true;
}
if (undefined === options.ifExists)
return false;
var newValue = options.ifExists instanceof Function
? options.ifExists(foundEntry[1], base_1.Token)
: options.ifExists;
if (Object.is(newValue, foundEntry[1]))
return false;
if (base_1.Token === newValue) {
this.source = undefined;
this.entries.remove(index);
return true;
}
var result = this.entries.set(index, [atKey, newValue]);
var changed = undefined !== result;
if (changed)
this.source = undefined;
return changed;
};
HashMapCollisionBuilder.prototype.buildNE = function () {
if (undefined !== this.source)
return this.source;
return this.context.collision(this.entries.build().assumeNonEmpty());
};
HashMapCollisionBuilder.prototype.buildMapValues = function (f) {
if (undefined !== this.source)
return this.source.mapValues(f);
return this.context.collision(this.entries
.buildMap(function (entry) { return [
entry[0],
f(entry[1], entry[0]),
]; })
.assumeNonEmpty());
};
return HashMapCollisionBuilder;
}(common_2.CollisionBuilderBase));
exports.HashMapCollisionBuilder = HashMapCollisionBuilder;
//# sourceMappingURL=builder.cjs.map