ponedeljek, 4. oktober 2010

Iz C# v F# in nazaj

Po dolgem času sem spet zbral malo volje da nekaj spišem. Šlo bo za zadevo, ki sem jo sprogramiral že pred časom, da sem si malo ogledal C# 4.0. Nekaj F#-a in paralelizacije ter medsebojne uporabe knjižnic.

Kontekst
Računanje fakultete. Gre za matematično funkcijo, ki je zmnožek vseh manjših števil, vključno z n -
(5! = 1 * 2 * 3 * 4 * 5 = 120). Funkcije za računanje fakultet sem napisal na več načinov – za primerjavo.

F#
Gre za funkcijski programski jezik s podporo objektnemu programiranju, ki temelji na .NET platformi. Primeren je za kompleksne algoritme, simulacije, analize, matematične izračune, paralelizacijo, podatkovno rudarjenje, umetno inteligenco...

Implementacija


Rekurzivno funkcijo za izračun fakultete sem dal v posebno knjižnico, da ni bilo krožne reference. To funkcijo kličemo potem iz F#. Uporabiti je potrebno BigInteger saj rezultati kmalu postanejo ogromni.
using System.Numerics;

namespace FactorialLibrary
{
    public struct RecursiveFactorial
    {
        public static BigInteger FactorialRecursive(BigInteger number)
        {
            return number > 1 ? number * FactorialRecursive(number - 1) : 1;
        }
    }
}
F# modul z rekurzivno, iterativno in eksotično funkcijo za izračun fakultete. Spodnja funkcija pa je klic funkcije iz zgoraj omenjene C# knjižnice.
module FactorialFSharpLibrary
open FactorialLibrary

let factorialExotic(n) = Seq.fold ( * ) 1I [1I .. n];;

let rec factorialRecursive(n) =
    if n < 1I then 1I
    else n * factorialRecursive (n - 1I);;

let factorialIterative(n:bigint) = 
    let mutable result = 1I
    for i=1 to (int)n do
       result <- result * bigint(i)
    done
    result;;

let factorialRecursiveCSharp(n) =
    RecursiveFactorial.FactorialRecursive(n);;
V C# strukturi pa imamo iterativno in paralelno funkcijo za izračun fakultete. Prav tako pa kličemo tudi vse zgoraj napisane funkcije.
public struct Factorial
    {
        public static BigInteger Number { get; set; }

        public static BigInteger FactorialIterative()
        {
            var fact = new BigInteger(1);
            for (var i = 1; i <= Number; i++) { fact *= i; }
            return fact;
        }

        public static BigInteger FactorialIParallel()
        {
            var fact = new BigInteger(1);
            Parallel.For(1, (long)Number + 1, i => { fact *= i; });
            return fact;
        }

        public static BigInteger FactorialFSharpExotic()
        {
            return FactorialFSharpLibrary.factorialExotic(Number);
        }

        public static BigInteger FactorialFSharpRecursive()
        {
            return FactorialFSharpLibrary.factorialRecursive(Number);
        }

        public static BigInteger FactorialFSharpIterative()
        {
            return FactorialFSharpLibrary.factorialIterative(Number);
        }

        public static BigInteger FactorialRecursiveOverFSharp()
        {
            return FactorialFSharpLibrary.factorialRecursiveCSharp(Number);
        }
    }
V glavnem programu samo pokličemo vse metode in izpišemo rezultate. Čas izvajanja posamezne metode tudi izmerimo.
struct FactorialExamples
    {
        private const string Print = ":\n{0}";

        public delegate BigInteger ExecuteMethod();

        public static void MeasureMethod(string printLine, ExecuteMethod method)
        {
            var timer = Stopwatch.StartNew();
            var result = method();
            timer.Stop();
            Console.WriteLine(string.Format(printLine + Print, result) + "\ntime: " + timer.ElapsedMilliseconds + "ms");
        }

        public static void Main()
        {
            Factorial.Number = 100;
            Console.WriteLine("Factorial of {0}", Factorial.Number);
            MeasureMethod("recursive C# over F#", Factorial.FactorialRecursiveOverFSharp);
            MeasureMethod("iterative", Factorial.FactorialIterative);
            MeasureMethod("parallel", Factorial.FactorialIParallel);
            MeasureMethod("exotic F#", Factorial.FactorialFSharpExotic);
            MeasureMethod("recursive F#", Factorial.FactorialFSharpRecursive);
            MeasureMethod("iterative F#", Factorial.FactorialFSharpIterative);
            Console.ReadKey();
        }
    }
Rezultati fakultete števila 200 in čas izvajanja.

Testiranje
Vse funkcije tudi testiramo z Unit testi. Zgoraj so konstante za obvestila o napakah, spodaj pa klici vseh metod in primerjava njihovih rezultatov s pravilnim. Metode testiramo z različnimi argumenti.
[TestClass]
    public class TestFactorial
    {

        #region Printout fields
        private const string Recursive = "Error in recursive function";
        private const string Iterative = "Error in iterative function";
        private const string Parallel = "Error in parallel function";
        private const string ExoticF = "Error in exotic F# function";
        private const string IterativeF = "Error in iterative F# function";
        private const string RecursiveF = "Error in recursive F# function";
        #endregion

        private static void MethodRun(BigInteger parameter, BigInteger expectedResult)
        {
            Factorial.Number = parameter;
            var actual = Factorial.FactorialRecursiveOverFSharp();
            Assert.AreEqual(expectedResult, actual, Recursive);
            actual = Factorial.FactorialIterative();
            Assert.AreEqual(expectedResult, actual, Iterative);
            actual = Factorial.FactorialIParallel();
            Assert.AreEqual(expectedResult, actual, Parallel);
            actual = Factorial.FactorialFSharpExotic();
            Assert.AreEqual(expectedResult, actual, ExoticF);
            actual = Factorial.FactorialFSharpIterative();
            Assert.AreEqual(expectedResult, actual, IterativeF);
            actual = Factorial.FactorialFSharpRecursive();
            Assert.AreEqual(expectedResult, actual, RecursiveF);
        }

        #region Test Methods
        [TestMethod]
        public void TestFactorialsFor0()
        {
            MethodRun(0, 1);
        }

        [TestMethod]
        public void TestFactorialsFor1()
        {
            MethodRun(1, 1);
        }

        [TestMethod]
        public void TestFactorialsFor10()
        {
            MethodRun(10, 3628800);
        }

        [TestMethod]
        public void TestFactorialsFor20()
        {
            MethodRun(20, 2432902008176640000);
        }
        #endregion

    }
}
Rezultati testa - vsi testi so uspešni.
Za konec pa še metrika kode za C# projekte v tej rešitvi.

Ni komentarjev:

Objavite komentar