I have List<Vector3D> , where Vector3D is a coordinate. I want to find sum of all distance between Vector3D elements of list. I want to find it using java 8 streams. I try to use reduce but it cant help me.
UPD:
Class Vector3D has method double distance(Vector3D) witch find distance between two positions. e.g. i have list with (1,0,0) (2,0,0) (3,0,0). As a result i want to find length of this path. It is 3.
If we are using java 7 or lower we have to do:
public static double calcPathLength(List<Vector3D> path){
double length = 0d;
for (int i=0; i< path.size()-1; i++){
length += path.get(i).distance(path.get(i+1));
}
return length;
}
The operation you are performing is called Mutable reduction.
Pshemo’s answer shows how you can implement such operation ad-hoc by providing the three necessary functions. However, when all three functions are implemented by a dedicated class it might be useful to implement these functions inside a class implementing Collector for easier reuse:
public class Distance implements Collector<Vector3D, Distance.Helper, Double> {
public static final Distance COLLECTOR = new Distance();
static final class Helper {
private double sum = 0;
private Vector3D first = null, previous = null;
}
public Set<Characteristics> characteristics() {
return Collections.emptySet();
}
public Supplier<Helper> supplier() {
return Helper::new;
}
public BiConsumer<Helper, Vector3D> accumulator() {
return (helper,vector3d)-> {
if (helper.previous != null)
helper.sum += vector3d.distance(helper.previous);
else helper.first = vector3d;
helper.previous = vector3d;
};
}
public BinaryOperator<Helper> combiner() {
return (h1,h2)-> {
h2.sum += h1.sum;
if(h1.previous!=null && h2.first!=null) {
h2.sum += h1.previous.distance(h2.first);
h2.first=h1.first;
}
return h2;
};
}
public Function<Helper, Double> finisher() {
return helper -> helper.sum;
}
}
You will recognize the three function from the ad-hoc version. New is a fourth function, finisher which allows to specify how the final result can be extracted from the mutable container so we don’t need the getSum() call.
The use case simplifies to:
List<Vector3D> list;
//…
double distance=list.stream().collect(Distance.COLLECTOR);
One of the options would be creating some helper class which would remember previously used vector and based on it calculate difference between it and current vector. This class could look like
class DistanceHelper {
private double sum = 0;
private Vector3D first = null;
private Vector3D last = null;
public void add(Vector3D vector3d) {
if (first == null)
first = vector3d;
if (last != null)
sum += vector3d.distance(last);
last = vector3d;
}
public void combine(DistanceHelper otherHelper) {
//add distance of path from current thread with distance of path
//from other thread
sum += otherHelper.sum;
//also add distance between paths handled by separate threads like
// when path of Thread1 is A->B and Thread2 is C->D then we need to
// include path from `B` to `C`
if (this.last!=null && otherHelper.first!=null)
sum += this.last.distance(otherHelper.first);
this.last = otherHelper.last;
}
public double getSum() {
return sum;
}
}
and you can use it for example with combine instead of reduce like
double sum = list
.stream()//or parallelStream()
.collect(DistanceHelper::new, DistanceHelper::add,
DistanceHelper::combine).getSum();
Related
I'm doing something that produces the right result. However, it is wrong from a design POV.
The point of the program is to list the result of all the powers of a number up to and including the user-defined limit.
I have a constructor which accepts the base and the exponent from the Scanner. Then a method, which utilises a for loop to calculate the power for each exponent.
Now, the problem is that I'm printing the result from each loop iteration directly from this method. This beats the point of private variables and it being void in the 1st place.
Therefore, I want to define a getter method which returns the result of each power to the output. I used to set them just fine for if/switch statements, but I don't know how to do the same for loops. If I assign the result to a variable within the loop and return that variable from the getter then it will return only the output from the final iteration.
Private implementation
package Chapter6Review;
public class Powers {
private int target;
private int power;
public Powers(int target, int power) {
this.target = target;
this.power = power;
}
public void calculatePower() {
for (int i = 0; i <= power; i++) {
System.out.println((int) Math.pow(target, i));
}
}
/*
public int getPower() {
return
}
*/
}
User interface
package Chapter6Review;
import java.util.Scanner;
public class PowersTester {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.print("Enter your base: ");
int target = in.nextInt();
System.out.print("Enter your exponent: ");
int power = in.nextInt();
Powers tester = new Powers(target, power);
tester.calculatePower();
}
}
You can simply use a List ;
public List<Integer> calculatePower() {
int p;
List<Integer> result = new ArrayList<Integer>();
for (int i = 0; i <= power; i++) {
p = (int) Math.pow(target, i);
result.add(p);
}
return result;
}
Then in you main method, you can iterate the list to print the powers like that :
List<Integer> result = new ArrayList<Integer>();
Powers tester = new Powers(target, power);
result = tester.calculatePower();
for (int i = 0; i < result.size(); i++) {
System.out.println(result.get(i));
}
You could store each of the results in a List:
List<Power> list = new ArrayList<>();
and when you call it add it as well
list.add(new Powers(target, power));
At the end you can iterate over the list like this:
for (Power power : list){
// your code
}
You might consider using streams as well
public List<Integer> calculatePower() {
return IntStream
.rangeClosed(0, power). // iterate from 0 till power inclusive
.mapToObj(i -> (int) Math.pow(target,i))
.collect(Collectors.toList()); // get result as list
}
Thanks for all the answers. Using a list seems to be a good choice.
Since I haven't covered lists yet, I resorted to this solution for now. But I don't like having code that can affect the solution in the main. Ideally, the loop should go in the private implementation.
Main
Powers tester = new Powers(target, power);
for (int i = 0; i <= power; i++) {
tester.calculatePower(i);
System.out.println(tester.getPower());
}
Private implementation
public void calculatePower(int iPower) {
result = (int) Math.pow(target, iPower);
}
public int getPower() {
return result;
}
While trying out the merge in ConcurrentHashMap, I designed the following class to do the following:
Add a value (double) for a key. If there is an existing Statistic for that key then recalculate the running total (which is the sum of all the values so far seen), the counter is incremented (to take into account the number of hits).
To clarify, let's take an example: we need to hold the response times for a particular method invocation. For each response time, we invoke the "addStatistic" method and pass in a "response time". The response time is accumulated, the counter is incremented (to account for one more invocation event) and the min and max are recalculated and added as a Statistic back for that key.
This is a draft implementation of the functionality that captures, for example, the "response time" of an invocation.
Now the ConcurrentHashMap.merge, is documented as atomic and am taking care to not leak any references, was wondering if there is a thread safety / race condition in this code?
public class SummaryStatistics implements SummaryStatisticsI {
public static class Statistic {
private double min = Double.MAX_VALUE;
private double max = Double.MIN_VALUE;
// the sum of all the values seen so far
private DoubleAccumulator runningValue = new DoubleAccumulator((x, y) -> x + y, 0.0);
private int counter = 0;
protected double getMin() {
return min;
}
protected double getMax() {
return max;
}
protected double getValue() {
return runningValue.get();
}
protected int getCounter() {
return counter;
}
}
private final Map<String, Statistic> statsMap = new ConcurrentHashMap<>();
private Map<String, Statistic> getStatsMap() {
return statsMap;
}
#Override
public void addStatistic(String key, double value) {
Statistic s2 = new Statistic();
s2.runningValue.accumulate(value);
s2.min = value;
s2.max = value;
s2.counter++;
getStatsMap().merge(key, s2, new BiFunction<Statistic, Statistic, Statistic>() {
#Override
public Statistic apply(Statistic oldValue, Statistic s2) {
// only called when there is an existing value for this key
// all of this is atomic
oldValue.max = oldValue.max > s2.max ? oldValue.max : s2.max;
oldValue.min = oldValue.min < s2.min ? oldValue.min : s2.min;
// s2.min == s2.max == s2.runningValue.get()
oldValue.runningValue.accumulate(s2.min);
oldValue.counter++;
return oldValue;
}
});
}
#Override
public Statistic getStatistic(String key) {
return getStatsMap().get(key);
}
}
I have a DFS visit recursive method that sometimes throws a StackOverflowError. Since the size of the graph is large (around 20000 vertices), recursive calls are many, and so I tried to run with -Xss10M and everything works.
I'd just like to understand why adding at the beginning of the method a System.out.println, even without -Xss10M, the method doesn't throw any StackOverflowError. How is it possible?
This is the DFS visit method:
private int dfsVisit(Vertex<T> v, int time){
// System.out.println("Hello");
Vertex<T> n;
time++;
v.d = time;
v.color = Vertex.Color.GRAY;
for (Map.Entry<Vertex<T>, Float> a : v.neighbours.entrySet()){
n = a.getKey();
if(n.color == Vertex.Color.WHITE){
n.previous = v;
time = dfsVisit(n, time);
}
}
v.color = Vertex.Color.BLACK;
time++;
v.f = time;
return time;
}
This is the complete code
import java.io.*;
import java.util.*;
class Graph<T> {
private final Map<T, Vertex<T>> graph;
public static class Edge<T>{
public final T v1, v2;
public final float dist;
public Edge(T v1, T v2, float dist) {
this.v1 = v1;
this.v2 = v2;
this.dist = dist;
}
}
public static class Vertex<T> implements Comparable<Vertex>{ // SPOSTARE VAR IST NEL COSTRUTTORE
public enum Color {WHITE, GRAY, BLACK, UNKNOWN};
public final T name;
public float dist;
public Vertex<T> previous;
public final Map<Vertex<T>, Float> neighbours;
public Color color;
public int d, f;
public Vertex(T name) {
this.name = name;
dist = Float.MAX_VALUE;
previous = null;
neighbours = new HashMap<Vertex<T>, Float>(); // adjacency list
color = Color.UNKNOWN;
d = 0;
f = 0;
}
private void printPath() {
if (this == this.previous) {
System.out.print(this.name);
} else if (this.previous == null) {
System.out.print(this.name + " unreached");
} else {
this.previous.printPath();
System.out.print(" -> " + this.name + "(" + this.dist + ")");
}
}
public int compareTo(Vertex other){
if(this.dist == other.dist)
return 0;
else if(this.dist > other.dist)
return 1;
else
return -1;
}
}
// Builds a graph from an array of edges
public Graph(ArrayList<Graph.Edge> edges) {
graph = new HashMap<>(edges.size());
// add vertices
for (Edge<T> e : edges) {
if (!graph.containsKey(e.v1)) graph.put(e.v1, new Vertex<>(e.v1));
if (!graph.containsKey(e.v2)) graph.put(e.v2, new Vertex<>(e.v2));
}
// create adjacency list
for (Edge<T> e : edges) {
graph.get(e.v1).neighbours.put(graph.get(e.v2), e.dist);
graph.get(e.v2).neighbours.put(graph.get(e.v1), e.dist);
}
}
public void dijkstra(T startName) {
if (!graph.containsKey(startName)) {
System.err.println("Graph doesn't contain start vertex " + startName);
return;
}
final Vertex<T> source = graph.get(startName);
NavigableSet<Vertex<T>> q = new TreeSet<>(); // priority queue
// set-up vertices
for (Vertex<T> v : graph.values()) {
v.previous = v == source ? source : null;
v.dist = v == source ? 0 : Float.MAX_VALUE;
q.add(v);
}
dijkstra(q);
}
private void dijkstra(final NavigableSet<Vertex<T>> q) {
Vertex<T> u, v;
while (!q.isEmpty()) {
u = q.pollFirst();
if (u.dist == Float.MAX_VALUE) break; //???????????
for (Map.Entry<Vertex<T>, Float> a : u.neighbours.entrySet()) {
v = a.getKey();
final float alternateDist = u.dist + a.getValue();
if (alternateDist < v.dist) {
q.remove(v);
v.dist = alternateDist;
v.previous = u;
q.add(v);
}
}
}
}
public void printPath(T endName) {
if (!graph.containsKey(endName)) {
System.err.println("Graph doesn't contain end vertex " + "\"" + endName + "\"" );
return;
}
graph.get(endName).printPath();
System.out.println();
}
public void printAllPaths() {
for (Vertex<T> v : graph.values()) {
v.printPath();
System.out.println();
}
}
public Vertex<T> getVertex(T key){
if(graph.containsKey(key))
return graph.get(key);
return null;
}
public void printAdjacencyList(){
System.out.println("Adjacency list:");
for(Vertex<T> v : graph.values()){
System.out.print(v.name + ":\t");
for (Map.Entry<Vertex<T>, Float> a : v.neighbours.entrySet()){
System.out.print(a.getKey().name + "(" + a.getValue() + ") | ");
}
System.out.println();
}
}
/*
P.S. I know that if only used to calculate the connected components of the graph, dfs visit
could be written differently but I preferred to write it in a more general way, so that it
can be reused if necessary.
*/
private int dfsVisit(Vertex<T> v, int time){
// System.out.println("ciao");
Vertex<T> n;
time++;
v.d = time;
v.color = Vertex.Color.GRAY;
for (Map.Entry<Vertex<T>, Float> a : v.neighbours.entrySet()){
n = a.getKey();
if(n.color == Vertex.Color.WHITE){
n.previous = v;
time = dfsVisit(n, time);
}
}
v.color = Vertex.Color.BLACK;
time++;
v.f = time;
return time;
}
/*
Print the size of the connected components of the graph
*/
public void connectedComponents(){
for(Vertex<T> v : graph.values()){
v.color = Vertex.Color.WHITE;
v.previous = null;
}
for(Vertex<T> v : graph.values()){
if(v.color == Vertex.Color.WHITE)
System.out.println(dfsVisit(v, 0)/2);
}
}
}
here's the test class
import java.io.*;
import java.util.*;
public class Dijkstra {
private static ArrayList<Graph.Edge> a = new ArrayList<Graph.Edge>();
private static final String START = "torino";
private static final String END = "catania";
public static void main(String[] args) {
String fileName = "italian_dist_graph.txt";
try{
Scanner inputStream = new Scanner(new File(fileName));
String record;
while(inputStream.hasNextLine()){
record = inputStream.nextLine();
String[] array = record.split(",");
String from = array[0];
String to = array[1];
float dist = Float.parseFloat(array[2]);
a.add(new Graph.Edge(from, to, dist));
}
inputStream.close();
} catch(FileNotFoundException e){
System.out.println("Impossibile trovare il file "+fileName);
}
Graph<String> g = new Graph<String>(a);
g.dijkstra(START);
g.printPath(END);
//System.out.printf("%f\n", g.getVertex(END).dist/1000.0f);
g.connectedComponents();
}
}
N.B. try to comment g.dijkstra(START) and g.printPath(END); everything seems to work.
Here's the link to the data set
https://drive.google.com/open?id=0B7XZY8cd0L_fZVl1aERlRmhQN0k
Some general recommendations:
Your code mixes up attributes of vertices, that are related to a single run of dfs and such that are direct attributes of the vertices. Bad bad bad style. This is quite likely to break any more complex algorithm, can produce unexpected behavior and would require clearing the states after each run, to ensure stability of the code. Instead keep states that are related to a single run of a algorithm only visible to that function. E.g. store the states inside a Map, use the decorator-pattern to create a datastructure that provides additional attributes and that has method-local scope, etc.. As an example: running your code twice on the same graph (same Object) with the same input without clearing all states will lead to a wrong result (1).
In addition: creating an iterative version of DFS isn't exactly hard, so you should give it a try, especially since your graph appears to be pretty large.
As for why your code works (or doesn't) the way it does:
This is hard to tell, since it depends upon quite a lot of factors. You didn't provide full code, so I can't rerun any tests, or verify that everything behaves the way it should. The most likely answers:
Vertex uses the default hash-code provided by Object. This leads to random ordering of the entries in the map of neighbours, thus the order in which specific paths are traversed is random in each run and most likely different. Thus you're traversing the graph using random paths, that quite likely (especially due to the size of your graph) differ for each run. The reason isn't the System.out.println, but the mere fact, that your code generates a different structure (from a ordering-POV, not mathematical), each time it runs plus the coincident, that for some pretty weird reason each build of the graph, that doesn't reach the necessary recursion-depth for a StackOverflow, and the code compiled with System.out.println appeared together.
The Java compiler, or JIT modifies the behavior of the code in a weird way. Modern compilers have the tendency to produce quite weird code in their attempts to optimize everything they can get hold off.
I am building a data structure to learn more about java. I understand this program might be useless.
Here's what I want. I want to create a data structure that store smallest 3 values. if value is high, then ignore it. When storing values than I also want to put them in correct place so I don't have to sort them later. I can enter values by calling the add method.
so let's say I want to add 20, 10, 40, 30 than the result will be [10,20,30]. note I can only hold 3 smallest values and it store them as I place them.
I also understand that there are a lot of better ways for doing this but again this is just for learning purposes.
Question: I need help creating add method. I wrote some code but I am getting stuck with add method. Please help.
My Thinking: we might have to use a Iterator in add method?
public class MyJavaApp {
public static void main(String[] args){
MyClass<Integer> m = new MyClass<Integer>(3);
m.add(10);
m.add(20);
m.add(30);
m.add(40);
}
}
public class MyClass<V extends Comparable<V>> {
private V v[];
public MyClass(int s){
this.v = (V[])new Object[s];
}
public void add(V a){
}
}
Here is a rough sketch of the add method you have to implement.
You have to use the appropriate implementation of the compareTo method when comparing elements.
public void add(V a){
V temp = null;
if(a.compareTo( v[0]) == -1 ){
/*
keeping the v[0] in a temp variable since, v[0] could be the second
smallest value or the third smallest value.
Therefore call add method again to assign it to the correct
position.
*/
temp = v[0];
v[0] = a;
add(temp);
}else if(a.compareTo(v[0]) == 1 && a.compareTo(v[1]) == -1){
temp = v[1];
v[1] = a;
add(temp);
}else if(a.compareTo(v[1]) == 1 && a.compareTo(v[2]) == -1){
temp = v[2];
v[2] = a;
add(temp);
}
}
Therefore the v array will contain the lowerest elements.
Hope this helps.
A naive, inefficient approach would be (as you suggest) to iterate through the values and add / remove based on what you find:
public void add(Integer a)
{
// If fewer than 3 elements in the list, add and we're done.
if (m.size() < 3)
{
m.add(a);
return;
}
// If there's 3 elements, find the maximum.
int max = Integer.MIN_VALUE;
int index = -1;
for (int i=0; i<3; i++) {
int v = m.get(i);
if (v > max) {
max = v;
index = i;
}
}
// If a is less than the max, we need to add it and remove the existing max.
if (a < max) {
m.remove(index);
m.add(a);
}
}
Note: this has been written for Integer, not a generic type V. You'll need to generalise. It also doesn't keep the list sorted - another of your requirements.
Here's an implementation of that algorithm. It consists of looking for the right place to insert. Then it can be optimized for your requirements:
Don't bother looking past the size you want
Don't add more items than necessary
Here's the code. I added the toString() method for convenience. Only the add() method is interesting. Also this implementation is a bit more flexible as it respects the size you give to the constructor and doesn't assume 3.
I used a List rather than an array because it makes dealing with generics a lot easier. You'll find that using an array of generics makes using your class a bit more ugly (i.e. you have to deal with type erasure by providing a Class<V>).
import java.util.*;
public class MyClass<V extends Comparable<V>> {
private int s;
private List<V> v;
public MyClass(int s) {
this.s = s;
this.v = new ArrayList<V>(s);
}
public void add(V a) {
int i=0;
int l = v.size();
// Find the right index
while(i<l && v.get(i).compareTo(a) < 0) i++;
if(i<s) {
v.add(i, a);
// Truncate the list to make sure we don't store more values than needed
if(v.size() > s) v.remove(v.size()-1);
}
}
public String toString() {
StringBuilder result = new StringBuilder();
for(V item : v) {
result.append(item).append(',');
}
return result.toString();
}
}
I have a Path class which I think is immutable. In another class called Test, I have a final reference to an object of Path.
Yet, in between the constructor and the getter method, the Path object changes even though it is immutable and the reference is final. I know this because the length of the int array node in Path changes from the constructor to the getter. It seems as it the object is a completly different one altogether.
My program is multi-threaded but I've tried it with a single thread and that didn't resolve the problem.
Here is the immutable Path class
public class Path implements Iterable<Point> {
private final int[] nodes;
private final double distance;
public Path(Scenario scenario, int gateway, int sensor){
this.scenario = scenario;
nodes = new int[2];
nodes[1] = -gateway - 1;
nodes[0] = sensor;
distance = scenario.DISTANCE_GATEWAY_SENSOR[gateway][sensor];
}
public Path(Path base, int newSensor){
scenario = base.scenario;
//Copy the old path. These are rigid structures so that we do not need to deep copy
nodes = new int[base.nodes.length + 1];
for(int i = 0; i < base.nodes.length; i++)
nodes[i + 1] = base.nodes[i];
nodes[0] = newSensor;
distance = base.distance + scenario.DISTANCE_SENSOR_SENSOR[newSensor][nodes[1]];
}
public Path(Scenario scenario, int[] nodes, boolean isSensor, double distance){
this.scenario = scenario;
this.distance = distance;
this.nodes = Arrays.copyOf(nodes, nodes.length);
if(!isSensor)
for(int i = 0; i < this.nodes.length; i++)
this.nodes[i] = -this.nodes[i] -1;
}
#Override
public Iterator<Point> iterator() {
return new PointIterator();
}
public class PointIterator implements Iterator<Point>{
private int next = -1;
#Override
public boolean hasNext() {
return next + 1 < nodes.length;
}
#Override
public Point next() {
int p = nodes[++next];
if(p >= 0)
return scenario.SENSOR_LOCATION[p];
return scenario.CS_LOCATION[-p - 1];
}
#Override
public void remove() {
throw new IllegalAccessError("This method is not supported");
}
}
}
and here is the Test class (with a final reference to the Path class)
public class Test {
private final Path gatewayTour;
public Test(Scenario scenario, boolean[] chosenGateway){
distanceFitness = 0;
Point current = scenario.SINK_LOCATION;
boolean visited[] = new boolean[scenario.CONFIG.NUM_CS];
int nextGateway;
LinkedList<Integer> order = new LinkedList<>();
do {
double minimumDistance = Double.MAX_VALUE;
nextGateway = -1;
for(int i = 0; i < scenario.CONFIG.NUM_CS; i++)
if(!visited[i] && CHOSEN_GATEWAYS[i] && scenario.CS_LOCATION[i].isCloserThan(minimumDistance, current)) {
nextGateway = i;
minimumDistance = scenario.CS_LOCATION[i].distance(current);
}
if(nextGateway >= 0) {
distanceFitness += minimumDistance;
visited[nextGateway] = true;
order.add(nextGateway);
current = scenario.CS_LOCATION[nextGateway];
}
} while(nextGateway >= 0);
int path[] = new int[order.size()];
Iterator<Integer> it = order.iterator();
for(int i = 0; i < order.size(); i++)
path[i] = it.next().intValue();
gatewayTour = new Path(scenario, path, false, distanceFitness);
}
public Path getGatewayTour(){
//Here, the gatewayTour object has changed and does not have the same content as in the constructor
return gatewayTour;
}
}
Is there anything in my program that permits the object to change? I'll be more precise: is there anything that woud permit the int array "nodes" in Path class to change length? Because this is the real problem.
[EDIT]: My test was flawed, which led me to believe that the value of my 'nodes' array changed. Thanks to all who pointed flaws or possible improvements in my code.
I'll accept the answer of AlexR because he pointed out that one could change individual elements in a final array; something I didn't know and that help resolve the problem.
Word final means that the reference marked with this word cannot be changed. It does not mean that the referenced object cannot be changed.
This means that there is no problem to change the instance of Path by changing its fields. Yes, you are right, your fields are final too. But let's examine them:
private final int[] nodes;
private final double distance;
private final Scenario scenario;
distance is a primitive, so it indeed cannot be changed once assigned during initialization. nodes is an array, i.e. object. The array itself cannot be changed, i.e. the reference refers to the same array. However you can change elements of the array.
scenario is object too. You have not sent the class Scenario here, but again if fields of this class can be changed this object can be changed.
private final int[] nodes;
Is still mutable, assuming your constructor simply copies the array reference.
public Path(int[] nodes, double distance) {
this.node = nodes;
this.distance = distance;
}
This is because Path's nodes is still pointing to the instance that was passed in. If that instance changes, then your Path's state has changed.
One solution is to make a copy of node in the constructor (using System.arraycopy).
To be sure the answer is correct we need to see more code; it's not clear what is being changed where. However, if the idea is to guarantee that nodes is unmodifiable, a primitive array (final or not) will not work. Something more like
private final List<Integer> nodes;
public Path(Integer[] array /* note boxed as Integer */) {
nodes = java.util.Collections.unmodifiableList(
java.util.Arrays.asList(array));
/* etc. */
}
Problem here!
public Path(Scenario scenario, int[] nodes, boolean isSensor, double distance){
this.scenario = scenario;
this.distance = distance;
this.nodes = nodes;
You copy the nodes array reference.
Use:
this.nodes = Arrays.copy(nodes, 0, nodes.length);
If you modify the array, changes will be reflected into Path! Similarly, if you modify the array in the constructor, changes will be reflected to the caller...
As such, your class is NOT immutable at the moment. Also, "real" (to my sense) immutable classes are final themselves.