C#中的函數編程


原文鏈接:https://www.codeproject.com/Articles/1160683/Functional-Programming-In-Csharp-2

                   https://blog.csdn.net/mzl87/article/details/100025445

目錄

通過函數表示數據

集合

空集

全集

單元素集

其他集合

二元操作

並集

交集

笛卡爾積

差集

對等差分

其他操作

對於那些想要更進一步的人

歐幾里得平面

繪制磁盤

繪制水平和垂直半平面

函數

對於那些想要更進一步的人

分形

復數和繪圖

牛頓分形

對於那些想要更進一步的人

延遲簡介


 

通過函數表示數據

讓S是任何元素a,b,c...(例如,桌子上的書,或者歐幾里得平面的點)的集合,並讓S'是這些元素的任意子集(例如,桌子上的綠皮書,或者半徑為1的圓上以歐幾里得平面原點為中心的點)。

集合S'的特征函數S'(x)是一個函數,它將true或false與S的每個元素x相關聯。

  1.  
    S '(x) = true if x is in S'
  2.  
    S '(x) = false if x is not in S'

讓S成為桌子上的一套書,讓S'成為桌上的綠皮書。讓a和b是兩個綠色的書,讓c和d是在表中的兩個紅色的本。然后:

  1.  
    S '(a) = S'(b) = true
  2.  
    S '(c) = S'(d) = false

讓S是歐幾里德平面中的點的集合,並且讓S'在半徑為1的園中以歐幾里得平面(0,0)原點為中心的點的集合(單位圓)。讓a和b在單位圓的兩點,並讓c並且d是半徑為2的圓上以歐幾里得平面原點為中心的點。然后:

  1.  
    S '(a) = S'(b) = true
  2.  
    S '(c) = S'(d) = false

因此,任何集合S'總是可以由其特征函數表示。一個函數,它將一個元素作為參數,並返回true如果該元素在S'中,否則返回false。換句話說,可以通過C#中的謂詞來表示集合(抽象數據類型)。

Predicate<T> set;

在接下來的部分中,我們將看到如何通過C#以函數方式表示集合代數中的一些基本集合,然后我們將在集合上定義泛型二進制運算。然后,我們將在歐幾里德平面的子集上對數字應用這些操作。集合是抽象數據結構,數字的子集和歐幾里得平面的子集是抽象數據結構的表示,最后二元操作是適用於抽象數據結構的任何表示的通用邏輯。

集合

本節通過C#介紹集合代數中一些基本集的表示。

空集

 

讓E是空集和Empty是它的特征函數。在集合的代數中,E是沒有元素的唯一集合。因此,Empty可以定義如下:

  1.  
    Empty(x) = false if x is in E
  2.  
    Empty(x) = false if x is not in E

因此,E在C#中的表示可以定義如下:

  1.  
    public static Predicate<T> Empty<T>()
  2.  
    {
  3.  
    return x => false;
  4.  
    }

在集合的代數中,Empty表示如下:

 

因此,運行以下代碼:

  1.  
    Console.WriteLine( "\nEmpty set:");
  2.  
    Console.WriteLine( "Is 7 in {{}}? {0}", Empty<int>()(7));

結果如下:

 

全集

 

讓S是一集合和S'是包含所有要素的S的子集,All是其特色函數。在集合的代數中,S'是包含所有元素的完整集合。因此,All可以這樣定義:

All(x) = true if x is in S

因此,S'在C#中的表示可以定義如下:

  1.  
    public static Predicate<T> All<T>()
  2.  
    {
  3.  
    return x => true;
  4.  
    }

在集合的代數中,All表示如下:

 

因此,運行以下代碼:

Console.WriteLine("Is 7 in the integers set? {0}", All<int>()(7));

結果如下:

 

單元素集

讓E是單元素集並且Singleton是它的特征函數。在集合的代數中,E也稱為單元集合,或者1元組是具有恰好一個元素e的集合。因此,Singleton可以定義如下:

  1.  
    Singleton(x) = true if x is e
  2.  
    Singleton(x) = false if x is not e

因此,E在C#中的表示可以定義如下:

  1.  
    public static Predicate<T> Singleton<T>(T e)
  2.  
    {
  3.  
    return x => e.Equals(x);
  4.  
    }

因此,運行以下代碼:

  1.  
    Console.WriteLine( "Is 7 in the singleton {{0}}? {0}", Singleton(0)(7));
  2.  
    Console.WriteLine( "Is 7 in the singleton {{7}}? {0}", Singleton(7)(7));

結果如下:

其他集合

本節介紹整數集的子集。

偶數

讓E是一個偶數集合,並且Even是它的特征函數。在數學中,偶數是一個2的倍數。因此,Even可以定義如下:

  1.  
    Even(x) = true if x is a multiple of 2
  2.  
    Even(x) = false if x is not a multiple of 2

因此,E在C#中的表示可以定義如下:

Predicate<int> even = i => i % 2 == 0;

