0

The solution presented at CodeReview works fine on CentOS 7.1. I have tried to port it to Windows 7, Visual Studio 2012. With minor edits to allow for the parts of C++11 that VS 2012 doesn't support the code compiles, and the first execution of the loop works correctly. The rest of the execution of test cases fail, growing progressively more incorrect with each execution.

The code for this problem can be found on github.

finished computation at 0 /* problem here not included in question */
elapsed time: 0.202012 Seconds

The point of origin for all path searches was A3
The destination point for all path searches was H4
The number of squares on each edge of the board is 8
The slicing methodology used to further limit searches was no repeat visits to any rows or columns.
There are 5 Resulting Paths
There were 323 attempted paths
The average path length is 4.8
The median path length is 4
The longest path is 6 moves
The shortest path is 4 moves

I believe the problem is in one of the files listed below. I've been debugging this for 2 days, I can use some help.

CRKnightMoves_Cpp2.cpp

/*
 * KnightMoves.cpp
 *
 *      Author: pacmaninbw
 */

#include "stdafx.h"
#include <iostream>
#include <stdexcept>
#include <chrono>
#include <ctime>
#include <algorithm>
#include <functional>
#include <vector>
#include "KnightMoves.h"
#include "KnightMovesImplementation.h"
#include "KMBoardDimensionConstants.h"

double Average(std::vector<double> TestTimes)
{
    double AverageTestTime = 0.0;
    double SumOfTestTimes = 0.0;
    int CountOfTestTimes = 0;

    for (auto TestTimesIter : TestTimes)
    {
        SumOfTestTimes += TestTimesIter;
        CountOfTestTimes++;
    }

    if (CountOfTestTimes) { // Prevent division by zero.
        AverageTestTime = SumOfTestTimes / static_cast<double>(CountOfTestTimes);
    }

    return AverageTestTime;
}

void OutputOverAllStatistics(std::vector<double> TestTimes)
{
    if (TestTimes.size() < 1) {
        std::cout << "No test times to run statistics on!" << std::endl;
        return;
    }

    std::cout << std::endl << "Overall Results" << std::endl;
    std::cout << "The average execution time is " << Average(TestTimes) << " seconds" << std::endl;
    std::nth_element(TestTimes.begin(), TestTimes.begin() + TestTimes.size()/2, TestTimes.end());
    std::cout << "The median execution time is " << TestTimes[TestTimes.size()/2] << " seconds" << std::endl;
    std::nth_element(TestTimes.begin(), TestTimes.begin()+1, TestTimes.end(), std::greater<double>());
    std::cout << "The longest execution time is " << TestTimes[0] << " seconds" << std::endl;
    std::nth_element(TestTimes.begin(), TestTimes.begin()+1, TestTimes.end(), std::less<double>());
    std::cout << "The shortest execution time is " << TestTimes[0] << " seconds" << std::endl;
}

double ExecutionLoop(KMBaseData UserInputData)
{
    KnightMovesImplementation *KnightPathFinder = new KnightMovesImplementation(UserInputData);

    std::chrono::time_point<std::chrono::system_clock> start, end;
    start = std::chrono::system_clock::now();
    KMOutputData OutputData = KnightPathFinder->CalculatePaths();
    end = std::chrono::system_clock::now();

    std::chrono::duration<double> elapsed_seconds = end-start;
    std::time_t end_time = std::chrono::system_clock::to_time_t(end);
    double ElapsedTimeForOutPut = elapsed_seconds.count();
    char ctimebuffer[1024];
    std::cout << "finished computation at " << ctime_s(ctimebuffer, 1024, &end_time) << "\n"
          << "elapsed time: " << ElapsedTimeForOutPut << " Seconds\n" << "\n" << "\n";
    // Don't include output of results in elapsed time calculation
    OutputData.DontShowPathData();
    // OutputData.ShowPathData();
    OutputData.ShowResults();

    delete KnightPathFinder;

    return  ElapsedTimeForOutPut;
}

