Custom array function for MIN() in Google Sheets - javascript

Since the MIN() function in Sheets only returns a single value and there is no way to make it work with ARRAYFORMULA, I wanted to make a custom function that would take two arrays and compare the values at each entry, and return an array of the minimums. (I know there's a workaround that uses QUERY, but it wasn't going to work for my purposes)
What I have right now will take two arrays with one row and work perfectly. Unfortunately, it breaks when more than one row is introduced. I'm not sure why, so I'm lost on how to move forward. How can I make it work for any size arrays?
When I feed it any two dimensional range, it throws an error:
"TypeError: Cannot set property '0' of undefined"
on this line finalarray[x][y] = Math.min(arr1[x][y], arr2[x][y]);
The current ""working"" code:
function MINARRAY(arr1, arr2) {
if (arr1.length == arr2.length && arr1[0].length == arr2[0].length)
{
var finalarray = [[]];
for (x = 0; x < arr1.length; x++)
{
for(y = 0; y < arr1[x].length; y++)
{
finalarray[x][y] = Math.min(arr1[x][y], arr2[x][y]);
}
}
return finalarray;
}
else
{
throw new Error("These arrays are different sizes");
}
}

finalarray is a 2D array. [[]] sets only the first element of finalarray to a array. It is needed to set all elements of finalarray to a array. Inside the loop, add
finalarray[x]=[]//set `x`th element as a array or finalarray[x]= finalarray[x] || []
finalarray[x][y] = Math.min(arr1[x][y], arr2[x][y]);
Alternatively,
finalarray[x] = [Math.min(arr1[x][y], arr2[x][y])]

Related

Duplicate one array of objects in multiple arrays

I want to duplicate one array of objects into multiple ones, because every one of them I need to use in separate places.
if(!this.tempLookups){
for (let count = 0; count < this.dates.length; count++) {
this.tempLookups[count] = this.lookups[key];
}
}
Error: Uncaught (in promise) TypeError: Cannot set property '0' of null
The actual reason for the error is clearly in error message that you are trying to set the property for a null. So in order to fix simply define it after the if.
if(!this.tempLookups){
his.tempLookups = [];
for (let count = 0; count < this.dates.length; count++) {
this.tempLookups[count] = this.lookups[key];
}
}
You can do it in a single line without a for loop using Array#fill method since you are filling with the same value.
if(!this.tempLookups){
this.tempLookups = new Array(this.dates.length).fill(this.lookups[key]);
}
You can do this like this:
if (!this.tempLookups) {
this.tempLookups = [];
for (let i = 0; i < this.dates.length; i++) {
this.tempLookups.push(Array.from(this.lookups[key]));
}
}
Note that the this.tempLookups variable is initialized as empty array before we start inserting data. The Array.from call in for loop makes sure we actually create (shallow) copies of the this.lookups[key] array, instead of just assigning reference to the same this.lookups[key] array multiple times. Without Array.from changing one array would change all of them - because in reality there would be only one array referenced multiple times.

Unable to pass an array as argument of a javascript function

I'm trying to implement the quickSort algorithm in javascript, i have to extract 10,000 numbers from a txt file, pass them into an array, and pass this as an argument of my quickSort function, using the fs module of nodejs. The code is able to read the 10,000 numbers, and to convert them from an array of string to an array of number, but when i try to pass the array into my function, only 3472 numbers are passed, which i don't understand.
const fs = require('fs');
// Reading the data from the file containing the 10,000 numbers
const file = fs.readFileSync('./quickSort.txt', 'utf-8');
//Checking if it has read all the numbers correctly
console.log(file); // Displays the 10,000 numbers as strings in an array
// Convert them from string to integer
const finalFile = file.split('\n').map(e => {
return parseInt(e, 10);
})
// Checking if it has converted each element of the array to an integer
//console.log(finalFile) displays the array, with the 10,000 elements converted to integers
// Initialize a counter for the comparaisons made by the quickSort algorithm
let comparisons = 0;
// Sort them using quick sort
function quick_Sort(origArray) {
if (origArray.length <= 1) {
return origArray;
} else {
// Checking if the array has been correctly passed as an argument
console.log(origArray.length); //Displays 3742 instead of 10,000
var left = [];
var right = [];
var newArray = [];
var pivot = origArray.pop();
var length = origArray.length;
// I have tried comparisons += length - 1; too, but i doesn't work
comparisons += length;
for (var i = 0; i < length; i++) {
if (origArray[i] <= pivot) {
left.push(origArray[i]);
} else {
right.push(origArray[i]);
}
}
for (var i = 0; i < right.length; i++) {
comparisons++;
if (right[i] < pivot) {
return right.splice(i, 0, pivot);
}
}
return newArray.concat(quick_Sort(left), quick_Sort(right));
}
}
// Display the result
const result = quick_Sort(finalFile);
// expected output: 25
console.log(result);
Thank you very much.
Edit: In fact the problem of the size comes from the last for loop of the function, if i delete it, and insert the pivot between like that, it works (thanks to StardustGogeta) :
return newArray.concat(quick_Sort(left), pivot, quick_Sort(right));
This is a logical error. You need to change
return newArray.concat(quick_Sort(left), quick_Sort(right));
to
return newArray.concat(quick_Sort(left), pivot, quick_Sort(right));
With that change, the program works for me. The problem is that you are accidentally getting rid of (via .pop()) approximately 1/3 of your input values (the pivot values) during sorting.
try this:
const finalFile = file.split('\r?\n').map(.....)
Your parsing code works for me except for one issue: parseInt returns NaN for the last new line so you need to remove the last element from the array like this: finalFile.pop();. However this does not explain why you are seeing such a difference in the number of elements. There must be something different either in the code or the file you posted.

