I have an array of people
const family = [{name: 'Mike', age: 1}, {name: 'Monique', age: 99}]
family.map(member => ??)
the desired output is
Mike ......... 1
Monique ..... 99
the number of . is different betwen line 1 an 2. can you help me think about this? thanks!
const maxLength = 100;
family.map(member => {
let line = new Array(maxLength - (member.name + member.age).length).fill('.');
console.log(member.name + line.join('') + member.age);
})
goodluck
#params: objArray [Array of Objects]
lineSize [Number] length of each output line
.map() each Object
get the length of name value
get the length of score after its converted to String
subtract the total of both lengths and two spaces from the given lineSize
fill an Array of dots equal to the difference from previous step and then .join('') them into a String.
return each line as: obj.name .... obj.score into an Array
Create a documentFragment and a <ul> and ensure that a monospaced font is applied to <ul>. Monospaced fonts will make the list perfectly even.
.forEach() String of the previous Array:
create a <li>
add a line to <li>
append <li> to <ul>
Append <ul> to documentFragment then the documentFragment to <body>
Demo
const scores = [{
name: 'Mike',
score: 1
}, {
name: 'Monique',
score: 99
}, {
name: 'Matt',
score: 5150
}, {
name: 'Lynda',
score: 2112
}];
/* Step 1 */
const memberList = (objArray, lineSize) => {
/* Step 2 */
let scoreArray = objArray.map(obj => {
let nameSize = obj['name'].length;
let scoreSize = obj['score'].toString().length;
let subTotal = lineSize - (nameSize + scoreSize);
let delimiters = ` ${Array(subTotal).fill('.').join('')} `;
return `${obj.name}${delimiters}${obj.score}`;
});
/* Step 3 */
const docFrag = document.createDocumentFragment();
const list = document.createElement('ul');
list.style.cssText = `font: 400 3vw/1.5 Consolas;list-style: none;`
/* Step 4 */
scoreArray.forEach(line => {
const item = document.createElement('li');
item.textContent = line;
list.appendChild(item);
});
/* Step 5 */
docFrag.appendChild(list);
document.body.appendChild(docFrag);
}
memberList(scores, 20);
based on #tdjprog approach I wrote this
const separateWords = (string1, string2, length = 40) => {
const times = length - (string1 + string2).length;
return string1 + " " + ".".repeat(times) + " " + string2;
};
hope it helps someone. cheers
Related
I have a string that (potentially) contains HTML tags.
I want to split it into smaller valid HTML strings based on (text) character length. The use case is essentially pagination. I know the length of text that can fit on a single page. So I want to divide the target string into "chunks" or pages based on that character length. But I need each of the resulting pages to contain valid HTML without unclosed tags, etc.
So for example:
const pageCharacterSize = 10
const testString = 'some <strong>text with HTML</strong> tags
function paginate(string, pageSize) { //#TODO }
const pages = paginate(testString, pageCharacterSize)
console.log(pages)
// ['some <strong>text </strong>', '<strong>with HTML</strong> ', 'tags']
I think this is possible to do with a DocumentFragment or Range but I can't figure out how slice the pages based on character offsets.
This MDN page has a demo that does something close to what I need. But it uses caretPositionFromPoint() which takes X, Y coordinates as arguments.
Update
For the purposes of clarity, here are the tests I'm working with:
import { expect, test } from 'vitest'
import paginate from './paginate'
// 1
test('it should chunk plain text', () => {
// a
const testString = 'aa bb cc dd ee';
const expected = ['aa', 'bb', 'cc', 'dd', 'ee']
expect(paginate(testString, 2)).toStrictEqual(expected)
// b
const testString2 = 'a a b b c c';
const expected2 = ['a a', 'b b', 'c c']
expect(paginate(testString2, 3)).toStrictEqual(expected2)
// c
const testString3 = 'aa aa bb bb cc cc';
const expected3 = ['aa aa', 'bb bb', 'cc cc']
expect(paginate(testString3, 5)).toStrictEqual(expected3)
// d
const testString4 = 'aa bb cc';
const expected4 = ['aa', 'bb', 'cc']
expect(paginate(testString4, 4)).toStrictEqual(expected4)
// e
const testString5 = 'a b c d e f g';
const expected5 = ['a b c', 'd e f', 'g']
expect(paginate(testString5, 5)).toStrictEqual(expected5)
// f
const testString6 = 'aa bb cc';
const expected6 = ['aa bb', 'cc']
expect(paginate(testString6, 7)).toStrictEqual(expected6)
})
// 2
test('it should chunk an HTML string without stranding tags', () => {
const testString = 'aa <strong>bb</strong> <em>cc dd</em>';
const expected = ['aa', '<strong>bb</strong>', '<em>cc</em>', '<em>dd</em>']
expect(paginate(testString, 3)).toStrictEqual(expected)
})
// 3
test('it should handle tags that straddle pages', () => {
const testString = '<strong>aa bb cc</strong>';
const expected = ['<strong>aa</strong>', '<strong>bb</strong>', '<strong>cc</strong>']
expect(paginate(testString, 2)).toStrictEqual(expected)
})
Here is a solution that assumes and supports the following:
tags without attributes (you could tweak the regex to support that)
well formed tags assumed, e.g. not: <b><i>wrong nesting</b></i>, missing <b>end tag, missing start</b> tag
tags may be nested
tags are removed & later restored for proper characters per page count
page split is done by looking backwards for first space
function paginate(html, pageSize) {
let splitRegex = new RegExp('\\s*[\\s\\S]{1,' + pageSize + '}(?!\\S)', 'g');
let tagsInfo = []; // saved tags
let tagOffset = 0; // running offset of tag in plain text
let pageOffset = 0; // page offset in plain text
let openTags = []; // open tags carried over to next page
let pages = html.replace(/<\/?[a-z][a-z0-9]*>/gi, (tag, pos) => {
let obj = { tag: tag, pos: pos - tagOffset };
tagsInfo.push(obj);
tagOffset += tag.length;
return '';
}).match(splitRegex).map(page => {
let nextOffset = pageOffset + page.length;
let prefix = openTags.join('');
tagsInfo.slice().reverse().forEach(obj => {
if(obj.pos >= pageOffset && obj.pos < nextOffset) {
// restore tags in reverse order to maintain proper position
page = page.substring(0, obj.pos - pageOffset) + obj.tag + page.substring(obj.pos - pageOffset);
}
});
tagsInfo.forEach(obj => {
let tag = obj.tag;
if(obj.pos >= pageOffset && obj.pos < nextOffset) {
if(tag.match(/<\//)) {
// remove tag from openTags list
tag = tag.replace(/<\//, '<');
let index = openTags.indexOf(tag);
if(index >= 0) {
openTags.splice(index, 1);
}
} else {
// add tag to openTags list
openTags.push(tag);
}
}
});
pageOffset = nextOffset;
let postfix = openTags.slice().reverse().map(tag => tag.replace(/</, '</')).join('');
page = prefix + page.trim() + postfix;
return page.replace(/<(\w+)><\/\1>/g, ''); // remove tags with empty content
});
return pages;
}
[
{ str: 'some <strong>text <i>with</i> HTML</strong> tags, and <i>some <b>nested tags</b> sould be <b>supported</b> as well</i>.', size: 16 },
{ str: 'a a b b c c', size: 3 },
{ str: 'aa aa bb bb cc cc', size: 5 },
{ str: 'aa bb cc', size: 4 },
{ str: 'aa <strong>bb</strong> <em>cc dd</em>', size: 3 },
{ str: '<strong>aa bb cc</strong>', size: 2 }
].forEach(o => {
let pages = paginate(o.str, o.size);
console.log(pages);
});
Output:
[
"some <strong>text <i>with</i></strong>",
"<strong> HTML</strong> tags, and",
"<i>some <b>nested tags</b></i>",
"<i> sould be</i>",
"<i><b>supported</b> as</i>",
"<i>well</i>."
]
[
"a a",
"b b",
"c c"
]
[
"aa aa",
"bb bb",
"cc cc"
]
[
"aa",
"bb",
"cc"
]
[
"aa",
"<strong>bb</strong>",
" <em>cc</em>",
"<em>dd</em>"
]
[
"<strong>aa</strong>",
"<strong>bb</strong>",
"<strong>cc</strong>"
]
Update
Based on new request in comment I fixed the split regex from '[\\s\\S]{1,' + pageSize + '}(?!\\S)' to '\\s*[\\s\\S]{1,' + pageSize + '}(?!\\S)', e.g. added \\s* to catch leading spaces. I also added a page.trim() to remove leading spaces. Finally I added a few of the OP examples.
My problem is that I want to get the total sum of SplitValue that has the SplitType of RATIO and use it inside the FOR loop to calculate the ratio of split. i.e. 3 + 2 = 5.
The comments I made in each line of my code below explain my troubles, I'm getting a loop of 3 and 5 instead of just 5. Thanks
var details = {
"Amount": 1396.8000000000002,
"SplitInfo": [{
"SplitType": "RATIO",
"SplitValue": 3
},
{
"SplitType": "RATIO",
"SplitValue": 2
}
]
};
var conRatioSumArr = 0;
var balance = details.Amount;
for (var i = 0; i < details.SplitInfo.length; i++) {
// I want to Get the total sum of SplitValue that has the SplitType of RATIO
// 3 + 2 = 5
// Here is what I've tried
splitTypeArr = details.SplitInfo[i].SplitType;
splitValueArr = details.SplitInfo[i].SplitValue;
if (splitTypeArr === "RATIO") {
conRatioSumArr += splitValueArr;
console.log(conRatioSumArr); // This gives me a loop of 3 & 5. I only need the total value which is 5 instead of both 3 and 5. Note that if I put this outside the for loop, I get only the total which is 5, but I want to use the sum inside of this for loop not outside the for loop to enable me calculate the ratio below.
splitAmount = (balance * (splitValueArr / 5)); // The total above is expected to replace the 5 here, but I'm getting a loop of 3 & 5 instead.
// Like this
// splitAmount = (balance * (splitValueArr / conRatioSumArr));
// splitAmount is expected to give: 838.08 and 558.7200000000001 split respectively
console.log(splitAmount);
}
}
I think you are making it too complicated.
All you need to do is get the total amount of parts in the ratio, and then do the (simple) math.
var details = {
amount: 1396.8000000000002,
splitInfo: [
{
type: "RATIO",
value: 3,
},
{
type: "RATIO",
value: 2,
},
],
};
let totalParts = 0;
for (const split of details.splitInfo) {
if (split.type == "RATIO") totalParts += split.value;
}
for (const split of details.splitInfo) {
if (split.type == "RATIO") {
let amountForSplit = details.amount * (split.value / totalParts);
console.log(amountForSplit);
}
}
This code is simple to understand and modify I hope to your liking. Using the array method reduce.
var details = {
"Amount": 1396.8000000000002,
"SplitInfo": [{
"SplitType": "RATIO",
"SplitValue": 3
},
{
"SplitType": "RATIO",
"SplitValue": 2
}
]
};
var result = details.SplitInfo.reduce(function(agg, item) {
if (item.SplitType == "RATIO") {
agg.sum = (agg.sum || 0) + item.SplitValue
agg.count = (agg.count || 0) + 1
}
return agg;
}, {})
console.log(result)
console.log("average is " + result.sum / result.count)
I am working on an application which has meal delivery service. It checks for the meal which the user has already taken and allows user to choose a meal depending on the time.
But if the user skips the meals in between it should get logged in the skipped section so the user also allotted those skipped meals.
e.g. if already taken meal is 'morning' and user skips directly to 'night', they should also get 'afternoon', 'evening' meals.
Here's what I already have and it's giving me the desired output. But want to see if it can be done a better way or optimised way.
const meals = ['morning', 'afternoon', 'evening', 'night', 'midnight'];
const currentMeal = 'afternoon';
const navigatedMeal = 'midnight';
const skippedMealsBy = meals.indexOf(navigatedMeal) - meals.indexOf(currentMeal);
if(skippedMealsBy > 1) {
const navigatedMealIndex = meals.indexOf(navigatedMeal);
const currentMealIndex = meals.indexOf(currentMeal) + 1;
const skippedMealsArray = [];
for(var i=currentMealIndex; i<=navigatedMealIndex; i++) {
skippedMealsArray.push(meals[i]);
}
}
Expected output: ['evening', 'night', 'midnight'];
All you need to do is slice the array from the two indicies.
const meals = ['morning', 'afternoon', 'evening', 'night', 'midnight'];
const currentMeal = 'afternoon';
const navigatedMeal = 'midnight';
const result = meals.slice(
// add 1 so the currentMeal isn't included
meals.indexOf(currentMeal) + 1,
// add 1 because `.slice` excludes the final index in the result
meals.indexOf(navigatedMeal) + 1
);
console.log(result);
1) You should calculate the index of currentMeal and navigatedMeal once at the very beginning.
2) You can use slice to return subarray of an array and It doesn't affect the original data set.
const meals = ["morning", "afternoon", "evening", "night", "midnight"];
const currentMeal = "afternoon";
const navigatedMeal = "midnight";
const currentMealIndex = meals.indexOf(currentMeal);
const navigatedMealIndex = meals.indexOf(navigatedMeal);
const skippedMealsBy = navigatedMealIndex - currentMealIndex;
if (skippedMealsBy > 1) {
const skippedMealsArray = meals.slice(currentMealIndex + 1, navigatedMealIndex + 1);
console.log(skippedMealsArray);
}
I want to give the user a prize when he signs in;
but it needs to be there some rare prizes so I want to appear prizes with different chances to appear using percents
i want to display one of these
[50 : 'flower'], [30 : 'book'], [20 : 'mobile'];
using percents they have
if there any way using Node.js or just javascript functions it would be great
You can create a function to get weighted random results, something like this:
const weightedSample = (items) => {
// cache if necessary; in Chrome, seems to make little difference
const total = Object.values(items).reduce((sum, weight) => sum + weight, 0)
const rnd = Math.random() * total
let accumulator = 0
for (const [item, weight] of Object.entries(items)) {
accumulator += weight
if (rnd < accumulator) {
return item
}
}
}
// check frequencies of each result
const prizes = { flower: 50, book: 30, mobile: 20 }
const results = Object.fromEntries(Object.keys(prizes).map(k => [k, 0]))
for (let i = 0; i < 1e6; ++i) {
const prize = weightedSample(prizes)
++results[prize]
}
// sample results: { flower: 500287, book: 299478, mobile: 200235 }
console.log(results)
This will work regardless of whether the weights add up to 100, whether they're integers, and so on.
'Right off the top of my head'-approach would be to prepare an array where each source item occurs the number of times that corresponds to respective probability and pick random item out of that array (assuming probability value has no more than 2 decimal places):
// main function
const getPseudoRandom = items => {
const {min, random} = Math,
commonMultiplier = 100,
itemBox = []
for(item in items){
for(let i = 0; i < items[item]*commonMultiplier; i++){
const randomPosition = 0|random()*itemBox.length
itemBox.splice(randomPosition, 0, item)
}
}
return itemBox[0|random()*itemBox.length]
}
// test of random outcomes distribution
const outcomes = Array(1000)
.fill()
.map(_ => getPseudoRandom({'flower': 0.5, 'book': 0.3, 'mobile': 0.2})),
distribution = outcomes.reduce((acc, item, _, s) =>
(acc[item] = (acc[item]||0)+100/s.length, acc), {})
console.log(distribution)
.as-console-wrapper{min-height:100%;}
While above approach may seem easy to comprehend and deploy, you may consider another one - build up the sort of probability ranges of respective width and have your random value falling into one of those - the wider the range, the greater probability:
const items = {'flower': 0.5, 'book': 0.2, 'mobile': 0.2, '1mUSD': 0.1},
// main function
getPseudoRandom = items => {
let totalWeight = 0,
ranges = [],
rnd = Math.random()
for(const itemName in items){
ranges.push({
itemName,
max: totalWeight += items[itemName]
})
}
return ranges
.find(({max}) => max > rnd*totalWeight)
.itemName
},
// test of random outcomes distribution
outcomes = Array(1000)
.fill()
.map(_ => getPseudoRandom(items)),
distribution = outcomes.reduce((acc, item, _, s) =>
(acc[item] = (acc[item]||0)+100/s.length, acc), {})
console.log(distribution)
"Certain probability" and "random" could lead to different approaches!
If you want random each time, something like:
let chances = [[0.2,'mobile'],[0.5,'book'],[1.0,'flower']]
let val = Math.random() // floating number from 0 to 1.0
let result = chances.find( c => c[0] <= val )[1]
This will give a random result each time. It could be possible to get 'mobile' 100 times in a row! Rare, of course, but a good random number generate will let that happen.
But perhaps you want to ensure that, in 100 results, you only hand out 20 mobiles, 30 books, and 50 flowers. Then you might want a "random array" for each user. Pre-fill the all the slots and remove them as they are used. Something like:
// when setting up a new user
let userArray = []
let chances = [[20,'mobile'],[30,'book'],[50,'flower']]
changes.forEach( c => {
for(let i = 0; i < c[0]; i++) userArray.push(c[1])
})
// save userArray, which has exactly 100 values
// then, when picking a random value for a user, find an index in the current length
let index = Math.floor(Math.random() * userArray.length)
let result = userArray[index]
userArray.splice(index,1) // modify and save userArray for next login
if(userArray.length === 0) reinitializeUserArray()
There are different approaches to this, but just some ideas to get you started.
Below I reproduce my code for calculate average ratings for foods.
3+4+5+2 / 4 should be equal to 3.5.
But this not gives me the correct output. What's wrong with my code?
const ratings = [
{food:3},
{food:4},
{food:5},
{food:2}
];
let food = 0;
ratings.forEach((obj,index)=>{
food = (food + obj.food)/++index
})
console.log("FOOD",food)
Despite of having several answers I would like to add mine focusing to improve the code quality:
You can use destructuring in forEach() to just get the food property of the object as that is only the property you are interested with.
Despite of dividing inside loop we can have only one division operation after the loop is completed. This saves a lot of computation when the array is huge(thousands of objects)
You can use short hand like += in the loop for summing up the value.
You can make a single line code in forEach()
const ratings = [
{food:3},
{food:4},
{food:5},
{food:2}
];
let foodTotal = 0;
let length = ratings.length;
ratings.forEach(({food})=> foodTotal += food);
console.log("FOOD",foodTotal/length);
Here is another solution using .reduce()
const ratings = [
{food: 3},
{food: 4},
{food: 5},
{food: 2}
]
const average = ratings.reduce((a, b) => {
return {food: a.food + b.food}
}).food / ratings.length
console.log('FOOD', average)
You'd use reduce to sum, then simply divide by rating's length
const ratings = [
{food:3},
{food:4},
{food:5},
{food:2}
];
const avg = ratings.reduce((a, {food}) => a + food, 0) / ratings.length;
console.log(avg);
You need to divide by the number of elements after you have summed them together
const ratings = [
{food:3},
{food:4},
{food:5},
{food:2}
];
let food = 0;
ratings.forEach((obj,index)=>{
food = (food + obj.food)
});
food = food / ratings.length;
console.log("FOOD",food)
You need to add the relative contribution to the average of each food.
Since average is - sum of items / number of items in your case it's
(3+4+5+2) / 4
Which we can split to
3/4 + 4/4 + 5/4 + 2/4
const ratings = [{"food":3},{"food":4},{"food":5},{"food":2}];
let food = 0;
ratings.forEach((obj) => {
food = food + obj.food / ratings.length;
})
console.log("FOOD", food)
You can also use Array.reduce() to shorten the code a bit:
const ratings = [{"food":3},{"food":4},{"food":5},{"food":2}];
const food = ratings.reduce((r, { food }) =>
r + food
, 0) / ratings.length;
console.log("FOOD", food)
What you are doing is 3/1 + 4/2 + 5/3 + 2/4 which is not equivalent to 3/4 + 4/4 + 5/4 + 2/4. What you can do is
ratings.forEach((obj,index)=>{
food = (food + obj.food)
});
food = food / ratings.length;
console.log("FOOD",food)
If you are into functional way of doing things then I would use a reduce function to get the result.
const food = ratings.reduce((sum, { food }) => sum + food, 0) / ratings.length;
console.log("FOOD", food)