/**
 * Exercise 5.2.1 1. The sum function uses a linear recursion. Can you write a
 * tail-recursive one by filling in the ??’s?
 *
 * def sum(f: Int => Int)(a: Int, b: Int): Int = {
 *   def iter(a: Int, result: Int): Int = {
 *     if (??) ??
 *     else iter(??, ??)
 *   }
 *   iter(??, ??)
 * }
 */

def sum(f: Int => Int)(a: Int, b: Int): Int = {
  def iter(a: Int, result: Int): Int = {
    if (a > b)
      result 
    else
      iter(a + 1, f(a) + result)
  }
  iter(a, 0)
}

println(sum(x => x)(1, 30))
println(sum(x => x*x)(1, 30))

 

 

/**
 * Exercise 5.2.2 Write a function product that computes the product of the
 * values of functions at points over a given range.
 */

def product(f: BigInt => BigInt)(a: BigInt, b: BigInt): BigInt = {
  def iter(a: BigInt, result: BigInt): BigInt = {
    if (a > b)
      result 
    else
      iter(a + 1, f(a) * result)
  }
  iter(a, 1)
}

println(product(x => x)(1, 30))
println(product(x => x*x)(1, 30))

 

 

/**
 * Exercise 5.2.3 Write factorial in terms of product.
 */

def product(f: BigInt => BigInt)(a: BigInt, b: BigInt): BigInt = {
  def iter(a: BigInt, result: BigInt): BigInt = {
    if (a > b)
      result 
    else
      iter(a + 1, f(a) * result)
  }
  iter(a, 1)
}

def factorial(x: BigInt) = product(x => x)(1, x)

println(factorial(5))
println(factorial(10))
println(factorial(100))

 

 

/**
 * Exercise 5.2.4 Can you write an even more general function which
 * generalizes both sum and product?
 */

def general(op: (BigInt, BigInt) => BigInt)(f: BigInt => BigInt)
           (a: BigInt, b: BigInt): BigInt = {
  def iter(a: BigInt, result: BigInt): BigInt = {
    if (a > b)
      result 
    else
      iter(a + 1, op(f(a), result))
  }
  iter(a, 1)
}

def sum(f: BigInt => BigInt)(a: BigInt, b: BigInt): BigInt =
  general((x, y) => x + y)(f)(a, b)

def product(f: BigInt => BigInt)(a: BigInt, b: BigInt): BigInt =
  general((x, y) => x * y)(f)(a, b)


println(sum(x => x)(1, 30))
println(sum(x => x*x)(1, 30))

println(product(x => x)(1, 30))
println(product(x => x*x)(1, 30))

 

 

/**
 * Exercise 5.3.1 Write a function for cube roots using fixedPoint and
 * averageDamp.
 */

def abs(x: Double) = if (x >= 0) x else -x
val tolerance = 2.2250738585072014E-308 
def isCloseEnough(x: Double, y: Double) = abs((x - y) / x) < = tolerance

def fixedPoint(f: Double => Double)(firstGuess: Double) = {
  def iterate(guess: Double): Double = {
    val next = f(guess)
    if (isCloseEnough(guess, next)) next
    else iterate(next)
  }
  iterate(firstGuess)
}

def averageDamp(f: Double => Double)(x: Double) = (x + f(x)) / 2

def cube(x: Double) = fixedPoint(averageDamp(y => x/(y * y)))(1.0)


println(cube(8))
println(cube(27))
println(cube(64))
println(cube(125))