JavaScript Generator: implement a ticket queue system - javascript

I am trying to implement a ticket queue system, where by default it would have 3 different queues to holder tickets that are of severity 1, severity 2, and severity 3 respectively. And I have a method getTicketAtHighestSeverity in it that returns the oldest ticket in the highest severity queue, so it starts at the first queue and looks for the first item in the queue and moves onto the next queue if the current queue is empty and another method getTicketBySeverity to iterate through all the all queues return the ticket starting at the highest severity
Here is my implementation.
class ticketQueues {
constructor(numOfQueues = 3) {
this.queues = Array.from({length: numOfQueues}).fill([])
}
addSev1(ticket) {
this.queues[0].push(ticket)
}
addSev2(ticket) {
this.queues[1].push(ticket)
}
addSev3(ticket) {
this.queues[2].push(ticket)
}
*getTicketBySeverity() {
for(const queue of this.queues) {
for(const ticket of queue) {
yield ticket
}
}
return null
}
getTicketAtHighestSeverity() {
for(const queue of this.queues) {
for(const ticket of queue) {
return ticket
}
}
return null
}
}
However it seems like getTicketBySeverity is not working properly.
const queues = new ticketQueues()
queues.addSev1({timestamp: Date(), name: 't1'})
queues.addSev2({timestamp: Date(), name: 't2'})
queues.addSev3({timestamp: Date(), name: 't3'})
for(let i = 2; i >= 0; i--) {
console.log(queues.getTicketBySeverity().next().value) // 🚨 this keeps returning the first item from the queue
}
Because it is not moving to the next ticket as it only returns the first ticket. The reason I chose Generator to implement this method is that I wanted to take advantage of the lazy evaluation model because the data set can be huge, I don't want to necessarily have to get all of the tickets all at once.
Can someone fix my implementation with getTicketBySeverity. And any suggestions about the naming here? I feel like the naming here i.e. getTicketBySeverity and getTicketAtHighestSeverity might not be the best choice. Also, feel free to comment on my usage of Generator here if you think this might not be a legit use case for that.

One problem is
this.queues = Array.from({length: numOfQueues}).fill([])
.fill does not work well with non-primitives (usually), since each item in the new array will be a reference to the same object. You've only created a single array. The problem is the same as why the following doesn't work as one might expect:
const subarr = [];
arr.push(subarr);
arr.push(subarr);
since there's only one subarr.
Use a mapper function with Array.from to explicitly create a new array for each iteration:
this.queues = Array.from({length: numOfQueues}, () => []);
Also, to iterate over the iterator, use for..of - either that, or remove the found item from the array when it's found (otherwise, every time it's called, it'll return the same item).
You can control the number of tickets to remove at once with for..of by passing an argument to the generator and keeping track of the number of elements yielded:
class ticketQueues {
constructor(numOfQueues = 3) {
this.queues = Array.from({length: numOfQueues}, () => []);
}
addSev1(ticket) {
this.queues[0].push(ticket)
}
addSev2(ticket) {
this.queues[1].push(ticket)
}
addSev3(ticket) {
this.queues[2].push(ticket)
}
*getTicketsBySeverity(limit) {
let count = 0;
for(const queue of this.queues) {
while (queue.length) {
yield queue.shift();
count++;
if (count === limit) {
return null;
}
}
}
return null
}
}
const queues = new ticketQueues()
queues.addSev1({timestamp: Date(), name: 't1'})
queues.addSev1({timestamp: Date(), name: 't1-2'})
queues.addSev2({timestamp: Date(), name: 't2'})
queues.addSev3({timestamp: Date(), name: 't3'})
for (const ticket of queues.getTicketsBySeverity(3)) {
console.log(ticket);
}
console.log(queues.queues);

Related

Javascript - Add Item in the middle of an array inside map function

