Click here to Skip to main content
15,921,382 members
Articles / Programming Languages / C++

Advent Of Code – Reindeer Olympics – Puzzle 14

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
15 Oct 2019CPOL4 min read 3.9K   1   4
Reindeer olympics

This is Part 14 of a long series on Advent Of Code. You can find the previous parts here.

For this new post, we are going to solve the problem from 14th December 2015, named "Reindeer Olympics". The solution I will propose is in C++, but the reasoning can be applied to other languages.

Part 1

The Problem

The full version of this problem can be found directly on the Advent of Code website, I will only describe the essence of the problem here:

It’s the Reindeer Olympics! A race between Santa’s reindeer! I would love to witness such an event! 🎅

The reindeer can fly only for several seconds before needing some rest. For example, we can have a reindeer named Comet which can fly 14 km/s for 10 seconds, but then must rest for 127 seconds.

After one second, Comet has gone 14 km. After ten seconds, Comet has gone 140 km. On the eleventh second, Comet begins resting (staying at 140 km) for 127 seconds.

For this race, we will have to find what distance the winning reindeer runs through, knowing that the race will last exactly 2503 seconds (because all races should last 2503 seconds 😆).

Solution

First thing first, let’s define what is going to be a Reindeer in our program.

C++
class Reindeer
{
public:
    using Speed = size_t;
    using Position = size_t;
    using Time = size_t;

    Reindeer (const std::string& name_, const Speed flyingSpeed_, 
              const Time flyingTime_, const Time restTime_);
    ~Reindeer ();

    void oneSecondPassed ();
    Position getCurrentPosition () const;

private:
    std::string name;
    Speed flyingSpeed{0};
    Time flyingTime{0};
    Time restTime{0};

    Position position{0};
    Time timeBeforeChangingState{0};
    bool isFlying{true};
};

Wow, that’s a lot of variables! But we are going to need all of them (except of the name, that I only used for debug purposes). The variables flyingSpeed, flyingTime and restTime are the characteristics of the Reindeer for the Race. The variable position is the one which is going to help us find out which of the Reindeer wins the race, and the two last variables (timeBeforeChangingState and isFlying) are going to help us in figuring out when the reindeer can fly and when it must rest. So let’s take a look at how they are used:

C++
void Reindeer::oneSecondPassed ()
{
    if(timeBeforeChangingState == 0)
    {
        isFlying = !isFlying;
        timeBeforeChangingState = isFlying ? flyingTime : restTime;
    }

    if(isFlying)
    {
        position += flyingSpeed;
    }
    --timeBeforeChangingState;
}

How does it work?!

The variable timeBeforeChangingState tracks the time the reindeer still has either to fly or to rest. So when it reaches 0, we change state and reset timeBeforeChangingState with the time variable matching the new state.

And we also update the position if the reindeer is currently flying.

So now that we know what a reindeer looks like, and how they behave at each second; making them go through the race will be pretty straightforward. Indeed, since the race lasts 2503 seconds, we only need to use for loops:

C++
for(auto i = 0; i < 2503; ++i)
{
    for(auto& reindeer: reindeers)
    {
        reindeer.oneSecondPassed();
    }
}

But before running the race, we have to know who are the participants of the race. For information, a reindeer description looks like that: "Prancer can fly 18 km/s for 6 seconds, but then must rest for 103 seconds." So to extract the useful information to make an instance of a Reindeer.

C++
Reindeer extractInformationFrom (const std::string& line)
{
    std::istringstream stream(line);
    std::string reindeerName, can, fly, km_s, for_, seconds, 
                              but, then, must, rest, for__, seconds_;
    Reindeer::Speed flyingSpeed;
    Reindeer::Time flyingTime;
    Reindeer::Time restTime;
 
    stream >> reindeerName >> can >> fly >> flyingSpeed >> km_s >> for_ >> 
    flyingTime >> seconds >> but >> then >> must >> rest >> for__ >> restTime >> seconds_;
    return Reindeer(reindeerName, flyingSpeed, flyingTime, restTime);
}

Inspired by a comment on my last article on Reddit, I have decided to use a std::istringstream to get each word in the line describing each reindeer.

