<< problem 164 - Numbers for which no three consecutive digits ... | Criss Cross - problem 166 >> |
Problem 165: Intersections
(see projecteuler.net/problem=165)
A segment is uniquely defined by its two endpoints.
By considering two line segments in plane geometry there are three possibilities:
the segments have zero points, one point, or infinitely many points in common.
Moreover when two segments have exactly one point in common it might be the case that that common point is an endpoint of either one of the segments or of both.
If a common point of two segments is not an endpoint of either of the segments it is an interior point of both segments.
We will call a common point T of two segments L_1 and L_2 a true intersection point of L_1 and L_2 if T is the only common point of L_1 and L_2 and T is an interior point of both segments.
Consider the three segments L_1, L_2, and L_3:
L_1: (27, 44) to (12, 32)
L_2: (46, 53) to (17, 62)
L_3: (46, 70) to (22, 40)
It can be verified that line segments L_2 and L_3 have a true intersection point.
We note that as the one of the end points of L_3: (22,40) lies on L_1 this is not considered to be a true point of intersection.
L_1 and L_2 have no common point. So among the three line segments, we find one true intersection point.
Now let us do the same for 5000 line segments. To this end, we generate 20000 numbers using the so-called "Blum Blum Shub" pseudo-random number generator.
s_0 = 290797
s_{n+1} == s_n * s_n mod 50515093
t_n == s_n mod 500
To create each line segment, we use four consecutive numbers tn. That is, the first line segment is given by:
(t_1, t_2) to (t_3, t_4)
The first four numbers computed according to the above generator should be: 27, 144, 12 and 232. The first segment would thus be (27,144) to (12,232).
How many distinct true intersection points are found among the 5000 line segments?
My Algorithm
I studied Computer Graphics at university and had no problem coming up with an intersection algorithm.
Wikipedia's explanations of the use of the determinant are somehow weird and I would have trouble unterstanding it - but I already knew that from classes at university.
The main issue inside my intersect
code is numerical stability.
These three lines reduce the number of digits so that the correct result is found:
const auto Precision = 0.00000001;
where.x = round(where.x / Precision) * Precision;
where.y = round(where.y / Precision) * Precision;
The constant Precision
was found by trial-and-error.
The pseudo-random Blum Blum shub algorithm can be converted to a few lines of code (see next
).
My code provides two simple classes Point
and Segment
to keep things organized.
Point
needs to support comparisons operators for std::sort
and std::unique
(weeding out duplicate intersections).
main
finds all (!) intersections and defers checking for duplicates until the end.
Duplicates are eliminated as follows:
- sort all intersections → identical intersections will be neighboring elements in the sorted set
- call
std::unique
to move all duplicates to the end of the data container ("garbage") - remove all garbage points
Interactive test
You can submit your own input to my program and it will be instantly processed at my server:
This is equivalent toecho 10 | ./165
Output:
Note: the original problem's input 5000
cannot be entered
because just copying results is a soft skill reserved for idiots.
(this interactive test is still under development, computations will be aborted after one second)
My code
… was written in C++11 and can be compiled with G++, Clang++, Visual C++. You can download it, too.
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
// generate pseudo-random numbers (Blum Blum Shub algorithm)
unsigned int next()
{
static unsigned long long seed = 290797;
seed *= seed;
seed %= 50515093;
return seed % 500;
}
// a 2D point
struct Point
{
double x, y;
// needed for std::unique
bool operator==(const Point& other) const
{
return x == other.x && y == other.y;
}
// needed for std::sort
bool operator< (const Point& other) const
{
if (x != other.x)
return x < other.x;
else
return y < other.y;
}
};
// define a segment
struct Segment
{
Point from, to;
};
// find intersection of two segments, out parameter "where" is only valid if function returns true
bool intersect(const Segment& segment1, const Segment& segment2, Point& where)
{
// shorter names for the four endpoints
auto a = segment1.from;
auto b = segment1.to;
auto c = segment2.from;
auto d = segment2.to;
// store slope in a Point (just because I'm lazy and don't want to introduce another data type)
Point slope1, slope2;
slope1.x = b.x - a.x;
slope1.y = b.y - a.y;
slope2.x = d.x - c.x;
slope2.y = d.y - c.y;
auto determinant = slope1.x * slope2.y - slope2.x * slope1.y;
// parallel ?
if (determinant == 0)
return false;
// now the lines intersect, but not necessarily the segments
auto s = (slope1.x * (a.y - c.y) - slope1.y * (a.x - c.x)) / determinant;
auto t = (slope2.x * (a.y - c.y) - slope2.y * (a.x - c.x)) / determinant;
// parameters s and t must be in (0 ... 1)
// borders (=endpoints) are not true intersections according to problem statement
if (s <= 0 || s >= 1 || t <= 0 || t >= 1)
return false;
// yes, intersection found (might be a duplicate, though !)
where.x = a.x + t * slope1.x;
where.y = a.y + t * slope1.y;
// cut off a few digits to avoid rounding issues
const auto Precision = 0.00000001;
where.x = round(where.x / Precision) * Precision;
where.y = round(where.y / Precision) * Precision;
return true;
}
int main()
{
std::vector<Segment> segments;
std::vector<Point> intersections;
unsigned int limit = 5000;
std::cin >> limit;
for (unsigned int i = 0; i < limit; i++)
{
// create "random" segment
Segment current;
current.from.x = next();
current.from.y = next();
current.to .x = next();
current.to .y = next();
// try to intersect with all other segments
Point where;
for (auto compare : segments)
if (intersect(current, compare, where))
intersections.push_back(where);
// add current segment to list of segments
segments.push_back(current);
}
// eliminate duplicate intersection points
std::sort(intersections.begin(), intersections.end());
auto garbage = std::unique(intersections.begin(), intersections.end());
intersections.erase(garbage, intersections.end());
// display result
std::cout << intersections.size() << std::endl;
return 0;
}
This solution contains 19 empty lines, 19 comments and 4 preprocessor commands.
Benchmark
The correct solution to the original Project Euler problem was found in 0.6 seconds on an Intel® Core™ i7-2600K CPU @ 3.40GHz.
Peak memory usage was about 69 MByte.
(compiled for x86_64 / Linux, GCC flags: -O3 -march=native -fno-exceptions -fno-rtti -std=gnu++11 -DORIGINAL
)
See here for a comparison of all solutions.
Note: interactive tests run on a weaker (=slower) computer. Some interactive tests are compiled without -DORIGINAL
.
Changelog
June 20, 2017 submitted solution
June 20, 2017 added comments
Hackerrank
see https://www.hackerrank.com/contests/projecteuler/challenges/euler165
My code solves 11 out of 11 test cases (score: 100%)
Difficulty
Project Euler ranks this problem at 65% (out of 100%).
Hackerrank describes this problem as easy.
Note:
Hackerrank has strict execution time limits (typically 2 seconds for C++ code) and often a much wider input range than the original problem.
In my opinion, Hackerrank's modified problems are usually a lot harder to solve. As a rule thumb: brute-force is rarely an option.
Links
projecteuler.net/thread=165 - the best forum on the subject (note: you have to submit the correct solution first)
Heatmap
Please click on a problem's number to open my solution to that problem:
green | solutions solve the original Project Euler problem and have a perfect score of 100% at Hackerrank, too | |
yellow | solutions score less than 100% at Hackerrank (but still solve the original problem easily) | |
gray | problems are already solved but I haven't published my solution yet | |
blue | solutions are relevant for Project Euler only: there wasn't a Hackerrank version of it (at the time I solved it) or it differed too much | |
orange | problems are solved but exceed the time limit of one minute or the memory limit of 256 MByte | |
red | problems are not solved yet but I wrote a simulation to approximate the result or verified at least the given example - usually I sketched a few ideas, too | |
black | problems are solved but access to the solution is blocked for a few days until the next problem is published | |
the flashing problem is the one I solved most recently |
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 |
76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 |
101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 |
126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 |
151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 |
176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 |
201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 |
226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 |
251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 |
276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 |
301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 |
326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 |
351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 |
376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 |
401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 |
426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 |
451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 |
476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 |
501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 |
526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 |
551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 |
576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 |
601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 |
I scored 13,486 points (out of 15700 possible points, top rank was 17 out of ≈60000 in August 2017) at Hackerrank's Project Euler+.
My username at Project Euler is stephanbrumme while it's stbrumme at Hackerrank.
Look at my progress and performance pages to get more details.
Copyright
I hope you enjoy my code and learn something - or give me feedback how I can improve my solutions.
All of my solutions can be used for any purpose and I am in no way liable for any damages caused.
You can even remove my name and claim it's yours. But then you shall burn in hell.
The problems and most of the problems' images were created by Project Euler.
Thanks for all their endless effort !!!
<< problem 164 - Numbers for which no three consecutive digits ... | Criss Cross - problem 166 >> |