From 5dcbf60660da51570ab207733d5636ed6e8db6f7 Mon Sep 17 00:00:00 2001 From: mercury Date: Thu, 4 Dec 2003 02:28:46 +0000 Subject: [PATCH] Initial revision --- Graph.java | 511 ++++++++++++++++++++++++++++++++++++++++++++ GraphDriver.java | 193 +++++++++++++++++ GraphException.java | 9 + GraphNode.java | 44 ++++ KeyedItem.java | 21 ++ 5 files changed, 778 insertions(+) create mode 100644 Graph.java create mode 100644 GraphDriver.java create mode 100644 GraphException.java create mode 100644 GraphNode.java create mode 100644 KeyedItem.java diff --git a/Graph.java b/Graph.java new file mode 100644 index 0000000..be29ca9 --- /dev/null +++ b/Graph.java @@ -0,0 +1,511 @@ +import java.util.ArrayList; + +/** + * A representation of a mathematical relationship model using Java. It supports
+ * the ability to add and remove items and relationships between the items from the graph
+ * as well as finding various items in the graph using several different algorithms. + * @author Andrew Coleman + */ +public class Graph { + //size integer variable to keep track of number of nodes in the graph + private int size; + //Double two-dimensional array for the adjacency matrix + private double[][] adjacent; + //Array for holding the movie edges + private String[][] movielist; + //Array for holding the dates of movies + private int[][] datelist; + //boolean to determine whether a graph is directed + private boolean directed; + //Vertex storage list + private ArrayList vertexList; + //int to determin generic unweighted value + private final int UNWEIGHTED_VALUE = 1; + + /** + * Default Constructor, creates an undirected graph. + */ + public Graph () { + size = 0; + adjacent = new double[size][size]; + movielist = new String[size][size]; + datelist = new int[size][size]; + vertexList = new ArrayList(); + directed = false; + } + + /** + * Constructor, takes a boolean to determine directed or undirected. + */ + public Graph ( boolean param ) { + size = 0; + adjacent = new double[size][size]; + movielist = new String[size][size]; + datelist = new int[size][size]; + vertexList = new ArrayList(); + directed = param; + } + + /** + * Removes all vertecies in the graph and sets the size to zero. + */ + public void makeEmpty() { + size = 0; + adjacent = new double[size][size]; + movielist = new String[size][size]; + datelist = new int[size][size]; + vertexList.clear(); + } + + /** + * Convienence method for determining if the graph is empty. + * @return boolean if the graph is empty. + */ + public boolean isEmpty() { + return ( size == 0 ); + } + + /** + * Convienence method for determining the number of vertecies in the graph. + * @return int of the size of the graph.pat + */ + public int numVertices() { + return size; + } + + /** + * Determines the number of edges between all points in the graph. + * @return int of the number of edges. + */ + public int numEdges() { + int c = 0; + for ( int x = 0; x < size; x++ ) { + for ( int y = 0; y < size; y++ ) { + if ( adjacent[x][y] != Double.POSITIVE_INFINITY ) { + c++; //But wait, it's Java! + } + } + } + return c; + } + + /** + * Method to resize the adjacency matrix. + * @param Size integer value of the size of the graph. + */ + private void resizeAdjacent ( int size ) { + double[][] temp = new double[size][size]; + String[][] tmovie = new String[size][size]; + int[][] tdate = new int[size][size]; + for( int x = 0; x < size - 1; x++) + for ( int y = 0; y < size - 1; y++ ) { + temp[x][y] = adjacent[x][y]; + tmovie[x][y] = movielist[x][y]; + tdate[x][y] = datelist[x][y]; + } + for ( int x = 0; x < size; x++ ) { + temp[x][size - 1] = Double.POSITIVE_INFINITY; + temp[size - 1][x] = Double.POSITIVE_INFINITY; + tmovie[x][size - 1] = ""; + tmovie[size - 1][x] = ""; + tdate[x][size - 1] = 0; + tdate[size - 1][x] = 0; + } + adjacent = temp; + movielist = tmovie; + datelist = tdate; + } + + /** + * Adds a new GraphNode into the graph. + * @param GraphNode the node to be added. + * @throws GraphException if the node is already in the graph. + */ + public void addVertex ( GraphNode myItem ) throws GraphException { + if ( findIndex ( myItem.getKey() ) >= 0 ) + throw new GraphException ( "Vertex already exists!" ); + size++; + vertexList.add ( myItem ); + resizeAdjacent ( vertexList.size() ); + } + + /** + * Adds an edge with a weight. + * @param searchKey1 first vertex to use in the edge. + * @param searchKey2 second vertex to use in the edge. + * @param weight double value of the edge between searchKey1 and searchKey2. + * @throws GraphException if an edge already exists. + */ + public void addEdge ( Comparable searchKey1, Comparable searchKey2, double weight ) throws GraphException { + int x = findIndex ( searchKey1 ); + int y = findIndex ( searchKey2 ); + if ( x < 0 || y < 0 ) + throw new GraphException ( "No matching vertecies were found!" ); + if ( adjacent[x][y] > 0 ) + throw new GraphException ( "Edge already exists!" ); + adjacent[x][y] = weight; + } + + /** + * Adds an edge between two actors and also records the name and date of the film connecting the actors. + * @param searchKey1 first actor to find. + * @param searchKey2 second actor to find. + * @param movie movie that both actors starred in. + * @param date date of the movie's release. + * @throws GraphException if a duplicate was found or if no vertecies were found. + */ + public void addEdge ( Comparable searchKey1, Comparable searchKey2, String movie, String date ) throws GraphException { + int x = findIndex ( searchKey1 ); + int y = findIndex ( searchKey2 ); + if ( x < 0 || y < 0 ) + throw new GraphException ( "No matching vertecies were found!" ); + int datenum = Integer.parseInt ( date ); + if ( datenum == 0 || datelist[x][y] < datenum || (datelist[x][y] == datenum && movielist[x][y].compareTo ( movie ) > 0) ) { + adjacent[x][y] = UNWEIGHTED_VALUE; + adjacent[y][x] = UNWEIGHTED_VALUE; + movielist[x][y] = movie; + movielist[y][x] = movie; + datelist[x][y] = datenum; + datelist[y][x] = datenum; + } + else { + throw new GraphException ( "Duplicate edge exists!" ); + } + } + + /** + * Adds an unweighted edge between two keys in the graph. + * @param searchKey1 first vertex to find. + * @param searchKey2 second vertex to find. + * @throws GraphException if a duplicate was found or no matching vertecies found. + */ + public void addEdge ( Comparable searchKey1, Comparable searchKey2 ) throws GraphException { + int x = findIndex ( searchKey1 ); + int y = findIndex ( searchKey2 ); + if ( x < 0 || y < 0 ) + throw new GraphException ( "Vertecies not found!" ); + if ( adjacent[x][y] > 0 ) + throw new GraphException ( "Edge already exists!" ); + adjacent[x][y] = UNWEIGHTED_VALUE; + if(!directed) { + adjacent[y][x] = UNWEIGHTED_VALUE; + } + } + + /** + * Returns the value of the edge between two keys in the graph. + * @param searchKey1 first vertex to find. + * @param searchKey2 second vertex to find. + * @throws GraphException if a duplicate was found. + */ + public double getWeight ( Comparable searchKey1, Comparable searchKey2 ) throws GraphException { + int x = findIndex ( searchKey1 ); + int y = findIndex ( searchKey2 ); + if ( x < 0 || y < 0 ) + throw new GraphException ( "Edge does not exist!" ); + return adjacent[x][y]; + } + + /** + * Returns the movie connection between two actors. + * @param searchKey1 first actor to find. + * @param searchKey2 second actor to find. + * @return String representing the oldest and first movie alphabetically found to be linking the two actors. + * @throws GraphException if no edge was found. + */ + public String getMovie ( Comparable searchKey1, Comparable searchKey2 ) throws GraphException { + int x = findIndex ( searchKey1 ); + int y = findIndex ( searchKey2 ); + if ( x < 0 || y < 0 ) + throw new GraphException ( "Movie connection does not exist!" ); + return movielist[x][y] + "(" + datelist[x][y] + ")"; + } + + /** + * Returns a vertex by index. + * @param index Integer number representing the vertex you want. + * @return GraphNode of the vertex at the specified index. + * @throws GraphException if index is out of range ( 1 - size ). + */ + public GraphNode getVertex ( int index ) throws GraphException { + if ( index > size || index < 0 ) + throw new GraphException ( "Index out of bounds!" ); + else + return (GraphNode)vertexList.get ( index ); + } + + /** + * Returns the vertex represented by a searchable key. + * @param searchKey vertex to find. + * @return GraphNode representing the vertex at key searchKey, + * @throws GraphException if vertex is not found, + */ + public GraphNode getVertex ( Comparable searchKey ) throws GraphException { + return getVertex ( findIndex ( searchKey ) ); + } + + /** + * Returns the searchable key found at a particular index. + * @param index integer index to get the key from. + * @return Comparable of the searchable key. + * @throws GraphException if the index is out of range. + */ + public Comparable getSearchKey ( int index ) throws GraphException { + if ( index > size || index < 0 ) + throw new GraphException ( "Index out of range!" ); + return ((GraphNode)(vertexList.get ( index ))).getKey(); + } + + /** + * Private method for finding the index of a given searchable key. + * @param key of the vertex to find in the graph. Assumes to be in the range 1 - size. + * @return int representing the index of the vertex or -1 if no vertex is found. + */ + private int findIndex ( Comparable key ) { + for ( int i = 0; i < vertexList.size(); i++ ) + if ( ((GraphNode)vertexList.get ( i )).getKey().compareTo ( key ) == 0 ) + return i; + return -1; + } + + /** + * Clears all vertices, sets all to be unmarked. + */ + private void clearMarks() { + for ( int x = 0; x < size; x++ ) { + ((GraphNode)vertexList.get ( x )).setMarked ( false ); + } + } + + /** + * Removes an edge between two searchable keys. + * @param searchKey1 first vertex to find. + * @param searchKey2 second vertex to find. + * @throws GraphException if vertecies do not exist. + */ + public void removeEdge ( Comparable searchKey1, Comparable searchKey2 ) throws GraphException { + int a = findIndex ( searchKey1 ); + int b = findIndex ( searchKey2 ); + if ( a == -1 || b == -1 ) throw new GraphException ( "Entry not found in list!" ); + adjacent[a][b] = Double.POSITIVE_INFINITY; + movielist[a][b] = ""; + if ( !directed ) { + adjacent[b][a] = Double.POSITIVE_INFINITY; + movielist[b][a] = ""; + } + } + + /** + * Removes a vertex by a searchable key. + * @param key the vertex to find. + * @return GraphNode of the vertex removed from the graph. + * @throws GraphException if the vertex is not in the graph. + */ + public GraphNode removeVertex ( Comparable key ) throws GraphException { + int index = findIndex ( key ); + if ( index == -1 ) throw new GraphException ( "Vertex not in graph!" ); + double[][] temp = new double[adjacent.length - 1][adjacent.length - 1]; + String[][] mtemp = new String[adjacent.length - 1][adjacent.length - 1]; + for ( int row = 0; row < adjacent.length; row++ ) { + for ( int col = 0; col < adjacent[0].length; col++ ) { + if ( row != index && col != index ) { + int newrow = row; + int newcol = col; + /* make sure the row/col are not in the row/col of the vertex to be removed */ + if ( row > index ) newrow = row - 1; + if ( col > index ) newcol = col - 1; + temp[newrow][newcol] = adjacent[row][col]; + mtemp[newrow][newcol] = movielist[row][col]; + } + } + } + adjacent = temp; + movielist = mtemp; + size--; + return (GraphNode)vertexList.remove ( index ); + } + + /** + * Returns the breadth-first traversal of a searchable key. + * @param searchKey vertex to start from. + * @return ArrayList of the vertecies in the path. Empty if no path is found. + */ + public ArrayList bft ( Comparable searchKey ) { + GraphNode temp; + ArrayList searchList = new ArrayList(); + QueueReferenceBased bfsQueue = new QueueReferenceBased(); + bfsQueue.enqueue ( vertexList.get ( findIndex ( searchKey ) ) ); + getVertex ( searchKey ).setMarked ( true ); + searchList.add ( getVertex ( searchKey ) ); + + while ( !bfsQueue.isEmpty() ) { + temp = (GraphNode)bfsQueue.dequeue(); + + for ( int g = 0; g < size; g++ ) { + if ( adjacent[findIndex ( temp.getKey() )][g] != Double.POSITIVE_INFINITY && !getVertex ( g ).isMarked() ) { + ((GraphNode)vertexList.get ( g )).setMarked ( true ); + bfsQueue.enqueue ( vertexList.get ( g ) ); + searchList.add ( getVertex ( g ) ); + } + } + } + clearMarks(); + return searchList; + } + + /** + * Determines the diameter of the graph, returns infinity if unconnected. + * @return int representing the diameter of the graph. + */ + public double diameter() { + double mins[] = new double[size]; + for ( int x = 0; x < size; x++ ) { + mins[x] = 0; + ArrayList t = bft ( getVertex ( x ).getKey() ); + if ( t.size() != size ) return Double.POSITIVE_INFINITY; + for ( int y = 1; y < t.size(); y++ ) { + mins[x] += getWeight ( ((GraphNode) t.get ( y - 1 )).getKey(), ((GraphNode) t.get ( y )).getKey() ); + } + } + + double minsize = mins[0]; + for ( int x = 0; x < size; x++ ) { + if ( minsize > mins[x] ) + minsize = mins[x]; + } + return minsize; + } + + /** + * Returns a breadth-first search between two searchable keys. + * @param searchKey1 first key to find. + * @param searchKey2 second key to find. + * @return ArrayList containing the path between the two keys, empty if no path is possible. + */ + public ArrayList bfs ( Comparable searchKey1, Comparable searchKey2 ) { + ArrayList p = bft ( searchKey1 ); + Comparable curkey = searchKey1; + int x = 0; + for ( x = 0; x < p.size() && curkey.compareTo ( searchKey2 ) != 0; x++ ) + curkey = ((GraphNode) p.get ( x )).getKey(); + + if ( x > p.size() / 2 ) { + for ( int y = p.size() - 1; y > x; y-- ) + p.remove( y ); + } + else { + ArrayList q = new ArrayList ( x ); + for ( int y = 0; y < x; y++ ) + q.add ( p.get ( x ) ); + p = q; + } + return p; + } + + /** + * Performs a depth-first search for a searchable key. + * @param searchKey vertex to find. + * @return ArrayList containing the path to the vertex. + * @throws GraphException if no path is found. + */ + public ArrayList dfs ( Comparable searchKey ) throws GraphException { + ArrayList dfsList = new ArrayList(); + dfsList = dfsRec ( getVertex ( searchKey ), dfsList ); + clearMarks(); + if ( dfsList.isEmpty() ) { + throw new GraphException ( "No path is found!" ); + } + return dfsList; + } + + /** + * Private recursive method called to generate a breadth-first search. + * @param vertex GraphNode to search from. + * @param searchRecList list to use while recursing. + * @return ArrayList of the generated path. + */ + private ArrayList dfsRec ( GraphNode vertex, ArrayList searchRecList ) { + searchRecList.add ( vertex ); + vertex.setMarked ( true ); + int i = vertexList.indexOf ( vertex ); + for ( int j = 0; j= weight[j] && !((GraphNode)vertexList.get ( j )).isMarked() ) { + /* note smallest vertex */ + smallest = j; + smallestweight = weight[j]; + } + /* mark smallest vertex */ + GraphNode smallnode = (GraphNode)vertexList.get ( smallest ); + smallnode.setMarked ( true ); + /* update the weight/path arrays */ + for ( int j = 0; j < weight.length; j++ ) + /* if a new weight to that vertex is less than the current weight, change the weight in the array and change the path arraylist */ + if ( weight[j] > weight[smallest] + adjacent[smallest][j] ) { + weight[j] = weight[smallest] + adjacent[smallest][j]; + path.set ( j, vertexList.get ( smallest ) ); + } + } + /* backwards path */ + ArrayList result = new ArrayList(); + GraphNode node = getVertex ( lastkey ); + int lastindex = findIndex ( lastkey ); + /* while the node is not the first node */ + while ( lastindex != firstindex ) { + result.add ( node ); + node = (GraphNode)path.get ( lastindex ); + int newlastindex = findIndex ( node.getKey() ); + /* if the indexes are the same, then there is no way to get to that vertex */ + if ( newlastindex == lastindex ) + throw new GraphException ( "No connecting path!" ); + else + lastindex = newlastindex; + } + /* gotta add the first one */ + result.add ( getVertex ( firstkey ) ); + + /* reverse the arraylist */ + path.clear(); + for ( int i = result.size() - 1; i >= 0; i-- ) + path.add ( result.get ( i ) ); + return path; + } +} diff --git a/GraphDriver.java b/GraphDriver.java new file mode 100644 index 0000000..ef29023 --- /dev/null +++ b/GraphDriver.java @@ -0,0 +1,193 @@ +import java.util.ArrayList; +import java.io.*; + +/** + * + * @author Coleman + * This class provides a wrapper for the Graph object and parses all input files. + */ +public class GraphDriver { + + // only need one graph + private static Graph mygraph; + + /** + * Prints a path of actors from an ArrayList. + * @param path An ArrayList returned from shortestPath or the like. + */ + private static void printPath ( ArrayList path ) { + for ( int x = 0; x < path.size() - 1; x++ ) { + Comparable one = ((GraphNode)path.get ( x )).getKey(); + Comparable two = ((GraphNode)path.get ( x + 1 )).getKey(); + String movie = mygraph.getMovie ( one, two ); + System.out.println ( one.toString() + " starred with " + two.toString() + " in the movie " + movie ); + } + } + + /** + * Prints a path of actors from an ArrayList. + * @param path An ArrayList returned from shortestPath or the like. + */ + private static void printPathToo ( ArrayList path ) { + for ( int x = 0; x < path.size() - 1; x++ ) { + System.out.print ( ((GraphNode)path.get ( x )).getKey() ); + if ( x != path.size() - 2 ) + System.out.print ( ", " ); + else + System.out.println(); + } + } + + /** + * Parses and inserts all data from an input file. + * @param filename Name of the file to be read. + * @param filenum Number of the file in the sequence, can be -1 if not known. + * @param len Total number of input files. + */ + private static void readFile ( String filename, int filenum, int len ) { + BufferedReader read = null; + ArrayList actors = new ArrayList(); + try { + String t = ""; + if ( filenum != -1 ) { + t = " (" + (filenum + 1) + "/" + len + ")"; + } + System.out.println ( "***Reading from file: " + filename + t ); + read = new BufferedReader ( new FileReader ( filename ) ); + //this big loop reads in the movie name and all the actors + while ( read.ready() ) { + String line = read.readLine(); + if ( line.equals ( "" ) ) continue; + //split the date off the movie name, makes for easier tie breaking. + String data[] = line.split ( "\\(\\d{4}\\)\\s*$" ); + String date = line.substring ( data[0].length() + 1, data[0].length() + 5 ); + data[0].trim(); + + //get all the actors in a usable form + line = read.readLine(); + while ( !line.equals ( "" ) ) { + actors.add ( line ); + line = read.readLine(); + } + String actor[] = new String[actors.size()]; + actor = (String [])actors.toArray ( actor ); + //now we must add all the actors into the graph + for ( int x = 0; x < actor.length; x++ ) { + try { + mygraph.addVertex ( new GraphNode ( actor[x] ) ); + } + catch ( GraphException exception ) {} + } + //and add an edge between all the actors for this movie + for ( int x = 0; x < actor.length - 1; x++ ) { + for ( int y = x + 1; y < actor.length; y++ ) { + try { + mygraph.addEdge ( actor[x], actor[y], data[0], date ); + } + catch ( GraphException exception ) {} + } + } + //start all over again + actors.clear(); + }// end while + } + catch ( IOException exception ) { + System.out.println ( "Error reading the file: " + filename ); + exception.printStackTrace(); + } + } + + public static void main ( String[] args ) { + if ( args.length < 1 ) { + System.out.println ( "Usage: java GraphDriver [input files]" ); + System.exit ( 1 ); + } + + mygraph = new Graph(); + + for ( int filenum = 0; filenum < args.length; filenum++ ) { + readFile ( args[filenum], filenum, args.length ); + } + + /* this block is the user interface. it is simple, but yet still allows for all + * the neccessary functionality of the project. + */ + String command = ""; + while ( !command.equals ( "quit" ) ) { + System.out.println ( "*Commands*\t*Description*" ); + System.out.println ( "path\t\tDjikstra's Shortest Path" ); + System.out.println ( "bfs\t\tBreadth-First Search" ); + System.out.println ( "add\t\tUpdate Graph From File" ); + System.out.println ( "dia\t\tCompute Diameter Of Graph" ); + System.out.println ( "quit\t\tQuit" ); + System.out.print ( "> " ); + BufferedReader in = new BufferedReader ( new InputStreamReader ( System.in ) ); + try { + command = in.readLine().trim(); + } + catch ( IOException exception ) { + System.out.println ( "bailing from:" ); + exception.printStackTrace(); + break; + } + if ( command.equals( "path" ) ) { + System.out.print ( "actors (one,two)> " ); + String actors[] = null; + try { + actors = (String [])in.readLine().trim().split ( "," ); + } + catch ( IOException exception ) { + System.out.println ( "bailing from:" ); + exception.printStackTrace(); + break; + } + if ( actors.length != 2 ) { + System.out.println ( "Enter two actors only!" ); + } + ArrayList path = mygraph.shortestPath ( actors[0].trim(), actors[1].trim() ); + printPath ( path ); + } + else if ( command.equals( "bfs" ) ) { + System.out.print ( "actors (one,two)> " ); + String actors[] = null; + try { + actors = (String [])in.readLine().trim().split ( "," ); + } + catch ( IOException exception ) { + System.out.println ( "bailing from:" ); + exception.printStackTrace(); + break; + } + if ( actors.length != 2 ) { + System.out.println ( "Enter two actors only!" ); + } + //put in real bfs here for two states + ArrayList path = mygraph.bfs ( actors[0].trim(), actors[1].trim() ); + printPathToo ( path ); + } + else if ( command.equals ( "add" ) ) { + System.out.print ( "read from file> " ); + String filename[] = null; + try { + filename = (String [])in.readLine().trim().split ( " " ); + } + catch ( IOException exception ) { + System.out.println ( "bailing from:" ); + exception.printStackTrace(); + break; + } + for ( int q = 0; q < filename.length; q++ ) { + readFile ( filename[q], q, filename.length ); + } + } + else if ( command.equals ( "dia" ) ) { + System.out.println ( "Please wait while searching through " + mygraph.numVertices() + " vertecies..." ); + System.out.println ( "The diameter of the graph is " + mygraph.diameter() ); + } + else if ( command.equals ( "quit" ) ) { + break; + } + System.out.println(); + } + } +} diff --git a/GraphException.java b/GraphException.java new file mode 100644 index 0000000..bb72582 --- /dev/null +++ b/GraphException.java @@ -0,0 +1,9 @@ +/** + * @author Coleman + * This class provides an exception for handling errors within the graph. + */ +public class GraphException extends RuntimeException { + public GraphException ( String s ) { + super(s); + } +} \ No newline at end of file diff --git a/GraphNode.java b/GraphNode.java new file mode 100644 index 0000000..36934ca --- /dev/null +++ b/GraphNode.java @@ -0,0 +1,44 @@ +/** + * @author Andrew Coleman + * GraphNode is an object representing a state or item in a tree. Allows for one unique searchable key to identify the vertex. + */ + +public class GraphNode extends KeyedItem +{ + //the current state of this vertex + private boolean mark; + + /** + * Constructor, makes a new GraphNode. + * @param item A Comparable for the unique searchable key of this vertex. + */ + GraphNode ( Comparable item ) { + super ( item ); + mark = false; + } + + /** + * Returns true if node is marked, false if it is not. + * @return A boolean of the current GraphNode's state. + */ + public boolean isMarked() { + return mark; + } + + /** + * Sets value of mark to true or false. + * @param boolean State of the current vertex. + */ + public void setMarked ( boolean setbool ) { + mark = setbool; + } + + /** + * Allows for the comparison of two GraphNode objects. + * @param node A GraphNode to be compared against the current object. + * @return Returns a boolean indicating if the two GraphNodes are equal. + */ + public boolean equals ( GraphNode node ) { + return ( node.getKey().compareTo ( this.getKey() ) == 0); + } +} \ No newline at end of file diff --git a/KeyedItem.java b/KeyedItem.java new file mode 100644 index 0000000..7b39b1d --- /dev/null +++ b/KeyedItem.java @@ -0,0 +1,21 @@ +//given to me from mark boshart +public abstract class KeyedItem +{ + private Comparable searchKey; + + public KeyedItem(Comparable key) + { + searchKey = key; + } // end constructor + + public Comparable getKey() + { + return searchKey; + } // end getKey + + public String toString() + { + return searchKey.toString(); + } + +} \ No newline at end of file