mirror of
				https://github.com/flynx/types.js.git
				synced 2025-10-31 11:30:10 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			323 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			323 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**********************************************************************
 | |
| * 
 | |
| *
 | |
| *
 | |
| **********************************************/  /* c8 ignore next 2 */
 | |
| ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
 | |
| (function(require){ var module={} // make module AMD/node compatible...
 | |
| /*********************************************************************/
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /*********************************************************************/
 | |
| 
 | |
| // Array.prototype.flat polyfill...
 | |
| //
 | |
| // NOTE: .flat(..) is not yet supported in IE/Edge...
 | |
| Array.prototype.flat
 | |
| 	|| (Array.prototype.flat = function(depth){
 | |
| 		depth = typeof(depth) == typeof(123) ? depth : 1
 | |
| 		return this.reduce(function(res, e){ 
 | |
| 			return res.concat(e instanceof Array && depth > 0 ? 
 | |
| 				e.flat(depth-1) 
 | |
| 				: [e]) }, []) })
 | |
| 
 | |
| 
 | |
| // Array.prototype.includes polyfill...
 | |
| //
 | |
| Array.prototype.includes
 | |
| 	|| (Array.prototype.includes = function(value){
 | |
| 		return this.indexOf(value) >= 0 }) 
 | |
| 
 | |
| 
 | |
| // first/last element access short-hands...
 | |
| //
 | |
| //	.first()
 | |
| //	.last()
 | |
| //		-> elem
 | |
| //
 | |
| //	.first(value)
 | |
| //	.last(value)
 | |
| //		-> array
 | |
| //
 | |
| // NOTE: setting a value will overwrite an existing first/last value.
 | |
| // NOTE: for an empty array both .first(..)/.last(..) will return undefined 
 | |
| // 		when getting a value and set the 0'th value when setting...
 | |
| Array.prototype.first
 | |
| 	|| (Array.prototype.first = function(value){
 | |
| 		return arguments.length > 0 ?
 | |
| 			((this[0] = value), this)
 | |
| 			: this[0]})
 | |
| Array.prototype.last
 | |
| 	|| (Array.prototype.last = function(value){
 | |
| 		return arguments.length > 0 ?
 | |
| 			((this[this.length - 1 || 0] = value), this)
 | |
| 			: this[this.length - 1]})
 | |
| 
 | |
| 
 | |
| /*/ XXX not yet sure should these be funcs or props...
 | |
| 'first' in Array.prototype
 | |
| 	|| Object.defineProperty(Array.prototype, 'first', {
 | |
| 		enumerable: false,
 | |
| 		get : function () {
 | |
| 			return this[0] },
 | |
| 		set : function(value){
 | |
| 			this[0] = value 
 | |
| 			return this }, })
 | |
| 
 | |
| 'last' in Array.prototype
 | |
| 	|| Object.defineProperty(Array.prototype, 'last', {
 | |
| 		enumerable: false,
 | |
| 		get : function () {
 | |
| 			return this[this.length - 1] },
 | |
| 		set : function(value){
 | |
| 			this[this.length - 1 || 0] = value 
 | |
| 			return this }, })
 | |
| //*/
 | |
| 
 | |
| 
 | |
| // Compact a sparse array...
 | |
| //
 | |
| // NOTE: this will not compact in-place.
 | |
| Array.prototype.compact = function(){
 | |
| 	return this.filter(function(){ return true }) }
 | |
| 
 | |
| 
 | |
| // like .length but for sparse arrays will return the element count...
 | |
| 'len' in Array.prototype
 | |
| 	|| Object.defineProperty(Array.prototype, 'len', {
 | |
| 		get : function () {
 | |
| 			return Object.keys(this).length
 | |
| 		},
 | |
| 		set : function(val){},
 | |
| 	})
 | |
| 
 | |
| 
 | |
| // Return an array with duplicate elements removed...
 | |
| //
 | |
| // NOTE: order is preserved... 
 | |
| Array.prototype.unique = function(normalize){
 | |
| 	return normalize ? 
 | |
| 		[...new Map(this.map(function(e){ return [normalize(e), e] })).values()]
 | |
| 		: [...new Set(this)] }
 | |
| Array.prototype.tailUnique = function(normalize){
 | |
| 	return this
 | |
| 		.slice()
 | |
| 		.reverse()
 | |
| 		.unique(normalize)
 | |
| 		.reverse() }
 | |
| 
 | |
| 
 | |
| // Compare two arrays...
 | |
| //
 | |
| // XXX unify this with ./Object.js (.match(..)) and diff.js
 | |
| Array.prototype.cmp = function(other){
 | |
| 	if(this === other){
 | |
| 		return true }
 | |
| 	if(this.length != other.length){
 | |
| 		return false }
 | |
| 	for(var i=0; i<this.length; i++){
 | |
| 		if(this[i] != other[i]){
 | |
| 			return false } }
 | |
| 	return true }
 | |
| 
 | |
| 
 | |
| // Compare two Arrays as sets...
 | |
| //
 | |
| // NOTE: this will ignore order and repeating elments...
 | |
| Array.prototype.setCmp = function(other){
 | |
| 	return this === other 
 | |
| 		|| (new Set([...this, ...other])).length == (new Set(this)).length }
 | |
| 
 | |
| 
 | |
| // Sort as the other array...
 | |
| //
 | |
| // This will sort the intersecting items in the head keeping the rest 
 | |
| // of the items in the same relative order...
 | |
| //
 | |
| // NOTE: if an item is in the array multiple times only the first index 
 | |
| // 		is used...
 | |
| Array.prototype.sortAs = function(other){
 | |
| 	// NOTE: the memory overhead here is better than the time overhead 
 | |
| 	// 		when using .indexOf(..)...
 | |
| 	other = other.toMap()
 | |
| 	var orig = this.toMap()
 | |
| 	return this.sort(function(a, b){
 | |
| 		var i = other.get(a)
 | |
| 		var j = other.get(b)
 | |
| 		return i == null && j == null ?
 | |
| 				orig.get(a) - orig.get(b)
 | |
| 			: i == null ? 
 | |
| 				1
 | |
| 			: j == null ? 
 | |
| 				-1
 | |
| 			: i - j }) }
 | |
| 
 | |
| 
 | |
| // Same as .sortAs(..) but will not change indexes of items not in other...
 | |
| //
 | |
| // XXX not sure if this is the best way to do this...
 | |
| Array.prototype.inplaceSortAs = function(other){
 | |
| 	// sort only the intersection...
 | |
| 	var sorted = this
 | |
| 		.filter(function(e){ 
 | |
| 			return other.includes(e) })
 | |
| 		.sortAs(other)
 | |
| 	// zip the sorted items back into this...
 | |
| 	this.forEach(function(e, i, l){
 | |
| 		other.includes(e) 
 | |
| 			&& (l[i] = sorted.shift()) })
 | |
| 	return this }
 | |
| 
 | |
| 
 | |
| // Equivalent to .map(..) / .filter(..) / .reduce(..) that process the 
 | |
| // contents in chunks asynchronously...
 | |
| //
 | |
| //	.mapChunks(func)
 | |
| //	.mapChunks(chunk_size, func)
 | |
| //	.mapChunks([item_handler, chunk_handler])
 | |
| //	.mapChunks(chunk_size, [item_handler, chunk_handler])
 | |
| //		-> promise(list)
 | |
| //	
 | |
| //	.filterChunks(func)
 | |
| //	.filterChunks(chunk_size, func)
 | |
| //	.filterChunks([item_handler, chunk_handler])
 | |
| //	.filterChunks(chunk_size, [item_handler, chunk_handler])
 | |
| //		-> promise(list)
 | |
| //	
 | |
| //	.reduceChunks(func, res)
 | |
| //	.reduceChunks(chunk_size, func, res)
 | |
| //	.reduceChunks([item_handler, chunk_handler], res)
 | |
| //	.reduceChunks(chunk_size, [item_handler, chunk_handler], res)
 | |
| //		-> promise(res)
 | |
| //
 | |
| //
 | |
| //	chunk_handler(chunk, result, offset)
 | |
| //
 | |
| //
 | |
| // chunk_size can be:
 | |
| // 	20			- chunk size
 | |
| // 	'20'		- chunk size
 | |
| // 	'20C'		- number of chunks
 | |
| //	
 | |
| //
 | |
| // The main goal of this is to not block the runtime while processing a 
 | |
| // very long array by interrupting the processing with a timeout...
 | |
| //
 | |
| var makeChunkIter = function(iter, wrapper){
 | |
| 	wrapper = wrapper
 | |
| 		|| function(res, func, array, e){
 | |
| 			return func.call(this, e[1], e[0], array) }
 | |
| 	return function(size, func, ...rest){
 | |
| 		var that = this
 | |
| 		var args = [...arguments]
 | |
| 		size = (args[0] instanceof Function 
 | |
| 				|| args[0] instanceof Array) ? 
 | |
| 			(this.CHUNK_SIZE || 50)
 | |
| 			: args.shift()
 | |
| 		size = typeof(size) == typeof('str') ?
 | |
| 				// number of chunks...
 | |
| 				(size.trim().endsWith('c') || size.trim().endsWith('C') ?
 | |
| 				 	Math.round(this.length / (parseInt(size) || 1)) || 1
 | |
| 				: parseInt(size))
 | |
| 			: size
 | |
| 		var postChunk
 | |
| 		func = args.shift()
 | |
| 		;[func, postChunk] = func instanceof Array ? func : [func]
 | |
| 		rest = args
 | |
| 		var res = []
 | |
| 		var _wrapper = wrapper.bind(this, res, func, this)
 | |
| 
 | |
| 		return new Promise(function(resolve, reject){
 | |
| 				var next = function(chunks){
 | |
| 					setTimeout(function(){
 | |
| 						var chunk, val
 | |
| 						res.push(
 | |
| 							val = (chunk = chunks.shift())[iter](_wrapper, ...rest))
 | |
| 						postChunk
 | |
| 							&& postChunk.call(that, 
 | |
| 								chunk.map(function([i, v]){ return v }), 
 | |
| 								val,
 | |
| 								chunk[0][0])
 | |
| 						// stop condition...
 | |
| 						chunks.length == 0 ?
 | |
| 							resolve(res.flat(2))
 | |
| 							: next(chunks) }, 0) }
 | |
| 				next(that
 | |
| 					// split the array into chunks...
 | |
| 					.reduce(function(res, e, i){
 | |
| 						var c = res.slice(-1)[0]
 | |
| 						c.length >= size ?
 | |
| 							// initial element in chunk...
 | |
| 							res.push([[i, e]])
 | |
| 							// rest...
 | |
| 							: c.push([i, e])
 | |
| 						return res }, [[]])) }) } }
 | |
| 
 | |
| Array.prototype.CHUNK_SIZE = 50 
 | |
| Array.prototype.mapChunks = makeChunkIter('map')
 | |
| Array.prototype.filterChunks = makeChunkIter('map', 
 | |
| 	function(res, func, array, e){
 | |
| 		return !!func.call(this, e[1], e[0], array) ? [e[1]] : [] })
 | |
| Array.prototype.reduceChunks = makeChunkIter('reduce',
 | |
| 	function(total, func, array, res, e){
 | |
| 		return func.call(this, 
 | |
| 			total.length > 0 ? 
 | |
| 				total.pop() 
 | |
| 				: res, 
 | |
| 			e[1], e[0], array) })
 | |
| 
 | |
| 
 | |
| // Convert an array to object...
 | |
| //
 | |
| // Format:
 | |
| // 	{
 | |
| // 		<item>: <index>,
 | |
| // 		...
 | |
| // 	}
 | |
| //
 | |
| // NOTE: items should be strings, other types will get converted to 
 | |
| // 		strings and thus may mess things up.
 | |
| // NOTE: this will forget repeating items...
 | |
| // NOTE: normalize will slow things down...
 | |
| Array.prototype.toKeys = function(normalize){
 | |
| 	return normalize ? 
 | |
| 		this.reduce(function(r, e, i){
 | |
| 			r[normalize(e)] = i
 | |
| 			return r }, {})
 | |
| 		: this.reduce(function(r, e, i){
 | |
| 			r[e] = i
 | |
| 			return r }, {}) }
 | |
| 
 | |
| 
 | |
| // Convert an array to a map...
 | |
| //
 | |
| // This is similar to Array.prototype.toKeys(..) but does not restrict 
 | |
| // value type to string.
 | |
| //
 | |
| // Format:
 | |
| // 	Map([
 | |
| // 		[<item>, <index>],
 | |
| // 		...
 | |
| // 	])
 | |
| //
 | |
| // NOTE: this will forget repeating items...
 | |
| // NOTE: normalize will slow things down...
 | |
| Array.prototype.toMap = function(normalize){
 | |
| 	return normalize ? 
 | |
| 		this
 | |
| 			.reduce(function(m, e, i){
 | |
| 				m.set(normalize(e), i)
 | |
| 				return m }, new Map())
 | |
| 		: this
 | |
| 			.reduce(function(m, e, i){
 | |
| 				m.set(e, i)
 | |
| 				return m }, new Map()) }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /**********************************************************************
 | |
| * vim:set ts=4 sw=4 :                               */ return module })
 |