I am trying to figure out how to make a method which finds the shortest way between two nodes, but cant seem to figure it out. I was given two files, one file containing actors (which will be the nodes) and one with movies (which will be the edges).
I have represented a graph as a HashMap where the key is a actor, and the value is a ArrayList with the movies that actor has played in:
HashMap<Actor, ArrayList[Movie]> graph;
Now i wanna find the shortest way between two actors but i dont know how to. I was thinking DFS or BFS but im not quite sure how to do that with this hashmap..
You can use your current structure (HashMap<Actor, ArrayList<Movie>>) as an adjacency list, but there is something missing: an efficient way to go from movies back to actors that starred in them. You can build a Map<Movie, List<Actor>>, and that would work... but it is simpler to use a single adjacency list, instead of having one for actors and another for movies. I would do as follows (assuming that Actor and Movie both have equals and hashcode implemented, and no actor equals any movie):
Map<Object, List<Object>> edges = new HashMap<>();
for (Map.Entry<Actor, ArrayList<Movie>> e : graph.entries()) {
Actor a = e.getKey();
edges.put(a, new ArrayList<Object>(e.getValues()));
for (Movie m : e.getValues()) {
List<Actor> otherActors = edges.getOrDefault(m, new ArrayList<Object>());
otherActors.add(a);
edges.put(m, otherActors);
}
}
Now you can use edges to perform a standard breadth-first search. It is guaranteed to give you a shortest path (there may be multiple equally-short paths) between any 2 actors, and the path will contain the movies that link them.
It's not the entire solution just to point you in the direction, you will have to create a Map<Movie, List> as tucuxi suggested. You traverse every actor one by one and mark the distance on the current actor from your reference actor.
private static void fn(HashMap<Actor, List<Movie>> graph, Actor referenceActor) {
var movieMap = new HashMap<Movie, List<Actor>>();
graph.forEach((key, value) -> {
for (var movie : value) {
movieMap.compute(movie, (k, v) -> {
if (v == null) {
return new ArrayList<>(List.of(key));
}
v.add(key);
return v;
});
}
});
final var visited = new HashSet<Actor>();
var dq = new ArrayDeque<Actor>();
var map = new HashMap<Actor, Integer>();
dq.add(referenceActor);
int distance = 0;
while (!dq.isEmpty()) {
var size = dq.size();
for (int i = 0; i < size; i++) {
var actor = dq.poll();
visited.add(actor);
map.putIfAbsent(actor, distance);
for (Movie movie : graph.get(actor)) {
// adding every non visited actor in the same movie in the queue
dq.addAll(movieMap
.get(movie)
.stream()
.filter(x -> !visited.contains(x))
.collect(Collectors.toList()));
}
}
distance++;
}
System.out.println(map);
}
I used ArrayDeque here to traverse in BFS fashion you can mend if you want DFS bear in mind the logic to calculate distance will also need to be changed. Pasting the image from the comments, taking node B as reference actor.
Related
I have a list of lines and points and I need to get the list of connected lines based on points.
The input is Line and its 2 points so in the above example input is
In Tabular format
And the output should be 2 lists
[1,2,3,4,5,6,7] and [8]
I was creating a map of point to list of lines it belongs
A - 1
B - 1,2,3
C - 2
...
and then trying to merge the list of lines where common points are found. But not able to find the correct way to merge those lines.
Can there be simple or another solution to it?
Are 3 and 5 different lines?
If yes, then it is obvious that each line can have no more than 2 points.
If no, then a tabular format is not appropriate.
If I were you, I would do one Map with a line as the key and a List of points as the value.
Then create an interface to define if a point belongs to several lines at once.
One solution is to create an adjacent map of points to point-lines.
You define what are the adjacent points of a given point and what are the lines:
class PointAdj {
String point;
String line;//Or int
}
Define the map as:
Map<String, List<PointAdj>> pontAdjMap = new HashMap<>();
Start reading from the table and fill the map:
(I assume the input is a two dimensional array called input)
for (int i =0; i < input.length; i++) {
String[] row = input[i];
List<PointAdj>> adj = pontAdjMap.putIfAbsent(row[1], new ArrayList<>());
if(adj == null) {
adj = pontAdjMap.get(row[1]);
}
adj.add(new PointAdj(row[2], row[0]));
//Also put the reverse side
}
Now pontAdjMap contains all points with their adjacent points.
Now define a list of Set to add lines and loop the map and add lines as much as possible.
Use a queue to continuously traverse adjacent points;
List<Set<String>> lines = new ArrayList<>();
final Set<String> calculated = new HashSet();//Takes care of redundant processing of points;
pontAdjMap.keySet().foreach(pointInMap->{
Set<String> lineSet = new HashSet();
Queue<String> queue = new LinkedList<String>();
queue.offer(pointInMap);
while(!queue.isEmpty()) {
String point = queue.poll();
if(calculated.add(point)) {
for(PointAdj pa: pontAdjMap.get(point)) {
lineSet.add(pa.line);
queue.offer(pa.point);
}
}
}
if(lineSet.size() > 0) {
lines.add(lineSet);
}
});
Now lines should be the final result.
Please note that I did't test this solution and it may contain some edge case issues. But I think the overall idea is ok.
I wrote this static method in Python to do breadth first search. However, I mainly use Java, and I want to get a sense of how the data structures convert to Java, given generics, etc. My code is:
def bfs(graph, start_vertex, target_value):
path = [start_vertex] #a list that contains start_vertex
vertex_and_path = [start_vertex, path] #a list that contains start_vertex and path
bfs_queue = [vertex_and_path]
visited = set() #visited defined as an empty set
while bfs_queue: #while the queue is not empty
current_vertex, path = bfs_queue.pop(0) #removes element from queue and sets both equal to that first element
visited.add(current_vertex) #adds current vertex to visited list
for neighbor in graph[current_vertex]: #looks at all neighboring vertices
if neighbor not in visited: #if neighbor is not in visited list
if neighbor is target_value: #if it's the target value
return path + [neighbor] #returns path with neighbor appended
else:
bfs_queue.append([neighbor, path + [neighbor]]) #recursive call with path that has neighbor appended
a graph I'd use this on would be:
myGraph = { //I'm not sure how to structure this in Java
'lava': set(['sharks', 'piranhas']),
'sharks': set(['lava', 'bees', 'lasers']),
'piranhas': set(['lava', 'crocodiles']),
'bees': set(['sharks']),
'lasers': set(['sharks', 'crocodiles']),
'crocodiles': set(['piranhas', 'lasers'])
}
and I would call it like
public static void main(String[] args){
System.out.println(bfs(myGraph, "crocodiles", "bees"));
}
So far, here's the Java I have:
public class BreadthFirstSearch{
///NOT DONE YET
public static ArrayList<String> BFS(Map<String, String[]> graph, String start, String target) {
List<String> path = new ArrayList<>();
path.add(start);
List<String> vertexAndPath = new ArrayList<>();
vertexAndPath.add(start);
vertexAndPath.add(path.get(0));
ArrayList<String> queue = new ArrayList<>();
queue.add(vertexAndPath.get(0));
queue.add(vertexAndPath.get(1));
Set<String> visited = new HashSet<String>();
while(!queue.isEmpty()) {
String currentVertex = queue.remove(0);
String curVerValue = currentVertex;
path.add(currentVertex);
.
.
.
}
}
}
Good effort on the translation. Let me offer my code, then an explanation:
import java.util.*;
class BreadthFirstSearch {
public static ArrayList<String> BFS(
Map<String, String[]> graph, String start, String target
) {
Map<String, String> visited = new HashMap<>();
visited.put(start, null);
ArrayDeque<String> deque = new ArrayDeque<>();
deque.offer(start);
while (!deque.isEmpty()) {
String curr = deque.poll();
if (curr.equals(target)) {
ArrayList<String> path = new ArrayList<>();
path.add(curr);
while (visited.get(curr) != null) {
curr = visited.get(curr);
path.add(curr);
}
Collections.reverse(path);
return path;
}
for (String neighbor : graph.get(curr)) {
if (!visited.containsKey(neighbor)) {
visited.put(neighbor, curr);
deque.offer(neighbor);
}
}
}
return null;
}
public static void main(String[] args) {
Map<String, String[]> myGraph = new HashMap<>();
myGraph.put(
"lava", new String[] {"sharks", "piranhas"}
);
myGraph.put(
"sharks", new String[] {"lava", "bees", "lasers"}
);
myGraph.put(
"piranhas", new String[] {"lava", "crocodiles"}
);
myGraph.put(
"bees", new String[] {"sharks"}
);
myGraph.put(
"lasers", new String[] {"sharks", "crocodiles"}
);
myGraph.put(
"crocodiles", new String[] {"piranhas", "lasers"}
);
System.out.println(BFS(myGraph, "crocodiles", "bees"));
System.out.println(BFS(myGraph, "crocodiles", "crocodiles"));
System.out.println(BFS(myGraph, "crocodiles", "zebras"));
}
}
Output
[crocodiles, lasers, sharks, bees]
[crocodiles]
null
Explanation
I made the design decision to avoid copying a path ArrayList on every node in the graph in favor of a visited hash that stores nodes in childNode => parentNode pairs. This way, once I've located the destination node, I retrace my steps to create the path in one shot, instead of building a path for every node, most of which ultimately lead nowhere. This is more efficient in space and time; Python makes it too easy to ruin your time complexity with the [] + [] O(n) list concatenation operator.
Using a child => parent visited HashMap is also simpler to code in Java, which doesn't have a light weight Pair/Tuple/struct that can conveniently store different types as nodes in the queue. To do what you're doing in Python in passing a 2-element list into the queue, you'd either have to write your own Pair class, use two ArrayDeques, or avoid generics and use casting, all of which are ugly (especially the last, which is also unsafe).
Another issue I noticed in your code is usage of an ArrayList as a queue. Insertion and removal on the front of a list is a O(n) operation as all elements in the list must be shifted forward or backward in the underlying array to maintain sequencing. The optimal queue structure in Java is an ArrayDeque, which offers O(1) add and removal at both ends and is not thread safe, unlike the Queue collection.
Similarly, in Python, you'll find performance is best using the deque collection which offers a fast popleft operation for all your queuing needs. Additionally, in your Python implementation, each key in your hash points to a set, which is okay, but seems like an unnecessary structure when a list would do (you've switched to a primitive array in Java). If you're not manipulating the graph and only iterating over neighbors, this seems ideal.
Note that this code also presumes that every node has a key in the hash that represents the graph, as your input does. If you plan to input graphs where nodes may not have keys in the hash, you'll want to make sure that graph.get(curr) is wrapped with a containsKey check to avoid crashing.
Another assumption worth mentioning: ensure your graph doesn't contain nulls since the visited hash relies on null to indicate that a child has no parent and is the start of the search.
You would need to create a separate class to hold nodes of a graph. Those nodes could not be static, as they all have unique vertexes. From there the rest is very similar.
public class Node {
public String name;
public ArrayList<Node> vertices;
public void addEdge(Node node) {
edges.add(node);
}
}
I've tried every other question and answer on SO regarding this topic, but I haven't managed to make it work.
I have a list of specifically ordered strings for example: ["serviceT", "servicC", "serviceA", ... "serviceZ"].
I have another list of complete paths to jar files of the same name:
["C:\Users\USER\projects .... \serviceB-1.0-SNAPSHOT.jar" ...]. This list is completely unordered. This list is generated using this:
servicesPaths = Files.walk(Paths.get(parentFolder))
.map(Path::toString)
.filter(path -> {
boolean flag = false;
for (String service : services)
if (path.endsWith(version + ".jar") && path.contains(service)) flag = true;
return flag;
})
.collect(Collectors.toList());
Essentially I just give it a parent folder, and it returns a list of all paths that contain service. Both lists end up the same size. I mean, each service has exactly one path.
I need to order the servicePath list, so that it will be the same order as the services ordered list.
I've tried using every method of sorting / comparator I could think of, and could only make it work using a very brute for loop:
List<String> temp = new ArrayList<>();
for (String service : services)
for (String path : servicesPaths)
if (path.indexOf(service) > 0)
temp.add(path);
Is there any other sensible way to sort the servicePaths list ?
A really straight-forward solution, but I think it's appropriate:
List<String> services = Arrays.asList("serviceA", "serviceC", "serviceB");
List<String> servicesPaths = Arrays.asList("serviceA-bla-bla", "serviceB-bla-bla", "serviceC-bla-bla");
List<String> sortedServices = servicesPaths.stream()
.sorted((a, b) -> Integer.compare(
services.indexOf(a.substring(0, a.indexOf("-"))),
services.indexOf(b.substring(0, b.indexOf("-")))))
.collect(Collectors.toList());
The substring part is just extracting the service name from path. If you have any other ways to do it, just replace it with smth else.
Also, I would improve this snippet by storing the comparing func in a variable:
Function<String, Integer> indexInServices = path -> services.indexOf(path.substring(0, path.indexOf("-")));
List<String> orderedServices = servicesPaths.stream()
.sorted(Comparator.comparing(indexInServices))
.collect(Collectors.toList());
The first optimization: as both lists have equal size you can remove the paths after adding them. That reduces the number of inner loops for each run of the outer loop.
But what I would do: change the way how you generate that path list. You see: first of all you have another N * N there already. And then another N * N later one?!
Instead:
put your services into a Map<String, Integer>: key is the string, value the index in the service list
create an empty list for the paths - that has service empty slots!
then, when you process the file paths, simply check for each Map key if it "matches" - and on a match ... you just look into that map and you know the position that this path should have. So you can simply insert it into that list you prepared in step 2!
Should be enough to get you going - I guess you will have to carefully look into constraints such as "sizes of lists really identical" and so on (if you have less paths than services, that intermediate list might contain some empty slots which you would have to remove then).
Sort servicesPaths by the substring that follows the string "Service"
Let m be the size of servicesPath
Let n be the size of your sorted Strings.
Then use the pseudocode
for each string in servicesPath
search SortedStrings by binary search
This solution is time O(m log (n) )*
since binary search is order O(log n)
Your brute force solution is time *O(m * n)*
Should the size of sorted paths be smaller than the size of sorted strings
create an array of records. (Note, that is not the situation poster has.)
class Pair {
String str;
int pos;
}
Pair []Pairs;
Pairs = new Pair[sorted strings.length];
for each item in the sorted Strings, copy the string to Pair[i]s.str and
its ordinal position to Pairs[i].pos
Then, sort Pairs by str, using the straightforward comparator
Then for each item in servicesPaths, you take the string and do a binary
in Pairs for it. When you find the one that matches, call its position pos.
Copy that item of servicesPaths into path[pos]
.
This is a great exercise for a CS2 (datastructures) class.
Every item in the list you call "sorted" (but which really is just ordered according to some opaque criteria) has a position or index within the list.
THAT position/index is the key to use in sorting the other list.
For each element in the list to be sorted, look it up in the "ordered" list and apply the found element's index as the sort key.
You probably realize that you need to sort based on services index. Here another way to do what you need:
public static class ServicePath{
private Integer index = 0;
private String path = null;
public ServicePath(Integer index, String path){
this.index = index;
this.path = path;
}
public Integer getIndex() {
return index;
}
public void setIndex(Integer index) {
this.index = index;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
}
List<ServicePath> servicesPaths = Files.walk(Paths.get(parentFolder))
.map(Path::toString)
.map( path -> {
for (int i = 0; i < services.size(); i++) if ( path.endsWith(version + ".jar" ) && path.contains( services.get(i) ) ) return new ServicePath(i, path);
return null;
})
.filter( sp -> sp != null)
.collect( Collectors.toList() );
servicesPaths.sort( (sp1, sp2) -> sp1.getIndex().compareTo( sp2.getIndex() ) );
List<String> sortedPaths = servicesPaths.stream().map( sp -> sp.getPath() ).collect( Collectors.toList() );
I'm trying to calculate the degree of each node in a graph. However I'm having trouble because the nodes are part of the node class and I don't know how to convert them to String. At least I think that's what's wrong.
Here's what I've been trying, I have a Hashset where I store the nodes and another one for the edges (undirected graph), and I need to get a table with all the degrees that exists followed by the nodes that have those degrees:
public void DegreeList () {
List<Nodes> listnodes = new ArrayList<Nodes>(Node);
Map <Integer, List<Nodes>> DegreeList = new HashMap<Integer, List<Nodes>>();
for (Nodes n: Node){
int degree=0;
for (Edges e: Edge){
if (n.equals(e.start)||n.equals(e.end)){
degree++;
DegreeList.put(degree,n);
}
}
}
}
The error from Eclipse is for the last line and says:
The method put(Integer, List) in the type Map> is not applicable for the arguments (int, Nodes).
I'm open to try other approach.
Edit: Nodes and Edges are classes. Edge and Node are the Hashsets storing the values. (Sorry for any confusion)
Working Assumptions
It looks from your code as if the type Nodes represents a single node, and Node represents a Collection of nodes. (And that assumption was confirmed by your edit.) Those names seem backwards, but I'm going by what the code is doing with them. Please correct me if I'm wrong.
The Immediate Question
There are several problems here, but the immediate one is pretty simple: your Map expects a value of type List<Nodes>, but you're giving it a single instance of Nodes. If you can change your Map to a Guava Multimap then please do so. Otherwise, instead of
DegreeList.put(degree, n);
you'll need something like
List<Nodes> innerList = DegreeList.get(degree);
if (innerList == null) {
innerList = new ArrayList<Nodes>();
DegreeList.put(degree, innerList);
}
innerList.add(n);
That way there's a List associated with each degree. You need this because a Map can only store one value with each key. If your Map was defined like Map<Integer, Nodes> then you could only store one node with each distinct degree number. But that doesn't make any sense, does it? Any number of nodes could share the same degree number. So you need a Map that associates an Integer (representing degree) with a Collection of nodes. You seem to be using List as your chosen Collection. Set would probably be better.
Using Set, you'd define your Map as
Map<Integer, Set<Nodes>> degreeMap = new HashMap<>();
Then, when it came time to put something into the Map you'd do it like this:
Set<Nodes> innerSet = degreeMap.get(degree);
if (innerSet == null) {
innerSet = new HashSet<>();
degreeMap.put(degree, innerSet);
}
innerSet.add(n);
In either case you no longer need your listNodes List.
Other Observations
The code above describes how to put something into the Map. But we also need to think about when to put something into the Map. Right now you have code inserting into the Map each time there's an edge that matches the node you're evaluating:
for (Edges e: Edge){
if (n.equals(e.start)||n.equals(e.end)){
degree++;
DegreeList.put(degree,n); // this shouldn't be here
}
}
// instead, it belongs here
Instead, you should insert into the Map only once per node, after determining the node's degree:
for (Edges e: Edge){
if (n.equals(e.start)||n.equals(e.end)){
degree++;
}
}
// insert into Map here
Set<Nodes> innerSet = degreeMap.get(degree);
if (innerSet == null) {
innerSet = new HashSet<>();
degreeMap.put(degree, innerSet);
}
innerSet.add(n);
While Erick's answer is correct, it is not very efficient. If you want to calculate the degree of all nodes in a graph, and you want to store that in a map keyed by degrees, a faster algorithm would be the following:
public static Map<Integer, ArrayList<Node>> nodesByDegree(
Collection<Edge> edges, Collection<Node> nodes) {
HashMap<Node, Integer> degrees = new HashMap<>();
// initialize all nodes with 0 degrees
for (Node n : nodes) {
degrees.put(n, 0);
}
// calculate all degrees at the same time, in a single pass through E
for (Edge e : edges) {
degrees.put(e.start, degrees.get(n.start)+1);
degrees.put(e.end, degrees.get(n.end)+1);
}
// transform into multimap
HashMap<Integer, ArrayList<Node>> result = new HashMap<>();
for (Map.Entry<Node, Integer> e : degrees) {
if ( ! result.containsKey(e.getValue()) {
result.put(e.getValue(), new ArrayList<Node>());
}
result.get(e.getValue()).add(e.getKey());
}
return result;
}
Note that, in the above code, edges is a collection of Edge, and nodes is a collection of Node. This code requires O(|V|) + O(|E|) run-time, which should be much lower than O(|V|) * O(|E|)
so I have a list of basic nodes, for example nodes A B C.
each component can see what it is attached to for example:
a->b
b->c
c->a
I want a way that I can get a list of all the nodes in the graph. However, I'm running into trouble as my current system can't detect if it has already reached a point. EG in the above example it will go a->b->c->a->b etc. How can I detect this or how can I solve this problem.
My current "solution" getList() in the Node class:
ArrayList<Node> tempList = new ArrayList<Node>();
tempList.add(this);
for(int i = 0 ; i < nodesAttachedTo.size();i++){
tempList.addAll(nodesAttachedTo.get(i).getList());
}
return tempList;
You can use a HashSet. It will not allow one element to be added twice.
Here's an example code that first creates the graph similar to your example, then starts at some point in the graph and goes through it.
import java.util.HashSet;
public class Node
{
private HashSet<Node> nextNodes = new HashSet<Node>();
public Node()
{
}
public void addNextNode(Node node)
{
nextNodes.add(node);
}
public static void main(String[] args)
{
// this builds the graph of connected nodes
Node a = new Node();
Node b = new Node();
Node c = new Node();
a.addNextNode(b);
b.addNextNode(c);
c.addNextNode(a);
//this is the set that will lsit all nodes:
HashSet<Node> allNodes = new HashSet<Node>();
// this goes through the graph
a.listAllNodes(allNodes);
System.out.println(allNodes);
}
private void listAllNodes (HashSet<Node> listOfNodes)
{
// try to put all next nodes of the node into the list:
for(Node n : nextNodes)
{
if (listOfNodes.add(n)) // the set returns true if it did in fact add it.
n.listAllNodes(listOfNodes); // recursion
}
}
}
This goes from one node to all the nodes this node knows. (say that really fast three times)
Until it hits a dead end (= a node it already visited)
I chose to use a HashSet in the Node itself to store all the nodes it knows.
This could also be an ArrayList or whatever. But as I don't think there should be a connection twice, a HashSet seems to be a good choice in this situation, too.
I'm not familiar with your notation, but you could use two pointers to solve your issue. Start with two pointers that point to the same node. Increment one pointer until it returns to the start. Some pseudocode is below.
ArrayList<Node> tempList = new ArrayList<Node>();
Node head = nodesAttachedTo.get(0); //get the head of the list
tempList.add(head);
Node runner = head;
runner = runner.next;
while (!runner.equals(head)) {
tempList.add(runner);
runner = runner.next;
}
A hashmap is probably the way to go here. It allows constant time access (some overhead required, but I'm assuming you want a solution that scales well) to any element in the map.
HashMap<String, String> specificSolution = new HashMap<String, String>();
specificSolution.put("a", "b");
specificSolution.put("b", "c");
specificSolution.put("c", "a");
// To get all nodes in the graph
Set<String> nodes = specificSolution.keySet();
I implemented with String here because you don't provide a definition for the Node Class in your question, but it can be easily swapped out.
There are many different ways to represent a graph and each has their own limitations/advantages. Maybe another might be more appropriate but we would need more information about the problem.