How to detect duplicate characters in an Array, and create an argument accordingly?

Good evening, I attempting to detect duplicate characters in a string. More specifically, I am trying to find up to two different duplicates within an Array. If there is one duplicate, add a sub-string, and if there is another duplicate, add a different sub-string. Is there any way to do this?
Here is some example code I have so far:
var CodeFieldArray = ["Z80.0", "Z80.1", "Z80.0", "Z70.4"];
/* We have an array here used to create the final string at the end of the
code. It is a dummy array with similar variables in my actual code. For
reference sake, there may be only one object in the array, or 7 total,
depending on the user's input, which is where the duplicate detection should
come in, in case the user enters in multiples of the same code. */
var i, Index;
for (i = 0, L = 0; i < CodeFieldArray.length; i++) {
Index = CodeFieldArray[i].indexOf(CodeFieldArray[i]);
if(Index > -1) L += 1;
Extra0 = CodeFieldArray.indexOf("Z80.8");
Extra1 = CodeFieldArray.indexOf("Z80.9");
if(L >= 2 && Extra0 == -1) CodeFieldArray.push("Z80.8");
Extra0 = CodeFieldArray.indexOf("Z80.8");
if(L >= 4 && Extra0 != -1 && Extra1 == -1) CodeFieldArray.push("Z80.9");
console.println(Extra0);
}
/*^ we attempted to create arguments where if there are duplicates
'detected', it will push, "Z80.8" or, "Z80.9" to the end of the Array. They
get added, but only when there are enough objects in the Array... it is not
actually detecting for duplicates within the Array itself^*/
function UniqueCode(value, index, self) {
return self.indexOf(value) === index;
}
CodeFieldArray = CodeFieldArray.filter(UniqueCode);
FamilyCodes.value = CodeFieldArray.join(", ");
/* this is where we turn the Array into a string, separated by commas. The expected output would be "Z80.0, Z80.1, Z70.4, Z80.8"*/
I have it to where it will add "Z80.8" or "z80.9" if they are not present, but they are being added, only if there are enough objects in the Array. My for-loop isn't detecting specifically the duplicates themselves. If there was a way to detect specifically the duplicates, and create an argument based off of that, then we would be doing grand. The expected output would be "Z80.0, Z80.1, Z70.4, Z80.8"
You can use Set and forEach and includes
var CodeFieldArray = ["Z80.0", "Z80.1", "Z80.0", "Z70.4"];
let unique = [...new Set(CodeFieldArray)];
let match = ['Z80.8','Z80.9'];
let numOfDup = CodeFieldArray.length - unique.length;
if(numOfDup){
match.forEach(e=>{
if(!unique.includes(e) && numOfDup){
unique.push(e);
numOfDup--;
}
})
}
console.log(unique.join(','))
So the idea is
Use Set to get unique values.
Now see the difference between length of original array and Set to get number of duplicates.
Now will loop through match array and each time we push item from match array into unique we reduce numOfDup by so ( to handle case where we have only one duplicate or no duplicate ).
In the end join by ,
You could do something like this:
var uniqueArray = function(arrArg) {
return arrArg.filter(function(elem, pos,arr) {
return arr.indexOf(elem) == pos;
});
};
uniqueArray ( CodeFieldArray )

How to remove an array element without splice and without undefined remaining?