int LetUserEnterTestCaseNumber(std::vector<KMBaseData> &TestData)
{
    int i = 1;
    int Choice = -1;

    std::cout << "Select the number of the test case you want to run.\n";
    std::cout << "Test Case #" << "\tStart Name" << "\tTarget Name" << "\tBoard Size" << "\tSlicing Method" << "\n";
    for (auto TestCase: TestData) {
        std::cout << i << "\t" << TestCase.m_StartName << "\t" << TestCase.m_TargetName << "\t" << TestCase.m_DimensionOneSide << "\t";
        switch (TestCase.m_LimitationsOnMoves)
        {
            default :
                throw std::runtime_error("LetUserEnterTestCaseNumber : Unknown type of Path Limitation.");
            case DenyByPreviousLocation :
                std::cout << "Can't return to previous location";
                break;
            case DenyByPreviousRowOrColumn:
                std::cout << "Can't return to previous row or column";
                break;
        }
        std::cout << "\n";
        i++;
    }
    std::cout << i << "\tAll of the above except for 13 and 14\n";
    std::cout << ++i <<"\tAll of the above (Go get lunch)\n";

    std::cin >> Choice;

    if (Choice == 15)
    {
        std::vector<KMBaseData> TempTests;
        for (auto TestCase: TestData)
        {
            if ((TestCase.m_DimensionOneSide != MaximumBoardDimension) && (TestCase.m_LimitationsOnMoves != DenyByPreviousLocation))
            {
                TempTests.push_back(TestCase);
            }
        }
        TestData = TempTests;
    }
    return Choice;
}

void InitTestData(std::vector<KMBaseData> &TestData)
{
    KMBaseData TestCases[] = {
        {1,3,"A3",8,4,"H4", DefaultBoardDimensionOnOneSide, DenyByPreviousRowOrColumn},
        {1,1,"A1",8,8,"H8", DefaultBoardDimensionOnOneSide, DenyByPreviousRowOrColumn},
        {1,8,"A8",8,1,"H1", DefaultBoardDimensionOnOneSide, DenyByPreviousRowOrColumn},
        {2,3,"B3",8,4,"H4", DefaultBoardDimensionOnOneSide, DenyByPreviousRowOrColumn},
        {2,3,"B3",8,8,"H8", DefaultBoardDimensionOnOneSide, DenyByPreviousRowOrColumn},
        {3,1,"C1",8,4,"H4", DefaultBoardDimensionOnOneSide, DenyByPreviousRowOrColumn},
        {3,1,"A3",8,8,"H8", DefaultBoardDimensionOnOneSide, DenyByPreviousRowOrColumn},
        {1,3,"A3",2,5,"B5", DefaultBoardDimensionOnOneSide, DenyByPreviousRowOrColumn},    // Minimum should be one move
        {8,4,"H4",1,3,"A3", DefaultBoardDimensionOnOneSide, DenyByPreviousRowOrColumn},
        {4,4,"D4",1,8,"A8", DefaultBoardDimensionOnOneSide, DenyByPreviousRowOrColumn},
        {4,4,"D4",5,6,"E6", DefaultBoardDimensionOnOneSide, DenyByPreviousRowOrColumn},
        {1,3,"A3",2,5,"B5", 12, DenyByPreviousRowOrColumn},                            // Minimum should be one move
        {1,3,"A3",2,5,"B5", DefaultBoardDimensionOnOneSide,  DenyByPreviousLocation},            // Minimum should be one move
        {1,3,"A3",2,5,"B5", MaximumBoardDimension, DenyByPreviousRowOrColumn}        // Minimum should be one move
    };
    for (int i = 0; i < sizeof(TestCases)/sizeof(KMBaseData); i++) {
        TestData.push_back(TestCases[i]);
    }
}

int _tmain(int argc, _TCHAR* argv[])
{
    int status = 0;

    std::vector<KMBaseData> TestData;
    std::vector<double> TestTimes;

    try {

        InitTestData(TestData);

        int Choice = LetUserEnterTestCaseNumber(TestData);

        if (Choice < 0)
        {
            return status;
        }

        if (Choice < 15)
        {
            ExecutionLoop(TestData[Choice-1]);
        }
        else
        {
            for (auto TestDataIter: TestData) {
                TestTimes.push_back(ExecutionLoop(TestDataIter));
            }
        }

        OutputOverAllStatistics(TestTimes);

        return status;
    }
    catch(std::runtime_error &e) {
        std::cerr << "A fatal error occurred in KnightMoves: ";
        std::cerr << e.what()  << std::endl;
        status = 1;
    }
    catch(std::runtime_error *e) {
        std::cerr << "A fatal error occurred in KnightMoves: ";
        std::cerr << e->what()  << std::endl;
        status = 1;
    }
    catch(...) {
        std::cerr << "An unknown fatal error occurred in KnightMoves." << std::endl;
        status = 1;
    }
    return status;
}

KnightMovesImplementation.h

#pragma once
/*
 * KnightMovesImplementation.h
 *
 *  Created on: Mar 18, 2016
 *  Modified on: June 20, 2016
 *      Author: pacmaninbw
 *
 *      This class provides the search for all the paths a Knight on a chess
 *      board can take from the point of origin to the destination. It
 *      implements a modified Knights Tour. The classic knights tour problem
 *      is to visit every location on the chess board without returning to a
 *      previous location. That is a single path for the knight. This
 *      implementation returns all possible paths from point a to point b.
 *      The actual implementation is documented in the CPP file because it
 *      can be changed. This head file provides the public interface which
 *      should not be changed. The public interface may be moved to a
 *      super class in the future.
 */

