14.3.1 表示仿真的世界

来源:互联网 发布:rar解压密码破解软件 编辑:程序博客网 时间:2024/04/27 23:49

14.3.1 表示仿真的世界

 

    我们的仿真世界是很简单的,它只包含动物和捕食者,因此,可以用两个列表来表示。原则上,我们还应该包括这个世界范围的宽度和高度,但是,我们使用固定的大小来,以使事情更简单。你可以得到有关这个世界更好地了解,我们试图通过图 14.3 来表示,它显示了仿真运行中的一个屏幕快照。

image

图 14.3 运行中的念真,有 10 个捕食者(大圆圈),在捕猎 100 个动物(小圆圈)

 

    我们来看看在两种语言中,这个仿真中有趣的元素,就像我们在图像应用程序中所做的。我们首先用 F# 版本来说明典型的函数式方法。

 

用 F# 表示这个仿真的状态

 

    如我们所提到的,仿真的状态会有两个列表,表示动物和捕食者的位置。我们需要用位置来进行计算,比如,计算在两个位置之间的路径上的几个位置。为简化此过程,我们要实现 Vector (向量)类型,使用它来表示动物的位置(相对于原点)。还会实现几个运算符,用于向量的加、减法,以及向量乘以标量的浮点数。

    我们将我们的向量实现为简单的不可变的值类型,仿真的状态是一个 F# 的记录类型,有两个字段。你可以在清单 14.20 中看到这个数据结构的声明。

 

Listing 14.20 Representing the state of the world (F#)

 

[<Struct>]
type Vector(x:float, y:float) =
  member t.X = x
  member t.Y = y
  static member (+) (vect1:Vector, vect2:Vector) =
    Vector(vect1.X + vect2.X, vect1.Y + vect2.Y)
  static member (*) (vect:Vector, f) =
    Vector(vect.X * f, vect.Y * f)
  static member (-) (vect1:Vector, vect2:Vector) =
    vect1 + (vect2 * -1.0)

type Simulation =
{ Animals : list<Vector>
  Predators : list<Vector> }

 

    位置是用 Struct 属性标记的简单对象,它包含在构造函数中设置的不可变的属性, X 和 Y 坐标,它支持按分量逐位(component-wise)加法,乘以一个标量,向量减法的操作。注意,减法使用另外两个运算符实现的。如你所料,所有的运算符都返回新的值。表示仿真的类型也很简单明了,这个类型也是不可变的,因此,为了处理它,就需要为每一步的仿真,构建一个新的 Simulation 值。

    现在,让我们看看 C# 的表示形式。我们主要使用标准 .NET 类型,但是,会以函数的方式处理。

 

用 C# 表示仿真的状态

 

    在 C# 中,最简单的方法是使用可变类型,表示一些状态,因为,这正是 C# 语言和标准的 .NET 库提供了最大的支持。特别是,.NET 不能提供函数式的列表类型。我们使用以前章节中的 FuncList<T> 类型,这会有两个类似的表示。然而,函数式编程是一种风格,并不是一种技术,因此,我们甚至可以用已有的类,来写函数式代码。只是必须更小心,才能保证做到正确。

    清单 14.21 显示了这样的类,我们将用来在 C# 中表示这个仿真。我们省略了 Vector 类型的实现,因为它是一个简单的不可变结构,有重载的运算符,与 F# 版本完全相同。

 

Listing 14.21 Representing the state of the world (C#)

 

public class Simulation {
  private readonly IEnumerable<Vector> animals;
  private readonly IEnumerable<Vector> predators;

  public Simulation(List<Vector> animals, List<Vector> predators) {
    this.animals = animals;
    this.predators = predators;
  }

  public IEnumerable<Vector> Animals
  { get { return animals; } }
  public IEnumerable<Vector> Predators
  { get { return predators; } }
}

 

    在这里,我们使用了两种不同的集合类型,一个用于构造函数的参数,当在其中创建仿真状态时,另一个用于属性。在构造函数中,我们使用List<T>,以确保得到完全计算过的集合,包含所有的位置。因为 IEnumerable<T>是一个延迟序列,我们不可能知道,位置是否已经计算过,或者,是否在以后计算,当我们在以后代码中需要它们的时候。这不是一个大问题,但它会让使测量性能变得困难,因为,我们不可能知道代码何时执行。此外,如果我们运行嘀嗒操作多次,而不强制序列进行计算,它可能会创建一个延迟的序列,将执行后面的工作,在这类应用程序中,这可能会引起混乱。

    我们不想把状态公开为 List<T>,因为这是一种可变类型,有人可能会对其进行修改。相反,我们使用 IEnumerable<T>,因此,客户端代码可以对动物和捕食者进行迭代,而不会直接修改现有状态。

    现在,我们已经有了表示状态的数据结构,也应该看一下能用它来做什么了。在典型的函数式设计中,这总是下一步要做的事。