Brewsterware

September 30, 2020

Sudoku cracker for Dynamics 365 Finance and Operations

Filed under: 365 for Finance and Operations — Joe Brewer @ 12:44 pm

It’s been a long time since I have done any coding just for fun, and I rarely do it with X++, but a sudoku cracker for Finance and Operations seemed like a good challenge.

This cracker will crack even the most toughest of puzzles by using recursion and backtracking for when there are multiple possibilities for values in a cell.

/// <summary>
/// Sudoku puzzle cracker
/// </summary>
class SudokuCracker
{
    // this macro allows us to specify the line and column of the grid array
    #localMacro.GridIndex
        (%1 - 1) * 9 + %2
    #endMacro

    private const int TotalRows = 9;
    private const int TotalColumns = 9;

    private int grid[TotalRows, TotalColumns];
    private int cellsFilled;

    /// <summary>
    /// Runs the class with the specified arguments.
    /// </summary>
    /// <param name = "_args">The specified arguments.</param>
    public static void main(Args _args)
    {
        SudokuCracker cracker = new SudokuCracker();

        cracker.loadGrid();
        cracker.solve();
        cracker.saveGrid();
    }

    /// <summary>
    /// import a file with the known numbers and populate a 2 dimensional array with them
    /// </summary>
    public void loadGrid()
    {
        System.String stringLine;
        str line;
        int gridLine = 0;
        var fileUpload = File::GetFileFromUser() as FileUploadTemporaryStorageResult;

        using (var reader = new System.IO.StreamReader(fileUpload.openResult()))
        {
            stringLine = reader.ReadLine();

            while (!System.String::IsNullOrEmpty(stringLine))
            {
                line = strKeep(stringLine, '123456789 ');

                if (line)
                {
                    gridLine++;

                    for (int i = 1; i <= TotalRows; i++)
                    {
                        int gridIndex = #GridIndex(gridLine, i);
                        int value = str2Int(subStr(line, i, 1));

                        grid[gridIndex] = value;

                        if (value)
                        {
                            cellsFilled++;
                        }
                    }
                }

                stringLine = reader.ReadLine();
            }
        }
    }