#ifndef KNIGHTMOVESIMPLEMENTATION_H_
#define KNIGHTMOVESIMPLEMENTATION_H_

#include "KMPath.h"
#include "KMOutputData.h"
#include "KMMoveFilters.h"

class KnightMovesImplementation {
private:
    KMBoardLocation m_PointOfOrigin;
    KMBoardLocation m_Destination;
    unsigned int m_SingleSideBoardDimension;
    KnightMovesMethodLimitations m_PathLimitations;
    KMOutputData *m_Results;
    KMMoveFilters *m_MoveFilters;
    KMPath *m_Path;

protected:
    bool CalculatePath(KMMove CurrentMove);        // Recursive function
    void InitPointOfOrigin(KMBaseData UserData);
    void InitDestination(KMBaseData UserData);

public:
    KnightMovesImplementation(KMBaseData UserData);
    virtual ~KnightMovesImplementation(void);
    KMOutputData CalculatePaths();
};

#endif /* KNIGHTMOVESIMPLEMENTATION_H_ */

KnightsImplementation.cpp

/*
 * KnightMovesImplementation.cpp
 *
 *  Created on: Mar 18, 2016
 *  Modified on: June 21, 2016
 *  Commented on: June 24, 2016
 *      Author: pacmaninbw
 *
 *      This class implements the search for all possible paths for a Knight
 *      on a chess board from one particular square on the board to another
 *      particular square on the board.
 *
 *      The current implementation is a Recursive Breadth First Search. Conceptually
 *      the algorithm implements a B+ tree with a maximum of 8 possible branches
 *      at each level. The root of the tree is the point of origin. A particular
 *      path terminates in a leaf. A leaf is the result of either reaching the
 *      destination, or reaching a point where there are no more branches to
 *      traverse.
 *
 *      The m_Path variable is used as a stack within the search.
 *
 *      The public interface CalculatePaths establishes the root and creates
 *      the first level of branching. The protected interface CalculatePath
 *      performs the recursive depth first search, however, the
 *      m_MoveFilters.GetPossibleMoves() function it calls performs a breadth
 *      first search of the current level.
 *
 */

#include "stdafx.h"
#include "KnightMoves.h"
#include "KnightMovesImplementation.h"
#include "KMBoardDimensionConstants.h"

KnightMovesImplementation::~KnightMovesImplementation(void)
{
    delete m_MoveFilters;
    delete m_Results;
    delete m_Path;
}

KnightMovesImplementation::KnightMovesImplementation(KMBaseData UserInputData)
 : m_SingleSideBoardDimension(UserInputData.m_DimensionOneSide),
   m_PathLimitations(UserInputData.m_LimitationsOnMoves)
{
    InitPointOfOrigin(UserInputData);
    InitDestination(UserInputData);
    m_Path = new KMPath;
    m_MoveFilters = new KMMoveFilters(static_cast<unsigned int>(UserInputData.m_DimensionOneSide), UserInputData.m_LimitationsOnMoves);
    m_Results = new KMOutputData(m_PointOfOrigin, m_Destination, m_SingleSideBoardDimension, m_PathLimitations);
}

void KnightMovesImplementation::InitPointOfOrigin(KMBaseData UserInputData)
{
    m_PointOfOrigin.SetRow(UserInputData.m_StartRow);
    m_PointOfOrigin.SetColumn(UserInputData.m_StartColumn);
    m_PointOfOrigin.SetName(UserInputData.m_StartName);
    m_PointOfOrigin.SetBoardDimension(m_SingleSideBoardDimension);
}

void KnightMovesImplementation::InitDestination(KMBaseData UserInputData)
{
    m_Destination.SetRow(UserInputData.m_TargetRow);
    m_Destination.SetColumn(UserInputData.m_TargetColumn);
    m_Destination.SetName(UserInputData.m_TargetName);
    m_Destination.SetBoardDimension(m_SingleSideBoardDimension);
}

KMOutputData KnightMovesImplementation::CalculatePaths()
{
    KMRandomAccessMoveCollection PossibleFirstMoves = m_MoveFilters->GetPossibleMoves(m_PointOfOrigin);

    if (PossibleFirstMoves.empty())
    {
        std::cerr << "No Possible Moves in KnightMovesImplementation::CalculatePaths" << std::endl;
    }
    else
    {
        for (auto CurrentMoveIter : PossibleFirstMoves)
        {
            KMMove CurrentMove = CurrentMoveIter;
            CurrentMove.SetOriginCalculateDestination(m_PointOfOrigin);
            if (CurrentMove.IsValid()) {
                CalculatePath(CurrentMove);
            }
        }
    }
    return *m_Results;
}

