244 lines
8.2 KiB
C++
244 lines
8.2 KiB
C++
/*
|
|
* Copyright 2014 Google Inc. All rights reserved.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#ifndef SEMISTATIC_GRAPH_H
|
|
#define SEMISTATIC_GRAPH_H
|
|
|
|
#include "memory_pool.h"
|
|
#include <fruit/impl/data_structures/semistatic_map.h>
|
|
|
|
#if FRUIT_EXTRA_DEBUG
|
|
#include <iostream>
|
|
#endif
|
|
|
|
namespace fruit {
|
|
namespace impl {
|
|
|
|
// The alignas ensures that a SemistaticGraphInternalNodeId* always has 0 in the low-order bit.
|
|
struct alignas(2) alignas(alignof(std::size_t)) SemistaticGraphInternalNodeId {
|
|
// This stores the index in the vector times sizeof(NodeData).
|
|
std::size_t id;
|
|
|
|
bool operator==(const SemistaticGraphInternalNodeId& x) const;
|
|
bool operator<(const SemistaticGraphInternalNodeId& x) const;
|
|
};
|
|
|
|
/**
|
|
* A direct graph implementation where most of the graph is fixed at construction time, but a few nodes and edges can be
|
|
* added
|
|
* later.
|
|
*
|
|
* Also, nodes can either be normal nodes or terminal nodes. Terminal nodes can't have outgoing edges. Note that a node
|
|
* with no
|
|
* outgoing edges may or may not be marked as terminal.
|
|
*
|
|
* While insertion of elements after construction is supported, inserting or changing the neighbors of more than O(1)
|
|
* nodes
|
|
* after construction will raise the cost of any further operations to more than O(1).
|
|
*
|
|
* Even though adding nodes/edges after construction is inefficient, it is efficient to turn non-terminal nodes into
|
|
* terminal ones
|
|
* (and therefore removing all the outgoing edges from the node) after construction.
|
|
*
|
|
* NodeId and Node must be default constructible and trivially copyable.
|
|
*/
|
|
template <typename NodeId, typename Node>
|
|
class SemistaticGraph {
|
|
private:
|
|
using InternalNodeId = SemistaticGraphInternalNodeId;
|
|
|
|
// The node data for nodeId is in nodes[node_index_map.at(nodeId)/sizeof(NodeData)].
|
|
// To avoid hash table lookups, the edges in edges_storage are stored as indexes of `nodes' instead of as NodeIds.
|
|
// node_index_map contains all known NodeIds, including ones known only due to an outgoing edge ending there from
|
|
// another node.
|
|
SemistaticMap<NodeId, InternalNodeId> node_index_map;
|
|
|
|
struct NodeData {
|
|
#if FRUIT_EXTRA_DEBUG
|
|
NodeId key;
|
|
#endif
|
|
|
|
public:
|
|
// If edges_begin==0, this is a terminal node.
|
|
// If edges_begin==1, this node doesn't exist, it's just referenced by another node.
|
|
// Otherwise, reinterpret_cast<InternalNodeId*>(edges_begin) is the beginning of the edges range.
|
|
std::uintptr_t edges_begin;
|
|
|
|
// An explicit "public" specifier here prevents the compiler from reordering the fields.
|
|
// We want the edges_begin field to be stored first because that's what we're going to branch on.
|
|
public:
|
|
Node node;
|
|
};
|
|
|
|
std::size_t first_unused_index;
|
|
|
|
FixedSizeVector<NodeData> nodes;
|
|
|
|
// Stores vectors of edges as contiguous chunks of node IDs.
|
|
// The NodeData elements in `nodes' contain indexes into this vector (stored as already multiplied by
|
|
// sizeof(NodeData)).
|
|
// The first element is unused.
|
|
FixedSizeVector<InternalNodeId> edges_storage;
|
|
|
|
#if FRUIT_EXTRA_DEBUG
|
|
template <typename NodeIter>
|
|
void printGraph(NodeIter first, NodeIter last);
|
|
#endif
|
|
|
|
NodeData* nodeAtId(InternalNodeId internalNodeId);
|
|
const NodeData* nodeAtId(InternalNodeId internalNodeId) const;
|
|
|
|
static NodeData* nodeAtId(NodeData* nodes_begin, InternalNodeId internalNodeId);
|
|
static const NodeData* nodeAtId(const NodeData* nodes_begin, InternalNodeId internalNodeId);
|
|
|
|
public:
|
|
class edge_iterator;
|
|
|
|
class node_iterator {
|
|
private:
|
|
NodeData* itr;
|
|
|
|
friend class SemistaticGraph<NodeId, Node>;
|
|
|
|
explicit node_iterator(NodeData* itr);
|
|
|
|
public:
|
|
Node& getNode();
|
|
|
|
bool isTerminal();
|
|
|
|
// Turns the node into a terminal node, also removing all the deps.
|
|
void setTerminal();
|
|
|
|
// Assumes !isTerminal().
|
|
// neighborsEnd() is NOT provided/stored for efficiency, the client code is expected to know the number of
|
|
// neighbors.
|
|
edge_iterator neighborsBegin();
|
|
|
|
bool operator==(const node_iterator&) const;
|
|
};
|
|
|
|
class const_node_iterator {
|
|
private:
|
|
const NodeData* itr;
|
|
|
|
friend class SemistaticGraph<NodeId, Node>;
|
|
|
|
explicit const_node_iterator(const NodeData* itr);
|
|
|
|
public:
|
|
explicit const_node_iterator(node_iterator itr);
|
|
|
|
const Node& getNode();
|
|
|
|
bool isTerminal();
|
|
|
|
bool operator==(const const_node_iterator&) const;
|
|
};
|
|
|
|
|
|
class edge_iterator {
|
|
private:
|
|
// Iterator on edges_storage.
|
|
InternalNodeId* itr;
|
|
|
|
friend class SemistaticGraph<NodeId, Node>;
|
|
friend class SemistaticGraph<NodeId, Node>::node_iterator;
|
|
|
|
explicit edge_iterator(InternalNodeId* itr);
|
|
|
|
public:
|
|
// getNodeIterator(graph.nodes.begin()) returns the first neighbor.
|
|
node_iterator getNodeIterator(node_iterator nodes_begin);
|
|
|
|
void operator++();
|
|
|
|
// Equivalent to i times operator++ followed by getNodeIterator(nodes_begin).
|
|
node_iterator getNodeIterator(std::size_t i, node_iterator nodes_begin);
|
|
};
|
|
|
|
// Constructs an *invalid* graph (as if this graph was just moved from).
|
|
SemistaticGraph() = default;
|
|
|
|
/**
|
|
* A value x obtained dereferencing a NodeIter::value_type must support the following operations:
|
|
* - x.getId(), returning a NodeId
|
|
* - x.getValue(), returning a Node
|
|
* - x.isTerminal(), returning a bool
|
|
* - x.getEdgesBegin() and x.getEdgesEnd(), that if !x.isTerminal() define a range of values of type NodeId
|
|
* (the outgoing edges).
|
|
*
|
|
* This constructor is *not* defined in semistatic_graph.templates.h, but only in semistatic_graph.cc.
|
|
* All instantiations must have a matching instantiation in semistatic_graph.cc.
|
|
*
|
|
* The MemoryPool is only used during construction, the constructed object *can* outlive the memory pool.
|
|
*/
|
|
template <typename NodeIter>
|
|
SemistaticGraph(NodeIter first, NodeIter last, MemoryPool& memory_pool);
|
|
|
|
SemistaticGraph(SemistaticGraph&&) noexcept = default;
|
|
SemistaticGraph(const SemistaticGraph&) = delete;
|
|
|
|
/**
|
|
* Creates a copy of x with the additional nodes in [first, last). The requirements on NodeIter as the same as for the
|
|
* 2-arg
|
|
* constructor.
|
|
* The nodes in [first, last) must NOT be already in x, but can be neighbors of nodes in x.
|
|
* The new graph will share data with `x', so must be destroyed before `x' is destroyed.
|
|
* Also, after this is called, `x' must not be modified until this object has been destroyed.
|
|
*
|
|
* The MemoryPool is only used during construction, the constructed object *can* outlive the memory pool.
|
|
*/
|
|
template <typename NodeIter>
|
|
SemistaticGraph(const SemistaticGraph& x, NodeIter first, NodeIter last, MemoryPool& memory_pool);
|
|
|
|
~SemistaticGraph();
|
|
|
|
SemistaticGraph& operator=(const SemistaticGraph&) = delete;
|
|
SemistaticGraph& operator=(SemistaticGraph&&) noexcept = default;
|
|
|
|
// The result is unspecified. The only guarantee is that it's the right value to pass to edge_iterator's
|
|
// getNodeIterator() methods.
|
|
node_iterator begin();
|
|
|
|
node_iterator end();
|
|
const_node_iterator end() const;
|
|
|
|
// Precondition: `nodeId' must exist in the graph.
|
|
// Unlike std::map::at(), this yields undefined behavior if the precondition isn't satisfied (instead of throwing).
|
|
node_iterator at(NodeId nodeId);
|
|
|
|
// Prefer using at() when possible, this is slightly slower.
|
|
// Returns end() if the node ID was not found.
|
|
node_iterator find(NodeId nodeId);
|
|
const_node_iterator find(NodeId nodeId) const;
|
|
|
|
#if FRUIT_EXTRA_DEBUG
|
|
// Emits a runtime error if some node was not created but there is an edge pointing to it.
|
|
void checkFullyConstructed();
|
|
#endif
|
|
};
|
|
|
|
} // namespace impl
|
|
} // namespace fruit
|
|
|
|
#include <fruit/impl/data_structures/semistatic_graph.defn.h>
|
|
|
|
// semistatic_graph.templates.h is not included here to limit the transitive includes. Include it explicitly (in .cpp
|
|
// files).
|
|
|
|
#endif // SEMISTATIC_GRAPH_H
|