因此,運行以下代碼:

  1.  
    Console.WriteLine( "Is {0} even? {1}", 99, even(99));
  2.  
    Console.WriteLine( "Is {0} even? {1}", 998, even(998));

結果如下:

奇數

讓E是一個奇數集合並且Odd是它的特征函數。在數學中,奇數是一個不是2的倍數的數字。因此,Odd可以定義如下:

  1.  
    Odd(x) = true if x is not a multiple of 2
  2.  
    Odd(x) = false if x is a multiple of 2

因此,E在C#中的表示可以定義如下:

Predicate<int> odd = i => i % 2 == 1;

因此,運行以下代碼:

  1.  
    Console.WriteLine( "Is {0} odd? {1}", 99, odd(99));
  2.  
    Console.WriteLine( "Is {0} odd? {1}", 998, odd(998));

結果如下:

3的倍數

讓E是一個3的倍數的集合兵器MultipleOfThree是它的特征函數。在數學中,3的倍數是可被3整除的數。因此,MultipleOfThree可以定義如下:

  1.  
    MultipleOfThree(x) = true if x is divisible by 3
  2.  
    MultipleOfThree(x) = false if x is not divisible by 3

因此,E在C#中的表示可以定義如下:

Predicate<int> multipleOfThree = i => i % 3 == 0;

因此,運行以下代碼:

  1.  
    Console.WriteLine( "Is {0} a multiple of 3? {1}", 99, multipleOfThree(99));
  2.  
    Console.WriteLine( "Is {0} a multiple of 3? {1}", 998, multipleOfThree(998));

結果如下:

5的倍數

讓E是5的倍數的集合兵器MultipleOfFive是它的特征函數。在數學中,5的倍數是可被5整除的數。因此,MultipleOfFive可以定義如下:

  1.  
    MultipleOfFive(x) = true if x is divisible by 5
  2.  
    MultipleOfFive(x) = false if x is not divisible by 5

因此,E在C#中的表示可以定義如下:

Predicate<int> multipleOfFive = i => i % 5 == 0;

因此,運行以下代碼:

  1.  
    Console.WriteLine( "Is {0} a multiple of 5? {1}", 15, multipleOfFive(15));
  2.  
    Console.WriteLine( "Is {0} a multiple of 5? {1}", 998, multipleOfFive(998));

結果如下:

質數

很久以前,當我接觸Project Euler問題時,我不得不解決以下問題:

  1.  
    By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13,
  2.  
    we can see that the 6th prime is 13.
  3.  
    What is the 10 001st prime number?

為了解決這個問題,我首先必須編寫一個快速算法來檢查給定的數字是否為素數。編寫算法后,我編寫了一個迭代算法,遍歷素數直到找到第10 001個素數。

讓E是素數的集合並且Prime是它的特征函數。在數學中,素數是大於1的自然數,除了1和自身之外沒有正除數。因此,Prime可以定義如下:

  1.  
    Prime(x) = true if x is prime
  2.  
    Prime(x) = false if x is not prime

因此,E在C#中的表示可以定義如下:

Predicate<int> prime = IsPrime;

IsPrime是檢查給定數字是否為素數的方法。

  1.  
    static bool IsPrime(int i)
  2.  
    {
  3.  
    if (i == 1) return false; // 1 is not prime
  4.  
    if (i < 4) return true; // 2 and 3 are primes
  5.  
    if ((i >> 1) * 2 == i) return false; // multiples of 2 are not prime
  6.  
    if (i < 9) return true; // 5 and 7 are primes
  7.  
    if (i % 3 == 0) return false; // multiples of 3 are not primes
  8.  
     
  9.  
    // If a divisor less than or equal to sqrt(i) is found
  10.  
    // then i is not prime
  11.  
    int sqrt = (int)Math.Sqrt(i);
  12.  
    for (int d = 5; d <= sqrt; d += 6)
  13.  
    {
  14.  
    if (i % d == 0) return false;
  15.  
    if (i % (d + 2) == 0) return false;
  16.  
    }
  17.  
     
  18.  
    // Otherwise i is prime
  19.  
    return true;
  20.  
    }

因此,運行下面的代碼來解決我們的問題:

  1.  
    int p = Primes(prime).Skip(10000).First();
  2.  
    Console.WriteLine( "The 10 001st prime number is {0}", p);

其中Primes定義如下:

  1.  
    static IEnumerable <int> Primes(Predicate<int> prime)
  2.  
    {
  3.  
    yield return 2;
  4.  
     
  5.  
    int p = 3;
  6.  
    while (true)
  7.  
    {
  8.  
    if (prime(p)) yield return p;
  9.  
    p += 2;
  10.  
    }
  11.  
    }

結果如下:

二元操作

本節介紹了從給定集合和操作集合構造新集合的幾個基本操作。下面是集合代數中的維恩圖

並集

讓E和F兩個集合。E和F的並集,用E u F表示的是E或F的所有元素的集合。

讓Union成為並操作。因此,可以在C#中實現如下Union操作:

  1.  
    public static Predicate<T> Union<T>(this Predicate<T> e, Predicate<T> f)
  2.  
    {
  3.  
    return x => e(x) || f(x);
  4.  
    }

如您所見,Union是一組特征函數的擴展函數。所有操作將被定義為集合的特征函數上的擴展函數。從而,運行以下代碼:

Console.WriteLine("Is 7 in the union of Even and Odd Integers Set? {0}", Even.Union(Odd)(7));

結果如下:

交集

讓E和F兩個集合。E和F的交集,由E n F表示的是所有元素的集合,他們是E和F的成員。

讓Intersection成為交叉操作。因此,可以在C#中實現如下Intersection操作:

  1.  
    public static Predicate<T> Intersection<T>(this Predicate<T> e, Predicate<T> f)
  2.  
    {
  3.  
    return x => e(x) && f(x);
  4.  
    }

如您所見,Intersection是一組特征函數的擴展函數。從而,運行以下代碼:

  1.  
    Predicate< int> multiplesOfThreeAndFive = multipleOfThree.Intersection(multipleOfFive);
  2.  
    Console.WriteLine( "Is 15 a multiple of 3 and 5? {0}", multiplesOfThreeAndFive(15));
  3.  
    Console.WriteLine( "Is 10 a multiple of 3 and 5? {0}", multiplesOfThreeAndFive(10));

結果如下:

笛卡爾積

讓E和F兩個集合。E和F的笛卡兒積,用E × F表示的是所有有序對(e, f) 集合,因此e是E的成員,f是F的成員。

讓CartesianProduct成為笛卡爾積操作。因此,CartesianProduct可以在C#中實現如下操作:

  1.  
    public static Func<T1, T2, bool> CartesianProduct<T1, T2>(this Predicate<T1> e, Predicate<T2> f)
  2.  
    {
  3.  
    return (x, y) => e(x) && f(y);
  4.  
    }

如您所見,CartesianProduct是一組特征函數的擴展函數。從而,運行以下代碼:

  1.  
    Func< int, int, bool> cartesianProduct = multipleOfThree.CartesianProduct(multipleOfFive);
  2.  
    Console.WriteLine( "Is (9, 15) in MultipleOfThree x MultipleOfFive? {0}", cartesianProduct(9, 15));

結果如下:

差集

讓E和F兩個集合。F中E的差集,用E \ F表示的是所有元素的集合,其中的元素是E的成員但不是F的成員。

讓Complement成為差集操作。因此,Complement可以在C#中實現如下操作:

  1.  
    public static Predicate<T> Complement<T>(this Predicate<T> e, Predicate<T> f)
  2.  
    {
  3.  
    return x => e(x) && !f(x);
  4.  
    }

如您所見,Complement是一個集合的特征函數的擴展方法。從而,運行以下代碼:

  1.  
    Console.WriteLine( "Is 15 in MultipleOfThree \\ MultipleOfFive set? {0}",
  2.  
    multipleOfThree.Complement(multipleOfFive)( 15));
  3.  
    Console.WriteLine( "Is 9 in MultipleOfThree \\ MultipleOfFive set? {0}",
  4.  
    multipleOfThree.Complement(multipleOfFive)( 9));

結果如下:

對等差分

讓E和F兩個集合。E和F的對等差分,用E ▲ F表示的是所有元素的集合,其中的元素是E和F的成員,但不是E和F的交集成員。

讓SymmetricDifference成為對等差分操作。因此,SymmetricDifference可以在C#中以兩種方式實現操作。一個簡單的方法是使用並和差操作,如下:

  1.  
    public static Predicate<T> SymmetricDifferenceWithoutXor<T>(this Predicate<T> e, Predicate<T> f)
  2.  
    {
  3.  
    return Union(e.Complement(f), f.Complement(e));
  4.  
    }

另一種方法是使用XOR二進制操作如下:

  1.  
    public static Predicate<T> SymmetricDifferenceWithXor<T>(this Predicate<T> e, Predicate<T> f)
  2.  
    {
  3.  
    return x => e(x) ^ f(x);
  4.  
    }

如您所見,SymmetricDifferenceWithoutXor和SymmetricDifferenceWithXor是集合的特征函數的擴展方法。從而,運行以下代碼:

  1.  
    // SymmetricDifference without XOR
  2.  
    Console.WriteLine( "\nSymmetricDifference without XOR:");
  3.  
    Predicate< int> sdWithoutXor = prime.SymmetricDifferenceWithoutXor(even);
  4.  
    Console.WriteLine
  5.  
    ( "Is 2 in the symetric difference of prime and even Sets? {0}", sdWithoutXor(2));
  6.  
    Console.WriteLine
  7.  
    ( "Is 4 in the symetric difference of prime and even Sets? {0}", sdWithoutXor(4));
  8.  
    Console.WriteLine
  9.  
    ( "Is 7 in the symetric difference of prime and even Sets? {0}", sdWithoutXor(7));
  10.  
     
  11.  
    // SymmetricDifference with XOR
  12.  
    Console.WriteLine( "\nSymmetricDifference with XOR:");
  13.  
    Predicate< int> sdWithXor = prime.SymmetricDifferenceWithXor(even);
  14.  
    Console.WriteLine( "Is 2 in the symetric difference of prime and even Sets? {0}", sdWithXor(2));
  15.  
    Console.WriteLine( "Is 4 in the symetric difference of prime and even Sets? {0}", sdWithXor(4));
  16.  
    Console.WriteLine( "Is 7 in the symetric difference of prime and even Sets? {0}", sdWithXor(7));

