- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
我们已经介绍过数据抽象,这是一种构造系统的方法学,它能够使程序中的大部分描述与其所操作的数据对象的具体表示无关,比如一个有理数程序的设计与有理数的实现相分离。这里的关键是构筑数据抽象屏障——在有理数的例子中即有理数的构造函数( make_rat )和获取有理数分子分母的选择函数( numer 、 denom )——它能将有理数的使用方式与其借助于表结构的具体表示形式隔离开.
数据抽象屏障是控制复杂性的强有力工具,然而这种类型的数据抽象还不够强大有力。从一个另一个角度看,对于一个数据对象可能存在多种有用的表示方式,且我们希望所设计的系统能够处理多种表示形式。比如,复数就可以表示为两种几乎等价的形式:直角坐标形式(实部和虚部)和极坐标形式(模和幅角)。有时采用直角坐标更方便,有时采用幅角更方便。我们希望设计的过程能够对具有任意表示形式的复数工作.
我们从简单的复数实例开始,看看如何为复数设计出直角坐标表示和极坐标表示,而又维持一种抽象的“复数”数据对象的概念。做到这一点的方式就是基于通用型选择函数( real_part 、 img_part 、 magnitude 、 angle )来定义复数的算术运算( add_complex 、 sub_complex 、 mul_complex 和 div_complex ),使这些选择函数能访问一个复数的各个部分,无论复数采用的是什么表示方式。采用这种方法设计的复数系统如下图所示:
上图中包含两种不同的抽象屏障。“水平”抽象屏障所扮演的角色如我们在有理数中讲的相同,他们将“高层”操作与“底层”表示隔离开。与此同时,还存在一道“垂直”屏障,它使我们能够隔离不同的设计,并且还能够安装其他的表示方式.
复数表示为有序对有两种可能表示方式:直角坐标形式(实部和虚部)和极坐标形式(模和幅角)。我们将复数集合设想为一个带有两个坐标轴(“实”轴和“虚”轴)的两维空间,如下图所示。按照这一观点,复数 \(z = x + iy\) (其中 \(i^2=-1\) )可看作这个平面上的一个点,其中的实坐标是 \(x\) 而虚坐标为 \(y\) 。在这种表示下,复数的加法就可以归结为两个坐标相加:
当需要乘两个复数时,更自然的考虑是采用复数的极坐标形式,此时复数用一个模和一个幅角表示(上图中的 \(r\) 和 \(A\) )。两个复数的乘积也是一个向量,得到它的方式是模相乘,幅角相加.
虽说复数的两种不同表示方式适合不同的运算,但从开发人员角度来看,数据抽象原理希望所有复数操作都应该可以使用,而无论计算机所用的具体表示形式是什么。例如我们常常需要取得一个复数的模,即使它原本采用的是复数的直角坐标表示;同样我们也常常需要得到复数的实部,即使它采用的是极坐标形式.
我们沿用之前有理数的设计策略,假定所有复数运算的实现都基于如下四个选择函数: real_part 、 img_part 、 magnitude 和 angle ;还要假定有两个构造复数的过程: make_from_real_imag 根据实部和虚部返回一个(基于某种表示的)复数, make_from_mag_ang 根据模和幅角描述返回一个(基于某种表示的)复数。这些过程的性质是。对于任何复数 \(z\) (不管其基于何种表示方式),下面两者:
make_from_real_imag(real_part(z), imag_part(z))
和 。
make_from_mag_ang(magnitude(z), angle(z))
产生出的复数都应该等于 \(z\) (且保持原来的表示方式).
我们可以利用这些构造函数和选择函数来刻画“抽象数据”,从而实现复数的算术。正如上面公式中所述,复数的加法和减法采用实部和虚部方式描述,而乘法和除法采用模和幅角的方式描述.
def add_complex(z1, z2):
return make_from_real_imag(real_part(z1) + real_part(z2), imag_part(z1) + imag_part(z2))
def sub_complex(z1, z2):
return make_from_real_imag(real_part(z1) - real_part(z2), imag_part(z1) - imag_part(z2))
def mul_complex(z1, z2):
return make_from_mag_ang(magnitude(z1) * magnitude(z2), angle(z1) + angle(z2))
def div_complex(z1, z2):
return make_from_mag_ang(magnitude(z1) / magnitude(z2), angle(z1) - angle(z2))
为了完成这一复数包,我们必须选择一种表示方式。我们假定有两个程序员Ben和Hacker。Ben选择了复数的直角坐标形式,Alyssa选择了复数的极坐标形式。对于选择直角坐标形式的Ben而言,此时实部和虚部的获取是直截了当的,但为了得到模和幅角,或需要在给定模和幅角下构造复数时,他利用了下面的三角关系:
这些公式建立起实部和虚部对偶 \((x, y)\) 与模和幅角对偶 \((r, A)\) 之间的联系。Ben基于这种表示给出了下面这几个选择函数和构造函数:
import math
def real_part(z):
return z[0]
def imag_part(z):
return z[1]
def magnitude(z):
return math.sqrt(real_part(z) ** 2 + imag_part(z) ** 2)
def angle(z):
return math.atan2(imag_part(z), real_part(z))
def make_from_real_imag(x, y):
return [x, y]
def make_from_mag_ang(r, a):
return [r * math.cos(a), r * math.sin(a)]
下列是我们对Ben的直角坐标表示方法的测试结果:
complex_1 = make_from_real_imag(math.sqrt(3)/2, 1/2) # (sqrt(3)/2, 1/2)
complex_2 = make_from_real_imag(1/2, math.sqrt(3)/2) # (1/2, sqrt(3)/2)
print(add_complex(complex_1, complex_2))
# [1.3660254037844386, 1.3660254037844386], 对应(sqrt(3)/2 + 1/2, sqrt(3)/2 + 1/2)
print(mul_complex(complex_1, complex_2))
# [6.123233995736765e-17, 0.9999999999999998],对应(0, 1)
但在另一边,对于选择了复数的极坐标形式的Alyssa而言,选取模和幅角的操作直截了当,但必须通过三角关系去得到实部和虚部。Alyssa基于复数的极坐标形式所给出的选择函数和构造函数如下:
def real_part(z):
return magnitude(z) * math.cos(angle(z))
def imag_part(z):
return magnitude(z) * math.sin(angle(z))
def magnitude(z):
return z[0]
def angle(z):
return z[1]
def make_from_real_imag(x, y):
return [math.sqrt(x**2 + y**2), math.atan2(y, x)]
def make_from_mag_ang(r, a):
return [r, a]
下列是我们对Alyssa的极坐标表示方法的测试结果:
complex_1 = make_from_mag_ang(1, math.pi/6) # (1, pi/6)
complex_2 = make_from_mag_ang(1, math.pi/3) # (1, pi/3)
print(add_complex(complex_1, complex_2))
# [1.9318516525781366, 0.7853981633974483], 对应(sqrt(6) + sqrt(2))/2, arctan(sqrt(3)/2 + 1/2, sqrt(3)/2 + 1/2))
print(mul_complex(complex_1, complex_2))
# [1, 1.5707963267948966] 对应(1, pi/2)
数据抽象的规则保证了 add_complex 、 sub_complex 、 mul_complex 和 div_complex 的同一套实现对于Ben的表示或者Alyssa的表示都能正常工作.
认识数据抽象的一种方式是将其看作“最小允诺原则”的一个应用。在 2.4.1 节中我们可以选择采用Ben的直角坐标表示形式或者Alyssa的极坐标表示形式,由选择函数和构造函数形成的抽象屏障,使我们可以把为自己所用数据对象选择具体表示形式的事情尽量往后推,而且还能保持系统设计的最大灵活性.
最小允诺原则还可以推进到更极端的情况,我们可以在完成了对选择函数和构造函数的设计,并决定了同时使用Ben的表示和Alyssa的表示之后,依然维持所用表示方式的不确定性。如果要在同一个系统中包含这两种不同表示,那么就需要采用一种方式将极坐标形式的数据和直角坐标形式的数据区分开.
完成这种区分的一种方式,就是在每个复数里包含一个类型标志部分——符号 rectangular 或者 polar ,我们在操作复数时可以借助此标志来确定使用的选择函数.
为了能对带标志数据进行各种操作,我们假定有过程 type_tag 和 contents ,它们分别从数据对象中提取类型标志和实际内容(在复数的例子中即极坐标或者直角坐标)。还要假定一个过程 attach_tag ,它以一个标志和实际内容为参数,生成出一个带标志的数据对象。实现这些的直接方式就是采用普通的表结构:
def attach_tag(type_tag, contents):
return [type_tag, contents]
def type_tag(datum):
if isinstance(datum, list):
return datum[0]
else:
raise ValueError("Bad tagged dataum -- TYPE-TAG", datum)
def contents(datum):
if isinstance(datum, list):
return datum[1]
else:
raise ValueError("Bad tagged dataum -- CONTENTS", datum)
利用这些过程,我们就可以定义出谓词 is_rectangular 和 is_polar ,它们分别辨识直角坐标的和极坐标的复数:
def is_rectangular(z):
return type_tag(z) == "rectangular"
def is_polar(z):
return type_tag(z) == "polar"
有了类型系统之后,Ben和Alyssa现在就可以修改自己的代码,使他们的两种不同表示能共存于一个系统中了。当Ben构造一个复数时,总为它加上标志,说明采用的是直角坐标:
def real_part_rectangular(z):
return z[0]
def imag_part_rectangular(z):
return z[1]
import math
def magnitude_rectangular(z):
return math.sqrt(real_part_rectangular(z)**2 +
imag_part_rectangular(z)**2)
def angle_rectangular(z):
return math.atan2(imag_part_rectangular(z),
real_part_rectangular(z))
def make_from_real_imag_rectangular(x, y):
return attach_tag("rectangular", [x, y])
def make_from_mag_ang_rectangular(r, a):
return attach_tag("rectangular", [r * math.cos(a), r * math.sin(a)])
Alyssa构造复数时,总将其标志设为极坐标:
def real_part_polar(z):
return magnitude_polar(z) * math.cos(angle_polar(z))
def imag_part_polar(z):
return magnitude_polar(z) * math.sin(angle_polar(z))
def magnitude_polar(z):
return z[0]
def angle_polar(z):
return z[1]
def make_from_real_imag_polar(x, y):
return attach_tag("polar",
[math.sqrt(x**2 + y**2),
math.atan2(y, x)])
def make_from_mag_ang_polar(r, a):
return attach_tag("polar", [r, a])
每个通用型选择函数都需要考虑到可能存在的两种复数表示情况,故它需要先检查参数的标志,然后调用处理该类数据的适当过程。例如为了得到一个复数的实部, real_part 需要通过检查确定是使用 Ben 的 real_part_rectangular 还是 Alyssa 的 real_part_polar 。在这两种情况下,我们都用 contents 提取出原始的无标志数据,并将它送给所需的直角坐标过程或极坐标过程:
def real_part(z):
if is_rectangular(z):
return real_part_rectangular(contents(z))
elif is_polar(z):
return real_part_polar(contents(z))
else:
raise ValueError("Unknown type -- REAL-PART", z)
def imag_part(z):
if is_rectangular(z):
return imag_part_rectangular(contents(z))
elif is_polar(z):
return imag_part_polar(contents(z))
else:
raise ValueError("Unknown type -- IMAG-PART", z)
def magnitude(z):
if is_rectangular(z):
return magnitude_rectangular(contents(z))
elif is_polar(z):
return magnitude_polar(contents(z))
else:
raise ValueError("Unknown type -- MAGNITUDE", z)
def angle(z):
if is_rectangular(z):
return angle_rectangular(contents(z))
elif is_polar(z):
return angle_polar(contents(z))
else:
raise ValueError("Unknown type -- ANGLE", z)
在实现复数算术运算时,我们仍然可以采用取自2.4.1节的同样过程 add_complex 、 sub_complex 、 mul_complex 和 div_complex ,因为它们所调用的选择函数都是通用型的,对任何表示都能工作,例如过程 add_complex 仍然是:
def add_complex(z1, z2):
return make_from_real_imag(real_part(z1) + real_part(z2),
imag_part(z1) + imag_part(z2))
最后,我们还必须选择是采用Ben的表示还是Alyssa的表示构造复数。一种合理的选择是,手头有实部和虚部时,构造函数的返回采用直角坐标表示;手头有模和幅角时,构造函数的返回采用极坐标表示:
def make_from_real_imag(x, y):
# 手头有实部和虚部时,构造函数的返回采用直角坐标表示
return make_from_real_imag_rectangular(x, y)
def make_from_mag_angle(r, a):
# 手头有模和幅角时,构造函数的返回采用极坐标表示
return make_from_mag_ang_polar(r, a)
下面是我们对这样的复数系统进行的测试结果:
complex_1 = make_from_mag_ang_polar(1, math.pi/6) # (1, pi/6)
complex_2 = make_from_mag_ang_polar(1, math.pi/3) # (1, pi/3)
print(add_complex(complex_1, complex_2))
# ['rectangular', [1.3660254037844388, 1.3660254037844386]], 对应(sqrt(3)/2 + 1/2, sqrt(3)/2 + 1/2)
实际上,这样得到的复数系统所具有的结构如下所示:
可见这一系统已经分解为三个相对独立的部分:复数算术运算、Alyssa的极坐标实现和Ben的直角坐标实现。极坐标或直角坐标的实现可以是Ben和Alyssa独立工作写成的东西,这两部分又被第三个程序员作为基础表示,用于在抽象构造函数和选择函数的界面(interface)之上实现各种复数算术过程.
检查一个数据项的类型,并据此去调用某个适当过程称为 基于类型的分派 。在系统设计中,这是一种获得模块性的强有力策略。而在另一方面,像2.4.2节那样实现的分派有两个明显的弱点。第一个弱点是,其中的这些通用型界面过程( real_part 、 imag_part 、 magnitude 和 angle )必须知道所有的不同表示。举例来说,假定现在希望为前面的复数系统增加一种表示,我们就必须将这一新表示方式标识为一种新类型,而且要在每个通用界面过程里增加一个子句,检查这一新类型.
第二个弱点是,即使这些独立的表示形式可以分别设计,我们也必须保证在系统里不存在两个名字相同的过程。正因如此,Ben和Alyssa必须去修改原来在2.4.1节中给出的那些过程的名字.
位于这两个弱点之下的基础问题是,上面这种实现通用型界面的技术不具有 可加性 。在每次增加一种新表示形式时,使用通用选择函数的人都必须修改他们的过程,而那些做独立表示的界面的人也必须修改代码以避免名字冲突问题,这非常不方便,且容易引进错误.
现在我们需要的是一种能够将系统设计进一步模块化的方法。一种称为 数据导向的程序设计 的编程技术提供了这种能力。在这种编程技术中,我们可以将处理针对不同类型的一些公共通用操作视为处理一个二维表格,其中一维包含着所有的可能操作,另一维就是所有的可能类型。在前一节开发的复数系统里,我们也可以将同样的信息组织为一个表格,如下图所示:
在我们上面所实现的复数系统中,采用的方式是用一些过程做为复数算术与两个表示包之间的界面,并且让这些过程中的每一个去做基于类型的显式分派。而数据导向的程序设计则意味着我们可以把这一界面实现为一个过程,让它用操作名和参数类型的组合到表格中查找,以便找出应该调用的适当过程.
最后此篇关于SICP:复数的直角和极坐标的表示(Python实现)的文章就讲到这里了,如果你想了解更多关于SICP:复数的直角和极坐标的表示(Python实现)的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
背景: 我最近一直在使用 JPA,我为相当大的关系数据库项目生成持久层的轻松程度给我留下了深刻的印象。 我们公司使用大量非 SQL 数据库,特别是面向列的数据库。我对可能对这些数据库使用 JPA 有一
我已经在我的 maven pom 中添加了这些构建配置,因为我希望将 Apache Solr 依赖项与 Jar 捆绑在一起。否则我得到了 SolarServerException: ClassNotF
interface ITurtle { void Fight(); void EatPizza(); } interface ILeonardo : ITurtle {
我希望可用于 Java 的对象/关系映射 (ORM) 工具之一能够满足这些要求: 使用 JPA 或 native SQL 查询获取大量行并将其作为实体对象返回。 允许在行(实体)中进行迭代,并在对当前
好像没有,因为我有实现From for 的代码, 我可以转换 A到 B与 .into() , 但同样的事情不适用于 Vec .into()一个Vec . 要么我搞砸了阻止实现派生的事情,要么这不应该发
在 C# 中,如果 A 实现 IX 并且 B 继承自 A ,是否必然遵循 B 实现 IX?如果是,是因为 LSP 吗?之间有什么区别吗: 1. Interface IX; Class A : IX;
就目前而言,这个问题不适合我们的问答形式。我们希望答案得到事实、引用资料或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visit the
我正在阅读标准haskell库的(^)的实现代码: (^) :: (Num a, Integral b) => a -> b -> a x0 ^ y0 | y0 a -> b ->a expo x0
我将把国际象棋游戏表示为 C++ 结构。我认为,最好的选择是树结构(因为在每个深度我们都有几个可能的移动)。 这是一个好的方法吗? struct TreeElement{ SomeMoveType
我正在为用户名数据库实现字符串匹配算法。我的方法采用现有的用户名数据库和用户想要的新用户名,然后检查用户名是否已被占用。如果采用该方法,则该方法应该返回带有数据库中未采用的数字的用户名。 例子: “贾
我正在尝试实现 Breadth-first search algorithm , 为了找到两个顶点之间的最短距离。我开发了一个 Queue 对象来保存和检索对象,并且我有一个二维数组来保存两个给定顶点
我目前正在 ika 中开发我的 Python 游戏,它使用 python 2.5 我决定为 AI 使用 A* 寻路。然而,我发现它对我的需要来说太慢了(3-4 个敌人可能会落后于游戏,但我想供应 4-
我正在寻找 Kademlia 的开源实现C/C++ 中的分布式哈希表。它必须是轻量级和跨平台的(win/linux/mac)。 它必须能够将信息发布到 DHT 并检索它。 最佳答案 OpenDHT是
我在一本书中读到这一行:-“当我们要求 C++ 实现运行程序时,它会通过调用此函数来实现。” 而且我想知道“C++ 实现”是什么意思或具体是什么。帮忙!? 最佳答案 “C++ 实现”是指编译器加上链接
我正在尝试使用分支定界的 C++ 实现这个背包问题。此网站上有一个 Java 版本:Implementing branch and bound for knapsack 我试图让我的 C++ 版本打印
在很多情况下,我需要在 C# 中访问合适的哈希算法,从重写 GetHashCode 到对数据执行快速比较/查找。 我发现 FNV 哈希是一种非常简单/好/快速的哈希算法。但是,我从未见过 C# 实现的
目录 LRU缓存替换策略 核心思想 不适用场景 算法基本实现 算法优化
1. 绪论 在前面文章中提到 空间直角坐标系相互转换 ,测绘坐标转换时,一般涉及到的情况是:两个直角坐标系的小角度转换。这个就是我们经常在测绘数据处理中,WGS-84坐标系、54北京坐标系
在软件开发过程中,有时候我们需要定时地检查数据库中的数据,并在发现新增数据时触发一个动作。为了实现这个需求,我们在 .Net 7 下进行一次简单的演示. PeriodicTimer .
二分查找 二分查找算法,说白了就是在有序的数组里面给予一个存在数组里面的值key,然后将其先和数组中间的比较,如果key大于中间值,进行下一次mid后面的比较,直到找到相等的,就可以得到它的位置。
我是一名优秀的程序员,十分优秀!