Find separation values from a starting node - javascript

I found on some online coding exercises and this one looks really cool and I wanted to give it a shot.
Problem Statement
Quinn is a pretty popular, and extremely modest guy. Other students measure their popularity in a unit called QDist.
One can calculate their QDist value by finding the degrees of separation between their self and Quinn. For example:
If Quinn is friends with Dave, and Dave is friends with Travis, then Dave's QDist value is 1, and Travis is 2.
Output
name QDist for each person entered ordered alphabetically by name.
In the event that a person is not connected to Quinn in anyway, output name uncool
Given a list of friendships, list each person and their QDist value in alphabetical order.
Sample Input/output
10
Alden Toshiko
Che Kortney
Che Dorian
Ronda Lindell
Sharon Alden
Dorian Quinn
Owen Sydnee
Alden Che
Dorian Tyra
Quinn Ally
Output
Alden 3
Che 2
Dorian 1
Kortney 3
Lindell uncool
Ally 1
Owen uncool
Quinn 0
Ronda uncool
Sharon 4
Sydnee uncool
Toshiko 4
Tyra 2
My Approach
Firstly, I don't want the answer I just want a hint or some guidance on how I should approach the problem in javascript (as its the language i'm the most familiar with). My thought was to break the program into an object and arrays, and try to create a family relationship between each name, sort of as a nested object or perhaps an array. Then I could use some sort of recursion to find how deep the array or object goes.
What would be the best approach?