Is it possible to remove an array element at a certain position, without rearranging indexes, and without that position changing to undefined?
I don't think that is possible with delete nor splice?
I need an accurate way to view the length of the array, without rearranging indexes.
I do not want to use splice because i have an object that has specific positions mapped to actual X,Y points of a tabel (Punkt).
UPDATE: actually, knowing if the array element exists out of ONLY undefined values might also help me, is there an easier way then looping through?
var keys = Object.keys(racks);
for (var i = 0; i < keys.length; i++)
{
for (var x = 0; x < racks[keys[i]].punkt.length; x++)
{
if(racks[keys[i]].punkt[x].y == fullName)
{
//delete racks[keys[i]].punkt[x];
racks[keys[i]].punkt.splice(x,1);
console.log(keys[i] + " : " + racks[keys[i]].punkt.length);
}
}
}
I don't think that is possible with delete nor splice?
I need an accurate way to view the length of the array, without rearranging indexes.
Then delete, a hasOwnProperty or in guard when retrieving from the array, and a loop counting the elements (or a separate variable keeping track) is the only way to do it. JavaScript's standard arrays are inherently sparse (because they're not really arrays at all), they can have gaps in them where they don't have entries. To create a gap, delete the array entry using delete.
Example:
// Setup
var a = ["a", "b", "c", "d"];
console.log(a.length); // 4
// Using delete
delete a[2]; // Delete the entry containing "c"
console.log(a.length); // Still 4
a.hasOwnProperty(2); // false
// Using the guard when getting an entry
if (a.hasOwnProperty(2)) { // Or `if (2 in a)`
// Get and use [2]
}
else {
// Do whatever it is you want to do when the array doesn't have the entry
}
// Finding out how many it *really* has:
var key;
var count = 0;
for (key in a) {
if (a.hasOwnProperty(key) && // See below
/^0$|^[1-9]\d*$/.test(key) &&
key <= 4294967294
) {
++count;
}
}
console.log(count); // 3
See this other answer for the details behind that if in the loop. If you never put non-element properties on the array, you can skip the second and third parts of that if.
This works perfectly.
var delrow = window.event.srcElement;
while ((delrow = delrow.parentElement) && delrow.tagName != "TR");
delrow.parentElement.removeChild(delrow);
var punten = racks[keys[i]].punkt.length;
if(racks[keys[i]].punkt[x].y == fullName)
{
delete racks[keys[i]].punkt[x];
punten--;
}
if(punten==0)
{
console.log("Could have removed device: " + keys[i]);
}

Infinite recursion in JavaScript quicksort?

Here is the quicksort code I wrote. The function doesn't work because it can't reach the base case. If I log the pivot, r and l to the console, they remain the same no matter how many times the sort function is called. So I wonder if the argument l, r are not really passed into the function as data. Why did it happen?
function sort(data){
if(data.length < 2){
return data;
}
else{
var l = [];
var r = [];
var pivot = parseInt(data.length/2);
for(i=0; i<data.length; i++){
if(data[i] > data[pivot]){
r.push(data[i]);
}
else{
l.push(data[i]);
}
}
return sort(l).concat(sort(r));
}
}
I think that the issue here is that your partitioning step does not necessarily shrink the input array. For example, let's trace what happens if you try sorting [1, 2]. In this case, your pivot element will be the element 2. Since 1 > 2 is false, 1 is added to the list l. Since 2 > 2 is false, 2 is added to the list l. As a result, your recursive call on the list l will have exactly the same arguments as your original call, causing infinite recursion.
To fix this, try splitting the input into three lists - one of smaller values, one of equal values, and one of greater values. This code is shown here:
function sort(data){
if (data.length < 2){
return data;
} else {
var l = [];
var r = [];
var e = [];
var i = 0;
var pivot = (data.length / 2) | 0;
for(i = 0; i < data.length; i++) {
if (data[i] > data[pivot]) {
r.push(data[i]);
} else if (data[i] < data[pivot]) {
l.push(data[i]);
} else {
e.push(data[i]);
}
}
return sort(l).concat(e, sort(r));
}
}
This new version explicitly groups the equal elements into their own list, so they aren't recursively sorted by either of the recursive calls. It also gracefully handles duplicate elements.
If you pick the largest value of the array as the pivot element, then all values of data will end up in the array l and none in r. Thus will make the recursion never stop (and keep l, r and pivot at the same values).
Unless this is a brain excercise, using data.sort() should do a better job. ;)
JavaScript passes objects by reference (arrays are objects too). If you want to pass them by value, you need to use the splice function as explained here.
Note that this will create a lot of copies of your data. You probably want to use the native sort() function.

Categories