bool KnightMovesImplementation::CalculatePath(KMMove CurrentMove)
    {
    bool CompletedSearch = false;
    KMBoardLocation CurrentLocation = CurrentMove.GetNextLocation();
m_Path->AddMoveToPath(CurrentMove);
    m_MoveFilters->PushVisited(CurrentLocation);

    if (CurrentLocation == m_Destination)
    {
        m_Results->AddPath(*m_Path);
        CompletedSearch =  true;
        m_Results->IncrementAttemptedPaths();
    }
    else
    {
        if (CurrentMove.IsValid())
        {
            KMRandomAccessMoveCollection PossibleMoves = m_MoveFilters->GetPossibleMoves(CurrentLocation);
            if (!PossibleMoves.empty())
            {
                for (auto NextMove : PossibleMoves)
                {
                    CalculatePath(NextMove);
                }
            }
            else
            {
                // No more moves to test, record the attempted path
                m_Results->IncrementAttemptedPaths();
            }
        }
        else
        {
            // There is a logic error if we get here.
            std::cerr << "In KnightMovesImplementation::CalculatePath CurrentMove Not Valid" << std::endl;
        }
    }

    m_Path->RemoveLastMove();
    m_MoveFilters->PopVisited();
    return CompletedSearch;
}

KMMoveFilters.h

#pragma once
/*
 * KMMoveFilters.h
 *
 *  Created on: Jun 20, 2016
 *      Author: pacmaninbw
 *
 *      This class provides all the possible Knight moves for a specified location
 *      on the chess board. In the center of the chess board there are 8 possible
 *      moves. In the middle of the edge on the chess board there are 4 possible
 *      moves. In a corner of the chess board there are 2 possible moves. The
 *      location on the board provides the first filter.
 *      Slicing is used to allow the program to complete in a reasonable finite
 *      amount of time. The slicing method can be varied, the default slicing
 *      method is the knight can't return to any row or column it has previously
 *      visited. The slicing is the second filter.
 */

#ifndef KMMOVEFILTERS_H_
#define KMMOVEFILTERS_H_

#include <vector>
#include "KnightMoves.h"
#include "KMMove.h"

class KMMoveFilters {
private:
    std::vector<KMBoardLocation> m_VisitedLocations;
    std::vector<unsigned int> m_VisitedRows;
    std::vector<unsigned int> m_VisitedColumns;
    unsigned int m_SingleSideBoardDimension;
    KnightMovesMethodLimitations m_PathLimitations;
    static KMRandomAccessMoveCollection AllPossibleMoves;
    // The 8 possible moves the knight can make.
    static KMMove Left1Up2;
    static KMMove Left2Up1;
    static KMMove Left2Down1;
    static KMMove Left1Down2;
    static KMMove Right1Up2;
    static KMMove Right2Up1;
    static KMMove Right2Down1;
    static KMMove Right1Down2;

protected:
    bool IsNotPreviouslyVisited(KMMove Move) const { return IsNotPreviouslyVisited(Move.GetNextLocation()); };
    bool IsNotPreviouslyVisited(KMBoardLocation Destination) const;

public:
    KMMoveFilters(void);
    KMMoveFilters(unsigned int BoardDimension, KnightMovesMethodLimitations SlicingMethod);
    void ResetFilters(unsigned int BoardDimension, KnightMovesMethodLimitations SlicingMethod) {m_SingleSideBoardDimension = BoardDimension; m_PathLimitations = SlicingMethod; }
    virtual ~KMMoveFilters(void);
    void PushVisited(KMBoardLocation Location);
    void PopVisited();
    KMRandomAccessMoveCollection GetPossibleMoves(const KMBoardLocation CurrentLocation) const;
};

KMMoveFilters.cpp

/*
 * KMMoveFilters.cpp
 *
 *  Created on: Jun 20, 2016
 *      Author: pacmaninbw
 */

#include "stdafx.h"
#include <stdexcept>
#include <algorithm>
#include "KMBoardDimensionConstants.h"
#include "KMMoveFilters.h"

KMMoveFilters::~KMMoveFilters(void)
{
}

KMMoveFilters::KMMoveFilters(void)
 : m_SingleSideBoardDimension(DefaultBoardDimensionOnOneSide),
   m_PathLimitations(DenyByPreviousRowOrColumn)
{
    AllPossibleMoves.push_back(Left1Up2);
    AllPossibleMoves.push_back(Left2Up1);
    AllPossibleMoves.push_back(Left2Down1);
    AllPossibleMoves.push_back(Left1Down2);
    AllPossibleMoves.push_back(Right1Up2);
    AllPossibleMoves.push_back(Right2Up1);
    AllPossibleMoves.push_back(Right2Down1);
    AllPossibleMoves.push_back(Right1Down2);
}

