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