I'm trying to add an item in a specific index inside an array inside a map function and it's been behaving unexpectedly. Here's the code for it
const addItemToLevelTwoArray= (uniqueID, arrayID )=> {
const reportObject = {
id:arrayID,
title:'',
}
data.map(section=>{
section.content.map((report, reportIndex)=>{
if(report.id===uniqueID){
section.content.splice(reportIndex, 0, reportObject);
}
return report;
})
return section;
})
}
Here's a working pen - https://codepen.io/raufabr/pen/vYZYgOV?editors=0011
Expected behaviour is that it would insert an object in the specific index, right above the object where the ID matches.
However, it's acting weirdly and sometimes I'm getting 2 items being added instead of one.
Any tip on what I'm doing would be massively appreciated! I know I'm close but I've been stuck on this for a while now and can't figure out what I'm doing wrong!
Preface: You're using map incorrectly. If you're not using the array that map builds and returns, there's no reason to use it; just use a loop or forEach. More in my post here. And one reason to use an old-fashioned for loop is that you're in control of iteration, which matters because...
However, it's acting weirdly and sometimes I'm getting 2 items being added instead of one.
That's because you're inserting into the array being looped by the map, so on the next pass, it picks up the entry you're adding.
If you do a simple loop, you can easily avoid that by incrementing the index when you insert, or by looping backward; here's the looping backward approach:
const addItemToLevelTwoArray = (uniqueID, arrayID) => {
const reportObject = {
id: arrayID,
title: "",
};
for (const section of data) {
for (let reportIndex = section.content.length - 1; reportIndex >= 0; --reportIndex) {
const report = section.content[reportIndex];
if (report.id === uniqueID) {
section.content.splice(reportIndex, 0, reportObject);
}
}
}
};
Because we're looping backward, we won't pick up the entry we just added on the next pass.
Since the outer loop doesn't have that problem, I used the more convenient for-of.
Since you asked about map, if you do use the array map returns, you can do this by returning an array with the two entries, and then calling flat on the array map builds. (This only works if the array doesn't already contain arrays, because they'll get flattened to.) This is common enough that it's combined in one function: flatMap. It's not what I'd do (I'd do a loop), but it's certainly feasible. Sticking with forEach and flatMap rather than using for-of and for:
const addItemToLevelTwoArray = (uniqueID, arrayID) => {
const reportObject = {
id: arrayID,
title: "",
}
data.forEach(section => {
section.content = section.content.flatMap(report => {
if (report.id === uniqueID) {
// Return the new one and the old one
return [reportObject, report];
}
// Return just the old one
return report;
});
});
};
That assumes it's okay to modify the section object. If it isn't, Alberto Sinigaglia's answer shows creating a new replacement object instead, which is handy in some sitautions.
You can just use flatMap:
const data = [
{
content: [
{
id: 1,
title: "a"
},{
id: 3,
title: "c"
},
]
}
]
const addItemToLevelTwoArray= (uniqueID, arrayID )=> {
const reportObject = {
id:arrayID,
title:'',
}
return data.map(section=> {
return {
...section,
content: section.content.flatMap( report =>
report.id === uniqueID
? [reportObject, report]
: report
)
}
}
)
}
console.log(addItemToLevelTwoArray(3, 2))
The following will extend the inner array .contentwithout modifying the original array data:
const data = [ {id: 0,title:'main',content:[{id:1,title:'Hello'},
{id:2,title:"World"}] } ];
const addItemToLevelTwoArray= (uniqueID, arrayID )=> {
const reportObject = {
id:arrayID,
title:'something new!',
}
return data.map(d=>(
{...d, content:d.content.reduce((acc, rep)=>{
if(rep.id===uniqueID) acc.push(reportObject);
acc.push(rep)
return acc;
},[]) // end of .reduce()
})); // end of .map()
}
const res=addItemToLevelTwoArray(1,123);
console.log(res);

How to prevent duplicate data from for loop

