How can I compare two arrays and run a function depending on the number of matching items (regardless of index)? - javascript

I had a previous solution with .forEach that didn't consider the fact that the answers can be selected in the wrong order, so the items at to be at the same exact index. If it was selected in the wrong order it wouldn't match (even though it was the right answer).
Now I'm trying a solution with .every, but it's not changing score when the button "check answers" is clicked. In case you'd like to check the entire code here it is (it's a bit too much to type in - I'm new to stackoverflow so lmk if a better method is suggested to share multiple components): https://replit.com/#arshia93/Quizzical#sections/QuizData.jsx
function scoreQuiz(allCorrectAnswers, selections) {
let totalScore = 0;
if(allCorrectAnswers.length === selections.length) {
return allCorrectAnswers.every((element, index) => {
element === selections[index] ? totalScore++ : totalScore
})
setScore(totalScore)
}
setFinished(!finished)
}

What alonealgorithm said is true that you should not return the .every() call, since you want to perform a state update after it is complete. However, I don't think .every() is the array method you want to use here. It will check every item and perform the totalScore++, but it is not the most declarative way to do so.
I would recommend using the .reduce() method, since that is essentially what you're trying to do here. This should help you out:
const totalScore = selections.reduce((score, selection) =>
score + (allCorrectAnswers.includes(selection) ? 1 : 0)
, 0);
setScore(totalScore);
Note! If it is possible for 2 questions to have the same answer option, then this will not work and incorrectly identify a correct answer. In order to accommodate for this, you would need to replace the selection at a particular index (rather than appending each one as it is selected), or match each selection to a question id.

I looked at your code in the replit you linked. I think the issue is at line 41 where you return the result of calling every method on your allCorrectAnswers array. So, after the every method is done executing you return the value true or false and you exit the scoreQuiz function before you reach the next line where you would have setScore for your quiz. So, you should remove the return statement and let the setScore and setFinished hooks execute.

Related

Use multiple key-value filters on an object of objects?

