Normalize numbers for stats - javascript

I'm trying to make a simple daily follower stats like the screenshot below.
And here's the code I'm using.
const normalize = ({ min, max, number }) => {
return parseFloat((((number - min) / (max - min)) * 100).toFixed(2))
}
const followers = {
'03-23-2022': 2115,
'03-24-2022': 2128,
'03-25-2022': 2175,
'03-26-2022': 2195,
'03-27-2022': 2215,
'03-28-2022': 2206,
'03-29-2022': 2258
}
const numbers = []
for (const day in followers) {
const stats = followers[day]
numbers.push(stats)
}
const min = Math.min.apply(null, numbers)
const max = Math.max.apply(null, numbers)
for (const day in followers) {
const activity = followers[day]
const percent = normalize({ min, max, number: activity })
console.log({ day, activity, percent })
}
As you can see on the console log the normalize function returns 0 for the minimum number and that's why nothing is visible on the site.
What I'd like to do is I want to get some meaningful numbers from the normalize function to be able to show the stats in a more readable style. How do I do that?

Related

How to cut range to let them have about same number of objects?

For example, there are 1000 products unevenly distributed in $100~$150. And I want to cut let every price range has about same number of products.
My thought is cut it half and half recursively, something like
function cut(min, max, maxNum, result = []){
if (number of min~max > maxNum) {
mid = (max+min)/2
cut(min, mid, maxNum)
cut(mid+1, max, maxNum)
} else {
result.push(`${min}~${max}`)
}
}
It works fine but the result may include the range which has much smaller than maxNum of product.
For example, $100~$112 have only 5 product while maxNum=200.
Is there any idea or better solution?
I'd suggest sorting the products by price, then dividing into N ranges using Array.slice().
Once we have the ranges we can output the count, min price, max price etc for each range:
const totalCount = 1000;
const maxNum = 200;
const minPrice = 100;
const maxPrice = 150;
// How many ranges to divide into
const rangeCount = totalCount / maxNum;
const rangeSize = totalCount / rangeCount;
function getRandomPrice() {
return Math.floor(Math.random() * (1 + maxPrice - minPrice)) + minPrice;
}
const products = Array.from({ length: totalCount }, (v, k) => ({ id: k + 1, name: `product ${k + 1}`, price: getRandomPrice() }));
// Sort our products by price...
const sortedProducts = products.sort(( a, b ) => a.price - b.price);
const ranges = Array.from({ length: rangeCount }, (v, k) => sortedProducts.slice(k * rangeSize, (k + 1) * rangeSize));
console.log('Ranges:');
for(let range of ranges) {
console.log(`Count: ${range.length} Price: $${range[0].price} -> $${range[range.length - 1].price} `)
}
.as-console-wrapper { max-height: 100% !important; }

Javascript - Generate random small intervals in a range