結果如下:

其他操作

本節介紹集合上其他有用的二進制操作。

包含

讓Contains是檢查元素是否在集合中的操作。此操作是一個集合的特征函數的擴展函數,它將元素作為參數,如果元素在集合中則返回true,否則返回false。

因此,此操作在C#中定義如下:

  1.  
    public static bool Contains<T>(this Predicate<T> e, T x)
  2.  
    {
  3.  
    return e(x);
  4.  
    }

因此,運行以下代碼:

  1.  
    Console.WriteLine( "Is 7 in the singleton {{0}}? {0}", Singleton(0).Contains(7));
  2.  
    Console.WriteLine( "Is 7 in the singleton {{7}}? {0}", Singleton(7).Contains(7));

結果如下:

讓Add是將一個元素添加到集合中的操作。此操作是一個集合的特征函數的擴展函數,它將元素作為參數並將其添加到集合中。

因此,此操作在C#中定義如下:

  1.  
    public static Predicate<T> Add<T>(this Predicate<T> s, T e)
  2.  
    {
  3.  
    return x => x.Equals(e) || s(x);
  4.  
    }

因此,運行以下代碼:

  1.  
    Console.WriteLine( "Is 7 in {{0, 7}}? {0}", Singleton(0).Add(7)(7));
  2.  
    Console.WriteLine( "Is 0 in {{1, 0}}? {0}", Singleton(1).Add(0)(0));
  3.  
    Console.WriteLine( "Is 7 in {{19, 0}}? {0}", Singleton(19).Add(0)(7));

結果如下:

刪除

讓Remove是從集合中刪除元素的操作。此操作是對集合的特征函數的擴展函數,該函數將元素作為參數並將其從集合中移除。

因此,此操作在C#中定義如下: 

  1.  
    public static Predicate<T> Remove<T>(this Predicate<T> s, T e)
  2.  
    {
  3.  
    return x => !x.Equals(e) && s(x);
  4.  
    }

因此,運行以下代碼:

  1.  
    Console.WriteLine( "Is 7 in {{}}? {0}", Singleton(0).Remove(0)(7));
  2.  
    Console.WriteLine( "Is 0 in {{}}? {0}", Singleton(7).Remove(7)(0));

結果如下:

對於那些想要更進一步的人

您可以通過函數式編程看到我們在C#中使用集合代數是多么容易。在前面的部分中顯示了最基本的定義。但是,如果你想進一步,你可以考慮:

  • 關系集
  • 抽象代數,如單倍體,群,場,環,K-矢量空間等
  • 包含排除原則
  • 羅素的悖論
  • 康托爾的悖論
  • 雙向量空間
  • 定理和推論

歐幾里得平面

在上一節中,集合的基本概念是在C#中實現的。在本節中,我們將練習在平面點集(歐幾里德平面)上實現的概念。

繪制磁盤

磁盤是由圓圈限定的平面的子集。有兩種類型的磁盤。封閉的磁盤是包含構成其邊界的圓的點的磁盤,而打開的磁盤是不包含構成其邊界的圓的點的磁盤。

在本節中,我們將設置特征函數盤,並在WPF應用程序繪制。

要設置特征函數,首先需要一個計算平面中兩點之間歐氏距離的函數。該函數實現如下:

  1.  
    public static double EuclidianDistance(Point point1, Point point2)
  2.  
    {
  3.  
    return Math.Sqrt(Math.Pow(point1.X - point2.X, 2) + Math.Pow(point1.Y - point2.Y, 2));
  4.  
    }

其中Point是在System.Windows命名空間中定義的struct。這個公式是基於畢達哥拉斯定理。

其中c是歐幾里德距離,a²是(point1.X - point2.X)²和b²是(point1.Y - point2.Y)²。

讓Disk是特征函數的閉盤。在集合的代數中,實數集中的閉盤的定義如下:

其中a和b是中心和R半徑的坐標。

因此,Disk在C#中的實現如下:

  1.  
    public static Predicate<Point> Disk(Point center, double radius)
  2.  
    {
  3.  
    return p => EuclidianDistance(center, p) <= radius;
  4.  
    }

為了查看集合,我決定實現一個在歐幾里得平面中繪制集合的函數Draw。我選擇了WPF,因此使用System.Windows.Controls.Image作為畫布和Bitmap作為上下文。

因此,我通過Draw方法建立了下面說明的歐幾里德平面

