Search code examples
scalafunctional-programmingimmutabilitytail-recursionimmutable-collections

What is a better way to solve this exercise in more Immutable way?


I am trying to solve a problem on HackerRank. I am trying to solve this problem in more functional way (using immutability). I have attempted a solution but I am not fully confident about it.

Here’s a link to the problem:

https://www.hackerrank.com/challenges/sock-merchant/problem?h_l=interview&playlist_slugs%5B%5D=interview-preparation-kit&playlist_slugs%5B%5D=warmup

My mutable solution goes like this:

/**
  * Mutable solution
  * MSet => mutable set is used
  * val pairs => it is delclared var and getting reassigned
  */

import scala.annotation.tailrec
import scala.collection.mutable.{Set => MSet}

def sockMerchant2(n: Int, ar: Array[Int]): Int = {
  val sockInventory : MSet[Int] = MSet.empty[Int]
  var pairs = 0
  ar.foreach { elem =>
    if(sockInventory.contains(elem)) {
      pairs = pairs + 1
      sockInventory -= elem
    } else sockInventory += elem
  }
  pairs
}

sockMerchant(5, Array(1,2,1,2,4,2,2))

Immutable version of the same solution:

/**
  * Solution with tail recursion.
  * Immutable Set is used. No variable is getting reassigned
  * How it is getting handled internally ?
  * In each iteration new states are assigned to same variables.
  * @param n
  * @param ar
  * @return
  */

import scala.annotation.tailrec
def sockMerchant(n: Int, ar: Array[Int]): Int = {
  @tailrec
  def loop(arr: Array[Int], counter: Int, sockInventory: Set[Int]): Int ={
    if(arr.isEmpty) counter
    else if(sockInventory.contains(arr.head))
      loop(arr.tail, counter +1, sockInventory-arr.head)
    else loop(arr.tail, counter, sockInventory + arr.head)
  }
  loop(ar, 0, Set.empty)
}

sockMerchant(5, Array(1,2,1,2,4,2,2))

What is ideal way to solve this problem, considering functional programming principles?


Solution

  • First possibility is to use pattern matching:

      def sockMerchant(n: Int, ar: Array[Int]): Int = {
        @tailrec
        def loop(list: List[Int], counter: Int, sockInventory: Set[Int]): Int =
        list match {
          case Nil =>
            counter
          case x::xs if sockInventory.contains(x) =>
            loop(xs, counter +1, sockInventory-x)
          case x::xs =>
            loop(xs, counter, sockInventory + x)
        }
        loop(ar.toList, 0, Set.empty)
      }
    

    If you change the Array to a List you get a good readable solution.

    An even more functional solution would be to use folding:

      def sockMerchant(n: Int, ar: Array[Int]): Int = {
        ar.foldLeft((0, Set.empty[Int])){case ((counter, sockInventory), x: Int) =>
          if (sockInventory.contains(x))
            (counter +1, sockInventory-x)
            else
            (counter, sockInventory + x)
        }._1
      }
    

    This is a bit harder to read/ understand - so when I started I preferred the version with recursion.

    And as jwvh shows in its comment - if you cannot do it in one line with Scala - you may miss something;).