KMMoveFilters::KMMoveFilters(unsigned int BoardDimension, KnightMovesMethodLimitations SlicingMethod)
: m_SingleSideBoardDimension(BoardDimension), m_PathLimitations(SlicingMethod)
{
    AllPossibleMoves.push_back(Left1Up2);
    AllPossibleMoves.push_back(Left2Up1);
    AllPossibleMoves.push_back(Left2Down1);
    AllPossibleMoves.push_back(Left1Down2);
    AllPossibleMoves.push_back(Right1Up2);
    AllPossibleMoves.push_back(Right2Up1);
    AllPossibleMoves.push_back(Right2Down1);
    AllPossibleMoves.push_back(Right1Down2);
}

const int Left1 = -1;
const int Left2 = -2;
const int Down1 = -1;
const int Down2 = -2;
const int Right1 = 1;
const int Right2 = 2;
const int Up1 = 1;
const int Up2 = 2;

KMMove KMMoveFilters::Left1Up2(Left1, Up2, DefaultBoardDimensionOnOneSide);
KMMove KMMoveFilters::Left2Up1(Left2, Up1, DefaultBoardDimensionOnOneSide);
KMMove KMMoveFilters::Left2Down1(Left2, Down1, DefaultBoardDimensionOnOneSide);
KMMove KMMoveFilters::Left1Down2(Left1, Down2, DefaultBoardDimensionOnOneSide);
KMMove KMMoveFilters::Right1Up2(Right1, Up2, DefaultBoardDimensionOnOneSide);
KMMove KMMoveFilters::Right2Up1(Right2, Up1, DefaultBoardDimensionOnOneSide);
KMMove KMMoveFilters::Right2Down1(Right2, Down1, DefaultBoardDimensionOnOneSide);
KMMove KMMoveFilters::Right1Down2(Right1, Down2, DefaultBoardDimensionOnOneSide);

KMRandomAccessMoveCollection KMMoveFilters::AllPossibleMoves;

KMRandomAccessMoveCollection KMMoveFilters::GetPossibleMoves(const KMBoardLocation CurrentLocation) const
{
    KMRandomAccessMoveCollection PossibleMoves;
    for (auto PossibeMove : AllPossibleMoves) {
        KMMove *TempMove = new KMMove(PossibeMove);
        TempMove->SetBoardDimension(m_SingleSideBoardDimension);
        TempMove->SetOriginCalculateDestination(CurrentLocation);
        if ((TempMove->IsValid()) && (IsNotPreviouslyVisited(*TempMove))) {
            PossibleMoves.push_back(*TempMove);
        }
    }
    return PossibleMoves;
}

bool KMMoveFilters::IsNotPreviouslyVisited(KMBoardLocation PossibleDestination) const
{
    bool NotPrevioslyVisited = true;

    if (!m_VisitedLocations.empty()) {    // This is always a test, we can't move backwards
        if (std::find(m_VisitedLocations.begin(), m_VisitedLocations.end(), PossibleDestination)
            != m_VisitedLocations.end()) {
            NotPrevioslyVisited = false;
        }
    }

    switch (m_PathLimitations) {
    default :
        throw std::runtime_error("KMPath::CheckMoveAgainstPreviousLocations : Unknown type of Path Limitation.");
    case DenyByPreviousLocation :
        // Always tested above.
        break;
    case DenyByPreviousRowOrColumn:
        if ((!m_VisitedRows.empty()) && (!m_VisitedColumns.empty())) {
            unsigned int PossibleRow = PossibleDestination.GetRow();
            if (std::find(m_VisitedRows.begin(), m_VisitedRows.end(), PossibleRow) != m_VisitedRows.end()) {
                NotPrevioslyVisited = false;
                break;
            }
            unsigned int PossibleColum = PossibleDestination.GetColumn();
            if (std::find(m_VisitedColumns.begin(), m_VisitedColumns.end(), PossibleColum) != m_VisitedColumns.end()) {
                NotPrevioslyVisited = false;
            }
        }
        break;
    }

    return NotPrevioslyVisited;
}

void KMMoveFilters::PushVisited(KMBoardLocation Location)
{
    m_VisitedRows.push_back(Location.GetRow());
    m_VisitedColumns.push_back(Location.GetColumn());
    m_VisitedLocations.push_back(Location);
}

void KMMoveFilters::PopVisited()
{
    m_VisitedRows.pop_back();
    m_VisitedColumns.pop_back();
    m_VisitedLocations.pop_back();
}
false
  • 10,264
  • 13
  • 101
  • 209
