gravatar
alpar (Alpar Juttner)
alpar@cs.elte.hu
Don't assume that the default maps are reference maps (in Elevator)
0 1 0
default
1 file changed with 19 insertions and 17 deletions:
↑ Collapse diff ↑
Ignore white space 384 line context
1 1
/* -*- mode: C++; indent-tabs-mode: nil; -*-
2 2
 *
3 3
 * This file is a part of LEMON, a generic C++ optimization library.
4 4
 *
5 5
 * Copyright (C) 2003-2008
6 6
 * Egervary Jeno Kombinatorikus Optimalizalasi Kutatocsoport
7 7
 * (Egervary Research Group on Combinatorial Optimization, EGRES).
8 8
 *
9 9
 * Permission to use, modify and distribute this software is granted
10 10
 * provided that this copyright notice appears in all copies. For
11 11
 * precise terms see the accompanying LICENSE file.
12 12
 *
13 13
 * This software is provided "AS IS" with no warranty of any kind,
14 14
 * express or implied, and with no claim as to its suitability for any
15 15
 * purpose.
16 16
 *
17 17
 */
18 18

	
19 19
#ifndef LEMON_ELEVATOR_H
20 20
#define LEMON_ELEVATOR_H
21 21

	
22 22
///\ingroup auxdat
23 23
///\file
24 24
///\brief Elevator class
25 25
///
26 26
///Elevator class implements an efficient data structure
27 27
///for labeling items in push-relabel type algorithms.
28 28
///
29 29

	
30 30
#include <test/test_tools.h>
31 31
namespace lemon {
32 32

	
33 33
  ///Class for handling "labels" in push-relabel type algorithms.
34 34

	
35 35
  ///A class for handling "labels" in push-relabel type algorithms.
36 36
  ///
37 37
  ///\ingroup auxdat
38 38
  ///Using this class you can assign "labels" (nonnegative integer numbers)
39 39
  ///to the edges or nodes of a graph, manipulate and query them through
40 40
  ///operations typically arising in "push-relabel" type algorithms.
41 41
  ///
42 42
  ///Each item is either \em active or not, and you can also choose a
43 43
  ///highest level active item.
44 44
  ///
45 45
  ///\sa LinkedElevator
46 46
  ///
47 47
  ///\param Graph the underlying graph type
48 48
  ///\param Item Type of the items the data is assigned to (Graph::Node,
49 49
  ///Graph::Edge, Graph::UEdge)
50 50
  template<class Graph, class Item>
51 51
  class Elevator
52 52
  {
53 53
  public:
54 54

	
55 55
    typedef Item Key;
56 56
    typedef int Value;
57 57

	
58 58
  private:
59 59

	
60 60
    typedef Item *Vit;
61 61
    typedef typename ItemSetTraits<Graph,Item>::template Map<Vit>::Type VitMap;
62 62
    typedef typename ItemSetTraits<Graph,Item>::template Map<int>::Type IntMap;
63 63

	
64 64
    const Graph &_g;
65 65
    int _max_level;
66 66
    int _item_num;
67 67
    VitMap _where;
68 68
    IntMap _level;
69 69
    std::vector<Item> _items;
70 70
    std::vector<Vit> _first;
71 71
    std::vector<Vit> _last_active;
72 72

	
73 73
    int _highest_active;
74 74

	
75 75
    void copy(Item i, Vit p)
76 76
    {
77
      _where[*p=i]=p;
77
      _where.set(*p=i,p);
78 78
    }
79 79
    void copy(Vit s, Vit p)
80 80
    {
81 81
      if(s!=p)
82 82
        {
83 83
          Item i=*s;
84 84
          *p=i;
85
          _where[i]=p;
85
          _where.set(i,p);
86 86
        }
87 87
    }
88 88
    void swap(Vit i, Vit j)
89 89
    {
90 90
      Item ti=*i;
91 91
      Vit ct = _where[ti];
92
      _where[ti]=_where[*i=*j];
93
      _where[*j]=ct;
92
      _where.set(ti,_where[*i=*j]);
93
      _where.set(*j,ct);
94 94
      *j=ti;
95 95
    }
96 96

	
97 97
  public:
98 98

	
99 99
    ///Constructor with given maximum level.
100 100

	
101 101
    ///Constructor with given maximum level.
102 102
    ///
103 103
    ///\param g The underlying graph
104 104
    ///\param max_level Set the range of the possible labels to
105 105
    ///[0...\c max_level]
106 106
    Elevator(const Graph &g,int max_level) :
107 107
      _g(g),
108 108
      _max_level(max_level),
109 109
      _item_num(_max_level),
110 110
      _where(g),
111 111
      _level(g,0),
112 112
      _items(_max_level),
113 113
      _first(_max_level+2),
114 114
      _last_active(_max_level+2),
115 115
      _highest_active(-1) {}
116 116
    ///Constructor.
117 117

	
118 118
    ///Constructor.
119 119
    ///
120 120
    ///\param g The underlying graph
121 121
    ///The range of the possible labels is [0...\c max_level],
122 122
    ///where \c max_level is equal to the number of labeled items in the graph.
123 123
    Elevator(const Graph &g) :
124 124
      _g(g),
125 125
      _max_level(countItems<Graph, Item>(g)),
126 126
      _item_num(_max_level),
127 127
      _where(g),
128 128
      _level(g,0),
129 129
      _items(_max_level),
130 130
      _first(_max_level+2),
131 131
      _last_active(_max_level+2),
132 132
      _highest_active(-1)
133 133
    {
134 134
    }
135 135

	
136 136
    ///Activate item \c i.
137 137

	
138 138
    ///Activate item \c i.
139 139
    ///\pre Item \c i shouldn't be active before.
140 140
    void activate(Item i)
141 141
    {
142 142
      const int l=_level[i];
143 143
      swap(_where[i],++_last_active[l]);
144 144
      if(l>_highest_active) _highest_active=l;
145 145
    }
146 146

	
147 147
    ///Deactivate item \c i.
148 148

	
149 149
    ///Deactivate item \c i.
150 150
    ///\pre Item \c i must be active before.
151 151
    void deactivate(Item i)
152 152
    {
153 153
      swap(_where[i],_last_active[_level[i]]--);
154 154
      while(_highest_active>=0 &&
155 155
            _last_active[_highest_active]<_first[_highest_active])
156 156
        _highest_active--;
157 157
    }
158 158

	
159 159
    ///Query whether item \c i is active
160 160
    bool active(Item i) const { return _where[i]<=_last_active[_level[i]]; }
161 161

	
162 162
    ///Return the level of item \c i.
163 163
    int operator[](Item i) const { return _level[i]; }
164 164

	
165 165
    ///Return the number of items on level \c l.
166 166
    int onLevel(int l) const
167 167
    {
168 168
      return _first[l+1]-_first[l];
169 169
    }
170 170
    ///Return true if the level is empty.
171 171
    bool emptyLevel(int l) const
172 172
    {
173 173
      return _first[l+1]-_first[l]==0;
174 174
    }
175 175
    ///Return the number of items above level \c l.
176 176
    int aboveLevel(int l) const
177 177
    {
178 178
      return _first[_max_level+1]-_first[l+1];
179 179
    }
180 180
    ///Return the number of active items on level \c l.
181 181
    int activesOnLevel(int l) const
182 182
    {
183 183
      return _last_active[l]-_first[l]+1;
184 184
    }
185 185
    ///Return true if there is not active item on level \c l.
186 186
    bool activeFree(int l) const
187 187
    {
188 188
      return _last_active[l]<_first[l];
189 189
    }
190 190
    ///Return the maximum allowed level.
191 191
    int maxLevel() const
192 192
    {
193 193
      return _max_level;
194 194
    }
195 195

	
196 196
    ///\name Highest Active Item
197 197
    ///Functions for working with the highest level
198 198
    ///active item.
199 199

	
200 200
    ///@{
201 201

	
202 202
    ///Return a highest level active item.
203 203

	
204 204
    ///Return a highest level active item.
205 205
    ///
206 206
    ///\return the highest level active item or INVALID if there is no active
207 207
    ///item.
208 208
    Item highestActive() const
209 209
    {
210 210
      return _highest_active>=0?*_last_active[_highest_active]:INVALID;
211 211
    }
212 212

	
213 213
    ///Return a highest active level.
214 214

	
215 215
    ///Return a highest active level.
216 216
    ///
217 217
    ///\return the level of the highest active item or -1 if there is no active
218 218
    ///item.
219 219
    int highestActiveLevel() const
220 220
    {
221 221
      return _highest_active;
222 222
    }
223 223

	
224 224
    ///Lift the highest active item by one.
225 225

	
226 226
    ///Lift the item returned by highestActive() by one.
227 227
    ///
228 228
    void liftHighestActive()
229 229
    {
230
      ++_level[*_last_active[_highest_active]];
230
      Item it = *_last_active[_highest_active];
231
      _level.set(it,_level[it]+1);
231 232
      swap(_last_active[_highest_active]--,_last_active[_highest_active+1]);
232 233
      --_first[++_highest_active];
233 234
    }
234 235

	
235 236
    ///Lift the highest active item.
236 237

	
237 238
    ///Lift the item returned by highestActive() to level \c new_level.
238 239
    ///
239 240
    ///\warning \c new_level must be strictly higher
240 241
    ///than the current level.
241 242
    ///
242 243
    void liftHighestActive(int new_level)
243 244
    {
244 245
      const Item li = *_last_active[_highest_active];
245 246

	
246 247
      copy(--_first[_highest_active+1],_last_active[_highest_active]--);
247 248
      for(int l=_highest_active+1;l<new_level;l++)
248 249
        {
249 250
          copy(--_first[l+1],_first[l]);
250 251
          --_last_active[l];
251 252
        }
252 253
      copy(li,_first[new_level]);
253
      _level[li]=new_level;
254
      _level.set(li,new_level);
254 255
      _highest_active=new_level;
255 256
    }
256 257

	
257 258
    ///Lift the highest active item.
258 259

	
259 260
    ///Lift the item returned by highestActive() to the top level and
260 261
    ///deactivates it.
261 262
    ///
262 263
    ///\warning \c new_level must be strictly higher
263 264
    ///than the current level.
264 265
    ///
265 266
    void liftHighestActiveToTop()
266 267
    {
267 268
      const Item li = *_last_active[_highest_active];
268 269

	
269 270
      copy(--_first[_highest_active+1],_last_active[_highest_active]--);
270 271
      for(int l=_highest_active+1;l<_max_level;l++)
271 272
        {
272 273
          copy(--_first[l+1],_first[l]);
273 274
          --_last_active[l];
274 275
        }
275 276
      copy(li,_first[_max_level]);
276 277
      --_last_active[_max_level];
277
      _level[li]=_max_level;
278
      _level.set(li,_max_level);
278 279

	
279 280
      while(_highest_active>=0 &&
280 281
            _last_active[_highest_active]<_first[_highest_active])
281 282
        _highest_active--;
282 283
    }
283 284

	
284 285
    ///@}
285 286

	
286 287
    ///\name Active Item on Certain Level
287 288
    ///Functions for working with the active items.
288 289

	
289 290
    ///@{
290 291

	
291 292
    ///Returns an active item on level \c l.
292 293

	
293 294
    ///Returns an active item on level \c l.
294 295
    ///
295 296
    ///Returns an active item on level \c l or \ref INVALID if there is no such
296 297
    ///an item. (\c l must be from the range [0...\c max_level].
297 298
    Item activeOn(int l) const
298 299
    {
299 300
      return _last_active[l]>=_first[l]?*_last_active[l]:INVALID;
300 301
    }
301 302

	
302 303
    ///Lifts the active item returned by \c activeOn() member function.
303 304

	
304 305
    ///Lifts the active item returned by \c activeOn() member function
305 306
    ///by one.
306 307
    Item liftActiveOn(int level)
307 308
    {
308
      ++_level[*_last_active[level]];
309
      Item it =*_last_active[level];
310
      _level.set(it,_level[it]+1);
309 311
      swap(_last_active[level]--, --_first[level+1]);
310 312
      if (level+1>_highest_active) ++_highest_active;
311 313
    }
312 314

	
313 315
    ///Lifts the active item returned by \c activeOn() member function.
314 316

	
315 317
    ///Lifts the active item returned by \c activeOn() member function
316 318
    ///to the given level.
317 319
    void liftActiveOn(int level, int new_level)
318 320
    {
319 321
      const Item ai = *_last_active[level];
320 322

	
321 323
      copy(--_first[level+1], _last_active[level]--);
322 324
      for(int l=level+1;l<new_level;l++)
323 325
        {
324 326
          copy(_last_active[l],_first[l]);
325 327
          copy(--_first[l+1], _last_active[l]--);
326 328
        }
327 329
      copy(ai,_first[new_level]);
328
      _level[ai]=new_level;
330
      _level.set(ai,new_level);
329 331
      if (new_level>_highest_active) _highest_active=new_level;
330 332
    }
331 333

	
332 334
    ///Lifts the active item returned by \c activeOn() member function.
333 335

	
334 336
    ///Lifts the active item returned by \c activeOn() member function
335 337
    ///to the top level.
336 338
    void liftActiveToTop(int level)
337 339
    {
338 340
      const Item ai = *_last_active[level];
339 341

	
340 342
      copy(--_first[level+1],_last_active[level]--);
341 343
      for(int l=level+1;l<_max_level;l++)
342 344
        {
343 345
          copy(_last_active[l],_first[l]);
344 346
          copy(--_first[l+1], _last_active[l]--);
345 347
        }
346 348
      copy(ai,_first[_max_level]);
347 349
      --_last_active[_max_level];
348
      _level[ai]=_max_level;
350
      _level.set(ai,_max_level);
349 351

	
350 352
      if (_highest_active==level) {
351 353
        while(_highest_active>=0 &&
352 354
              _last_active[_highest_active]<_first[_highest_active])
353 355
          _highest_active--;
354 356
      }
355 357
    }
356 358

	
357 359
    ///@}
358 360

	
359 361
    ///Lift an active item to a higher level.
360 362

	
361 363
    ///Lift an active item to a higher level.
362 364
    ///\param i The item to be lifted. It must be active.
363 365
    ///\param new_level The new level of \c i. It must be strictly higher
364 366
    ///than the current level.
365 367
    ///
366 368
    void lift(Item i, int new_level)
367 369
    {
368 370
      const int lo = _level[i];
369 371
      const Vit w = _where[i];
370 372

	
371 373
      copy(_last_active[lo],w);
372 374
      copy(--_first[lo+1],_last_active[lo]--);
373 375
      for(int l=lo+1;l<new_level;l++)
374 376
        {
375 377
          copy(_last_active[l],_first[l]);
376 378
          copy(--_first[l+1],_last_active[l]--);
377 379
        }
378 380
      copy(i,_first[new_level]);
379
      _level[i]=new_level;
381
      _level.set(i,new_level);
380 382
      if(new_level>_highest_active) _highest_active=new_level;
381 383
    }
382 384

	
383 385
    ///Move an inactive item to the top but one level (in a dirty way).
384 386

	
385 387
    ///This function moves an inactive item to the top but one level.
386 388
    ///It makes the underlying datastructure corrupt, so use is only if
387 389
    ///you really know what it is for.
388 390
    ///\pre The item is on the top level.
389 391
    void dirtyTopButOne(Item i) {
390
      _level[i] = _max_level - 1;
392
      _level.set(i,_max_level - 1);
391 393
    }
392 394

	
393 395
    ///Lift all items on and above a level to the top (and deactivate them).
394 396

	
395 397
    ///This function lifts all items on and above level \c l to \c
396 398
    ///maxLevel(), and also deactivates them.
397 399
    void liftToTop(int l)
398 400
    {
399 401
      const Vit f=_first[l];
400 402
      const Vit tl=_first[_max_level];
401 403
      for(Vit i=f;i!=tl;++i)
402
        _level[*i]=_max_level;
404
        _level.set(*i,_max_level);
403 405
      for(int i=l;i<=_max_level;i++)
404 406
        {
405 407
          _first[i]=f;
406 408
          _last_active[i]=f-1;
407 409
        }
408 410
      for(_highest_active=l-1;
409 411
          _highest_active>=0 &&
410 412
            _last_active[_highest_active]<_first[_highest_active];
411 413
          _highest_active--) ;
412 414
    }
413 415

	
414 416
  private:
415 417
    int _init_lev;
416 418
    Vit _init_num;
417 419

	
418 420
  public:
419 421

	
420 422
    ///\name Initialization
421 423
    ///Using this function you can initialize the levels of the item.
422 424
    ///\n
423 425
    ///This initializatios is started with calling \c initStart().
424 426
    ///Then the
425 427
    ///items should be listed levels by levels statring with the lowest one
426 428
    ///(with level 0). This is done by using \c initAddItem()
427 429
    ///and \c initNewLevel(). Finally \c initFinish() must be called.
428 430
    ///The items not listed will be put on the highest level.
429 431
    ///@{
430 432

	
431 433
    ///Start the initialization process.
432 434

	
433 435
    void initStart()
434 436
    {
435 437
      _init_lev=0;
436 438
      _init_num=&_items[0];
437 439
      _first[0]=&_items[0];
438 440
      _last_active[0]=&_items[0]-1;
439 441
      Vit n=&_items[0];
440 442
      for(typename ItemSetTraits<Graph,Item>::ItemIt i(_g);i!=INVALID;++i)
441 443
        {
442 444
          *n=i;
443
          _where[i]=n;
444
          _level[i]=_max_level;
445
          _where.set(i,n);
446
          _level.set(i,_max_level);
445 447
          ++n;
446 448
        }
447 449
    }
448 450

	
449 451
    ///Add an item to the current level.
450 452

	
451 453
    void initAddItem(Item i)
452 454
    {
453
     swap(_where[i],_init_num);
454
      _level[i]=_init_lev;
455
      swap(_where[i],_init_num);
456
      _level.set(i,_init_lev);
455 457
      ++_init_num;
456 458
    }
457 459

	
458 460
    ///Start a new level.
459 461

	
460 462
    ///Start a new level.
461 463
    ///It shouldn't be used before the items on level 0 are listed.
462 464
    void initNewLevel()
463 465
    {
464 466
      _init_lev++;
465 467
      _first[_init_lev]=_init_num;
466 468
      _last_active[_init_lev]=_init_num-1;
467 469
    }
468 470

	
469 471
    ///Finalize the initialization process.
470 472

	
471 473
    void initFinish()
472 474
    {
473 475
      for(_init_lev++;_init_lev<=_max_level;_init_lev++)
474 476
        {
475 477
          _first[_init_lev]=_init_num;
476 478
          _last_active[_init_lev]=_init_num-1;
477 479
        }
478 480
      _first[_max_level+1]=&_items[0]+_item_num;
479 481
      _last_active[_max_level+1]=&_items[0]+_item_num-1;
480 482
      _highest_active = -1;
481 483
    }
482 484

	
483 485
    ///@}
484 486

	
485 487
  };
486 488

	
487 489
  ///Class for handling "labels" in push-relabel type algorithms.
488 490

	
489 491
  ///A class for handling "labels" in push-relabel type algorithms.
490 492
  ///
491 493
  ///\ingroup auxdat
492 494
  ///Using this class you can assign "labels" (nonnegative integer numbers)
493 495
  ///to the edges or nodes of a graph, manipulate and query them through
494 496
  ///operations typically arising in "push-relabel" type algorithms.
495 497
  ///
496 498
  ///Each item is either \em active or not, and you can also choose a
497 499
  ///highest level active item.
498 500
  ///
499 501
  ///\sa Elevator
500 502
  ///
501 503
  ///\param Graph the underlying graph type
502 504
  ///\param Item Type of the items the data is assigned to (Graph::Node,
503 505
  ///Graph::Edge, Graph::UEdge)
504 506
  template <class Graph, class Item>
505 507
  class LinkedElevator {
506 508
  public:
507 509

	
508 510
    typedef Item Key;
509 511
    typedef int Value;
510 512

	
511 513
  private:
512 514

	
513 515
    typedef typename ItemSetTraits<Graph,Item>::
514 516
    template Map<Item>::Type ItemMap;
515 517
    typedef typename ItemSetTraits<Graph,Item>::
516 518
    template Map<int>::Type IntMap;
517 519
    typedef typename ItemSetTraits<Graph,Item>::
518 520
    template Map<bool>::Type BoolMap;
519 521

	
520 522
    const Graph &_graph;
521 523
    int _max_level;
522 524
    int _item_num;
523 525
    std::vector<Item> _first, _last;
524 526
    ItemMap _prev, _next;
525 527
    int _highest_active;
526 528
    IntMap _level;
527 529
    BoolMap _active;
528 530

	
529 531
  public:
530 532
    ///Constructor with given maximum level.
531 533

	
532 534
    ///Constructor with given maximum level.
533 535
    ///
534 536
    ///\param g The underlying graph
535 537
    ///\param max_level Set the range of the possible labels to
536 538
    ///[0...\c max_level]
537 539
    LinkedElevator(const Graph& graph, int max_level)
538 540
      : _graph(graph), _max_level(max_level), _item_num(_max_level),
539 541
        _first(_max_level + 1), _last(_max_level + 1),
540 542
        _prev(graph), _next(graph),
541 543
        _highest_active(-1), _level(graph), _active(graph) {}
542 544

	
543 545
    ///Constructor.
544 546

	
545 547
    ///Constructor.
546 548
    ///
547 549
    ///\param g The underlying graph
548 550
    ///The range of the possible labels is [0...\c max_level],
549 551
    ///where \c max_level is equal to the number of labeled items in the graph.
550 552
    LinkedElevator(const Graph& graph)
551 553
      : _graph(graph), _max_level(countItems<Graph, Item>(graph)),
552 554
        _item_num(_max_level),
553 555
        _first(_max_level + 1), _last(_max_level + 1),
554 556
        _prev(graph, INVALID), _next(graph, INVALID),
555 557
        _highest_active(-1), _level(graph), _active(graph) {}
556 558

	
557 559

	
558 560
    ///Activate item \c i.
559 561

	
560 562
    ///Activate item \c i.
561 563
    ///\pre Item \c i shouldn't be active before.
562 564
    void activate(Item i) {
563 565
      _active.set(i, true);
564 566

	
565 567
      int level = _level[i];
566 568
      if (level > _highest_active) {
567 569
        _highest_active = level;
568 570
      }
569 571

	
570 572
      if (_prev[i] == INVALID || _active[_prev[i]]) return;
571 573
      //unlace
572 574
      _next.set(_prev[i], _next[i]);
573 575
      if (_next[i] != INVALID) {
574 576
        _prev.set(_next[i], _prev[i]);
575 577
      } else {
576 578
        _last[level] = _prev[i];
577 579
      }
578 580
      //lace
579 581
      _next.set(i, _first[level]);
580 582
      _prev.set(_first[level], i);
581 583
      _prev.set(i, INVALID);
582 584
      _first[level] = i;
583 585

	
584 586
    }
585 587

	
586 588
    ///Deactivate item \c i.
587 589

	
588 590
    ///Deactivate item \c i.
589 591
    ///\pre Item \c i must be active before.
590 592
    void deactivate(Item i) {
591 593
      _active.set(i, false);
592 594
      int level = _level[i];
593 595

	
594 596
      if (_next[i] == INVALID || !_active[_next[i]])
595 597
        goto find_highest_level;
596 598

	
597 599
      //unlace
598 600
      _prev.set(_next[i], _prev[i]);
599 601
      if (_prev[i] != INVALID) {
600 602
        _next.set(_prev[i], _next[i]);
601 603
      } else {
602 604
        _first[_level[i]] = _next[i];
603 605
      }
604 606
      //lace
605 607
      _prev.set(i, _last[level]);
606 608
      _next.set(_last[level], i);
607 609
      _next.set(i, INVALID);
608 610
      _last[level] = i;
609 611

	
610 612
    find_highest_level:
611 613
      if (level == _highest_active) {
612 614
        while (_highest_active >= 0 && activeFree(_highest_active))
613 615
          --_highest_active;
614 616
      }
615 617
    }
616 618

	
617 619
    ///Query whether item \c i is active
618 620
    bool active(Item i) const { return _active[i]; }
619 621

	
620 622
    ///Return the level of item \c i.
621 623
    int operator[](Item i) const { return _level[i]; }
622 624

	
623 625
    ///Return the number of items on level \c l.
624 626
    int onLevel(int l) const {
625 627
      int num = 0;
626 628
      Item n = _first[l];
627 629
      while (n != INVALID) {
628 630
        ++num;
629 631
        n = _next[n];
630 632
      }
631 633
      return num;
632 634
    }
633 635

	
634 636
    ///Return true if the level is empty.
635 637
    bool emptyLevel(int l) const {
636 638
      return _first[l] == INVALID;
637 639
    }
638 640

	
639 641
    ///Return the number of items above level \c l.
640 642
    int aboveLevel(int l) const {
641 643
      int num = 0;
642 644
      for (int level = l + 1; level < _max_level; ++level)
643 645
        num += onLevel(level);
644 646
      return num;
645 647
    }
646 648

	
0 comments (0 inline)