All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.neo4j.gds.paths.yens.MutablePathResult Maven / Gradle / Ivy

There is a newer version: 2.15.0
Show newest version
/*
 * Copyright (c) "Neo4j"
 * Neo4j Sweden AB [http://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.neo4j.gds.paths.yens;

import org.neo4j.gds.paths.ImmutablePathResult;
import org.neo4j.gds.paths.PathResult;

import java.util.Arrays;

/**
 * Helper data structure for Yen's algorithm that allows us
 * to manipulate {@link org.neo4j.gds.paths.PathResult}s.
 */
final class MutablePathResult {

    private final long[] EMPTY_ARRAY = new long[0];
    private long index;

    private final long sourceNode;

    private final long targetNode;

    private long[] nodeIds;

    private long[] relationshipIds;

    private double[] costs;

    static MutablePathResult of(PathResult pathResult) {
        return new MutablePathResult(
            pathResult.index(),
            pathResult.sourceNode(),
            pathResult.targetNode(),
            pathResult.nodeIds(),
            pathResult.relationshipIds(),
            pathResult.costs()
        );
    }

    private MutablePathResult(
        long index,
        long sourceNode,
        long targetNode,
        long[] nodeIds,
        long[] relationshipIds,
        double[] costs
    ) {
        this.index = index;
        this.sourceNode = sourceNode;
        this.targetNode = targetNode;
        this.nodeIds = nodeIds;
        this.relationshipIds = relationshipIds;
        this.costs = costs;
    }

    PathResult toPathResult() {
        return ImmutablePathResult.of(index, sourceNode, targetNode, nodeIds, relationshipIds, costs);
    }

    /**
     * Changes the index field to the given value and returns the mutated instance.
     */
    MutablePathResult withIndex(int index) {
        this.index = index;
        return this;
    }

    int nodeCount() {
        return nodeIds.length;
    }

    long node(int index) {
        return nodeIds[index];
    }

    long relationship(int index) {
        return relationshipIds[index];
    }

    double totalCost() {
        return costs[costs.length - 1];
    }

    /**
     * Returns the path from the start to the given index (exclusive).
     */
    MutablePathResult subPath(int index) {
        return new MutablePathResult(
            index,
            sourceNode,
            targetNode,
            Arrays.copyOf(nodeIds, index),
            Arrays.copyOf(relationshipIds, index - 1),
            Arrays.copyOf(costs, index)
        );
    }

    /**
     * Returns true, iff this path matches the given path up until the given index (exclusive).
     * Two paths match, if they have the same node ids, but not necessarily the same costs.
     */
    boolean matches(MutablePathResult path, int index) {
        for (int i = 0; i < index; i++) {
            if (nodeIds[i] != path.nodeIds[i]) {
                return false;
            }
        }
        return true;
    }

    /**
     * Returns true, iff this path matches the given path up until the given index (exclusive).
     * Two paths match, if they have the same nodes as well as the same relationship ids
     */
    boolean matchesExactly(MutablePathResult path, int index) {

        if (relationshipIds.length == 0 || path.relationshipIds.length == 0) {
            return matches(path, index);
        }
        for (int i = 0; i < index; i++) {
            if (nodeIds[i] != path.nodeIds[i]) {
                return false;
            }
            if (i >= 1) {
                if (relationshipIds[i - 1] != path.relationshipIds[i - 1]) {
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * Appends the given path to this path.
     *
     * The last node in this path, must match the first node in the given path.
     * This node will only appear once in the resulting path.
     * The cost value associated with the last value in this path, is added to
     * the costs for each node in the second path.
     */
    
    private void append(MutablePathResult path, long[] relationships) {
        // spur node is end of first and beginning of second path
        assert nodeIds[nodeIds.length - 1] == path.nodeIds[0];

        var oldLength = nodeIds.length;

        var newNodeIds = new long[oldLength + path.nodeIds.length - 1];
        var newCosts = new double[oldLength + path.nodeIds.length - 1];

        // copy node ids
        System.arraycopy(this.nodeIds, 0, newNodeIds, 0, oldLength);
        System.arraycopy(path.nodeIds, 1, newNodeIds, oldLength, path.nodeIds.length - 1);

        // copy costs
        System.arraycopy(this.costs, 0, newCosts, 0, oldLength);
        System.arraycopy(path.costs, 1, newCosts, oldLength, path.costs.length - 1);

        // add cost from previous path to each cost in the appended path
        var baseCost = newCosts[oldLength - 1];
        for (int i = oldLength; i < newCosts.length; i++) {
            newCosts[i] += baseCost;
        }

        this.nodeIds = newNodeIds;
        this.relationshipIds = relationships;
        this.costs = newCosts;
    }

    void append(MutablePathResult path) {

        var oldRelationshipIdsLength = relationshipIds.length;
        var newRelationshipIds = new long[oldRelationshipIdsLength + path.relationshipIds.length];
        // copy relationship ids
        System.arraycopy(this.relationshipIds, 0, newRelationshipIds, 0, oldRelationshipIdsLength);
        System.arraycopy(
            path.relationshipIds,
            0,
            newRelationshipIds,
            oldRelationshipIdsLength,
            path.relationshipIds.length
        );
        
        append(path, newRelationshipIds);
    }

    /**
     * Appends the given path to this path without creating an explicit relationship array.
     *
     * The last node in this path, must match the first node in the given path.
     * This node will only appear once in the resulting path.
     * The cost value associated with the last value in this path, is added to
     * the costs for each node in the second path.
     */
    void appendWithoutRelationshipIds(MutablePathResult path) {
        // spur node is end of first and beginning of second path
        append(path, EMPTY_ARRAY);
    }


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        var other = (MutablePathResult) o;
        return Arrays.equals(nodeIds, other.nodeIds) && Arrays.equals(relationshipIds, other.relationshipIds);
    }

    @Override
    public int hashCode() {
        int h = 5381;
        h += (h << 5) + Arrays.hashCode(nodeIds);
        h += (h << 5) + Arrays.hashCode(relationshipIds);
        return h;
    }

    @Override
    public String toString() {
        return "MutablePathResult{" +
               "index=" + index +
               ", sourceNode=" + sourceNode +
               ", targetNode=" + targetNode +
               ", nodeIds=" + Arrays.toString(nodeIds) +
               ", relationshipIds=" + Arrays.toString(relationshipIds) +
               ", costs=" + Arrays.toString(costs) +
               '}';
    }

    long index() {
        return index;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy