I want to know how to convert a one-dimensional array into a frequency dictionary with the elements of the array as keys, and their occurrences as values, in JavaScript.
For example, the following Python script creates a list of 1024 random numbers between 0 and 255 and counts the elements as described:
import random
from collections import Counter
sorted(Counter(random.randbytes(1024)).items(), key=lambda x: -x[1])
I can do the same in JavaScript, but much less concise:
var numbers = Array.from({length: 1024}, () => Math.floor(Math.random() * 256))
var counter = Object()
for (let number of numbers) {
if (counter.hasOwnProperty(number)) {counter[number] += 1}
else {counter[number] = 1}
}
Object.entries(counter).sort(([,a],[,b]) => b-a)
How do I make it more concise?
This surprised me: on my Mac, the Map version took 2.7s, and the Object version took 0.6s when testing with 100 million numbers.
(Your original version takes the same time as the Object version in the code below)
const numbers = Array.from({length:10**8},()=>Math.random()*256|0)
let timerStart = new Date()
{
let counter = new Map()
numbers.forEach(n=>counter.set(n,(counter.get(n)??0)+1))
counter = new Map([...counter].sort(([,a],[,b]) => b-a))
}
console.log(`Map version took ${new Date()-timerStart} ms`)
timerStart = new Date()
{
let counter = numbers.reduce((a,c)=>(a[c]=(a[c]??0)+1,a),{})
Object.entries(counter).sort(([,a],[,b]) => b-a)
}
console.log(`Object version took ${new Date()-timerStart} ms`)
Here's an ES6 one-liner to solve the problem, using Array.reduce and the fact that the value of a comma separated list of expressions is the value of the last one:
const numbers = Array.from({length: 1024}, () => Math.floor(Math.random() * 256))
const counter = numbers.reduce((acc, num) => (acc[num] = (acc[num] || 0) + 1, acc), {})
const sorted = Object.entries(counter).sort(([,a],[,b]) => b-a)
console.log(sorted)
You could try using a Map:
var numbers = Array.from({length: 1024}, () => Math.floor(Math.random() * 256))
const map = numbers.reduce((accum, d) => accum.set(d, (accum.get(d) || 0) + 1), new Map());
A quick "improvement" would be to use the ternary statement to make the code more concise.
var numbers = Array.from({length: 1024}, () => Math.floor(Math.random() * 256))
var counter = Object()
for (let number of numbers) {
//If counter has prop number, increment it by one otherwise set to 1
counter.hasOwnProperty(number) ? counter[number]++ : counter[number] = 1;
//Alternatively
//counter[number] = counter.hasOwnProperty(number) ? counter[number] + 1 : 1
}
Object.entries(counter).sort(([,a],[,b]) => b-a)
Related
I create an array of random and unique numbers from 1 to 51 //[0,1,2,3,4...,51]
i want to take 5 random numbers from that array EXAMPLE //[3,5,27,31,44]
Now i wont to delete that numbers from the principal array so the decrease of array decrease of 5
This is the code i created is:
const filterRandomValue = function (deck) {
const randomNumbers = deck
.sort(() => Math.random() - Math.random())
.slice(0, 5);
return deck.filter((val) => !randomNumbers.includes(val));
};
filterRandomValue(randomUniqueNum(51, 51)) // it returns an array of 46 elements and just for one times it works
I create a button and each time i click it, i wont to decrease the numbers until it will reach a number at least equal of 5 or >= 5, and i don't know how to do it.
Because if i run the functions again it will create a full (51 elements) array.
Anyone can help me??
The issue can be fixed by separating the creation from the editing. Create the array and store it in a block variable. Then you can access that array in your function everytime the button is clicked.
const filterRandomValue = function(deck) {
const randomNumbers = deck.sort(() => Math.random() - Math.random()).slice(0, 5);
return deck.filter((val) => !randomNumbers.includes(val)).sort( (a,b) => +a - +b);
};
const randomUniqueNum = () => {
let x = 0,
d = [];
while (++x < 51) d.push(x)
return d
}
let array = randomUniqueNum()
window.addEventListener('load', () => {
document.querySelector('.extract').addEventListener('click', () => {
array = filterRandomValue(array)
display()
})
display()
})
const display = () => {
document.querySelector('.numbers').innerText = array.join(', ')
document.querySelector('.n').innerText = array.length
}
.numbers{
width:400px;
height:100px;
}
<textarea class='numbers'></textarea>
<p>#array items: <span class='n'></span>
<button class='extract'>extract</button>
Instead of putting randomUniqueNum straight into your filterRandomValue function, try creating an array with randomUniqueNum and then using that.
let myArr = randomUniqueNum(51, 51)
.sort(() => Math.random() - Math.random())
.slice(0, 5);
filterRandomValue(myArr); // array length: 46
filterRandomValue(myArr); // array length: 41
The error is that you are generating a new array each time you call randomUniqueNum.
Though your question is now answered, I think there are some fundamental problems with your code.
The randomUniqueNum function is pointless if you're using 51 unique numbers in a length 51 array. By that logic, the array is just the numbers 1 to 51, or 0 to 50, depending on where you start.
I think you should take a look at the steps you're taking and work out what parts are necessary.
How to get all possible variations of terms to add up to sum, in a given amount of piles, using javascript?
Let's say I have a sum of 10 and I want to split this into 4 piles with positive terms and zeros only.
function getCombinations(sum, piles){
...
}
getCombinations(10,4);
Returns something like this in a two dimensional array:
[
[3,3,3,1],
[3,3,1,3],
[7,1,1,1],
[10,0,0,0],
...
]
It's not mandatory to return [3,3,3,1] and [3,3,1,3] as different solutions, fastest way will do. I will only work with small numbers, max sum will probably be 10.
It's a variation of the Count the coins problem, http://rosettacode.org/wiki/Count_the_coins, but I want the solutions returned, I have a given set of piles and I use all positive terms (and zero) not only specific coin values.
this should do the trick:
const matrix = (num, cols) => {
const matrix = [[num, ...[...Array(cols-1)].map(() => 0)]];
const hashes = new Set;
const coef = Math.pow(10, cols-1);
let digits = 10 * coef - 1;
while (digits-- >= coef) {
const nums = ('' + digits).split('').map(d => +d);
const hash = nums.sort((a, b) => b - a).join('');
if (hashes.has(hash) || nums.reduce((a, b) => a + b, 0) !== num)
continue;
hashes.add(hash);
matrix.push(nums);
}
return matrix;
};
console.log(matrix(10, 4));
Here is a pratical way of doing this:
function getCombinations(sum, piles){
var array =[];
for(var i = 1; i <= piles; i ++) {
var subArray = Array.apply(null, Array(4)).map(
function () {
return Math.floor(Math.random() * sum)});
array.push(subArray);
}
return array;
}
var result = getCombinations(10,4);
I am struggling with infinite loop problem while Array exercise implementation which needs to be done with Java Script functional way:
I have a code which creates an array and fills its values with numbers which fulfil condition:
Each array element has a value,
which we draw from the range <100, 200> until the sum of digits is
a number having exactly two dividers, not counting 1 and this one
numbers.
I have a code like below:
const generateNumber = (min, max) =>
Math.floor(Math.random() * (max - min + 1)) + Math.floor(min);
const unities = number => number % 10;
const hundreds = number => Math.floor((number % 1000) / 100);
const tens = number => Math.floor((number % 100) / 10);
const sumDigits = (number) => unities(number) + hundreds(number) + tens(number);
const countNumberFactors = number => Array
.from(Array(number + 1), (_, i) => i)
.filter(i => number % i === 0)
.slice(1, -1)
.length;
const generateNumberUntilConditionNotAchieve = (min, max) => {
let number = generateNumber(min, max);
const digitsSum = sumDigits(number);
while (countNumberFactors(digitsSum) === 2) {
number = generateNumber(min, max)
}
return number;
}
const generateArray = (minArrSize, maxArrSize, minItemValue, maxItemValue) =>
Array(generateNumber(minArrSize, maxArrSize))
.fill(0)
.map(
() => generateNumberUntilConditionNotAchieve(minItemValue,
maxItemValue));
const main = () => {
const generatedArray = generateArray(1, 5, 100, 200);
console.log("Array -> " + generatedArray);
}
main();
For small minArraySize and maxArraySize values sometimes I am receiving desirable result but for params like <10, 100> my IDE is freezing. On online editor with pasted above code, I am receiving information about the infinite loop on line:
while (countNumberFactors(digitsSum) === 2)
I tried to investigate a root cause by trial and error but I did not find out a solution. I will be grateful for suggestions on how to solve the above infinite loop problem.
You are changing number but checking digitsSum. All you need to do to fix this is add digitsSum = sumDigits(number) in the while loop. e.g.
const generateNumberUntilConditionNotAchieve = (min, max) => {
let number = generateNumber(min, max);
const digitsSum = sumDigits(number);
while (countNumberFactors(digitsSum) === 2) {
number = generateNumber(min, max);
digitsSum = sumDigits(number);
}
return number;
}
How to generate a random string containing only hex characters (0123456789abcdef) of a given length?
Short alternative using spread operator and .map()
Demo 1
const genRanHex = size => [...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join('');
console.log(genRanHex(6));
console.log(genRanHex(12));
console.log(genRanHex(3));
Pass in a number (size) for the length of the returned string.
Define an empty array (result) and an array of strings in the range of [0-9] and [a-f] (hexRef).
On each iteration of a for loop, generate a random number 0 to 15 and use it as the index of the value from the array of strings from step 2 (hexRef) -- then push() the value to the empty array from step 2 (result).
Return the array (result) as a join('')ed string.
Demo 2
const getRanHex = size => {
let result = [];
let hexRef = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
for (let n = 0; n < size; n++) {
result.push(hexRef[Math.floor(Math.random() * 16)]);
}
return result.join('');
}
console.log(getRanHex(6));
console.log(getRanHex(12));
console.log(getRanHex(3));
NodeJS Users
You can use randomBytes available in the crypto module, to generate cryptographically strong pseudorandom data of a given size. And you can easily convert it to hex.
import crypto from "crypto";
const randomString = crypto.randomBytes(8).toString("hex");
console.log(randomString) // ee48d32e6c724c4d
The above code snippet generates a random 8-bytes hex number, you can manipulate the length as you wish.
There are a few ways. One way is to just pull from a predefined string:
function genHexString(len) {
const hex = '0123456789ABCDEF';
let output = '';
for (let i = 0; i < len; ++i) {
output += hex.charAt(Math.floor(Math.random() * hex.length));
}
return output;
}
The other way is to append a random number between 0 and 15 converted to hex with toString:
function genHexString(len) {
let output = '';
for (let i = 0; i < len; ++i) {
output += (Math.floor(Math.random() * 16)).toString(16);
}
return output;
}
This securely generates a 32-byte random string and encodes it as hex (64 characters).
Array.from(crypto.getRandomValues(new Uint8Array(32)))
.map(b => b.toString(16).padStart(2, '0')).join('');
Long version:
function generateRandomHexString(numBytes) {
const bytes = crypto.getRandomValues(new Uint8Array(numBytes));
const array = Array.from(bytes);
const hexPairs = array.map(b => b.toString(16).padStart(2, '0'));
return hexPairs.join('')
}
If you can use lodash library here is the code snippet to generate a 16 chars string:
let randomString = _.times(16, () => (Math.random()*0xF<<0).toString(16)).join('');
You can use a hexa number 0xfffff to random a hex string
getHexaNumb() {
return Math.floor(Math.random() * 0xffffff).toString(16).padEnd(6, "0")
}
Length of the array is the length of the random string.
const randomHex = Array.from({ length: 32 }, () => "0123456789ABCDEF".charAt(Math.floor(Math.random() * 16))).join('');
console.log(randomHex);
Here's a version that avoids building one digit at a time; it's probably only suitable for short lengths.
function genHexString(len) {
const str = Math.floor(Math.random() * Math.pow(16, len)).toString(16);
return "0".repeat(len - str.length) + str;
}
This works for lengths up to 13:
randomHex = length => (
'0'.repeat(length)
+ Math.floor((Math.random() * 16 ** length))
.toString(16)
).slice(-length);
console.log(randomHex(4));
console.log(randomHex(6));
console.log(randomHex(13));
console.log(randomHex(20));
Up to 7 characters may be quickly taken from one Math.random() call (A):
const halfBytesIn35 = 7 // = 3.5 bytes
const byte35 = Math.pow(16, halfBytesIn35)
const bytes35 = () => ((Math.random() * byte35) | 0).toString(16).padStart(halfBytesIn35,'0')
console.log('A: ' + bytes35())
const bytes65 = len => Math.floor(Math.random() * Math.pow(16, len*2)).toString(16).padStart(len,'0')
console.log('B: ' + bytes65(6))
function moreBytes (len) {
len *= 2; // alternative: len <<= 1 if you do not use half bytes. This might allow optimizations based on len always being an Integer then.
let builder = "";
while (len > 0) {
builder += bytes35()
len -= 7
}
return builder.slice(0,len)
}
console.log('C: ' + moreBytes(16))
Store the Math.pow constant if you plan to use this with high frequency.
An 8th letter overflows into the sign bit in the binary floor.
You can reach up to 13 characters from one call by using Math.floor instead (B) or even loop the generator for an arbitrary length (C).
Note that this could be used to define premature optimization. If your bottleneck really is the creation of Random Numbers consider using LUTs. This is common if you are developing for embedded. (And in this case somehow got stuck using javascript, but do not have the timebuget to generate random Numbers)
Using for Loop, charAt and Math.random
let result = "";
let hexChar = "0123456789abcdef";
for (var i = 0; i < 6; i++) {
result += hexChar.charAt(Math.floor(Math.random() * hexChar.length));
}
console.log(`#${result}`);
Using Math.random, you can do 13 characters at a time in a convenient way. If you want an arbitrary length string, you can still do it with a "one-liner":
const makeRandomHexString = (length: number) =>
Array.from({ length: Math.ceil(length / 13) })
.map(() =>
Math.floor(Math.random() * (Number.MAX_SAFE_INTEGER / 2))
.toString(16)
.padStart(13, '0')
)
.join('')
.substring(0, length);
Here is a simplified program to generate random hexadecimal Colour code:
let items = ["a", "b", "c", "d", "e", "f"];
let item = items[Math.floor(Math.random() * items.length)];
console.log(item);
let random = Math.random().toString().slice(2, 6);
console.log(`#${item}${random}${item}`);
let generateMacAdd = (function () {
let hexas = '0123456789ABCDEF'
let storeMac = []
let i = 0
do {
let random1st = hexas.charAt(Math.floor(Math.random() * hexas.length))
let random2nd = hexas.charAt(Math.floor(Math.random() * hexas.length))
storeMac.push(random1st + random2nd)
i++
} while (i <= 6)
return storeMac.join(':')
})()
console.log(generateMacAdd); //will generate a formatted mac address
I use self invoking function here so you can just call the variable
w/o any arguments
I also use do while here, just for my own convenience, you can do
any kind of loop depends what you're comfortable
I was wondering what was the most concise way to get an array of a certain size, of unique random numbers.
I get random numbers like this:
times(4, () => random(30, 95));
However this is not unique. I can filter this with uniq but I need to gurantee length of 4 of array. And I want to do it the lodash way. Any ideas?
Much easiear would be...
const uniqRandomNumbers = _.sampleSize(_.range(30, 95), 4);
console.log(uniqRandomNumbers);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>
I know this isn't "the lodash way", but it guarantees uniqueness, and allows you to use the same arguments as you were using before. It also scales better than methods that require a binary or linear search through an array, as set.has() is O(1) on average, rather than O(log(n)) or O(n).
function uniqRandom (times, ...args) {
const set = new Set()
while (times > 0) {
const rand = _.random(...args)
if (!set.has(rand)) {
set.add(rand)
times--
}
}
return Array.from(set)
}
console.log(uniqRandom(4, 30, 33));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>
I solved it using from a functional programming perspective. refillRandom is a recursive function that checks the number of items left to generate and calls itself again until the are the required number of items.
It also throws an Error when is imposible to generate the sequence, if the distance between min and max random number is greater than the required unique items. It's better to throw an Error than waiting forever.
const generator = (min, offset) => () =>
Math.floor(Math.random() * offset + min);
const refillRandom = (list, min, max, times) => {
const offset = max - min,
num = times - list.length;
if (times > offset) {
throw new Error("Imposible to generate it");
}
const result = _.uniq(_.times(num, generator(min,offset)));
if (result.length < num) {
return result.concat(
refillRandom(list, min, max, num - result.length)
);
}
return result;
}
const r = refillRandom([], 30, 95, 4);
console.log(r);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>
EDIT: I found another solution, I mantain an ordered array of generated numbers and increment the generated number so it get mapped to a number that has not been generated yet. This way I only call random the times specified.
const randomGenerator = (min, offset, generated, times) => {
if (!times || !offset) return generated;
var number = Math.floor(Math.random() * offset + min);
const len = generated.length;
for (var i = 0; i < len; i++) {
if (generated[i] <= number) {
number++;
} else {
generated.splice(i, 0, number);
return randomGenerator(min, offset - 1, generated, times - 1);
}
}
generated[i] = number;
return randomGenerator(min, offset - 1, generated, times - 1);
};
const r = randomGenerator(30, 95 - 30, [], 12);
console.log(r);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>