0

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?

Muntasir Alam
  • 1,777
  • 2
  • 17
  • 26
  • 1
    "What would be the best approach?" - try something and ask for more specific question should help. – Yury Tarabanko Jul 23 '16 at 13:22
  • Whats more specifics do you need? I've given my approach which I have tried, but doesn't seem practical and overly hard to code. I'm simply asking for some ideas. – Muntasir Alam Jul 23 '16 at 13:27

3 Answers3

1

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

kevin ternet
  • 4,514
  • 2
  • 19
  • 27
  • Hmm, yes that might be the best thing to do. Seems awfully challenging. I'll give it a shot later today. – Muntasir Alam Jul 23 '16 at 13:39
  • Some of the solutions with java use hashmaps and some data structures. Unfortunately I just started learning java recently. I'll try your approach though – Muntasir Alam Jul 23 '16 at 13:39
  • grr having some trouble implementing this, I've tried for a bit now, could you provide some more hints? – Muntasir Alam Jul 23 '16 at 19:22
1

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);
trincot
  • 317,000
  • 35
  • 244
  • 286
  • What do I type in to learn about /\s+/? I've seen stuff like that before where people manipulate strings but I've never actually learnt it. I'll try your method though and then check out your solution. Thanks alot – Muntasir Alam Jul 24 '16 at 00:30
  • I'm having a hard time understand the var persons process I'm going to write what I think it does and could you correct me – Muntasir Alam Jul 24 '16 at 02:51
  • `/\s+/` is a [regular expression](http://www.regular-expressions.info/). I added lots of comments to the code in the snippet, splitting each statement across several lines: hopefully it will help you get an understanding of what the code does. – trincot Jul 24 '16 at 06:43
0

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?

Muntasir Alam
  • 1,777
  • 2
  • 17
  • 26
  • You are interpreting this very well. The `( .... ,obj)` part uses the [`comma operator`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Comma_Operator): I have it there because I need to return the object to the `reduce` implementation. Instead of the comma, I could have made a statement block, with first `{ obj[name] = ...;` and then `return obj; }`. But I like to have it short, and so I avoid the statement block, and just do `( ....., obj)` which is an expression that evaluates to `obj` while still executing the `...` part. – trincot Jul 24 '16 at 06:53
  • concerning the modulo: the index starts at 0. There `name[0]` has `name[1]` as friend. Then at index 1 the reverse relation is set up: `name[1]` has `name[0]` as friend. And so it continues. So to correct your statement: it is `obj['Alden'].concat(names[1])`. The `,obj` part that follows is not an argument of `concat`, but a next expression for the *comma* operator. – trincot Jul 24 '16 at 07:01
  • Ok when i = 0, it goes to i+1, but when i =1, shouldn't it go to i+1 as well, and not to i-1? Since 1 isn't divisible by 2 – Muntasir Alam Jul 24 '16 at 12:48
  • Yes that is implied. `i%2` is i modulo 2, and can only have two outcomes: 0 or 1. For JavaScript 0 is falsy and 1 is truthy, so there is no need to do this `i%2===1`, which would have exactly the same effect on choosing the part to evaluate. – trincot Jul 24 '16 at 12:51
  • When i = 1 it should evaluate i-1. This is because I want the person in the second "column" (of the input), i.e. at i=1, to connect back to the first person (i = 0). In general, when i is odd, then we have a name that is in the second column of the input, and want to connect to the person on the left of it (i.e. at i-1). When i is even, we look to the person on the right, i.e. at i+1. – trincot Jul 24 '16 at 12:54
  • Yes this makes sense.. I don't know how you thought of this. it all makes sense... But now I just don't get why we need to return obj every iteration of reduce – Muntasir Alam Jul 24 '16 at 12:58
  • Do we basically have to return the object every iteration to let the engine know what the updated "obj" is? – Muntasir Alam Jul 24 '16 at 13:10
  • Yes, indeed, it works like that. Whatever you return will be available as the first argument in the next iteration. It's how reduce works. If you don't return it, you will have lost it in the next iteration, because then the first argument will be ... undefined. And in the last iteration, the return value will be the return value of the whole `reduce` itself. – trincot Jul 24 '16 at 13:26
  • Note that it will be better to discuss in the comment section under my answer, because this "answer" will have to be deleted as it is not an answer but a follow-up question. – trincot Jul 24 '16 at 13:29
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/118131/discussion-between-cresjoy-and-trincot). – Muntasir Alam Jul 24 '16 at 13:30
  • Maybe it can help to understand the code: I added a second snippet, which follows the same algorithm, but does not use any of the the tricks with `[ , ][0]`, comma operator, `reduce`, `filter`, or ternary operator. It sticks to basic `for` loops and `if` statements. It is of course less "functional" style of code, but if it helps, then my goal is reached. ;-) – trincot Jul 24 '16 at 15:43
  • That helped ALOT. If you have time soon could you come in the chat? – Muntasir Alam Jul 24 '16 at 18:54
  • Hey trincot, another problem on that website requires me to do some logic based off reading a file? Is this possible with javascript? Or are these type of exercises made for other languages? – Muntasir Alam Jul 24 '16 at 23:09
  • You can read a file via an ajax request. See for instance this [answer](http://stackoverflow.com/a/14446538/5459839). But if the question is really about reading at specific offsets and seeking in a file, then indeed JS might not be the best tool to solve that. – trincot Jul 24 '16 at 23:21
  • Would Java be able to do that type of stuff? I bought a java book last week and I'm going through a couple examples and pages everyday. Is Java more suited for that? Take this question for example http://potw.quinnftw.com/problem/2016/26/ – Muntasir Alam Jul 24 '16 at 23:33
  • That question does not really need file operations. You can do everything in Java, but in this case you could do it in JavaScript as well. You just start with a certain database, which can be an array of lines of text, or an array of arrays of words. But if you have questions about that, you should have a go at it and then ask a new question. You can always link me to your question. ;) – trincot Jul 25 '16 at 05:06
  • 1
    Ok, I'm trying right now. By the way I did this problem on my own without looking at your solution and got it!! – Muntasir Alam Jul 25 '16 at 18:09
  • Hi, I posted the new question here, was wondering if you could give your two cents on the solution given.Particularly what the line find = (regex, str) means here http://stackoverflow.com/a/38603735/6471733 – Muntasir Alam Jul 27 '16 at 12:26
  • Hi, your probably sleeping but if you are free and can chat for a bit let me know, I have a question :) – Muntasir Alam Jul 28 '16 at 01:14