From the input you could create a list of persons. It could be an object, where each key is a person's name, and the corresponding value is an array of names, representing the friends of that person. Of course you should make sure that when you add B as a friend of A, you must also add A as a friend of B.
For the example input, the above structure would look like this:
{
"Alden": ["Toshiko","Sharon","Che"],
"Toshiko": ["Alden"],
"Che": ["Kortney","Dorian","Alden"],
"Kortney": ["Che"],
"Dorian": ["Che","Quinn","Tyra"],
"Ronda": ["Lindell"],
"Lindell": ["Ronda"],
"Sharon": ["Alden"],
"Quinn": ["Dorian","Ally"],
"Owen": ["Sydnee"],
"Sydnee": ["Owen"],
"Tyra": ["Dorian"],
"Ally": ["Quinn"]
}
Then keep track of a list of names, starting with just Quinn, and also a distance, starting at 0.
Then for each name in that list, assign the current distance as their QDist value. Then find their friends and put them all together. Remove names that have already received a QDist value.
Then increase the distance, and repeat the above for that new list of names.
Keep repeating until the list of names is empty.
Note that if you do things in the right order, you can replace a persons list of friends by the QDist value. So the above structure would change after the first two iterations to:
{
"Alden": ["Toshiko","Sharon","Che"],
"Toshiko": ["Alden"],
"Che": ["Kortney","Dorian","Alden"],
"Kortney": ["Che"],
"Dorian": 1,
"Ronda": ["Lindell"],
"Lindell": ["Ronda"],
"Sharon": ["Alden"],
"Quinn": 0,
"Owen": ["Sydnee"],
"Sydnee": ["Owen"],
"Tyra": ["Dorian"],
"Ally": 1
}
When the algorithm finishes, you have:
{
"Alden": 3,
"Toshiko": 4,
"Che": 2,
"Kortney": 3,
"Dorian": 1,
"Ronda": ["Lindell"],
"Lindell": ["Ronda"],
"Sharon": 4,
"Quinn": 0,
"Owen": ["Sydnee"],
"Sydnee": ["Owen"],
"Tyra": 2,
"Ally": 1
}
Now the remaining friends arrays need to be replaced with "uncool", as apparently the corresponding people have no connection with Quinn. Also the list needs to be sorted.
Spoiler warning!
Here is a working snippet:
// Get input as text
var input = `10
Alden Toshiko
Che Kortney
Che Dorian
Ronda Lindell
Sharon Alden
Dorian Quinn
Owen Sydnee
Alden Che
Dorian Tyra
Quinn Ally`;
// Build persons list with friends list
var persons =
// Take the input string
input
// Split it by any white-space to get array of words
.split(/\s+/)
// Skip the word at position 0: we don't need the line count
.slice(1)
// Loop over that array and build an object from it
.reduce(
// Arguments: obj = result from previous iteration
// name = current name in names array
// i = index in that array
// names = the whole array being looped over
(obj, name, i, names) => (
// Get the list of friends we already collected for this name.
// Create it as an empty array if not yet present.
obj[name] = (obj[name] || [])
// Add to that list the previous/next name, depending
// whether we are at an odd or even position in the names array
.concat([names[i%2 ? i-1 : i+1]])
// Use the updated object as return value for this iteration
, obj)
// Start the above loop with an empty object
, {});
// Now we have a nice object structure:
// { [name1]: [friendName1,friendName2,...], [name2]: ... }
// Start with Quinn as the only person we currently look at.
var friends = ['Quinn'];
// Increment the distance for each "generation" of friends
// until there are none left.
for (var i = 0; friends.length; i++) {
// Replace the friends list with a new list,
// while giving the friends in the current list a distance
friends =
// Start with the current list of friends
friends
// Loop over these friends.
// Only keep those that still have a friends array (object) assigned to them,
// since the others were already assigned a distance number.
.filter(friend => typeof persons[friend] === "object")
// Loop over those friends again, building a new list of friends
.reduce((friends, friend, k) => [
// Add this friends' friends to the new list
friends.concat(persons[friend]),
// ... and then replace this friends' friend list
// by the current distance we are at.
persons[friend] = i
// Return the first of the above two results (the new list)
// for the next iteration.
][0]
// Start with an empty array for the new friends list
, []);
}
// Now we have for each person that connects to Quinn a number:
// { [name1]: number, ... }
// Convert this to a format suitable to output
var result =
// Get list of names from the object (they are the keys)
Object.keys(persons)
// Sort that list of names
.sort()
// Loop over these names to format them
.map(name =>
// Format as "name: distance" or "name: uncool" depending on whether there
// still is an array of friends (object) in this entry
name + ': ' + (typeof persons[name] == 'object' ? 'uncool' : persons[name]));
// Output the result in the console
console.log(result);
And a more verbose, but easier to understand version:
// Get input as text
var input = `10
Alden Toshiko
Che Kortney
Che Dorian
Ronda Lindell
Sharon Alden
Dorian Quinn
Owen Sydnee
Alden Che
Dorian Tyra
Quinn Ally`;
// Build persons list with friends list
// Take the input string
var persons = input;
// Split it by any white-space to get array of words
persons = persons.split(/\s+/)
// Skip the word at position 0: we don't need the line count
persons = persons.slice(1)
// Loop over that array and build an object from it
var obj = {}; // Start loop with an empty object
for (var i = 0; i < persons.length; i++) {
var name = persons[i]; // name = current name in names array
// Get the list of friends we already collected for this name.
// Create it as an empty array if not yet present.
if (obj[name] === undefined) obj[name] = [];
// Add to that list the previous/next name, depending
// whether we are at an odd or even position in the names array
obj[name].push(persons[i%2 === 1 ? i-1 : i+1]);
}
// Assign result to persons
persons = obj;
// Now we have a nice object structure:
// { [name1]: [friendName1,friendName2,...], [name2]: ... }
// Start with Quinn as the only person we currently look at.
var friends = ['Quinn'];
// Increment the distance for each "generation" of friends
// until there are none left.
for (var i = 0; friends.length !== 0; i++) {
// Loop over those friends, building a new list of friends
// Start with an empty array for the new friends list
var newFriends = [];
for (var k = 0; k < friends.length; k++) {
var friend = friends[k];
// Only consider those that still have a friends array (object) assigned to them,
// since the others were already assigned a distance number.
if (typeof persons[friend] === "object") {
// Add this friends' friends to the new list
newFriends = newFriends.concat(persons[friend]);
// ... and then replace this friends' friend list
// by the current distance we are at.
persons[friend] = i;
}
};
// Make the new list the current list:
friends = newFriends;
}
// Now we have for each person that connects to Quinn a number:
// { [name1]: number, ... }
// Convert this to a format suitable to output
// Get list of names from the object (they are the keys)
var result = Object.keys(persons);
// Sort that list of names
result.sort();
// Loop over these names to format them
for (var i = 0; i < result.length; i++) {
var name = result[i];
// Format as "name: distance" or "name: uncool" depending on whether there
// still is an array of friends (object) in this entry
if (typeof persons[name] == 'object') {
result[i] = name + ': uncool';
} else {
result[i] = name + ': ' + persons[name];
}
}
// Output the result in the console
console.log(result);