pacmaninbw
  • 439
  • 1
  • 10
  • 22
  • 1
    You have a version that works on one platform, and a version that fails on another platform, and you can't find where they diverge? – Beta Jun 26 '16 at 01:34
  • @Beta More or less. The version that fails actually passes on the first run through the loop. It's just when I use multiple test cases in the loop that it fails. It's possible that the static vector in MoveFilters.cpp is being stepped on at some point. It definitely runs into memory problems on the 4th iteration of the test case loop. – pacmaninbw Jun 26 '16 at 01:45
  • It fails on the 4th iteration, does that mean it pass 1-3? What if you swap the 3rd and 4th test cases in the queue? Should that static vector be reset between test cases? With a little detective work you can narrow this problem down quite a lot. – Beta Jun 26 '16 at 01:50
  • @Beta It doesn't matter what order the test cases are in, I've tried swapping them. I've commented out the timing code in the function that presents the results to see if that was the issue. I changed the implementation so that the classes are allocated every time the loop runs to see if that was the issue. I've stepped through the recursive algorithm, but probably too early to find the problem. – pacmaninbw Jun 26 '16 at 01:55
  • @Beta Only the first time through the loop presents the expected results. FYI, this is 2 orders of magnitude slower than what I was seeing on the CentOS platform. – pacmaninbw Jun 26 '16 at 01:57
  • So only two test cases are needed to reproduce the error-- or maybe one test case run twice. What happens when you run the new version of the code on CentOS? And at what point in the execution of the second test case do you detect the divergence? – Beta Jun 26 '16 at 02:08
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/115613/discussion-between-pacmaninbw-and-beta). – pacmaninbw Jun 26 '16 at 02:09
  • 1
    Why is `AllPossibleMoves` static? - it seems like you will end up with more and more possible moves each time you create a `KMMoveFilters`. You don't access it statically, so there isn't a need for it to be static. – The Dark Jun 26 '16 at 02:42
  • 1
    @pacmaninbw In `KMMoveFilters::GetPossibleMoves` you have a memory leak. You're allocating memory for `TempMove`, and you lose the pointer. There is no need to allocate dynamically -- just create a `TempMove` instance locally in that loop – PaulMcKenzie Jun 26 '16 at 03:42
  • @TheDark your comment was helpful, in the C++11 version it was static const and worked fine, but then it wasn't initialized in the constructor. – pacmaninbw Jun 26 '16 at 16:14
  • @PaulMcKenzie I was creating copies so that I wasn't modifying the original KMMoves, those need to remain unchanged. – pacmaninbw Jun 26 '16 at 16:16
  • Files updated with correction on github. – pacmaninbw Jun 26 '16 at 16:22
  • @pacmaninbw -- Are you explaining why you needlessly used `new` there? You're dereferencing the dynamically allocated object -- you are doing actually nothing with the pointer you allocated except dereference it and then leak it. Creating an object locally serves exactly the same purpose, without the memory leak, and in all likelihood would make that loop run faster. – PaulMcKenzie Jun 26 '16 at 19:30
  • @PaulMcKenzie In the answer presented below is there still a memory leak? There wouldn't be in C++11 on CentOS and the results are faster without the new. – pacmaninbw Jun 26 '16 at 20:30

1 Answers1

0

The problem was the static declaration of AllPossibleMoves, the memory leak in GetPossibleMoves may have been an additional contributor to the problem. In the CentOS C++11 version AllPossibleMoves was declared as static const, and was not initialized in the constructor, it was initialized outside as each of it's member moves are. This did not compile in Visual Studio 2012 C++. AllPossibleMoves was declared as static const for execution time reasons in the original version.

I am disappointed in the results since this is much slower than the CentOS version using C++11 compiled with g++. The computer I'm running this on is 2 years new than the CentOS computer and has 8GB of memory with an i7 processor.

First I present the working code, then I present the output of the program.

The final code that solves the problem is:

KMMoveFilters.h

#pragma once
/*
 * KMMoveFilters.h
 *
 *  Created on: Jun 20, 2016
 *      Author: pacmaninbw
 *
 *      This class provides all the possible Knight moves for a specified location
 *      on the chess board. In the center of the chess board there are 8 possible
 *      moves. In the middle of the edge on the chess board there are 4 possible
 *      moves. In a corner of the chess board there are 2 possible moves. The
 *      location on the board provides the first filter.
 *      Slicing is used to allow the program to complete in a reasonable finite
 *      amount of time. The slicing method can be varied, the default slicing
 *      method is the knight can't return to any row or column it has previously
 *      visited. The slicing is the second filter.
 */

#ifndef KMMOVEFILTERS_H_
#define KMMOVEFILTERS_H_

