JS data flattening

Recently, I found some excellent articles on data flattening, which I share with you here. I hope it will be helpful to you

What is flattening

The flattening of an array is to convert a nested multi-layer array (nesting can be any number of layers) into an array with only one layer.

For example, suppose a function named flatten can flatten the array, and the effect will be as follows:

var arr = [1, [2, [3, 4]]];
console.log(flatten(arr)) // [1, 2, 3, 4]

Circular array + recursion

Implementation idea: loop array. If there is an array in the data, recursively call the flatten flat function (using the for loop flat), connect with concat, and finally return result;

 function flatten(arr){
     var result = [];
     for(var i = 0, len = arr.length; i < len; i++){
         if(Array.isArray(arr[i])){
             result = result.concat(flatten(arr[i]));
         }else{
             result.push(arr[i]);
         }
     }
     return result;
 }

flatten(arr)   // [1,2,3,4]

recursion

The first thing we can think of is to loop array elements. If it is still an array, call the method recursively:

	var arr = [1, [2, [3, 4]]];
	
	function flatten(arr) {
	    var result = [];
	    for (var i = 0, len = arr.length; i < len; i++) {
	        if (Array.isArray(arr[i])) {
	            result = result.concat(flatten(arr[i]))
	        }
	        else {
	            result.push(arr[i])
	        }
	    }
	    return result;
	}


console.log(flatten(arr))

tostring

If the elements of the array are all numbers, we can consider using the toString method because:

[1, [2, [3, 4]]].toString() // "1,2,3,4"
Calling the toString method returns a comma separated flat string. At this time, we can split it and turn it into a number to realize flattening

	// Method 2
	var arr = [1, [2, [3, 4]]];
	
	function flatten(arr) {
	    return arr.toString().split(',').map(function(item){
	        return +item
	    })
	}
	
	console.log(flatten(arr))

However, the scenarios used in this method are very limited. If the array is [1, '1', 2, '2], this method will produce wrong results.

reduce

Since the array is processed and finally a value is returned, we can consider using reduce to simplify the code:

	// Method 3
	var arr = [1, [2, [3, 4]]];
	
	function flatten(arr) {
	    return arr.reduce(function(prev, next){
	        return prev.concat(Array.isArray(next) ? flatten(next) : next)
	    }, [])
	}
	
	console.log(flatten(arr))

ES6 adds an extension operator to take out all traversable attributes of the parameter object and copy them to the current object:

var arr = [1, [2, [3, 4]]];
console.log([].concat(...arr)); // [1, 2, [3, 4]]
We can only flatten one layer with this method, but we can write this method by thinking along this method:

	var arr = [1, [2, [3, 4]]];

		function flatten(arr) {
		
		    while (arr.some(item => Array.isArray(item))) {
		        arr = [].concat(...arr);
		    }
		
		    return arr;
		}
		
		console.log(flatten(arr))

undercore

So how to write an abstract flat function to facilitate our development? It's time for us to copy underscore again~

The source code and comments are given directly here, but it should be noted that the flatten function here is not the final Flatten, in order to facilitate multiple API calls, more flat configurations are made here.

	/**
	 * Flatten arrays 
	 * @param  {Array} input   Array to process
	 * @param  {boolean} shallow Is it only one layer flat
	 * @param  {boolean} strict  Whether to strictly handle elements is explained below
	 * @param  {Array} output  This is a parameter passed to facilitate recursion
	 */

Source address: https://github.com/jashkenas/underscore/blob/master/underscore.js#L528

	function flatten(input, shallow, strict, output) {

    // output is used when recursion is used
    output = output || [];
    var idx = output.length;

    for (var i = 0, len = input.length; i < len; i++) {

        var value = input[i];
        // If it is an array, it is processed
        if (Array.isArray(value)) {
            // If there is only one flat layer, traverse the array and fill in the output accordingly
            if (shallow) {
                var j = 0, len = value.length;
                while (j < len) output[idx++] = value[j++];
            }
            // If it is all flat, it recurses, passes in the processed output, and then processes the output in the recursion
            else {
                flatten(value, shallow, strict, output);
                idx = output.length;
            }
        }
        // Not an array. Judge whether to skip without processing or put it into output according to the value of strict
        else if (!strict){
            output[idx++] = value;
        }
    }

    return output;

}

Explain strict. In the code, we can see that when traversing an array element, if the element is not an array, we will judge the negative result of strict. If strict is set to true, we will skip without any processing, which means that non array elements can be filtered. For example:

var arr = [1, 2, [3, 4]];
console.log(flatten(arr, true, true)); // [3, 4]
So what's the use of setting strict? No hurry. Let's first look at the results corresponding to various values of shallow and strct:

shallow true + strict false: normal flat layer
shallow false + strict false: all layers are normally flat
Share true + strict true: remove non array elements
Share false + strict true: returns a []
Let's take a look at which methods in underscore call the basic function flatten:

_.flatten

The first is flatten:

	_.flatten = function(array, shallow) {
	    return flatten(array, shallow, false);
	};

In normal flat, we don't need to remove non array elements.

_.union

This function passes in multiple arrays, and then returns the union of the passed in arrays,

for instance:

_.union([1, 2, 3], [101, 2, 1, 10], [2, 1]);
=> [1, 2, 3, 101, 10]
If the parameter passed in is not an array, it will be skipped:

_.union([1, 2, 3], [101, 2, 1, 10], 4, 5);
=> [1, 2, 3, 101, 10]
In order to achieve this effect, we can flatten all the passed in arrays and then de duplicate them, because only arrays can be passed in. At this time, we can directly set strict to true to skip the passed in non array elements.

//For unique, see array de duplication of JavaScript topics https://github.com/mqyqingfeng/Blog/issues/27

function unique(array) {
   return Array.from(new Set(array));
}

_.union = function() {
    return unique(flatten(arguments, true, true));
}

_.difference

Does it feel useful to toss strict? Let's see another difference:

The syntax is:

_.difference(array, *others)

The effect is to take out the elements from the array and do not exist in multiple other arrays. Follow Like union, elements that are not arrays are excluded.

for instance:

_.difference([1, 2, 3, 4, 5], [5, 2, 10], [4], 3);
=> [1, 3]
The implementation method is also very simple. Flatten the array of others and filter out the values in the array that are not in the flattened array:

function difference(array, ...rest) {

    rest = flatten(rest, true, true);

    return array.filter(function(item){
        return rest.indexOf(item) === -1;
    })
}

If it helps you, you are welcome to pay attention. I will update the technical documents regularly to discuss, learn and make progress together.

 

 

Posted on Wed, 01 Dec 2021 09:30:46 -0500 by Fractal