1

I'm working out of Scala for the Impatient (3rd Edition). (I'm not doing this for a class.). I'm trying to implement a factorial function using to and reduceLeft without a loop or recursion. This is what I have come up with.

def computeFactorial(startingInt: Int): Int = {
  if startingInt == 0 then
    1
  else {
    val i = startingInt - 1
    (i to 1).foldLeft(startingInt)(_ * _)
  }
}
computeFactorial(0)
computeFactorial(5)

The function works when I pass in zero. However, when I pass in 5, I get 5 instead of 120. I obviously don't understand how foldLeft works because I thought the result would be this:

((5 * 4) * 3) * 2) * 1)

I tried to debug this in IntelliJ with breakpoints but foldLeft port of the code is a black box, and I can't figure out what is going on in there. Any help would be appreciated?

1
  • 5
    The problem is how you express the range. It should be (n to 1 by -1). Without the -1, the default value is 1. Commented Jun 14 at 19:57

2 Answers 2

5

As mentioned in a comment, to takes an optional step argument. Alternatively, you can change the step with the by method; the following examples should give you an idea of how it works:

assert((1 to 3) == List(1, 2, 3))
assert((3 to 1) == List())
assert(3.to(1, -1) == List(3, 2, 1))
assert((3 to 1 by -1) == List(3, 2, 1))
assert(1.to(5, 2) == List(1, 3, 5))
assert((1 to 5 by 2) == List(1, 3, 5))

I had a look at the documentation for to and indeed it's not explicit in this regard.

As you already noticed, since multiplication is commutative, you can simply use the range growing from 1 to n. Notice that you can take advantage of this further to simplify your function as follows:

def computeFactorial(n: Int): Int =
  1.to(n).foldLeft(1)(_ * _)

assert(computeFactorial(0) == 1)
assert(computeFactorial(5) == 120)

You can play around with this code here on Scastie.

As a final note, both your implementation and my suggestion have the problem of being defined for negative numbers, which the factorial is not. A way to express this explicitly is to use the Option type as in the following example:

def computeFactorial(n: Int): Option[Int] =
  Option.when(n >= 0) { 1.to(n).foldLeft(1)(_ * _) }

assert(computeFactorial(-1) == None)
assert(computeFactorial(0) == Some(1))
assert(computeFactorial(5) == Some(120))

This code is also available on Scastie for you to play with.

Since the exercise required you use reduceLeft, this is an alternative, although I find foldLeft to be a better fit for this problem in particular (but of course, if it's about learning, experimenting is great).

def computeFactorial(n: Int): Option[Int] = {
  if (n < 0) {
    None
  } else if (n == 0) {
    Some(1)
  } else {
    Some((1 to n).reduceLeft(_ * _))
  }
}

assert(computeFactorial(-1) == None)
assert(computeFactorial(0) == Some(1))
assert(computeFactorial(5) == Some(120))
Sign up to request clarification or add additional context in comments.

Comments

2

This worked.

def computeFactorial(startingInt: Int): Int = {
  if startingInt == 0 then
    1
  else {
    val i = startingInt - 1
    val numbers = (1 to i).toArray.reverse
    numbers.foldLeft(startingInt)(_ * _)
  }
}
computeFactorial(0)
computeFactorial(5)

I guess I can't just do:

(4 to 1).toArray

Apparently, I don't understand how to works.

3 Comments

def computeFactorial(startingInt: Int): Int = (1 to startingInt).product
def computeFactorial(startingInt: Int): Int = (1 to startingInt).foldRight(1)(_ * _)
def computeFactorial(startingInt: Int): Int = (1 to startingInt).foldLeft(1)(_ * _)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.