Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
nearp_parser.cc
Go to the documentation of this file.
1// Copyright 2010-2025 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <array>
17#include <iterator>
18#include <optional>
19#include <string>
20#include <string_view>
21#include <vector>
22
23#include "absl/strings/str_join.h"
24#include "absl/strings/str_split.h"
25#include "absl/strings/string_view.h"
28
30
31NearpParser::NearpParser() { Initialize(); }
32
33void NearpParser::Initialize() {
34 name_.clear();
35 comment_.clear();
36 num_arcs_ = 0;
37 num_edges_ = 0;
38 num_nodes_ = 0;
39 num_arcs_with_servicing_ = 0;
40 num_edges_with_servicing_ = 0;
41 num_nodes_with_servicing_ = 0;
42 depot_ = 0;
43 arc_traversing_costs_.clear();
44 edge_traversing_costs_.clear();
45 arc_servicing_demands_.clear();
46 edge_servicing_demands_.clear();
47 node_servicing_demands_.clear();
48 num_vehicles_ = 0;
49 capacity_ = 0;
50 section_ = METADATA;
51}
52
53bool NearpParser::LoadFile(absl::string_view file_name) {
54 Initialize();
55 return ParseFile(file_name);
56}
57
58bool NearpParser::ParseFile(absl::string_view file_name) {
59 // Only put the first word as header, as the main check is just done on this
60 // first word (no ambiguity is possible for well-formed files; a more precise
61 // check is done for metadata).
62 static auto section_headers = std::array<const char*, 14>({
63 "Name",
64 "Optimal", // "value"
65 "#Vehicles",
66 "Capacity",
67 "Depot", // "Node"
68 "#Nodes",
69 "#Edges",
70 "#Arcs",
71 "#Required", // "N", "E", or "A"
72 "ReN.",
73 "ReE.",
74 "EDGE",
75 "ReA.",
76 "ARC",
77 });
78
79 for (const std::string& line :
81 const std::vector<std::string> words =
82 absl::StrSplit(line, absl::ByAnyChar(" :\t"), absl::SkipEmpty());
83
84 if (words.empty()) continue;
85
86 if (absl::c_linear_search(section_headers, words[0])) {
87 // First, check if a new section has been met.
88 if (words[0] == "ReN.") {
89 node_servicing_demands_.reserve(NumberOfNodesWithServicing());
90 section_ = NODES_WITH_SERVICING;
91 } else if (words[0] == "ReE.") {
92 edge_traversing_costs_.reserve(NumberOfEdges());
93 edge_servicing_demands_.reserve(NumberOfEdgesWithServicing());
94 section_ = EDGES_WITH_SERVICING;
95 } else if (words[0] == "EDGE") {
96 edge_traversing_costs_.reserve(NumberOfEdges());
97 section_ = EDGES_WITHOUT_SERVICING;
98 } else if (words[0] == "ReA.") {
99 arc_traversing_costs_.reserve(NumberOfArcs());
100 arc_servicing_demands_.reserve(NumberOfArcsWithServicing());
101 section_ = ARCS_WITH_SERVICING;
102 } else if (words[0] == "ARC") {
103 arc_traversing_costs_.reserve(NumberOfArcs());
104 section_ = ARCS_WITHOUT_SERVICING;
105 } else {
106 if (!ParseMetadataLine(words)) {
107 LOG(ERROR) << "Error when parsing a metadata line: " << line;
108 return false;
109 }
110 }
111 } else {
112 // If no new section is detected, process according to the current state.
113
114 // Is there still data expected? Don't process the line if every element
115 // it should contain have been read: there might be some garbage at the
116 // end (like comments without delimiter).
117 switch (section_) {
118 case NODES_WITH_SERVICING:
119 if (node_servicing_demands_.size() == NumberOfNodesWithServicing())
120 continue;
121 break;
122 case EDGES_WITH_SERVICING:
123 if (edge_servicing_demands_.size() == NumberOfEdgesWithServicing())
124 continue;
125 break;
126 case EDGES_WITHOUT_SERVICING:
127 // edge_traversing_costs_ is filled with all edges, whether they need
128 // servicing or not. The section EDGES_WITHOUT_SERVICING must be after
129 // EDGES_WITH_SERVICING: once edge_traversing_costs_ has one value
130 // per edge (coming first from EDGES_WITH_SERVICING, then from
131 // EDGES_WITHOUT_SERVICING), no more value should come.
132 if (edge_traversing_costs_.size() == NumberOfEdges()) continue;
133 break;
134 case ARCS_WITH_SERVICING:
135 if (arc_servicing_demands_.size() == NumberOfArcsWithServicing())
136 continue;
137 break;
138 case ARCS_WITHOUT_SERVICING:
139 // arc_traversing_costs_ is filled with all arcs, do they need
140 // servicing or not. The section ARCS_WITHOUT_SERVICING must be after
141 // ARCS_WITH_SERVICING: once edge_traversing_costs_ has one value
142 // per arc (coming first from ARCS_WITH_SERVICING, then from
143 // ARCS_WITHOUT_SERVICING), no more value should come.
144 if (arc_traversing_costs_.size() == NumberOfArcs()) continue;
145 break;
146 default:
147 break;
148 }
149
150 // Data is still expected: parse the current line according to the state.
151 switch (section_) {
152 case NODES_WITH_SERVICING:
153 if (!ParseNode(line)) {
154 LOG(ERROR) << "Could not parse line in required nodes: " << line;
155 return false;
156 }
157 break;
158 case EDGES_WITH_SERVICING:
159 if (!ParseEdge(line, true)) {
160 LOG(ERROR) << "Could not parse line in required edges: " << line;
161 return false;
162 }
163 break;
164 case EDGES_WITHOUT_SERVICING:
165 if (!ParseEdge(line, false)) {
166 LOG(ERROR) << "Could not parse line in edges: " << line;
167 return false;
168 }
169 break;
170 case ARCS_WITH_SERVICING:
171 if (!ParseArc(line, true)) {
172 LOG(ERROR) << "Could not parse line in required arcs: " << line;
173 return false;
174 }
175 break;
176 case ARCS_WITHOUT_SERVICING:
177 if (!ParseArc(line, false)) {
178 LOG(ERROR) << "Could not parse line in arcs: " << line;
179 return false;
180 }
181 break;
182 default:
183 LOG(ERROR) << "Could not parse line outside node-edge-arc lists: "
184 << line;
185 return false;
186 }
187 }
188 }
189
190 return section_ != METADATA;
191}
192
193namespace {
194std::optional<int64_t> ParseNodeIndex(std::string_view text);
195
196struct ArcOrEdge {
197 const int64_t tail;
198 const int64_t head;
199 const int64_t traversing_cost;
200 const int64_t servicing_demand;
201 const int64_t servicing_cost;
202};
203
204std::optional<ArcOrEdge> ParseArcOrEdge(std::string_view line,
205 bool with_servicing);
206} // namespace
207
208bool NearpParser::ParseMetadataLine(const std::vector<std::string>& words) {
209 if (words[0] == "Name") {
210 name_ = absl::StrJoin(words.begin() + 1, words.end(), " ");
211 } else if (words[0] == "Optimal" && words[1] == "value") {
212 comment_ = absl::StrJoin(words.begin() + 2, words.end(), " ");
213 } else if (words[0] == "#Vehicles") {
214 num_vehicles_ = strings::ParseLeadingInt32Value(words[1], -1);
215 // -1 indicates that the number of vehicles is unknown.
216 if (num_vehicles_ < -1) {
217 LOG(ERROR) << "Error when parsing the number of vehicles: " << words[1];
218 return false;
219 }
220 } else if (words[0] == "Capacity") {
221 capacity_ = strings::ParseLeadingInt64Value(words[1], -1);
222 if (capacity_ <= 0) {
223 LOG(ERROR) << "Error when parsing the capacity: " << words[1];
224 return false;
225 }
226 } else if (words[0] == "Depot" && words[1] == "Node") {
227 const std::optional<int64_t> depot = ParseNodeIndex(words[2]);
228 if (!depot.has_value()) {
229 LOG(ERROR) << "Error when parsing the depot: " << words[1];
230 return false;
231 }
232 depot_ = depot.value();
233 } else if (words[0] == "#Nodes") {
234 num_nodes_ = strings::ParseLeadingInt32Value(words[1], -1);
235 if (num_nodes_ <= 0) {
236 LOG(ERROR) << "Error when parsing the number of nodes: " << words[1];
237 return false;
238 }
239 } else if (words[0] == "#Edges") {
240 num_edges_ = strings::ParseLeadingInt32Value(words[1], -1);
241 if (num_edges_ <= 0) {
242 LOG(ERROR) << "Error when parsing the number of edges: " << words[1];
243 return false;
244 }
245 } else if (words[0] == "#Arcs") {
246 num_arcs_ = strings::ParseLeadingInt32Value(words[1], -1);
247 if (num_arcs_ <= 0) {
248 LOG(ERROR) << "Error when parsing the number of arcs: " << words[1];
249 return false;
250 }
251 } else if (words[0] == "#Required" && words[1] == "N") {
252 num_nodes_with_servicing_ = strings::ParseLeadingInt32Value(words[2], -1);
253 if (num_nodes_with_servicing_ <= 0) {
254 LOG(ERROR) << "Error when parsing the number of nodes with servicing: "
255 << words[1];
256 return false;
257 }
258 } else if (words[0] == "#Required" && words[1] == "E") {
259 num_edges_with_servicing_ = strings::ParseLeadingInt32Value(words[2], -1);
260 if (num_edges_with_servicing_ <= 0) {
261 LOG(ERROR) << "Error when parsing the number of edges with servicing: "
262 << words[1];
263 return false;
264 }
265 } else if (words[0] == "#Required" && words[1] == "A") {
266 num_arcs_with_servicing_ = strings::ParseLeadingInt32Value(words[2], -1);
267 if (num_arcs_with_servicing_ <= 0) {
268 LOG(ERROR) << "Error when parsing the number of arcs with servicing: "
269 << words[1];
270 return false;
271 }
272 } else {
273 LOG(ERROR) << "Unrecognized metadata line: " << absl::StrJoin(words, " ");
274 return false;
275 }
276 return true;
277}
278
279bool NearpParser::ParseArc(std::string_view line, bool with_servicing) {
280 std::optional<ArcOrEdge> parsed_arc = ParseArcOrEdge(line, with_servicing);
281 if (!parsed_arc.has_value()) {
282 return false;
283 }
284
285 Arc arc{parsed_arc->tail, parsed_arc->head};
286 arc_traversing_costs_[arc] = parsed_arc->traversing_cost;
287
288 if (with_servicing) {
289 arc_servicing_demands_[arc] = parsed_arc->servicing_demand;
290 arc_servicing_costs_[arc] = parsed_arc->servicing_cost;
291 }
292
293 return true;
294}
295
296bool NearpParser::ParseEdge(std::string_view line, bool with_servicing) {
297 std::optional<ArcOrEdge> parsed_edge = ParseArcOrEdge(line, with_servicing);
298 if (!parsed_edge.has_value()) {
299 return false;
300 }
301
302 Edge edge{parsed_edge->tail, parsed_edge->head};
303 edge_traversing_costs_[edge] = parsed_edge->traversing_cost;
304
305 if (with_servicing) {
306 edge_servicing_demands_[edge] = parsed_edge->servicing_demand;
307 edge_servicing_costs_[edge] = parsed_edge->servicing_cost;
308 }
309
310 return true;
311}
312
313bool NearpParser::ParseNode(std::string_view line) {
314 const std::vector<std::string> words =
315 absl::StrSplit(line, absl::ByAnyChar(" :\t(),"), absl::SkipEmpty());
316
317 // Parse the name and ID.
318 const int64_t id = strings::ParseLeadingInt64Value(words[0].substr(1), 0) - 1;
319 if (id < 0) {
320 LOG(ERROR) << "Error when parsing the node name: " << words[0];
321 return false;
322 }
323
324 // Parse the servicing details if needed.
325 const int64_t servicing_demand =
327 if (servicing_demand < 0) {
328 LOG(ERROR) << "Error when parsing the node servicing demand: " << words[1];
329 return false;
330 }
331
332 const int64_t servicing_cost = strings::ParseLeadingInt64Value(words[2], -1);
333 if (servicing_cost < 0) {
334 LOG(ERROR) << "Error when parsing the node servicing cost: " << words[1];
335 return false;
336 }
337
338 // Once the values have been parsed successfully, save them.
339 node_servicing_demands_.insert({id, servicing_demand});
340 node_servicing_costs_.insert({id, servicing_cost});
341
342 return true;
343}
344
345namespace {
346std::optional<int64_t> ParseNodeIndex(std::string_view text) {
347 const int64_t node = strings::ParseLeadingInt64Value(text, -1);
348 if (node < 0) {
349 LOG(ERROR) << "Could not parse node index: " << text;
350 return std::nullopt;
351 }
352 return {node - 1};
353}
354
355std::optional<ArcOrEdge> ParseArcOrEdge(std::string_view line,
356 bool with_servicing) {
357 const std::vector<std::string> words =
358 absl::StrSplit(line, absl::ByAnyChar(" :\t(),"), absl::SkipEmpty());
359
360 // Parse the name.
361
362 // Parse the tail and the head of the arc/edge.
363 std::optional<int64_t> opt_tail = ParseNodeIndex(words[1]);
364 if (!opt_tail.has_value()) {
365 LOG(ERROR) << "Error when parsing the tail node: " << words[1];
366 return {};
367 }
368 const int64_t tail = opt_tail.value();
369
370 std::optional<int64_t> opt_head = ParseNodeIndex(words[2]);
371 if (!opt_head.has_value()) {
372 LOG(ERROR) << "Error when parsing the head node: " << words[2];
373 return {};
374 }
375 const int64_t head = opt_head.value();
376
377 if (tail == head) {
378 LOG(ERROR) << "The head and tail nodes are identical: " << line;
379 return {};
380 }
381
382 // Parse the traversing cost.
383 const int64_t traversing_cost = strings::ParseLeadingInt64Value(words[3], -1);
384 if (traversing_cost < 0) {
385 LOG(ERROR) << "Error when parsing the traversing cost: " << words[3];
386 return {};
387 }
388
389 // Ensure there are no extraneous elements.
390 const int64_t next_id = (with_servicing) ? 6 : 4;
391 if (words.size() > next_id) {
392 LOG(ERROR) << "Extraneous elements in line, starting with: "
393 << words[next_id];
394 return {};
395 }
396
397 // Parse the servicing details if needed and return the elements.
398 if (!with_servicing) {
399 return ArcOrEdge{tail, head, traversing_cost,
400 /*servicing_demand=*/-1,
401 /*servicing_cost=*/-1};
402 } else {
403 const int64_t servicing_demand =
405 if (servicing_demand < 0) {
406 LOG(ERROR) << "Error when parsing the servicing demand: " << words[4];
407 return {};
408 }
409
410 const int64_t servicing_cost =
412 if (servicing_cost < 0) {
413 LOG(ERROR) << "Error when parsing the servicing cost: " << words[5];
414 return {};
415 }
416
417 return ArcOrEdge{tail, head, traversing_cost, servicing_demand,
418 servicing_cost};
419 }
420}
421} // namespace
422
423std::string NearpParser::GetArcName(Arc arc) const {
424 if (arc_servicing_costs_.contains(arc)) {
425 int64_t arc_position = std::distance(arc_servicing_demands_.begin(),
426 arc_servicing_demands_.find(arc));
427 CHECK_LT(arc_position, arc_servicing_demands_.size());
428 return absl::StrCat("A", arc_position + 1);
429 } else {
430 int64_t arc_position = std::distance(arc_traversing_costs_.begin(),
431 arc_traversing_costs_.find(arc));
432 CHECK_LT(arc_position, arc_traversing_costs_.size());
433 return absl::StrCat("NrA", arc_position - num_arcs_with_servicing_ + 1);
434 }
435}
436
437std::string NearpParser::GetEdgeName(Edge edge) const {
438 if (edge_servicing_costs_.contains(edge)) {
439 int64_t edge_position = std::distance(edge_servicing_demands_.begin(),
440 edge_servicing_demands_.find(edge));
441 CHECK_LT(edge_position, edge_servicing_demands_.size());
442 return absl::StrCat("E", edge_position + 1);
443 } else {
444 int64_t edge_position = std::distance(edge_traversing_costs_.begin(),
445 edge_traversing_costs_.find(edge));
446 CHECK_LT(edge_position, edge_traversing_costs_.size());
447 return absl::StrCat("NrE", edge_position - num_edges_with_servicing_ + 1);
448 }
449}
450} // namespace operations_research::routing
ABSL_ATTRIBUTE_REINITIALIZES void clear()
int64_t depot() const
Returns the index of the depot.
std::string GetEdgeName(Edge edge) const
bool LoadFile(absl::string_view file_name)
Loads instance from a file into this parser object.
std::string GetArcName(Arc arc) const
Common utilities for parsing routing instances.
int32_t ParseLeadingInt32Value(const char *str, int32_t deflt)
Definition numbers.cc:60
int64_t ParseLeadingInt64Value(const char *str, int64_t deflt)
Definition numbers.cc:167