以下是該方法的實現:

  1.  
    public static void Draw(this Predicate<Point> set, Image plan)
  2.  
    {
  3.  
    Drawing.Bitmap bitmap = new Drawing.Bitmap((int)plan.Width, (int)plan.Height);
  4.  
     
  5.  
    //
  6.  
    // Graph drawing
  7.  
    //
  8.  
    double semiWidth = plan.Width / 2;
  9.  
    double semiHeight = plan.Height / 2;
  10.  
     
  11.  
    double xMin = -semiWidth;
  12.  
    double xMax = +semiWidth;
  13.  
    double yMin = -semiHeight;
  14.  
    double yMax = +semiHeight;
  15.  
     
  16.  
    for (int x = 0; x < bitmap.Height; x++)
  17.  
    {
  18.  
    double xp = xMin + x * (xMax - xMin) / plan.Width;
  19.  
     
  20.  
    for (int y = 0; y < bitmap.Width; y++)
  21.  
    {
  22.  
    double yp = yMax - y * (yMax - yMin) / plan.Height;
  23.  
     
  24.  
    if (set(new Point(xp, yp)))
  25.  
    {
  26.  
    bitmap.SetPixel(x, y, Drawing.Color.Black);
  27.  
    }
  28.  
    }
  29.  
    }
  30.  
     
  31.  
    plan.Source = Imaging.CreateBitmapSourceFromHBitmap(
  32.  
    bitmap.GetHbitmap(),
  33.  
    IntPtr.Zero,
  34.  
    System.Windows.Int32Rect.Empty,
  35.  
    BitmapSizeOptions.FromWidthAndHeight(bitmap.Width, bitmap.Height));
  36.  
     
  37.  
    }

在Draw方法中,創建具有與歐幾里德平面容器相同的寬度和相同高度的bitmap 。然后,如果bitmap每個像素點(x,y)都屬於set,則將其替換為一個黑點。xMin,xMax,yMin和yMax是在上方圖中所示的歐幾里得平面的邊界值。

如您所見,Draw是一組點的特征函數的擴展函數。因此,運行以下代碼:

Plan.Disk(new Point(0, 0), 20).Draw(plan);

結果如下:

繪制水平和垂直半平面

水平垂直半平面或者是平面分割歐幾里得空間的兩個子集之一。水平半平面是兩個子集的任意一個子集,其中一個平面通過與Y軸垂直的線將歐幾里得空間分割成兩個子集。垂直半平面是兩個子集的任意一個子集,其中一個平面通過與x軸垂直的線將歐幾里得空間分割成兩個子集。

在本節中,我們將設置水平垂直半平面的特征函數,在WPF應用程序中繪制它們,看看如果我們將它們與磁盤子集組合,我們可以做些什么。

讓HorizontalHalfPlane是水平半平面的特征函數。HorizontalHalfPlanE在C#中的實現如下:

  1.  
    public static Predicate<Point> HorizontalHalfPlane(double y, bool lowerThan)
  2.  
    {
  3.  
    return p => lowerThan ? p.Y <= y : p.Y >= y;
  4.  
    }

因此,運行以下代碼:

Plan.HorizontalHalfPlane(0, true).Draw(plan);

結果如下:

讓VerticalHalfPlane是垂直半平面的特征函數。VerticalHalfPlanE在C#中的實現如下:

  1.  
    public static Predicate<Point> VerticalHalfPlane(double x, bool lowerThan)
  2.  
    {
  3.  
    return p => lowerThan ? p.X <= x : p.X >= x;
  4.  
    }

因此,運行以下代碼:

Plan.VerticalHalfPlane(0, false).Draw(plan);

結果如下:

在本文的第一部分中,我們在集合上設置了基本的二元操作。因此,通過組合disk和half-plane的交集,我們可以繪制半磁盤子集。

因此,運行以下示例:

Plan.VerticalHalfPlane(0, false).Intersection(Plan.Disk(new Point(0, 0), 20)).Draw(plan);

結果如下:

函數

本節介紹歐幾里德平面上的集合的函數。

轉變

讓Translate是轉變在平面上的點的函數。在歐幾里德幾何中,Translate 是一個將給定點在指定方向上移動恆定距離的函數。因此,C#中的實現如下:

  1.  
    public static Func<Point, Point> Translate(double deltax, double deltay)
  2.  
    {
  3.  
    return p => new Point(p.X + deltax, p.Y + deltay);
  4.  
    }

其中(deltax, deltay)是轉變的常量向量。

讓TranslateSet是轉化平面中集合的函數。此函數在C#中簡單實現如下:

  1.  
    public static Predicate<Point> TranslateSet(this Predicate<Point> set,
  2.  
    double deltax, double deltay)
  3.  
    {
  4.  
    return x => set(Translate(-deltax, -deltay)(x));
  5.  
    }

