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.