#include <vector>
#include "KnightMoves.h"
#include "KMMove.h"

class KMMoveFilters {
private:
    std::vector<KMBoardLocation> m_VisitedLocations;
    std::vector<unsigned int> m_VisitedRows;
    std::vector<unsigned int> m_VisitedColumns;
    unsigned int m_SingleSideBoardDimension;
    KnightMovesMethodLimitations m_PathLimitations;
    KMRandomAccessMoveCollection AllPossibleMoves;
    // The 8 possible moves the knight can make.
    static KMMove Left1Up2;
    static KMMove Left2Up1;
    static KMMove Left2Down1;
    static KMMove Left1Down2;
    static KMMove Right1Up2;
    static KMMove Right2Up1;
    static KMMove Right2Down1;
    static KMMove Right1Down2;

protected:
    bool IsNotPreviouslyVisited(KMMove Move) const { return IsNotPreviouslyVisited(Move.GetNextLocation()); }
    bool IsNotPreviouslyVisited(KMBoardLocation Destination) const;

public:
    KMMoveFilters(void);
    KMMoveFilters(unsigned int BoardDimension, KnightMovesMethodLimitations SlicingMethod);
    void ResetFilters(unsigned int BoardDimension, KnightMovesMethodLimitations SlicingMethod) {m_SingleSideBoardDimension = BoardDimension; m_PathLimitations = SlicingMethod; }
    virtual ~KMMoveFilters(void);
    void PushVisited(KMBoardLocation Location);
    void PopVisited();
    KMRandomAccessMoveCollection GetPossibleMoves(const KMBoardLocation CurrentLocation) const;
};

#endif /* KMMOVEFILTERS_H_ */

Only the changes in KMMoveFilters.cpp

KMRandomAccessMoveCollection KMMoveFilters::GetPossibleMoves(const KMBoardLocation CurrentLocation) const
{
    KMRandomAccessMoveCollection SafeAllPossibleMoves = AllPossibleMoves;
    KMRandomAccessMoveCollection PossibleMoves;
    for (auto PossibleMove : SafeAllPossibleMoves) {
        PossibleMove.SetBoardDimension(m_SingleSideBoardDimension);
        PossibleMove.SetOriginCalculateDestination(CurrentLocation);
        if ((PossibleMove.IsValid()) && (IsNotPreviouslyVisited(PossibleMove))) {
            PossibleMoves.push_back(PossibleMove);
        }
    }
    return PossibleMoves;
}

The resulting output

Select the number of the test case you want to run.
Test Case # Start Name Target Name Board Size Slicing Method
1 A3 H4 8 Can't return to previous row or column
2 A1 H8 8 Can't return to previous row or column
3 A8 H1 8 Can't return to previous row or column
4 B3 H4 8 Can't return to previous row or column
5 B3 H8 8 Can't return to previous row or column
6 C1 H4 8 Can't return to previous row or column
7 A3 H8 8 Can't return to previous row or column
8 A3 B5 8 Can't return to previous row or column
9 H4 A3 8 Can't return to previous row or column
10 D4 A8 8 Can't return to previous row or column
11 D4 E6 8 Can't return to previous row or column
12 A3 B5 12 Can't return to previous row or column
13 A3 B5 8 Can't return to previous location
14 A3 B5 26 Can't return to previous row or column
15 All of the above except for 13 and 14
16 All of the above (Go get lunch)

finished computation at 0
elapsed time: 0.209012 Seconds

The point of origin for all path searches was A3
The destination point for all path searches was H4
The number of squares on each edge of the board is 8
The slicing methodology used to further limit searches was no repeat visits to any rows or columns.
There are 5 Resulting Paths
There were 323 attempted paths
The average path length is 4.8
The median path length is 4
The longest path is 6 moves
The shortest path is 4 moves

finished computation at 0
elapsed time: 0.0930054 Seconds

The point of origin for all path searches was A1
The destination point for all path searches was H8
The number of squares on each edge of the board is 8
The slicing methodology used to further limit searches was no repeat visits to any rows or columns.
There are 22 Resulting Paths
There were 160 attempted paths
The average path length is 6.36364
The median path length is 6
The longest path is 8 moves
The shortest path is 6 moves

finished computation at 0
elapsed time: 0.0950054 Seconds

The point of origin for all path searches was A8
The destination point for all path searches was H1
The number of squares on each edge of the board is 8
The slicing methodology used to further limit searches was no repeat visits to any rows or columns.
There are 22 Resulting Paths
There were 160 attempted paths
The average path length is 6.36364
The median path length is 6
The longest path is 8 moves
The shortest path is 6 moves

finished computation at 0
elapsed time: 0.248014 Seconds

