# Building a graph of people, and their relationships with each other

### Exercise 1

• src/test/java/resources/people.csv
• src/test/java/resources/relationships.csv

and use them to build an in-memory data structure that represents the people in the file and their relationships with each other.

### Exercise 2 - Validate correct people loaded

Write a test to validate that you have loaded the expected number of people.

### Exercise 3 - Validate correct relationships loaded

Write a test to validate that the following people have the correct expected number of connections to other people

• Bob (4 relationships)
• Jenny (3 relationships)
• Nigel (2 relationships)
• Alan (0 relationships)

### Exercise 4 - Write a method that calculates the size of the extended family

Write a method which, when passed the object representing a particular person, returns an int representing the size of their extended family including themselves. Their extended family includes anyone connected to them by a chain of family relationships of any length, so your solution will need to work for arbitrarily deep extended families. It should not count their friends. Write tests that validate this returns the correct result for the families of:

• Jenny (4 family members)
• Bob (4 family members)

## Solution

    package com.graph;

import java.util.ArrayList;
import java.util.List;

public class People {
private String name;
private String email;
private int age;
boolean visited = false;
private List<Relatives> relations = new ArrayList<>();

public People(String name, String email, int age) {
this.name = name;
this.email = email;
this.age = age;
}

public int getRelationShipCount() {
return this.relations.size();
}

public void addRelationShip(People people, String relationShip) {
if (this.getRelationShipCount() > 0) {
this.getRelations().forEach(rel -> {
// if user already in the relationship list then don't add the user in the
// relations
if (rel.getPerson().getEmail().equals(people.getEmail())) {
return;
}
});
}
Relatives newRelationShip = new Relatives(Relation.valueOf(relationShip), people);
// Relation is bi-directional so add this to the relation of person
newRelationShip = new Relatives(Relation.valueOf(relationShip), this);
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public List<Relatives> getRelations() {
return relations;
}

public void setRelations(List<Relatives> relations) {
this.relations = relations;
}

public boolean isVisited() {
return visited;
}

public void setVisited(boolean visited) {
this.visited = visited;
}

@Override
public String toString() {
return "name: " + this.getName() + " email: " + this.getEmail() + " age: " + this.getAge();
}
}

package com.graph;

public enum Relation {

FAMILY,
FRIEND
}

package com.graph;

public class Relatives {
private Relation relationShip;
private People person;

public Relatives(Relation relationShip, People person) {
super();
this.relationShip = relationShip;
this.person = person;
}

public Relation getRelationShip() {
return relationShip;
}

public void setRelationShip(Relation relationShip) {
this.relationShip = relationShip;
}

public People getPerson() {
return person;
}

public void setPerson(People person) {
this.person = person;
}

}

package com.graph;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.stream.Collectors;

public List<People> getAllPeople() {
List<People> peopleList = new ArrayList<>();

String line;
while ((line = br.readLine()) != null) {
String[] peoples = line.split(",");
People p = new People(peoples[0], peoples[1], Integer.valueOf(peoples[2]));
}

} catch (IOException e) {
System.err.format("IOException: %s%n", e);
}
return peopleList;
}

public List<Relatives> getAllPeopleRelationShip(List<People> listPeople) {
List<Relatives> relationshipsList = new ArrayList<>();

String line;
while ((line = br.readLine()) != null) {
if (line.trim().length() == 0) {
continue; // Skip blank lines
}
String[] rr = line.split(",");
People currentPerson = getPersonByEmail(listPeople, rr[0]);
People nextPerson = getPersonByEmail(listPeople, rr[2]);
if (null != nextPerson && null != currentPerson) {
}
}

} catch (IOException e) {
System.err.format("IOException: %s%n", e);
}
return relationshipsList;
}

private People getPersonByEmail(List<People> peopleList, String email) {
List<People> listPeople = peopleList.stream().filter(people -> people.getEmail().equals(email))
.collect(Collectors.toList());
if (null != listPeople && !listPeople.isEmpty()) {
return listPeople.get(0);
}
return null;
}

public static void main(String[] args) throws IOException {
List<People> allPeople = r.getAllPeople();
System.out.println(allPeople.toString());

allPeople.forEach(people -> {
System.out.println(r.getRelationShipCount(people.getName()));
System.out.println(people.getName() + " : " + r.getExtendedFamilyCount(people.getName()));
});

}

public int getRelationShipCount(String name) {
List<People> peopleList = getAllPeople();
getAllPeopleRelationShip(peopleList);
// should be based on the unique key as email
List<People> peoples = peopleList.stream().filter(people -> people.getName().equalsIgnoreCase(name))
.collect(Collectors.toList());
if (null == peoples || peoples.size() > 1 || peoples.isEmpty()) {
return 0;
}
return peoples.get(0).getRelationShipCount();
}