TranslateSet是集合上的擴展函數。它以deltax作為參數,deltax是第一個歐幾里得維度中的delta距離,deltay是第二個歐幾里得維度中的delta距離。如果點Pxy在集合S被轉變,則其坐標將變為x'y'=x + delatxy + deltay。因此,點X ' - delatxY' - DELTAY將始終屬於集合S。在集合代數中,TranslateSet稱為同構,換句話說,所有轉變的集合形成轉變組T,其與空間本身同構。這解釋了函數的主要邏輯。

因此,在我們的WPF應用程序中運行以下代碼:

TranslateDiskAnimation();

其中TranslateDiskAnimation描述如下:

  1.  
    private const double Delta = 50;
  2.  
    private double _diskDeltay;
  3.  
    private readonly Predicate<Point> _disk = Plan.Disk(new Point(0, -170), 80);
  4.  
     
  5.  
    private void TranslateDiskAnimation()
  6.  
    {
  7.  
    DispatcherTimer diskTimer = new DispatcherTimer { Interval = new TimeSpan(0, 0, 0, 1, 0) };
  8.  
    diskTimer.Tick += TranslateTimer_Tick;
  9.  
    diskTimer.Start();
  10.  
    }
  11.  
     
  12.  
    private void TranslateTimer_Tick(object sender, EventArgs e)
  13.  
    {
  14.  
    _diskDeltay = _diskDeltay <= plan.Height ? _diskDeltay + Delta : Delta;
  15.  
    Predicate<Point> translatedDisk = _diskDeltay <= plan.Height ?
  16.  
      _disk.TranslateSet( 0, _diskDeltay) : _disk;
  17.  
    translatedDisk.Draw(plan);
  18.  
    }

結果如下:

相似擴大

讓Scale是將任何點M發送到另一個點N的函數,這樣段SNSM在同一條線上,但按系數lambda進行縮放。在集合的代數中,Scale表述如下:

因此,C#中的實現如下:

  1.  
    public static Func<Point, Point> Scale
  2.  
    ( double deltax, double deltay, double lambdax, double lambday)
  3.  
    {
  4.  
    return p => new Point(lambdax * p.X + deltax, lambday * p.Y + deltay);
  5.  
    }

其中(deltax, deltay)是轉變的常量向量,(lambdax, lambday)是?向量。

讓ScaleSet是對計划中的集合上應用相似擴大的函數。此函數在C#中簡單實現如下:

  1.  
    public static Predicate<Point> ScaleSet(this Predicate<Point> set,
  2.  
    double deltax, double deltay, double lambdax, double lambday)
  3.  
    {
  4.  
    return x => set(Scale(-deltax / lambdax, -deltay / lambday, 1 / lambdax, 1 / lambday)(x));
  5.  
    }

ScaleSet是集合上的擴展函數。它以deltax作為參數,deltax是第一個歐幾里得維度中的delta距離deltay是第二個歐幾里得維度中的delta距離,以及(lambdax, lambday)是常數因子向量?如果點P(x,y)在集合S中通過ScaleSet轉換,則其坐標將更改為(x',y')=(lambdax*x+delatx,lambday*y+deltay)。因此,點((x’-delatx)/lambdax,(y’-deltay)/lambday)將始終屬於集合S,如果?當然,與向量0不同。在集合代數中,ScaleSet稱為同構,換句話說,所有同構的集合形成同構群H,同構於空間本身。這解釋了函數的主要邏輯。

因此,在我們的WPF應用程序中運行以下代碼:

ScaleDiskAnimation();

其中ScaleDiskAnimation描述如下:

  1.  
    private const double Delta = 50;
  2.  
    private double _lambdaFactor = 1;
  3.  
    private double _diskScaleDeltay;
  4.  
    private readonly Predicate<Point> _disk2 = Plan.Disk(new Point(0, -230), 20);
  5.  
     
  6.  
    private void ScaleDiskAnimation()
  7.  
    {
  8.  
    DispatcherTimer scaleTimer = new DispatcherTimer { Interval = new TimeSpan(0, 0, 0, 1, 0) };
  9.  
    scaleTimer.Tick += ScaleTimer_Tick;
  10.  
    scaleTimer.Start();
  11.  
    }
  12.  
     
  13.  
    private void ScaleTimer_Tick(object sender, EventArgs e)
  14.  
    {
  15.  
    _diskScaleDeltay = _diskScaleDeltay <= plan.Height ? _diskScaleDeltay + Delta : Delta;
  16.  
    _lambdaFactor = _diskScaleDeltay <= plan.Height ? _lambdaFactor + 0.5 : 1;
  17.  
    Predicate<Point> scaledDisk = _diskScaleDeltay <= plan.Height
  18.  
    ? _disk2.ScaleSet( 0, _diskScaleDeltay, _lambdaFactor, 1)
  19.  
    : _disk2;
  20.  
    scaledDisk.Draw(plan);
  21.  
    }

結果如下:

旋轉

讓Rotation是以theta角度旋轉一點的函數。在矩陣代數中,Rotation表述如下:

其中x'y'是旋轉后點的坐標,x'y'的公式如下:

