Aan de slag met F# (F-Sharp)

Ingediend door Dirk Hornstra op 03-feb-2020 23:24

Een aantal jaren geleden heb ik ergens voorbij zien komen dat er ook F# was, maar toen heb ik er niets mee gedaan. Ik luister oude podcasts van Scott Hanselman en kwam daarbij aflevering 76 tegen. Scott zat in een boek van F# (Foundations of F#) en sprak met de schrijver van het boek, Robert Pickering. Ik hoop dat het boek beter geschreven is dan dat hij spreekt, want "uhm.." en traag praten, de aflevering was niet erg boeiend. Maar wel kwam voorbij dat F# erg goed is bij een programma waarbij je met allemaal command-line parameters werkt, beter dan C#.

Toen werd ik getriggerd, want we zijn met eigen tools bezig, één daarvan is voor het opzetten van websites, klonen en nog vele andere zaken en de configuratie/parameters gaan via de command-prompt. En ook had ik in mijn mailbox een mail van Microsoft waarin F# benoemd werd. Dit kan geen toeval meer zijn, dit is een mooie concrete case om mijn eerste stappen te maken.

Functioneel programmeren is mij niet geheel onbekend, want bij de opleiding Hogere Informatica hebben we dit ook wel gehad. Maple voldoet hier volgens mij aan en het programma Amanda wat door onze leraar Dick Bruin geschreven/gemaakt is. Nu ik erop Google zie ik dat deze ook op Github beschikbaar is: link.

In Visual Studio 2017 (Community Edition die ik voor eigen gebruik op mijn computer heb staan) kan ik een nieuw F# project maken. In dat project staat dat er meer informatie op de website te vinden is, daarvoor kun je naar deze site gaan: https://fsharp.org/learn.html