public int getExtendedFamilyCount(String name) {
List<People> peopleList = getAllPeople();
getAllPeopleRelationShip(peopleList);

People start = peopleList.stream().filter(people -> people.getName().equalsIgnoreCase(name))
.collect(Collectors.toList()).get(0);
if (null != start) {
stack.push(start);
start.setVisited(true);
}
int extendedFamilyCount = 0;
while (!stack.isEmpty()) {
People poped = stack.pop();
extendedFamilyCount++;
poped.getRelations().forEach(rel -> {
if (!rel.getPerson().isVisited() && rel.getRelationShip().equals(Relation.FAMILY)) {
stack.push(rel.getPerson());
rel.getPerson().setVisited(true);
}
});
}
return extendedFamilyCount;
}

}


## Files

### People.csv

Bob,[email protected],31
Derek,[email protected],25
Anna,[email protected],25
Jenny,[email protected],52
Pete,[email protected],57
Kerry,[email protected],29
Joe,[email protected],28
Nigel,[email protected],40
Amber,[email protected],12
Finn,[email protected],15
Alan,[email protected],23
Dave,[email protected],49
.....................................


### relationships.csv

[email protected],FAMILY,[email protected]
[email protected],FAMILY,[email protected]
[email protected],FAMILY,[email protected]
[email protected],FAMILY,[email protected]
[email protected],FAMILY,[email protected]
[email protected],FAMILY,[email protected]

[email protected],FRIEND,[email protected]
[email protected],FAMILY,[email protected]
[email protected],FAMILY,[email protected]
[email protected],FAMILY,[email protected]
[email protected],FAMILY,[email protected]

[email protected],FRIEND,[email protected]
[email protected],FAMILY,[email protected]
[email protected],FRIEND,[email protected]
[email protected],FRIEND,[email protected]
[email protected],FRIEND,[email protected]

• Welcome to Code Review, please divide the code in separated classes to improve readibility of your question. You used kotlin and gradle tags, are these tags really necessary? Apr 20, 2021 at 8:01
• Yes, I used kotlin plugin. kotlin("jvm") version "1.3.21" and Gradle to build it. Apr 20, 2021 at 8:14
• You might have used kotlin("jvm") and Gradle to build it, but I don't see any traces of either Kotlin or Gradle in the code so I'm removing those tags. This is Java code, not Kotlin. And just because you're using Gradle doesn't mean that your code is about Gradle. Apr 20, 2021 at 10:06
• Please note that the "example.com" domain is reserved for the sole purpose of allowing you to create addresses for testing purposes that are guaranteed to not exist in real life. Please do not create random e-mail addresses with valid domain name like gmail.com because you will either accidentally e-mail to them or you will publish them and they will end up on spam lists. This will be a major annoyment to the people who own those addresses and do not want unsolicited e-mails. (I have edited the question to hide the addresses). Apr 21, 2021 at 4:44
• @RaushanKumar Yes, that is the point. You can safely use it in tests and be sure that it does not represent anything that occurs in real life. Apr 21, 2021 at 7:22

Nice implementation, find below my suggestions.

Exercise 1 Please implement code and data structures that read the files

The data structure (that represents the graph) in your implementation is List<People>, which is a bit confusing. Creating a class Graph to hold and manage the people would be more appropriate in my opinion.

Exercise 2 - Validate correct people loaded

ReadPersonAndRelationship r = new ReadPersonAndRelationship();
List<People> allPeople = r.getAllPeople();
System.out.println(allPeople.toString());


This is one way to test. However, it's hard to know how many people are in the list. It prints a long line to the console and we need to count manually the people to see if they are 12 (like in the file). If there are more people, it would be unfeasible.

In Java, you can use Junit to unit test the code. For example:

@Test
public void testGetAllPeople() {

List<People> allPeople = r.getAllPeople();

assertEquals(12, allPeople.size());
}


Running this test will immediately tell if the 12 people in the file are actually in the list.

Exercise 3 - Validate correct relationships loaded

Exercise 4 - Write a method that calculates the size of the extended family

allPeople.forEach(people -> {
System.out.println(r.getRelationShipCount(people.getName()));
System.out.println(people.getName() + " : " + r.getExtendedFamilyCount(people.getName()));
});


Similarly, instead of printing the result to the console, two more tests can be created. One example for testing getRelationShipCount:

@Test
public void testGetRelationshipCount() {

int bobRelationshipsCount = r.getRelationShipCount("Bob");
int jennyRelationshipsCount = r.getRelationShipCount("Jenny");

assertEquals(4, bobRelationshipsCount);
assertEquals(3, jennyRelationshipsCount);
}


## Responsibility

The class ReadPersonAndRelationship seems to have a lot of responsibility. Besides reading the files, it goes through the "graph" and returns statistics like the relationships count and the extended family count for a person. This logic should be delegated to another class, perhaps to a class Graph that I mentioned earlier.

## Performance

The methods getRelationShipCount and getExtendedFamilyCount need to read the file every time. In your main method:

    allPeople.forEach(people -> {
System.out.println(r.getRelationShipCount(people.getName()));
System.out.println(people.getName() + " : " + r.getExtendedFamilyCount(people.getName()));
});


Because there are 12 people, this part reads the file people.csv 24 times. And the file relationships.csv 12 times.

Consider this workflow:

1. Read the files once and create a graph
2. Call the methods on the graph (which works on the people in memory)

## Naming

The class People should be Person since it stores the information for only one person. Same for the class Relatives.