The point of origin for all path searches was B3
The destination point for all path searches was H4
The number of squares on each edge of the board is 8
The slicing methodology used to further limit searches was no repeat visits to any rows or columns.
There are 8 Resulting Paths
There were 446 attempted paths
The average path length is 5
The median path length is 5
The longest path is 7 moves
The shortest path is 3 moves

finished computation at 0
elapsed time: 0.251014 Seconds

The point of origin for all path searches was B3
The destination point for all path searches was H8
The number of squares on each edge of the board is 8
The slicing methodology used to further limit searches was no repeat visits to any rows or columns.
There are 39 Resulting Paths
There were 447 attempted paths
The average path length is 6.23077
The median path length is 7
The longest path is 7 moves
The shortest path is 5 moves

finished computation at 0
elapsed time: 0.17801 Seconds

The point of origin for all path searches was C1
The destination point for all path searches was H4
The number of squares on each edge of the board is 8
The slicing methodology used to further limit searches was no repeat visits to any rows or columns.
There are 7 Resulting Paths
There were 324 attempted paths
The average path length is 4.85714
The median path length is 4
The longest path is 6 moves
The shortest path is 4 moves

finished computation at 0
elapsed time: 0.18201 Seconds

The point of origin for all path searches was A3
The destination point for all path searches was H8
The number of squares on each edge of the board is 8
The slicing methodology used to further limit searches was no repeat visits to any rows or columns.
There are 36 Resulting Paths
There were 324 attempted paths
The average path length is 6
The median path length is 6
The longest path is 8 moves
The shortest path is 4 moves

finished computation at 0
elapsed time: 0.131008 Seconds

The point of origin for all path searches was A3
The destination point for all path searches was B5
The number of squares on each edge of the board is 8
The slicing methodology used to further limit searches was no repeat visits to any rows or columns.
There are 6 Resulting Paths
There were 243 attempted paths
The average path length is 3
The median path length is 3
The longest path is 5 moves
The shortest path is 1 moves

finished computation at 0
elapsed time: 0.17301 Seconds

The point of origin for all path searches was H4
The destination point for all path searches was A3
The number of squares on each edge of the board is 8
The slicing methodology used to further limit searches was no repeat visits to any rows or columns.
There are 12 Resulting Paths
There were 318 attempted paths
The average path length is 5.66667
The median path length is 6
The longest path is 8 moves
The shortest path is 4 moves

finished computation at 0
elapsed time: 0.332019 Seconds

The point of origin for all path searches was D4
The destination point for all path searches was A8
The number of squares on each edge of the board is 8
The slicing methodology used to further limit searches was no repeat visits to any rows or columns.
There are 24 Resulting Paths
There were 602 attempted paths
The average path length is 5.25
The median path length is 5
The longest path is 7 moves
The shortest path is 3 moves

finished computation at 0
elapsed time: 0.266015 Seconds

The point of origin for all path searches was D4
The destination point for all path searches was E6
The number of squares on each edge of the board is 8
The slicing methodology used to further limit searches was no repeat visits to any rows or columns.
There are 21 Resulting Paths
There were 487 attempted paths
The average path length is 4.14286
The median path length is 5
The longest path is 7 moves
The shortest path is 1 moves

finished computation at 0
elapsed time: 1.86411 Seconds

The point of origin for all path searches was A3
The destination point for all path searches was B5
The number of squares on each edge of the board is 12
The slicing methodology used to further limit searches was no repeat visits to any rows or columns.
There are 6 Resulting Paths
There were 3440 attempted paths
The average path length is 3
The median path length is 3
The longest path is 5 moves
The shortest path is 1 moves

Overall Results
The average execution time is 0.335186 seconds
The median execution time is 0.209012 seconds
The longest execution time is 1.86411 seconds
The shortest execution time is 0.0930054 seconds

Overall Results with optimized version.

Overall Results
The average execution time is 0.00266682 seconds
The median execution time is 0.0020001 seconds
The longest execution time is 0.0140008 seconds
The shortest execution time is 0.001 seconds

CentOS version Overall Results
The average execution time is 0.00195405 seconds
The median execution time is 0.00103346 seconds
The longest execution time is 0.00130368 seconds
The shortest execution time is 0.000716237 seconds

pacmaninbw
  • 439
  • 1
  • 10
  • 22
  • Are you running a "debug" or unoptimized version of your program? If so, then the results are meaningless. Only optimized / release versions should be timed. Please post the compile options you're using. – PaulMcKenzie Jun 26 '16 at 19:33
  • @PaulMcKenzie The results presented here are debug, I have since compiled the release version. Results are about 4 times slower than the CentOS version, however, they are an order of magnitude faster than what is shown here. – pacmaninbw Jun 26 '16 at 20:27