I'm working on the FreeCodeCamp 'Random Quote Machine' front end library project using React JSX, which was working fine with the exception that it frequently produced the same quote two or three times in a row. This was not a good thing for a user to exprience.
Looking into Math.random() I quickly realisded that this was quite normal - two or three repeats of the same number in a row. This is the code I had in a React method called getQuote:
// get new quote from array
getQuote() {
let x = Math.floor(Math.random() * quotes.length);
{console.log("random num = ", x)};
this.setState(quotes[x]
);
This meant that I had to write a method of producing a random number that wasn't the same as the last. Which, thanks to the quality content on this site and input from #Etienne Martin I found pretty quickly:
Most of the answers in this thread are over complicated.
Here's a concise example of how I would do that:
function getNumber(){
return (getNumber.number = Math.floor(Math.random() * (4 + 1))) === getNumber.lastNumber ? getNumber() : getNumber.lastNumber = getNumber.number; }
console.log(getNumber()); // Generates a random number between 0 and 4
Live example: https://jsfiddle.net/menv0tur/3/ share edit flag edited
Apr 22 '17 at 16:25 answered Apr 20 '17 at 19:35
This allowed me to write my own method:
getQuote() {
const randNum = () => {
return (randNum.x = Math.floor(Math.random() * (quotes.length))) === randNum.y ? randNum() : randNum.y = randNum.x;
}
this.setState(quotes[randNum()])
}
My problem is; I don't fully understand it. Specifically getNumber.number it looks as though it is using dot notation to access a key value of our function getNumber(). I have never seen this before. As you can see in my re-write I defined the function name randNum() and I'm using randNum.x and randNum.y instead of getNumber.number and getNumber.lastNumber.
Could someone kindly enlighten me as to exactly what we are doing here?
Many thanks in advance.
The first thing you should know is that a function is an object at the same time in javascript. So I can do this:
const randNum = () => {}
randNum.property = "value";
and that's exactly what your function is doing. Probably it will be better to write it as:
const randNum = () => {
randNum.x = Math.floor(Math.random() * (quotes.length));
// I can access randNum.x and randNum.y because randNum is a function and an object
if (randNum.x === randNum.y) {
// current value is the same as older value, run the function again
return randNum();
} else {
// current value is not the same as the older value
randNum.y = randNum.x;
return randNum.y;
}
};
or, even better:
let oldValue;
const randNum = () => {
const currentValue = Math.floor(Math.random() * (quotes.length));
if (currentValue === oldValue) {
return randNum();
} else {
oldValue = currentValue;
return currentValue;
}
};
I know this is not a one line solution but is easier to understand what it does (in my opinion)
Let's rewrite getNumber function so that it's more readable:
function getNumber() {
let result;
if (
(getNumber.number = Math.floor(Math.random() * (4 + 1))) ===
getNumber.lastNumber
) {
result = getNumber(); // recursive call
} else {
result = getNumber.lastNumber = getNumber.number; // assignment expression
}
return result;
}
As you can see the function uses recursion. Also in this line: result = getNumber.lastNumber = getNumber.number; the code getNumber.lastNumber = getNumber.number is an assignment expression so result is the same as getNumber.number. Lastly it's possible to write getNumber.number because getNumber is an object. In Javascript even functions are objects. So when you call getNumber() you invoke the function, when you call getNumber.number you accessing object field.
After you define the function getNumber you can then add as many fields to it as you want for example getNumber.anotherProperty = 'whatever' then console.log(getNumber.anotherProperty) // logs 'whatever'.
You may populate your state with a shuffled version of array of quotes, popping one at a time upon nextQuote until all of them are shown, then repeat, once your shuffled array is empty, thus, the quote will not appear until all the rest are shown.
However, it does not eliminate probability of the following outcome:
quote1, quote3, quote5, quote4 | quote5, quote1, quote3..
Pushing the distance between quotes further ultimately leads you to the point where the interval of repeating for each quote is source array length, i.e. your initial array going round:
quote1, quote2... , quote5| quote1, quote2..., quote5 | quote1, quote2...
But then it won't be random quotes any longer
Live-demo of my approach, you may find as follows:
const { useState } = React,
{ render } = ReactDOM,
rootNode = document.getElementById('root')
const quotes = ['quote1', 'quote2', 'quote3', 'quote4', 'quote5'],
shuffle = arr => [...arr].reduceRight((r,_,__,s) =>
(r.push(s.splice(0|Math.random()*s.length,1)[0]), r),[])
const App = () => {
const [currentQuote, setCurrentQuote] = useState(quotes[0|Math.random()*quotes.length]),
[quoteList, setQuoteList] = useState(shuffle(quotes).filter(q => q!= currentQuote)),
nextQuote = () => {
const [newQuote, ...rest] = quoteList
!rest.length ?
setQuoteList(shuffle(quotes).filter(q => q != newQuote)) :
setQuoteList(rest)
setCurrentQuote(newQuote)
}
return (
<div>
<div>{currentQuote}</div>
<button onClick={() => nextQuote()}>Next</button>
</div>
)
}
render (
<App />,
rootNode
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
Related
My apologies in advance if this is a very basic question/answer - I have searched, and am "old" learning for the first time and have found myself a little lost.
It's my first time coding, and basically I have an array (lets say fruits), I've been able to create it displaying the array and shuffling that array on click - which is exactly what I wanted (yay go me).
Now I am trying to have the ability to keep track of how many times I have clicked the shuffle button = before I either click reset, leave the page or refresh the page in which the counter resets.
This is where my trouble lays. I am having trouble 'inter-twining' the array + how many times I have clicked shuffle into my code. Honestly, I am getting confused, with so many 'help blogs' who all have different ways of doing things and I'd truly TRULY appreciate any help to get me sorted.
This is my current code to produce the list of fruits and being able to display it on my page.
function shuffleArray(arr) {
for (let i = arr.length - 1; i > 0; i--) {
const r = Math.floor(Math.random() * (i + 1));
[arr[i], arr[r]] = [arr[r], arr[i]];
}
return arr;
};
const array = ["Apple", "Pear", "Apricot", "Nashy", "Kiwi", "Watermelon"];
function printArray(arr) {
return arr.reduce((acc, n, i) => {
return acc.concat(i < arr.length - 1 ? `${n}\n ` : `${n}`);
}, '');
};
function shuffle() {
const copiedArr = array.map(n => n);
const shuffledArray = shuffleArray(copiedArr);
document.getElementById("array").innerText = printArray(shuffledArray);
}
function restore() {
document.getElementById("array").innerText = printArray(array);
}
restore();
<div class="container">
<span id="array"><pre></pre></span>
</div>
<br>
<br>
<button onclick="shuffle()" value="randomize">Shuffle!</button>
<button onclick="restore()">Retore</button>
To be honest I have tried so many different things I am lost and confused. And so am hoping to start from scratch this is my code and I am trying to get a counter to keep track of every time I click shuffle that resets when Reset is clicked.
Here you go. While swahili geeks answer is correct. It is quite confusing for a learner.
So I took a different approach. You define a state object for your application and work upon it.
It would be nice to wrap the whole thing in a function to isolate the scope of declared functions and variables and not pollute the window, but you get there on your speed.
Also note how I am using a spread operator [...arr] to clone an array. No need to .map(n => n)
const state = {
originalArray: ["Apple", "Pear", "Apricot", "Nashy", "Kiwi", "Watermelon"],
currentArray: ["Apple", "Pear", "Apricot", "Nashy", "Kiwi", "Watermelon"],
numberOfShuffles: 0,
};
const outputElement = document.getElementById("js-array");
const counterElement = document.getElementById("js-count");
function formatArray(arr) {
return arr.reduce((acc, n, i) => {
return acc.concat(i < arr.length - 1 ? `${n}\n` : `${n}`);
}, "");
}
function shuffleArray(arr) {
for (let i = arr.length - 1; i > 0; i--) {
const r = Math.floor(Math.random() * (i + 1));
[arr[i], arr[r]] = [arr[r], arr[i]];
}
return arr;
}
function shuffle() {
const copiedArr = [...state.currentArray];
const shuffledArr = shuffleArray(copiedArr);
state.currentArray = shuffledArr;
state.numberOfShuffles++;
updateUi();
}
function restore() {
state.currentArray = [...state.originalArray];
state.numberOfShuffles = 0;
updateUi();
}
function updateUi() {
outputElement.innerText = formatArray(state.currentArray);
counterElement.innerText = state.numberOfShuffles;
}
updateUi();
<pre id="js-array"></pre>
<br />
<span id="js-count"></span>
<br />
<br />
<button onclick="shuffle()">Shuffle!</button>
<button onclick="restore()">Restore</button>
The closure in JavaScript can be solution but it's a bit challenging to understand.
const add = (function () {
let counter = 0;
return function () {counter += 1;
return counter}
})();
// Call add() 3 times
add();
add();
add();
// the counter is now 3
The variable add is assigned to the return value of a self-invoking function.
The self-invoking function only runs once. It sets the counter to zero (0), and returns a function expression.
This way add becomes a function. The "wonderful" part is that it can access the counter in the parent scope.
This is called a JavaScript closure. It makes it possible for a function to have "private" variables.
The counter is protected by the scope of the anonymous function, and can only be changed using the add function.
I have a Javascript function declartions as a string (gotten from Function.toString), and I want to wrap all variable declarations with a function (also in Javascript), E.g.
const value = 42 to const value = wrapper(42).
First I thought of using RegEx to get the original values and location and then replace them with the wrapped value, but the RegEx got too complex very fast because of needing to think about things like multiline strings and objects. Using RegEx would also impact the ease of other people contributing to the project.
After that I looked into using a module for this, I found Acorn (used by Babel, Svelte. Parses the Javascript into an ESTree, the spec for Javascript Abstract Syntax Trees): https://github.com/acornjs/acorn, but I couldn't find a way of parsing the ESTree back to a Javascript function declaration after making the modifications.
Is there a way of parsing the ESTree back to a function, or another better solution?
You don't really need a function to stringify the tree back into code. Instead, take note of the offsets where the change should occur, and then don't apply the change in the tree, but to the original string.
Here is a demo with the acorn API:
function test () { // The function we want to tamper with
const value = 42, prefix = "prefix";
let x = 3;
for (let i = 0; i < 10; i++) {
x = (x * 997 + value) % 1000;
}
return prefix + " " + x;
}
function addInitWrappers(str) { // Returns an updated string
let ast = acorn.parse(str, {ecmaVersion: 2020});
function* iter(node) {
if (Object(node) !== node) return; // Primitive
if (node.type == "VariableDeclaration" && node.kind == "const") {
for (let {init} of node.declarations) {
yield init; // yield the offset where this initialisation occurs
}
}
for (let value of Object.values(node)) {
yield* iter(value);
}
}
// Inject the wrapper -- starting at the back
for (let {start, end} of [...iter(ast)].reverse()) {
str = str.slice(0, start) + "wrapper(" + str.slice(start, end) + ")" + str.slice(end);
}
return str;
}
function wrapper(value) { // A wrapper function to demo with
return value + 1;
}
console.log("before wrapping test() returns:", test());
let str = test.toString();
str = addInitWrappers(str);
eval(str); // Override the test function with its new definition
console.log("after wrapping test() returns:", test());
<script src="https://cdnjs.cloudflare.com/ajax/libs/acorn/8.7.1/acorn.min.js"></script>
This is the code I use currently. The function createRandomNumberString returns a string of 4 numbers. I run this function as long as it returns a string which I already have inside this.usernameIds
while (this.usernameIds.includes(randomString)) {
randomString = createRandomNumberString();
}
this.usernameIds.push(randomString);
The problem I have with this code is that it might run indefinitely and want to avoid this of happening. How could this piece of code be rewritten?
(I already check beforehand that this.usernameIds's length is less than 10,000.
You can count the number of loops too:
let maxLoop = 10;
do
{
randomString = createRandomNumberString();
}
while(this.usernameIds.includes(randomString) && --maxLoop);
if (maxLoop)
this.usernameIds.push(randomString);
As long as you're not concerned about memory consumption you may just use source array of all available identifiers and select from it:
const availableIds = Array.from({ length: 10000 }, (_, i) => i)
const usernameIds = []
for (let i = 0; i < 10; i++) {
if (!availableIds.length) break;
const rnd = Math.floor(Math.random() * availableIds.length)
const [id] = availableIds.splice(rnd, 1)
const strId = String(id).padStart(4, '0')
usernameIds.push(strId)
}
console.log(usernameIds)
splice is removing 1 element of the source array on each cycle so your ids will always be unique. Untill you run out of them. Then the for loop just breaks to the outer code;
I have been trying to solve URL Shortener question on Codewars but I have been unable to pass the test cases. My solution is getting timed out. I cannot find a fast efficient way of hashing which returns a string. Most algorithms that I found are returning an integer after hashing a string. The shortened URL should consist only of lowercase letters.
My solution
const map = new Map();
const longToShortURL = new Map();
let url = 'short.ly/';
const urlShortener = (longURL) => {
if (longToShortURL.has(longURL)) return url + longToShortURL.get(longURL);
const letters = 'abcdefghijklmnopqrstuvwxyz';
let key = getKey(letters);
while (map.has(key)) {
key = getKey(letters);
}
map.set(key, longURL);
longToShortURL.set(longURL, key);
return url + key;
};
const getRandomInt = (max) => {
return Math.floor(Math.random() * Math.floor(max));
};
const getKey = (letters) => {
let key = '';
for (let i = 0; i < 4; i++) {
key += letters.charAt(getRandomInt(letters.length));
}
return key;
};
const urlRedirector = (shortURL) => {
const key = shortURL.split(url)[1];
return map.get(key);
};
Result
Passed: 2 Failed: 0 Exit Code: 1
Test Results:
Should pass all of these
Testing two different URLs
Testing same URLs
Completed in 1ms
Strict tests
STDERR
Execution Timed Out (12000 ms)
Why did my code time out?
Our servers are configured to only allow a certain amount of time for your code to execute. In rare cases the server may be taking on too much work and simply wasn't able to run your code efficiently enough. Most of the time though this issue is caused by inefficient algorithms. If you see this error multiple times you should try to optimize your code further.
I'm trying to make a sorting algorithm visualizer using Angular. Currently doing merge sort and its giving me troubles. I am a Java programmer so I think there is something I'm doing wrong with the typescript with the recursive calls. When I run the program it is giving me errors saying 'cannot read property length of undefined'. I am lost and can't seem to put a finger on where I am going wrong.
In my first function I repeatedly split the original array called visualArr which is a global array in my program sized by the user and used for the visualization of the sorting algorithms. Then the second function I have is used for the merging of the split arrays.
performMergeSort(arr) {
if (arr.length <= 1) {
return arr;
}
const middle = Math.floor(arr.length / 2);
let left = arr.slice(0, middle);
let right = arr.slice(middle, arr.length);
left = this.performMergeSort(left);
right = this.performMergeSort(right);
return this.performMerge(left, right);
}
The timer function in the second code block is used for visuals of the sort. However, I think there is something wrong with either my logic or something I am doing wrong in TypeScript. Any feedback is appreciated.
performMerge(leftArr, rightArr) {
let lIdx = 0;
let rIdx = 0;
let i = 0;
timer(0, 100)
.pipe(takeWhile(() => leftArr.length > lIdx && rightArr.length > rIdx))
.subscribe(() => {
const lItem = leftArr[lIdx];
const rItem = rightArr[rIdx];
if (lItem > rItem) {
this.visualArr[i] = rItem;
rIdx++;
} else {
this.visualArr[i] = lItem;
lIdx++;
}
i++;
});
timer(0, 100)
.pipe(takeWhile(() => leftArr.length > lIdx ))
.subscribe(() => {
this.visualArr[lIdx] = leftArr[lIdx++];
});
timer(0, 100)
.pipe(takeWhile(() => rightArr.length > rIdx ))
.subscribe(() => {
this.visualArr[rIdx] = rightArr[rIdx++];
});
}
This error normally comes when you haven't initialized array correctly.
Try this:-
visualArr:number[]=[];
yes, you cannot read the property length of an array if it is not defined, i.e. it is not initialized, so try to initialize all your arrays:
if you know the type: myArray:string[]=[]
if not just do this: myArray=[]