Bij het terugzoeken van de podcast kwam ik ook nog nummer 96 tegen, Dustin Campbell, een ontwikkelaar van Microsoft. Bij het zoeken op zijn naam kom ik een boek tegen waarvan hij het voorwoord geschreven heeft, dat recenter is dan het boek van Robert en wat via manning.com te koop is (Get Programming with F# door Isaac Abraham). Het mooie is dat je daar een deel van het boek alvast kun inkijken: link (is het wat voor mij?) en je daar dus ook direct een link naar de Github-locatie krijgt voor de source-code: https://github.com/isaacabraham/get-programming-fsharp

De bijlage bij dit artikel is van een puzzel die in de Telegraaf staat, "Tentje-Boompje". Boven, naast of onder elke boom staat een tentje. De tentjes raken elkaar niet, ook niet diagonaal.
De cijfers geven aan hoeveel tenten er in een rij of kolom staan. Voor zo'n probleem is een functionele programmeertaal bedoeld. Hierbij mijn eerste opzet zoals ik die in C# uitgewerkt heb (nog niet functioneel dus). Je ziet dat ik daarbij alleen nog maar wat mogelijkheden uitsluit en daardoor alleen de bovenste rij de tenten te plek kan zetten. Maar de regel "de tenten raken elkaar niet" zit er nog niet in. En daardoor kijk je als mens over grenzen heen die je in code moet "programmeren". Als je een tentje plaatst onder de 2e boom van boven in de eerste kolom, dan weet je dat er geen boven de 3e boom van boven in de eerste kolom komt. En ook niet boven de 2e boom van boven in de tweede kolom. En ook kwam ik erachter dat ik mijn variabele wel "_Matrix" genoemd had, maar het eigenlijk een verzameling "rijen met kolommen" is.


using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;

namespace TreeWithTent
{
    class Program
    {
        private const int _ListBounds = 11;
        private const char _Tent = 'T';
        private const char _Tree = '1';
        private const char _PossibleTent = '0';
        private const char _VoidSpace = 'x';

        private static char[][] _Matrix = new char[_ListBounds][];
        private static int[] _RowAmounts = new int[_ListBounds];
        private static int[] _ColumnAmounts = new int[_ListBounds];
        private static List<Point> _Trees = new List<Point>();

        static void Main(string[] args)
        {
            FillTrees();
            FillMatrix();
            FillTotalPerRowAndColumn();
            PrintMatrix();
            CalculateTents();
            PrintMatrix();
            Console.ReadKey();
        }

        /// <summary>
        /// while allmost everything starts at zero I start at 1 because of readability
        /// </summary>
        private static void FillTrees()
        {
            // row 1
            _Trees.Add(new Point(1, 2));
            _Trees.Add(new Point(1, 9));
            // row 2
            _Trees.Add(new Point(2, 1));
            _Trees.Add(new Point(2, 6));
            _Trees.Add(new Point(2, 10));
            // row 3
            _Trees.Add(new Point(3, 4));
            _Trees.Add(new Point(3, 6));
            _Trees.Add(new Point(3, 9));
            // row 4
            _Trees.Add(new Point(4, 1));
            _Trees.Add(new Point(4, 5));
            _Trees.Add(new Point(4, 7));
            // row 5
            _Trees.Add(new Point(5, 2));
            // row 6
            _Trees.Add(new Point(6, 8));
            _Trees.Add(new Point(6, 9));
            _Trees.Add(new Point(6, 10));
            // row 7
            _Trees.Add(new Point(7, 1));
            // row 8
            _Trees.Add(new Point(8, 4));
            _Trees.Add(new Point(8, 6));
            // row 9
            _Trees.Add(new Point(9, 2));
            _Trees.Add(new Point(9, 3));
            _Trees.Add(new Point(9, 4));
            _Trees.Add(new Point(9, 6));
            _Trees.Add(new Point(9, 10));
            // row 10
            _Trees.Add(new Point(10, 7));
        }

        private static void FillMatrix()
        {
            for (var k=1; k < _ListBounds; k++)
            {
                _Matrix[k] = new char[_ListBounds];
                for (var m=1; m < _ListBounds; m++)
                {
                    _Matrix[k][m] = _PossibleTent;
                }
            }
            foreach (var point in _Trees)
            {
                _Matrix[point.X][point.Y] = _Tree;
            }
        }

        private static void FillTotalPerRowAndColumn()
        {
            _RowAmounts[1] = 5;
            _RowAmounts[2] = 0;
            _RowAmounts[3] = 5;
            _RowAmounts[4] = 0;
            _RowAmounts[5] = 3;
            _RowAmounts[6] = 1;
            _RowAmounts[7] = 1;
            _RowAmounts[8] = 4;
            _RowAmounts[9] = 0;
            _RowAmounts[10] = 5;

            _ColumnAmounts[1] = 3;
            _ColumnAmounts[2] = 2;
            _ColumnAmounts[3] = 3;
            _ColumnAmounts[4] = 1;
            _ColumnAmounts[5] = 3;
            _ColumnAmounts[6] = 2;
            _ColumnAmounts[7] = 2;
            _ColumnAmounts[8] = 3;
            _ColumnAmounts[9] = 1;
            _ColumnAmounts[10] = 4;
        }

        private static void PrintMatrix()
        {
            for (var k = 1; k < _ListBounds; k++)
            {
                for (var m = 1; m < _ListBounds; m++)
                {
                    Console.Write(_Matrix[k][m]+" ");
                }
                Console.WriteLine();
            }
            Console.WriteLine();
            Console.WriteLine();
        }

        private static void CalculateTents()
        {
            // if a row has a total of zero, all cells stay empty
            for (var k=1; k < _ListBounds; k++)
            {
                if (_RowAmounts[k] == 0)
                {
                    for (var m=1; m < _ListBounds; m++)
                    {
                        if (_Matrix[k][m] != _Tree)
                        {
                            _Matrix[k][m] = _VoidSpace;
                        }
                    }
                }
                if (_ColumnAmounts[k] == 0)
                {
                    for (var m = 1; m < _ListBounds; m++)
                    {
                        if (_Matrix[m][k] != _Tree)
                        {
                            _Matrix[m][k] = _VoidSpace;
                        }
                    }
                }
            }
            // all tents are below a tree. so no tree: no tent!
            for (var k = 1; k < _ListBounds; k++)
            {
                for (var m = 1; m < _ListBounds; m++)
                {
                    if ((_Matrix[k][m] != _Tree) &&(_Matrix[k][m] != _Tent))
                    {
                        if (NeightbourIsTree(k, m) == false)
                        {
                            _Matrix[k][m] = _VoidSpace;
                        }
                    }
                }
            }
            // we removed invalid places. check if we have match(es)
            for (var k=1; k < _ListBounds; k++)
            {
                if (_Matrix[k].Where(rec => rec == _PossibleTent).Count() == _RowAmounts[k])
                {
                    // row found with all places to fill!
                    for (var m = 1; m < _ListBounds; m++)
                    {
                        if (_Matrix[k][m] == _PossibleTent)
                        {
                            _Matrix[k][m] = _Tent;
                        }
                    }
                }

                // columns are more difficult because we started from "rows"
                int possibilities = 0;
                for (var z=1; z < _ListBounds; z++)
                {
                    possibilities += (_Matrix[z][k] == _PossibleTent ? 1 : 0);
                }
                if (possibilities == _ColumnAmounts[k])
                {
                    // colum found with all places to fill!
                    for (var m = 1; k < _ListBounds; k++)
                    {
                        if (_Matrix[m][k] == _PossibleTent)
                        {
                            _Matrix[m][k] = _Tent;
                        }
                    }
                }
            }
        }

        private static bool NeightbourIsTree(int row, int column)
        {
            bool result = false;
            if (row > 1)
            {
                //above
                result = result || (_Matrix[row - 1][column] == _Tree);
            }
            if (column > 1)
            {
                // before
                result = result || (_Matrix[row][column-1] == _Tree);
            }
            if (column < (_ListBounds-1))
            {
                // after
                result = result || (_Matrix[row][column + 1] == _Tree);
            }
            if (row < (_ListBounds-1))
            {
                //below
                result = result || (_Matrix[row + 1][column] == _Tree);
            }
            return result;
        }

    }
}

 

Screenshot
Puzzel Tentje-Boompje