I am wondering what the best way is to prevent duplicate data from getting into a new array. I have a service call that returns the same array 3 times. I'm trying to take a number from inside the objects in the array and add them up to create a "total" number (fullRentAmt), but since the array gets returned 3 times I'm getting the total*3. I am thinking maybe .some() or .filter() could be of use here but I've never used those/am not sure how that would be implemented here. Thanks for any help!
What I tried, but it's not working/the new array isn't getting populated:
Component
properties = [];
fullRentAmt: number = 0;
const propertyDataSub = this.mainService.requestPropertyData()
.subscribe((pData: PropertyData[]) => {
if (pData) {
const propertyData = pData;
for (let i = 0; i < propertyData.length; i++) {
if (this.properties[i].propertyId !== propertyData[i].propertyId) {
this.properties.push(propertyData[i]);
}
}
for (let i = 0; i < this.properties.length; i++) {
this.fullRentAmt += this.properties[i].tenancyInformation[0].rentAmount;
}
});
Data returned from backend (array of 2 objects):
[
{
"tenantsData":[
{
"email":null,
"tenantNames":null,
"propertyId":2481,
}
],
"tenancyInformation":[
{
"id":2487,
"rentAmount":1000,
}
],
},
{
"tenantsData":[
{
"email":null,
"tenantNames":null,
"propertyId":3271,
}
],
"tenancyInformation":[
{
"id":3277,
"rentAmount":1200,
}
],
},
I'm not an angular developer, but I hope my answer will help you.
let the for loop duplicate the data as much as it wants. you just have to change the idea of storing the stuff from an array to a JavaScript Set
basically, it's very similar to arrays they're lists and iteratables that are very similar to arrays, the only difference is that they don't allow duplication,
usage:
const properties = new Set()
properties.add("yellow")
properties.add("blue")
properties.add("orange")
console.log(properties) // yellow, blue, orange
properties.add("blue")
properties.add("blue")
properties.add("blue")
console.log(properties) // yellow, blue, orange
after your for loop finishes, you may want to convert this set into a normal array, all you have to do is to use destructuring:
const propertiesArray = [...properties]
#YaYa is correct. I added this to show the correct code in Angular
properties = [];
fullRentAmt: number = 0;
const propertyDataSub = this.mainService.requestPropertyData()
.subscribe((pData: PropertyData[]) => {
if (pData && pData.length) {
let arrSet = new Set()
const propertyData = pData;
for (let i = 0; i < propertyData.length; i++) {
if (this.properties[i].propertyId !== propertyData[i].propertyId) {
arrSet.add(propertyData[i])
}
}
this.properties = Array.from(arrSet);
for (let i = 0; i < this.properties.length; i++) {
this.fullRentAmt += this.properties[i].tenancyInformation[0].rentAmount;
}
});
First thing you need to do is to fix your server and return the list once.
If server is out of your reach, you can leverage distinctUntilChanged pipe in combination with isEqual method in the frontend. You can either implement it yourself, or use a library such as lodash.
Also you do not have to subscribe, use async pipe in the template.
this.properties$ = this.mainService.requestPropertyData()
.pipe(
distinctUntilChanged(isEqual) // provide isEqual function somehow
);
this.totalRentAmount$ = properties$.pipe(
map(getTotalRentAmount)
);
// maybe in some other utility file:
export const getTotalRentAmount = (properties: Property[]): number => {
return properties
.map(property => property.tenancyInformation.rentAmount)
.reduce((total, amount) => total + amount, 0);
}
Then in the template:
<div>Total Rent Amount: {{ totalRentAmount | async }}</div>
Also if you really need to subscribe in the component and are only interested in the first emitted value of an observable, you can use first() or take(1) pipe to automatically unsubscribe after first value.
this.mainService.requestPropertyData()
.pipe(
first() // or take(1)
)
.subscribe(properties => this.properties = properties);
See the difference between first() and take(1)

Select and deselect multiple items from array

I have a snippet of code here where i have an array that may or may not have keys in it. When the user presses on a 'friend' they add them to a list (array) where they might start a chat with them (add 3 friends to the array, then start a chatroom). The users selected might be toggled on or off.
Current Behavior:
i can add/remove one person, but i cant add multiple people to the array at the same time. When i add one person, select another - the first person is 'active', when i remove the first person, the second person automatically becomes active
Expected Behavior:
I would like to be able to add multiple people to the array and then remove any of the selected items from the array
onFriendChatPress = (key) => {
console.log(key) // this is my key 'JFOFK7483JFNRW'
let friendsChat = this.state.friendsChat // this is an empty array initially []
if (friendsChat.length === 0) {
friendsChat.push(key)
} else {
// there are friends/keys in the array loop through all possible items in the array to determine if the key matches any of the keys
for (let i = 0; i < this.state.selGame.friends.length; i++) {
// if the key matches, 'toggle' them out of the array
if (friendsChat[i] === key) {
friendsChat = friendsChat.filter(function (a) { return a !== key })
}
else {
return friendsChat.indexOf(key) === -1 ? friendsChat.push(key) :
}
}
}
}
Help please!
From your code, I was quite confused regarding the difference between this.state.selGame.friends and this.state.friendsChat. Maybe I missed something in your explication. However, I felt that your code seemed a bit too overcomplicated for something relatively simple. Here's my take on that task:
class Game {
state = {
friendsChat: [] as string[],
};
onFriendToggle(key: string) {
const gameRoomMembers = this.state.friendsChat;
if (gameRoomMembers.includes(key)) {
this.state.friendsChat = gameRoomMembers.filter(
(member) => member !== key
);
} else {
this.state.friendsChat = [...gameRoomMembers, key];
}
}
}
I used typescript because it makes things easier to see, but your JS code should probably give you a nice type inference as well. I went for readability over performance, but you can easily optimize the script above once you understand the process.
You should be able to go from what I sent you and tweak it to be according to what you need

Javascript's method forEach() creates array with undefined keys

I am building a simple todo app, and I'm trying to get the assigned users for each task. But let's say that in my database, for some reason, the tasks id starts at 80, instead of starting at 1, and I have 5 tasks in total.
I wrote the following code to get the relationship between user and task, so I would expect that at the end it should return an array containing 5 keys, each key containing an array with the assigned users id to the specific task.
Problem is that I get an array with 85 keys in total, and the first 80 keys are undefined.
I've tried using .map() instead of .forEach() but I get the same result.
let assignedUsers = new Array();
this.taskLists.forEach(taskList => {
taskList.tasks.forEach(task => {
let taskId = task.id;
assignedUsers[taskId] = [];
task.users.forEach(user => {
if(taskId == user.pivot.task_id) {
assignedUsers[taskId].push(user.pivot.user_id);
}
});
});
});
return assignedUsers;
I assume the issue is at this line, but I don't understand why...
assignedUsers[taskId] = [];
I managed to filter and remove the empty keys from the array using the line below:
assignedUsers = assignedUsers.filter(e => e);
Still, I want to understand why this is happening and if there's any way I could avoid it from happening.
Looking forward to your comments!
If your taskId is not a Number or autoconvertable to a Number, you have to use a Object. assignedUsers = {};
This should work as you want it to. It also uses more of JS features for the sake of readability.
return this.taskLists.reduce((acc, taskList) => {
taskList.tasks.forEach(task => {
const taskId = task.id;
acc[taskId] = task.users.filter(user => taskId == user.pivot.task_id);
});
return acc;
}, []);
But you would probably want to use an object as the array would have "holes" between 0 and all unused indexes.
Your keys are task.id, so if there are undefined keys they must be from an undefined task id. Just skip if task id is falsey. If you expect the task id to possibly be 0, you can make a more specific check for typeof taskId === undefined
this.taskLists.forEach(taskList => {
taskList.tasks.forEach(task => {
let taskId = task.id;
// Skip this task if it doesn't have a defined id
if(!taskId) return;
assignedUsers[taskId] = [];
task.users.forEach(user => {
if(taskId == user.pivot.task_id) {
assignedUsers[taskId].push(user.pivot.user_id);
}
});
});
});

Are Java's Streams like JavaScript's Arrays? [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 5 years ago.
Improve this question
I try to build the Javascript equvivalent for Java's IntStream.range(0, 5).forEach(System.err::println); and reached
const IntStream = (function () {
function range(start, end, numbers = []) {
if (start === end) {
return numbers
}
return range(start + 1, end, numbers.concat(start))
}
return {
range
}
})()
IntStream.range(0, 5).forEach(number => console.log(number))
All the stream magic of Java is builtin in a normal JavaScript array. Why can't an ArrayList in Java do all same things as a Stream or is there a purpose I didn't figure out yet?
Array higher order functions will eagerly do the whole thing at each step.
const isOdd = v => v % 2 == 1;
const multiply = by => v => v * by;
const arrRange = IntStream.range(10, 20);
const arrOdd = arrRange.filter(isOdd);
const arrOddM3 = arrOdd.map(multiply(3));
Here all the bindings are distinct arrays created by each of the steps. Even when you chain them the intermediate arrays are always made and the whole array at each step need to be finished before the next can begin.
const arrOddM3 = IntStream.range(10, 20).filter(isOdd).map(multiply(3));
arrOddM3; // ==> [33, 39, 45, 51, 57]
Streams are different since they only compute values when they are accessed. A stream version would look very similar.
const streamOddM3 = Stream.range(10, Infinity).filter(isOdd).map(multiply(3));
streamOddM3; // ==> Stream
Notice I have changed the end to go to infinity. I can do that because at most it calculates the very first value and some implementations doesn't do any calculations at all until you ask for the values. To force the calculations you can take some values and get them returned as an array:
streamOddM3.take(3); // ==> [33, 39, 45]
Here is a Stream implementation loosely based on the one from the SICP videos which work similar to Java's streams.
class EmptyStream {
map() {
return this;
}
filter() {
return this;
}
take() {
return [];
}
}
class Stream extends EmptyStream {
constructor(value, next) {
super();
this._next = next;
this.value = value;
}
/**
* This prevents the value to be computed more than once
* #returns {EmptyStream|Stream}
*/
next() {
if( ! (this._next instanceof EmptyStream) ) {
this._next = this._next();
}
return this._next;
}
map(fn) {
return new Stream(fn(this.value), () => this.next().map(fn));
}
filter(fn) {
return fn(this.value) ?
new Stream(this.value, () => this.next().filter(fn)) :
this.next().filter(fn);
}
take(n) {
return n == 0 ? [] : [this.value, ...this.next().take(n && n - 1)];
}
static range(from, to, step = 1) {
if (to !== undefined && ( step > 0 && from > to || step < 0 && from < to )) {
return Stream.emptyStream;
}
return new Stream(from, () => Stream.range(from + step, to, step));
}
}
Stream.emptyStream = new EmptyStream();
There are alternatives to Stream that might work in their place.
In JavaScript you have generators (aka coroutines) and you can make a map and filter generator function that takes a generator source and becomes a new generator with that transformation. Since it is already in the language it might be a better match than Streams but I haven't studied it enough to make a generator example of the above.
In Clojure you have transducers that allows you to compose steps so that an eventual list making only happens for the elements that makes it to the final result. They are easily implemented in JavaScript.
Theres a big difference between Streams and Javasvript arrays:
[1,2,3,4]
.filter(el => {
console.log(el);
return el%2 === 0;
})
.forEach( el => console.log(el));
The result in javascript will be:
1,2,3,4 2,4
for a Stream it will be:
1,2 2 3,4 4
So as you can see javascript mutates the collection, then iterates the collection. An element passed into a Stream traverses the stream. If a collection is passed to a Stream, one element after another will be passed in the stream.
A possible Stream implementation would be:
class Stream {
constructor(){
this.queue = [];
}
//the modifying methods
forEach(func){
this.queue.push(["forEach",func]);
return this;
}
filter(func){
this.queue.push(["filter",func]);
return this;
}
map(func){
this.queue.push(["map",func]);
return this;
}
subStream(v){
this.forEach(d => v.get(d));
return this;
}
//data methods
get(value,cb){
for( let [type,func] of this.queue ){
switch(type){
case "forEach":
func(value);
break;
case "map":
value = func(value);
break;
case "filter":
if(! func(value)) return;
}
}
cb(value);
}
range(start,end){
const result = [];
Array.from({length:end-start})
.forEach((_,i)=> this.get(i+start, r => result.push(r)));
return result;
}
}
Usecase:
const nums = new Stream();
const even = new Stream();
even.filter(n => !(n%2) ).forEach(n => console.log(n));
const odd = new Stream();
even.filter(n => (n%2) ).forEach(n => console.log(n));
nums
.subStream(even)
.subStream(odd)
.range(0,100);
No they are not the same because of how they proccess the data.
In LINQ (C#) or javascript, each operation on a collection must end befor calling to the next operation in the pipeline.
In streams, its different. For example:
Arrays.asList(1,2,3).stream()
.filter((Integer x)-> x>1)
.map((Integer x)->x*10)
.forEach(System.out::println);
source collection: 1, 2 ,3
filter(1) -> You are not OK. Element 1 will not pass to the next operation
in the pipeline. Now deal with element 2.
filter(2) -> You are OK. element 2 pass to the next operation.
map(2) -> create new element 20 and put it in the new stream.
forEach(20) -> print 20. End dealing with element 2 in the source collection.
Now deal with element 3.
filter(3) -> You are OK. element 3 pass to the next operation
map(3) -> create new element 30 and put it in the new stream.
forEach(20) -> print 30. No more elements in the source collection.
finish excuting the stream.
output:
20
30
Illustration:
One of the outcome of this approach is sometimes some operations in the pipeline won't go over each element because some of them filtered out in the proccess.
This explanation were taken from: Streams In Depth By Stav Alfi

Categories