Split a JS array into multiple arrays by a comparison function - javascript

Here's a pattern I've been reusing in my Javascript a few times, enough that I ended up writing this function for it, using Underscore's groupBy:
var sliceBy = function(array, comparison){
var sliceCounter = 0;
var slices = _(array).groupBy(function(b, i){
var a = array[i - 1];
return i === 0 ? 0 :
sliceCounter += comparison(a, b);
});
return _(slices).values();
};
It iterates through the array, comparing each b value to its previous a value, and determining whether to group b in an array with a or to start a new array. Since groupBy is expecting its iteratee to return a value corresponding to, well, how to group stuff by, I have to use sliceCounter to keep track of this.
Specifically, I'm using this algorithm for two main purposes: to group consecutive points in an array of not-necessarily-consecutive points (i.e b.x === a.x + 1), and to group which paths in an array of SVGs overlap each other.
What I'm worried about is that this feels like a common enough problem that I shouldn't be the first person to write an algorithm for it, yet I haven't been able to find any libraries that provide a function to split an array by a comparison function. My question is:
Is there a CS name for this type of operation, in the same pantheon as map, reduce, filter, sort, etc.?
Is there a more performant way of doing this? (Which is sort of related to question 1, because knowing the answer to that would make it easier to research)

Related

I need help understanding this algorithm solution of Top k frequent element

I am having trouble understanding the last line of this algorithm specifically the inside of the sort method, I don't understand why b[1] is being subtracted by a[1] I tried working through the return statement one step at a time: I know that at Object.entries(storage) I should have an array with 3 arrays inside like so [[1,4],[3,3],[2,1]] I then console logged b[1] to see what it would be and it gave me 4 from the first array and 3 from the second array then I console logged a[1] and the output was 3 from the second array and 2 from the third array so I'm really confused as to why they don't even start from the same index which would be [1]. If I'm not clear or if I'm missing any information let me know and I will try to update my question quickly
The testcase I'm using is nums=[1,1,1,1,3,3,3,2,2] and k=2
the original question in leetcode
var topKFrequent = (nums, k) => {
let storage = {}
for(let num of nums){
storage[num] = (storage[num] ?? 0) + 1
console.log(storage[num],'tset')
}
return Object.entries(storage).sort((a,b) => b[1] -a[1]).map(val=>Number(val[0])).slice(0,k);
}
I'm really confused as to why they don't even start from the same index which would be [1]
The sort method decides "on its own" which two values (two little subarrays in this case -- I will refer to them as "elements") from the input array it will pass as arguments to your callback function. We are not supposed to be concerned by that choice.
Realise that the internal sort algorithm will call your callback function when it wants to know how two elements from the given array compare.
It is normal that this internal sort algorithm will select a and b as two different elements, as it doesn't need to know how the first element compares with itself. It needs to know how two distinct elements compare and for that it will call your callback function. a and b could really be elements at any index in your array, and in subsequent calls of the callback those indices don't have to follow a certain order. It really could be any pair from the array depending on which algorithm the sort method uses. It could in theory even decide to start with the last two elements in your array...
Although we could dive into the source code of the JavaScript engine we are working with (like V8) to determine what a and b would be when the callback is called, it really shouldn't be of our concern. All we are responsible for is to have the callback answer the question how the given a and b compare.
We do this by:
returning 0 when we want a and b to be treated as equal in the sorting order,
returning a negative number when we want a to be ordered before b in the result (we define that a is "less than" b)
returning a positive number when we want a to be ordered after b in the result (we define that a is "greater than" b).
As long as the sorting algorithm doesn't have enough information to complete the sort, it will call the callback to get more info. All the rest of the sorting algorithm is taken care of by the sort method implementation.
Well, given [[1,4],[3,3],[2,1]], it first compares a = [3,3] with b = [1,4]. Your "same index" sounds like you expect it to compare [1,4] with itself? Anyway, b[1] - a[1] = 4-3 = 1 is greater than zero. The sorting algorithm really is ascending, so "greater" means "belongs on the right". So you're telling it that [3,3] belongs to the right of [1,4].
In the second comparison, you're telling it that [2,1] belongs to the right of [3,3].
That's all the sorter needs to know. The only possible order is [[1,4],[3,3],[2,1]].
After that, the map extracts the first number of each pair: [1,3,2]. And then the slice gives you the first k of those, e.g., [1,3] if k=2.
The part after => and before the ) is your compare function. In your case:
b[1] - a[1]
Subtracting your sort values (a,b) makes sort compare numbers instead of strings. The sort order (ascending/descending) depends on comparing b subtracted from a or a from b. Your function assumes there are no Infinity or NaN in the array.

