I have the following, which works fine. It generates a unique random number for a given empty array and a Max determined by the another array (data) length. I would like to add a check that does:
when the array length is = MaxN, I want to store the last value of the array inside a variable so that if it is = to a new random generated number I will call "generateRandomNumber(array, maxN)" again.
const generateRandomNumber = (array, maxN, lastN) => {
let randomN = Math.floor(Math.random() * maxN) + 0;
console.log(lastN)
if(lastN == randomN) {
// do your thing
}
if(array.includes(randomN)) {
return generateRandomNumber(array, maxN, lastN);
}
if(array.push(randomN) == maxN) {
lastN = array.length - 1
array.length = 0;
}
return randomN
}
export default generateRandomNumber
however I am always getting undefined inside the console.log. I am passing lastN like so:
let lastN;
I would think that that value which is undefined at first would later get updated inside:
if(array.push(randomN) == maxN) {
lastN = array.length - 1
array.length = 0;
}
component where generateRandomNumber is used:
...
const utilityArray = []
const tempQuestions = []
let lastN
class App extends Component {
constructor(props) {
super(props);
this.state = {
collection: gridItemsCollection,
intro: false,
instructions: false,
grid: true,
questions: this.props.questions,
selectedQuestion: ""
}
}
getRandomN = (arr, max, lastN) => {
let s = generateRandomNumber(arr, max, lastN)
return s
}
hideGridItem(e) {
let //index = this.getRandomN(utilityArray, gridItemsCollection.length),
collection = this.state.collection,
newCollection,
//updatedGridItem = collection[index].hidden = true,
questions = this.state.questions.questions,
n = this.getRandomN(tempQuestions, questions.length, lastN);
console.log(lastN)
// this.setState({
// newCollection: [ ...collection, updatedGridItem ]
// })
// if(this.getAnswer(e)) {
this.generateNewQuestion(questions[n])
// }
// else {
// console.log('no')
// }
}
generateNewQuestion(selectedQuestion) {
this.setState({
selectedQuestion
})
}
componentDidMount = () => {
const questions = this.state.questions.questions
let randomNumber = this.getRandomN(tempQuestions, questions.length, lastN)
this.generateNewQuestion(questions[randomNumber])
}
getAnswer = (e) =>
e.target.getAttribute('data-option') == this.state.selectedQuestion.correct_option
render() {
const state = this.state
const { collection, grid, intro, selectedQuestion } = state
console.log(tempQuestions)
return (
<div className="wrapper">
<div className="wrapper-inner">
<View isVisible={state.intro}>
<p> intro screen </p>
</View>
<View isVisible={state.grid}>
<Grid gridItemsCollection={collection}/>
<Question question={selectedQuestion.question} />
<Controls
onClick={this.hideGridItem.bind(this)}
gridItemsCollection={collection}
answers={selectedQuestion}
answer={selectedQuestion.correct_option}
/>
</View>
</div>
</div>
);
}
}
export default App;
It looks like you declare lastN but it is never actually declared for the first time. This means the first time you access it, it'll always be undefined.
You have two options to solve this:
Define lastN to some suitable default value (I would think something like -1 may be suitable based on the code presented).
let lastN = -1;
Just ignore it. From your code, it doesn't look like lastN being undefined should even be a problem since the only check you do is lastN == randomN, which will always be false if lastN is undefined.
It looks like it should get updated, but possibly not on the first call. It looks like it depends on how many questions you have. Unless you have 1 question, it won't update for a few tries. Also, if you have zero questions, it'll never update (since array.push() will be 1 and maxN will be 0).
When you use lastN as a parameter, the local variable lastN takes precedence over global lastN and you are actually updating the local variable, not the global. Just change your argument name.
const generateRandomNumber = (array, maxN, lastN) => {
let randomN = Math.floor(Math.random() * maxN) + 0;
console.log(lastN)
if(lastN == randomN) {
// do your thing
}
if(array.includes(randomN)) {
return generateRandomNumber(array, maxN, lastN);
}
if(array.push(randomN) == maxN) {
lastN = array.length - 1 //-> Here you update the local copy, the global is not affected. So rename you argument to... say lastNArg
array.length = 0;
}
return randomN
}
export default generateRandomNumber
And don't forget to initialize your global lastN var like: let lastN = 0;
So, here it is modified:
const generateRandomNumber = (array, maxN, lastNArg) => {
let randomN = Math.floor(Math.random() * maxN) + 0;
console.log(lastNArg)
if(lastNArg == randomN) {
// do your thing
}
if(array.includes(randomN)) {
return generateRandomNumber(array, maxN, lastNArg);
}
if(array.push(randomN) == maxN) {
lastN = array.length - 1; //make sure lastN is initialized and you have access to it
array.length = 0;
}
return randomN
}
export default generateRandomNumber
Related
How to refactor this function to a higher order function?
It is meant to return a new array containing the sub-arrays of characters that contain the value 'Rambo'.
function isRamboCharacter(characters) {
const x = [];
for (let i = 0; i < characters.length; i++) {
if (characters[i].movie.includes('Rambo')) {
x.push(characters[i]);
}
}
return x;
}
I tried:
return characters.movie.includes('Rambo');
Solution
const characters = [
{ movie: ["Rambo", "Rockey", "Avengers"] },
{ movie: ["Fatherhood", "Rockey", "Avengers"] }
]
const isRamboCharacter = (characters) => characters.filter((char) => char.movie.includes("Rambo"))
console.log(isRamboCharacter(characters));
Or you could directly call Array's filter method
console.log(characters.filter((char) => char.movie.includes("Rambo")));
//output - [ { movie: [ 'Rambo', 'Rockey', 'Avengers' ] } ]
You can curry it (Return a function in a function)
function isCharacterInMovie(movie) {
return function filterFunction(characters) {
const x = [];
for (let i = 0; i < characters.length; i++) {
if (characters[i].movie.includes(movie)) {
x.push(characters[i]);
}
}
return x;
}
}
const isRamboInMovie = isCharacterInMovie('rambo') // returns the inner function which holds on to the value from when you called it
isRamboInMovie(charactersInTheMatrix); // []
isRamboInMovie(charactersInRambo); // ['rambo']
The 'You don't know JS' book series has a great (short AND free) book on this, and it explains it incredibly well
You Don't Know JS Yet: Scope & Closures - 2nd Edition
I have such function and global variable (as array):
const arraysList = []
export const changeColorCategories = (array, draggedColumnId) => {
const isColor = arraysList.length ? arraysList[0][0]?.color : [];
if (typeof isColor === 'string') {
firstLevelColor = isColor;
}
return array.map((item, index, categories) => {
item.color = draggedColumnId !== 3 ? '#010172' : '#000000';
arraysList.push(categories);
if (firstLevelColor && !draggedColumnId) {
item.color = firstLevelColor;
}
if (item?.children?.length) {
changeColorCategories(item.children);
}
return item;
})
}
Every call of this function push some data to array. In this function I use recursion. So how i can clear this array only when this function will end it's work.
You can call the recursion function inside another function this way you can run anything you want when the function ends
const arraysList = []
export const changeColorCategories = (array, draggedColumnId) => {
const isColor = arraysList.length ? arraysList[0][0]?.color : [];
if (typeof isColor === 'string') {
firstLevelColor = isColor;
}
return array.map((item, index, categories) => {
item.color = draggedColumnId !== 3 ? '#010172' : '#000000';
arraysList.push(categories);
if (firstLevelColor && !draggedColumnId) {
item.color = firstLevelColor;
}
if (item?.children?.length) {
changeColorCategories(item.children);
}
return item;
})
}
function runRucFunc(){
const result = changeColorCategories();
//Your other code goes here
return result;
}
You can just put your recursion part inside a sub function.
Below I've called the inner function inner, I've also moved the arrayList into the function, due to closures you wound't even need to clear the arrayList, it would be cleared automatically as it goes out of scope.
eg.
export const changeColorCategories = (array, draggedColumnId) => {
const arraysList = []
function inner(array, draggedColumnId) {
const isColor = arraysList.length ? arraysList[0][0]?.color : [];
if (typeof isColor === 'string') {
firstLevelColor = isColor;
}
return array.map((item, index, categories) => {
item.color = draggedColumnId !== 3 ? '#010172' : '#000000';
arraysList.push(categories);
if (firstLevelColor && !draggedColumnId) {
item.color = firstLevelColor;
}
if (item?.children?.length) {
inner(item.children); //we call inner here instead.
}
return item;
})
}
// now call our inner
// you could do something even before your recursion.
const result = inner(array, draggedColumnId);
// here we can put what we want after recursion.
return result;
}
You could wrap the recursive call in another function like so:
const arr = []
const recursive = (counter = 0) => {
if(counter === 5)
return arr.map((v) => String.fromCodePoint(65 + v))
arr.push(counter)
return recursive(++counter)
}
const go = () => {
console.log(recursive()) // [A,B,C,D,E]
console.log(arr) // [0,1,2,3,4]
arr.length = 0 // clear the array
console.log(arr) // []
}
go()
Alternatively, if the global array does not actually need to be global, and is merely a container for working information of the recursive algorithm, then you could make it a parameter of the recursive function, which will then fall out of scope (and be garbage collected) when the recursion ends.
const recursive = (counter = 0, arr = []) => {
if(counter === 5)
return arr.map((v) => String.fromCodePoint(65 + v))
arr.push(counter)
return recursive(++counter, arr)
}
console.log(recursive()) // [A,B,C,D,E]
console.log(arr) // Error! Not in scope!
go()
Or, you could make the recursive function more intelligent and able to detect when it is processing the final recursion: how this is done will depend on the precise logic of the recursive function.
I have a problem to solve involving default parameters and object destructuring.
I have an object 'product' with this shape:
{
name: "Slip Dress",
priceInCents: 8800,
availableSizes: [ 0, 2, 4, 6, 10, 12, 16 ]
}
Here is my code so far, but I am receiving an error that 'availableSizes' is not iterable. Can someone help me correct this code?
I have tried adjusting the default parameters in my function and I have moved my return statements to no avail.
function checkIfSizeIsAvailable(product = {availableSizes:[]}, size = 0) {
// let availableSizes = product;
let foundSize = "";
for (let sizeCheck of product.availableSizes) {
if (sizeCheck === size) {
foundSize = size;
}
}
if (foundSize === ""){
return false;
} else {
return true;
}
//for (let i = 0; i < sizes.length; i++) {
// return false;
}
As VLAZ mentioned in a comment, you can pass an object without an availableSizes field and it'll cause that error.
Destructuring happens when your variables together form an object/array on the left-hand size of the assignment:
function checkIfSizeIsAvailable(product = {availableSizes:[]}, size = 0) {
// ^ Because of the default parameter, product always exists
// Now we actually destructure with a default value for missing fields
const { availableSizes = [] } = product;
}
or more compact:
function checkIfSizeIsAvailable({ availableSizes = [] } = {availableSizes:[]}, size = 0) {
}
Mind that this does not defend against non-array values, or even falsy values. A user can pass { availableSizes: false } and your availableSizes would also be false.
I am building a react project for visualizing insertion sort using redux. I am using react-redux to create and handle actions. However, the problem is that in my insertionSort algorithm, I dispatch an updateArray action every time the array being sorted changes. I put print statements inside the reducer and saw that the state was in fact changing and the action was being dispatched correctly, however, my actual array does not re-render. I put prints inside the relevant UI component's render() function and saw that it was only being called once or twice rather than every time the reducer receives the action. I tried restructuring my code multiple times and reading about similar problems that people have had but their answers did not help me.
Am I just structuring this the wrong way? Should I not be using dispatches every second or so to update my array?
I have a main.js file which is used to render the UI components including my array:
class Main extends React.Component {
setArray = () => {
this.props.setArray(50, window.innerHeight / 1.4)
startSort = () => {
this.props.startSorting(this.props.algorithm, this.props.array)
}
render() {
let { array} = this.props
return (
<div>
<Navbar
startSort={this.startSort}
setArray={this.setArray}
/>
<MainWrapper>
<Row />
</MainWrapper>
</div>
)
}
}
const mapStateToProps = state => {
return {
array: state.array,
}
}
const mapDispatchToProps = dispatch => {
return {
setArray: (length, height) => {
let array = Array.from({ length: length }, () =>
Math.floor(Math.random() * height))
dispatch(setArray(array))
},
startSorting: (algorithm, array) => {
var doSort
if (algorithm == 'insertionSort') {
doSort = insertionSort
}
doSort(array, dispatch)
}
}
}
My actual array is generated with Row.js
class Row extends React.Component {
generateNodes(array) {
var elements = []
array.forEach((value, index) => {
elements.push(
<CenteredColumn>
<ArrayNode idx={index} value={value} />
</CenteredColumn>
)
})
return elements
}
render() {
let { array } = this.props
console.log('UPDATED ARRAY: ' + array)
var arrayElements = this.generateNodes(array)
return <RowWrapper>{arrayElements}</RowWrapper>
}
}
const mapStateToProps = state => {
return {
array: state.array
}
}
And finally, my actual algoritm is in insertionSort.js in which I import my actions from their reducers and pass in a dispatch function from main.js:
function delayedInsertion(array, dispatch) {
let n = array.length
var i = 0
function loop() {
setTimeout(function() {
var temp = array[i]
var j = i - 1
while (j >= 0 && array[j] > temp) {
array[j + 1] = array[j]
j--
}
array[j + 1] = temp
// console.log('ARRAY: ' + array)
dispatch(updateArray(array))
i++
if (i < n) {
loop()
}
}, 200)
}
loop()
console.log('DONE')
}
It seems that you are mutating your state.
You are passing this.props.array to your doSort action and as I understand your idea correctly, you are just calling delayedInsertion from that action (you did not post source code of that action).
But in delayedInsertion you are mutating the passed array when you are changing positions of you items, here:
while (j >= 0 && array[j] > temp) {
array[j + 1] = array[j]
j--
}
array[j + 1] = temp
You need to perform immutable change of the array.
So I'm using a constructor like this
const RPNCalculator = function () {
let methods = {
numberList: [],
calc: 0,
push(num) {
this.numberList.push(num);
},
plus() {
for (let i = 0; i <= this.numberList.length; i++) {
console.log('before:' + this.calc);
this.calc = this.calc + this.numberList[i];
}
console.log('after:' + this.calc);
this.numberList = [];
}
};
return methods;
}
const rpnCalculatorInstance = new RPNCalculator;
The fist console.log prints correctly and adds the elements but the second console.log prints NaN. I've used this pattern before with Object.create but for some reason the this.calc variable isn't persisting when using a constructor.
Any help is appreciated!
you can use reduce to sum up an array https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
run snippet below
class RpnCalculator{
constructor(){
this.numberList = [];
this.push = (num) => { this.numberList = [...this.numberList, num ]}
this.sum = () => {return this.numberList.reduce(( a, c) => a + c, 0);}
}
}
const rpnCalculator = new RpnCalculator();
rpnCalculator.push(1)
rpnCalculator.push(2)
rpnCalculator.push(3)
console.log(rpnCalculator.sum());
Apparently with the dataset I was given the last item in the array was an undefined element. I fixed it by using
if (typeof (this.numberList[i]) === 'number') {
console.log('before:' + this.calc);
this.calc = this.calc + this.numberList[i];
}