Skip to content

Commit

Permalink
Add deleteAll
Browse files Browse the repository at this point in the history
Including docs and tests and method reference spreadsheet.
  • Loading branch information
kriskowal committed May 14, 2014
1 parent 6f85d94 commit 39710a0
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 4 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,27 @@ Collections: (Array+, List, Deque, Set, Map, MultiMap, SortedSet, SortedMap,
LruSet, LruMap, LfuSet, LfuMap, SortedArray, SortedArraySet, SortedArrayMap,
FastSet, FastMap, Dict, Heap)

### deleteAll(value, opt_equals)

Deletes every value equivalent to the given value from the collection.
For sets, this is equivalent to delete, but for lists, arrays, and sorted
arrays, may delete more than one value.
For lists and arrays, this involves a linear search, from the beginning,
splicing out each node as it is traversed.
For sorted arrays, there is a mode for the provided equals and the intrinsic
equals.
The provided equals falls back to the linear search provided by the underlying
array.
However, if deleteAll uses its intrinsic order and equivalence, it can guarantee
that all intrinsic values are within a range from the first to the last
equivalent value, so it can splice all equivalent values at once, using a binary
search to find the first equivalent value, and a linear search to find the last..
The method is not implemented on Deque or Heap since random manipulation of
internal content is out of scope for these collections.

Collections: (Array+, List, Set, SortedSet, LruSet, LfuSet, SortedArray,
SortedArraySet)

### indexOf(value)

Returns the position in the collection of a value, or `-1` if it is
Expand Down
1 change: 1 addition & 0 deletions checklist.csv
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Order,Method,Interface,Set,SortedSet,LruSet,LfuSet,SortedArraySet,FastSet,ArrayS
0d2a1b,"delete(value, equals)",order,(alt),(alt),(alt),(alt),(alt),(alt),(alt),(alt),(alt),(alt),(alt),(alt),(alt),(alt),(alt),List,(na),(alt),(alt),(na),Array,(alt),,,,
0d2a2,delete(key or index),map,(alt),(alt),(alt),(alt),(alt),(alt),(alt),GenericMap,GenericMap,GenericMap,GenericMap,GenericMap,GenericMap,Dict,WeakMap,(alt),(na),(alt),(alt),(na),(alt),(todo maybe for the property change),,,GenericMap,
0d2b1,"deleteEach(keys or values, optional equals)",collection,GenericCollection,GenericCollection,GenericCollection,GenericCollection,GenericCollection,GenericCollection,(alt),GenericCollection,GenericCollection,GenericCollection,GenericCollection,GenericCollection,GenericCollection,GenericCollection,(todo maybe),GenericCollection,(na),GenericCollection,GenericCollection,(na),GenericCollection,(todo maybe),GenericCollection,,,
0d2c,"deleteAll(value, equals)",(na),GenericSet,GenericSet,GenericSet,GenericSet,GenericSet,GenericSet,GenericSet,(na),(na),(na),(na),(na),(na),(na),(na),List,(na),(na),SortedArray,(na),Array,(na),,GenericSet,,
1a1,"indexOf(value, index)",array,(na),SortedSet,(na),(na),SortedArray O(log length),(na),(todo),(na),(na),(na),(na),(na),(na),(na),(na),(todo),Deque,(na),SortedArray O(log length),(todo),(spec),(na),,,,
1a2,"lastIndexOf(value, index)",order,(na),(na because uniqueness guarantees equivalence to indexOf),(na),(na),SortedArray O(log length),(na),(todo),(na),(na),(na),(na),(na),(na),(na),(na),(todo),Deque,(na),SortedArray O(log length),(todo),(spec),(na),,,,
1b1,"find(callback, thisp, index)",order,(todo),(todo),(na),(na),(todo),(na),(todo),(na),(na),(na),(na),(na),(na),(na),(na),(todo),(todo),(na),(todo),(todo),ES6,(na),,,,
Expand Down
7 changes: 7 additions & 0 deletions generic-set.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ GenericSet.prototype.symmetricDifference = function (that) {
return union.difference(intersection);
};

GenericSet.prototype.deleteAll = function (value) {
// deleteAll is equivalent to delete for sets since they guarantee that
// only one value exists for an equivalence class, but deleteAll returns
// the count of deleted values instead of whether a value was deleted.
return +this["delete"](value);
};

