This library made the mistake of mixing mutable and immutable apis. I'm keeping it online as some of the code is interesting and worth using in object/array utils (e.g sorted)
If you need to work with plain Objects and Arrays in an immutable fashion, check immupdate; If you want slower but richer immutable collections, check immutable-js
Provides a small set of general purpose collections with implementations tailored for Javascript.
Some inspiration comes from Scala and its rich object/functional hybrid approach which fits JS well.
It should be compatible with any JS engine, even IE6's (Although it wasn't tested against it thus far)
collection-js weights 16KB minified and focuses solely on collections.
- Code example
- Using the library in your code
- Motivation
- Building collection-js
- API
- Extending collection-js
var List = Collection.List;
var Set = Collection.Set;
var ArrayMap = Collection.ArrayMap;
var Seq = Collection.Seq;
var people = Set(john, alice, robert);
var peopleArray = [john, alice, robert];
Seq(peopleArray).each(console.log);
Seq(peopleArray).count(function(person) {return person.age > 70});
var sportyPeople = ArrayMap(
alpinism, List(alice, john),
rugby, List(robert, john),
swimming, List(john, alice)
);
var isAthleteFor = function(sport) {
return function(person) {return person.weeklyTrainings.get(sport) > 10};
};
var athletes = sportyPeople.map(function(sport, people) {
return [sport, people.filter(isAthleteFor(sport))];
});
var swimmingAthletes = athletes.get(swimming);
console.log('There are ' + swimmingAthletes.size() + ' pro swimmers');
console.log('All athletes: ' + athletes.values().flatten().distinct());
List = Collection.List
Set = Collection.Set
ArrayMap = Collection.ArrayMap
Seq = Collection.Seq
people = Set(john, alice, robert)
peopleArray = [john, alice, robert]
Seq(peopleArray).each console.log
Seq(peopleArray).count (person) -> person.age > 70
sportyPeople = ArrayMap(
alpinism, List(alice, john),
rugby, List(robert, john),
swimming, List(john, alice)
)
isAthleteFor = (sport) ->
(person) -> person.weeklyTrainings.get(sport) > 10
athletes = sportyPeople.map (sport, people) ->
[sport, people.filter isAthleteFor(sport)]
swimmingAthletes = athletes.get(swimming)
console.log("There are #{ swimmingAthletes.size() } pro swimmers")
console.log("All athletes: #{ athletes.values().flatten().distinct() }")
As a script tag in the browser
Use collection-debug or collection-release in a script tag. This creates a global Collection namespace.
In node.js
collection-debug and collection-release can be used as node modules.
Using an AMD loader
collection-amd-debug is an AMD compatible module.
The cleanest notation to use this library in your code using AMD is probably:
var Set = require("collection").Set;
This library has no dependencies.
Javascript is a tiny language used more and more to build big sites and apps. It's even used on the server sometimes. To store your data, Javascript provides out of the box:
Array
JS Arrays are quite decent; They're dynamic so you thankfully don't have to resize them yourself. I consider some of the Array's API old fashioned and clunky, e.g splice which takes two integers as its first arguments; There is no remove() method which certainly would be used more often than splice(). Some of the API that was added over the years feel a bit more modern, like forEach although without shims they're not available in all browsers (forEach only since IE9).
Some libraries like underscore take the approach of wrapping an Array instance in a function to augment it;
While augmenting with function wrapping can be an elegant pattern, I find the syntax for collections a bit ugly and repetitive, also chaining looks like a hack so people usually skip it.
Some other libraries modify the Array prototype with non standard methods; I'm usually against this when it's done from a third party library.
Hence List, a richer, different type from Array. Like in some high level languages, using List is often the preferred approach but Arrays can still be used.
Object used as... An object
Nothing wrong here.
Object used as a Map
Now it gets bad. You can represent associations using an Object:
{'key1': value1, 'key2': value2}
The biggest limitation is that effectively, only strings can be used as keys. You could override all your objects' toString() methods to achieve this 'transparently' but it comes with some problems, is cumbersome (Especially when using object literals) and doesn't make sense semantically: toString() when overriden should be a nice string representation of the object, not its identity across the runtime.
Code using maps this way typically have to juggle a lot between objects and their string representation (Usually some id or name property); Some data structure maintains a list of ids, another one has the objects themselves and there is code in a few places to go back and forth between the two. It's cumbersome and distracting for the reader.
Another issue is that Objects have no Map API or state. Something as simple as getting the size of the map is an exercice on its own: Object.keys(map).length can only be used if shimmed.
You have to use a low level loop over the keys to do pretty much any work, as Objects have no Map-like API beside []
, .
and delete
.
Hence Map and its ordered sister ArrayMap.
Object used as a Set
All the issues mentionned for maps apply to sets, since an Object-based set is a map with fake truthy values:
{'key1': true, 'key2': true}
it's also pretty ugly and semantically weak compared to
Set('key1', 'key2')
Hence Set.
npm install uglify-js
node build.js
In this documentation, Any
means any Javascript primitive, native or custom object.
Iterable is used internally as a trait for indexed collections.
You don't use Iterable directly.
Whenever an Iterable method returns an Iterable, its type will be the same as the original's.
None of the Iterable methods mutate the original collection.
For ArrayMap, some of the method signatures are different; See ArrayMap.
Iterables (Array, List and ArrayMap) have the following properties and methods:
The current Array representation of the collection.
It should be considered read-only and never modified directly.
Returns the number of items in this collection.
Indicates whether this collection is empty.
Returns the item located at the specified index.
Returns the first item of this collection.
Returns the last item of this collection.
Applies a function to all items of this collection.
Builds a new collection by applying a function to all items of this collection.
ArrayMap will require that you return [key, value] tuples to create a new ArrayMap.
Note: If you intended to invoke filter and map in succession you can merge these operations into just one map() call by returning Collection.NOT_MAPPED for the items that shouldn't be in the final collection.
Builds a List of the extracted properties of this collection of objects.
This is a special case of map(). The property can be arbitrarily nested.
Example: var postCodes = users.pluck('address.postCode');
Selects all items of this collection which satisfy a predicate.
Counts the number of items in this collection which satisfy a predicate.
Finds the first item of the collection satisfying a predicate, if any.
Finds the first item of this collection of objects that owns a property set to a given value.
This is a special case of find(). The property can be arbitrarily nested.
Example:
var users = List(...);
var hacker = users.findBy('id', 1337);
var people = ArrayMap(...);
var homer = people.findBy('value.traits.color', 'yellow');
Tests whether a predicate holds for some of the items of this collection.
Tests whether a predicate holds for all items of this collection.
Partitions items in fixed size collections.
Partitions this collection into a map of Lists according to a discriminator function.
Folds the items of this collection using the specified operator.
fold is sometimes also called reduce.
Partitions this collection in two collections according to a predicate.
The first element of the returned Array contains the items that satisfied the predicate.
Selects all items except the first n ones.
Selects all items except the last n ones.
Drops items till the predicate no longer hold.
Selects the first n items.
Selects the last n items.
Selects items till the predicate no longer hold.
Returns a new collection with the items in reversed order.
Selects an interval of items, starting from the start
index and until, but not including end
.
Returns a new sorted collection. The sort is stable.
An option Object can be passed to modify the sort behavior.
All options are compatible with each other.
The supported options are:
ignoreCase: Assuming strings are going to be sorted, ignore their cases. Defaults to false.
localCompare: Assuming strings are going to be sorted,
handle locale-specific characters correctly at the cost of reduced sort speed. Defaults to false.
by: Assuming objects are being sorted, a String (See pluck
) or Function either pointing to or computing the value
that should be used for the sort. Defaults to null.
reverse: Reverse the sort. Defaults to false.
Example:
var numbers = [2, 3, 1];
var sortedNumbers = Seq(numbers).sorted();
var users = List(...);
var sortedByEmail = users.sorted({by: 'info.email'});
var sortedArbitrarily = users.sorted({by: function(person) { return info.email.slice(2, 6); }});
var people = ArrayMap(...);
var sortedPeople = people.sorted({by: 'info.email', ignoreCase: 1});
Displays all items of this collection as a string.
Converts this collection to a List.
Converts this collection to an Array.
If you do not require a new Array instance, consider using the items property instead.
Creates a (shallow) copy of this collection.
Sequence is used internally as a trait for iterable collections that are also sequences.
Whenever a Sequence method returns a Sequence, its type will be the same as the original's.
None of the Sequence methods mutate the original collection.
Sequence (or Seq, an alias) can be used to wrap an Array instance: See Array.
Sequences (Array and List) have the following properties and methods:
Tests whether this sequence contains a given item.
Builds a new sequence without any duplicate item.
Converts this sequence of collections into a sequence formed by the items of these collections.
Returns the index of the first occurence of item
in this sequence or -1 if none exists.
Returns the index of the last occurence of item
in this sequence or -1 if none exists.
Checks whether the specified sequence contains the same items in the same order as this sequence.
Builds a new sequence where all ocurrences of the specified arguments have been removed.
Example: var sanitized = sequence.removeItems(null, undefined);
An Array instance can be temporarily augmented (A la underscore) with all methods from Iterable and Sequence.
The Iterable/Sequence API remains the same except that when an Iterable/Sequence would have been returned, an Array
is returned instead. This makes chaining impossible.
Wrapping an Array can be useful as a one-off when using a List over an Array is not wanted.
Examples:
var people = [john, alice, robert];
Seq(people).each(console.log);
var seniors = Seq(people).count(function(person) {return person.age > 70});
var oneTwoThree = Seq([1, 2, 3, 2, 1]).distinct();
List is essentially a richer Array.
var list = List(1, 2, 3);
// or
var list = new List(1, 2, 3);
// or
var list = List.fromArray([1, 2, 3]);
In addition to all Iterable and Sequence methods, List has the following mutating methods:
Appends the item at the last position of this list.
Adds the item at a specific index.
Replaces the item at the given index with a new value.
Removes the item from this list.
Removes and returns the item located at the specified index.
Removes the first item from this list.
This is a mutating equivalent of Iterable's drop(1).
Removes the last item from this list.
This is a mutating equivalent of Iterable's dropRight(1).
Removes all items from this list.
Removes all items satisfying a predicate from this list.
Returns the List of removed items.
This is a mutating, (reversed) equivalent of Iterable's filter.
Converts this list to a Set.
Set is an unordered collection that does not allow duplicates.
A set can hold any primitive or object.
var set = Set(1, 2, 3);
// or
var set = new Set(1, 2, 3);
// or
var set = Set.fromArray([1, 2, 3]);
// or
function personEmail(person) {return person.email};
// Enables user-defined equality instead of the default instance equality
var set = Set.withKey(personEmail, john, sarah, alice);
Set methods:
Adds the item to this set if it is not already present.
Returns true if the item was added, false if it was already in this set.
Tests whether this set contains the specified item.
Removes the item from this set.
Returns true if the item was removed, false if the item was not in this set.
Removes all items satisfying a predicate.
Removes all items from this set.
Applies a function to all items of this set.
Returns the number of items in this set.
Computes the union between this set and another set.
Returns a set consisting of the items that are in this set or in the other set.
Computes the intersection between this set and another set.
Returns a set consisting of the items that are both in this set and in the other set.
Computes the difference of this set and another set.
Returns a set containing the items of this set that are not also contained in the other set.
Converts this set to a List.
Converts this set to an Array.
Creates a copy of this set.
Map is an unordered collection of key-value pairs.
Any primitive or object can be used as a key or value.
var map = Map(
1, 10,
2, 20,
3, 30
);
// or
var map = Map();
map.put(1, 10);
map.put(2, 20);
map.put(3, 30);
// or
var map = Map(
john, 40,
sarah, 50,
alice, 37
);
// or
function personEmail(person) {return person.email};
// Enables user-defined equality instead of the default instance equality
var map = Map.withKey(personEmail,
john, 40,
sarah, 50,
alice, 37
);
Map methods:
Adds a value for the specified key.
Returns the previous value mapped for this key, or undefined if the key is new.
Removes and returns the value mapped to the specified key.
Removes all key-value mappings satisfying a predicate.
Removes all key-value mappings from this map.
Returns the value associated with the specified key, or undefined.
If the given key is already in this map, returns the associated value.
Otherwise, either use the provided value as is if it's not a function or the result from that function call.
The value is then associated with that key and returned.
Example:
var list = multiMap.getOrPut(key, List);
list.add(...);
var counter = counters.getOrPut(key, 0);
counters.put(key, counter + 1);
Tests whether this map contains a binding for this key.
Tests whether this map contains this value at least once.
Returns a List of all the keys of this map, in no particular order.
Returns a List of all the values of this map, in no particular order.
Applies a function to all key-value of this map.
Returns the number of key-value pairs in this map.
Converts this map to a List.
Converts this map to an Array.
Creates a copy of this map.
ArrayMap is an indexed collection of key-value pairs.
The key-value pairs are stored in the order they were inserted.
ArrayMap is used like a Map. Use it over a Map when the insertion order is important and/or when using the methods from Iterable is desirable.
All methods from Map and Iterable are available with a few small differences:
each() also gets the current key-value index (You can skip that argument if you don't need it though):
In general, any methods from Iterable that invoked a predicate or callback with the current item, now calls the function passing both the current key and value, e.g
Internally, ArrayMap stores the items as {key: A, value: B} objects so this is the kind of object you're going to get when using methods such as first() or when you read the items property.
In addition to all Map and Iterable methods, ArrayMap has the following methods:
Same as Iterable's sorted
but sort the map based on its keys.
This is an alias of Iterable's sorted
.
Returns a list of integers from start
to stop
(inclusive), incremented or decremented by step
.
You can also use the shortcut range (n: Number) which returns the list of the n first integers, starting from 0.
Example:
var range = Collection.range;
range(10).each(alert); // will irritatingly alert 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
var someMultiplesOfFive = range(5, 20, 5); // List(5, 10, 15, 20)
collection-js aims to provide a few general purpose collections meeting common requirements.
It does not provide 37 implementation alternatives of almost the same thing nor collections meeting edge case requirements.
You can add more collections as your app require them.
As an example, below is the code for an AMD module that adds a simplistic but fully functional MultiMap type to the collection module.
It uses require.js, the closure style and just need to be loaded by your bootstrap/main.
define(function(require) {
var collection = require("lib/collection");
var Map = collection.Map;
var List = collection.List;
var MultiMap = function() {
var map = Map();
var instance = {};
instance.put = function(key, value) {
var list = map.getOrPut(key, List);
list.add(value);
};
instance.get = function(key) {
return map.get(key);
};
instance.remove = function(key, value) {
var list = map.get(key);
if (!list) return;
if (value === undefined) list.removeFirst();
else list.remove(value);
if (list.isEmpty()) map.remove(key);
};
return instance;
};
collection.MultiMap = MultiMap;
});