If i had to solve this problem,
First I would create an array and initialise it with student who are 1 with Quinn by finding rows (elements) studentX ←→ Quinn in original array.
Then I would search recursively those who are level n with quinn by finding rows studentX ←→ student(n-1)FromQuinn

My attempt to understand
var persons = input.split(/\s+/).slice(1).reduce(function(obj,name,i,names){
return (obj[name] = (obj[name] || []).concat([names[i%2 ? i-1 : i+1]]), obj);
},{});
First input.split(/\s+/).slice(1) Gives us an array with all of the names in it.
Now
(obj[name] = (obj[name] || []).concat([names[i%2 ? i-1 : i+1]]), obj);
obj is set by default to due to {} according to the reduce method property.
name is current value which is basically going from Alden all the way to Ally. i will go from 1-10 and names is the array
Now we are saying set obj[name] = obj[name].concat([names[i%2 ? i-1 : i+1]]),obj); IF this is possible. If this isn't possible set obj[name] = [].concat([names[i%2 ? i-1 : i+1]]),obj);. This is my interpretation from reading up on ||
Example iteration
first obj = {}, and name will be Alden
so the type Alden i.e persons = { Alden: ..} will be obj[Alden].concat(names[2],obj), it will be 2, since 1 mod 2 doesn't get reached.
Now there is where I am a bit confused... what exactly is the ,obj doing here..? am I interpreting this right?

Related

Javascript ForEach on Array of Arrays

I am looping through a collection of blog posts to firstly push the username and ID of the blog author to a new array of arrays, and then secondly, count the number of blogs from each author. The code below achieves this; however, in the new array, the username and author ID are no longer separate items in the array, but seem to be concatenated into a single string. I need to retain them as separate items as I need to use both separately; how can I amend the result to achieve this?
var countAuthors = [];
blogAuthors = await Blog.find().populate('authors');
blogAuthors.forEach(function(blogAuthor){
countAuthors.push([blogAuthor.author.username, blogAuthor.author.id]);
})
console.log(countAuthors);
// Outputs as separate array items, as expected:
// [ 'author1', 5d7eed028c298b424b3fb5f1 ],
// [ 'author2', 5dd8aa254d74b30017dbfdd3 ],
var result = {};
countAuthors.forEach(function(x) {
result[x] = (result[x] || 0) + 1;
});
console.log(result);
// Username and author ID become a single string and cannot be accessed as separate array items
// 'author1,5d7eed028c298b424b3fb5f1': 15,
// 'author2,5dd8aa254d74b30017dbfdd3': 2,
Update:
Maybe I can explain a bit further WHY on what to do this. What I am aiming for is a table which displays the blog author's name alongside the number of blogs they have written. However, I also want the author name to link to their profile page, which requires the blogAuthor.author.id to do so. Hence, I need to still be able to access the author username and ID separately after executing the count. Thanks
You could use String.split().
For example:
let result = 'author1,5d7eed028c298b424b3fb5f1'.split(',')
would set result to:
['author1' , '5d7eed028c298b424b3fb5f1']
You can then access them individually like:
result[1] //'5d7eed028c298b424b3fb5f1'
Your issue is that you weren't splitting the x up in the foreach callback, and so the whole array was being converted to a string and being used as the key when inserting into the results object.
You can use array destructuring to split the author name and blog id, and use them to optionally adding a new entry to the result object, and then update that result.
countAuthors = [
['author1', 'bookId1'],
['author2', 'bookId2'],
['author1', 'bookId3'],
['author1', 'bookId4'],
['author2', 'bookId5']
]
var result = {};
countAuthors.forEach(([author, id]) => {
if (result[author] === undefined) {
result[author] = {count: 0, blogIds: []};
}
result[author].count += 1;
result[author].blogIds.push(id);
});
console.log(result);

Efficient way to search within multiple values in json

