An overview of the
Functions are one of the most common syntax in JavaScript and can be used to encapsulate common capabilities or business logic.
This article introduces two functions, one corresponding to the maximum balance method and the other asynchronous concurrency limit function.
Maximum balance method
When I was writing pie chart business recently, I found that in the case of taking two decimal places, the pie chart percentage provided by Echarts is exactly 100%, and if I do my own calculation, the probability of rounding up is not 100%. So I looked into Echarts’ method.
Internally, Echarts uses the maximum balance method to calculate. Let’s take a look at its Wiki definition.
The maximum remainder method (also known as the amount system or Hamilton method) is a method of seat allocation in a proportional representation voting system.
Let’s combine the wiki example with a hypothetical election with 100,000 votes and 10 seats allocated. Election results:
Every 10,000 votes can get one seat, the list gets one, the list has been allocated 4, how to allocate the remaining 5? The distribution method of the maximum balance method is based on the order of the balance, so there is (7000), B (6100), E (6000), D (5800) and A (6100), so the distribution is completed.
What does this have to do with our calculation of percentages? Usually we may use rounding to process, so that the sum of percentages is not a percentage. Now we can use the maximum balance method.
Round off:
We’re assuming that the percentage keeps two decimal places, so the thing after two decimal places is the same thing as the balance. We first calculate how many “seats” are left, that is, how far apart the values of the two decimal places are from adding up to 100%. If there is a difference of 0.02%, we divide the 0.02% according to the balance of the items.
Maximum balance method:
We can make an optimization here, we can make sure the minimum unit of seats is 1, and we can enlarge the quota and seats
Now let’s implement it. Let me reorganize my thoughts:
- Calculate the scale of amplification according to the reserved decimal place
- Calculate the percentage of each item and multiply it by 100 *. The sum of the terms should be 100 * proportional (multiplied by 100, because the result is returned as a percentage).
- Calculate the current number of seats and the balance separately, with the integer part being the number of seats currently obtained and the decimal part being the balance.
- Calculate how many seats are left unallocated and allocate according to the maximum balance.
function getPercentWithPrecision(valueList, precision) {
// Zoom in according to reserved decimal places
const digits = Math.pow(10, precision)
const sum = valueList.reduce((total, cur) = > total + cur, 0)
// Calculate the proportion of each item and enlarge it so that the integer part is the number of seats currently obtained and the decimal part is the balance
const votesPerQuota = valueList.map((val) = > {
return val / sum * 100 * digits
})
// The integer part is the first allocation of seats for each category
const seats = votesPerQuota.map((val) = > {
return Math.floor(val);
});
// Calculate the balance of items
const remainder = votesPerQuota.map((val) = > {
return val - Math.floor(val)
})
/ / the total seats
const totalSeats = 100 * digits
// The total number of seats currently allocated
let currentSeats = votesPerQuota.reduce((total, cur) = > total + Math.floor(cur), 0)
// Based on the maximum balance method
while(totalSeats - currentSeats > 0) {
let maxIdx = -1 // The id with the largest remainder
let maxValue = Number.NEGATIVE_INFINITY // Maximum balance, initially reset to infinitesimal
// Select the maximum value of this set of balance data
for(var i = 0; i < remainder.length; i++) {
if (maxValue < remainder[i]) {
maxValue = remainder[i]
maxIdx = i
}
}
// Add 1 to the corresponding item, clear the balance, and add 1 to the current allocation
seats[maxIdx]++
remainder[maxIdx] = 0
currentSeats++
}
return seats.map((val) = > `${val / totalSeats * 100}% `)}Copy the code
So now that we have the maximum balance method in place, if you’re interested in echarts, the corresponding method getPercentWithPrecision is pretty much the same.
Asynchronous concurrency limit
Limiting asynchronous concurrency is also a very common feature. If you send a bunch of requests at the same time, the network may be tied up with a bunch of requests, and other requests will fall behind, so we want to limit the number of concurrent requests.
Let’s look directly at the implementation of async-pool-Tiny
async function asyncPool(poolLimit, iterable, iteratorFn) {
// Used to store all asynchronous requests
const ret = [];
// The user saves the request in progress
const executing = new Set(a);for (const item of iterable) {
// Construct the request Promise
const p = Promise.resolve().then(() = > iteratorFn(item, iterable));
ret.push(p);
executing.add(p);
// The request is removed from the array in progress after execution
const clean = () = > executing.delete(p);
p.then(clean).catch(clean);
// If the number of requests in progress is greater than the number of concurrent requests, use promise.race to wait for a request to complete as soon as possible
if (executing.size >= poolLimit) {
await Promise.race(executing); }}// Return all results
return Promise.all(ret);
}
// How to use it
const timeout = i= > new Promise(resolve= > setTimeout(() = > resolve(i), i));
asyncPool(2[1000.5000.3000.2000], timeout).then(results= > {
console.log(results)
})
Copy the code
The core implementation uses promise.race and promise.all. If the number of requests issued is equal to the number of concurrent requests, the next request is not made, but the promise.race is used to wait for a request to end as soon as possible, and then continue making requests. Finally, return all endings with promise.all.
conclusion
The maximum balance method and asyncPool implementations are both clever, elegant and well worth learning. Everybody has no useful function to recommend once, learn from each other.