GenericSet.prototype.equals = function (that, equals) {
var self = this;
return (
Expand Down
20 changes: 18 additions & 2 deletions list.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ List.prototype.find = function (value, equals, index) {
var head = this.head;
var at = this.scan(index, head.next);
while (at !== head) {
if (equals(at.value, value)) {
if (equals(value, at.value)) {

This comment has been minimized.

Copy link
@mackwic

mackwic Jun 16, 2014

@kriskowal: sorry for the digging but do you mind if I ask the point of this change ?
Is it because of type coercion rules (coercing at.value to primitive if needed) ? I can't see how coercing the old value is better than coercing the new one.
again, sorry for the noise.

This comment has been minimized.

Copy link
@kriskowal

kriskowal Jun 16, 2014

Author Member

There’s no coercion with equals. I discovered while writing Jasminum that I needed to support patterns like this:

expect(value).toEqual(Any(Number))

Where the Any prototype overrides equals such that any.equals(1) would be true. This necessitates that the test value be able to determine the nature of the test.

Thank you for asking. I might have never found an opportunity to share this tidbit.

This comment has been minimized.

Copy link
@mackwic

mackwic Jun 16, 2014

well, equals could have been anything as it can be provided by the user... but it didn't make much sense.

Thanks for your quick explanation. I will look at Jasminium implementation, seems promising !

return at;
}
at = at.next;
Expand All @@ -49,7 +49,7 @@ List.prototype.findLast = function (value, equals, index) {
var head = this.head;
var at = this.scan(index, head.prev);
while (at !== head) {
if (equals(at.value, value)) {
if (equals(value, at.value)) {
return at;
}
at = at.prev;
Expand Down Expand Up @@ -88,6 +88,22 @@ List.prototype['delete'] = function (value, equals) {
return false;
};

List.prototype.deleteAll = function (value, equals) {
equals = equals || this.contentEquals;
var head = this.head;
var at = head.next;
var count = 0;
while (at !== head) {
if (equals(value, at.value)) {
at["delete"]();
count++;
}
at = at.next;
}
this.length -= count;
return count;
};

List.prototype.clear = function () {
var plus, minus;
if (this.dispatchesRangeChanges) {
Expand Down
14 changes: 14 additions & 0 deletions shim-array.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,20 @@ define("delete", function (value, equals) {
return false;
});

define("deleteAll", function (value, equals) {
equals = equals || this.contentEquals || Object.equals;
var count = 0;
for (var index = 0; index < this.length;) {
if (equals(value, this[index])) {
this.swap(index, 1);
count++;
} else {
index++;
}
}
return count;
});

define("find", function (value, equals) {
equals = equals || this.contentEquals || Object.equals;
for (var index = 0; index < this.length; index++) {
Expand Down
30 changes: 30 additions & 0 deletions sorted-array.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,34 @@ SortedArray.prototype["delete"] = function (value, equals) {
}
};

SortedArray.prototype.deleteAll = function (value, equals) {
if (equals) {
var count = this.array.deleteAll(value, equals);
this.length -= count;
return count;
} else {
var start = searchFirst(this.array, value, this.contentCompare, this.contentEquals);
if (start !== -1) {
var end = start;
while (this.contentEquals(value, this.array[end])) {
end++;
}
var minus = this.slice(start, end);
if (this.dispatchesRangeChanges) {
this.dispatchBeforeRangeChange([], minus, start);
}
this.array.splice(start, minus.length);
this.length -= minus.length;
if (this.dispatchesRangeChanges) {
this.dispatchRangeChange([], minus, start);
}
return minus.length;
} else {
return 0;
}
}
};

SortedArray.prototype.indexOf = function (value) {
return searchFirst(this.array, value, this.contentCompare, this.contentEquals);
};
Expand All @@ -173,6 +201,7 @@ SortedArray.prototype.find = function (value, equals, index) {
if (index) {
throw new Error("SortedArray#find does not support third argument: index");
}
// TODO support initial partition index
return searchFirst(this.array, value, this.contentCompare, this.contentEquals);
};

Expand All @@ -183,6 +212,7 @@ SortedArray.prototype.findLast = function (value, equals, index) {
if (index) {
throw new Error("SortedArray#findLast does not support third argument: index");
}
// TODO support initial partition index
return searchLast(this.array, value, this.contentCompare, this.contentEquals);
};

Expand Down
8 changes: 8 additions & 0 deletions spec/array-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -286,5 +286,13 @@ describe("Array", function () {

});

describe("deleteAll", function () {
it("should delete a range of equivalent values", function () {
var array = [1, 1, 1, 2, 2, 2, 3, 3, 3];
expect(array.deleteAll(2)).toBe(3);
expect(array).toEqual([1, 1, 1, 3, 3, 3]);
});
});

});

14 changes: 14 additions & 0 deletions spec/list-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,20 @@ describe("List", function () {

});

describe("deleteAll", function () {
it("deletes all equivalent values", function () {
var anyEven = {
equals: function (that) {
return that % 2 === 0;
}
};
var collection = List([1, 2, 3, 4, 5]);
expect(collection.deleteAll(anyEven)).toBe(2);
expect(collection.toArray()).toEqual([1, 3, 5]);
expect(collection.length).toBe(3);
});
});

describeRangeChanges(List);

});
Expand Down
6 changes: 6 additions & 0 deletions spec/set.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ function describeSet(Set, sorted) {
expect(set.has(object)).toBe(false);
});

it("can deleteAll", function () {
var set = new Set([0]);
expect(set.deleteAll(0)).toBe(1);
expect(set.deleteAll(0)).toBe(0);
});

if (!sorted) {
it("can add and delete objects from the same bucket", function () {
var a = {id: 0}, b = {id: 1};
Expand Down
23 changes: 21 additions & 2 deletions spec/sorted-array-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,27 @@ describe("SortedArray", function () {
});

describe("non-uniqueness", function () {
var array = SortedArray([1, 2, 3, 1, 2, 3]);
expect(array.slice()).toEqual([1, 1, 2, 2, 3, 3]);
it("should retain non-unique values", function () {
var array = SortedArray([1, 2, 3, 1, 2, 3]);
expect(array.slice()).toEqual([1, 1, 2, 2, 3, 3]);
});
});

describe("deleteAll", function () {
it("should delete a range of equivalent values", function () {
var array = SortedArray([1, 1, 1, 2, 2, 2, 3, 3, 3]);
expect(array.deleteAll(2)).toBe(3);
expect(array.toArray()).toEqual([1, 1, 1, 3, 3, 3]);
});
it("deletes all equivalent values for an alternate relation", function () {
var equivalent = function (a, b) {
return a % 2 === b % 2;
};
var collection = SortedArray([1, 2, 3, 4, 5]);
expect(collection.deleteAll(2, equivalent)).toBe(2);
expect(collection.toArray()).toEqual([1, 3, 5]);
expect(collection.length).toBe(3);
});
});

// TODO test stability
Expand Down

0 comments on commit 39710a0

Please sign in to comment.