0

I stumbled upon this challenge on stackoverflow while learning about property based testing in scala using ScalaCheck.

Find the smallest positive integer that does not occur in a given sequence

I thought of trying to write a generator driven property based test for this problem to check the validity of my program but can't seem to be able to think of a how to write a relevant test case. I understand that I could write a table driven property based testing for this use case but that limit the number of properties I could test this algo with.

import scala.annotation.tailrec

object Solution extends App {
  def solution(a: Array[Int]): Int = {
    val posNums = a.toSet.filter(_ > 0)

    @tailrec
    def checkForSmallestNum(ls: Set[Int], nextMin: Int): Int = {
      if (ls.contains(nextMin)) checkForSmallestNum(ls, nextMin + 1)
      else nextMin
    }

    checkForSmallestNum(posNums, 1)
  }
}
James Z
  • 12,209
  • 10
  • 24
  • 44
iamsmkr
  • 800
  • 2
  • 10
  • 29

1 Answers1

2

Using Scalatest's (since you did tag scalatest) Scalacheck integration and Scalatest matchers, something like

forAll(Gen.listOf(Gen.posNum[Int]) -> "ints") { ints =>
  val asSet = ints.toSet
  val smallestNI = Solution.solution(ints.toArray)
  asSet shouldNot contain(smallestNI)

  // verify that adding non-positive ints doesn't change the result
  forAll(
    Gen.frequency(
      1 -> Gen.const(0),
      10 -> Gen.negNum[Int]
    ) -> "nonPos"
  ) { nonPos =>
    // Adding a non-positive integer to the input shouldn't affect the result
    Solution.solution((nonPos :: ints).toArray) shouldBe smallestNI
  }

  // More of a property-based approach
  if (smallestNI > 1) {
    forAll(Gen.oneOf(1 until smallestNI) -> "x") { x =>
      asSet should contain(x)
    }
  } else succeed  // vacuous

  // Alternatively, but perhaps in a less property-based way
  (1 until smallestNI).foreach { x =>
    asSet should contain(x)
  }
}

Note that if scalatest is set to try forAlls 100 times, the nested property check will check values 10k times. Since smallestNI will nearly always be less than the number of trials (as listOf rarely generates long lists), the exhaustive check will in practice be faster than the nested property check.

The overall trick, is that if something is the least positive integer for which some predicate applies, that's the same as saying that for all positive integers less than that something the predicate does not apply.

Levi Ramsey
  • 18,884
  • 1
  • 16
  • 30
  • Thanks for your answer. Couple of questions though: (1) How does this answer accounts for the range of values an array could hold? As per question it could be anything between -100000 to 100000? (2) the test is failing with error: "oneOf called on empty collection" – iamsmkr Jun 24 '21 at 21:17
  • (3) what is the idea behind converting the generated list to set? – iamsmkr Jun 24 '21 at 21:20
  • 1
    1) You would use `Gen.listOf(Gen.choose(-1000000, 1000000)` – Levi Ramsey Jun 24 '21 at 22:29
  • 2) if `smallestNI` is 1, you can declare the test a success (there's no smaller positive integer) – Levi Ramsey Jun 24 '21 at 22:30
  • 1
    3) a set is much faster to check membership than a sequence – Levi Ramsey Jun 24 '21 at 22:31
  • Alternatively around 1) you can make a stronger claim: that the result doesn't change if negative or zero is included in the input – Levi Ramsey Jun 24 '21 at 22:38
  • A lot of times property based testing, especially if you don't want to implement an "oracle" means that sort of lateral thinking. – Levi Ramsey Jun 24 '21 at 22:39