I am trying to generate random intervals between the range: 2,5k - 10M.
Currently, I am doing the following:
const MIN_NUMBER = 2500;
const MAX_NUMBER = 10000000;
const random = (min, max, floating = false) => {
const result = Math.random() * max + min;
return floating ? result : Math.floor(result);
};
const min = random(MIN_NUMBER, MAX_NUMBER / 10);
const max = random(min, min * 10);
const interval = `[${min}, ${max}]`;
console.log(interval);
But as you can see, the probability that the generated interval is small/medium is not very high.
I want to get random intervals like:
[2500, 10400]
[2500, 9919]
[3000000, 3301029]
[500000, 611223]
I am not following any specific rule, but as you can see, in relation with
[2500, 400000]
[2500, 71000]
[3000000, 10000000]
[500000, 3120000]
they are considered "small/medium", because there is not a "really huge" diff between the max and the min).
With my current algorithm, you can check that the generated average diff is high:
const MIN_NUMBER_OF_LIKES = 2500;
const MAX_NUMBER_OF_LIKES = 10000000;
const random = (min, max, floating = false) => {
const result = Math.random() * max + min;
return floating ? result : Math.floor(result);
};
let averageDiff = 0;
const numIterations = 1000;
for (let i = 0; i < numIterations; i++) {
const min = random(MIN_NUMBER_OF_LIKES, MAX_NUMBER_OF_LIKES / 10);
const max = random(min, min * 10);
averageDiff += max - min;
}
averageDiff /= numIterations;
console.log({ averageDiff });
How can I do for getting random small segments instead?
--Note: the difference between the randomly generated intervals is random too, but it has to be "small/medium" (not as huge as with my current solution).
What about first choosing random size of interval within the size you wish - so you get the value of INTERVAL_SIZE. As second step you randomly find the minimum between MIN_NUMBER_OF_LIKES and MAX_NUMBER_OF_LIKES - INTERVAL so you get STARTPOINT.
So final INTERVAL will have STARTPOINT and ENDPOINT = STARTPOINT + INTERVAL_SIZE
Based on #krnz solution:
const MIN = 2500;
const MAX = 10000000;
const random = (min, max, floating = false) => {
const result = Math.random() * max + min;
return floating ? result : Math.floor(result);
};
function generateRandomSmallInterval() {
const intervalSize = random(1000, 10000);
const start = random(MIN, MAX-intervalSize);
const end = start + intervalSize;
return {start, end};
}
const interval = generateRandomSmallInterval();
console.log({ interval });
console.log(`Diff: ${interval.end - interval.start}`);
Refactored and generalized code using lodash:
import { random } from 'lodash';
function generateRandomIntervalInRange(min, max, maxIntervalSize = max - min) {
if (max - min <= 0) {
throw new Error("The `max` argument must be greater than `min`.");
}
if (maxIntervalSize <= 0) {
throw new Error("The maximum interval size must be greater than 0.");
}
if (maxIntervalSize > max - min) {
throw new Error(
`The maximum interval size mustn't be greater than ${max - min}.`
);
}
const intervalSize = random(1, maxIntervalSize);
const start = random(min, max - intervalSize);
const end = start + intervalSize;
return { start, end };
}
//
// MAIN
//
const MIN = 1;
const MAX = 10;
const MAX_INTERVAL_SIZE = 5;
console.log(generateRandomIntervalInRange(MIN, MAX, MAX_INTERVAL_SIZE));

Getting infinity loop while checking factors of a number in while loop

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;
}

Compounding interest monthly with a deposit

I want to compound interest on a weekly/fortnightly/monthly/annual basis.
I also want an option to have a deposit amount that can be added in.
I have already tried the standard formula of calculating the final amount accrued, as seen here:
(source: gstatic.com)
For example here is my method for calculating the interest compounding weekly:
function calculateWeekly(state: any) {
const { savings, deposit ,interest, timePeriodSelector, timePeriodLength } = state;
let numberOfYears = 0;
if (timePeriodSelector === "weekly") {
numberOfYears = timePeriodLength / weeksInAYear;
} else if (timePeriodSelector === "fortnightly") {
numberOfYears = (timePeriodLength / weeksInAYear) * 2;
} else if (timePeriodSelector === "monthly") {
numberOfYears = (timePeriodLength / weeksInAYear) * weeksInAMonth;
} else if (timePeriodSelector === "annually") {
numberOfYears = (timePeriodLength / weeksInAYear) * weeksInAYear;
}
const weeklyRate = interest / 100 / weeksInAYear;
const lengthOfCompunding = numberOfYears * weeksInAYear;
let startingFigure = parseInt(savings) + parseInt(deposit);
//total gets added on for every time cycle of week
let total =
(startingFigure * (Math.pow(1 + weeklyRate, lengthOfCompunding) - 1)) / weeklyRate;
return roundToTwoDP(total);
}
The issue with the above code is that the deposit gets added into the calculation every time the interest accrues. So a deposit of $10 weekly for 10 weeks will actually get added up to $100.
I attempted a method to accrue the interest using a loop for each week here:
// loops how many times to compound the interest
for(let i = numberOfYears - (1/weeksInAYear); i > 0; i-= (1/weeksInAYear)){
let interestGained = (total * (Math.pow((1 + weeklyRate), lengthOfCompunding))) - total;
total += interestGained + savings;
}
Thanks for any help!
This should do what you want:
const range = (min, max) => {
const size = 1 + max - min
return [...Array(size).keys()].map(n => n + min)
}
const weeksInAYear = 52
const addWeeklyInterest = interestRatePerWeek => (savings, _) => savings + savings * interestRatePerWeek
const calculateTotal = (savings, numberOfYears, interestRatePerWeek) => {
const numberOfWeeks = numberOfYears * weeksInAYear
return range(1, numberOfWeeks).reduce(addWeeklyInterest(interestRatePerWeek), savings)
}
console.log(calculateTotal(1000.00, 1, 0.02))
Output is 2800.328185448178. You might want to round that for display purposes, but also keep in mind that if accuracy is important, you can't use floating-point numbers.

Sized array of random unique numbers

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>

Categories