這個公式的演示非常簡單。看看這個旋轉。

演示如下:

因此,C#中的實現如下:

  1.  
    public static Func<Point, Point> Rotate(double theta)
  2.  
    {
  3.  
    return p => new Point(p.X * Math.Cos(theta) - p.Y * Math.Sin(theta),
  4.  
    p.X * Math.Cos(theta) + p.Y * Math.Sin(theta));
  5.  
    }

讓RotateSet是在平面的集合上應用的帶角度旋轉的函數?此函數在C#中簡單實現如下。

  1.  
    public static Predicate<Point> RotateSet(this Predicate<Point> set, double theta)
  2.  
    {
  3.  
    return p => set(Rotate(-theta)(p));
  4.  
    }

RotateSet是集合上的擴展函數。它以theta作為參數,即旋轉角度。如果點Pxy)通過集合S中的RotateSet進行變換,則其坐標將變為(x', y') = (x * cos(?) - y * sin(?), x * cos(?) + y * sin(?))。因此,點(x' * cos(?) + y' * sin(?), x' * cos(?) - y' * sin(?))將永遠屬於集合S。在集合的代數中,RotateSet稱為同構,換句話說,所有旋轉的集合形成旋轉組R,其與空間本身同構。這解釋了該函數的主要邏輯。

因此,在我們的WPF應用程序中運行以下代碼:

RotateHalfPlaneAnimation();

其中RotateHalfPlaneAnimation描述如下:

  1.  
    private double _theta;
  2.  
    private const double TwoPi = 2 * Math.PI;
  3.  
    private const double HalfPi = Math.PI / 2;
  4.  
    private readonly Predicate<Point> _halfPlane = Plan.VerticalHalfPlane(220, false);
  5.  
     
  6.  
    private void RotateHalfPlaneAnimation()
  7.  
    {
  8.  
    DispatcherTimer rotateTimer = new DispatcherTimer
  9.  
      { Interval = new TimeSpan(0, 0, 0, 1, 0) };
  10.  
    rotateTimer.Tick += RotateTimer_Tick;
  11.  
    rotateTimer.Start();
  12.  
    }
  13.  
     
  14.  
    private void RotateTimer_Tick(object sender, EventArgs e)
  15.  
    {
  16.  
    _halfPlane.RotateSet(_theta).Draw(plan);
  17.  
    _theta += HalfPi;
  18.  
    _theta = _theta % TwoPi;
  19.  
    }

結果如下:

對於那些想要更進一步的人

很簡單,不是嗎?對於那些想要更進一步的人,你可以探索這些:

  • 橢圓
  • 三維歐氏空間
  • 橢圓體
  • 拋物面
  • 雙曲面
  • 球面諧波
  • 超橢球體
  • 妊神星
  • 同形體
  • 焦平面

分形

分形是具有通常超過其拓撲維度並且可能落在整數之間的分形維數的集合。例如,Mandelbrot集是由一系列復數二次多項式定義的分形:

Pc(z) = z^2 + c

其中c是復數。Mandelbrot分形被定義為所有點的集合c,使得上述序列不逃逸到無窮大。在集合的代數中,這表達如下:

Mandelbrot集如上圖所示。

分形(抽象數據類型)總是可以在C#中表示如下:

Func<Complex, Complex> fractal;

復數和繪圖

為了能夠繪制分形,我需要操縱復數。因此,我使用了Meta.numerics庫。我還需要一個實用程序來繪制Bitmap中的復數,因此我使用了類ColorMap和ClorTriplet類。

牛頓分形

我創建了一個牛頓分形(抽象數據類型表示)P(z) = z^3 - 2*z + 2,可在下面找到。

  1.  
    public static Func<Complex, Complex> NewtonFractal()
  2.  
    {
  3.  
    return z => z * z * z - 2 * z + 2;
  4.  
    }