I have multiple records like this,
name: John Doe aliases: John, Doe, JD unique_id: 1 ...
My question is how do I search efficiently within the aliases & full name.
If the search query is any of those 4 (John Doe, John, Doe, JD) I would like to find the unique id (in this case 1).
What I have done: I have a very straightforward implementation that loops through the entire data until it finds. It takes a long time since the number of fields is very high.
Note: I am using javascript if it helps. Also I have the permission to change the data format (permanently), if it will make the search more efficient. Most of the search queries tend to be one of the aliases rather than full name.
Sample Code: https://jsfiddle.net/nh7yqafh/
function SearchJSON(json, query) {
var champs = json.champs;
for (var i = 0; i < champs.length; ++i) {
if (query == champs[i].name)
return champs[i].unique_id;
for (var j = 0; j < champs[i].aliases.length; ++j) {
if (query == champs[i].aliases[j])
return champs[i].unique_id;
}
}
}
//Data format is similar to what vivick said
var json_string = '{"count":5,"champs":[{"name":"Abomination","aliases":["abomination","AB","ABO"],"unique_id":1},{"name":"Black Bolt","aliases":["blackbolt","BB","BBT"],"unique_id":2},{"name":"Black Panther","aliases":["blackpanther","BP","BPR"],"unique_id":3},{"name":"Captain America","aliases":["captainamerica","CA","CAP"],"unique_id":4}]}'
var json = JSON.parse(json_string);
query="CA";
alert( "id of "+query+" is "+SearchJSON(json, query));
I guess you have a structure similar to the following one :
[
{
"name": "xxx",
"aliases": ["x", "xx", "xxx"],
"unique_id": 1,
/* [...] */
},
/* [...] */
]
You can then do something like this :
const queryParam = /*search query*/;
const arr = /* get the JSON record */;
const IDs = arr
.filter( entry =>(entry.aliases.includes(queryParam) || queryParam===entry.name) )
.map(entry=>entry.uniqueId);
This will give you an array of IDs which are potential matches.
If you need either 0 or 1 result only :
const ID = IDs[0] || null;
This will simply retrieve the first matched ID if there's one, otherwise it will set ID to null.
NB:
If you use an object of objects instead of an array of object, there's just a little bit of modifications to do (mainly using Object.entries) but it still is trivial.
PS:
I would recommend to always add the full name in the aliases, this will ease the filtering part (no || would be required).

Loop and concatenate same VAR

Is it possible to use do{}while() to concatenate the same var depending on the result of another var?
I am running a loop where I capture Customers and their Payments, each line is a result, sometimes for the same customer I have 2 or more Payments, i.e:
Customer A --- 'Payment#01' --- $10.00
Customer A --- 'Payment#02' --- $10.00
Customer B --- 'Payment#01' --- $10.00
Customer B --- 'Payment#02' --- $10.00
Customer B --- 'Payment#03' --- $10.00
[...]
I want to check the customer on the first line and while the next line continues with the same customer I'd like to concatenate each result in one string, so I will have something like this:
Customer A --- 'Payment#01,Payment#02' --- $20.00
Customer B --- 'Payment#01,Payment#02,Payment#03' --- $30.00
[Edit: code so far]
try{
do{
resultSet = searchResults.getResults(resultIndex, resultIndex + resultStep);
resultIndex = resultIndex + resultStep;
for(var i = 0; !!resultSet && i < resultSet.length; i++){
var results = resultSet[i];
var columns = results.getAllColumns();
var customer = results.getValue(columns[0]);
var paymentamt = results.getValue(columns[1]);
var document = results.getValue(columns[2]);
}
} while (!!resultSet && resultSet.length > 0)
} catch(error){
var message = error.message;
}
If the customers are always in a neat order, ie you won't ever get Customer A, Customer B, Customer A, then just use a variable to keep track of the current customer ID, current payment string, and current value.
Loop through the lot - is the customer ID at [loop] the same as the current one? If yes, add to the string and value.
If not, output (or whatever you do with it) the current line, reset all the variables to this new customer's data.
If they can arrive out of order then you could store the values in an object - eg. customerRecord = {customerID : { paymentstring:, value:}}.
As you read in the next customer in the loop, check if it exists in the object (hasOwnProperty if you use the customerID as a key as above) and add to its values. If not, add a new object to customerRecord.
The most generic approach is to keep an object with keys or the more modern Map to keep the totals and all payments. The most direct approach would be to keep objects and simply keep appending the string. An alternative is to keep the separate payments (descriptions) inside those objects and only concatenate them when displaying. For example:
//this part is just for emulating the resultset
let resultSet =[ {vals:['Customer A' ,'Payment#01' ,10.00]}, {vals:['Customer A' ,'Payment#02' ,10.00]}, {vals:['Customer B' ,'Payment#01' ,10.00]}, {vals:['Customer B' ,'Payment#02' ,10.00]}, {vals:['Customer B' ,'Payment#03' ,10.00]} ];resultSet.forEach(o=> {o.getAllColumns = ()=> [0,1,2]; o.getValue = i => o.vals[i]});
let map = new Map(); // <- declare outside the do..while , if it should concatenate customers from all searches, otherwise inside
do{
//get result set code...
if(!resultSet)break;
for(var i = 0; i < resultSet.length; i++){
let results = resultSet[i],
columns = results.getAllColumns();
customer = results.getValue(columns[0]),
tot = map.get(customer);
if(!tot) map.set(customer,tot = {customer:customer, payments:[], totalAmt:0, get Payments(){return this.payments.join(', ');} , toString: function(){return `${this.customer} --- ${this.Payments} --- ${this.totalAmt}`;}});
tot.payments.push(results.getValue(columns[1]));
tot.totalAmt += results.getValue(columns[2]);
}
}while(false); //replace with your own while (since break is used, you could simply do while(true))
//test output code.
for(let tot of map.values())
console.log(tot.toString());