The useless words will be deleted from the stack at the end of the function, while the useful one will serve to create the "Reindeer".

I found this solution pretty simple, I don’t think I would have been able to think about such a thing by myself, so thank you algmyr for your comment. 😃 If you, who are reading this post, want to make a comment about some part of it, be my guest, I will gladly read them. 🙂

Finally, for this first part, we must find the winner of the race! To find it, all we need is to use the std function std::max_element, like that:

C++
const auto winningReindeer =
    std::max_element(
        std::begin(reindeers),
        std::end(reindeers),
        [](const auto& firstReindeer, const auto& secondReindeer)
        {
            return firstReindeer.getCurrentPosition () < secondReindeer.getCurrentPosition ();
        });
const auto distanceOfWinningReindeer = winningReindeer->getCurrentPosition ();

And voilà, we have the distance run by the winner of the race.

Part 2

Problem

When seeing the result of the race in Part 1, Santa decides to change the scoring system. So, instead of the old one, now, he gives one point to each reindeer leading the race at each second.

Solution

So now, the race looks a bit different:

C++
for(auto i = 0; i < 2503; ++i)
{
    for(auto& reindeer: reindeers)
    {
        reindeer.oneSecondPassed();
    }

    const auto winningReindeer = std::max_element(std::begin(reindeers), 
                                 std::end(reindeers), [](const auto& firstReindeer, 
                                 const auto& secondReindeer)
                                 { return firstReindeer.getCurrentPosition () < 
                                   secondReindeer.getCurrentPosition (); });
    const auto distanceOfWinningReindeer = winningReindeer->getCurrentPosition ();

    for(auto& reindeer: reindeers)
    {
        if(reindeer.getCurrentPosition() == distanceOfWinningReindeer)
        {
            reindeer.winBonusPoint();
        }
    }
}

So after each of the Reindeer makes his move for the current second, we look at the leading reindeer, with std::max_element. Once we have it, we look how far it is from the start, and we give one bonus point to each reindeer at the same distance from the start.

The function Reindeer::winBonusPoint only increases a new variable in the Reindeer class, to store the number of points of each reindeer.

And now, to know the winner, we use almost the same code as in the first part:

C++
const auto winningReindeer =
    std::max_element
    std::begin(reindeers),
    std::end(reindeers),
    [](const auto& firstReindeer, const auto& secondReindeer)
    {
        return firstReindeer.getCurrentBonusPoint () < secondReindeer.getCurrentBonusPoint ();
    });
const auto pointsOfWinningReindeer = winningReindeer->getCurrentBonusPoint ();

And voilà, we know the number of points of the winner of the race. 🤘

Conclusion

You can note that the solutions, written in this post, don’t include all the sources to make running programs, but only the interesting part of the sources to solve this problem. If you want to see the programs from end to end, you can go on my GitHub account, explore the full solution, add comments or ask questions if you want to, on the platform you read this article, it will also help me improve the quality of my articles.

Here is the list of elements that we have used, I can’t encourage you enough to look at their definitions:

Thanks for reading, hope you liked it! 😃

And until the next part, have fun learning and growing.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



Comments and Discussions

 
QuestionAn essay in anti-minmalism Pin
Member 1285032216-Oct-19 11:57
Member 1285032216-Oct-19 11:57 
This is my first posting on Codeproject. I manage developers and I see such bloated coding solutions every day.

Many middle-level developers believe they can predict the future; for example they "know" there will be a scoring system other than the first at the finish wins. Because of this, they do not focus on solving a problem, but rather their energy is spend with a plurality of hypothetical problems, that "may arise" in future.

If you think you can predict the future, put your money were your mouth is and invest at the stock exchange. You will learn quickly that the future is a continuous surprise.

Here is the solution for the first sentence in problem 1:

C++
#include <iostream>
double flightDistance(double secondsFly, double speedFly, double secondsRest, double secondsLen) {
    // duration of fly/rest cycle
    double cycle = secondsFly + secondsRest;
    // all zero means we stay
    if (cycle == 0)
       return 0; 
    // number of cycles in our run
    int cycles = secondsLen / cycle;
    // time left in the last cycle
    double remainTime = secondsLen - cycles * cycle;
    // are we resting in the last cycle?
    if (remainTime > secondsFly)
      remainTime = secondsFly;
    // total distance is the distance of each cycle plus the distance in the last cycle
    return speedFly * (cycles* secondsFly   +  remainTime);
}