    /// <summary>
    /// create a pretty grid with the numbers filled in, and send it back to the user
    /// </summary>
    public void saveGrid()
    {
        TextBuffer output;

        output = new TextBuffer();

        for (int line = 1; line <= TotalRows; line++)
        {
            output.appendText(strFmt('%1%2%3|%4%5%6|%7%8%9\r\n', 
                grid[#GridIndex(line, 1)], 
                grid[#GridIndex(line, 2)], 
                grid[#GridIndex(line, 3)],
                grid[#GridIndex(line, 4)],
                grid[#GridIndex(line, 5)],
                grid[#GridIndex(line, 6)],
                grid[#GridIndex(line, 7)],
                grid[#GridIndex(line, 8)],
                grid[#GridIndex(line, 9)]));

            if (line mod 3 == 0 &amp;&amp;
                line != TotalRows)
            {
                output.appendText('---+---+---\r\n');
            }
        }

        File::SendStringAsFileToUser(output.getText(), 'solved.txt');
    }

    /// <summary>
    /// method to determine whether a value is valid at a specific line and column
    /// </summary>
    /// <param name="_line">The line number of the grid</param>
    /// <param name="_column">The column number of the grid</param>
    /// <param name="_value">An integer value to be tested</param>
    /// <returns>true if the value is valid, false if not</returns>
    private boolean isValuePossible(int _line, int _column, int _value)
    {
        // check to see whether the value is possible in the line
        for (int i = 1; i <= TotalRows; i++)
        {
            if (grid[#GridIndex(_line, i)] == _value)
            {
                return false;
            }
        }

        // check to see whether the value is possible in the column
        for (int i = 1; i <= TotalColumns; i++)
        {
            if (grid[#GridIndex(i, _column)] == _value)
            {
                return false;
            }
        }

        // check to see whether the value is possible in the square

        // work out the starting point for the square
        int line = real2int(roundDownDec((_line - 1) / 3, 0)) * 3;
        int column = real2int(roundDownDec((_column - 1) / 3, 0)) * 3;

        for (int i = 1; i <= 3; i++)
        {
            for (int j = 1; j <= 3; j++)
            {
                int gridIndex = #GridIndex(line + i, column + j);

                if (grid[gridIndex] == _value)
                {
                    return false;
                }
            }
        }

        return true;
    }

    /// <summary>
    /// recursive backtracking method which fills in the missing numbers to solve the puzzle
    /// </summary>
    public void solve()
    {
        for (int line = 1; line <= TotalRows; line++)
        {
            for (int column = 1; column <= TotalColumns; column++)
            {
                if (grid[#GridIndex(line, column)] == 0)
                {
                    for (int value = 1; value <= 9; value++)
                    {
                        if (this.isValuePossible(line, column, value))
                        {
                            cellsFilled++;
                            grid[#GridIndex(line, column)] = value;

                            this.solve();

                            // have we finished?
                            if (cellsFilled == (TotalRows * TotalColumns))
                            {
                                return;
                            }

                            // start backtracking
                            cellsFilled--;
                            grid[#GridIndex(line, column)] = 0;
                        }
                    }

                    // nothing to see here, move along please.....
                    return;
                }
            }
        }
    }
}

The format of the file that this class uses is an ASCII based grid using pipes, hyphens and plus symbols to separate the nine squares of the sudoku grid. Below is an example that can be used to test the code – copy it into notepad and save it somewhere where the 365FO client can access it.

  9| 85|   
 6 |   |  9
 78|   | 14
---+---+---
   |   |   
  5| 18|   
   |7  |482
---+---+---
   |  7| 4 
2  |6 9|   
 8 |   | 7 

Run the class and choose and upload the file that you created in the above step. After a few moments you will be prompted to download a file which should contain the solution.

Happy cracking!

November 12, 2019

Creating and testing a strongly typed Stack class in X++

Filed under: 365 for Finance and Operations — Joe Brewer @ 12:47 pm

For my latest project in X++ I needed to use a Stack. .NET does already provide a Stack class however MS recommends using the Generics version as it is faster because it is typed. See Non-generic collections shouldn’t be used on GitHub. I had initially tried creating a Stack class using a container, but it was painfully slow – it took several minutes to push and pop 100,000 elements onto and off of the stack.

Here is my implementation:

class Stack extends List
{
    private Types dataType;

    public void push(anytype _value)
    {
        // double check that we have received a variable of the correct type
        if (typeOf(_value) != dataType)
        {
            throw error("Incorrect data type");
        }

        this.addStart(_value);
    }

    public void new(Types _type)
    {
        dataType = _type;

        super(_type);
    }

    public anytype pop()
    {
        ListIterator iterator;
        anytype topValue;

        // double check that there is an element on the stack
        if (!this.elements())
        {
            return null;
        }

        iterator = new ListIterator(this);

        // position the iterator to the top
        iterator.more();

        // retreive the value
        topValue = iterator.value();

        // remove the element from the stack
        iterator.delete();

        // return the value
        return topValue;
    }
}

Here is a job to demonstrate how to use the class and to test the speed of pushing and popping 100,000 elements on to and off of the stack:

class StackTest
{        
    /// <summary>
    /// Runs the class with the specified arguments.
    /// </summary>
    /// <param name = "_args">The specified arguments.</param>
    public static void main(Args _args)
    {    
        System.Random random;
        System.Diagnostics.Stopwatch stopwatch;
        System.TimeSpan timeSpan;
        Stack numberStack;
        str elapsedTime;

        random = new System.Random();
        numberStack = new GWStack(Types::Integer);
        stopwatch = new System.Diagnostics.Stopwatch();

        stopwatch.Start();

        // push 100,000 random numbers onto the stack
        for (int i = 0 ; i <= 100000 ; i++)
        {
            numberStack.push(random.Next(1, 1000));
        }

        // pop everything off of the stack one by one
        for (int i = 0 ; i <= 100000 ; i++)
        {
            numberStack.pop();
        }

        stopwatch.Stop();
        timeSpan = stopwatch.Elapsed;

        elapsedTime = System.String::Format("{0:00}:{1:00}:{2:00}.{3:0000}",
            timeSpan.Hours, timeSpan.Minutes, timeSpan.Seconds,
            timeSpan.Milliseconds);

        info(strFmt("elapsed time for list based stack: %1", elapsedTime));
    }

}

The results show that pushing and popping 100,000 elements is well under 1 second.

Let me know if you use it or if you found this helpful.

October 4, 2019

Too many forms – Dynamics AX 2012 error

Filed under: Dynamics AX 2012 — Joe Brewer @ 4:33 pm

Recently I had an issue with a customer who is using Microsoft Dynamics AX 2012 where users were experiencing the dreaded “too many forms” error. If you have experienced this issue you will know that the only way to stop it is to restart the AX client. Restarting the client, however only gets rid of the error for a short period of time.

Searching the net for information doesn’t give much information on what can cause the error. One post from the awesome art of creation blog has some good information though. Especially for explaining how to monitor the user objects using windows task manager.

The “too many forms” error was unfortunately not caused by a missing super() call in a form, however the customer was happy to monitor the task manager whilst testing. I was pretty surprised at this as the people testing thought that this was incredibly tedious. However, the testers managed to narrow down where the problematic code was, and I was able to track down and sort the issue.

:)

The issue was with third party code, as you would probably expect

The problem was essentially that a function was written that returned an object, but was called with out assigning the value to a variable. The main method of the SalesFormLetter class had been overridden to return the local salesFormLetter variable. The users only had to use this class a handful of times before getting the error and having to restart the client.

If you are getting this error, I would recommend getting your testers to monitor the task manager whilst testing. It may annoy them as it significantly slows down their testing, but will make it easier to narrow down the code that is causing it. When you have the form, class or whatever seems to have the issue, check out any customisations in that area.

July 7, 2012

Updates for the wordpress affiliate link cloaker plugin

Filed under: PHP and MySQL,Wordpress — Joe Brewer @ 6:02 pm

It’s been nearly a year since my last post, and many months since the plugin last had any updates. This post outlines the new features in version 1.00.03:

Removing data from the redirect log

The way that the plugin works out how many redirects there are for each address is by logging each redirect and counting the redirects on the link and address pages. One of the things that I didnt account for in the previous version was how to delete or reset this data. Now, when you uncheck the option “count redirects” on the link page and save the options, the entries from the log are removed. Re enabling this option will effectively reset the counter.

Case sensitive keyword/phrase searching

This is a feature that had been requested a little while ago. It enables keyword/phrase searching to be set to case insensitive – this means that the keyword “television” will find and replace “Television” (and other combinations of upper/lower case) in blog posts.

Redirects graph

Under the “count redirects” option on the links page there is now an option to show a traffic graph. Once the check box has been checked and saved, the graph will show at the bottom of the page. This shows the redirects for the current month.

July 11, 2011

Affiliate link cloaker WordPress plugin

Filed under: Internet,Uncategorized,Wordpress — Joe Brewer @ 4:50 pm

Affiliate link cloaker is a free WordPress plugin that can geo target cloaked affiliate links. This plugin has the capability to generate a link like http://www.brewsterware.com/recommends/amazon and have visitors from the USA click on the link and get sent to www.amazon.com, visitors from the UK sent to www.amazon.co.uk and so on.

In addition to this, these cloaked links can be automatically inserted into content. Keywords or phrases can be set to be changed to nofollowed links that open in a new window so that visitors are not taken away from your website.
(more…)

Older Posts »

Powered by WordPress