2020-11-27 11:47:21 +01:00
|
|
|
#include "trianglepattterntopology.hpp"
|
|
|
|
#include <boost/graph/biconnected_components.hpp>
|
|
|
|
#include <boost/graph/connected_components.hpp>
|
|
|
|
#include <numeric>
|
|
|
|
|
|
|
|
FlatPatternTopology::FlatPatternTopology() {}
|
|
|
|
|
2021-08-06 13:35:28 +02:00
|
|
|
FlatPatternTopology::FlatPatternTopology(const std::vector<size_t> &numberOfNodesPerSlot,
|
|
|
|
const std::vector<vcg::Point2i> &edges)
|
|
|
|
: numberOfNodesPerSlot(numberOfNodesPerSlot)
|
|
|
|
{
|
|
|
|
pattern = BoostGraph(
|
|
|
|
std::accumulate(numberOfNodesPerSlot.begin(), numberOfNodesPerSlot.end(), 0));
|
|
|
|
isAdjacentTo.resize(boost::num_vertices(pattern),
|
|
|
|
std::vector<bool>(boost::num_vertices(pattern), false));
|
|
|
|
for (const vcg::Point2i &e : edges) {
|
|
|
|
boost::add_edge(e[0], e[1], pattern);
|
|
|
|
isAdjacentTo[e[0]][e[1]] = true;
|
|
|
|
isAdjacentTo[e[1]][e[0]] = true;
|
|
|
|
}
|
2020-11-27 11:47:21 +01:00
|
|
|
|
2021-08-06 13:35:28 +02:00
|
|
|
constructNodeToSlotMap();
|
|
|
|
constructSlotToNodeMap();
|
|
|
|
constructCorresponginNodeMap();
|
2020-11-27 11:47:21 +01:00
|
|
|
}
|
|
|
|
|
2021-08-06 13:35:28 +02:00
|
|
|
bool FlatPatternTopology::containsArticulationPoints(std::vector<int> &articulationPointsVi) const
|
|
|
|
{
|
|
|
|
assert(numberOfNodesPerSlot.size() == 7 && numberOfNodesPerSlot[4] < 2 && !nodeToSlot.empty()
|
|
|
|
&& !correspondingNode.empty());
|
|
|
|
BoostGraph copyOfPattern(pattern);
|
|
|
|
// std::cout << std::endl;
|
|
|
|
// std::vector<int> componentsBefore(boost::num_vertices(copyOfPattern));
|
|
|
|
// size_t num_components = boost::connected_components(copyOfPattern, &componentsBefore[0]);
|
|
|
|
// std::cout << "Number of cc before:" << num_components << std::endl;
|
|
|
|
// printGraph(copyOfPattern);
|
2020-11-27 11:47:21 +01:00
|
|
|
|
2021-08-06 13:35:28 +02:00
|
|
|
copyOfPattern = constructRotationallySymmetricPattern(copyOfPattern,
|
|
|
|
slotToNode,
|
|
|
|
nodeToSlot,
|
|
|
|
correspondingNode);
|
|
|
|
// // Remove edges connected to the bottom edge node
|
|
|
|
// assert(slotToNode.find(4) != slotToNode.end());
|
|
|
|
// std::unordered_set<size_t> bottomEdgeNodeSet =
|
|
|
|
// (slotToNode.find(4))->second;
|
|
|
|
// size_t bottomEdgeNodeIndex = *bottomEdgeNodeSet.begin();
|
|
|
|
// boost::clear_vertex(bottomEdgeNodeIndex, copyOfPattern);
|
2020-11-27 11:47:21 +01:00
|
|
|
|
2021-08-06 13:35:28 +02:00
|
|
|
// std::vector<int> componentsAfter(boost::num_vertices(copyOfPattern));
|
|
|
|
// num_components = boost::connected_components(copyOfPattern, &componentsAfter[0]);
|
|
|
|
// std::cout << "Number of cc after:" << num_components << std::endl;
|
|
|
|
// printGraph(copyOfPattern);
|
2020-11-27 11:47:21 +01:00
|
|
|
|
2021-08-06 13:35:28 +02:00
|
|
|
// Compute articulation points on the edited graph
|
|
|
|
std::vector<vertex_t> articulationPoints;
|
|
|
|
boost::articulation_points(copyOfPattern, std::back_inserter(articulationPoints));
|
|
|
|
for (const auto apVi : articulationPoints) {
|
|
|
|
articulationPointsVi.push_back(static_cast<int>(apVi));
|
|
|
|
}
|
|
|
|
// std::cout << "Found " << articulationPoints.size()
|
|
|
|
// << " articulation points.\n";
|
|
|
|
// size_t numberOfNonValidArticulationPoints = 0;
|
|
|
|
// for (std::size_t i = 0; i < articulationPoints.size(); ++i) {
|
2020-11-27 11:47:21 +01:00
|
|
|
// std::cout << articulationPoints[i] << std::endl;
|
|
|
|
// if (boost::out_degree(articulationPoints[i], copyOfPattern) < 3) {
|
|
|
|
// numberOfNonValidArticulationPoints++;
|
|
|
|
// }
|
2021-08-06 13:35:28 +02:00
|
|
|
// }
|
|
|
|
// if (numberOfNonValidArticulationPoints == articulationPoints.size()) {
|
|
|
|
// return false;
|
|
|
|
// }
|
|
|
|
return !articulationPoints.empty();
|
2020-11-27 11:47:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void FlatPatternTopology::constructNodeToSlotMap(
|
|
|
|
const std::vector<size_t> &numberOfNodesPerSlot,
|
|
|
|
std::unordered_map<size_t, size_t> &nodeToSlot) {
|
|
|
|
const size_t numberOfNodes = std::accumulate(numberOfNodesPerSlot.begin(),
|
|
|
|
numberOfNodesPerSlot.end(), 0);
|
|
|
|
assert(numberOfNodes != 0 && nodeToSlot.empty() &&
|
|
|
|
numberOfNodesPerSlot.size() == 7);
|
|
|
|
std::vector<size_t> maxNodeIndexPerSlot(numberOfNodesPerSlot.size());
|
|
|
|
for (size_t i = 0; i < maxNodeIndexPerSlot.size(); ++i) {
|
|
|
|
maxNodeIndexPerSlot[i] = std::accumulate(
|
|
|
|
numberOfNodesPerSlot.begin(), numberOfNodesPerSlot.begin() + i + 1, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (size_t nodeIndex = 0; nodeIndex < numberOfNodes; nodeIndex++) {
|
|
|
|
const size_t slotIndex =
|
|
|
|
std::distance(maxNodeIndexPerSlot.begin(),
|
|
|
|
std::upper_bound(maxNodeIndexPerSlot.begin(),
|
|
|
|
maxNodeIndexPerSlot.end(), nodeIndex));
|
|
|
|
nodeToSlot[nodeIndex] = slotIndex;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FlatPatternTopology::constructNodeToSlotMap() {
|
|
|
|
constructNodeToSlotMap(numberOfNodesPerSlot, nodeToSlot);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FlatPatternTopology::constructSlotToNodeMap() {
|
|
|
|
constructSlotToNodeMap(nodeToSlot, slotToNode);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FlatPatternTopology::constructSlotToNodeMap(
|
|
|
|
const std::unordered_map<size_t, size_t> &nodeToSlot,
|
|
|
|
std::unordered_map<size_t, std::unordered_set<size_t>> &slotToNode) {
|
|
|
|
assert(!nodeToSlot.empty());
|
|
|
|
for (size_t nodeIndex = 0; nodeIndex < nodeToSlot.size(); nodeIndex++) {
|
|
|
|
slotToNode[nodeToSlot.at(nodeIndex)].insert(nodeIndex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: The function expects that the numberOfNodesPerSlot follows a
|
|
|
|
// specific format and that the vertex container was populated in a
|
|
|
|
// particular order.
|
|
|
|
void FlatPatternTopology::constructCorresponginNodeMap() {
|
|
|
|
assert(!nodeToSlot.empty() && correspondingNode.empty() &&
|
|
|
|
numberOfNodesPerSlot.size() == 7);
|
|
|
|
|
|
|
|
for (size_t nodeIndex = 0; nodeIndex < boost::num_vertices(pattern);
|
|
|
|
nodeIndex++) {
|
|
|
|
const size_t slotIndex = nodeToSlot[nodeIndex];
|
|
|
|
if (slotIndex == 1) {
|
|
|
|
correspondingNode[nodeIndex] = nodeIndex + 1;
|
|
|
|
} else if (slotIndex == 2) {
|
|
|
|
correspondingNode[nodeIndex] = nodeIndex - 1;
|
|
|
|
} else if (slotIndex == 3) {
|
|
|
|
const size_t numberOfNodesBefore =
|
|
|
|
nodeIndex - std::accumulate(numberOfNodesPerSlot.begin(),
|
|
|
|
numberOfNodesPerSlot.begin() + 3, 0);
|
|
|
|
correspondingNode[nodeIndex] =
|
|
|
|
std::accumulate(numberOfNodesPerSlot.begin(),
|
|
|
|
numberOfNodesPerSlot.begin() + 6, 0) -
|
|
|
|
1 - numberOfNodesBefore;
|
|
|
|
} else if (slotIndex == 5) {
|
|
|
|
const size_t numberOfNodesAfter =
|
|
|
|
std::accumulate(numberOfNodesPerSlot.begin(),
|
|
|
|
numberOfNodesPerSlot.begin() + 6, 0) -
|
|
|
|
1 - nodeIndex;
|
|
|
|
correspondingNode[nodeIndex] =
|
|
|
|
numberOfNodesAfter + std::accumulate(numberOfNodesPerSlot.begin(),
|
|
|
|
numberOfNodesPerSlot.begin() + 3,
|
|
|
|
0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-06 13:35:28 +02:00
|
|
|
bool FlatPatternTopology::pathExists(int src, int dest) const
|
|
|
|
{
|
|
|
|
const int N = boost::num_vertices(pattern);
|
|
|
|
std::vector<bool> visited(N, false);
|
|
|
|
visited[src] = true;
|
|
|
|
std::stack<int> next;
|
|
|
|
next.push(src);
|
|
|
|
|
|
|
|
while (!next.empty()) {
|
|
|
|
int cv = next.top();
|
|
|
|
next.pop();
|
|
|
|
|
|
|
|
for (int nv = 0; nv < N; ++nv) {
|
|
|
|
if (!visited[nv] && isAdjacentTo[cv][nv] == 1) {
|
|
|
|
visited[nv] = true;
|
|
|
|
next.push(nv);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// dest was reached from src?
|
|
|
|
return visited[dest];
|
|
|
|
}
|
|
|
|
|
2020-11-27 11:47:21 +01:00
|
|
|
/*
|
|
|
|
* In this function I create an "extended" pattern of the one in the base
|
|
|
|
* triangle by:
|
|
|
|
* 1)copying the edges from left edge to the right edge and vice versa
|
|
|
|
* 2)I label all nodes that lay on the boarder of the base triangle as
|
|
|
|
* "external" and add edges connecting them to each other (this is wrong in the
|
|
|
|
* case in which all "external" nodes are connected via a single node!))
|
|
|
|
* */
|
|
|
|
|
|
|
|
BoostGraph FlatPatternTopology::constructRotationallySymmetricPattern(
|
|
|
|
const BoostGraph &pattern,
|
|
|
|
const std::unordered_map<size_t, std::unordered_set<size_t>> &slotToNodes,
|
|
|
|
const std::unordered_map<size_t, size_t> &nodeToSlot,
|
2021-08-06 13:35:28 +02:00
|
|
|
const std::unordered_map<size_t, size_t> &correspondingNode) const
|
|
|
|
{
|
|
|
|
BoostGraph rotationallySymmetricPattern(pattern);
|
2020-11-27 11:47:21 +01:00
|
|
|
|
2021-08-06 13:35:28 +02:00
|
|
|
// for (const std::pair<size_t, size_t> &correspondingPair : correspondingNode) {
|
|
|
|
// const auto v0 = boost::vertex(correspondingPair.first, pattern);
|
|
|
|
// const auto v1 = boost::vertex(correspondingPair.second, pattern);
|
|
|
|
// if (boost::degree(v0, pattern) != 0 || boost::degree(v1, pattern) != 0) {
|
|
|
|
// boost::add_edge(v0, v1, rotationallySymmetricPattern);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
boost::graph_traits<BoostGraph>::out_edge_iterator ei, ei_end;
|
|
|
|
// Copy edges that lay on the left edge to the right edge
|
|
|
|
const auto slot3NodesPairIt = slotToNodes.find(3);
|
|
|
|
if (slot3NodesPairIt != slotToNodes.end()) {
|
|
|
|
for (const size_t &nodeIndex : slot3NodesPairIt->second) {
|
|
|
|
for (boost::tie(ei, ei_end) = boost::out_edges(nodeIndex, pattern); ei != ei_end; ++ei) {
|
|
|
|
auto vt = boost::target(*ei, pattern);
|
|
|
|
const auto vtNodeSlotPairIt = nodeToSlot.find(vt);
|
|
|
|
assert(vtNodeSlotPairIt != nodeToSlot.end());
|
|
|
|
const size_t vtSlot = vtNodeSlotPairIt->second;
|
|
|
|
if (vtSlot == 3 || vtSlot == 1 || vtSlot == 0) {
|
|
|
|
// Connect the corresponding nodes on the opposite edge
|
|
|
|
auto correspondingNodeIndexIt = correspondingNode.find(nodeIndex);
|
|
|
|
assert(correspondingNodeIndexIt != correspondingNode.end());
|
|
|
|
auto correspondingVtIt = correspondingNode.find(vt);
|
|
|
|
assert(correspondingVtIt != correspondingNode.end() || vtSlot == 0);
|
|
|
|
const size_t &correspondingNodeIndex = correspondingNodeIndexIt->second;
|
|
|
|
size_t correspondingVt = 0;
|
|
|
|
if (correspondingVtIt != correspondingNode.end()) {
|
|
|
|
correspondingVt = correspondingVtIt->second;
|
|
|
|
}
|
|
|
|
boost::add_edge(correspondingNodeIndex,
|
|
|
|
correspondingVt,
|
|
|
|
rotationallySymmetricPattern);
|
|
|
|
}
|
|
|
|
}
|
2020-11-27 11:47:21 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-06 13:35:28 +02:00
|
|
|
// // Copy edges that lay on the right edge to the left edge
|
|
|
|
const auto slot5NodesPairIt = slotToNodes.find(5);
|
|
|
|
if (slot5NodesPairIt != slotToNodes.end()) {
|
|
|
|
for (const size_t &nodeIndex : slot5NodesPairIt->second) {
|
|
|
|
for (boost::tie(ei, ei_end) = boost::out_edges(nodeIndex, pattern); ei != ei_end; ++ei) {
|
|
|
|
auto vt = boost::target(*ei, pattern);
|
|
|
|
const auto vtNodeSlotPairIt = nodeToSlot.find(vt);
|
|
|
|
assert(vtNodeSlotPairIt != nodeToSlot.end());
|
|
|
|
const size_t vtSlot = vtNodeSlotPairIt->second;
|
|
|
|
if (vtSlot == 5 || vtSlot == 2 || vtSlot == 0) {
|
|
|
|
// Connect the corresponding nodes on the opposite edge
|
|
|
|
auto correspondingNodeIndexIt = correspondingNode.find(nodeIndex);
|
|
|
|
assert(correspondingNodeIndexIt != correspondingNode.end());
|
|
|
|
auto correspondingVtIt = correspondingNode.find(vt);
|
|
|
|
assert(correspondingVtIt != correspondingNode.end() || vtSlot == 0);
|
|
|
|
const size_t &correspondingNodeIndex = correspondingNodeIndexIt->second;
|
|
|
|
size_t correspondingVt = 0;
|
|
|
|
if (correspondingVtIt != correspondingNode.end()) {
|
|
|
|
correspondingVt = correspondingVtIt->second;
|
|
|
|
}
|
|
|
|
boost::add_edge(correspondingNodeIndex,
|
|
|
|
correspondingVt,
|
|
|
|
rotationallySymmetricPattern);
|
|
|
|
}
|
|
|
|
}
|
2020-11-27 11:47:21 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-06 13:35:28 +02:00
|
|
|
// NOTE: The problem with that approach is that I connect !all! "external"
|
|
|
|
// nodes with each other, which might not be entirely true. If the number of
|
|
|
|
// cc of the tilled configuration is not 1 this might not label patterns as
|
|
|
|
// having an articulation node although they might have one. Create set of
|
|
|
|
// nodes connected with "external" edges
|
|
|
|
std::unordered_set<size_t> externallyConnectedNodes;
|
|
|
|
// Mark the star node as external
|
|
|
|
const auto slot0NodesPairIt = slotToNodes.find(0);
|
|
|
|
if (slot0NodesPairIt != slotToNodes.end()) {
|
|
|
|
externallyConnectedNodes.insert(slot0NodesPairIt->second.begin(),
|
|
|
|
slot0NodesPairIt->second.end());
|
|
|
|
}
|
|
|
|
// Mark all bottom nodes as external since they are allways connected to the
|
|
|
|
// south-neighbouring pattern
|
|
|
|
const auto slot4NodesPairIt = slotToNodes.find(4);
|
|
|
|
if (slot4NodesPairIt != slotToNodes.end()) {
|
|
|
|
externallyConnectedNodes.insert(slot4NodesPairIt->second.begin(),
|
|
|
|
slot4NodesPairIt->second.end());
|
|
|
|
}
|
|
|
|
// Add all slot3 nodes that have a connection to the "inside"
|
|
|
|
if (slot3NodesPairIt != slotToNodes.end()) {
|
|
|
|
externallyConnectedNodes.insert(slot3NodesPairIt->second.begin(),
|
|
|
|
slot3NodesPairIt->second.end());
|
|
|
|
for (const size_t &nodeIndex : slot3NodesPairIt->second) {
|
|
|
|
auto correspondingNodePairIt = correspondingNode.find(nodeIndex);
|
|
|
|
// for (boost::tie(ei, ei_end) = boost::out_edges(nodeIndex, pattern); ei != ei_end; ++ei) {
|
|
|
|
// auto vt = boost::target(*ei, pattern);
|
|
|
|
// const auto vtNodeSlotPairIt = nodeToSlot.find(vt);
|
|
|
|
// assert(vtNodeSlotPairIt != nodeToSlot.end());
|
|
|
|
// const size_t vtSlot = vtNodeSlotPairIt->second;
|
|
|
|
// if (vtSlot != 3) {
|
|
|
|
// assert(correspondingNodePairIt != correspondingNode.end());
|
|
|
|
// externallyConnectedNodes.insert(correspondingNodePairIt->second);
|
|
|
|
// // boost::add_edge(correspondingNodePairIt->second,
|
|
|
|
// // vt,
|
|
|
|
// // rotationallySymmetricPattern);
|
|
|
|
// // boost::add_edge(nodeIndex, vt, rotationallySymmetricPattern);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
boost::add_edge(correspondingNodePairIt->second,
|
|
|
|
nodeIndex,
|
|
|
|
rotationallySymmetricPattern);
|
2020-11-27 11:47:21 +01:00
|
|
|
}
|
|
|
|
}
|
2021-08-06 13:35:28 +02:00
|
|
|
// Add all slot5 nodes that have a connection to the "inside"
|
|
|
|
if (slot5NodesPairIt != slotToNodes.end()) {
|
|
|
|
for (const size_t &nodeIndex : slot5NodesPairIt->second) {
|
|
|
|
auto correspondingNodePairIt = correspondingNode.find(nodeIndex);
|
|
|
|
// for (boost::tie(ei, ei_end) = boost::out_edges(nodeIndex, pattern); ei != ei_end; ++ei) {
|
|
|
|
// auto vt = boost::target(*ei, pattern);
|
|
|
|
// const auto vtNodeSlotPairIt = nodeToSlot.find(vt);
|
|
|
|
// assert(vtNodeSlotPairIt != nodeToSlot.end());
|
|
|
|
// const size_t vtSlot = vtNodeSlotPairIt->second;
|
|
|
|
// if (vtSlot != 5) {
|
|
|
|
// assert(correspondingNodePairIt != correspondingNode.end());
|
|
|
|
// externallyConnectedNodes.insert(correspondingNodePairIt->second);
|
|
|
|
// boost::add_edge(correspondingNodePairIt->second,
|
|
|
|
// vt,
|
|
|
|
// rotationallySymmetricPattern);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
boost::add_edge(correspondingNodePairIt->second,
|
|
|
|
nodeIndex,
|
|
|
|
rotationallySymmetricPattern);
|
2020-11-27 11:47:21 +01:00
|
|
|
}
|
2021-08-06 13:35:28 +02:00
|
|
|
externallyConnectedNodes.insert(slot5NodesPairIt->second.begin(),
|
|
|
|
slot5NodesPairIt->second.end());
|
2020-11-27 11:47:21 +01:00
|
|
|
}
|
|
|
|
|
2021-08-06 13:35:28 +02:00
|
|
|
// connecting all is wrong. Maybe I should check whether the external nodes
|
|
|
|
// are connected via a single node? If so this node is an articulation point.
|
|
|
|
// I could test this by checking if it filters out only the falsely labeled
|
|
|
|
// pattern 2367
|
|
|
|
const size_t &n = externallyConnectedNodes.size();
|
|
|
|
const size_t numberOfExternalEdges = n * (n - 1) / 2;
|
|
|
|
// Connect all external nodes with each other
|
|
|
|
for (size_t edgeIndex = 0; edgeIndex < numberOfExternalEdges; edgeIndex++) {
|
|
|
|
const int sei0 = n - 2
|
|
|
|
- std::floor(std::sqrt(-8 * edgeIndex + 4 * n * (n - 1) - 7) / 2.0 - 0.5);
|
|
|
|
const int sei1 = edgeIndex + sei0 + 1 - n * (n - 1) / 2 + (n - sei0) * ((n - sei0) - 1) / 2;
|
|
|
|
const size_t ni0 = *std::next(externallyConnectedNodes.begin(), sei0);
|
|
|
|
const size_t ni1 = *std::next(externallyConnectedNodes.begin(), sei1);
|
|
|
|
if (correspondingNode.contains(ni0) || correspondingNode.contains(ni1)) {
|
|
|
|
if (correspondingNode.contains(ni0) && correspondingNode.contains(ni1)
|
|
|
|
&& pathExists(correspondingNode.at(ni0), correspondingNode.at(ni1))) {
|
|
|
|
boost::add_edge(ni0, ni1, rotationallySymmetricPattern);
|
|
|
|
} else if (!correspondingNode.contains(ni0) && correspondingNode.contains(ni1)
|
|
|
|
&& pathExists(ni0, correspondingNode.at(ni1))) {
|
|
|
|
boost::add_edge(ni0, ni1, rotationallySymmetricPattern);
|
|
|
|
} else if (correspondingNode.contains(ni0) && !correspondingNode.contains(ni1)
|
|
|
|
&& pathExists(correspondingNode.at(ni0), ni1)) {
|
|
|
|
boost::add_edge(ni0, ni1, rotationallySymmetricPattern);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-11-27 11:47:21 +01:00
|
|
|
|
2021-08-06 13:35:28 +02:00
|
|
|
return rotationallySymmetricPattern;
|
2020-11-27 11:47:21 +01:00
|
|
|
}
|
|
|
|
|
2021-08-06 13:35:28 +02:00
|
|
|
void FlatPatternTopology::printGraph(const BoostGraph &g)
|
|
|
|
{
|
|
|
|
boost::graph_traits<BoostGraph>::edge_iterator ei, ei_end;
|
|
|
|
for (boost::tie(ei, ei_end) = boost::edges(g); ei != ei_end; ++ei)
|
|
|
|
std::cout << (char) (boost::source(*ei, g) + 'A') << " -- "
|
|
|
|
<< (char) (boost::target(*ei, g) + 'A');
|
|
|
|
std::cout << std::endl;
|
2020-11-27 11:47:21 +01:00
|
|
|
}
|