int main()
{
    int secs = 2503;
    std::cout << "seconds: " << secs << " distance:" << flightDistance(10,14,127,secs) << std::endl ;
}



In my opinion, the posting makes some mistakes:

- creating the class Reindeer is unnecessary, bloating the code. Focus moves to OO rather than solving the problem.

- unnecessary destructor. This is what i call "ritualistic coding".

- unnecessary definition of types speed, distance and time. They don't help us in any way.

- The constructor has a lot of parameters (more or less, same as my function). The real reindeer class may be very complicated; it will include number of legs, age, pay-scale grade, years of experience, country of origin and the date they took their FAA certification. Also will include the firmware version of reindeer's MCAS. In that case, a constructor with so many parameters is very hard to use.

- given the text of problem 1, it is unnecessary to advance each reindeer each second; rather, computing the position after a number of seconds provides numerical stability.

- time and distance are floating point values, rather than the number of attempts to write a program right, which is an unsigned integer.


The solution of the race winner(s) is here:

C++
int main()
{
    std::set<std::string> winner;
    double winDistance = 0;
    double raceLen;
    std::cout << "Race length:";
    std::cin >> raceLen;      
    while (true) {
        std::string name;
        std::cout << "Name:";
        std::cin >> name;
        double speed;
        std::cout << "Speed:";
        std::cin >> speed;  
        double flytime;
        std::cout << "Fly time:";
        std::cin >> flytime;
        double restTime;
        std::cout << "Rest time:";
        std::cin >> restTime; 
        double distance = flightDistance(flytime,speed,restTime,raceLen);
        if (distance > winDistance) {
            winner.clear();
            winDistance = distance;
        }
        if (distance == winDistance) 
            winner.insert(name);
        std::cout << "Winner(s) so far:" ;
        for (std::string win : winner)
          std::cout << win << " ";
        std::cout << "distance: " << winDistance << std::endl; 
    }
}



The main mistake in the posting code is not handling ties, only the first winning time is being recorded.

Also, no data input is being shown, in my example the most of the code is data input.

It is certain that the final version of this code will not be console IO. However, since it is not specified, I did not make any assumption, like read data from a web form, a Google Sheet or a Microsoft Excel file; the choice of console IO was intentional, as the simplest approach to contain the full problem and not show code chunks.

I still didn't see any reason to introduce objects. If I will ever apply again for a developer position, I would rather prefer to ask the interviewer about the class structure they require. If reindeers will be produced by a factory (so far I was under the impression that they are mammals) then a ReindeerFactory it is. Obviously implementing the singleton pattern too. If a facade pattern is required to handle metric, imperial or even Klingon systems, a facade will be created. Certainly the Race class will be generic (maybe next time we race snails, imperial walkers or Brownian particles) and injected in the code, but that's for the Java interview. I mean, the possibilities to unnecessarily extend the code base are infinite. I have very clear rules for creating objects, rather late in the project. Too early implementation of objects will break the design with each further change.

Also, my approach is not a solution of "code golfing". I could have compressed the code even more, by sacrificing readability.

I've written the posting because bloated code is an endemic I try to fight every day. It prohibits a developer to reach the legendary 10x level, it prohibits other developers to use efficiently the existing code and it generates sterile discussions in the team. Given the complexity of the problem, I assume it is addressing the beginners, which shall understand that complexity is their major obstacle, not their lack of fully understanding some API or language.
AnswerRe: An essay in anti-minmalism Pin
10xlearner8-Nov-19 5:26
10xlearner8-Nov-19 5:26 
PraiseMy vote of 5! Pin
jediYL16-Oct-19 9:23
professionaljediYL16-Oct-19 9:23 
GeneralRe: My vote of 5! Pin
10xlearner8-Nov-19 5:04
10xlearner8-Nov-19 5:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.