Why does reduce return the number itself and, as in functional programming, replace if and break?

There is a simple function, its essence is to count from a number (n) to 0.
But when using reduce, the function just doesn't work, and no matter how I rewrite it, it returns either an empty array, or undefined, or the number itself 2.
First, I created an array that will take n, then I created a reduce method in which currentValue will take n and subtract 1 from it, after accumulator it takes the resulting number and using the push method, add it to the list array, but I don’t understand how I should add a condition that if accumulator is equal to 0, then the function must be stopped.
const countBits = (n) => {
let list = [n];
let resultReduce = n.reduce((accumulator, currentValue) => {
accumulator = currentValue - 1;
list.push(accumulator);
});
return resultReduce;
};
console.log(countBits([2]));
Why isn't this working the way I intended it to?
reduce will run on each of the items in the array, with the accumulator (first argument to the callback function) being the value that is returned from the callback function's previous iteration. So if you don't return anything, accumulator will be undefined for the next iteration.
If you want to count from n to 0, reduce is not the way to go (as well as the fact that in your current implementation, you don't even use list which would contain all of your stored numbers from n to 0). I would advise that instead, you simply loop from n to 0 and push those values into an array like so:
const countBits = (n) => {
let list = [];
for (let i = n; i > -1; i--) {
list.push(i);
}
return list;
};
console.log(countBits(2));
Also note I've changed your syntax slightly in the function call - you were passing an array with a single element seemingly unnecessarily, so I just passed the element itself to simplify the code.
The answer by Jack Bashford is correct, but for completeness I would like to point out that generating a range of numbers is a common need. Libraries like Underscore, Lodash and Ramda provide a ready-to-use function for this purpose. You don’t have to write your own implementation every time you need something common and mundane like that; save the time and enjoy the fact that you can spend your time on something more groundbreaking instead.
console.log(_.range(2, -1, -1));
<script src="https://underscorejs.org/underscore-umd-min.js"></script>
Also for the sake of completeness, let’s consider how you might implement a downwards range function using reduce, anyway. reduce expects an input array, though it can also accept an object if using Underscore or Lodash. To make meaningful use of the input collection, we could generate a consecutive number for every element of the collection. For an array, we could of course just do _.range(collection.length - 1, -1, -1) instead, but for an object, or something that you don’t know the length of in advance, such as a generator, using reduce for this purpose might make sense. The mapDownwardsRange function below will do this:
function unshiftNext(array) {
const front = array.length ? array[0] : -1;
return [front + 1].concat(array);
}
function mapDownwardsRange(collection) {
return _.reduce(collection, unshiftNext, []);
}
console.log(mapDownwardsRange(['a', 'b', 'c']));
<script src="https://underscorejs.org/underscore-umd-min.js"></script>

Is there a way to return the rest of a JavaScript array

Is there a way to return the rest of an array in JavaScript i.e the portion of the array that consists of all elements but the first element of the array?
Note: I do not ask for returning a new array e.g. with arr.slice(1) etc. and I do not want to chop off the first element of the array e.g. with arr.shift().
For example, given the array [3, 5, 8] the rest of the array is [5, 8] and if the rest of the array is changed, e.g. by an assignment (a destructive operation), the array also changes. I just figured out that as a test that proves the rest is the rest of the array but not a new array consists of the rest of the elements of the array.
Note: The following code example is to describe what I want, but not specifically what I want to do (i.e. not the operations I want to perform). What I want to do is in the every algorithm at the bottom.
var arr = [3, 5, 8];
var rest = rest(arr); // rest is [5, 8]
rest.push(13); // rest is [5, 8, 13] and hence the arr is [3, 5, 8, 13]
An example I possibly need this and I would want to have it is following algorithm and many other I am writing in that GitHub organization, in both of which I use always arr.slice(1):
function every(lst, f) {
if (lst.length === 0) {
return false;
} else {
if (f(lst[0]) === true) {
return every(lst.slice(1), f);
} else {
return false;
}
}
}
I think having what I ask for instead of arr.slice(1) would keep the memory usage of such algorithms and retain the recursive-functional style I want to employ.
No, this is generally not possible. There are no "views on" or "pointers to" normal arrays1.
You might use a Proxy to fake it, but I doubt this is a good idea.
1: It's trivial to do this on typed arrays (which are views on a backing buffer), but notice that you cannot push to them.
I possibly need this and I would want to have it for recursive-functional style algorithms where I currently use arr.slice(1) but would prefer to keep memory usage low
Actually, all of these implementations do have low memory usage - they don't allocate more memory than the input. Repeatedly calling slice(1) does lead to high pressure on the garbage collector, though.
If you were looking for better efficiency, I would recommend to
avoid recursion. JS engines still didn't implement tail recursion, so recursion isn't cheap.
not to pass around (new copies of) arrays. Simply pass around an index at which to start, e.g. by using an inner recursive function that closes over the array parameter and accesses array[i] instead of array[0]. See #Pointy's updated answer for an example.
If you were looking for a more functional style, I would recommend to use folds. (Also known as reduce in JavaScript, although you might need to roll your own if you want laziness). Implement your algorithms in terms of fold, then it's easy to swap out the fold implementation for a more efficient (e.g. iterative) one.
Last but not least, for higher efficiency while keeping a recursive style you can use iterators. Their interface might not look especially functional, but if you insist you could easily create an immutable wrapper that lazily produces a linked list.
please test this function
function rest(arr) {
var a = arr.slice(1);
a.push = function() {
for (var i = 0, l = arguments.length; i < l; i++) {
this[this.length] = arguments[i];
arr[this.length] = arguments[i];
}
return this.length;
};
return a;
}
Based on the code posted in the update to the question, it's clear why you might want to be able to "alias" a portion of an array. Here is an alternative that is more typical of how I would solve the (correctly) perceived efficiency problem with your implementation:
function every(lst, f) {
function r(index) {
if (index >= lst.length)
return true; // different from OP, but I think correct
return f(lst[index]) && r(index+1);
}
return r(0);
}
That is still a recursive solution to the problem, but no array copy is made; the array is not changed at all. The general pattern is common even in more characteristically functional programming languages (Erlang comes to mind personally): the "public" API for some recursive code is augmented by an "internal" or "private" API that provides some extra tools for keeping track of the progress of the recursion.
original answer
You're looking for Array.prototype.shift.
var arr = [1, 2, 3];
var first = arr.shift();
console.log(first); // 1
console.log(arr); // [2, 3]
This is a linear time operation: the execution cost is relative to the length of the original array. For most small arrays that does not really matter much, but if you're doing lots of such work on large arrays you may want to explore a better data structure.
Note that with ordinary arrays it is not possible to create a new "shadow" array that overlaps another array. You can do something like that with typed arrays, but for general purpose use in most code typed arrays are somewhat awkward.
The first limitation of typed arrays is that they are, of course, typed, which means that the array "view" onto the backing storage buffer gives you values of only one consistent type. The second limitation is that the only available types are numeric types: integers and floating-point numbers of various "physical" (storage) sizes. The third limitation is that the size of a typed array is fixed; you can't extend the array without creating a new backing buffer and copying.
Such limitations would be quite familiar to a FORTRAN programmer of course.
So to create an array for holding 5 32-bit integers, you'd write
var ints = new Int32Array(5);
You can put values into the array just like you put values into an ordinary array, so long as you get the type right (well close enough):
for (let i = 0; i < 5; i++)
ints[i] = i;
console.log(ints); // [0, 1, 2, 3, 4]
Now: to do what the OP asked, you'd grab the buffer from the array we just created, and then make a new typed array on top of the same buffer at an offset from the start. The offsets when doing this are always in bytes, regardless of the type used to create the original array. That's super useful for things like looking at the individual parts of a floating point value, and other "bit-banging" sorts of jobs, though of course that doesn't come up much in normal JavaScript coding. Anyway, to get something like the rest array from the original question:
var rest = new Int32Array(ints.buffer, 4);
In that statement, the "4" means that the new array will be a view into the buffer starting 4 bytes from the beginning; 32-bit integers being 4 bytes long, that means that the new view will skip the first element of the original array.
Since JavaScript can't do this, the only real solution to your problem is WebAssembly. Otherwise use Proxy.

Iterate through array, add the sum as an object into that Array (lodash)

I'm trying to understand how to iterate through this simple array.
var data = [{"blahh":"hi","blah2":"333","third":"920","fourth":"800"}];
What I am trying to accomplish is, appending an object that is the sum of certain keys (the 'third' and 'fourth')...resulting data being this:
var data = [{"blahh":"hi","blah2":"333","third":"920","fourth":"800", "sum": "1720"}];
I imagine 'lodash' library is capable of neatly accomplishing this. I just can't figure out how to pull that off. I know this is a newbie question but perhaps answering it may helps some soul better understand lodash.
As mentioned - you don't need lodash for this. You're looking at the map function to iterate over an array and return a new array (in this case an array of objects returning a new array with objects that have a sum property) and reduce to iterate over all desired keys and sum their values. For example:
//If you want to treat numbers as numbers then use numbers.
//Some answers here are coercing strings with + - however this will result in NaN on non coerceable strings -ie 'hello',
//which will cause unexpected errors for you later (and typeof NaN is number)
var data = [{"blahh":"hi","blah2":333,"third":920,"fourth":800}];
function returnNewArrayWithSums(keys, array) {
return array.map(obj => {
obj.sum = keys.reduce((sum, key) => typeof obj[key] === 'number' ? obj[key] + sum : sum, 0);
return obj;
});
}
returnNewArrayWithSums(['third', 'fourth'], data);
edited to add - this answer is to give you a use case w/ map/reduce - ideally you wouldn't have to specify numeric keys and make this function more general for any arrays that have objects with k/v pairs that are nums that you want to sum
No need for a library to do this ... the code will likely be more complicated looking than using native methods
data.forEach(item){
item.sum = (+item.third || 0) + (+item.fourth || 0);
});
You have an array of one object - which I dont think you are trying to do. Arrays do not have a key:value pair in them (well actually they do in JavaScript but ignore that). They are an indexed list of values. You probably want to do something like this:
// notice im not putting the integer values in quotes
var data = {'blah':'hi', 'blah2':333, 'third':920, 'fourth':800 };
data.sum = data.third + data.fourth;
That will add a 'sum' field to your object with the value of the sum of third and fourth. Is this what you are trying to do?