Bit of a lengthy one so those of you who like a challenge (or I'm simply not knowledgeable enough - hopefully it's an easy solution!) read on!
(skip to the actual question part to skip the explanation and what I've tried)
Problem
I have a site that has a dataset that contains an object with multiple objects inside. Each of those objects contains an array, and within that array there are multiple objects. (yes this is painful but its from an API and I need to use this dataset without changing or modifying it.) I am trying to filter the dataset based of the key-value pairs in the final object. However, I have multiple filters being executed at once.
Example of Path before looping which retrieves the key-value pair needed for one hall.
["Hamilton Hall"]["Hire Options"][2].Commercial
After Looping Path of required key-value pair for all halls, not just one (the hall identifier is stored):
[0]["Hire Options"][2].Commercial
Looping allows me to check each hall for a specific key-value pair (kind of like map or forEach, but for an object).
After getting that out of the way back to the question.
How would I go about filtering which of the looped objects are displayed?
What I have Tried
(userInput is defined elsewhere - this happens on a btn click btw)
let results = Object.keys(halls);
for (key of results) {
let weekend = [halls[ `${key}` ][ 'Hire Options' ][4][ 'Weekend function' ]];
if(userInput == weekend) {
outputAll([halls[ `${key}` ]]);
}
}
That filters it fine. However, I run into an issue here. I want to filter by multiple queries, and naturally adding an AND into the if statement doesn't work. I also dont want to have 10 if statements (I have 10+ filters of various data types I need to sort by).
I have recently heard of ternary operators, but do not know enough about them to know if that is the correct thing to do? If so, how? Also had a brief loook at switches, but doesnt seem to look like what I want (correct me if I am wrong.)
Actual Question minus the babble/explanation
Is there a way for me to dynamically modify an if statements conditions? Such as adding or removing conditions of an if statement? Such as if the filter for 'a' is set to off, remove the AND condition for 'a' in the if statement? This would mean that the results would only filter with the active filters.
Any help, comments or 'why haven't you tried this' remark are greatly appreciated!
Thanks!
Just for extra reference, here is the code for retrieving each of the objects from the first object as it loops through them:
(Looping Code)
halls = data[ 'Halls' ];
let results = Object.keys(halls);
for (key of results) {
let arr = [halls[ `${key}` ]];
outputAll(arr);
}
You can use Array.filter on the keys array - you can structure the logic for a match how you like - just make it return true if a match is found and the element needs to be displayed.
let results = Object.keys(halls);
results.filter(key => {
if (userInput == halls[key]['Hire Options'][4]['Weekend function']) {
return true;
}
if (some other condition you want to match) {
return true;
}
return false;
}).forEach(key => outputAll([halls[key]]));

Is My Approach to the Todo App Delete Function Wrong?

I am learning React and just created a simple todo app using only React. My todo app has the standard structure of having a text input and an "ADD" button next to it. The user would type their todo in the input and every time they click on the "ADD" button next to it, a new ordered list of their inputs would appear underneath the input and "ADD" button.
The user can also delete a todo entry by clicking on the entries individually, like this:
To accomplish this behaviour of deleting entries, I used this delete function:
delete(elem) {
for (var i = 0; i < this.state.listArray.length; i++) {
if (this.state.listArray[i] === elem) {
this.state.listArray.splice(i, 1);
this.setState({
listArray: this.state.listArray
});
break;
}
}
}
My todo app works exactly the way that I want it to work, but as I look at other people's more conventional approach to this delete function, they either just simply use the splice method or the filter method.
For the splice method approach, they apparently just simply "remove" the unwanted entry from the listArray when the user clicks the particular entry. This does not work for me as using this method results in all my entries getting deleted except for the entry that I clicked on, which is the one that I want to delete.
On the other hand, the filter method approach apparently works by comparing the elem, which is the data passed from a child component, with each element in the listArray, and if the element in the for loop does not equal to the elem, then it would be passed onto a new array. This new array would be the one to not be deleted. This approach works better than the simple splice approach, however, one problem that I had encountered with this approach is that if I have more than one entry of the same value, for example, "Feed the dog". I only want one of the "Feed the dog" entries to be deleted, but it deletes both of them.
I thought of an approach to tackle this problem, eventually coming up with the current version of my code, which uses the splice method, but the splice method is used before I set it in the state. As evident here:
this.state.listArray.splice(i, 1);
this.setState({
listArray: this.state.listArray
});
My question can be broken down into three subquestions:
Considering that React states should be immutable, is the first line of the code above mutating my state? Is this approach not okay?
I thought that all React states were only possible to be changed inside a "setState" function, but my first line of code from above is not inside a setState function, yet it changed the state of listArray. How is this possible?
If my approach is mutating the state and is not ideal, how would you go about making the delete function so that it only deletes one entry and not more than one if there are multiple similar entries?
Yes, splice affects the array it acts on so don't use in this way. Instead you need to create a new array of the correct elements:
this.setState({
listArray: this.state.listArray.filter((el, idx) => idx !== i);
});
If you want to remove only the first instance, maybe couple with a findIndex (although indexOf would work in your example as well) first:
delete(elem) {
const idxToFilter = this.state.listArray.findIndex(el => el === elem);
if (idxToFilter < 0) {
return;
}
this.setState({
listArray: this.state.listArray.filter((el, idx) => idx !== idxToFilter);
});
}
This creates a new array without modifying the old which will cause anything that reacts to listArray changing to be notified since the reference has changed.

Indexing an array from the end

I am learning Javascript and I come from a Python background.
So, it's fairly intuitive to me to try and index an array from the end i.e using negative indices.
From what I have read so far, Javascript doesn't support them.
However, I found something which seems interesting but I am unable to understand the reason behind this.
todos = ['item1', 'item2', 'item3']
function updateTodos(index, new_value) {
todos[index] = new_value
console.log(todos)
}
function deleteTodos(index) {
todos.splice(index)
console.log(todos)
}
deleteTodos(-1)
updateTodos(-1, 'new_item')
deleteTodos(-1)
Output
["item1", "item2"]
["item1", "item2", -1: "new_item"]
["item1", -1: "new_item"]
Q: Why is deleteTodos able to delete the correct by index while updateTodos isn't?
Q: How can I accommodate for this behavior of negative indexing in updateTodos and any function dealing with the array data structure in general?
As far as I can make out, the indexing in updateTodos looks for the index variable and update the value at that index, if it exists, else, it creates a key-value pair. The splice method supports negative indexing, doesn't it?
I would appreciate if you can clarify my reasoning and/or help me with useful resources to understand this concept better.
According to MDN, splice does support negative indexing.
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
Index at which to start changing the array (with origin 0). If greater
than the length of the array, actual starting index will be set to the
length of the array. If negative, will begin that many elements from
the end of the array (with origin 1).
So, that's why deleting works.
To enable negative indexing in update, you could check if the supplied argument is negative. If it is, it is a simple manner of using array.length + index to have the python-like indexing.
2 things to add to the accepted answer:
1) Remember that when using splice that if you don't specify an amount of items to delete (second argument) then everything from the index you specify to the end of the array will be deleted. From your question I doubt this is what you want.
2) Splice can also be used to add elements to an array (third argument, fourth argument etc.) so would work fine for your update function.
A complete example to fix both issues would be like this:
function updateTodos(index, new_value) {
todos.splice(index, 1, new_value);
}
function deleteTodos(index) {
todos.splice(index, 1);
}
You could get even fancier and combine them both into one function where if you specify a value then that gets updated else it gets deleted, but it's probably unnecessary.

