Skip to content

Commit

Permalink
Merge pull request #72 from bustlelabs/fix-linked-list
Browse files Browse the repository at this point in the history
add `LinkedList#removeBy`
  • Loading branch information
mixonic committed Aug 12, 2015
2 parents cee6170 + 1cf6e59 commit 6d4983d
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 9 deletions.
37 changes: 28 additions & 9 deletions src/js/utils/linked-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@ export default class LinkedList {
this.head = null;
this.tail = null;
this.length = 0;

if (options) {
let {adoptItem, freeItem} = options;
this.adoptItem = adoptItem;
this.freeItem = freeItem;
const {adoptItem, freeItem} = options;
this._adoptItem = adoptItem;
this._freeItem = freeItem;
}
}
adoptItem(item) {
if (this._adoptItem) { this._adoptItem(item); }
}
freeItem(item) {
if (this._freeItem) { this._freeItem(item); }
}
get isEmpty() {
return this.length === 0;
}
Expand All @@ -28,10 +35,11 @@ export default class LinkedList {
this.insertBefore(item, nextItem);
}
insertBefore(item, nextItem) {
this.remove(item);
if (this.adoptItem) {
this.adoptItem(item);
if (item.next || item.prev || this.head === item) {
throw new Error('Cannot insert an item into a list if it is already in a list');
}
this.adoptItem(item);

if (nextItem && nextItem.prev) {
// middle of the items
let prevItem = nextItem.prev;
Expand Down Expand Up @@ -62,9 +70,8 @@ export default class LinkedList {
this.length++;
}
remove(item) {
if (this.freeItem) {
this.freeItem(item);
}
this.freeItem(item);

let didRemove = false;
if (item.next && item.prev) {
// Middle of the list
Expand Down Expand Up @@ -148,4 +155,16 @@ export default class LinkedList {
this.insertBefore(newItem, nextItem);
});
}
removeBy(conditionFn) {
let item = this.head;
while (item) {
let nextItem = item.next;

if (conditionFn(item)) {
this.remove(item);
}

item = nextItem;
}
}
}
110 changes: 110 additions & 0 deletions tests/unit/utils/linked-list-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -345,3 +345,113 @@ test(`#splice can reorganize items`, (assert) => {
assert.equal(list.objectAt(1), itemOne, 'itemOne is present');
assert.equal(list.objectAt(2), itemTwo, 'itemTwo is present');
});

test(`#removeBy mutates list when item is in middle`, (assert) => {
let list = new LinkedList();
let items = [
new LinkedItem(),
new LinkedItem(),
new LinkedItem(),
new LinkedItem()
];
items[1].shouldRemove = true;
items.forEach(i => list.append(i));

assert.equal(list.length, 4);
list.removeBy(i => i.shouldRemove);
assert.equal(list.length, 3);
assert.equal(list.head, items[0]);
assert.equal(list.objectAt(1), items[2]);
assert.equal(list.objectAt(2), items[3]);
assert.equal(list.tail, items[3]);
});

test(`#removeBy mutates list when item is first`, (assert) => {
let list = new LinkedList();
let items = [
new LinkedItem(),
new LinkedItem(),
new LinkedItem(),
new LinkedItem()
];
items[0].shouldRemove = true;
items.forEach(i => list.append(i));

assert.equal(list.length, 4);
list.removeBy(i => i.shouldRemove);
assert.equal(list.length, 3);
assert.equal(list.head, items[1]);
assert.equal(list.objectAt(1), items[2]);
assert.equal(list.tail, items[3]);
});

test(`#removeBy mutates list when item is last`, (assert) => {
let list = new LinkedList();
let items = [
new LinkedItem(),
new LinkedItem(),
new LinkedItem(),
new LinkedItem()
];
items[3].shouldRemove = true;
items.forEach(i => list.append(i));

assert.equal(list.length, 4);
list.removeBy(i => i.shouldRemove);
assert.equal(list.length, 3);
assert.equal(list.head, items[0]);
assert.equal(list.objectAt(1), items[1]);
assert.equal(list.tail, items[2]);
});

test('#removeBy calls `freeItem` for each item removed', (assert) => {
let freed = [];

let list = new LinkedList({
freeItem(item) {
freed.push(item);
}
});

let items = [
new LinkedItem(),
new LinkedItem(),
new LinkedItem()
];
items[0].name = '0';
items[1].name = '1';
items[2].name = '2';

items[0].shouldRemove = true;
items[1].shouldRemove = true;

items.forEach(i => list.append(i));

list.removeBy(i => i.shouldRemove);

assert.deepEqual(freed, [items[0], items[1]]);
});

test('#insertBefore throws if item to be inserted is already in this list', (assert) => {
let item1 = new LinkedItem();
let list1 = new LinkedList();
list1.append(item1);

assert.throws(() => {
list1.insertBefore(item1, null);
});
});

test('#insertBefore throws if item to be inserted is in another non-empty list', (assert) => {
let item1 = new LinkedItem();
let item2 = new LinkedItem();
let list1 = new LinkedList();
list1.append(item1);
list1.append(item2);

let list2 = new LinkedList();

assert.throws(() => {
list2.insertBefore(item1, null);
});
});

0 comments on commit 6d4983d

Please sign in to comment.