I'm learning Javascript, so pardon any mistakes in how I phrase the question.
I am writing a chess program to practice and learn. Currently, I am trying to write a function to find the color of a piece with the position as the parameter. The relevant pieces of code are as follows. The first two work as they were designed to, but the last does not.
let allPieces = board.getElementsByClassName('piece');
This sets allPieces as an object with the key values the html elemnts representing each piece, both black and white.
const getPiecePosition = function(element) {
let position = window.getComputedStyle(element).getPropertyValue('grid-row-start');
let letterIndex = alphabet.findIndex(function(letter) {
return letter === position[0];
});
letterIndex += 1;
return [letterIndex, Number(position[1])];
}
This takes a parameter in the form of the allPieces object with a specific key and returns the position as an array with the column number first and the row number second. ex. [2,3].
const getPieceByPosition = function(position) {
let pce = Object.keys(allPieces).forEach(function(piece) {
if (getPiecePosition(allPieces[piece]) == position) {
return allPieces[piece].borderColor;
}
})
return pce;
}
This is the function I am having trouble with. The idea behind it is that it will take each key in the allPieces object and loop through them using forEach() into the getPiecePosition() function to compare it with the position entered as the parameter. Since only one piece can inhabit any tile at once, it should never return multiple values.
I honestly don't know where to start debugging this code, but I have been trying for about an hour. It always just returns undefined instead of a truthy value of any kind.
Your last function has a few issues:
getPiecePosition(allPieces[piece]) == position
Assuming position is an array, you're trying to compare an array with an array here using ==. However, since the two arrays are different references in memory, this will always give false, even if they contain the same elements:
console.log([2, 3] == [2, 3]); // false
You're trying to return from the callback of .forEach(). This won't achieve what you want, as return will jump out of the .forEach callback function, not your outer getPieceByPosition() function. This leads me to your final issue:
The .forEach() method doesn't return anything. That is, it doesn't evaluate to a value once it is called. This means that let pce will always be undefined since you're trying to set it to the return value of .forEach(). This, in contrast to let letterIndex, is different, as letterIndex is set to the return value of .findIndex(), which does have a return value and is determined by the function you pass it.
One additional thing you can fix up is the use of Object.keys(allPieces). While this works, it's not the best approach for looping over your elements. Ideally, you would be able to do allPieces.forEach() to loop over all your elements. However, since allPieces is a HTMLCollection, you won't be able to do that. Instead, you can use a regular for loop or a for..of loop to loop over the values in your HTMLCollection.
Alternatively, there is a way to make allPieces.forEach() work.
Instead of using board.getElementsByClassName('piece');, you can use the method .querySelectorAll('.piece'), which will give you a NodeList. Unlike a HTMLCollection, a NodeList allows you to use .forEach() on it to loop through its elements.
The return type of getElementsByClassName HTMLCollection Object. You should't use Object.keys to loop through each of 'piece' element. Insted, use the follow.
for(var i = 0 ; i < allPieces.length ; i++){
var piece = allPieces[i];
... // and, do whatever with the getPiecePosition(piece)
}
Related
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>
I have a grid of values, represented by a two-dimensional array.
Here is a literal representation of the data: dataValues[row][column]
So dataValues[3][0] would be the fourth row, first column.
Well, I need to search the first column of every row for a value and I'm looking for the least computational intensive way of doing that.
I know I can do it with a loop:
for (var i in dataValues) {
if (dataValues[i][0] == "Totals") {
matchingRow = i;
break;
}
}
But I like to avoid loops wherever possible and I can't think of a way to apply Array.prototype.indexOf in a useful way here.
Is there even a computational difference between a loop and indexOf? It seems to me that indexOf would probably just run its own loop.
You can use Array#find() which will break once condition is met and return first matching element or return undefined if condition is not met
let matchingItem = dataValues.find(arr=> arr[0] == "Totals");
if(matchingItem ){
// do what you want with matching elemnt of dataValues
}
I know that map returns a new array, and that forEach does not return anything (the docs say it returns undefined).
For example, if I had some code like this:
let test;
values.forEach((value, idx) => {
if (someNumber >= value) {
test = value;
}
});
Here I am just checking if someNumber is greater than some value, and if it is then set test = value. Is there another array method I should use here?
Or is it fine to use .forEach
Your example doesn't make sense because it finds the last value that is less than or equal to someNumber, repeatedly assigning to the test variable if more than one is found. Thus, your code is not truly expressing your intent well since other developers can be confused about what you're trying to achieve. In fact, other answers here have had differing opinions on your goal due to this ambiguity. You even said:
if the number is great than or equal to the value from the array at whatever index, stop, and set test equal to that value
But your code doesn't stop at the first value! It keeps going through the entire array and the result in test will be the last value, not the first one.
In general, making your loop refer to outside variables is not the best way to express your intent. It makes it harder for the reader to understand what you're doing. It's better if the function you use returns a value so that it's clear the variable is being assigned.
Here's a guide for you:
forEach
Use this when you want to iterate over all the values in order to do something with each of them. Don't use this if you are creating a new output value--but do use it if you need to modify existing items or run a method on each one, where the forEach has no logical output value. array.forEach at MDN says:
There is no way to stop or break a forEach() loop other than by throwing an exception. If you need such behavior, the forEach() method is the wrong tool, use a plain loop instead. If you are testing the array elements for a predicate and need a Boolean return value, you can use every() or some() instead. If available, the new methods find() or findIndex() can be used for early termination upon true predicates as well.
find
Use this when you want to find the first instance of something, and stop. What you said makes it sound like you want this:
let testResult = values.find(value => value <= someNumber);
This is far superior to setting the test value from inside the lambda or a loop. I also think that reversing the inequality and the variables is better because of the way we tend to think about lambdas.
some
These only give you a Boolean as a result, so you have to misuse them slightly to get an output value. It will traverse the array until the condition is true or the traversal is complete, but you have to do something a bit hacky to get any array value out. Instead, use find as above, which is intended to output the found value instead of simply a true/false whether the condition is met by any element in the array.
every
This is similar to some in that it returns a Boolean, but is what you would expect, it is only true if all the items in the array meet the condition. It will traverse the array until the condition is false or the traversal is complete. Again, don't misuse it by throwing away the Boolean result and setting a variable to a value. If you want to do something to every item in an array and return a single value, at that point you would want to use reduce. Also, notice that !arr.every(lambdacondition) is the same as arr.some(!lambdacondition).
reduce
The way your code is actually written—finding the last value that matches the condition—naturally lends itself to reduce:
let testResult = values.reduce(
(recent, value) => {
if (value <= someNumber) {
recent = value;
}
return recent;
},
undefined
);
This does the same job of finding the last value as your example code does.
map
map is for when you want to transform each element of an array into a new array of the same length. If you have any experience with C# it is much like the Linq-to-objects .Select method. For example:
let inputs = [ 1, 2, 3, 4];
let doubleInputs = inputs.map(value => value * 2);
// result: [ 2, 4, 6, 8]
New requirements
Given your new description of finding the adjacent values in a sorted array between which some value can be found, consider this code:
let sortedBoundaries = [ 10, 20, 30, 40, 50 ];
let inputValue = 37;
let interval = sortedBoundaries
.map((value, index) => ({ prev: value, next: sortedBoundaries[index + 1] }))
.find(pair => pair.prev < inputValue && inputValue <= pair.next);
// result: { prev: 20, next: 30 }
You can improve this to work on the ends so that a number > 50 or <= 10 will be found as well (for example, { prev: undefined, next: 10 }).
Final notes
By using this coding style of returning a value instead of modifying an outside variable, you not only communicate your intent better to other developers, you then get the chance to use const instead of let if the variable will not be reassigned afterward.
I encourage you to browse the documentation of the various Array prototype functions at MDN—doing this will help you sort them out. Note that each method I listed is a link to the MDN documentation.
I would suggest you to use Array#some, instead of Array#forEach.
Array#forEach keeps iterating the array even if given condition was fulfilled.
Array#some stops iteration when given condition was fulfilled.
One of the advantages would be connected with performance, another - depends on your purposes - Array#forEach keeps overwriting the result with every passed condition, Array#some assigns the first found value and stops the iteration.
let test,
values = [4,5,6,7],
someNumber = 5;
values.some((value, idx) => {
if (someNumber >= value) {
test = value;
return test;
}
});
console.log(test);
Another option would be to use the Array.some() method.
let test;
const someNumber = 10;
[1, 5, 10, 15].some(function (value) {
if (value > someNumber) {
return test = value
}
})
One advantage to the .some() method over your original solution is optimization, as it will return once the condition has been met.
How about Object?
you can search with for-of
In FORTRAN and C++, the address of a specific array element can be passed into a function. For example, in the main routine, WA1 is a work array of size 25 and offset is an integer variable that indicates the offset from the 0-index. Say offset is presently 6.
The declaration of the sub-routine might look like the following:
void Array_Manip1(double* WorkArray1){
. . .
When the sub-routine is called in the main program, the call might look like this:
Array_Manip1(&WA1[offset]);
By doing this, I can index WorkArray1 within the sub-routine starting at the 0-index, but knowing it is actually WA1[6].
e.g. -
for (int i = 0; i < 19; ++i)
WorkArray1[i] = whatever computation is required.
To do this in Javascript, I suppose the full array could be passed in to the sub-routine, plus one more variable to hold the offset. And then within the sub-routine, the offset would have to be added to the array index value.
e. g. -
for (int i = 0; i < 19; ++i){
WorkArray1[offset + i] = whatever computation is required.
But now I am passing one more variable into the sub-routine, and have to add the offset to the array index each time through the loop.
Is there a better way to do this in Javascript?
Is there a way to imitate C++'s ability to pass the address of a specific array element into a function?
The cleanest way would be to splice the array and pass in a subarray from the current index on. That way you still have one reference, and everything stays clean.
But no, arrays in most higher level languages do not allow you to reference a single element and then get back to the array. It is dangerous for a number of reasons on those kinds of languages where the underlying data may not even be stored contiguously. JavaScript is no exception, and you can pass in an array and an index, or a subarray, but you can't pass in a reference to an element in the array and get back to the array after passing it in.
Tim's answer is correct. I just want to add something about the C-like typed arrays: they can be created as a view into an ArrayBuffer, in which case you could create a new view of the same buffer as the larger array but starting at an offset, and pass that, without duplicating the underlying data. Closest you can get to your pointers.
You can sort of do what you want. And in fact, sometimes javascript can only do what you want. It all depends on what data the array contains.
In Javascript, the content of a variable may either be a value or a reference (pointer but without pointer arithmetic). But you have no choice in the matter. Numbers and strings are always values (there are exceptions but none of them apply when passing as function arguments) and everything else are always references.
So to get the behavior you want, simply use an object or array as your value holder instead of a string or number:
var ref_array = [ {value:1}, {value:2}, {value:3} ];
function increment (v_obj) {
v_obj.value ++;
}
var ref = ref_array[1];
increment(ref);
// ref_array will now contain: [{value:1},{value:3},{value:3}]
It's not that simple though. While the object appears to be passed by reference, the reference is however copied when the function is called. What this means is that ref and ref_array[1] and v_obj are three separate variables that point to the same thing.
For example, this wouldn't work:
function replace (obj1, obj2) {
obj1 = obj2;
}
replace(ref_array[1], {value:9});
// ref_array is still: [{value:1},{value:3},{value:3}]
That's because, while obj1 in the function above points to the same object as ref_array[1], it is not really a pointer to ref_array[1] but a separate variable. In C, this would be something like obj1 = &ref_array[1]. So passing an argument passes a copy of the pointer but not the pointer itself.
For searching elements of one array inside the other, one may use the indexOf() on the target of search and run a loop on the other array elements and in each step check existence. This is trivial and my knowledge on Javascript is too. Could any one please suggest a more efficient way? Maybe even a built-in method of the language could help? Although I couldn't hit to such a method using google.
You can use Array.filter() internally and implement a function on Array's prototype which returns elements which are common to both.
Array.prototype.common = function(a) {
return this.filter(function(i) {
return a.indexOf(i) >= 0;
});
};
alert([1,2,3,4,5].common([4,5,6])); // "4, 5"
Again as you mention in your post, this logic also works by taking each element and checking whether it exists in the other.
A hair more efficient way is convert one of the arrays into a hash table and then loop through the second one, checking the presence of elements at O(1) time:
a = [1,2,3,4,5]
b = [1,7,3,8,5]
map = {}
a.forEach(function(x) { map[x] = 1 })
intersection = b.filter(function(x) { return map[x] === 1 })
document.write(JSON.stringify(intersection))
This only works if elements in question are primitives. For arrays of objects you have to resort to the indexOf method.
ES6 ("Harmony") does support Set, but strangely not set operations (union, intersection etc), so these should be coded by hand:
// Firefox only
a = [1,2,3,4,5]
b = [1,7,3,8,5]
sa = new Set(a)
sb = new Set(b)
sa.forEach(function(x) {
if (!sb.has(x))
sa.delete(x);
});
document.write(uneval([...sa]))
JavaScript's arrays don't have a method that does intersections. Various libraries provide methods to do it (by looping the array as you describe), including Underscore and PrototypeJS (and others, I'm sure).