How to compare a 2-D array and 1-D Array and to Store common Data in Another Array in Java Script

I have 2 Arrays and one is 2 dimensional and another is 1 dimensional. I need to compare both and need to store there common data in another array. I tried the below approach:-
tw.local.listtodisplayNW = new tw.object.listOf.listtodisplayNWBO();
//if(tw.local.SQLResults[0].rows.listLength >
// tw.local.virtualServers.listLength)
var k=0;
for (var i=0;i<tw.local.SQLResults[0].rows.listLength;i++)
{
log.info("Inside SQLResults loop - For RuntimeID: "
+tw.local.SQLResults[0].rows[i].data[3]);
for(var j=0;j<tw.local.virtualServers.listLength;j++)
{
log.info("Inside API loop - For RuntimeID: "
+tw.local.virtualServers[j].runtimeid);
if(tw.local.SQLResults[0].rows[i].data[3] ==
tw.local.virtualServers[j].runtimeid)
{
tw.local.listtodisplayNW[k] = new tw.object.listtodisplayNWBO();
tw.local.listtodisplayNW[k].vsysName =
tw.local.virtualServers[j].virtualSystemName;
tw.local.listtodisplayNW[k].vsysID =
tw.local.virtualServers[j].virtualSystemId;
tw.local.listtodisplayNW[k].serverName =
tw.local.virtualServers[j].serverName;
tw.local.listtodisplayNW[k].serverID =
tw.local.virtualServers[j].serverId;
tw.local.listtodisplayNW[k].runtimeID =
tw.local.virtualServers[j].runtimeid;
//tw.local.listtodisplayNW[k].IPAddress =
tw.local.virtualServers[j].nics[j].ipAddress;
log.info("VsysName:
"+tw.local.listtodisplayNW[k].vsysName+"RuntimeID:
"+tw.local.listtodisplayNW[k].runtimeID);
//tw.local.listtodisplayNW[k] = new
tw.object.listtodisplayNWBO();
tw.local.listtodisplayNW[k].currentSpeed =
tw.local.SQLResults[0].rows[i].data[5];
log.info("VsysName:
"+tw.local.listtodisplayNW[k].vsysName+"RuntimeID:
"+tw.local.listtodisplayNW[k].runtimeID+"CurrentSpeed:
"+tw.local.listtodisplayNW[k].currentSpeed);
if(tw.local.listtodisplayNW[k].currentSpeed != "100 Mbps")
{
tw.local.listtodisplayNW[k].desiredSpeed = "100 Mbps";
}
else
{
tw.local.listtodisplayNW[k].desiredSpeed = "1 Gbps";
}
log.info("DesiredSpeed:
"+tw.local.listtodisplayNW[k].desiredSpeed);
k++;
}
}
log.info("Length of
listtodisplayNW"+tw.local.listtodisplayNW.listLength);
}
In above code SQLResults is a 2-d array and virtualServers is a 1-D array.
I need to compare both these array and common data need to be store in another array. Here performance is not good. Is there any other way to do this efficiently. Please make a needful favour and Thanks in advance.
Assuming integer data, the following example works on the theme of array implementation of set intersection, which will take care of performance.
Convert 2D array to 1D.
var 2DtoIDArray = 2DArray.join().split(",");
Create an array named marker whose purpose is to serve as a lookup that element.
This needs to be done as follows.
Iterate through the smaller array, say 1DArray and keep setting marker as follows throughout iteration.
marker[1DArray[counter]]='S1';
Now iterate through 2Dto1DArray array(you may use nested loop iteration if you dont want to convert it to 1 dimesnional) and for each element
of this array check if its marked as 'S1' in the marker lookup array.
If yes, keep adding the elements in the commonElementsArray.
Follow this simple approach
Since the matching condition is only one between the two large arrays, create two maps (one for each array) to map each record against that attribute which is to be matched
For SQLResults
var map1 = {};
tw.local.SQLResults[0].rows.each( function(row){
map1[ row.data[3] ] = row;
});
and similarly for virtual servers
var map2 = {};
tw.local.virtualServers.each( function(vs){
map2[ vs.runtimeid ] = vs;
});
Now iterate these two maps wrt to their keys and set the values in new array
new array being tw.local.listtodisplayNW
tw.local.listtodisplayNW = [];
Object.keys( map1 ).forEach( function( key ){
if( map2[ key ] )
{
//set the values in tw.local.listtodisplayNW
}
})
Complexity of the approach is simply O(n) since there is no nested loops.

JavaScript database correlation

I've been trying to 'correlate' between user picked answers and an object property name so that if the two matches then it will display what is inside.
My program is a recipe finder that gives back a recipe that consists of the ingredients the user picked.
my code currently looks like:
//property are the ingredients and the value are the recipes that contain those ingredients. The map is automatically generated
``var map = {
"pork" : [recipe1, recipe2, ...],
"beef" : [],
"chicken" :[],
}
//this gets the user pick from the dom
var cucumber = specificVegetable[7];
var lemon = specificFruits[0];
//Then this code finds the intersection of the recipe(recipes that use more than one ingredients)
function intersect(array1, array2)
{
return array1.filter(function(n) {
return array2.indexOf(n) != -1
});
}
var recipiesWithLemon = map["lemon"]; **// makes the lemon object is map**
var recipiesWithCucumber = map["cucumber"]; **// makes the cucumber object in map**
//Here is where I am stuck
function check(){
var both = intersect(recipiesWithLemon, recipiesWithCucumber);
if ( cucumber.checked && lemon.checked){
for (var stuff in map){
if(stuff="cucumber" && stuff="lemon"){
return both;
}
}
}
}
check();
so basically what I tried to do was I made my intersect and then if user pick is lemon and cucumber then look at the properties in the map object. if the name of the property equals to the exact string then return both. That was the plan but the code does not work and I'm not sure how to fix it.
My plan is to write code for every possible outcome the user may makes so I need to find the correlation between the user pick and the map which stores the recipe. I realize this is not the most effective way but I'm stumped on how to do it another way.
Thanks for the help.
Im using the open source project jinqJs to simplify the process.
I also changed your map to an array of JSON objects. If you must have the map object not as an array, let me know. I will change the sample code.
var map = [
{"pork" : ['recipe1', 'recipe2']},
{"beef" : ['recipe3', 'recipe4']},
{"peach" :['recipe5', 'recipe6']},
{"carrot" :['recipe7', 'recipe8']}
];
var selectedFruit = 'peach';
var selectedVeggie = 'carrot';
var selections = [selectedFruit, selectedVeggie];
var result = jinqJs().from(map).where(function(row){
for(var f in row) {
if (selections.indexOf(f) > -1)
return true;
}
return false;
}).select();
document.body.innerHTML += '<pre>' + JSON.stringify(result, null, 2) + '</pre><br><br>';
<script src="https://rawgit.com/fordth/jinqJs/master/jinqjs.js"></script>

Categories

Resources