pycollections.js contains class definitions for Dict, DefaultDict, Counter, OrderedDict, and NamedTuple, stocked with functionality that (mostly) mirrors their collections.py counterparts and exceeds that of the ES6 Map class.
It has been tested on NodeJS v20.8.1, and the latest Chrome, Firefox, and Safari browsers.
TypeScript built-ins have better test coverage, and they use the TS compiler.
The TypeScript Map
class is very close to Python's dict
class.
This TS code turns a native Map
into a defaultdict:
class DefaultDict<K, V> {
constructor(
private readonly map: Map<K, V>,
private readonly defaultFn: (key: K) => V
) {}
get(key: K): V {
if (this.map.has(key)) {
return this.map.get(key)!;
}
const val = this.defaultFn(key);
this.map.set(key, val);
return val;
}
}
A collections.Counter
is basically a DefaultDict
with a default function
that returns 0, plus some extras for getting the most common elements. The
pycollections.js library never did anything fancy like the heap queue algorithm
anyway.
The built-in Map
preserves the insertion order of its entries.
TypeScript's type aliases or interfaces are good substitutes for namedtuples.
- Getting it
- Dict
- Creating a new Dict
- Detecting presence of a key
- Getting a potentially-missing key's value
- Distinguishing between keys of different types
- Deleting a key
- Popping a key
- Clearing out all key-value pairs
- Forming a copy
- Updating key-value pairs
- Iterating over keys, values, and items
- Using non-Python methods
- DefaultDict
- Counter
- OrderedDict
- NamedTuple
npm install pycollections
bower install pycollections
Coming soon!
- fromKeys(keys, opt_valueForAllKeys)
- clear()
- copy()
- set(key, value)
- update(iterable)
- hasKey(key)
- get(key, opt_defaultValue)
- del(key)
- pop(key, opt_defaultValue)
- iterkeys(cb)
- keys()
- popitem()
- length()
- iteritems(cb)
- items()
- itervalues(cb)
- values()
- getFirstKey()
- getFirstMatchingKey(predicate)
- isEmpty()
- setOneNewValue(key, fn)
- setSomeNewValues(keys, fn)
- setAllNewValues(fn)
py = {}
py = dict.fromkeys([1, 2, 3], {'the': 'value'})
py = dict(a=1, b=2)
py = dict([('a', 1), ('b', 2)])
py = dict(dict(a=1))
py = dict(123) # raises TypeError
var js = new Dict();
var js = Dict.fromKeys([1, 2, 3], {'the': 'value'});
var js = new Dict({'a': 1, 'b': 2});
var js = new Dict([['a', 1], ['b', 2]]);
var js = new Dict(new Dict({'a': 1}));
var js = new Dict(123); // throws Error
py = {}
987 in py # False
[] in py # raises TypeError due to unhashable key
var js = new Dict();
js.hasKey(987); // false
js.hasKey([]); // throws DictKeyNotHashable
py = {}
py['missing key'] # raises KeyError
py.get('missing key', 'default') # 'default'
py.get('missing key') # None
py[[]] # raises TypeError due to unhashable key
py.get([]) # raises TypeError due to unhashable key
var js = new Dict();
js.get('my key'); // throws DictKeyNotFound
js.get('missing key', 'default'); // 'default'
// Must explicitly pass null/undefined as the default;
// when passing only one .get() argument, the key is must exist,
// otherwise it thows an error.
js.get('missing key', null); // null
js.get([]); // throws DictKeyNotHashable
js.get([], null); // throws DictKeyNotHashable
Support for distinct Boolean, Number, String, NaN, null, and undefined keys.
py = {}
py[8] = 'num'
8 in py # True
'8' in py # False
len(py) # 1
py['8'] = 'str'
len(py) # 2
py[8] # 'num'
py['8'] # 'str'
var js = new Dict();
js.set(8, 'num');
js.hasKey(8); // true
js.hasKey('8'); // false
js.length(); // 1
js.set('8', 'str');
js.length(); // 2
js.get(8); // 'num'
js.get('8'); // 'str'
py = {'a': 1}
del py['a']
del py['b'] # raises KeyError
var js = new Dict({'a': 1});
js.del('a');
js.del('b'); // throws DictKeyNotFound
py = {'a': 1, 'b': 2}
py.pop('a') # 1
py.popitem() # ('b', 2)
py.pop(123) # raises KeyError
py.pop(123, 45) # 45
var js = new Dict({'a': 1, 'b': 2});
js.pop('a'); // 1
js.popitem(); // ['b', 2]
js.pop(123); // throws DictKeyNotFound
js.pop(123, 45); // 45
py = {'a': 1, 'b': 2}
len(py) # 2
py.clear()
len(py) # 0
py['a'] # raises KeyError
var js = new Dict({'a': 1, 'b': 2});
js.length(); // 2
js.clear();
js.length(); // 0
js.get('a'); // throws DictKeyNotFound
py1 = {'a': 1, 'b': 2}
py2 = py1.copy()
py1['a'] = 987
py2['a'] # 1
var js1 = new Dict({'a': 1, 'b': 2});
var js2 = js1.copy();
js1.set('a', 987);
js2.get('a'); // 1
py = {'a': 1, 'b': 2}
py.update({'a': 999})
py['a'] # 999
len(py) # 2
py.update([('b', 123)])
py['b'] # 123
var js = new Dict({'a': 1, 'b': 2});
js.update({'a': 999});
js.get('a'); // 999
js.length(); // 2
js.update([['b', 123]]);
js.get('b'); // 123
py = {'a': 1}
# does not put all keys into memory at once
for key in py:
print key # prints 'a'
py.keys() # ['a']
# does not put all values into memory at once
for value in py.itervalues():
print value # prints 1
py.values() # [1]
# does not put all items into memory at once
for item in py.iteritems():
print item # prints ('a', 1)
py.items() # [('a', 1)]
var js = new Dict({'a': 1});
// does not put all keys into memory at once
js.iterkeys(function(key) {
console.log(key); // logs 'a'
});
js.keys(); // ['a']
// does not put all values into memory at once
js.itervalues(function(value) {
console.log(value); // logs 1
});
js.values(); // [1]
// does not put all items into memory at once
js.iteritems(function(key, value) {
console.log(key, value); // logs 'a', 1
});
js.items(); // [['a', 1]]
var js = new Dict();
js.isEmpty(); // true
js.set('key', 99);
js.isEmpty(); // false
// executes without putting all keys into a new array in memory
js.getFirstKey(); // 1
// executes without putting all keys into a new array in memory
js.getFirstMatchingKey(function(k) {
return typeof(k) === 'number';
}); // throws DictKeyNotFound
js.getFirstMatchingKey(function(k) {
return typeof(k) === 'string';
}); // 'key'
// Since there is no direct access to stored elements,
// you can't do things like "py[123] += 1".
// This is especially true of keys whose values are primitive types:
// Boolean, String, or Number.
// Instead, you must do one of the following:
js.setOneNewValue('key', function(currentValue) {
return currentValue + 10;
});
js.get('key'); // 109
js.setSomeNewValues(['key'], function(currentValue) {
return currentValue + 10;
});
js.get('key'); // 119
js.setAllNewValues(function(currentValue) {
return currentValue + 10;
});
js.get('key'); // 129
js.set('array key', []);
js.get('array key').push(123);
js.get('array key'); // [123]
py = defaultdict(list)
py = defaultdict(int, a=1)
py = defaultdict(str, [('a', 1)])
py = defaultdict(int, dict(a=1))
py = defaultdict(123) # raises TypeError
var js = new DefaultDict([].constructor);
var js = new DefaultDict(function(){return 0}, {'a': 1});
var js = new DefaultDict(String, [['a', 1]]);
var js = new DefaultDict(function(){return 0}, new Dict({'a': 1}));
var js = new DefaultDict(123); // throws Error
py = defaultdict(int)
len(py) # 0
py[1] # 0
len(py) # 1
py.keys() # [1]
py.get(123) # None
len(py) # 1
py.keys() # [1]
var js = new DefaultDict(function(){return 0});
js.length(); // 0
js.get(1); // 0
js.length(); // 1
js.keys(); // [1]
js.get(123, null); // null
js.length(); // 1
js.keys(); // [1]
py = defaultdict(list)
for letter in 'abc':
py[letter].append(123)
py.items() # [('a', [123]), ('b', [123]), ('c', [123])]
var js = new DefaultDict([].constructor);
'abc'.split('').forEach(function(letter) {
js.get(letter).push(123);
});
js.items(); // [['a', [123]], ['b', [123]], ['c', [123]]]
py = defaultdict(dict)
py[123][0] = 9
nested_dict = py[123]
nested_dict[0] # 9
var js = new DefaultDict(function(){return new Dict()});
js.get(123).set(0, 9);
var nestedDict = js.get(123);
nestedDict.get(0); // 9
py = defaultdict(lambda: py)
py[1][1][2][3] = 123
len(py) # 3
py[1] is py # True
3 in py # True
2 in py # True
1 in py # True
py[3] # 123
var js = new DefaultDict(function(){return js});
js.get(1).get(1).get(2).set(3, 123);
js.length(); // 3
js.get(1) === js; // true
js.hasKey(3); // true
js.hasKey(2); // true
js.hasKey(1); // true
js.get(3); // 123
- fromKeys() is not implemented (throws an error)
- getIncrementor(incrementBy)
- iterelements(cb)
- elements()
- subtract(iterable)
- mostCommon(opt_n)
- leastCommon(opt_n)
py = Counter()
py = Counter(a=2, b=1)
py = Counter(['a', 'a', 'b'])
py = Counter(123) # raises TypeError
var js = new Counter();
var js = new Counter({'a': 2, 'b': 1});
var js = new Counter(['a', 'a', 'b']);
var js = new Counter(123); // throws Error
py = Counter()
py.update(['a', 'a', 32, 'false', False, float('inf')])
py.items() # [('a', 2), (32, 1), ('false', 1), (False, 1), (inf, 1)]
list(py.elements()) # ['a', 'a', 32, 'false', False, inf]
var js = new Counter();
js.update(['a', 'a', 32, 'false', false, NaN]);
js.items(); // [['a', 2], [32, 1], ['false', 1], [false, 1], [NaN, 1]]
js.elements(); // ['a', 'a', 32, 'false', false, NaN]
py = Counter(['a', 'b', 'b', 'c', 'c', 'c'])
py[99] # 0
py['a'] # 1
py['b'] # 2
py['c'] # 3
py.update(['a', 'a', 'b'])
py['a'] # 3
py['b'] # 3
py[99] # 0
py['c'] # 3
py.subtract({'a': 4})
py['a'] # -1
var js = new Counter(['a', 'b', 'b', 'c', 'c', 'c']);
js.get(99); // 0
js.get('a'); // 1
js.get('b'); // 2
js.get('c'); // 3
js.update(['a', 'a', 'b'])
js.get('a'); // 3
js.get('b'); // 3
js.get(99); // 0
js.get('c'); // 3
js.subtract({'a': 4})
js.get('a') // -1
py = Counter(['a', 'b', 'b', 'c', 'c', 'c'])
py.most_common(1) # [('c', 3)]
py.most_common(2) # [('c', 3), ('b', 2)]
py.most_common(99) # [('c', 3), ('b', 2), ('a', 1)]
py.most_common() # [('c', 3), ('b', 2), ('a', 1)]
var js = new Counter(['a', 'b', 'b', 'c', 'c', 'c']);
js.mostCommon(1); // [['c', 3]]
js.mostCommon(2); // [['c', 3], ['b', 2]]
js.mostCommon(99); // [['c', 3], ['b', 2], ['a', 1]]
js.mostCommon(); // [['c', 3], ['b', 2], ['a', 1]]
py = OrderedDict()
py = OrderedDict(a=1, b=2)
py = OrderedDict([('a', 1), ('b', 2)])
var js = new OrderedDict();
var js = new OrderedDict({a:1, b:2});
var js = new OrderedDict([['a', 1], ['b', 2]]);
py = OrderedDict()
py['1st'] = 0
py['2nd'] = 1
py['3rd'] = 2
py.keys() # ['1st', '2nd', '3rd']
py.values() # [0, 1, 2]
py.pop('1st') # 0
py.keys() # ['2nd', '3rd']
py.values() # [1, 2]
py['1st'] = 0
py.keys() # ['2nd', '3rd', '1st']
py.values() # [1, 2, 0]
var js = new OrderedDict();
js.set('1st', 0);
js.set('2nd', 1);
js.set('3rd', 2);
js.keys(); // ['1st', '2nd', '3rd']
js.values(); // [0, 1, 2]
js.pop('1st'); // 0
js.keys(); // ['2nd', '3rd']
js.values(); // [1, 2]
js.set('1st', 0);
js.keys(); // ['2nd', '3rd', '1st']
js.values(); // [1, 2, 0]
Py = namedtuple('Py', ['a', 'b'])
var Js = NamedTuple('Js', ['a', 'b']);
py = Py(1, 2) # Py(a=1, b=2)
py = Py(1) # throws TypeError
py = Py(1, 2, 3) # throws TypeError
var js = new Js(1, 2);
js.toString(); // "Js(a=1, b=2)"
var js = new Js(1); // throws Error
var js = new Js(1, 2, 3); // throws Error
py = Py(1, [2, 3])
py.a # 1
py.b # [2, 3]
py[0] # 1
py[1] # [2, 3]
py.a = 123 # raises AttributeError
py[1] = 99 # raises TypeError
var js = new Js(1, [2, 3]);
js.a; // 1
js.b; // [2, 3]
js[0]; // 1
js[1]; // [2, 3]
js.a = 99; // throws Error in strict mode, otherwise changes nothing
js[1] = 99; // changes nothing
py = Py(1, 2)
for value in py:
print value # prints 1 then 2
all(py) # True
var js = new Js(1, 2);
js.forEach(function(value, index) {
console.log(value, index); // logs [1, 0] then [2, 1]
});
js.every(function(v){return v > 0}); // true