Tag: 数学

一种精巧的模式,可在Swift中制作高效缓存功能

Swift是在移动设备上很好地使用的一种语言,其中一些在(非常)有限的计算能力下运行—例如,以较旧的iPhone或Apple Watch为例。 在这种情况下,每个CPU周期都非常重要,精心设计的应用程序将充分利用它们。 减少应用程序占用空间的一种已知方法是通过缓存。 缓存通常被认为是一项棘手的业务。 当将诸如网页之类的外部资源放入缓存时,这是正确的,但是当我们仅考虑缓存纯函数的结果时,事情就容易得多。 如果某个函数始终为给定的输入返回相同的值,而不会产生任何副作用,则认为该函数是纯函数。 缓存纯非递归函数的结果 对于此类功能,功能编程是透明地提高其缓存效率的指定工具。 考虑以下代码: cached函数将一个函数作为参数,并返回一个新函数,该函数将计算与原始函数完全相同的结果。 但是,一旦第一次计算出一个值,它将被存储在缓存中。 因此,使用相同输入进行的任何后续调用将不再需要执行任何计算以得出正确的结果。 考虑到您正在编写2D游戏的代码,现在可以轻松使用标准三角函数的缓存有效版本: 如果您的游戏经常对相同的值进行三角函数计算,那么现在怀疑这种方法是否会对性能产生重大影响? 递归函数的问题 上面讨论的技术是如此易于使用,以至于人们可能认为它有一个陷阱! 确实有一个限制,或者说是一个限制。 当您使用非递归函数时,一切都很好,并且已cached函数就像一个符咒一样工作。 但是,如果您尝试将其传递给递归函数,例如: 您不会注意到性能的提高,并且实际上对于大于20的n值,该函数仍将花费大量时间返回-请记住,此函数在不缓存中间结果的情况下具有指数级的时间复杂度。 为什么会这样? 嗯, fib是一个递归函数,因此它在自己的体内进行调用。 确实,当您将fib放入cached函数中并调用生成的函数时,将进行缓存查找。 但是之后,对fib的递归调用仍将指向原始fib ,因此将不再发生缓存查找。 为了允许相同的缓存机制与递归函数一起使用,将需要其他工作。 编写高效的缓存递归函数 首先,我们将无法创建现有功能的缓存版本。 但是我们可以提供一个框架来轻松编写具有缓存效率的递归函数。 我们需要解决的关键问题是递归调用。 我们需要一种透明地将一些代码包装在递归调用周围的方法,以便在实际执行缓存查找之前执行它。 解决方案在于以下类型签名: ((In) -> Out, In) -> Out – In和Out是泛型类型。 此签名描述了一个带有两个参数的函数: 递归函数 递归函数的输入 通过这种类型,我们既可以使用第一个参数进行递归调用,又可以通过第二个参数访问函数的输入。 从那里,我们只需要编写一个内部函数,该函数实际上将在执行递归调用之前执行高速缓存查找。 尽管此实现确实有点复杂,但很棒的事情是使用非常容易,因为将其他语法保持在最低限度: 从那里,您现在可以使用缓存的功能,并看到它运行得更快! 从那里去哪里? 我上面描述的包含缓存中间结果的过程称为“记忆化”。 它的原理很简单:它将内存换为CPU周期。 尽管可以进行任何形式的折衷,但是它可以带来显着的计算加速,但这并不是万灵丹。 因此,在应用每个特定情况之前,必须考虑到每个特定情况: 在某些情况下,您可能需要限制高速缓存的大小,并实施某些高速缓存替换策略以管理高速缓存。 […]

OpenCalc — React Native —深入研究(第2部分)

这是OpenCalc的2部分系列的第2部分,OpenCalc是使用react-native,javascript和flow构建的开源移动计算器。 第一部分处理设计和UI组件,而上一篇文章则介绍了如何在应用市场中编写该应用程序。 第二部分将处理计算和验证。 OpenCalc在iOS和Android上可用。 主控制器具有一个称为Brain的属性,它是CalculatorBrain的一个实例。 控制器调用以下大脑功能: brain.clear()//清除大脑队列 brain.setItem(button)//将一个项目添加到队列 brain.deleteLast()//删除队列中的最后一项 brain.getDisplay()//返回队列的文本显示 brain.getResult()//返回评估队列的结果 大脑的主要目的是处理一系列操作。 操作在操作文件中定义。 运作方式 Operations.js提供了许多与定义,存储和使用Operations相关的功能。 Operations文件的主要目的之一是定义Operation类。 操作课 Operation构造函数如下: 构造函数 stringVal:字符串, operationType:数字, operationSubType:数字, val:任何, 优先级:?数字, operationArgs:?Set ) stringVal只是显示中的字符串表示形式。 operationType和operationSubType仅包含一个值,以帮助验证器确定操作是否合法以及如何处理每个操作。 类型和子类型只是枚举值。 val用于存储与运算符相关的功能。 优先级是操作(例如PEMDAS)的优先级。 例如,+和*的优先级分别为2和4。 因此,当计算器计算1 + 2 * 3时,它会优先执行*,因为它具有较高的优先级。 最后,operationArgs只是一组存储特殊情况的枚举。 例如,pi是一个数字,但应将其打印为字符串而不是数字表示形式。 将所有可用估值存储在计算器中 操作是计算器可用的各种操作的字典。 在此屏幕上添加新操作或更改操作方式很容易。 操作示例: ‘+’:操作( stringVal =’+’, operationType = OperationType.Operation, operationSubType = OperationSubType.BinaryOp, val = function(x,y){return […]

对不起,您睡过三角法了吗? 你将会。

在我所有的数学课程中,从代数到几何再到微积分,三角学始终是事后的想法。 真可惜,因为当我听到有人说“数学在现实生活中从来没有帮助过我!”时,我立即认为那仅仅是因为他们不记得自己的三角学。 我很幸运地拥有一位非常热衷于让我了解Trig,尤其是Unit Circle的导师,即使我不记得所有可用的东西,我也永远会记住这个经常变化的数学领域有多么宝贵。 三角函数是一个非常简单的名字的奇特名字:将圆分成多个角度。 关于圆的令人敬畏的事情是:1)它在整个表面上是连续的,并且2)它控制可能的角度的整个范围。 我们将这两种性质放在一起,得到基本的三角函数。 因此,了解三角学的基本单位是单位圆,这就是我们要开始的地方。 这里有单位圆。 它看起来确实很复杂,但实际上您需要记住的是,它只是一个划分为基本角度的圆。 我们的操作方法如下:我们知道一个圆的周长为2π,因此将圆分成虚数部分,称为弧度,而在圆中有2π。 假设我们对角度有所了解,您可以说我们有一些简单的弧度值,只需将弧度的总和减半即可将其转换为角度: 一直绕圆-将其视为360度-2π弧度 绕圆的一半-这是180度-π弧度 再说一半,即四分之一圆-这是90度-π/ 2弧度 最后四分之一的一半(45度)为π/ 4弧度 所以减半很棒,但是我们如何获得其他重要角度,或者如何获得所需的任何角度? 好吧,我想做的是相对于90度旋转。 如果我想要30度,那我知道那是90度的1/3。 不好了! 现在我们必须除分数! 我曾经讨厌这个,但如今我知道这没什么大不了的。 就像π/ 2的一半是π/ 4,π/ 2的1/3(记住是90度)是π/ 6。 所以现在我们可以从这里得到想要的任何角度,只要是90的偶数倍即可: 记得! 90度是π/ 2弧度。 30度:π/ 6弧度(30进入90 3倍,如π/ 2的1/3所述为π/ 6) 10度:π/ 18弧度(因为10进入30 3倍) 1度:π/ 180弧度 哇! 看看那个最后一个家伙! 我们怎么到那里? 好吧,请记住180度是π弧度……我们知道我们可以将任意个数除以1,因此,如果将π弧度除以180,则1度必须是π/ 180弧度! 现在我们可以将任何角度转换为弧度! 只需取所需的度数,然后乘以分数π/ 180即可! 如果您因担心数学而瘫痪,甚至不需要减少! (恭喜!:) 我的想象力受损的读者说:“嘿,等等!这不是Swift博客吗? 这如何适用于Swift? […]