Is it possible to implement a js version of Haskell's unzip in a purely functional fashion?

I'm implementing a javascript ray-casting point-in-polygon algorithm in a purely functional fashion (no particular reason behind it).
I got stuck as i needed to get two arrays from a 2-dimentional array (replicating a list of tuples); something akin to Haskell's unzip.
Is it possible, starting from something like [[a,b],[c,d],[e,f]] to obtain [[a,c,e],[b,d,f]] without using procedural-style iterators?
(I know it's a trivial question, and I could just implement the function procedurally and then forget about it, but I was curious to know if there was a solution)
EDIT: To clarify, I know how to implement zip and unzip: I was wondering wether it might be possible to implement them without for loops and variable reassignments.
Your unzip is just a zip but with multiple arguments. The only reason most people don't just use the same function is that most of the time zip receives a variadic list of arguments instead of a array so you need to unpack things with apply in the unzip function.
In Dojo, the library I am using, they implement zip and unzip as
unzip: function(/*Array*/ a){
// summary: similar to dojox.lang.functional.zip(), but takes
// a single array of arrays as the input.
// description: This function is similar to dojox.lang.functional.zip()
// and can be used to unzip objects packed by
// dojox.lang.functional.zip(). It is here mostly to provide
// a short-cut for the different method signature.
return df.zip.apply(null, a);
}
zip: function(){
// summary: returns an array of arrays, where the i-th array
// contains the i-th element from each of the argument arrays.
// description: This is the venerable zip combiner (for example,
// see Python documentation for general details). The returned
// array is truncated to match the length of the shortest input
// array.
var n = arguments[0].length,
m = arguments.length,
i = 1,
t = new Array(n),
j,
p;
for(; i < m; n = Math.min(n, arguments[i++].length));
for(i = 0; i < n; ++i){
p = new Array(m);
for(j = 0; j < m; p[j] = arguments[j][i], ++j);
t[i] = p;
}
return t;
},
Note that zip receives multiple arguments so it is more like the Python zip and less like the Haskell one.
It should not be hard to conver this code to a "purely functional" style without variable assignments. Your existing code should already be handling the job of the first two fors in the example I posted (truncating the zip at the minimum length and iterating through the indices of one of the lists). All that is left is doing a similar thing for the third for - collecting the i-th value from a list of lists instead of collecting two values from two lists.

Categories

Resources