Separate strings in an array in a certain order - javascript

I have a list that I took from a converted CHANGELOG.md file, and it looks like this:
["[3.0.0]","Features", "changes done in file","[2.0.1]", "Bug Fixes", "fixed login"]
What I want to do is to separate each version into its own list, like this:
["[3.0.0]", "Features", "changes done in file"],
["[2.0.1]", "Bug Fixes", "fixed login"]
Obviously, because it's a changelog, there can be multiple features and multiple bugfixes in a single version, so I want to a piece of code that separates the code appropriately.
I tried using if (string.startsWith('[')) but i couldn't manage to fit it in a loop.
Any help is appreciated.

Here's something I came up with. The code basically loops through the input array and adds each string to a currentArray variable. Everytime it hits a [ it puts the currentArray into the output and clears currentArray. At the end it removes the first element as the first element of the output will always be an empty array (since the first element of the input starts with a [)
var input = ["[3.0.0]","Features", "changes done in file","[2.0.1]", "Bug Fixes", "fixed login"];
var output = [];
var currentArray = [];
for (var i = 0; i < input.length; i++) {
if (input[i].charAt(0) == '[') {
output.push(currentArray);
currentArray = [];
}
currentArray.push(input[i]);
}
output.push(currentArray);
currentArray = [];
//Since it will take the first one, and put empty one, need to do last step.
output.splice(0, 1);
console.log(output);
// ["[3.0.0]", "Features", "changes done in file"],
// ["[2.0.1]", "Bug Fixes", "fixed login"]

Assuming that you're always working in sets of three, this is a quick and ugly approach
var data = ["[3.0.0]","Features", "changes done in file","[2.0.1]", "Bug Fixes", "fixed login"],
items = [];
data.map( (el, idx) => {
var last = items.length;
if( idx % 3 === 0 ) {
items.push( [] );
last += 1;
}
last = items[ last - 1 ];
last.push( el );
} );
console.log( JSON.stringify( items ) );

Here's an alternative solution should you prefer it:
const arr = ["[3.0.0]","Features", "changes done in file","[2.0.1]", "Bug Fixes", "fixed login"];
const newArr = [];
let tempArr = [];
arr.forEach(function(v, i) {
if(/^\[\d+.\d+.\d\]$/.test(v) && i > 0) {
newArr.push(tempArr);
tempArr = [v];
} else {
tempArr.push(v)
}
});
newArr.push(tempArr);
console.log(newArr);
This snippet loops through the items one-by-one. It uses two arrays, one to hold the final result and one to populate with items for the current version.
I am using a regex to check if the item contains one [ followed by a number, then a period, number, period, number and finally the trailing ]. This allows the other strings that are not version tags to contain that character.
If the current item is a version tag, we push tempArr (which contains the changes of the current version that we've previously filled in our loop) to our result array newArr. Then, we empty the tempArr and give it the starting value of the next version tag.
If it is not, we just push the current item to our temporary array.

It would be interesting to know if you were guaranteed to get this data in triplets, as your example seems to imply. If you knew this up front, there are many creative solutions that could emerge. For just creating a 2D Array, however, I like this approach (you can run this directly in node.js to try it out):
const original = ['[3.0.0]', 'Features', 'changes done in file', '[2.0.1]', 'Bug Fixes', 'fixed login']
function transformToChangeLog (originalArray) {
const changeLog = originalArray.reduce((newList, element) => {
element.charAt(0) === '[' // check for version string
? newList.push([element]) // If version string, then push a new Array containing that string
: newList[newList.length - 1].push(element) // If something else, tack it onto the last Array in the changelog list
return newList // whatever is returned in the reduce function is passed to the next iteration, allowing us to build this 2D array one element at a time.
}, [])
return changeLog
}
console.log(transformToChangeLog(original))
I hope that helps! I like the reduce Array method, because of it's versatility and succinctness.

Related

How to array.splice from both ends simultaneously in Javascript?

How do you splice from both ends of an array either simultaneously or sequentially such that "leftsplice" number of fields are spliced from left and "rightsplice" number of fields are spliced from original array not the one after doing leftsplicing. Any tips?
const [skillname,setSkillName] = useState(["Engineering", "Product", "Organization", "Business", "Market", "Customer"]);
setSkillName(skillname => skillname.splice(0,leftsplice))
let temp=-5+leftsplice;
setSkillName(skillname => skillname.splice(temp, rightsplice))
This is doing only leftsplicing.
PS: leftsplice, rightsplice are working as desired. I console.logged them
Your code in splicing is okay, but you should only use setSkillName after both leftsplice and rightsplice is done by saving it on a temporary variable, I haven't tested this code below but it is answering the question "How to splice both at the same time from a state" :
function whatever() {
let tempSkillName = [...skillname]
tempSkillName = tempSkillName.splice(0,leftsplice))
let temp=-5+leftsplice;
tempSkillName = tempSkillName.splice(temp, rightsplice))
setSkillName(tempSkillName)
}

