寻找处理关系(即相等值)的FP排名实现

时间:2016-11-02 05:30:57

标签: scala functional-programming

从排序的值序列开始,我的目标是为每个值分配一个等级,使用相同的等级来表示相等的值(也就是关系):

输入:Vector(1, 1, 3, 3, 3, 5, 6)

输出:Vector((0,1), (0,1), (1,3), (1,3), (1,3), (2,5), (3,6))

可读性的几种类型别名:

type Rank = Int
type Value = Int
type RankValuePair = (Rank, Value)

使用可变rank变量的命令式实现可能如下所示:

var rank = 0
val ranked1: Vector[RankValuePair] = for ((value, index) <- values.zipWithIndex) yield {
  if ((index > 0) && (values(index - 1) != value)) rank += 1
  (rank, value)
}

// ranked1: Vector((0,1), (0,1), (1,3), (1,3), (1,3), (2,5), (3,6))

为了磨练我的FP技能,我试图提出一个功能实现:

val ranked2: Vector[RankValuePair] = values.sliding(2).foldLeft((0 , Vector.empty[RankValuePair])) {
  case ((rank: Rank, rankedValues: Vector[RankValuePair]), Vector(currentValue, nextValue)) =>
    val newRank = if (nextValue > currentValue) rank + 1 else rank
    val newRankedValues =  rankedValues :+ (rank, currentValue)
    (newRank, newRankedValues)
}._2

// ranked2: Vector((0,1), (0,1), (1,3), (1,3), (1,3), (2,5))

它的可读性较差,而且 - 更重要的是 - 缺少最后一个值(由于在奇数个值上使用sliding(2))。

如何修复和改进?

6 个答案:

答案 0 :(得分:11)

这对我很有用:

// scala
val vs = Vector(1, 1, 3, 3, 3, 5, 6)
val rank = vs.distinct.zipWithIndex.toMap
val result = vs.map(i => (rank(i), i))

使用Javaslang的Java 8中也是如此:

// java(slang)
Vector<Integer>                  vs = Vector(1, 1, 3, 3, 3, 5, 6);
Function<Integer, Integer>       rank = vs.distinct().zipWithIndex().toMap(t -> t);
Vector<Tuple2<Integer, Integer>> result = vs.map(i -> Tuple(rank.apply(i), i));

两种变体的输出都是

Vector((0, 1), (0, 1), (1, 3), (1, 3), (1, 3), (2, 5), (3, 6))

*)披露:我是Javaslang

的创建者

答案 1 :(得分:5)

这很简洁,但它假设你的Value不会消极。 (实际上它只是假设它们永远不会以-1开头。)

val vs: Vector[Value] = Vector(1, 1, 3, 3, 3, 5, 6)

val rvps: Vector[RankValuePair] =
  vs.scanLeft((-1,-1)){ case ((r,p), v) =>
    if (p == v) (r, v) else (r + 1, v)
  }.tail

修改

不做任何假设的修改,正如@Kolmar所建议的那样。

vs.scanLeft((0,vs.headOption.getOrElse(0))){ case ((r,p), v) =>
  if (p == v) (r, v) else (r + 1, v)
}.tail

答案 2 :(得分:1)

这是一种递归,模式匹配和守卫的方法。

有趣的部分是从列表中解构尾部的头部和头部(分别为hht),if检查它们是否相等。每个案例的逻辑调整等级并继续列表的剩余部分。

def rank(xs: Vector[Value]): List[RankValuePair] = {
  def rankR(xs: List[Value], acc: List[RankValuePair], rank: Rank): List[RankValuePair] = xs match{
    case Nil => acc.reverse
    case h :: Nil => rankR(Nil, (rank, h) :: acc, rank)
    case h :: ht :: t if (h == ht) => rankR(xs.tail, (rank, h) :: acc, rank)
    case h :: ht :: t if (h != ht) => rankR(xs.tail, (rank, h) :: acc, rank + 1)
  }
  rankR(xs.toList, List[RankValuePair](), 0)
}

输出:

scala> rank(xs)
res14: List[RankValuePair] = List((0,1), (0,1), (1,3), (1,3), (1,3), (2,5), (3,6))

答案 3 :(得分:1)

这是@jwvh对解决方案的修改,它没有对值进行任何假设:

val vs = Vector(1, 1, 3, 3, 3, 5, 6)
vs.sliding(2).scanLeft(0, vs.head) {
  case ((rank, _), Seq(a, b)) => (if (a != b) rank + 1 else rank, b)
}.toVector

请注意,如果vs为空,则会抛出,因此您必须使用vs.headOption getOrElse 0,或事先检查输入是否为空:if (vs.isEmpty) Vector.empty else ...

答案 4 :(得分:0)

import scala.annotation.tailrec

type Rank = Int
// defined type alias Rank
type Value = Int
// defined type alias Value
type RankValuePair = (Rank, Value)
// defined type alias RankValuePair

def rankThem(values: List[Value]): List[RankValuePair] = {

  // Assumes that the "values" are sorted
  @tailrec
  def _rankThem(currentRank: Rank, currentValue: Value, ranked: List[RankValuePair], values: List[Value]): List[RankValuePair] = values match {
    case value :: tail if value == currentValue => _rankThem(currentRank, value, (currentRank, value) +: ranked, tail)
    case value :: tail if value > currentValue => _rankThem(currentRank + 1, value, (currentRank + 1, value) +: ranked, tail)
    case Nil => ranked.reverse
  }

  _rankThem(0, Int.MinValue, List.empty[RankValuePair], values.sorted)
}
// rankThem: rankThem[](val values: List[Value]) => List[RankValuePair]

val valueList = List(1, 1, 3, 3, 5, 6)
// valueList: List[Int] = List(1, 1, 3, 3, 5, 6)

val rankValueList = rankThem(valueList)[RankedValuePair], values: Vector[Value])
// rankValueList: List[RankValuePair] = List((1,1), (1,1), (2,3), (2,3), (3,5), (4,6))

答案 5 :(得分:0)

val list = List(1, 1, 3, 3, 5, 6)
val result = list
    .groupBy(identity)
    .mapValues(_.size)
    .toArray
    .sortBy(_._1)
    .zipWithIndex
    .flatMap(tuple => List.fill(tuple._1._2)((tuple._2, tuple._1._1)))
result: Array[(Int, Int)] = Array((0,1), (0,1), (1,3), (1,3), (2,5), (3,6))

这个想法是使用groupBy找到相同的元素并找到它们的出现,然后排序然后flatMap。我会说时间复杂度是O(nlogn),groupBy是O(n),sort是O(nlogn),fl