I decided to make a random hex color generator for no reason. It works fine but it requires to write
var value1 = hexArray[Math.floor(Math.random() * hexArray.length)];"
6 times. How can I fix it so I don't do this? If I do something like this
var rand1 = rand2 = rand3 ect. = hexArray[Math.floor(Math.random() * hexArray.length)];
it would print out values all with the same number/letter. Any suggestions?
var hexArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f'];
var rand1 = hexArray[Math.floor(Math.random() * hexArray.length)];
var rand2 = hexArray[Math.floor(Math.random() * hexArray.length)];
var rand3 = hexArray[Math.floor(Math.random() * hexArray.length)];
var rand4 = hexArray[Math.floor(Math.random() * hexArray.length)];
var rand5 = hexArray[Math.floor(Math.random() * hexArray.length)];
var rand6 = hexArray[Math.floor(Math.random() * hexArray.length)];
var array2 = [rand1, rand2, rand3, rand4, rand5, rand6]
var value = "#" + array2.join("");
//ignore this
$(document).ready(function() {
$("body").css("background-color", value);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
The comments already provided short answers, but I'm going to elaborate here a bit how we get to it.
PART 1:
var rand1 = hexArray[Math.floor(Math.random() * hexArray.length)];
var rand2 = hexArray[Math.floor(Math.random() * hexArray.length)];
var rand3 = hexArray[Math.floor(Math.random() * hexArray.length)];
var rand4 = hexArray[Math.floor(Math.random() * hexArray.length)];
var rand5 = hexArray[Math.floor(Math.random() * hexArray.length)];
var rand6 = hexArray[Math.floor(Math.random() * hexArray.length)];
This part could be changed to use an array and a loop. A huge giveaway that this can be done is the fact that you have sequential numbers in your variable names. However, because arrays are usually zero-based, I'm not going to use 1-6 but 0-5 as indexes. In my loop, I'm counting a variable from 0 to 5 (although I'm checking whether it is < 6
and not <= 5
for readability - it is immediately clear that it runs 6 times) and fill the array. Like this:
var rand = [];
for(var i = 0; i < 6; i++) {
rand[i] = hexArray[Math.floor(Math.random() * hexArray.length)];
}
Then we could change this...
var array2 = [rand1, rand2, rand3, rand4, rand5, rand6];
...to this:
var array2 = [rand[0], rand[1], rand[2], rand[3], rand[4], rand[5]];
...which doesn't make much sense and can be replaced by another loop:
for(var i = 0; i < rand.length; i++) array2[i] = rand[i];
Now this basically just copies the entire array rand
into array2
, so we can simplify it to:
var array2 = rand;
OK, but now in total it looks like this:
var rand = [];
for(var i = 0; i < 6; i++) {
rand[i] = hexArray[Math.floor(Math.random() * hexArray.length)];
}
var array2 = rand;
It should become clear that we could have used array2
from the beginning, or - what makes more sense because the variable name is more meaningful - we can keep using rand
instead of creating a new array array2
, so we would change this...
var value = "#" + array2.join("");
...to this:
var value = "#" + rand.join("");
As a final improvement, instead of specifying always the array element we want to create in the loop above (rand[i] = ...
), we can just keep pushing into the array (rand.push(...)
) which will automatically create a new element with increasing ID each time:
var rand = [];
for(var i = 0; i < 6; i++) {
rand.push(hexArray[Math.floor(Math.random() * hexArray.length)]);
}
So, at this point, the code looks like this:
var hexArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f'];
var rand = [];
for(var i = 0; i < 6; i++) {
rand.push(hexArray[Math.floor(Math.random() * hexArray.length)]);
}
var value = "#" + rand.join("");
//ignore this
$(document).ready(function() {
$("body").css("background-color", value);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
This should have answered your initial question because it removed the repetition. But wait, there is more we can do!
PART 2:
What you are doing with the hexArray
is actually trying to generate a random hexadecimal digit. Now that I put it into this sentence, it should become clear that actually we can do this in a simpler way: generate a random value and convert it to its hexadecimal representation!
Generating a random value can be done using Math.random()
which will output a random value greater or equal to zero and less than one (e.g. 0.29283543
, 0.91724
, 0.18283123255
, you get the idea).
In order to get a random number between zero and another value, we can multiply the result from Math.random()
with the exclusive maximum, in our case 16.
Math.random() * 16
will give numbers like 3.182372
, 14.2222935
or 5.190023
, which is almost what we want, but we would like to get integral numbers only (without fractional part). We get this by feeding the result into Math.floor
which rounds down, so Math.floor(Math.random() * 16)
would give things like 3
, 14
and 5
which is exactly what we need.
This is basically what you already do, because hexArray.length
is 16
! I just explained it anyway because one may start using the someArray[Math.floor(Math.random() * someArray.length)]
idiom without really understanding how it works.
However, when we just stuff the numbers we get into a string, they will be decimal. We would like them to be in a hexadecimal representation, so that the 14
from the previous example would become e
. This can be done by calling the toString
method on the number, which takes an optional argument specifying the numerical base (for example, var number = 123; console.log(number.toString(16));
would output 7b
).
Therefore, we can get a random hexadecimal digit using:
Math.floor(Math.random() * 16).toString(16)
So let's simplify our previous code once more, and change this...
var hexArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f'];
var rand = [];
for(var i = 0; i < 6; i++) {
rand.push(hexArray[Math.floor(Math.random() * hexArray.length)]);
}
...to this:
var rand = [];
for(var i = 0; i < 6; i++) {
rand.push(Math.floor(Math.random() * 16).toString(16));
}
So the whole code now looks like this:
var rand = [];
for(var i = 0; i < 6; i++) {
rand.push(Math.floor(Math.random() * 16).toString(16));
}
var value = "#" + rand.join("");
//ignore this
$(document).ready(function() {
$("body").css("background-color", value);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
But hey, we're still not finished!
PART 3:
Let's review what the code does at the moment:
#
, to create a CSS color value.Now points 1-3 can be simplified further because at the end, we are basically creating a random 6-digit hexadecimal value!
In step 2, we started using Math.random(Math.floor() * 16).toString(16)
to create a random value between 0 and 16 (exclusively) and convert it to its hexadecimal representation. For clarity, let me change the first 16
here to hex as well (in case you didn't know, hexadecimal literals can be used by prefixing them with 0x
):
Math.random(Math.floor() * 0x10).toString(16)
We can use the same mechanism to create a random value between 0 and 0xFFFFFF (inclusively), i.e. 0 and 0x1000000 (exclusively), like this:
Math.random(Math.floor() * 0x1000000).toString(16)
This will give us random values such as 0x29F3D5 (decimal 2749397), 0xC720FE (decimal 13050110), etc.
This sounds like it's exactly what we need, right? Well, almost. The random values may be smaller than 0x100000 in which case they will have less than 6 digits, e.g. 0xE81C or 0x12345!
This means we have to pad it with zeroes to the left to always have exactly 6 digits, no less. Unfortunately there is no elegent built-in way to do it, so we have to add the zeroes manually if needed. So when the random function gives us the string E81C
, we would have to add two zeroes to make it 00E81C
.
Of course we could calculate the length of the existing string and calculate 6 - length
and then add that many zeroes... but there is a simpler way to do it. Since we know that we will always have at least one digit and maximum 6, and we want always 6, it means we would have to add between 0 and 5 zeroes depending on the existing length. We can now just assume the worst and always add 5 zeroes:
"00000" + Math.floor(Math.random() * 0x1000000).toString(16)
Of course this would mean that the string is now often too long (between 6 and 11 chars), but we can fix this by using only the last 6 characters, which can be done using the string method substr
. Since explaining exactly what different ways there are to call substr
would make this already quite long answer too long, I'll just link to the documentation for this method for further reading and tell you that you can use a negative value as parameter to extract a certain number of characters from the right. For example, "hello".substr(-3)"
will give you llo
.
So, knowing this, we can build code which always gives us 6 random hexadecimal digits again:
("00000" + Math.floor(Math.random() * 0x1000000).toString(16)).substr(-6)
Note that I had to add parens before .substr
because otherwise the substr
method would have been called only on the part right to the +
, not the whole thing.
Let's put this new approach into action. We can replace the whole rand
array and the rand.join("")
part. The code would now look like this:
var value = "#" + ("00000" + Math.floor(Math.random() * 0x1000000).toString(16)).substr(-6);
//ignore this
$(document).ready(function() {
$("body").css("background-color", value);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
And this is how we land at the code suggested by Niet the Dark Absol in the comments!
Update 2020: Nowadays, the substr
trick is no longer needed, since padStart
became standard. So, we could instead use "#" + Math.floor(Math.random() * 0x1000000).toString(16).padStart(6, '0')
.
I know this was a very verbose answer and maybe you already knew most of this, but maybe not. And maybe you or another reader scratch their head how we landed at this result or what other optimization steps there could have been. And that's why I wrote this walkthrough. :)