Select Random Item (shown in pairs) from Array & Remove It, Restart Once Array is Empty

I'm super newbie in coding and I need help to achieve this code.
I'm trying to get a random item (in pairs) from an array and then remove it from this array until user gets to the last item or 60 days have gone from using the service (cookie?)... I have build a script with the help of other questions here in stackoverflow and here is my results so far.
`<script>
var randomizer = document.getElementById("getImgBut");
var dog1 = '/app/wp-content/mediaApp/yo-creo-mi-realidad/01F.jpg';
var dog2 = '/app/wp-content/mediaApp/yo-creo-mi-realidad/01B.jpg';
var dogpics=[dog1,dog2];
var yourPics = [
dogpics,
[ '/app/wp-content/mediaApp/yo-creo-mi-realidad/02F.jpg', '/app/wp-content/mediaApp/yo-creo-mi-realidad/02B.jpg' ],
[ '/app/wp-content/mediaApp/yo-creo-mi-realidad/03F.jpg', '/app/wp-content/mediaApp/yo-creo-mi-realidad/03B.jpg' ],
[ '/app/wp-content/mediaApp/yo-creo-mi-realidad/04F.jpg', '/app/wp-content/mediaApp/yo-creo-mi-realidad/04B.jpg' ],
[ '/app/wp-content/mediaApp/yo-creo-mi-realidad/05F.jpg', '/app/wp-content/mediaApp/yo-creo-mi-realidad/05B.jpg' ],
[ '/app/wp-content/mediaApp/yo-creo-mi-realidad/06F.jpg', '/app/wp-content/mediaApp/yo-creo-mi-realidad/06B.jpg' ] //This array has 52 cards but I cutted it for example purposes
];
function get_random_number(array){
return Math.floor(Math.random() * array.length |0);
} // here is where I have tried to modify with other scripts like the one in this page https://stackoverflow.com/questions/38882487/select-random-item-from-array-remove-it-restart-once-array-is-empty with no success
randomizer.addEventListener("click", function() {
var rand_number = get_random_number(yourPics);
console.log(rand_number);
document.getElementById('img1').src = yourPics[rand_number][0];
document.getElementById('img2').src = yourPics[rand_number][1];
});
var card = document.querySelector('.card');
card.addEventListener( 'click', function() {
card.classList.toggle('is-flipped');
});
</script>`
Thank you for your help!
I don't fully understand what you mean by "remove in pairs", but I'll answer presuming you mean you wish to remove the image ending in 02F.jpg at the same time as removing the image ending in 02B.jpg, and then 03F.jpg at the same time as 03B.jpg.
The solution to this that I will propose is that we will structure your data a bit differently to begin with. That is, if those images, the "B image" and "F image" are linked, we could keep them in the same `javascript object. This would look like:
var yourPics = [
{
bImage: '/app/wp-content/mediaApp/yo-creo-mi-realidad/02F.jpg',
fImage: '/app/wp-content/mediaApp/yo-creo-mi-realidad/02B.jpg'
},
{
bImage: '/app/wp-content/mediaApp/yo-creo-mi-realidad/03F.jpg',
fImage: '/app/wp-content/mediaApp/yo-creo-mi-realidad/03B.jpg'
}...]
This would then be an array of objects, rather than strings. We can access the bImage property of an object with just
myObject = yourPics[0]
myObject.bImage
We could delete one of those objects those at random via splice.
myRandomlyRemovedObject = yourPics.splice(myIndexToDeleteFrom, 1) would remove 1 object from yourPics at position of myIndexToDeleteFrom, which you presumably would choose randomly. myRandomlyRemovedObject would be assigned to the one object we removed.
I think this object based approach is safer since you will know for a fact that you will removed both matching strings at the same time.

Generating a new array if a certain criteria is met (Javascript)

I have an array of users who all need to be added to a group array. If the the group array has less than 3 users, i want to add the user to that group array. If the group array already has 3 user, I want to push the current group array to another array that collects all the groups and start another new group array for the next 3 users until there are no users.
Error -
let group[i] = [];
Unexpected token [
I have been racking my brains trying to figure this out. Maybe staring at the screen for too long.
This is what i have been trying with different variations but the console is not impressed -
function createGroups(totalPeople){
let i = 1
let group[i] = [];
let array = totalPeople
totalPeople.map((user) => {
if(group[i] =< 3){
group[i].push(user)
}else{
array.push(group[i]);
i++
}
})
};
totalPeople is an array created earlier in my code and this is the only part of the file that is not running as intended. Any help with a method on how to do this or suggestions on fixing this code would be of great help! thank you!
Try to initialize group as an array:
let i = 1
let group = [] // Initialize as an array
group[i] = [];
let array = totalPeople
totalPeople.map((user) => {
if(group[i] =< 3){
group[i].push(user)
}else{
array.push(group[i]);
i++
}
})
There are a few issues in your code :
function createGroups(totalPeople){
let i = 1
let group[i] = []; // issue #1
let array = totalPeople
totalPeople.map((user) => {
if(group[i] =< 3){ // issues #2 and #3
group[i].push(user)
}else{
array.push(group[i]); // issue #4
i++; // issue #5
}
})
};
Issue #1 :
You need to define group as an array before adding an index.
let group = [];
group[i] = [];
Issue #2 :
Looks like you meant to compare group[i].length and 3
Issue #3 :
Use <= instead of =< to compare your numbers. Also, if you compare the length with <= 3, you'll have 4 people per group. Because the first index in arrays is 0.
Issue #4 :
You are pushing to array, which is a reference to totalPeople. Is this what you meant? Because I doubt it will produce the expected results. You may want to initialize an empty array and push your group[i] array in it. And then, return that new array. It's usually a good practice in functionnal programming to return a new array and not modify the one passed as a parameter.
Issue #5 :
If you increment i, you need to initialize group[i] as an array, otherwise you won't be able to push in it when comes the next loop iteration.
Differnet logic :
Now that you fixed the issues in your code, here's a Snippet showing another way to do it using Array.prototype.reduce :
const totalPeople = ["Joe", "Jack", "Jerry", "Jane", "Mary", "Billy", "Vicky", "Bobby"];
const groupsOfThree = totalPeople.reduce((accumulator, currentPerson, index) => {
// pushing the current person in the topest group in the accumulator
accumulator[accumulator.length-1].push(currentPerson);
// if it's the 3rd person, we're pushing the an empty group in the accumulator
if (index % 3 === 2) {
accumulator.push([]);
}
return accumulator;
}, [[]]); // the initial value of the accumulator will be an array containing an empty group
console.log(groupsOfThree);

Grouping different arrays based on it's key

I have arrays like this,
0: {"pure_id":"1","branch_id":"45"}
1: {"pure_id":"2","branch_id":"45"}
2: {"pure_id":"3","branch_id":"45"}
3: {"pure_id":"3","branch_id":"45"}
I am looking to group the above arrays into a single array based on pure_id, so the result array will be this below, and the index will be pure_id index
1: [{"pure_id":"1","branch_id":"45"}]
2: [{"pure_id":"2","branch_id":"45"}]
3: [{"pure_id":"3","branch_id":"45"},{"pure_id":"3","branch_id":"45"}]
I am trying lots of things honestly, but I cannot make it work.
Please help.
const result = [];
for(const el of array)
(result[el.pure_id] || (result[el.pure_id] = [])).push(el);
Using the new object literal rest and spread syntax, the property expression syntax, and .reduce() makes this pretty short and simple.
var arr = [
{"pure_id":"1","branch_id":"45"},
{"pure_id":"2","branch_id":"45"},
{"pure_id":"3","branch_id":"45"},
{"pure_id":"3","branch_id":"45"}
];
var result = arr.reduce((res, {pure_id, ...props}) =>
({...res, [pure_id]: (res[pure_id] || []).concat({pure_id, ...props})})
, {});
console.log(result);
The .reduce() is of course just creating a new object. Each iteration takes the accumulated properties and adds them to an object literal, but with the pure_id being the concatenation of the properties of the current object (copy) to an array.
I often see people struggle with something in programming that outside of it would not be a problem. Instead of arrays and objects, you can think of it as fruit.
Imagine a bucket of fruit. You want to sort the one bucket of fruit into 1 bucket for each color of fruit (color just being a property of a piece of fruit). So you would:
grab one fruit at a time
figure out it's color
if there is no bucket for that color, get one
put the fruit in the proper bucket
repeat until there are no fruits left
What we created was an algorithm for sorting fruit by color. The problem you are solving is very similar. In pseudocode it would be:
for each fruit
color = fruit.color
if not bucket[color]
make bucket[color]
put fruit in bucket[color]
to translate that to JS
var fruits = [
{color: "red"},
{color: "orange"},
{color: "pink"},
{color: "red"}
];
var buckets = {};
for (var i=0; i<fruits.length; i++) {
var fruit = fruits[i];
var color = fruit.color;
if (!buckets[color])
buckets[color] = [];
buckets[color].push(fruit);
}
So now, if you want to work with your array, the algorithm stays the same you just swap out the parts.
var things = [
{"pure_id":"1","branch_id":"45"},
{"pure_id":"2","branch_id":"45"},
{"pure_id":"3","branch_id":"45"},
{"pure_id":"3","branch_id":"45"}
];
var grouped = [];
for (let i=0; i<things.length; i++) {
var thing = things[i];
var id = thing['pure_id'];
if (!grouped[id])
grouped[id] = [];
grouped[id].push(thing);
}
console.log(grouped)
So, here we are at the first solution. It works! There are better solutions (using reduce, forEach or others) but don't let data structures like arrays or dictionaries scare you away. Think about it in simpler terms if it helps.
If don't use reduce, the codes will be like below:
Assuming your expected result is one array like:
[[{'pure_id':0, 'branch_id':1}], [{'pure_id':1, 'branch_id':2}]]
//assuming your array is like below:
var test = [{"pure_id":"1","branch_id":"43"},
{"pure_id":"2","branch_id":"44"},
{"pure_id":"3","branch_id":"45"},
{"pure_id":"3","branch_id":"46"}]
var result = []
var index_db = {} //use dict to save the mapping between the index of result and pure_id
var index = 0
test.forEach(function(item){
if(item.pure_id in index_db){
result[index_db[item.pure_id]].push(item)
}
else{
result.push([item])
index_db[item.pure_id] = index++
}
});
console.log(result)
Below is one reduce version:
//assuming your array is like below:
var test = [{"pure_id":"1","branch_id":"43"},
{"pure_id":"2","branch_id":"44"},
{"pure_id":"3","branch_id":"45"},
{"pure_id":"3","branch_id":"46"}]
result = test.reduce(function(pre, cur){
if(cur['pure_id'] in pre['indexes']){
pre['result'][pre['indexes'][cur['pure_id']]].push(cur)
}
else{
pre['result'].push([cur])
pre['indexes'][cur['pure_id']] = pre['pos']++
}
return pre
}, {'pos':0, 'indexes':{}, 'result':[]})
console.log(result['result'])

Meteor 1.0 - Mongo queries using variables as key, including $inc

I'm working with a large dataset that needs to be efficient with its Mongo queries. The application uses the Ford-Fulkerson algorithm to calculate recommendations and runs in polynomial time, so efficiency is extremely important. The syntax is ES6, but everything is basically the same.
This is an approximation of the data I'm working with. An array of items and one item being matched up against the other items:
let items = ["pen", "marker", "crayon", "pencil"];
let match = "sharpie";
Eventually, we will iterate over match and increase the weight of the pairing by 1. So, after going through the function, my ideal data looks like this:
{
sharpie: {
pen: 1,
marker: 1,
crayon: 1,
pencil: 1
}
}
To further elaborate, the value next to each key is the weight of that relationship, which is to say, the number of times those items have been paired together. What I would like to have happen is something like this:
// For each in the items array, check to see if the pairing already
// exists. If it does, increment. If it does not, create it.
_.each(items, function(item, i) {
Database.upsert({ match: { $exist: true }}, { match: { $inc: { item: 1 } } });
})
The problem, of course, is that Mongo does not allow bracket notation, nor does it allow for variable names as keys (match). The other problem, as I've learned, is that Mongo also has problems with deeply nested $inc operators ('The dollar ($) prefixed field \'$inc\' in \'3LhmpJMe9Es6r5HLs.$inc\' is not valid for storage.' }).
Is there anything I can do to make this in as few queries as possible? I'm open to suggestions.
EDIT
I attempted to create objects to pass into the Mongo query:
_.each(items, function(item, i) {
let selector = {};
selector[match] = {};
selector[match][item] = {};
let modifier = {};
modifier[match] = {};
modifier[match]["$inc"] = {};
modifier[match]["$inc"][item] = 1
Database.upsert(selector, modifier);
Unfortunately, it still doesn't work. The $inc breaks the query and it won't let me go more than 1 level deep to change anything.
Solution
This is the function I ended up implementing. It works like a charm! Thanks Matt.
_.each(items, function(item, i) {
let incMod = {$inc:{}};
let matchMod = {$inc:{}};
matchMod.$inc[match] = 1;
incMod.$inc[item] = 1;
Database.upsert({node: item}, matchMod);
Database.upsert({node: match}, incMod);
});
I think the trouble comes from your ER model. a sharpie isn't a standalone entity, a sharpie is an item. The relationship between 1 item and other items is such that 1 item has many items (1:M recursive) and each item-pairing has a weight.
Fully normalized, you'd have an items table & a weights table. The items table would have the items. The weights table would have something like item1, item2, weight (in doing so, you can have asymmetrical weighting, e.g. sharpie:pencil = 1, pencil:sharpie = .5, which is useful when calculating pushback in the FFA, but I don't think that applies in your case.
Great, now let's mongotize it.
When we say 1 item has many items, that "many" is probably not going to exceed a few thousand (think 16MB document cap). That means it's actually 1-to-few, which means we can nest the data, either using subdocs or fields.
So, let's check out that schema!
doc =
{
_id: "sharpie",
crayon: 1,
pencil: 1
}
What do we see? sharpie isn't a key, it's a value. This makes everything easy. We leave the items as fields. The reason we don't use an array of objects is because this is faster & cleaner (no need to iterate over the array to find the matching _id).
var match = "sharpie";
var items = ["pen", "marker", "crayon", "pencil"];
var incMod = {$inc:{}};
var matchMod = {$inc:{}};
matchMod.$inc[match] = 1;
for (var i = 0; i < items.length; i++) {
Collection.upsert({_id: items[i]}, matchMod);
incMod.$inc[items[i]] = 1;
}
Collection.upsert({_id: match}, incMod);
That's the easy part. The hard part is figuring out why you want to use an FFA for a suggestion engine :-P.

Categories