Angular customFilter function running twice

I'm trying to filter an array using a custom filter in angular.
Basically I have a binding as such: {{ myData.winners | getWinnerString }} that returns an array of items of any length between 1-4. If the array length is longer than 1, I'd like to return a custom string such as "Two way tie", "Three way tie", etc based on the array length. If it's just 1, I'd like to return the winner as is. This is what I have:
.filter('getWinnerString', function() {
return function(array) {
console.log(array);
return array;
}
});
When I run this, its looping through the array twice and logging the array twice. Any ideas as to why? Also any help/direction in actually setting up this function to return the correct string is greatly appreciated, as everything I've tried so far hasn't worked.
Check for the length of the array and return your results strings based the comparison.
if (winners.length === 1) {
return winners[0]; // return the only winner
} else {
return numberString(winners.length) + " way tie";
}
Filters in angular are just a nicer way to transform/filter date in the template. Think of it as a function with one argument writeOutWinnerString(winners).
If I remember correctly filters are executed every time there is an $scope $digest. So, as your application grows it may execute a lot more than that.
I found this link that my help you understand how filter execution works:
http://www.bennadel.com/blog/2489-How-Often-Do-Filters-Execute-In-AngularJS.htm

Checking for equivelance

OK, I'm missing something here and I just can't seem to find it because the logic seems correct to me, but I'm certain I'm not seeing the error.
var VisibleMarkers = function() {
var filtered = _.reject(Gmaps.map.markers, function(marker) {
return marker.grade != $('.mapDataGrade').val() && !_.contains(marker.subjects,$('.mapDataSubjects').val())
});
return filtered
}
I'm using underscore.js and jQuery to simplify my javascript work.
So right now, I'm checking by means of selects which data gets to be rejected and then I display the filtered markers on the (google) map (if it helps at all, this is using gmaps4rails which is working perfectly fine, its this bit of javascript that's making me lose the last of the hairs on my head).
Currently, the code functions 100% correctly for the ".mapDataGrade" select, but the ".mapDataSubjects" isn't. Now the markers object has a json array of the subjects (this is for students) and each item in the array has its ID. Its this ID that I am supposed to be checking.
Can someone see what I'm doing wrong?
If there's more info that needs to be included, please let me know.
This is on plain javascript on a RoR application using gmaps4rails
Now the markers object has a json array of the subjects (this is for students) and each item in the array has its ID. Its this ID that I am supposed to be checking.
_.contains compares a values, but it sounds like you want your iterator to compare a value to an object's "id" property. For that, _.some would work; it's like contains, except that, instead of comparing values, you can write the comparison as a function:
Returns true if any of the values in the list pass the iterator truth test.
Here's how you'd use it:
!_.some(marker.subjects, function(subject) {
return subject.id == $('.mapDataSubjects').val();
})
If I'm right, the whole line should be like this:
return marker.grade != $('.mapDataGrade').val() &&
// check that none of the subjects is a match
!_.some(marker.subjects, function(subject) {
// the current subject is a match if its ID matches the selected value
return subject.id == $('.mapDataSubjects').val();
});

Categories