精华内容
下载资源
问答
  • 2022-04-01 15:55:40

    代码地址

    解析类似1+2*3这样的数学表达式。

    解析器类型

    通常一个解析器会接受一个字符串,解析成功返回一些值和剩下的字符串,解析失败则什么也不返回。这个过程总结为这样一个函数类型:

    typealias Parser<Result> = (String) -> (Result, String)?
    

    将Parser类型定义为一个结构体而不是简单的类型别名,这样能将组合算子编写为Parser内的方法而不是一些无主的函数,代码也会更加易读:

    struct Parser<Result> {
        typealias Stream = String
        let parse: (Stream) -> (Result, Stream)?
    }
    

    实现第一个解析器,一个能够解析出匹配条件字符的解析器:

    func character(matching condition: @escaping (Character) -> Bool) -> Parser<Character> {
        return Parser { input in
            guard let char = input.first, condition(char) else { return nil }
            return (char, String(input.dropFirst()))
        }
    }
    

    测试解析器,从字符串中解析数字“1”:

    let one = character { $0 == "1" }
    if let v = one.parse("123") { print(v) }
    /*输出:
     ("1", "23")
     */
    

    为了方便使用解析器解析结果,为Parser添加一个run方法:

    extension Parser {
        func run(_ string: String) -> (Result, String)? {
            guard let result = parse(string) else { return nil }
            return result
        }
    }
    
    if let v = one.run("123") { print(v) }
    /*输出:
     ("1", "23")
     */
    

    想解析的是任意数字而不只是1,那么需要创建一个数字解析器。为了检查某个字符是不是10进制数字需要用到CharacterSet类。为CharacterSet扩展一个contains方法,希望接受一个UnicodeScalar类型而检查类型是Character的值:

    extension CharacterSet {
        func contains(_ c: Character) -> Bool {
            let scalars = c.unicodeScalars
            guard scalars.count == 1 else { return false }
            return contains(scalars.first!)
        }
    }
    

    有了上述方法,解析数字就方便了:

    let digit = character { CharacterSet.decimalDigits.contains($0) }
    if let v = digit.run("456") { print(v) }
    /*输出:
     ("4", "56")
     */
    

    下面将这些原子化的解析器组合为更强大的解析器。

    组合解析器

    希望解析的是整数而不是单独的数字,因此需要多次执行解析器digit将解析结果合并为一个整数。

    首先,创建一个组合算子many用来多次执行某个解析器,并将解析结果做为一个数组返回:

    extension Parser {
        var many: Parser<[Result]> {
            return Parser<[Result]> { input in
                var result: [Result] = []
                var remainder = input
                while let (element, newRemainder) = self.run(remainder) {
                    result.append(element)
                    remainder = newRemainder
                }
                return (result, remainder)
            }
        }
    }
    
    if let v = digit.many.run("123") { print(v) }
    /*输出:
     (["1", "2", "3"], "")
     */
    

    接下来的步骤只是将数字字符数组转化为一个整数,因此为Parser定义一个map方法将返回值为某种类型的解析器转换为返回值为另一种类型的解析器:

    extension Parser {
        func map<T>(_ transform: @escaping (Result) -> T) -> Parser<T> {
            return Parser<T> { input in
                guard let (result, remainder) = self.run(input) else { return nil }
                return (transform(result), remainder)
            }
        }
    }
    

    有了many和map之后,定义整数解析器就非常方便了:

    let integer = digit.many.map { Int(String($0)) }
    if let v = integer.run("123") { print(v) }
    /*输出:
     (Optional(123), "")
     */
    

    如果解析了不止包含数字的字符串,那么从第一个不是数字的字符开始后面的字符串作为剩余部分返回:

    if let v = integer.run("123abc") { print(v) }
    /*输出:
     (Optional(123), "abc")
     */
    

    解析器digit.many能对空字符串解析成功,如果对Int初始化方法的返回值做强制解包就会导致崩溃。

    顺序解析

    现在可以重复执行某个解析器,然后将结果进行组合。但是希望能够同时执行多个不同的解析器。出于这个目的,引入顺序组合算子followed(by:),接受另一个解析器作为参数,返回一个全新的解析器。该新解析器会将前两个解析器的结果组合为一个多元组返回:

    extension Parser {
        func followed<A>(by other: Parser<A>) -> Parser<(Result, A)> {
            return Parser<(Result, A)> { input in
                guard let (result1, remainder1) = self.run(input) else { return nil }
                guard let (result2, remainder2) = other.run(remainder1) else { return nil }
                return ((result1, result2), remainder2)
            }
        }
    }
    

    利用followed(by:)定义一个乘法表达式的解析器:

    let multiplication = integer
        .followed(by: character { $0 == "*" })
        .followed(by: integer)
    if let v = multiplication.run("2*3") { print(v) }
    /*输出:
     (((2, "*"), 3), "")
     */
    

    由于越多次调用 followed(by:) 会导致越多层级的多元组嵌套,解析器的结果会越复杂,于是用前边定义的map将乘法表达式的结果计算出来:

    let multiplication2 = multiplication.map { $0.0.0*$0.1 }
    if let v = multiplication2.run("2*3") { print(v) }
    /*输出:
     (6, "")
     */
    

    改进顺序解析

    每次使用 followed(by:)来组合两个解析器,其返回值都是一个多元组。与其包裹一个嵌套的多元组,不如向每个解析器的结果传入一个可执行的函数来避免这个问题。

    有两种方法表示一个拥有多个参数的函数:既可以表示为一次获取所有参数的函数,也可以表示为一次只获取一个参数的柯里化函数。

    将之前传入map的函数剥离出来:

    func multiply(lhs: (Int, Character), rhs: Int) -> Int {
        return lhs.0*rhs
    }
    

    还可以写得更可读一些:

    func multiply(_ x: Int, _ op: Character, _ y: Int) -> Int {
        return x*y
    }
    

    这个函数的柯里化版本:

    func curriedMultiply(_ x: Int) -> (Character) -> (Int) -> Int {
        return { op in
            { y in
                return x*y
            }
        }
    }
    print(curriedMultiply(2)("*")(3))
    /*输出:
     6
     */
    

    每次将一个解析器的解析结果传入该函数,这样可以避免多元组嵌套的问题。

    每次写一个柯里化函数比较费劲,可以定义一个curry函数将非柯里化函数转化为柯里化函数:

    func curry<A, B, C>(_ f: @escaping (A, B) -> C) -> (A) -> (B) -> C {
        return { a in { b in f(a, b) } }
    }
    

    如何使用curriedMultiply来简化乘法解析器?第一个整数解析器的解析结果类型与curriedMultiply的第一个参数类型相同。所以使用map对integer的解析结果应用curriedMultiply:

    let p1 = integer.map(curriedMultiply)
    

    p1的解析结果类型跟curriedMultiply的返回类型一样为(Character) -> (Int) -> Int。

    接下来使用followed(by:) 添加乘法符号解析器:

    let p2 = p1.followed(by: character { $0 == "*" })
    

    P2的解析结果类型是一个多元组为((Character) -> (Int) -> Int, Character),继续使用map将多元组的第二个元素作为参数传入第一个元素:

    let p3 = p2.map { $0.0($0.1) }
    

    此时p3的解析结果类型为(Int) -> Int,重复这个过程添加最后的整数解析器:

    let p4 = p3.followed(by: integer)
    let p5 = p4.map { $0.0($0.1) }
    

    现在P5的解析结果类型是Int了:

    if let v = p5.run("2*3") { print(v) }
    /*输出:
     (6, "")
     */
    

    将乘法解析器编写为一段连贯的代码:

    let multiplication3 = integer.map(curriedMultiply)
        .followed(by: character { $0 == "*"} )
        .map { $0.0($0.1) }
        .followed(by: integer)
        .map { $0.0($0.1) }
    

    已经没有嵌套的多元组了,不过代码依旧令人困惑,继续改进代码。

    观察发现一个通用模式都是调用 followed(by:)来合并一个解析器然后使用map映射结果,而每次调用map时传入的函数体也是一样的。所以先可以为这种通用模式抽象一个顺序解析运算符<*>:

    precedencegroup SequencePrecedence {
        associativity: left
        higherThan: AdditionPrecedence
    }
    infix operator <*> : SequencePrecedence
    func <*><A, B>(lhs: Parser<(A) -> B>, rhs: Parser<A>) -> Parser<B> {
        return lhs.followed(by: rhs).map { $0.0($0.1) }
    }
    

    使用这个运算符,解析器代码会更易读:

    let multiplication4 = integer.map(curriedMultiply)<*>character { $0 == "*" }<*>integer
    

    不过代码看起来还是有点瑕疵,.map(curriedMultiply)位于第一个解析器和其他解析器之间,如果调换顺序,整句代码更加易读。定义一个运算符<^>,用于将一个前置函数传入解析器的map方法中:

    infix operator <^> : SequencePrecedence
    func <^><A, B>(lsh: @escaping (A) -> B, rsh: Parser<A>) -> Parser<B> {
        return rsh.map(lsh)
    }
    

    于是可以这样编写一个乘法解析器:

    let multiplication5 = curriedMultiply<^>integer<*>character { $0 == "*" }<*>integer
    

    一但使用这样的语法,解读解析器的具体功能变得更加容易。

    另一种顺序解析

    除了定义<*>,通常还会顺便定义另一种顺序解析运算符,同样合并两个解析器,但是会丢弃一个解析器的结果。比如“*”或“+”尾随一个整数这样的算术表达式,这种解析器就会排上用场。在那些情况中,并不关注运算符的解析结果,只要确保被解析的符号是存在的就可以了。

    定义*> 运算符,参照<*>运算符:

    infix operator *> : SequencePrecedence
    func *><A, B>(lsh: Parser<A>, rsh: Parser<B>) -> Parser<B> {
        return curry { _, y in y }<^>lsh<*>rsh
    }
    
    let positive = character { $0 == "+" }*>digit
    if let v = positive.run("+5") { print(v) }
    /*输出:
     ("5", "")
     */
    

    类似定义一个<*运算符,丢弃右侧解析器的结果:

    infix operator <* : SequencePrecedence
    func <*<A, B>(lsh: Parser<A>, rsh: Parser<B>) -> Parser<A> {
        return curry { x, _ in x }<^>lsh<*>rsh
    }
    
    let op = character { $0 == "*" }<*digit
    if let v = op.run("*2") { print(v) }
    /*输出:
     ("*", "")
     */
    

    选择解析

    假如希望解析器解析一个“*”或者一个“+”,处于这个目的,对Parser添加一个组合算子:

    extension Parser {
        func or(_ other: Parser<Result>) -> Parser<Result> {
            return Parser { self.run($0) ?? other.run($0) }
        }
    }
    
    let star = character { $0 == "*" }
    let plus = character { $0 == "+" }
    let starOrPlus = star.or(plus)
    if let v = starOrPlus.run("+") { print(v) }
    /*输出:
     ("+", "")
     */
    

    类似于顺序解析运算符,定义一个<|>运算符:

    infix operator <|>
    func <|><A>(lsh: Parser<A>, rsh: Parser<A>) -> Parser<A> {
        return Parser { lsh.run($0) ?? rsh.run($0) }
    }
    if let v = (star<|>plus).run("+") { print(v) }
    /*输出:
     ("+", "")
     */
    

    多次解析

    前面的整数解析器有一个缺陷,组合算子many会导致解析器integer在解析空字符串时发生奔溃。这是因为many内部实现中即使一开始解析失败也会返回一个空数组作为解析结果。

    为了解决这个问题,先单次运行这个解析器,而后再重复多次。使用顺序解析运算符重新定义many1:

    extension Parser {
        var many1: Parser<[Result]> {
            return { x in { manyX in [x] + manyX } }<^>self<*>self.many
        }
    }
    if let v = digit.many1.run("1234") { print(v) }
    /*输出:
     (["1", "2", "3", "4"], "")
     */
    

    <^>之前的匿名柯里化函数将self的单个结果拼接到self.many解析返回的数组之前。这种写法可读性并不高,下面使用curry将非柯里化函数转化为柯里化函数:

    extension Parser {
        var many2: Parser<[Result]> {
            return curry { [$0] + $1 }<^>self<*>self.many
        }
    }
    if let v = digit.many2.run("23456") { print(v) }
    /*输出:
     (["2", "3", "4", "5", "6"], "")
     */
    

    可选

    有时希望特定字符被可选地解析,即无论解析还是不解析都不影响整个解析过程。定义可选组合算子optional:

    extension Parser {
        var optional: Parser<Result?> {
            return Parser<Result?> { input in
                guard let result = self.run(input) else { return (nil, input) }
                
                return result
            }
        }
    }
    

    解析算术表达式

    在使用解析器组合算子来编写算术表达式的解析器之前,需要考虑优先级规则比如,乘法比加法有更高的优先级。

    目前除了对类似圆括号这样的语素有明显的缺失以外,还有更严重的问题:比如,一个加法表达式现阶段只能包含一个运算符"+",而可能会希望计算多个被加数的和。要实现这些并不难,但是它会让整个例子更复杂。因此在默许这些限制的情况下实现解析器。

    好消息是,在使用解析器组合算子库时,代码会与算术表达式语法非常相似。例如解析表达式2*3+4*6/2-10,只需要将算术表达式按照优先级翻译为组合算子的代码就可以,首先解析乘法:

    let mul = curry { return $0*($1 ?? 1) }<^>integer<*>(character{ $0 == "*" }*>integer).optional
    if let v = mul.run("4*6")?.0 { print(v) }
    /*输出:
     24
     */
    

    在运算符 <^> 之前的部分是一个用于计算结果的函数,它接收两个参数。由于乘法表达式的第二部分在语法中被标记为可选,所以第二个参数是可选的。在运算符 <^> 之后,只需要写出表达式中不同部分的解析器就可以了:一个整数解析器,随后是一个符号*的字符解析器,再之后是另一个整数解析器。由于不需要字符解析器的结果,使用顺序解析运算符 *> 来丢弃该运算符左侧的解析结果。最后使用组合算子 optional 来标记后一个整数解析器是可选的。

    接着按照优先级使用同样的方式来定义余下的解析器:

    let division = curry { return $0/($1 ?? 1) }<^>mul<*>(character{ $0 == "/" }*>integer).optional
    if let v = division.run("4*6/2")?.0 { print(v) }
    /*输出:
     12
     */
    let addition = curry { return $0+($1 ?? 0) }<^>mul<*>(character{ $0 == "+" }*>division).optional
    if let v = addition.run("2*3+4*6/2")?.0 { print(v) }
    /*输出:
     18
     */
    let minus = curry { $0 - ($1 ?? 0) }<^>addition<*>(character{ $0 == "-" }*>integer).optional
    if let v = minus.run("2*3+4*6/2-10")?.0 { print(v) }
    /*输出:
     8
     */
    

    也可以将这些代码重构为更精简的代码,比如编写一个函数来生成指定算术运算符的解析器。

    更 Swift 化的解析器类型

    前面定义的Parser是一种纯函数式的定义方案:函数 parse 没有任何副作用,而解析结果与剩余的输入字符串会作为一个多元组返回。

    另一种解决方案,要用到关键字 inout:

    struct Parser2<Result> {
        typealias Stream = String
        let pase: (inout Stream) -> Result?
    }
    func character2(matching condition: @escaping (Character) -> Bool) -> Parser2<Character> {
        return Parser2<Character> { input in
            guard let c = input.first, condition(c) else { return nil }
            input = String(input.dropFirst())
            return c
        }
    }
    var s = "cd"
    let c = character2 { $0 == "c" }
    if let v = c.pase(&s) { print(v) }
    print(s)
    /*输出:
     c
     d
     */
    

    关键字 inout 允许修改输入的字符串,可以只返回解析结果而不再是同时包含了结果和剩余符号的多元组。值得注意的是,inout 的作用效果与 Objective-C 中将对某个值的引用进行传递是不同的。仍旧可以将该参数当做其它简单的值一样进行操作,区别在于,这个值会在函数返回的同时被 复制回去。因此,在使用 inout 时并不会产生危及全局的副作用,因为可变性被严格限制在某 个特定的变量上了。

    使用 inout 参数可以简化一些组合算子的实现。比如,组合算子 many、map 现在可以编 写如下:

    extension Parser2 {
        var many: Parser2<[Result]> {
            return Parser2<[Result]> { input in
                var result: [Result] = []
                while let element = self.pase(&input) {
                    result.append(element)
                }
                
                if result.count > 0 { return result }
                else { return nil }
            }
        }
        func map<T>(transform: @escaping (Result) -> T) -> Parser2<T> {
            return Parser2<T>{ input in
                guard let result = self.pase(&input) else { return nil }
                
                return transform(result)
            }
        }
    }
    let digit2 = character2 { CharacterSet.decimalDigits.contains($0) }
    s = "123"
    if let v = digit2.pase(&s) { print(v) }
    print(s)
    /*输出:
     1
     23
     */
    let integer2 = digit2.many.map {
        return Int(String($0))!
    }
    s = "12345abc"
    if let v = integer2.pase(&s) { print(v) }
    print(s)
    /*输出:
     12345
     abc
     */
    

    与之前的实现相比,鉴于每次调用 self.parse 时可以顺手修改 input,所以不必再去管理每一次解析之后的剩余部分,而像 or 这样的组合算子在实现时会更具技巧性:

    extension Parser2 {
        func or(_ other: Parser2<Result>) -> Parser2<Result> {
            return Parser2<Result> { input in
                let original = input
                guard let result = self.pase(&input) else {
                    input = original
                    return other.pase(&input)
                }
                return result
            }
        }
    }
    

    由于在调用 self.parse 时会修改 input,需要先将 input 的值复制到另一个变量中储存起来,以确保在 self.parse 返回 nil 的时候,可以将 input 恢复为解析前的值。两种方案中编写参数的方式都很不错。

    更多相关内容
  • 一次函数 函数 1、变量:在一个变化过程中可以取不同数值的量。 常量:在一个变化过程中只能取同一数值的量。 2、函数:一般的,在一个变化过程中,如果有两个变量x和y,并且对于x的每一个确定 的值,y都有唯一确定...
  • 一次函数正比例的公式是什么

    千次阅读 2020-12-20 14:36:10
    满意答案VIVIAN_MAY2013.10.09采纳率:43%等级:12已帮助:9004人自变量x和因变量y有如下关系:y=kx+b (k为任意不为零实数,b为任意实数)则此时称y是x的一次函数。特别的,当b=0时,y是x的正比例函数。即:y=kx (k为...

    满意答案

    VIVIAN_MAY

    2013.10.09

    采纳率:43%    等级:12

    已帮助:9004人

    自变量x和因变量y有如下关系:

    y=kx+b (k为任意不为零实数,b为任意实数)

    则此时称y是x的一次函数。

    特别的,当b=0时,y是x的正比例函数。

    即:y=kx (k为任意不为零实数)

    定义域:自变量的取值范围,自变量的取值应使函数有意义;若与实际相反,

    一次函数的性质

    1.y的变化值与对应的x的变化值成正比例,比值为k

    即:y=kx+b(k≠0) (k为任意不为零的实数 b取任何实数)

    2.当x=0时,b为函数在y轴上的截距。

    3.k为一次函数y=kx+b的斜率,k=tg角1(角1为一次函数图象与x轴正方向夹角)

    形。取。象。交。减

    一次函数的图像及性质

    1.作法与图形:通过如下3个步骤

    (1)列表[一般取两个点,根据两点确定一条直线];

    (2)描点;

    (3)连线,可以作出一次函数的图像--一条直线。因此,作一次函数的图像只需知道2点,并连成直线即可。(通常找函数图像与x轴和y轴的交点)

    2.性质:(1)在一次函数上的任意一点P(x,y),都满足等式:y=kx+b(k≠0)。(2)一次函数与y轴交点的坐标总是(0,b),与x轴总是交于(-b/k,0)正比例函数的图像总是过原点。

    3.函数不是数,它是指某一变量过程中两个变量之间的关系。

    4.k,b与函数图像所在象限:

    y=kx时

    当k>0时,直线必通过一、三象限,y随x的增大而增大;

    当k<0时,直线必通过二、四象限,y随x的增大而减小。

    y=kx+b时:

    当 k>0,b>0, 这时此函数的图象经过一,二,三象限。

    当 k>0,b<0, 这时此函数的图象经过一,三,四象限。

    当 k<0,b<0, 这时此函数的图象经过二,三,四象限。

    当 k<0,b>0, 这时此函数的图象经过一,二,四象限。

    当b>0时,直线必通过一、二象限;

    当b<0时,直线必通过三、四象限。

    特别地,当b=0时,直线通过原点O(0,0)表示的是正比例函数的图像。

    这时,当k>0时,直线只通过一、三象限;当k<0时,直线只通过二、四象限。

    4、特殊位置关系

    当平面直角坐标系中两直线平行时,其函数解析式中K值(即一次项系数)相等

    当平面直角坐标系中两直线垂直时,其函数解析式中K值互为负倒数(即两个K值的乘积为-1)

    确定一次函数的表达式

    已知点A(x1,y1);B(x2,y2),请确定过点A、B的一次函数的表达式。

    (1)设一次函数的表达式(也叫解析式)为y=kx+b。

    (2)因为在一次函数上的任意一点P(x,y),都满足等式y=kx+b。所以可以列出2个方程:y1=kx1+b …… ① 和 y2=kx2+b …… ②

    (3)解这个二元一次方程,得到k,b的值。

    (4)最后得到一次函数的表达式。

    一次函数在生活中的应用

    1.当时间t一定,距离s是速度v的一次函数。s=vt。

    2.当水池抽水速度f一定,水池中水量g是抽水时间t的一次函数。设水池中原有水量S。g=S-ft。

    常用公式(不全,希望有人补充)

    1.求函数图像的k值:(y1-y2)/(x1-x2)

    2.求与x轴平行线段的中点:|x1-x2|/2

    3.求与y轴平行线段的中点:|y1-y2|/2

    4.求任意线段的长:√(x1-x2)^2+(y1-y2)^2 (注:根号下(x1-x2)与(y1-y2)的平方和)

    5.求两一次函数式图像交点坐标:解两函数式

    两个一次函数 y1=k1x+b1 y2=k2x+b2 令y1=y2 得k1x+b1=k2x+b2 将解得的x=x0值代回y1=k1x+b1 y2=k2x+b2 两式任一式 得到y=y0 则(x0,y0)即为 y1=k1x+b1 与 y2=k2x+b2 交点坐标

    6.求任意2点所连线段的中点坐标:[(x1+x2)/2,(y1+y2)/2]

    7.求任意2点的连线的一次函数解析式:(X-x1)/(x1-x2)=(Y-y1)/(y1-y2) (其中分母为0,则分子为0)

    k b

    + + 在一、二、三象限

    + - 在一、三、四象限

    - + 在一、二、四象限

    - - 在二、三、四象限

    8.若两条直线y1=k1x+b1‖y2=k2x+b2,那么k1=k2,b1≠b2

    9.如两条直线y1=k1x+b1⊥y2=k2x+b2,那么k1×k2=-1

    应用

    一次函数y=kx+b的性质是:(1)当k>0时,y随x的增大而增大;(2)当k<0时,y随x的增大而减小。利用一次函数的性质可解决下列问题。

    一、确定字母系数的取值范围

    例1. 已知正比例函数 ,则当m=______________时,y随x的增大而减小。

    解:根据正比例函数的定义和性质,得 且m<0,即 且 ,所以 。

    二、比较x值或y值的大小

    例2. 已知点P1(x1,y1)、P2(x2,y2)是一次函数y=3x+4的图象上的两个点,且y1>y2,则x1与x2的大小关系是( )

    A. x1>x2 B. x1

    解:根据题意,知k=3>0,且y1>y2。根据一次函数的性质“当k>0时,y随x的增大而增大”,得x1>x2。故选A。

    三、判断函数图象的位置

    例3. 一次函数y=kx+b满足kb>0,且y随x的增大而减小,则此函数的图象不经过( )

    A. 第一象限 B. 第二象限

    C. 第三象限 D. 第四象限

    解:由kb>0,知k、b同号。因为y随x的增大而减小,所以k<0。所以b<0。故一次函数y=kx+b的图象经过第二、三、四象限,不经过第一象限。故选A . 典型例题:

    例1. 一个弹簧,不挂物体时长12cm,挂上物体后会伸长,伸长的长度与所挂物体的质量成正比例.如果挂上3kg物体后,弹簧总长是13.5cm,求弹簧总长是y(cm)与所挂物体质量x(kg)之间的函数关系式.如果弹簧最大总长为23cm,求自变量x的取值范围.

    分析:此题由物理的定性问题转化为数学的定量问题,同时也是实际问题,其核心是弹簧的总长是空载长度与负载后伸长的长度之和,而自变量的取值范围则可由最大总长→最大伸长→最大质量及实际的思路来处理.

    解:由题意设所求函数为y=kx+12

    则13.5=3k+12,得k=0.5

    ∴所求函数解析式为y=0.5x+12

    由23=0.5x+12得:x=22

    ∴自变量x的取值范围是0≤x≤22

    【考点指要】

    一次函数的定义、图象和性质在中考说明中是C级知识点,特别是根据问题中的条件求函数解析式和用待定系数法求函数解析式在中考说明中是D级知识点.它常与反比例函数、二次函数及方程、方程组、不等式综合在一起,以选择题、填空题、解答题等题型出现在中考题中,大约占有8分左右.解决这类问题常用到分类讨论、数形结合、方程和转化等数学思想方法.

    例2.如果一次函数y=kx+b中x的取值范围是-2≤x≤6,相应的函数值的范围是-11≤y≤9.求此函数的的解析式。

    解:(1)若k>0,则可以列方程组 -2k+b=-11

    6k+b=9

    解得k=2.5 b=-6 ,则此时的函数关系式为y=2.5x-6

    (2)若k<0,则可以列方程组 -2k+b=9

    6k+b=-11

    解得k=-2.5 b=4,则此时的函数解析式为y=-2.5x+4

    【考点指要】

    此题主要考察了学生对函数性质的理解,若k>0,则y随x的增大而增大;若k<0,则y随x的增大而减小。

    一次函数解析式的几种类型

    ①ax+by+c=0[一般式]

    ②y=kx+b[斜截式]

    (k为直线斜率,b为直线纵截距,正比例函数b=0)

    ③y-y1=k(x-x1)[点斜式]

    (k为直线斜率,(x1,y1)为该直线所过的一个点)

    ④(y-y1)/(y2-y1)=(x-x1)/(x2-x1)[两点式]

    ((x1,y1)与(x2,y2)为直线上的两点)

    ⑤x/a-y/b=0[截距式]

    (a、b分别为直线在x、y轴上的截距)

    解析式表达局限性:

    ①所需条件较多(3个);

    ②、③不能表达没有斜率的直线(平行于x轴的直线);

    ④参数较多,计算过于烦琐;

    ⑤不能表达平行于坐标轴的直线和过圆点的直线。

    倾斜角:x轴到直线的角(直线与x轴正方向所成的角)称为直线的倾斜 角。设一直线的倾斜角为a,则该直线的斜率k=tg(a)

    形如y=kx(k为常数,且k不等于0),y就叫做x的正比例函数.

    正比例函数属于一次函数,正比例函数是一次函数的特殊形式.

    即当一次函数 y=kx+b 若b=0,则此为正比例函数.

    图像做法

    1.列表

    2.描点

    3.连线(一定要经过坐标轴的原点)

    其次,正比例函数的图像是经过原点和(1,k)[或(2,2k),(3,3k)等]两点的一条直线。

    其他:当k>0时,它的图像(除原点外)在第一、三象限,y随x的增大而增大

    当k<0时,它的图像(除原点外)在第二、四象限,y随x的增大而减小

    总结:y=kx(k不等于0)

    而以方程的角度来说,只要将正比例函数上的一个点的坐标给出,就能确定这个解析式

    若求正比例函数与一次函数,二次函数或反比例函数的交点坐标,就是将两个已知的方程联立成方程组

    求出其x,y值便可

    正比例函数在线性规划问题中体现的力量也是无穷的

    比如斜率问题就取决于K值,当K越大,则该函数图像与x轴的夹角越大,反之亦然

    还有,Y=Kx是Y=K/x 图像的对称轴.

    1)正比例:两种相关联的量,一种量变化,另一种量也随着变化,如果这两种量相对应的两个数的比值(也就是商)一定,这两种量就叫做成正比例的量,它们的关系叫做成正比例关系. ①用字母表示:如果用字母x和y表示两种相关联的量,用k表示它们的比值,(一定)正比例关系可以用以下关系式表示:

    ②正比例关系两种相关联的量的变化规律:对于比值为正数的,即y=kx(k>0),此时的y与x,同时扩大,同时缩小,比值不变.例如:汽车每小时行驶的速度一定,所行的路程和所用的时间是否成正比例?

    以上各种商都是一定的,那么被除数和除数. 所表示的两种相关联的量,成正比例关系. 注意:在判断两种相关联的量是否成正比例时应注意这两种相关联的量,虽然也是一种量,随着另一种的变化而变化,但它们相对应的两个数的比值不一定,它们就不能成正比例. 例如:一个人的年龄和它的体重,就不能成正比例关系,正方形的边长和它的面积也不成正比例关系

    32分享举报

    展开全文
  • GPS从入门到放弃(二十六) --- RTKLIB函数解析

    千次阅读 多人点赞 2020-06-07 21:57:21
    GPS从入门到放弃(二十六) — RTKLIB函数解析 为了贴合这个系列的标题“从入门到放弃”,在入门之后现在就要放弃此方向了。虽然感觉遗憾,暂时也没有办法。在此附上此系列最后篇,希望能给大家一些帮助。 此文中...

    GPS从入门到放弃(二十六) — RTKLIB函数解析

    为了贴合这个系列的标题“从入门到放弃”,在入门之后现在就要放弃此方向了。虽然感觉遗憾,暂时也没有办法。在此附上此系列最后一篇,希望能给大家一些帮助。

    此文中一些函数解析参考了 https://www.cnblogs.com/taqikema/p/8819798.html,在此表示感谢!

    rtksvrthread

    void *rtksvrthread(void *arg)
    
    • 所在文件:rtksvr.c
    • 功能说明:rtk服务线程。
    • 参数说明:无
    • 处理过程:
    1. 检查svr->state,若为0,线程结束。
    2. 从input stream 读取数据。
    3. 写入到 log stream。
    4. 若为精密星历,调用 decodefile 解码;否则调用 decoderaw 解码。
    5. 对每一个观测数据,调用 rtkpos 进行定位计算。
    6. 若定位成功,调整时间,写solution;若没成功,写solution。
    7. 若有需要,发送 nmea request 给 base/nrtk input stream。
    8. 休眠等下一个cycle。

    rtkpos

    int rtkpos(rtk_t *rtk, const obsd_t *obs, int n, const nav_t *nav)
    
    • 所在文件:rtkpos.c
    • 功能说明:根据观测数据和导航信息,计算接收机的位置、速度和钟差。
    • 参数说明:
    函数参数,4个:
    rtk_t    *rtk      IO  rtk控制结构体
    obsd_t   *obs      I   观测数据
    int      n         I   观测数据的数量
    nav_t    *nav      I   导航数据
    返回类型:
    int                O   (0:no solution,1:valid solution)
    
    • 处理过程:
    1. 设置基准站位置,记录观测值数量。
    2. 调用 pntpos 进行接收机单点定位。若为单点定位模式,输出,返回。
    3. 若为 PPP 模式,调用 pppos 进行精密单点定位,输出,返回。
    4. 若无基准站观测数据,输出,返回。
    5. 若为移动基站模式,调用 pntpos 进行基站单点定位,并加以时间同步;否则只计算一下差分时间。
    6. 调用 relpos 进行相对基站的接收机定位,输出,返回。

    pntpos

    int pntpos (const obsd_t *obs, int n, const nav_t *nav, const prcopt_t *opt,
                sol_t *sol, double *azel, ssat_t *ssat, char *msg)
    
    • 所在文件:pntpos.c
    • 功能说明:依靠伪距和多普勒频移测量值来进行单点定位,给出接收机的位置、速度和钟差。
    • 参数说明:
    函数参数,8个:
    obsd_t   *obs      I   观测数据
    int      n         I   观测数据的数量
    nav_t    *nav      I   导航数据
    prcopt_t *opt      I   处理过程选项
    sol_t    *sol      IO  solution
    double   *azel     IO  方位角和俯仰角 (rad) (NULL: no output)
    ssat_t   *ssat     IO  卫星状态            (NULL: no output)
    char     *msg      O   错误消息
    返回类型:
    int                O   (1:ok,0:error)
    
    • 处理过程:
    1. 当处理选项 opt 中的模式不是单点模式时,电离层校正采用 broadcast 模型,即Klobuchar模型,对流层校正则采用 Saastamoinen 模型;相反,当其为单点模式时,对输入参数 opt 不做修改。
    2. 调用 satposs 计算卫星们位置、速度、时钟
    3. 调用 estpos 根据伪距估计接收机位置,其中会调用 valsol 进行 χ 2 \chi^2 χ2 检验和 GDOP 检验。
    4. 若3中的检验没通过,调用 raim_fde 进行接收机自主完好性监测,判决定位结果的有效性,并进行错误排除。
    5. 调用 estvel 根据多普勒频移测量值计算接收机的速度。
    6. 赋值给卫星状态结构体ssat。

    satposs

    void satposs(gtime_t teph, const obsd_t *obs, int n, const nav_t *nav,
                 int ephopt, double *rs, double *dts, double *var, int *svh)
    
    • 所在文件:ephemeris.c
    • 功能说明:按照所观测到的卫星顺序计算出每颗卫星的位置、速度、{钟差、频漂}
    • 参数说明:
    函数参数,9个:
    gtime_t  teph      I   time to select ephemeris (gpst)
    obsd_t   *obs      I   观测量数据
    int      n         I   观测量数据的数量
    nav_t    *nav      I   导航数据
    int      ephopt    I   星历选项 (EPHOPT_???)
    double   *rs       O   卫星位置和速度,长度为6*n,{x,y,z,vx,vy,vz}(ecef)(m,m/s)
    double   *dts      O   卫星钟差,长度为2*n, {bias,drift} (s|s/s)
    double   *var      O   卫星位置和钟差的协方差 (m^2)
    int      *svh      O   卫星健康标志 (-1:correction not available)
    返回类型:
    • 处理过程:
    1. 大循环针对每一条观测数据,按顺序处理。
    2. 首先初始化,将对当前观测数据的 rs、dts、var和svh数组的元素置 0。
    3. 通过判断某一频率下信号的伪距是否为 0,来得到此时所用的频率个数。注意,频率个数不能大于 NFREQ(默认为 3)。
    4. 用数据接收时刻减去伪距信号传播时间,得到卫星信号的发射时刻。
    5. 调用 ephclk 函数,由广播星历计算出当前观测卫星与 GPS 时间的钟差 dt。注意,此时的钟差是没有考虑相对论效应和 TGD 的。
    6. 用 4 中的信号发射时刻减去 5 中的钟差 dt,得到 GPS 时间下的卫星信号发射时刻。
    7. 调用 satpos 函数,计算信号发射时刻卫星的位置(ecef,m)、速度(ecef,m/s)、钟差((s|s/s))。注意,这里计算出的钟差是考虑了相对论效应的了,只是还没有考虑 TGD。
    8. 如果没有精密星历,则用广播星历的钟差替代。
    • 注意:
    1. 4中的公式原理:由 ρ = c ( t u − t s ) \rho = c(t_u-t_s) ρ=c(tuts) 可得 t s = t u − ρ / c t_s=t_u-\rho/c ts=tuρ/c

    ephclk

    int ephclk(gtime_t time, gtime_t teph, int sat, const nav_t *nav, double *dts)
    
    • 所在文件:ephemeris.c
    • 功能说明:通过广播星历来确定卫星钟差
    • 参数说明:
    函数参数,5个:
    gtime_t  time      I   信号发射时刻
    gtime_t  teph      I   用于选择星历的时刻 (gpst)
    int      sat       I   卫星号 (1-MAXSAT)
    nav_t    *nav      I   导航数据
    double   *dts      O   卫星钟差,长度为2*n, {bias,drift} (s|s/s)
    返回类型:
    int                O     (1:ok,0:error)
    
    • 处理过程:
    1. 调用 satsys 函数,根据卫星编号确定该卫星所属的导航系统和该卫星在该系统中的 PRN编号。
    2. 对于 GPS导航系统,调用 seleph 函数来选择最接近 teph 的那个星历。
    3. 调用 eph2clk 函数,通过广播星历和信号发射时间计算出卫星钟差。
    • 注意:
    1. 此时计算出的卫星钟差是没有考虑相对论效应和 TGD的。

    eph2clk

    int eph2clk (gtime_t time, const eph_t *eph)
    
    • 所在文件:ephemeris.c
    • 功能说明:根据信号发射时间和广播星历,计算卫星钟差
    • 参数说明:
    函数参数,2个
    gtime_t  time    I   time by satellite clock (gpst)
    eph_t    *eph    I   broadcast ephemeris
    返回类型:
    double    satellite clock bias (s) without relativeity correction
    
    • 处理过程:
      1. 计算与星历参考时间的偏差 dt = t-toc。
      2. 利用二项式校正计算出卫星钟差,从 dt中减去这部分,然后再进行一次上述操作,得到最终的 dt。(这一部分不知道是为什么?)
      3. 使用二项式校正得到最终的钟差。

    satpos

    int satpos(gtime_t time, gtime_t teph, int sat, int ephopt, const nav_t *nav,
               double *rs, double *dts, double *var, int *svh)
    
    • 所在文件:ephemeris.c
    • 功能说明:计算信号发射时刻卫星的 P(ecef,m)、V(ecef,m/s)、C((s|s/s))
    • 参数说明:
    函数参数,9个:
    gtime_t time      I   time (gpst)
    gtime_t teph      I   用于选择星历的时刻 (gpst)
    int     sat       I   卫星号
    nav_t   *nav      I   导航数据
    int     ephopt    I   星历选项 (EPHOPT_???)
    double  *rs       O   卫星位置和速度,长度为6*n,{x,y,z,vx,vy,vz}(ecef)(m,m/s)
    double  *dts      O   卫星钟差,长度为2*n, {bias,drift} (s|s/s)
    double  *var      O   卫星位置和钟差的协方差 (m^2)
    int     *svh      O   卫星健康标志 (-1:correction not available)
    返回类型:
    int               O     (1:ok,0:error)
    
    • 处理过程:
    1. 根据不同的星历选项的值调用不同的处理函数,如果星历选项是 EPHOPT_BRDC,调用 ephpos 函数,根据广播星历计算出算信号发射时刻卫星的 P、V、C。如果星历选项是 EPHOPT_PREC,调用 pephpos 函数,根据精密星历和时钟计算信号发射时刻卫星的 P、V、C。
    • 注意:
    1. 此时计算出的卫星钟差考虑了相对论,还没有考虑 TGD。

    ephpos

    int ephpos(gtime_t time, gtime_t teph, int sat, const nav_t *nav,
               int iode, double *rs, double *dts, double *var, int *svh)
    
    • 所在文件:ephemeris.c
    • 功能说明:根据广播星历计算出算信号发射时刻卫星的 P、V、C
    • 参数说明:
    函数参数,9个:
    gtime_t  time      I   transmission time by satellite clock
    gtime_t  teph      I   time to select ephemeris (gpst)
    int      sat       I   卫星号 (1-MAXSAT)
    nav_t    *nav      I   导航数据
    int      iode      I   星历数据期号
    double   *rs       O   卫星位置和速度,长度为6*n,{x,y,z,vx,vy,vz}(ecef)(m,m/s)
    double   *dts      O   卫星钟差,长度为2*n, {bias,drift} (s|s/s)
    double   *var      O   卫星位置和钟差的协方差 (m^2)
    int      *svh      O   卫星健康标志 (-1:correction not available)
    返回类型:
    int                O    (1:ok,0:error)
    
    • 处理过程:
    1. 调用 satsys 函数,确定该卫星所属的导航系统。
    2. 调用 seleph 函数来选择广播星历。
    3. 根据选中的广播星历,调用 eph2pos 函数来计算信号发射时刻卫星的 位置、钟差和相应结果的误差。
    4. 在信号发射时刻的基础上给定一个微小的时间间隔,再次计算新时刻的 P、V、C。与3结合,通过扰动法计算出卫星的速度和频漂。
    • 注意:
    1. 这里是使用扰动法计算卫星的速度和频漂,并没有使用那些位置和钟差公式对时间求导的结果。
    2. 由于是调用的 eph2pos 函数,计算得到的钟差考虑了相对论效应,没有考虑 TGD。

    eph2pos

    void eph2pos(gtime_t time, const eph_t *eph, double *rs, double *dts, double *var)
    
    • 所在文件:ephemeris.c
    • 功能说明:根据广播星历计算出算信号发射时刻卫星的位置和钟差
    • 参数说明:
    函数参数,5个
    gtime_t  time      I   transmission time by satellite clock
    eph_t    *eph      I   广播星历
    double   *rs       O   卫星位置和速度,长度为6*n,{x,y,z,vx,vy,vz}(ecef)(m,m/s)
    double   *dts      O   卫星钟差,长度为2*n, {bias,drift} (s|s/s)
    double   *var      O   卫星位置和钟差的协方差 (m^2)
    返回类型:无
    
    • 处理过程:
    1. 通过卫星轨道半长轴 A 判断星历是否有效,无效则返回。
    2. 计算规化时间 tk。
    3. 根据不同卫星系统设置相应的地球引力常数 mu 和 地球自转角速度 omge。
    4. 计算平近点角 M。
    5. 用牛顿迭代法来计算偏近点角 E。参考 RTKLIB manual P145 (E.4.19)。
    6. 计算升交点角距 u。
    7. 计算摄动校正后的升交点角距 u、卫星矢径长度 r、轨道倾角 i。
    8. 计算升交点赤经 O。
    9. 计算卫星位置存入 rs 中。
    10. 计算卫星钟差,此处考虑了相对论效应,没有考虑 TGD,也没有计算钟漂。
    11. 用 URA 值来标定误差方差,具体对应关系可在 ICD-GPS-200H 20.3.3.3.1.3 SV Accuracy 中找到。

    estpos

    int estpos(const obsd_t *obs, int n, const double *rs, const double *dts,
               const double *vare, const int *svh, const nav_t *nav,
               const prcopt_t *opt, sol_t *sol, double *azel, int *vsat,
               double *resp, char *msg)
    
    • 所在文件:pntpos.c
    • 功能说明:通过伪距实现绝对定位,计算出接收机的位置和钟差,顺带返回实现定位后每颗卫星的{方位角、仰角}、定位时有效性、定位后伪距残差。
    • 参数说明:
    函数参数,13个:
    obsd_t   *obs      I   观测量数据
    int      n         I   观测量数据的数量
    double   *rs       I   卫星位置和速度,长度为6*n,{x,y,z,vx,vy,vz}(ecef)(m,m/s)
    double   *dts      I   卫星钟差,长度为2*n, {bias,drift} (s|s/s)
    double   *vare     I   卫星位置和钟差的协方差 (m^2)
    int      *svh      I   卫星健康标志 (-1:correction not available)
    nav_t    *nav      I   导航数据
    prcopt_t *opt      I   处理过程选项
    sol_t    *sol      IO  solution
    double   *azel     IO  方位角和俯仰角 (rad)
    int      *vsat     IO  卫星在定位时是否有效
    double   *resp     IO  定位后伪距残差 (P-(r+c*dtr-c*dts+I+T))
    char     *msg      O   错误消息
    返回类型:
    int                O   1表示成功,0表示出错
    
    • 处理过程:
    1. 初始化:将 sol->rr 的前 3 项位置信息(ECEF)赋值给 x 数组。
    2. 开始迭代定位计算,首先调用 rescode 函数,计算当前迭代的伪距残差 v v v、几何矩阵 H H H、伪距残差的方差 var、所有观测卫星的方位角和仰角 azel、定位时有效性 vsat、定位后伪距残差 resp、参与定位的卫星个数 ns 和方程个数 nv。
    3. 确定方程组中方程的个数要大于未知数的个数。
    4. 以伪距残差的标准差的倒数作为权重,对 H H H v v v 分别左乘权重对角阵,得到加权之后的 H H H v v v
    5. 调用 lsq 函数,根据 d x = ( H H T ) − 1 H v dx=(HH^T)^{-1}Hv dx=(HHT)1Hv Q = ( H H T ) − 1 Q=(HH^T)^{-1} Q=(HHT)1,得到当前 x 的修改量 dx 和定位误差协方差矩阵中的权系数阵 Q Q Q
    6. 将 5 中求得的 dx 加入到当前 x 值中,得到更新之后的 x 值。
    7. 如果 5 中求得的修改量 dx 小于截断因子(目前是 1 0 − 4 10^{-4} 104),则将 6 中得到的 x 值作为最终的定位结果,对 sol 的相应参数赋值,之后再调用 valsol 函数确认当前解是否符合要求(伪距残差小于某个 χ 2 \chi^2 χ2值和 GDOP 小于某个门限值,参考 RTKLIB Manual P162, E.6.33, E.6.34)。否则,进行下一次循环。
    8. 如果超过了规定的循环次数,则输出发散信息后,返回 0。
    • 注意:
    1. 关于第 1步,如果是第一次定位,即输入的 sol 为空,则 x 初值为 0;如果之前有过定位,则通过 1 中操作可以将上一历元的定位值作为该历元定位的初始值。
    2. 关于定位方程,RTKLIB中的 H H H 相当于定位方程解算中的 G T G^T GT,即 H = G T H=G^T H=GT
    3. 关于加权最小二乘,这里的权重值是对角阵,这是建立在假设不同测量值的误差之间是彼此独立的基础上的。大部分资料上这里都是把权重矩阵 W 保留到方程的解的表达式当中,而这里是直接对 H 和 v 分别左乘权重对角阵,得到加权之后的 H 和 v,其表示形式像是没有加权一样。
    4. 解方程时的 dtr 单位是 m,是乘以了光速之后的,解出结果后赋给 sol->dtr 时再除以光速。
    5. sol->time 中存储的是减去接收机钟差后的信号观测时间。

    rescode

    int rescode(int iter, const obsd_t *obs, int n, const double *rs,
                const double *dts, const double *vare, const int *svh,
                const nav_t *nav, const double *x, const prcopt_t *opt,
                double *v, double *H, double *var, double *azel, int *vsat,
                double *resp, int *ns)
    
    • 所在文件:pntpos.c
    • 功能说明:计算当前迭代的伪距残差 v、几何矩阵 H、伪距残差的方差 var、所有观测卫星的方位角和仰角 azel、定位时有效性 vsat、定位后伪距残差 resp、参与定位的卫星个数 ns 和方程个数 nv。
    • 参数说明:
    函数参数,17int      iter      I   迭代次数
    obsd_t   *obs      I   观测量数据
    int      n         I   观测量数据的数量
    double   *rs       I   卫星位置和速度,长度为6*n,{x,y,z,vx,vy,vz}(ecef)(m,m/s)
    double   *dts      I   卫星钟差,长度为2*n, {bias,drift} (s|s/s)
    double   *vare     I   卫星位置和钟差的协方差 (m^2)
    int      *svh      I   卫星健康标志 (-1:correction not available)
    nav_t    *nav      I   导航数据
    double   *x        I   本次迭代开始之前的定位值
    prcopt_t *opt      I   处理过程选项
    double   *v        O   定位方程的右端部分,伪距残差
    double   *H        O   定位方程中的几何矩阵
    double   *var      O   参与定位的伪距残差的方差
    double   *azel     O   对于当前定位值,所有观测卫星的 {方位角、高度角} (2*n)
    int      *vsat     O   所有观测卫星在当前定位时是否有效 (1*n)
    double   *resp     O   所有观测卫星的伪距残差,(P-(r+c*dtr-c*dts+I+T)) (1*n)
    int      *ns       O   参与定位的卫星的个数
    返回类型:
    int                O   定位方程组的方程个数
    
    • 处理过程:
    1. 将之前得到的定位解信息赋值给 rr 和 dtr 数组,以进行关于当前解的伪距残差的相关计算。
    2. 调用 ecef2pos 函数,将上一步中得到的位置信息由 ECEF 转化为大地坐标系。
    3. 将 vsat、azel 和 resp 数组置 0,因为在前后两次定位结果中,每颗卫星的上述信息都会发生变化。
    4. 调用 satsys 函数,验证卫星编号是否合理及其所属的导航系统。
    5. 检测当前观测卫星是否和下一个相邻数据重复;重复则不处理这一条,去处理下一条。
    6. 调用 geodist 函数,计算卫星和当前接收机位置之间的几何距离 r r r 和接收机到卫星方向的观测矢量。然后检验几何距离是否 >0。此函数中会进行地球自转影响的校正(Sagnac效应)。
    7. 调用 satazel 函数,计算在接收机位置处的站心坐标系中卫星的方位角和仰角;若仰角低于截断值,不处理此数据。
    8. 调用 prange 函数,得到经过DCB校正后的伪距值 ρ \rho ρ
    9. 可以在处理选项中事先指定定位时排除哪些导航系统或卫星,这是通过调用 satexclude 函数完成的。
    10. 调用 ionocorr 函数,计算电离层延时 I I I (m)。所得的电离层延时是建立在 L1 信号上的,当使用其它频率信号时,依据所用信号频组中第一个频率的波长与 L1 波长的关系,对上一步得到的电离层延时进行修正。
    11. 调用 tropcorr 函数,计算对流层延时 T T T (m)。
    12. ρ − ( r + d t r − c ⋅ d t s + I + T ) \rho-(r+dt_r-c·dt_s+I+T) ρ(r+dtrcdts+I+T),计算出此时的伪距残差。
    13. 组装几何矩阵 H H H,前 3 行为 6 中计算得到的视线单位向量的反向,第 4 行为 1,其它行为 0。
    14. 处理不同系统(GPS、GLO、GAL、CMP)之间的时间偏差,修改矩阵 H H H
    15. 将参与定位的卫星的定位有效性标志设为 1,给当前卫星的伪距残差赋值,参与定位的卫星个数 ns 加 1。
    16. 调用 varerr 函数,计算此时的导航系统误差,然后累加计算用户测距误差(URE)。
    17. 为了防止不满秩的情况,把矩阵 H H H 补满秩了。
    • 注意:
    1. 输入参数x的size为7*1,前3个是本次迭代开始之前的定位值,第4个是钟差,后三个分别是gps系统与glonass、galileo、北斗系统的钟差。但是从代码看,后三个没有用上。
    2. 返回值 v和 resp的主要区别在于长度不一致, v是需要参与定位方程组的解算的,维度为 nv*1;而 resp仅表示所有观测卫星的伪距残余,维度为 n*1,对于没有参与定位的卫星,该值为 0。

    raim_fde

    int raim_fde(const obsd_t *obs, int n, const double *rs,
                 const double *dts, const double *vare, const int *svh,
                 const nav_t *nav, const prcopt_t *opt, sol_t *sol,
                 double *azel, int *vsat, double *resp, char *msg)
    
    • 所在文件:pntpos.c
    • 功能说明:使用伪距残差判决法对计算得到的定位结果进行接收机自主正直性检测(RAIM),每次舍弃一颗卫星测量值,用剩余的值组成一组进行定位运算,选择定位后伪距残差最小的一组作为最终结果。这样如果只有一个异常观测值的话,这个错误可以被排除掉;有两个或以上错误则排除不了。注意这里只会在对定位结果有贡献的卫星数据进行检测。
    • 参数说明:
    函数参数,13个:
    obsd_t   *obs      I   观测数据
    int      n         I   观测数据的数量
    double   *rs       I   卫星位置和速度,长度为6*n,{x,y,z,vx,vy,vz}(ecef)(m,m/s)
    double   *dts      I   卫星钟差,长度为2*n, {bias,drift} (s|s/s)
    double   *vare     I   卫星位置和钟差的协方差 (m^2)
    int      *svh      I   卫星健康标志 (-1:correction not available)
    nav_t    *nav      I   导航数据
    prcopt_t *opt      I   处理过程选项
    sol_t    *sol      IO  solution
    double   *azel     IO  方位角和俯仰角 (rad)
    int      *vsat     IO  卫星在定位时是否有效
    double   *resp     IO  定位后伪距残差 (P-(r+c*dtr-c*dts+I+T))
    char     *msg      O   错误消息
    返回类型:
    int                O   (1:ok,0:error)
    
    • 处理过程:
    1. 大循环是每次舍弃第 i 颗卫星。
    2. 舍弃第 i 颗卫星后,将剩下卫星的数据复制到一起,调用 estpos 函数计算使用剩下卫星进行定位的定位值。
    3. 累加使用当前卫星实现定位后的伪距残差平方和与可用卫星数目,如果 nvsat<5,则说明当前卫星数目过少,无法进行 RAIM_FDE 操作。
    4. 计算伪距残差平方和的标准差,如果小于 rms,则说明当前定位结果更合理,将 stat 置为 1,重新更新 sol、azel、vsat(当前被舍弃的卫星,此值置为0)、resp等值,并将当前的 rms_e更新到 rms 中。
    5. 继续弃用下一颗卫星,重复 2-4 操作。总而言之,将同样是弃用一颗卫星条件下,伪距残差标准平均值最小的组合所得的结果作为最终的结果输出。
    6. 如果 stat不为 0,则说明在弃用卫星的前提下有更好的解出现,输出信息,指出弃用了哪颗卫星。
    • 注意:
    1. 源码中有很多关于 i、j、k的循环。其中,i表示最外面的大循环,每次将将第 i颗卫星舍弃不用,这是通过 if (j==i) continue实现的;j表示剩余使用的卫星的循环,每次进行相应数据的赋值;k表示参与定位的卫星的循环,与 j一起使用。

    estvel

    void estvel(const obsd_t *obs, int n, const double *rs, const double *dts,
                const nav_t *nav, const prcopt_t *opt, sol_t *sol,
                const double *azel, const int *vsat)
    
    • 所在文件:pntpos.c
    • 功能说明:依靠多普勒频移测量值计算接收机的速度,用牛顿迭代法。
    • 参数说明:
    函数参数,9个:
    obsd_t   *obs      I   观测数据
    int      n         I   观测数据的数量
    double   *rs       I   卫星位置和速度,长度为6*n,{x,y,z,vx,vy,vz}(ecef)(m,m/s)
    double   *dts      I   卫星钟差,长度为2*n, {bias,drift} (s|s/s)
    nav_t    *nav      I   导航数据
    prcopt_t *opt      I   处理过程选项
    sol_t    *sol      IO  solution
    double   *azel     IO  方位角和俯仰角 (rad)
    int      *vsat     IO  卫星在定位时是否有效
    返回类型:
    int                O     (1:ok,0:error)
    
    • 处理过程:
    1. 在最大迭代次数限制内,调用 resdop,计算定速方程组左边的几何矩阵和右端的速度残余,返回定速时所使用的卫星数目。
    2. 调用最小二乘法 lsq 函数,解出{速度、频漂}的步长 dx,累加到 x 中。
    3. 检查当前计算出的步长的绝对值是否小于 1E-6。是,则说明当前解已经很接近真实值了,将接收机三个方向上的速度存入到 sol->rr 中;否,则进行下一次循环。
    • 注意:
    1. 最终向 sol_t 类型存储定速解时,并没有存储所计算出的接收器时钟频漂。
    2. 这里不像定位时,初始值可能为上一历元的位置(从 sol 中读取初始值),这里定速的初始值直接给定为 0.

    resdop

    int resdop(const obsd_t *obs, int n, const double *rs, const double *dts,
               const nav_t *nav, const double *rr, const double *x,
               const double *azel, const int *vsat, double *v, double *H)
    
    • 所在文件:pntpos.c
    • 功能说明:计算定速方程组左边的几何矩阵和右端的速度残余,返回定速时所使用的卫星数目
    • 参数说明:
    函数参数,11个:
    obsd_t   *obs      I   观测数据
    int      n         I   观测数据的数量
    double   *rs       I   卫星位置和速度,长度为6*n,{x,y,z,vx,vy,vz}(ecef)(m,m/s)
    double   *dts      I   卫星钟差,长度为2*n, {bias,drift} (s|s/s)
    nav_t    *nav      I   导航数据
    double   *rr       I   接收机位置和速度,长度为6{x,y,z,vx,vy,vz}(ecef)(m,m/s)
    double   *x        I   本次迭代开始之前的定速值,长度为4{vx,vy,vz,drift}
    double   *azel     IO  方位角和俯仰角 (rad)
    int      *vsat     I   卫星在定速时是否有效
    double   *v        O   定速方程的右端部分,速度残差
    double   *H        O   定速方程中的几何矩阵
    返回类型:
    int                O   定速时所使用的卫星数目
    
    • 处理过程:
    1. 调用 ecef2pos 函数,将接收机位置由 ECEF 转换为大地坐标系。
    2. 调用 xyz2enu 函数,计算此时的坐标转换矩阵。
    3. 去除在定速时不可用的卫星。
    4. 计算当前接收机位置下 ENU中的视向量,然后转换得到 ECEF 中视向量的值。
    5. 计算 ECEF 中卫星相对于接收机的速度。
    6. 计算考虑了地球自转的用户和卫星之间的几何距离变化率,校正公式见 RTKLIB manual P159 (F.6.29),此公式可由 P140 (E.3.8b) 对时间求导得到。
    7. 根据公式计算出定速方程组右端项的多普勒残差,构建左端项的几何矩阵,最后再将观测方程数增 1.
    • 注意:
    1. 这里与定位不同,构建几何矩阵时,就只有 4个未知数,而定位时是有 NX个。
    2. 多普勒定速方程中几何矩阵 G 与定位方程中的一样。
    3. 第7步中计算多普勒残差 b 与很多资料不同,因为 estvel 中用的是牛顿迭代法,其最小二乘法并不是直接求解x,而是求解dx,再加到x上。下面式子可以参考,其中 x r 、 y r x_r、y_r xryr 分别为接收机位置的x,y分量, x s 、 y s x_s、y_s xsys 分别为卫星s位置的x,y分量, r s r_s rs 为接收机到卫星s的距离。注意在计算 G 的时候忽略了地球自转校正项。
      x = [ v ,   c ⋅ δ t ˙ ] T = [ v x ,   v y ,   v z ,   c ⋅ δ t ˙ ] T \boldsymbol{x} = [\boldsymbol{v},\:c\cdot\dot{\delta_t}]^T=[v_x,\:v_y,\:v_z,\:c\cdot\dot{\delta_t}]^T x=[v,cδt˙]T=[vx,vy,vz,cδt˙]T

    r s = ( v s − v ) ⋅ e s + ω e c ( v s , y x r + y s v x − v s , x y r − x s v y ) r_{s} = (\boldsymbol{v_s}-\boldsymbol{v})\cdot\boldsymbol{e_s} + \frac{\omega_e}{c}(v_{s,y}x_r+y_sv_x-v_{s,x}y_r-x_sv_y) rs=(vsv)es+cωe(vs,yxr+ysvxvs,xyrxsvy)

    y = − λ f d = h ( x ) = r s + c ⋅ δ t ˙ − c ⋅ δ ˙ t , s y = -\lambda f_d = h(x) = r_{s} + c\cdot\dot{\delta_t} - c\cdot\dot{\delta}_{t,s} y=λfd=h(x)=rs+cδt˙cδ˙t,s

    G = ∂ h ∂ x = [ − e 1 , k 1 − e 2 , k 1 − e 3 , k 1 − e 4 , k 1 ] \boldsymbol{G} = \frac{\partial h}{\partial x} = \left[ \begin{array}{cc} -\boldsymbol{e}_{1,k} & 1\\ -\boldsymbol{e}_{2,k} & 1\\ -\boldsymbol{e}_{3,k} & 1\\ -\boldsymbol{e}_{4,k} & 1 \end{array} \right] G=xh=e1,ke2,ke3,ke4,k1111

    b = y − h ( x ) = − λ f d − ( r s + c ⋅ δ t ˙ − c ⋅ δ ˙ t , s ) b = y-h(x) = -\lambda f_d - (r_{s} + c\cdot\dot{\delta_t} - c\cdot\dot{\delta}_{t,s}) b=yh(x)=λfd(rs+cδt˙cδ˙t,s)

    pppos

    void pppos(rtk_t *rtk, const obsd_t *obs, int n, const nav_t *nav)
    
    • 所在文件:ppp.c
    • 功能说明:精确点定位
    • 参数说明:
    函数参数,4个:
    rtk_t    *rtk      IO  rtk控制结构体
    obsd_t   *obs      I   观测数据
    int      n         I   观测数据的数量
    nav_t    *nav      I   导航数据
    返回类型:
    • 处理过程:
    1. 调用 udstate_ppp 更新状态值 rtk->x 及其误差协方差 rtk->P。
    2. 调用 satposs 计算卫星位置和钟差。
    3. 若有需要排除观测的卫星,调用 testeclipse 进行排除。
    4. 根据设置的滤波迭代次数,循环调用 res_ppp 计算载波相位和伪距残差 v 和观测矩阵 H,以及测量误差的协方差 R,并将残差值存入 rtk->ssat[sat-1].resc 和 rtk->ssat[sat-1].resp 中。然后调用 filter 进行 Kalman 滤波运算。
    5. 4 中循环结束后,再次调用 res_ppp,更新残差值,并将残差值存入 rtk->ssat[sat-1].resc 和 rtk->ssat[sat-1].resp 中。
    6. 对某些周整模糊度计算模式,调用 pppamb 进行周整模糊度解算。
    7. 更新 solution 状态。
    • 注意:
    1. 状态变量包含接收机位置、接收机速度、接收机钟差、[对流层参数]、每颗卫星的载波偏移。(参考 RTKLIB Manual P177 E.8.16)。其中载波偏移包含周整模糊度以及小数部分,可参考 RTKLIB Manual P139 E.3.5。
    2. 过程5中的目的是在过程6中的某些周整模糊度计算中会用上,以及一些输出中可能会用上。

    udstate_ppp

    void udstate_ppp(rtk_t *rtk, const obsd_t *obs, int n, const nav_t *nav)
    
    • 所在文件:ppp.c
    • 功能说明:更新状态值 rtk->x。
    • 参数说明:
    函数参数,4个:
    rtk_t    *rtk      IO  rtk控制结构体
    obsd_t   *obs      I   观测数据
    int      n         I   观测数据的数量
    nav_t    *nav      I   导航数据
    返回类型:
    • 处理过程:
    1. 调用 udpos_ppp 根据不同模式初始化状态 rtk->x 中的位置值。
    2. 调用 udclk_ppp 初始化状态 rtk->x 中的钟差值(6个,因有6个系统)。
    3. 对某些对流层模型,初始化状态 rtk->x 中的对流层参数。
    4. 调用 udbias_ppp 更新载波相位偏移状态值以及其误差协方差。
    • 注意:
    1. 状态变量包含接收机位置、接收机速度、接收机钟差、[对流层参数]、每颗卫星的载波偏移。(参考 RTKLIB Manual P177 E.8.16)。其中载波偏移包含周整模糊度以及小数部分,可参考 RTKLIB Manual P139 E.3.5。
    2. 处理过程 1 的 udpos_ppp 中,PPP-static 模式只用 rtk->sol.rr 初始化一次状态 rtk->x,而 PPP-Kinematic 模式每次都会用 rtk->sol.rr 重新初始化 rtk->x。而 rtk->sol.rr 的值来源于单点定位。
    3. 处理过程 1 的 udpos_ppp 中,PPP-Fixed 模式只用于残差分析,不用于定位。

    udbias_ppp

    void udbias_ppp(rtk_t *rtk, const obsd_t *obs, int n, const nav_t *nav)
    
    • 所在文件:ppp.c
    • 功能说明:从观测值 obs 经过计算得到偏移值存入状态 rtk->x 中,若有周跳需要更新状态 rtk->x。更新偏移值的误差协方差到 rtk->P。
    • 参数说明:
    函数参数,4个:
    rtk_t    *rtk      IO  rtk控制结构体
    obsd_t   *obs      I   观测数据
    int      n         I   观测数据的数量
    nav_t    *nav      I   导航数据
    返回类型:
    • 处理过程:
    1. 调用 detslp_ll 通过 LLI 检查是否有周跳。
    2. 调用 detslp_gf 通过 geometry-free phase jump 检查是否有周跳。
    3. 若观测中断需要 reset 载波偏移值。
    4. 接收机位置转换到大地坐标系。
    5. 对每一组观测数据,调用 corrmeas 计算观测值 meas,是否有周跳,然后计算出偏移值 bias。
    6. 若载波和伪距跳变太大,为了保持一致性,需要进行校正。
    7. 对每一组观测值更新偏移值的误差协方差到 rtk->P。若有周跳,或者 rtk->x 状态还未初始化,则用偏移值 bias 重置 rtk->x。

    res_ppp

    int res_ppp(int iter, const obsd_t *obs, int n, const double *rs,
                const double *dts, const double *vare, const int *svh,
                const nav_t *nav, const double *x, rtk_t *rtk, double *v,
                double *H, double *R, double *azel)
    
    • 所在文件:ppp.c
    • 功能说明:计算载波相位和伪距残差 v 和观测矩阵 H,以及测量误差的协方差 R。并将残差值存入 rtk->ssat[sat-1].resc 和 rtk->ssat[sat-1].resp 中。
    • 参数说明:
    函数参数:14int     iter    I   迭代次数(该函数中没有用到)   
    obsd_t  *obs    I   观测数据
    int      n      I   观测数据的数量
    double  *rs     I   卫星位置和速度,长度为6*n,{x,y,z,vx,vy,vz}(ecef)(m,m/s)
    double  *dts    I   卫星钟差,长度为2*n, {bias,drift} (s|s/s)
    double  *vare   I   卫星位置和钟差的协方差 (m^2)
    int     *svh    I   卫星健康标志 (-1:correction not available)
    nav_t   *nav    I   导航数据
    double  *x      I   状态变量
    rtk_t   *rtk    IO  rtk控制结构体
    double  *v      O   实际观测量与预测观测量的残差 (2n个,因每个观测数据有phase和code两个观测值)
    double  *H      O   观测矩阵
    double  *R      O   测量误差的协方差
    double  *azel   O   方位角和俯仰角 (rad)
    返回类型:
    int             O   残差个数,<=0表示失败
    
    • 处理过程
    1. 接收机位置传给 rr。
    2. 若需要地球潮校正,调用 tidedisp 对 rr 进行校正。地球潮包含固体潮、极潮和海潮负荷。
    3. rr 转换坐标系到大地坐标系 pos。
    4. 大循环,对每一个观测量
    5. 计算卫星到接收机的几何距离 r,计算仰角,若仰角低于阈值,排除此观测量。
    6. 若设置有卫星需要排除的,排除掉。
    7. 根据对流层模型设置,计算对流层延时校正值 dtrp。
    8. 调用 satantpcv 根据卫星天线模型计算校正值 dants。
    9. 调用 antmodel 根据接收机天线模型计算校正值 dantr。
    10. 调用 windupcorr 计算相位缠绕校正值存入 rtk->ssat[sat-1].phw 中。
    11. 调用 corrmeas 计算经过电离层和天线相位校正后的测量值 meas(包含相位和伪距两个值),把 8~10 步中的计算值都合并到 meas 中了。
    12. 对几何距离 r 进行卫星钟差和对流层校正。
    13. 构造残差 v 和观测矩阵 H。v 由经过电离层和天线相位校正后的测量值 meas 减去经过卫星钟差和对流层校正后的 r,再减去接收机钟差(若 meas 为载波,再减去载波偏移)得到。 H 参考 RTKLIB Manual P177 E.8.21。并将残差值存入 rtk->ssat[sat-1].resc 和 rtk->ssat[sat-1].resp 中。
    14. 重复 5~13 直到大循环结束。
    15. 计算测量误差的协方差 R。
    • 注意:
    1. 状态变量包含接收机位置、接收机速度、接收机钟差、[对流层参数]、载波偏移。(参考 RTKLIB Manual P177 E.8.16)。其中载波偏移包含周整模糊度以及小数部分,可参考 RTKLIB Manual P139 E.3.5。

    satantpcv

    void satantpcv(const double *rs, const double *rr, const pcv_t *pcv,
                    double *dant)
    
    • 所在文件:ppp.c
    • 功能说明:根据模型计算卫星天线偏移校正值dant。
    • 参数说明:
    函数参数:4double   *rs     I   卫星位置和速度,长度为6*n,{x,y,z,vx,vy,vz}(ecef)(m,m/s)
    double   *rr     I   接收机位置和速度,长度为6*n,{x,y,z,vx,vy,vz}(ecef)(m,m/s)
    pcv_t    *pcv    I   天线相位中心参数结构体
    double   *dant   O   卫星天线校正值
    返回类型:无
    
    • 处理过程:
    1. 根据卫星位置和接收机位置计算天底角。
    2. 调用 antmodel_s 计算卫星天线偏移校正值 dant。
    • 注意:
    1. antmodel_s 中的计算只是对 phase center variation 进行了简单的插值,并没有考虑 phase center offset。这里存疑!?
    2. 类似的对接收机天线偏移进行校正计算的 antmodel 函数中则不仅考虑了phase center variation,也同时考虑了phase center offset。

    antmodel

    void antmodel(const pcv_t *pcv, const double *del, const double *azel,
                  int opt, double *dant)
    
    • 所在文件:rtkcmn.c
    • 功能说明:根据模型计算接收机天线偏移校正值dant。
    • 参数说明:
    函数参数:5个
    pcv_t    *pcv    I   天线相位中心参数结构体
    double   *del    I   相对天线参考点偏移值
    double   *azel   I   方位角和俯仰角
    int       opt    I   选项(非0则需要考虑pcv)
    double   *dant   O   接收机天线校正值
    返回类型:无
    
    • 处理过程:
    1. 根据方位角和俯仰角计算单位观测矢量。
    2. 对每一个频率,先计算偏移,再根据opt值看是否要加上pcv的影响,最后得出dant。

    windupcorr

    void windupcorr(gtime_t time, const double *rs, const double *rr,
                    double *phw)
    
    • 所在文件:rtkcmn.c
    • 功能说明:根据模型计算相位缠绕校正值 phw。
    • 参数说明:
    函数参数:4个
    gtime_t time     I   time (GPST)
    double  *rs      I   卫星位置 (ecef) {x,y,z} (m)
    double  *rr      I   接收机位置 (ecef) {x,y,z} (m)
    double  *phw     IO  相位缠绕校正值 (cycle)
    返回类型:无
    
    • 处理过程:
    1. 调用 sunmoonpos 获取太阳的位置。
    2. 计算卫星到接收机的单位矢量 ek。
    3. 计算卫星天线坐标系的三个轴的单位矢量 exs, eys, ezs。
    4. 计算站点坐标系的三个轴的单位矢量 exr, eyr。
    5. 计算有效偶极 ds 和 dr。
    6. 计算相位缠绕校正值 phw。
    • 注意:
    1. 使用这个函数时需要传入之前的相位缠绕校正值,因为此函数假设校正值不会跳变超过0.5个载波周期。

    corrmeas

    int corrmeas(const obsd_t *obs, const nav_t *nav, const double *pos,
                const double *azel, const prcopt_t *opt,
                const double *dantr, const double *dants, double phw,
                double *meas, double *var, int *brk)
    
    • 所在文件:ppp.c
    • 功能说明:计算经过电离层、DCB、卫星天线、接收机天线、相位缠绕校正后的测量值 meas(包含相位和伪距两个值)。
    • 参数说明:
    函数参数:11个
    obsd_t   *obs    I   观测数据
    nav_t    *nav    I   导航数据
    double   *pos    I   接收机位置 (lat,lon,h)(rad,m)
    double   *azel   I   方位角和俯仰角 (rad)
    prcopt_t *opt    I   处理过程选项
    double   *dantr  I   接收机天线校正值
    double   *dants  I   卫星天线校正值
    double   phw     I   相位缠绕校正值
    double   *meas   O   校正后的测量值
    double   *var    O   校正后的测量值的误差协方差
    int      *brk    O   用与判断是否有周跳
    返回类型:
    int              O   (>0:ok,0:sth wrong)
    
    • 处理过程:
    1. 若电离层校正模式为 iono-free LC,调用 ifmeas 计算 meas 值,然后返回。否则进行下面的步骤。
    2. 调用 testsnr 看观测值的信噪比是否过低,过低则忽略此观测值。
    3. 进行 DCB 校正。
    4. 调用 corr_ion 计算 slant ionospheric delay 值 ion。
    5. 合并各种校正值得到 meas 值。

    ifmeas

    int ifmeas(const obsd_t *obs, const nav_t *nav, const double *azel,
              const prcopt_t *opt, const double *dantr, const double *dants,
              double phw, double *meas, double *var)
    
    • 所在文件:ppp.c
    • 功能说明:计算经过电离层、DCB、卫星天线、接收机天线、相位缠绕校正后的测量值 meas(包含相位和伪距两个值)。电离层延时计算需要双频点数据。
    • 参数说明:
    函数参数:9个
    obsd_t   *obs    I   观测数据
    nav_t    *nav    I   导航数据
    double   *azel   I   方位角和俯仰角 (rad)
    prcopt_t *opt    I   处理过程选项
    double   *dantr  I   接收机天线校正值
    double   *dants  I   卫星天线校正值
    double   phw     I   相位缠绕校正值
    double   *meas   O   校正后的测量值
    double   *var    O   校正后的测量值的误差协方差
    返回类型:
    int              O   (>0:ok,0:sth wrong)
    
    • 处理过程:
    1. 选择所用频段,没有足够可用的频段则返回。
    2. 调用 testsnr 看观测值的信噪比是否过低,过低则返回。
    3. 按公式计算 γ , c 1 , c 2 \gamma, c_1, c_2 γ,c1,c2, 获取L1, L2, P1, P2, P1_C1, P1_P2的值。
    4. 按公式计算电离层校正和相位缠绕校正后的载波测量值。
    5. 按公式计算电离层校正和DCB校正后的伪距测量值。
    6. 若有SBAS,加上SBAS钟差校正到伪距测量值。
    7. 若有GLONASS,加上GPS和GLONASS的硬件偏差校正到伪距测量值。
    8. 加上卫星天线、接收机天线校正值到载波测量值和伪距测量值。

    filter

    int filter(double *x, double *P, const double *H, const double *v,
              const double *R, int n, int m)
    
    • 所在文件:rtkcmn.c
    • 功能说明:kalman滤波运算
    • 参数说明:
    函数参数,7个:
    double   *x        IO 状态变量 (n x 1)
    double   *P        IO 状态变量的误差协方差阵 (n x n)
    double   *H        I  观测矩阵的转置 (n x m)
    double   *v        I  实际观测量与预测观测量的残差 (measurement - model) (m x 1)
    double   *R        I  测量误差的协方差 (m x m)
    int      n         I  状态变量个数
    int      m         I  观测值个数
    返回类型:
    int                O (0:ok,<0:error)
    
    • 处理过程:
    1. 选择需要更新的状态 x 和对应的 P、H 到 x_、p_、H_ 中。
    2. 调用 filter_ 进行kalman滤波更新。
    3. 将更新值存到 x、P中。
    • 注意:
    1. 若状态 x[i]==0.0, 则不会更新 x[i] 和 P[i+i*n]

    filter_

    int filter_(const double *x, const double *P, const double *H,
               const double *v, const double *R, int n, int m,
               double *xp, double *Pp)
    
    • 所在文件:rtkcmn.c
    • 功能说明:kalman滤波运算, K = P H ( H T P H + R ) − 1 , x p = x + K v , P p = ( I − K H T ) P K=PH(H^TPH+R)^{-1}, xp=x+Kv, Pp=(I-KH^T)P K=PH(HTPH+R)1,xp=x+Kv,Pp=(IKHT)P
    • 参数说明:
    函数参数,9个:
    double   *x        I  状态变量 (n x 1)
    double   *P        I  状态变量的误差协方差阵 (n x n)
    double   *H        I  观测矩阵的转置 (n x m)
    double   *v        I  实际观测量与预测观测量的残差 (measurement - model) (m x 1)
    double   *R        I  测量误差的协方差 (m x m)
    int      n         I  状态变量个数
    int      m         I  观测值个数
    double   *xp       O  更新后的状态变量 (n x 1)
    double   *Pp       O  更新后的状态变量的误差协方差阵 (n x n)
    返回类型:
    int                O (0:ok,<0:error)
    
    • 处理过程:
    1. 调用矩阵运算函数按照公式进行矩阵运算
    • 注意:
    1. 矩阵是按列优先的顺序存的 (fortran convention)

    relpos

    int relpos(rtk_t *rtk, const obsd_t *obs, int nu, int nr, const nav_t *nav)
    
    • 所在文件:rtkpos.c
    • 功能说明:相对定位
    • 参数说明:
    函数参数,5个:
    rtk_t    *rtk      IO  rtk控制结构体
    obsd_t   *obs      I   观测数据
    int      nu        I   接收机观测数据的数量
    int      nr        I   基站观测数据的数量
    nav_t    *nav      I   导航数据
    返回类型:
    int                O   (1:ok,0:error)
    
    • 处理过程:
    1. 一系列值、状态、中间变量的初始化。
    2. 调用 satposs 计算卫星们的位置、速度和钟差。
    3. 调用 zdres 计算基站的没有差分的相位/码残差,若出错则返回0。
    4. 若为后处理,需要插值的,调用 intpres 进行插值。
    5. 调用 selsat 选择接收机与基站共同观测的卫星,返回共同观测的卫星个数,输出卫星号列表sat、在接收机观测值中的index值列表 iu 和在基站观测值中的index值列表 ir。
    6. 调用 udstate 更新状态值 rtk->x 及其误差协方差 rtk->P。
    7. 按迭代次数循环第8~10步。
    8. 调用 zdres 计算接收机的没有差分的相位/码残差。
    9. 调用 ddres 计算双差相位/码残差。
    10. 调用 filter 进行 Kalman 滤波运算。
    11. 再次调用 zdresddres 计算双差相位/码残差,调用 valpos 进行验证,若通过则更新 rtk->x 以及 rtk->P,并更新模糊度控制结构体。
    12. 根据选项模式调用不同的 resamb_* 函数解析周整模糊度。
    13. 保存solution状态。

    zdres

    int zdres(int base, const obsd_t *obs, int n, const double *rs,
              const double *dts, const int *svh, const nav_t *nav,
              const double *rr, const prcopt_t *opt, int index, double *y,
              double *e, double *azel)
    
    • 所在文件:rtkpos.c
    • 功能说明:计算接收机或基站的没有差分的相位/码残差(Zero-Difference Residuals)
    • 参数说明:
    函数参数,13个:
    int      base      I   0表示接收机,1表示基站
    obsd_t   *obs      I   观测数据
    int      n         I   观测数据的数量
    double   *rs       I   卫星位置和速度,长度为6*n,{x,y,z,vx,vy,vz}(ecef)(m,m/s)   
    double   *dts      I   卫星钟差,长度为2*n, {bias,drift} (s|s/s)
    int      *svh      I   卫星健康标志 (-1:correction not available)   
    nav_t    *nav      I   导航数据
    double   *rr       I   接收机/基站的位置和速度,长度为6*n,{x,y,z,vx,vy,vz}(ecef)(m,m/s)
    prcopt_t *opt      I   处理过程选项
    int      index     I   0表示接收机,1表示基站,与参数 base 重复了
    double   *y        O   相位/码残差
    double   *e        O   观测矢量 (ecef)
    double   *azel     O   方位角和俯仰角 (rad)
    返回类型:
    int                O   (1:ok,0:error)
    
    • 处理过程:
    1. 若没有接收机位置,返回0。
    2. 接收机位置传给 rr_。
    3. 若需要地球潮校正,调用 tidedisp 对 rr_ 进行校正。地球潮包含固体潮、极潮和海潮负荷。
    4. rr_ 转换坐标系到大地坐标系 pos。
    5. 大循环,对每一个观测量
    6. 调用 geodist 计算卫星到接收机的几何距离 r,调用 satazel 计算仰角,若仰角低于阈值,排除此观测量。
    7. 若设置有卫星需要排除的,排除掉。
    8. 根据卫星钟差校正 r。
    9. 根据对流层模型设置,调用 tropmodel 和 tropmapf 计算对流层延时校正值并校正 r。
    10. 根据接收机天线模型调用 antmodel 计算校正值 dant(对每一个频率都有一个值)。
    11. 调用 zdres_sat 计算没有差分的相位/码残差 y。
    12. 重复6~11直到大循环结束。

    zdres_sat

    void zdres_sat(int base, double r, const obsd_t *obs, const nav_t *nav,
                  const double *azel, const double *dant,
                  const prcopt_t *opt, double *y)
    
    • 所在文件:rtkpos.c
    • 功能说明:计算接收机或基站对某一颗卫星的没有差分的相位/码残差(Zero-Difference Residuals)。y = 观测值 - r - dant。
    • 参数说明:
    函数参数,13个:
    int      base      I   0表示接收机,1表示基站
    double   r         I   经过钟差和对流层校正后的几何距离。
    obsd_t   *obs      I   观测数据
    nav_t    *nav      I   导航数据
    double   *azel     I   方位角和俯仰角 (rad)
    double   *dant     I   接收机天线校正值
    prcopt_t *opt      I   处理过程选项
    double   *y        O   相位/码残差
    返回类型:
    • 处理过程:
    1. 按电离层校正模式是否为 IONOOPT_IFLC 分两种情况。
    2. 若是:检测信噪比,计算天线校正值 dant_if,然后计算残差。
    3. 若否:检测信噪比,然后计算残差。

    tidedisp

    void tidedisp(gtime_t tutc, const double *rr, int opt, const erp_t *erp,
                  const double *odisp, double *dr)
    
    • 所在文件:ppp.c
    • 功能说明:计算因地球潮汐而引起的站点位移校正值 dr。
    • 参数说明:
    函数参数,6个:
    gtime_t  tutc     I   time in utc
    double  *rr       I   站点位置 (ecef) (m)
    int      opt      I   选项(指定包含哪些潮的影响)
                          1: solid earth tide
                          2: ocean tide loading
                          4: pole tide
                          8: elimate permanent deformation
    double  *erp      I   地球自转参数
    double  *odisp    I   海潮负荷参数
                          odisp[0+i*6]: consituent i amplitude radial(m)
                          odisp[1+i*6]: consituent i amplitude west  (m)
                          odisp[2+i*6]: consituent i amplitude south (m)
                          odisp[3+i*6]: consituent i phase radial  (deg)
                          odisp[4+i*6]: consituent i phase west    (deg)
                          odisp[5+i*6]: consituent i phase south   (deg)
                          (i=0:M2,1:S2,2:N2,3:K2,4:K1,5:O1,6:P1,7:Q1,8:Mf,9:Mm,10:Ssa)
    double  *dr       O   因地球潮汐而引起的站点位移校正值 (ecef) (m)
    返回类型:
    • 处理过程:
    1. 若有erp,调用 geterp 获取erp参数。
    2. 若有选项,调用 tide_solid 计算固体潮。
    3. 若有选项,调用 tide_oload 计算海潮负荷。
    4. 若有选项,调用 tide_pole 计算极潮。

    geterp

    int geterp(const erp_t *erp, gtime_t time, double *erpv)
    
    • 所在文件:rtkcmn.c
    • 功能说明:获取ERP参数值。
    • 参数说明:
    函数参数,3个:
    erp_t  *erp        I   earth rotation parameters
    gtime_t time       I   time (gpst)
    double *erpv       O   erp values {xp,yp,ut1_utc,lod} (rad,rad,s,s/d)
    返回类型:
    int                O   (1:ok,0:error)
    
    • 处理过程:
    1. 计算当前时间与ERP参数中时间的插值。
    2. 若当前时间早于ERP参数中最早的时间,采用最早的时间来计算。
    3. 若当前时间晚于ERP参数中最晚的时间,采用最晚的时间来计算。
    4. 若当前时间在ERP参数中最早与最晚的时间之间,则先找到最接近的两个时间,然后用插值。

    udstate

    void udstate(rtk_t *rtk, const obsd_t *obs, const int *sat,
                const int *iu, const int *ir, int ns, const nav_t *nav)
    
    • 所在文件:rtkpos.c
    • 功能说明:更新状态值 rtk->x 及其误差协方差 rtk->P。
    • 参数说明:
    函数参数,7个:
    rtk_t    *rtk      IO  rtk控制结构体
    obsd_t   *obs      I   观测数据
    int      sat       I   接收机和基站共同观测的卫星号列表
    int      *iu       I   接收机和基站共同观测的卫星在接收机观测值中的index值列表
    int      *ir       I   接收机和基站共同观测的卫星在基站观测值中的index值列表
    int      ns        I   接收机和基站共同观测的卫星个数
    nav_t    *nav      I   导航数据
    返回类型:
    • 处理过程:
    1. 调用 udpos 根据不同模式更新rtk中的位置、速度、加速度值和协方差。
    2. 若 电离层模式>=IONOOPT_EST,调用 udion 更新状态 rtk->x 中的电离层参数(MAXSAT个)及其协方差。
    3. 若 对流层模式>=TROPOPT_EST,调用 udtrop 更新状态 rtk->x 中的对流层参数(2或6个)及其协方差。TROPOPT_EST初始化状态 rtk->x 中的对流层参数。
    4. 若为 GLONASS AR模式,调用 udrcvbias 更新接收机硬件偏移。
    5. 若 模式>PMODE_DGPS,调用 udbias 更新载波相位偏移状态值以及其误差协方差。
    • 注意:
    1. 状态变量包含接收机位置、速度、加速度值、[每颗卫星的电离层参数]、[对流层参数]、[接收机硬件偏移]、每颗卫星的载波偏移。其中载波偏移包含周整模糊度以及小数部分,可参考 RTKLIB Manual P139 E.3.5。
    2. 过程2、3中更新状态x,只根据需要作初始化,给初值;更新时只更新协方差。

    udpos

    void udpos(rtk_t *rtk, double tt)
    
    • 所在文件:rtkpos.c
    • 功能说明:更新rtk中的位置、速度、加速度值和协方差。
    • 参数说明:
    函数参数,2个:
    rtk_t    *rtk      IO  rtk控制结构体
    double   tt        I   本次更新与上次更新的时间差
    返回类型:
    • 处理过程:
    1. 若为 PMODE_FIXED 模式,直接从选项中取得位置值给rtk->x,然后返回。
    2. 若为第一个历元,用rtk->sol中的位置值初始化rtk->x。若为dynamics模式(即需要估计速度和加速度),一并初始化。
    3. 若为 PMODE_STATIC 模式,返回。
    4. 若非dynamics模式,用rtk->sol中的位置值初始化rtk->x,然后返回。
    5. 检查位置协方差,若大于阈值VAR_POS则用rtk->sol中的位置值重置rtk->x
    6. 根据Kalman滤波的预测方程 x=Fx 和 P=FP*F+Q 更新(参考RTKLIB Manual P161 E.7.4, E.7.5)。其中更新Q时需要坐标转换。

    udbias

    void udbias(rtk_t *rtk, double tt, const obsd_t *obs, const int *sat,
                const int *iu, const int *ir, int ns, const nav_t *nav)
    
    • 所在文件:rtkpos.c
    • 功能说明:更新载波相位偏移状态值到 rtk->x 以及更新其误差协方差到 rtk->P。
    • 参数说明:
    函数参数,8个:
    rtk_t    *rtk      IO  rtk控制结构体
    double   tt        I   本次更新与上次更新的时间差
    obsd_t   *obs      I   观测数据
    int      sat       I   接收机和基站共同观测的卫星号列表
    int      *iu       I   接收机和基站共同观测的卫星在接收机观测值中的index值列表
    int      *ir       I   接收机和基站共同观测的卫星在基站观测值中的index值列表
    int      ns        I   接收机和基站共同观测的卫星个数
    nav_t    *nav      I   导航数据
    返回类型:
    • 处理过程:
    1. 对每一颗卫星,循环2~4步。
    2. 调用 detslp_ll 通过 LLI 检查接收机和基站观测数据是否有周跳。
    3. 调用 detslp_gf_L1L2 和 detslp_gf_L1L5 通过 geometry-free phase jump 检查是否有周跳。
    4. 调用 detslp_dop 通过多普勒和相位差检查接收机和基站观测数据是否有周跳。
    5. 对每一个频率,循环6~10步。
    6. 若为instantaneous AR 模式或者超出 obs outage counter,重置载波偏移值。
    7. 若检测到周跳,重置载波偏移值。
    8. 用 phase - code 的值 ( ϕ − ρ λ \phi - \frac{\rho}{\lambda} ϕλρ) 来估计载波偏移值。
    9. 校正载波偏移值以保持载波与伪距的一致性。
    10. 更新载波偏移值及其误差协方差。

    ddres

    int ddres(rtk_t *rtk, const nav_t *nav, double dt, const double *x,
              const double *P, const int *sat, double *y, double *e,
              double *azel, const int *iu, const int *ir, int ns, double *v,
              double *H, double *R, int *vflg)
    
    • 所在文件:rtkpos.c
    • 功能说明:计算接收机或基站的双差相位/码残差(Double-Difference Residuals)
    • 参数说明:
    函数参数,16个:
    rtk_t    *rtk      IO  rtk控制结构体
    nav_t    *nav      I   导航数据
    double   dt        I   接收机和基站的时间差
    double   *x        IO  状态变量
    double   *P        IO  状态变量的误差协方差阵
    int      sat       I   接收机和基站共同观测的卫星号列表
    double   *y        IO  相位/码残差
    double   *e        IO  观测矢量 (ecef)
    double   *azel     O   方位角和俯仰角 (rad)
    int      *iu       I   接收机和基站共同观测的卫星在接收机观测值中的index值列表
    int      *ir       I   接收机和基站共同观测的卫星在基站观测值中的index值列表
    int      ns        I   接收机和基站共同观测的卫星个数
    double   *v        O   实际观测量与预测观测量的残差
    double   *H        O   观测矩阵
    double   *R        O   测量误差的协方差
    int      *vflg     O   数据有效标志
    返回类型:
    int                O   (>0:ok,0:error)
    
    • 处理过程:
    1. 一些初始化和坐标转换。
    2. 若 电离层模式>=IONOOPT_EST,调用 ionmapf 计算电离层延迟因子。
    3. 若 对流层模式>=TROPOPT_EST,调用 prectrop 计算对流层延迟因子。
    4. 大循环,对每一个频率,循环5~
    5. 寻找仰角最高的参考卫星。
    6. 小循环,对每一种导航系统,对每一颗卫星,循环7~14步,计算双差。
    7. 用传入的没有差分的相位/码残差y计算双差残差v,并计算对应的H。
    8. 若要估计电离层参数,模式IONOOPT_EST,用电离层延迟因子修正v和H。
    9. 若要估计对流层参数,模式TROPOPT_EST,用对流层延迟因子修正v和H。
    10. 用相位偏移修正v和H。
    11. 若是GLONASS系统观测值,做相关修正。
    12. 根据选项maxinno的值检测是否要排除此观测数据。
    13. 计算单差的测量误差协方差Ri、Rj。
    14. 设置数据有效标志。
    15. 若为移动基站模式PMODE_MOVEB,计算移动基站限制并设置相应的数据有效标志。
    16. 用Ri、Rj计算双差的测量误差协方差R。
    展开全文
  • Swift函数式编程十二(表格应用)

    千次阅读 2022-04-15 15:16:05
    这个示例为希望被解析的表达式编写解析器,并为这些表达式编写个求值器,然后将其嵌入界面中。 解析 基于解析器组合算子中的算术表达式解析器,引入额外的抽象层级。 之前,编写的解析器会直接返回计算结果。比如...

    代码地址

    这个示例为希望被解析的表达式编写解析器,并为这些表达式编写一个求值器,然后将其嵌入界面中。

    解析

    基于解析器组合算子中的算术表达式解析器,引入额外的抽象层级。

    之前,编写的解析器会直接返回计算结果。比如在解析 “2*3” 这样的乘法表达式时:

    let multiplication = curry { return $0*($1 ?? 1) }<^>integer<*>(character{ $0 == "*" }*>integer).optional
    

    multiplication 的类型是 Parser,也就是说,在这个传入字符串 “2*3” 并执行解析器之后, 会返回整数值 6。只有在表达式中不含有对任何外部数据的依赖的时候,才能够直接将结果计算出来。然而,在电子表格中,希望可以表达类似于 A3 这样对其它单元格值的引用,或者是像 SUM(A1:A3) 这样的函数调用。

    要支持这些特性,解析器需要将输入的字符串转换为一棵抽象语法树 (abstract syntax tree, AST),这是一种用于描述表达式内容的中间表现形式。在转换为抽象语法树后,可以取得这些结构化的数据,再对其做实际的计算。将这种中间表现形式定义为一个枚举:

    indirect enum Expression {
        case int(Int)
        case reference(String, Int)
        case infix(Expression, String, Expression)
        case funcation(String, Expression)
    }
    

    枚举 Expression 包含四个枚举值:

    • int表示简单的数值。
    • reference表示对其它单元格内值的引用,比如"A3"。其中,列引用被指定为从"A"开始的大写字母。而行引用则被定义为从 0 开始的数字。枚举值 reference 有两个关联值,一个字符串和一个整数,用于储存该引用的行与列。
    • infix表示类似"A2+3"这样位于运算符左右侧两个参数之间的一次运算。枚举值infix中的关联值储存了运算符左侧的表达式,运算符本身,以及右侧的表达式。
    • function表示一个类似于"SUM(A1:A3)"这样的函数调用。第一个关联值是函数名,第二个则是函数的参数(在这个示例中,函数只能够接收单个参数)。

    在有了的 Expression 枚举之后,就可以为每种表达式编写一个解析器了。

    枚举值 int 的解析器,可以利用解析器组合算子中的整数解析器,然后将整数结果封装为一个 Expression 类型:

    extension Expression {
        static var intParser: Parser<Expression> {
            return { int($0) } <^> integer
        }
    }
    if let v = Expression.intParser.run("123") { print(v) }
    /*输出:
    (Spreadsheet.Expression.int(123), "")
    */
    

    行列坐标的解析器,先定义一个大写字母的解析器,然后将大写字母的解析器与一个整数解析器合并起来,再将解析结果封装为枚举值 .reference:

    let capitalLetter = character { CharacterSet.uppercaseLetters.contains($0) }
    
    extension Expression {
        static var referenceParser: Parser<Expression> {
            return curry { reference(String($0), $1) } <^> capitalLetter <*> integer
        }
    }
    if let v = Expression.referenceParser.run("A3") { print(v) }
    /*输出:
    (Spreadsheet.Expression.reference("A", 3), "")
    */
    

    枚举值 .function的解析器,需要解析出一个函数名 (一个或更多个大写字 母),以及接下来的圆括号中的表达式。在此,圆括号中的表达式将只支持形如 “A1:A2” 的 参数类型。需要先定义三个额外的辅助函数。首先添加函数 string创建一个用于匹配特定字符串的解析器,使用已有的函数 character 就来实现。接着为 Parser 定义一个便利方法,用来将当前解析器封装在一个用于解析左右圆括号的 解析器中。然后定义一个将接受三个参数的非柯里化函数转化为柯里化函数的函数。这样就可以继续为函数表达式编写解析器了:

    func string(_ string: String) -> Parser<String> {
        return Parser<String> { input in
            var remainder = input
            for c in string {
                let paser = character { $0 == c }
                guard let (_, newRemainder) =  paser.run(remainder) else { return nil }
                remainder = newRemainder
            }
            
            return (string, remainder)
        }
    }
    if let v = string("SUM").run("SUM") { print(v) }
    /*输出:
    ("SUM", "")
    */
    
    extension Parser {
        var parenthesized: Parser<Result> {
            return string("(") *> self <* string(")")
        }
    }
    if let v = string("SUM").parenthesized.run("(SUM)") { print(v) }
    /*输出:
    ("SUM", "")
    */
    
    func curry<A, B, C, D>(_ f: @escaping (A, B, C) -> D) -> (A) -> (B) -> (C) -> D {
        return { a in { b in { c in f(a, b, c) } } }
    }
    
    extension Expression {
        static var funcationParser: Parser<Expression> {
            let name = ({ String($0) } <^> capitalLetter.many1)
            let argument = curry { infix($0, $1, $2) } <^> referenceParser <*> string(":") <*> referenceParser
            return curry { funcation($0, $1) } <^> name <*> argument.parenthesized
        }
    }
    
    if let v = Expression.funcationParser.run("SUM(A1:A2)") { print(v) }
    /*输出:
    (Spreadsheet.Expression.funcation("SUM", Spreadsheet.Expression.infix(Spreadsheet.Expression.reference("A", 1), ":", Spreadsheet.Expression.reference("A", 2))), "")
    */
    

    在 functionParser 中,首先定义了一个用于解析函数名的解析器。接下来则为函数的参数 定义了解析器,该参数是一个使用 “:” 作为运算符的 .infix 表达式,并以另外两个单元格的引用作为运算参数。最后按照先解析函数名,再解析圆括号里函数参数的顺序,将上述解析器进行了合并。

    定义一个对整数进行运算表达式解析器,不过这个 解析器可以处理多个运算符。先定义一个解析器 calculation,用于解析跟随在整数之后的 “+” 或 “-” 或 “*” 或 “/” 符号:

    let calculation = curry { ($0, $1) } <^> (string("+") <|> string("-") <|> string("*") <|> string("/")) <*> intParser
    

    该解析器的结果是一个包含运算符与整数的多元组。接着可以定义一个能够零次或多次解析输入字符串的解析器,像... <^> intParser <*> calculation.many1这样。需要编写一个用来合并右侧两个解析器结果的函数。这个函数要接收的参数类型是已知的,它们分别是一个 (从 intParser 返回的) Expresssion,以及一个由解析器 calculation.many1 返回的元素类型为多元组 (String, Expression) 的数组:

    func combineOperands(first: Expression, rest: [(String, Expression)]) -> Expression {
        return rest.reduce(first) { Expression.infix($0, $1.0, $1.1) }
    }
    

    上述函数使用了 reduce 将第一个表达式与之后由 .infix 表达式返回的 (operator, expression) 合并。先可以像这样编写第一版 infixParser 了:

    extension Expression {
        static var infixParser: Parser<Expression> {
            let calculation = curry { ($0, $1) } <^> (string("+") <|> string("-") <|> string("*") <|> string("/")) <*> intParser
            return curry(combineOperands(first:rest:)) <^> intParser <*> calculation.many1
        }
    }
    
    if let v = Expression.infixParser.run("10+5-3") { print(v) }
    /*输出:
    (Spreadsheet.Expression.infix(Spreadsheet.Expression.infix(Spreadsheet.Expression.int(10), "+", Spreadsheet.Expression.int(5)), "-", Spreadsheet.Expression.int(3)), "")
    */
    
    if let v = Expression.infixParser.run("2*3*4") { print(v) }
    /*输出:
    (Spreadsheet.Expression.infix(Spreadsheet.Expression.infix(Spreadsheet.Expression.int(2), "*", Spreadsheet.Expression.int(3)), "*", Spreadsheet.Expression.int(4)), "")
    */
    

    需要再解决运算只能在整数间进行的限制,为此引入了 primitiveParser,它能够解析出任意可以被算术运算符进行运算的元素:

    func lazy<A>(parser: @autoclosure @escaping () -> Parser<A>) -> Parser<A> {
        return Parser<A> { parser().run($0) }
    }
    
    extension Expression {
        static var primitiveParser: Parser<Expression> {
            return return intParser <|> referenceParser <|> funcationParser
        }
    
        static var parser = lazy(parser: infixParser) <|> lazy(parser: infixParser).parenthesized <|> primitiveParser
    }
    

    primitiveParser 使用了选择解析运算符 <|>,定义中可运算的元素可以是一个整数,一个单元 格引用,或者一个函数调用。

    parser 使用了选择解析运算符 <|>,定义中可运算的元素可以是一个子表达式,是一个被圆括号括起的子表达式,或者一个primitiveParser。这里最重要的部分是对辅助函数 lazy 的使用。必须确保任意表达式的解析器 parser 仅在必要时才被求值。否则将会陷入无尽的循环之中。为了实现这个功能,lazy 使用 @autoclosure 将参数封装在了一个函数中。

    现在可以用 primitiveParser 替换 intPaser 来编写之前的 infixParser :

    extension Expression {
        static var infixParser: Parser<Expression> {
            let calculation = curry { ($0, $1) } <^> (string("+") <|> string("-") <|> string("*") <|> string("/")) <*> primitiveParser
            return curry(combineOperands(first:rest:)) <^> primitiveParser <*> calculation.many1
        }
    }
    /*输出:
    (Spreadsheet.Expression.infix(Spreadsheet.Expression.infix(Spreadsheet.Expression.int(2), "+", Spreadsheet.Expression.int(4)), "*", Spreadsheet.Expression.funcation("SUM", Spreadsheet.Expression.infix(Spreadsheet.Expression.reference("A", 1), ":", Spreadsheet.Expression.reference("A", 2)))), "")
    */
    

    求值

    求值阶段的目标是将一棵 Expression 树转换为一个实际的结果。首先定义一 个 Result 类型:

    enum Result {
        case int(Int)
        case list([Result])
        case error(String)
    }
    

    Result 类型共有三个枚举值:

    • int用于结果为整数的表达式的求值,比如"2*3"或是"SUM(A1:A3)"(假设在单元格A1 - 至 A3 中的值都是合法的)。
    • list用于返回多个结果的表达式的求值。在示例中,只有像"A1:A3"这样的表达式会发生这种情况,这些表达式通常为 “SUM” 或是 “MIN” 这类函数的参数。
    • error表示在求值过程中出现的错误,在其关联值中会储存错误的详细说明。

    对 Expression 进行求值,会在其拓展中添加一个 evaluate 方法,参数 context 是一个数组,其中包含了该电子表格中其他单元格的所有表达式,这是为了能够处理像是 A2 这样的单元格引用。简单起⻅将该电子表格限制为只有一列单元格,这样就可以使用一个简单的数组来表示所有的表达式了。
    接下来只需要对 Expression 中不同的枚举值依次进行匹配,然后返回对应的 Result 值 就可以了。下面是 evaluate 方法:

    extension Expression {
        func evaluate(context: [Expression?]) -> Result {
            switch self {
            case let .int(x):
                return .int(x)
            case let .reference(_, row):
                return context[row]?.evaluate(context: context) ?? Result.error("Invalid reference \(self)")
            case .funcation:
                return evaluateFunction(context: context) ?? .error("Invalid function call \(self)")
            case let .infix(l, op, r):
                return self.evaluateArithmetic(context: context) ?? self.evaluateList(context: context) ?? .error("Invalid operator \(op) for operands \(l), \(r)")
            }
        }
    }
    

    第一个匹配条件 .int 很容易处理。.int 的关联值已经是一个整数值,只需要提取该值然后返回一个 .int 结果值就可以了。

    第二个匹配条件 .reference 则稍微复杂一些。考虑到电子表格被限制为仅有一列,只需要匹配对 A 列单元格的引用,并且将第二个关联值与变量 row 进行 绑定。在获得被引用单元格的行坐标后,利用 context 参数查询该单元格的表达式,并且 为其表达式递归地调用 evaluate。在该引用不存在的情况下,将一个 .error 作为结果值返回。

    第三个匹配条件 .function,只是将求值任务转移给了 Expression 另一个被命名为 evaluateFunction 的方法。虽然可以将这部分代码也写入 evaluate 方法中,但为了避免单个方法太过冗⻓决定将这个步骤提了出来。evaluateFuction 的代码如下:

    extension Expression {
        func evaluateFunction(context: [Expression?]) -> Result? {
            guard case let Expression.funcation(name, parameter) = self, case let .list(list) = parameter.evaluate(context: context) else {
                return nil
            }
            
            switch name {
            case "SUM":
                return list.reduce(.int(0), lift(+))
            case "MIN":
                return list.reduce(.int(Int.max), lift { min($0, $1) })
            default:
                return .error("Unknown function \(name)")
            }
        }
    }
    

    guard 语句会先检查该方法是否确实被 .function 枚举值调用,然后确定表达式中函数的参数求值后的结果是否为一个 .list。如果不满足以上某个条件,会直接返回 nil。
    剩下的 switch 语句并不复杂:为每个函数名实现一个匹配条件,然后利用 reduce 对从 parameter 中求得的列表进行相应的计算就可以了。
    这里要提到一个重要的细节,list 是一个元素类型为 Result 的数组。因此不能直接使用标准的算术运算符 (比如 “+”) 或是函数 (比如 min)。为此定义一个函数 lift,用于将任 意类型为 (Int, Int) -> Int 的函数转换为一个类型为 (Result, Result) -> Result 的函数:

    func lift(_ op: @escaping (Int, Int) -> Int) -> (Result, Result) -> Result {
        return { lhs, rhs in
            guard case let (Result.int(x), Result.int(y)) = (lhs, rhs) else {
                return .error("Invalid operands \(lhs), \(rhs) for integer operator")
            }
            
            return .int(op(x, y))
        }
    }
    

    在这里必须先确保处理的确实是两个 Result.int 值,否则会返回一个 .error 作为结果。一旦获取了两个整数,就可以由参数传入的运算函数 op 来计算结果,并将其再封装回一个 Result.int 值。

    evaluate 方法要处理的最后一个条件是枚举值 .infix。将对 .infix 表达式的求值代码拆分到了 Expression 的两个拓展中。第一个是 evaluateArithmetic。这个方法尝试在当 .infix 表达式是一个算术表达式时对其进行求值,如果求值失败则会返回 nil:

    extension Expression {
        func evaluateArithmetic(context: [Expression?]) -> Result? {
            guard case let .infix(l, op, r) = self else {
                return nil
            }
            
            let x = l.evaluate(context: context)
            let y = r.evaluate(context: context)
            switch op {
            case "+":
                return lift(+)(x, y)
            case "-":
                return lift(-)(x, y)
            case "*":
                return lift(*)(x, y)
            case "/":
                return lift(/)(x, y)
            default:
                return nil
            }
        }
    }
    

    由于这个方法可以被任意的 Expression 调用,需要先检查是否真的在处理一个 .infix 表达式。同样使用 guard 语句来直接获取关联值:左侧的表达式,运算符,以及右侧的表达式。
    接着会为左右两侧的表达式调用 evaluate,然后通过 switch 语句匹配运算符来计算结果。这里又一次使用了 lift 函数,使像是两个 Result 值相加这样的运算变得可行。这里要注意不必再考虑 x 或 y 可能不是 .int 的情况,lift 函数会搞定的。

    Expression 中第二个用于计算 .infix 表达式的方法用于处理列表运算符,比如 “A1:A3”:

    extension Expression {
        func evaluateList(context: [Expression?]) -> Result? {
            guard case let .infix(l, op, r) = self, op == ":", case let .reference(_, row1) = l, case let .reference(_, row2) = r else {
                return nil
            }
            
            return .list((row1...row2).map{ Expression.reference("A", $0).evaluate(context: context) })
        }
    }
    

    再一次先检查 evvaluateList 是否被适当的 Expression 调用。在 guard 语句中,检查了是否在处理一个 .infix 表达式,其中的运算符是否是一个 “:”,以及左右两个表达式是不是引用 “A” 列中某个单元格的 .reference。如果不匹配其中任何一个条件,会直接返回 nil。
    如果所有的检查都通过,会对从 row1 开始,到包括 row2 在内的所有序列值进行映射,对这些单元格中的表达式依次进行求值,再将映射得到的结果封装在 .list 中作为结果返回。
    以上就是需要在 Expression 的 evaluate 方法中实现的全部内容了。考虑到总是希望在对某个单元格求值时先求得 context 中另一个单元格的值 (需要能解决对单元格的引用), 添加一个额外的便利方法来对一个表达式数组进行求值:

    func evaluate(expressions: [Expression?]) -> [Result] {
        return expressions.map{ $0?.evaluate(context: expressions) ?? .error("Invalid expression \($0)") }
    }
    

    现在可以定义一个示例表达式的数组并尝试对它们进行求值:

    let expressions: [Expression] = [
        .infix(.infix(.int(1), "+", .int(2)), "*", .int(3)),
        .infix(.reference("A", 0), "*", .int(3)),
        .funcation("SUM", .infix(.reference("A", 0), ":", .reference("A", 1)))
    ]
    print(evaluate(expressions: expressions))
    /*输出:
    [Spreadsheet.Result.int(9), Spreadsheet.Result.int(27), Spreadsheet.Result.int(36)]
    */
    

    用户界面

    请添加图片描述

    构建了上图中用于嵌入解析与求值代码的用户界面。然而这个应用还有诸多限制,代码也还有很大的优化空间。比如一个单元格不能使用混合运算;不必因为用户修改了单个单元格的内容就对所有的单元格进行解析和求值;如果 10 个单元格同时引用了一 个单元格,那么这个单元格会被求值 10 次……

    展开全文
  • 用待定系数法求二次函数... 【知识点梳理】1、用待定系数法求二次函数解析式① 二次函数解析式常见有以下几种形式 :(1)一般式:(2)顶点式:(3)交点式:② 确定二次函数解析式常用待定系数法,用待定系数法求二次函...
  • 一次函数是很多最早学习的函数知识内容之一,它的图像是一条直线,而学好一次函数,那么首先要掌握好一元一次方程、二元一次方程、二元一次方程组等相关知识内容。从某种意义上来说,直线方程的概念本质上是刻画直线...
  • JDK8函数式接口

    千次阅读 2019-05-12 11:54:34
    Supplier函数式接口 Consumer函数式接口 Function函数式接口 Predicate函数式接口
  • 会求函数解析式 3.根据解析式画函数图像 二自主学习 阅读课本P33-34 1.函数的三种表示方法 . 2.分段函数的定义 . 3.二次函数的表示方法一般式 顶点式 两根式 4.画函数图象的一般步骤为 .在画图象时应注意以下几点? ...
  • JS函数声明和预解析的理解

    千次阅读 2017-09-06 15:26:54
    JS函数声明方法今天看到了个自己关注了的大神给我回了私信,觉得自己仿佛摸到了大神的裤腿,哈哈,而且人还特别好,居然会给你小菜鸟回私信,特别开心呀,个菜鸟的小激动,言归正传啦 1.最为常见的函数声明的...
  • 满意答案chinnl2014.05.25采纳率:41%等级:9已帮助:1265人(具有如下按键:SHIFT-S-VAR-2MODE(CLR))方法/步骤首先我们要切换到函数求解模式按【MODE】,可以看见有三个选项:1 COMP2 SD3 REG其中REG是求解模式,选择【3...
  • origin函数拟合

    千次阅读 2021-07-25 21:24:51
    下面记录一下用origin软件拟合函数求取系数的步骤 1.新建项目,设这个函数是y=Ax^ 2+Bx+C, 假设这个函数是 y=2x^2+3x+1,当x取值时1到10,得到10个y值,输入到项目中 2. 选中输入的数值,然后选中菜单栏 分析->...
  • iOS之深入解析Hash在iOS中的应用

    万次阅读 2021-10-19 15:43:24
    也就是说,它通过把关键码值映射到表中个位置来访问记录,以加快查找的速度,这个映射函数叫做散列函数,存放记录的数组叫做散列表。 给定表 M,存在函数 f(key),对任意给定的关键字值 key,代入函数后若能得到...
  • Tensorflow操作与函数全面解析

    千次阅读 2018-09-14 16:23:46
    转载自:http://blog.csdn.net/lenbow/article/details/521527661、tensorflow的基本运作为了快速的熟悉TensorFlow编程,下面从段简单的代码开始:import ...
  • 高斯分布函数解析

    万次阅读 2012-06-26 15:20:10
    高斯模糊是种图像模糊滤波器,它用正态分布计算图像中每个像素的变换。 N 维空间正态分布方程为 在二维空间定义为 其中 r 是模糊半径 (r2 = u2 + v2),σ 是正态分布的标准偏差。 在二维空间中...
  • 第五章:函数和代码复用

    千次阅读 2020-12-04 19:45:26
    函数的定义函数段具有特定功能的、可重用的语句组,用函数名来表示,并通过函数名进行功能调用。函数也可以看作是段具有名字的子程序,可以在需要的地方调用执行,不需要在每个执行地方重复编写这些语句。每次...
  • 到目前为止,在本系列的每期文章中,我都说明了为什么理解函数式编程非常重要。但是,有些原因是在多期文章中进行说明的,只有在综合思路的更大背景中,才可以完全了解这些原因。在本期文章中,我会探讨函数式编程...
  • 一般的,在个变化过程中,假设有两个变量x、y,如果对于任意个x都有唯一确定的个y和它对应,那么就称y是x的函数,其中x是自变量,y是因变量,x的取值范围叫做这个函数的定义域,相应y的取值范围叫做函数的值域...
  • 与React类组件相比,React函数式组件究竟有何不同? 一般的回答都是: 类组件比函数式组件多了更多的特性,比如 state,那如果有 Hooks 之后呢? 函数组件性能比类组件好,但是在现代浏览器中,闭包和类的原始性能...
  • 原标题:一次弄懂低通、高通、带通、带阻、状态可调滤波器!低通滤波器通知:第13部视频教程已开售☜☜猛戳1 二阶压控低通滤波器二阶压控低通滤波器电路如图所示,由R1、C1 及R2、C2 分别构成两个一阶低通滤波器,但...
  • Android Binder通信一次拷贝你真的理解了吗?

    千次阅读 多人点赞 2021-01-13 20:24:47
    最近有读者在询问一个关于Binder通信"一次拷贝"的问题,说在学习Binder驱动的实现中看到有多次调用了copy_from_user和copy_to_user来进行数据的跨用户空间和内核空间的拷贝,但是为啥Android官方和绝大部分的博客...
  • hash函数解析

    千次阅读 2011-12-01 16:31:31
    Hash函数在多个领域均有应用,而在数字签名和数据库实现时又用的最多,比如基于hash的索引,是最好的单值查找索引; 同时,在当前数据爆炸的场景下,执行相似item的查找时,在内存受限时,均可以采取LSH(local ...
  • 函数求二阶偏导

    千次阅读 2021-02-05 03:52:19
    §1 -7 高阶偏导数及泰勒公式 、高阶偏导数设z = f ( x, y )的偏导数为 ...高等数学重难点解析 考研复习 第七节 隐函数的求导方法个方程所确定的隐函数 及其导数二......人阅读|下载 第七节多元隐函数求偏...
  • 超硬核!数据结构学霸笔记,考试面试吹牛就靠它

    万次阅读 多人点赞 2021-03-26 11:11:21
    操作系统学霸笔记,考试复习面试全靠它 第一次笔记(复习c,课程概述) 第一节课复习了c语言的一些知识,并简单介绍了数据结构这门课程。 1、引用和函数调用: 1.1引用:对一个数据建立一个“引用”,他的作用是为一...
  • 函数凹凸区间怎么求

    千次阅读 2021-01-13 21:46:29
    函数、极限和连续()函数(1)理解函数的概念:函数的定义,函数的表示法,分段函数。(2)理解和掌握函数的简单性质:单调性,奇偶性,有界性,周期性。(3)了解反函数:反函数的定义,反函数的图象。(4)...
  • Kotlin是种增加许多新功能的语言,允许编写更简洁易读的代码,这使得我们的代码更易于维护。例如使用顶层函数和属性从此消除Java中的static、中缀表达式调用和解构声明等。 1、为什么要用顶层函数替代Java中的...
  • http://dawnote.net/2018/01/13/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E4%B8%AD%E6%A0%B8%E5%87%BD%E6%95%B0-Kernel-%E7%9A%84%E7%90%86%E8%A7%A3%E4%B8%8EKernel-SVM%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90/第节 ...
  • 双线性二插值原理解析

    万次阅读 2018-02-07 10:59:40
    许多实际问题中都用函数来表示某种内在联系或规律,而不少函数都只能通过实验和观测来了解。如对实践中的某个物理量进行观测,在若干个不同的地方得到相应的观测值,拉格朗日插值法可以找到个多项式,其恰好在各个...
  • 牛客网刷题汇总()附解析

    千次阅读 2017-02-09 10:32:31
    纯虚函数是在基类声明的虚函数,它在基类中没有定义,但是要求派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后面添加“=0”,比如 virtual void f()=0;而C++中包含纯虚函数的类称为抽象类...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 63,791
精华内容 25,516
关键字:

一次函数的解析式步骤