//
// RatPoly.swift
// RatNum
//
import Foundation
/**
RatPoly represents an immutable single-variate polynomial expression,
as in the examples below.
**AbstractState**:
RatPolys are sums of RatTerms with non-negative exponents.
Example RatPolys include "0", "x-10", and "x^3-2*x^2+5/3*x+3", and "NaN".
- Note: See RatNum's documentation for a definition of "immutable".
*/
public class RatPoly : CustomStringConvertible, Equatable {
/// Holds all the RatTerms in this RatPoly
private let terms : [RatTerm]
// Definitions:
// For a RatPoly p, let:
// C(p,i) be "p.terms[i].coeff"
// E(p,i) be "p.terms[i].expt"
// length(p) be "p.terms.count"
// (These are helper functions that will make it easier for us
// to write the remainder of the specifications. They are not
// executable code; they just represent complex expressions in a
// concise manner, so that we can stress the important parts of
// other expressions in the spec rather than get bogged down in
// the details of how we extract the coefficient for the 2nd term
// or the exponent for the 5th term. So when you see C(p,i),
// think "coefficient for the ith term in p".)
// Abstraction Function:
// AF(self) is the polynomial equal to the sum of the
// RatTerms contained in 'terms':
// sum (0 <= i < length(self)): self.terms[i]
// If there are no terms, then the RatPoly represents the zero
// polynomial.
// Representation Invariant RI(self):
// forall i such that (0 <= i < length(self)), C(self,i) != 0 &&
// forall i such that (0 <= i < length(self)), E(self,i) >= 0 &&
// forall i such that (0 <= i < length(self) - 1), E(self,i) > E(self, i+1)
// In other words:
// * The terms field always points to some usable object.
// * No term in a RatPoly has a zero coefficient.
// * No term in a RatPoly has a negative exponent.
// * The terms in a RatPoly are sorted in descending exponent order.
/// A constant holding a Not-a-Number (NaN) value of type RatPoly.
public static let nan = RatPoly(term: RatTerm.nan)
/// A constant holding a zero value of type RatPoly.
public static let zero = RatPoly()
/**
**Effects**: Constructs a new Poly, "0".
*/
public init() {
terms = []
}
/**
**Requires**: term.expt >= 0
**Effects**: Constructs a new Poly equal to "term". If term.coeff is zero,
constructs a "0" polynomial.
- Parameter term: The single term which the new RatPoly equals.
*/
public init(term : RatTerm) {
// TODO: Write Me
// preconditionFailure("RatPoly.init not implemented yet.")
// Remove the following line:
terms = []
}
/**
Builds a new RatPoly, given a descriptive String.
Valid inputs include "0", "x-10", and "x^3-2*x^2+5/3*x+3", and "NaN".
**Requires**: 'polyStr' is an instance of a string with no spaces that
expresses a poly in the form defined in the description computer property.
- Parameter description: A string of the format described in
the **Requires** clause.
- Returns: a RatPoly p such that p.description = description if description
is an instance of a string with no spaces that expresses a poly in the
form defined in the description computed property. if description is
not valid in that regard, this initializer returns nil.
*/
public convenience init?(_ description: String) {
if (description == "NaN") {
self.init(term: RatTerm.nan)
} else if (description == "") {
return nil
} else {
var terms = [RatTerm]()
let scanner = Scanner(string: description)
while (!scanner.isAtEnd) {
let negative = scanner.match(token: "-")
// at start of term...
let termStr = scanner.scanUpToCharacters(from: CharacterSet(charactersIn: "+-"))!
if let term = RatTerm(termStr) {
if (term != RatTerm.zero) {
terms = RatPoly.insertOrdered(lst: terms, term: (negative ? -term : term))
}
} else {
return nil
}
let _ = scanner.match(token: "+")
}
self.init(terms: terms)
}
}
/**
Builds a new RatPoly, given a constant and exponent.
**Requires**: e >= 0
**Effects**: Constructs a new Poly equal to "c*x^e". If c is zero, constructs
a "0" polynomial.
- Parameter c: The constant in the term which the new RatPoly equals.
- Parameter e: The exponent in the term which the new RatPoly equals.
*/
convenience init(c : Int, e : Int) {
// TODO: Write Me
// preconditionFailure("RatPoly.init not implemented yet.")
// Remove the following line:
self.init()
}
/**
**Requires**: 'rt' satisfies clauses given in rep. invariant
**Effects**: Constructs a new Poly using 'terms' as part of the representation.
The method does not make a copy of 'terms'.
- Parameter terms: A list of terms to be contained in the new RatPoly.
*/
init(terms: [RatTerm]) {
self.terms = terms
}
/**
The degree of this RatPoly, which must not be NaN. In other words,
the largest exponent with a non-zero coefficient, or 0 if self is
"0".
**Requires**: !self.isNan
- Note: It's a little unusual for a computed property to have
a precondition, but you can just assert it as usual at the
beginning of the code below.
*/
public var degree : Int {
// TODO: Write Me
// preconditionFailure("RatPoly.degree not implemented yet.")
// Remove the following line:
return 0
}
/// true if and only if self has some coefficient = "NaN".
public var isNaN : Bool {
// TODO: Write Me
// preconditionFailure("RatPoly.isNaN not implemented yet.")
// Remove the following line:
return false
}
/**
Gets the RatTerm associated with given degree, using Swift's subscripting
features. Example: if p is the RatPoly for "10 + x + 3*x^2", then
p[0] is the RatTerm "10", and p[2] is RatTerm "3*x^2"
**Requires**: !self.isNaN
- Parameter degree: The degree for which to find the corresponding RatTerm.
- Returns: the RatTerm of degree 'deg'. If there is no term of degree 'deg'
in this poly, then returns the zero RatTerm.
*/
subscript(_ degree: Int) -> RatTerm {
// TODO: Write Me
// preconditionFailure("RatPoly subscript not implemented yet.")
// Remove the following line:
return RatTerm.nan
}
/**
Scales coefficients of 'lst' by 'scalar' (helper procedure).
SeeAlso: `RatTerm` regarding (C . E) notation
- Parameter lst: The RatTerms to be scaled.
- Parameter scalar: The value by which to scale coefficients in lst.
- Returns: The scaled RatTerms. That is,
Forall i s.t. 0 <= i < lst.count, if lst.get(i) = (C . E) then result.get(i) = (C*scalar . E)
*/
private static func scaleCoefficients(lst: [RatTerm], scalar : RatNum) -> [RatTerm] {
// TODO: Write Me if you wish to use this helper
preconditionFailure("RatPoly.scaleCoefficients not implemented yet.")
}
/**
Increments exponents of 'lst' by 'degree' (helper procedure).
SeeAlso: `RatTerm` regarding (C . E) notation
- Parameter lst: The RatTerms whose exponents are to be incremented.
- Parameter degree: the value by which to increment exponents in lst.
- Returns: A list of RatTerms such that:
Forall i s.t. 0 <= i < lst.count,
if (C . E) = lst.get(i) then result.get(i) = (C . E+degree)
*/
private static func incrementExponents(lst: [RatTerm], degree : Int) -> [RatTerm] {
// TODO: Write Me
// preconditionFailure("RatPoly.incrementExponents not implemented yet.")
// Remove the following line:
return []
}
/**
Helper procedure: Inserts a term into a sorted sequence of terms,
preserving the sorted nature of the sequence. If a term with the given
degree already exists, adds their coefficients.
Definitions: Let a "Sorted [RatTerm]" be a [RatTerm] V such
that:
1. V is sorted in descending exponent order,
2. there are no two RatTerms with the same exponent in V, and
3. there is no RatTerm in V with a coefficient equal to zero.
For a Sorted [RatTerm] V and integer e, let cofind(V, e) be either
the coefficient for a RatTerm rt in V whose exponent is e, or zero if
there does not exist any such RatTerm in V. (This is like the coeff
function of RatPoly.) We will write sorted(lst) to denote that lst is a
Sorted [RatTerm], as defined above.
**Requires**: sorted(lst)
- Parameter lst: The list into which newTerm should be inserted.
- Parameter newTerm: The term to be inserted into the list.
- Returns: A [RatTerm] result such that:
- sorted(result)
- (cofind(result,newTerm.expt) = cofind(lst,newTerm.expt) + newTerm.coeff)
*/
private static func insertOrdered(lst: [RatTerm], term : RatTerm) -> [RatTerm] {
// TODO: Write Me
// preconditionFailure("RatPoly.insertOrdered not implemented yet.")
// Remove the following line:
return []
}
/**
Return the additive inverse of this RatPoly.
- Returns: a RatPoly equal to "0 - rhs"; if rhs.isNaN, returns some r
such that r.isNaN
*/
public static prefix func -(rhs: RatPoly) -> RatPoly {
// TODO: Write Me
// preconditionFailure("RatPoly unary - not implemented yet.")
// Remove the following line:
return rhs
}
/**
Addition operation.
- Returns: a RatPoly, r, such that r = "lhs + rhs"; if lhs.isNaN or
rhs.isNaN, returns some r such that r.isNaN
*/
public static func +(lhs: RatPoly, rhs: RatPoly) -> RatPoly {
// TODO: Write Me
// preconditionFailure("RatPoly + not implemented yet.")
// Remove the following line:
return lhs
}
/**
Subtraction operation.
- Returns: a RatPoly, r, such that r = "lhs - rhs"; if lhs.isNaN or
rhs.isNaN, returns some r such that r.isNaN
*/
public static func -(lhs: RatPoly, rhs: RatPoly) -> RatPoly {
// TODO: Write Me
// preconditionFailure("RatPoly - not implemented yet.")
// Remove the following line:
return lhs
}
/**
Multiplication operation.
- Returns: a RatPoly, r, such that r = "lhs * rhs"; if lhs.isNaN or
rhs.isNaN, returns some r such that r.isNaN
*/
public static func *(lhs: RatPoly, rhs: RatPoly) -> RatPoly {
// TODO: Write Me
// preconditionFailure("RatPoly * not implemented yet.")
// Remove the following line:
return lhs
}
/**
Division operation (truncating).
Division of polynomials is defined as follows: Given two polynomials u
and v, with v != "0", we can divide u by v to obtain a quotient
polynomial q and a remainder polynomial r satisfying the condition u = "q *
v + r", where the degree of r is strictly less than the degree of v, the
degree of q is no greater than the degree of u, and r and q have no
negative exponents.
For the purposes of this class, the operation "u / v" returns q as
defined above.
The following are examples of div's behavior:
- "x^3-2*x+3" / "3*x^2" = "1/3*x" (with r = "-2*x+3")
- "x^2+2*x+15 / 2*x^3" = "0" (with r = "x^2+2*x+15")
- "x^3+x-1 / x+1 = x^2-x+2 (with r = "-3")
Note that this truncating behavior is similar to the behavior of integer
division on computers.
- Returns: a RatPoly, r, such that r = "lhs / rhs"; if lhs.isNaN or
rhs.isNaN or rhs == zero, returns some r such that r.isNaN
*/
public static func /(lhs: RatPoly, rhs: RatPoly) -> RatPoly {
// TODO: Write Me
// preconditionFailure("RatPoly / not implemented yet.")
// Remove the following line:
return lhs
}
/**
Return the derivative of this RatPoly.
The derivative of a polynomial is the sum of the derivative of each term.
- Returns: a RatPoly, q, such that q = dy/dx, where self == y. In other
words, q is the derivative of self. If self.isNaN, then return
some q such that q.isNaN
*/
public func differentiate() -> RatPoly {
// TODO: Write Me
// preconditionFailure("RatPoly.differentiate not implemented yet.")
// Remove the following line:
return self
}
/**
Returns the antiderivative of this RatPoly.
The antiderivative of a polynomial is the sum of the antiderivative of
each term plus some constant.
- Parameter integrationConstant: The constant of integration to use when
computating the antiderivative.
- Returns: a RatPoly, q, such that dq/dx = self and the constant of
integration is "integrationConstant" In other words, q is the
antiderivative of self. If self.isNaN or integrationConstant.isNaN,
then return some q such that q.isNaN.
*/
public func antiDifferentiate(withConstant integrationConstant : RatNum) -> RatPoly {
// TODO: Write Me
// preconditionFailure("RatPoly.antiDifferentiate not implemented yet.")
// Remove the following line:
return self
}
/**
Returns the integral of this RatPoly, integrated from lowerBound to
upperBound.
The Fundamental Theorem of Calculus states that the definite integral of
f(x) with bounds a to b is F(b) - F(a) where dF/dx = f(x) NOTE: Remember
that the lowerBound can be higher than the upperBound.
- Parameter lowerBound: The lower bound of integration.
- Parameter upperBound: The upper bound of integration.
- Returns: a double that is the definite integral of self with bounds of
integration between lowerBound and upperBound. If self.isNaN,
or either lowerBound or upperBound is Double.NaN, return
Double.NaN.
*/
public func integrate(from lowerBound: Double, to upperBound: Double) -> Double {
// TODO: Write Me
// preconditionFailure("RatPoly.integrate not implemented yet.")
// Remove the following line:
return 0.0
}
/**
Returns the value of this RatPoly, evaluated at d.
- Parameter d: The value at which to evaluate this polynomial.
- Returns: the value of this polynomial when evaluated at 'd'. For example,
"x+2" evaluated at 3 is 5, and "x^2-x" evaluated at 3 is 6. if
(self.isNaN), return Double.NaN
*/
public func eval(at d : Double) -> Double {
// TODO: Write Me
// preconditionFailure("RatPoly.eval not implemented yet.")
// Remove the following line:
return 0.0
}
/**
A String representation of the expression represented by this,
with the terms sorted in order of degree from highest to lowest.
There is no whitespace in the returned string.
If the polynomial is itself zero, the returned string will just
be "0".
If self.isNaN, then the returned string will be just "NaN"
The string for a non-zero, non-NaN poly is in the form
"(-)T(+|-)T(+|-)...", where "(-)" refers to a possible minus
sign, if needed, and "(+|-)" refer to either a plus or minus
sign, as needed. For each term, T takes the form "C*x^E" or "C*x"
where C > 0, UNLESS: (1) the exponent E is zero, in which case T
takes the form "C", or (2) the coefficient C is one, in which
case T takes the form "x^E" or "x". In cases were both (1) and
(2) apply, (1) is used.
Valid example outputs include "x^17-3/2*x^2+1", "-x+1", "-1/2",
and "0".
*/
public var description: String {
get {
if (terms.count == 0) {
return "0";
} else if (isNaN) {
return "NaN"
} else {
return terms.reduce("", {
let termAsString = String(describing:$1)
if $0 == "" {
return termAsString
} else if ($1.coeff.isNegative) {
return $0 + termAsString
} else {
return $0 + "+" + termAsString
}
})
}
}
}
/**
Standard equality operation.
- Returns: true if and only if 'lhs' and 'rhs' represent the same
rational polynomial. Note that all NaN RatPolys are equal.
*/
public static func ==(lhs: RatPoly, rhs: RatPoly) -> Bool {
return lhs.isNaN && rhs.isNaN || lhs.terms == rhs.terms
}
/**
Checks that the representation invariant holds (if any).
*/
private func checkRep() {
for term in terms {
assert(term.coeff != RatNum.zero, "zero coefficient")
assert(term.exponent >= 0, "negative exponent")
}
if (terms.count > 0) {
for i in terms.startIndex..<terms.endIndex-1 {
assert(terms[i+1].exponent < terms[i].exponent, "terms out of order")
}
}
}
}
/**
Extensions to make `Scanner` more friendly to use in Swift code. The
provided methods are counter-parts to the built-in library class, except
that they return their results rather than passing them via in/out parameters.
*/
extension Scanner {
/// - Returns: A string read from the scanner up to one of the chars in set,
/// or nil if that cannot be done.
func scanUpToCharacters(from set: CharacterSet) -> String? {
var result: NSString?
return scanUpToCharacters(from: set, into: &result) ? (result! as String) : nil
}
/// - Returns: A string read from the scanner up to the provided string,
/// or nil if that cannot be done.
func scanUpTo(_ string: String) -> String? {
var result: NSString?
return self.scanUpTo(string, into: &result) ? (result! as String) : nil
}
/// - Returns: A double read from the scanner, or nil if the next token
/// is not a double.
func scanDouble() -> Double? {
var double: Double = 0
return scanDouble(&double) ? double : nil
}
/// - Returns: Scan past a given string, returning whether it succeeds.
func match(token : String) -> Bool {
return scanString(token, into: nil)
}
}