Scala 以更不变的方式解决此问题的更好方法是什么?
我正试图解决一个关于HackerRank的问题。我试图以更实用的方式(使用不变性)解决这个问题。我尝试了一个解决方案,但我对它没有充分的信心 以下是问题的链接: 我的可变解决方案如下所示:Scala 以更不变的方式解决此问题的更好方法是什么?,scala,functional-programming,immutability,tail-recursion,immutable-collections,Scala,Functional Programming,Immutability,Tail Recursion,Immutable Collections,我正试图解决一个关于HackerRank的问题。我试图以更实用的方式(使用不变性)解决这个问题。我尝试了一个解决方案,但我对它没有充分的信心 以下是问题的链接: 我的可变解决方案如下所示: /** * Mutable solution * MSet => mutable set is used * val pairs => it is delclared var and getting reassigned */ import scala.annotation.t
/**
* 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))
同一解决方案的不可变版本:
/**
* 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))
考虑到函数式编程原理,解决此问题的理想方法是什么?第一种可能性是使用模式匹配:
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)
}
如果将数组
更改为列表
,则会得到一个可读性好的解决方案
一个更实用的解决方案是使用折叠
:
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
}
这是一个有点难读/理解-所以当我开始时,我更喜欢使用递归
的版本
正如jwvh在其评论中所显示的那样——如果你不能用Scala在一行中完成它,你可能会错过一些东西;)
ar.groupBy(identity).map(u._2.length/2).sum
@jwvh感谢您的解决方案。它通过了所有测试用例。我假设“Identity”是指生成的地图的键。这里数组元素的数据类型是Int,所以键的域应该是Int。这种理解正确吗?这是正确的<代码>标识表示“按元素值对元素进行分组”。换句话说,如果a==b
,则它们将被分组在一起。数组#groupBy类型签名是groupBy[K](f:(T)=>K):Map[K,Array[T]]
。由于ar
是Array[Int]
,因此groupBy(identity)
返回Map[Int,Array[Int]]
。感谢您提供的解决方案。这两种解决方案都在工作,并且通过了所有测试用例。