為了能夠繪制復數,我需要更新Draw函數。因此,我創建了一個使用ColorMap和ClorTriplet類的Draw函數的重載。下面是C#中的實現。

  1.  
    public static void Draw(this Func<Complex, Complex> fractal, Image plan)
  2.  
    {
  3.  
    var bitmap = new Bitmap((int) plan.Width, (int) plan.Height);
  4.  
     
  5.  
    const double reMin = -3.0;
  6.  
    const double reMax = +3.0;
  7.  
    const double imMin = -3.0;
  8.  
    const double imMax = +3.0;
  9.  
     
  10.  
    for (int x = 0; x < plan.Width; x++)
  11.  
    {
  12.  
    double re = reMin + x*(reMax - reMin)/plan.Width;
  13.  
    for (int y = 0; y < plan.Height; y++)
  14.  
    {
  15.  
    double im = imMax - y*(imMax - imMin)/plan.Height;
  16.  
     
  17.  
    var z = new Complex(re, im);
  18.  
    Complex fz = fractal(z);
  19.  
     
  20.  
    if (Double.IsInfinity(fz.Re) || Double.IsNaN(fz.Re) || Double.IsInfinity(fz.Im) ||
  21.  
    Double.IsNaN(fz.Im))
  22.  
    {
  23.  
    continue;
  24.  
    }
  25.  
     
  26.  
    ColorTriplet hsv = ColorMap.ComplexToHsv(fz);
  27.  
     
  28.  
    ColorTriplet rgb = ColorMap.HsvToRgb(hsv);
  29.  
    var r = (int) Math.Truncate(255.0*rgb.X);
  30.  
    var g = (int) Math.Truncate(255.0*rgb.Y);
  31.  
    var b = (int) Math.Truncate(255.0*rgb.Z);
  32.  
    Color color = Color.FromArgb(r, g, b);
  33.  
     
  34.  
    bitmap.SetPixel(x, y, color);
  35.  
    }
  36.  
    }
  37.  
     
  38.  
    plan.Source = Imaging.CreateBitmapSourceFromHBitmap(
  39.  
    bitmap.GetHbitmap(),
  40.  
    IntPtr.Zero,
  41.  
    Int32Rect.Empty,
  42.  
    BitmapSizeOptions.FromWidthAndHeight(bitmap.Width, bitmap.Height));
  43.  
    }

因此,運行以下代碼:

Plan.NewtonFractal().Draw(plan);

結果如下:

對於那些想要更進一步的人

對於那些想要更進一步的人,你可以探索這些:

  • Mandelbrot分形
  • 朱莉婭分形
  • 其他牛頓分形
  • 其他分形

延遲簡介

在本節中,我們將看到如何從.NET Framework 3.5版開始創建一個Lazy類型。

延遲評估是一種評估策略,它將表達式的評估延遲到需要它的值,並且還避免重復評估。與其他非嚴格的評估策略(如按名稱調用)相比,共享可以通過指數因子減少某些函數的運行時間。下面列出了延遲評估的好處。

  • 通過避免不必要的計算以及評估復合表達式的錯誤條件來提高性能
  • 構造潛在無限數據結構的能力:我們可以輕松地創建一個無限的整數集,例如通過一個函數(參見集合部分中素數的例子)
  • 將控制流(結構)定義為抽象而不是基元的能力

我們來看看下面的代碼:

  1.  
    public class MyLazy<T>
  2.  
    {
  3.  
    #region Fields
  4.  
     
  5.  
    private readonly Func<T> _f;
  6.  
    private bool _hasValue;
  7.  
    private T _value;
  8.  
     
  9.  
    #endregion
  10.  
     
  11.  
    #region Constructors
  12.  
     
  13.  
    public MyLazy(Func<T> f)
  14.  
    {
  15.  
    _f = f;
  16.  
    }
  17.  
     
  18.  
    #endregion
  19.  
     
  20.  
    #region Operators
  21.  
     
  22.  
    //
  23.  
    // Use objects of type MyLazy<T> as objects of type T
  24.  
    // through implicit keyword
  25.  
    //
  26.  
    public static implicit operator T(MyLazy<T> lazy)
  27.  
    {
  28.  
    if (!lazy._hasValue)
  29.  
    {
  30.  
    lazy._value = lazy._f();
  31.  
    lazy._hasValue = true;
  32.  
    }
  33.  
     
  34.  
    return lazy._value;
  35.  
    }
  36.  
     
  37.  
    #endregion
  38.  
    }

MyLazy<T>是一個包含以下字段的泛型類:

  • _f:延遲評估的函數,返回T類型值
  • _value:T類型的值(凍結值)
  • _hasValue:一個布爾值,指示是否已計算該值

為了使用類型MyLazy<T>的對象作為類型T的對象,使用implicit關鍵字。評估在類型鑄造時完成,此操作稱為解凍

因此,運行以下代碼:

  1.  
    var myLazyRandom = new MyLazy<double>(GetRandomNumber);
  2.  
    double myRandomX = myLazyRandom;
  3.  
    Console.WriteLine( "\n Random with MyLazy<double>: {0}", myRandomX);

其中GetRandomNumber返回隨機double如下:

  1.  
    static double GetRandomNumber()
  2.  
    {
  3.  
    Random r = new Random();
  4.  
    return r.NextDouble();
  5.  
    }

給出以下輸出:

.NET Framework 4引入了一個用於延遲評估的類System.Lazy<T>。此類通過屬性Value返回值。運行以下代碼:

  1.  
    var lazyRandom = new Lazy<double>(GetRandomNumber);
  2.  
    double randomX = lazyRandom;

給出編譯錯誤,因為類型Lazy<T>與類型double不同。

要使用類System.Lazy<T>的值,必須按如下方式使用該屬性Value:

  1.  
    var lazyRandom = new Lazy<double>(GetRandomNumber);
  2.  
    double randomX = lazyRandom.Value;
  3.  
    Console.WriteLine( "\n Random with System.Lazy<double>.Value: {0}", randomX);

輸出如下:

.NET Framework 4還為延遲評估推出了ThreadLocal和LazyInitializer。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM