推广“下一个排列”功能

时间:2010-11-26 11:42:30

标签: scala types generics permutation

下面是一个函数的实现,它返回字典上的下一个排列。这在Euler问题中很有用。

它是写在Strings上的(我需要它)。但是,它应该适用于任何可比较值的索引序列。我已经尝试通过将String的两次出现更改为IndexedSeq [Char]来推广它,但这会出错:

euler-lib.scala:26: error: type mismatch;
 found   : IndexedSeq[Char]
 required: String
      ((n.slice(pivot+1, successor):+ n(pivot)) + n.drop(successor+1)).reverse
                                                        ^

为什么类型推断器会在那里推断出String?我似乎没有做任何需要字符串的操作?

我可以通过使用IndexedSeq [“可比较的东西”]使其更加通用吗?我无法做到这一点。

  // return the lexographically next permutation to the one passed as a parameter
  // pseudo-code from an article on StackOverflow
  def nextPermutation(n:String):String = {
  // 1. scan the array from right-to-left
  //1.1. if the current element is less than its right-hand neighbor,
  //    call the current element the pivot,
  //    and stop scanning
  // (We scan left-to-right and return the last such).
    val pivot = n.zip(n.tail).lastIndexWhere{ case (first, second) => first < second }

  //1.2. if the left end is reached without finding a pivot,
  //    reverse the array and return
  //    (the permutation was the lexicographically last, so its time to start over)
    if (pivot < 0) return n.reverse

  //2. scan the array from right-to-left again,
  //   to find the rightmost element larger than the pivot
  //  (call that one the successor)
    val successor = n.lastIndexWhere{_ > n(pivot)}

  //3. swap the pivot and the successor, and
  //4. reverse the portion of the array to the right of where the pivot was found
    return (n.take(pivot) :+ n(successor)) +
      ((n.slice(pivot+1, successor):+ n(pivot)) + n.drop(successor+1)).reverse
  }

4 个答案:

答案 0 :(得分:4)

+中的方法IndexedSeq用于生成包含一个额外给定元素的新序列,但您希望生成一个包含其他序列的元素。方法是++,因此您的最后一行必须如下所示:

(n.take(pivot) :+ n(successor)) ++
  ((n.slice(pivot+1, successor):+ n(pivot)) ++ n.drop(successor+1)).reverse

您看到这个奇怪的编译器消息,因为String的签名不匹配,因此需要+,因此用于字符串连接的显式转换会启动(此转换是因为它让你写的是List(8) + " Test")。

编辑:对有序元素的序列类型进行推广:

正如我在评论中所说,序列的泛化有点复杂。除了元素类型A之外,您还需要另一种表示序列的CC[X] <: SeqLike[X,CC[X]]类型。通常C <: SeqLike[A,C]就足够了,但类型推断器不喜欢那个(在调用该方法时,您总是需要传递AC的类型。)

如果您只是以这种方式更改签名,编译器会抱怨它需要隐式CanBuildFrom[CC[A],A,CC[A]]参数,因为需要参数,例如通过reverse方法。该参数用于从另一个序列类型构建一个序列类型 - 只需搜索站点以查看集合API如何使用它的一些示例。

最终结果如下:

import collection.SeqLike
import collection.generic.CanBuildFrom

def nextPermutation[A, CC[X] <: SeqLike[X,CC[X]]](n: CC[A])(
  implicit ord: Ordering[A], bf: CanBuildFrom[CC[A],A,CC[A]]): CC[A] = {

  import ord._
  // call toSeq to avoid having to require an implicit CanBuildFrom for (A,A)
  val pivot = n.toSeq.zip(n.tail.toSeq).lastIndexWhere{
    case (first, second) => first < second
  }

  if (pivot < 0) {
    n.reverse
  }
  else {
    val successor = n.lastIndexWhere{_ > n(pivot)}
    (n.take(pivot) :+ n(successor)) ++
    ((n.slice(pivot+1, successor):+ n(pivot)) ++ n.drop(successor+1)).reverse
  }
}

这样,如果您将方法传递给方法,则会得到Vector[Int],如果将方法传递给List[Double],则会得到String。那么Seq[Char]呢?这些不是实际的序列,但可以隐式转换为Seq[A]。有可能改变该方法的定义,期望某些类型可以隐式转换为String,但是再次类型推断不能可靠地工作 - 或者至少我无法使其可靠地工作。作为一种简单的解决方法,您可以为def nextPermutation(s: String): String = nextPermutation[Char,Seq](s.toSeq).mkString s定义另一种方法:

{{1}}

答案 1 :(得分:1)

小提示:

n(pivot)) + n.drop(successor+1)
                  ^

如果出现类型不匹配错误,并且^指向最后一个参数列表的第一个括号(即,它将指向(中的第二个x.foldLeft(y)(z)),这意味着该方法返回的值的类型错误。

或者,在这种情况下,n.drop(sucessor+1)的类型为IndexedSeq[Char],但+方法需要String

另一个小提示:接受+的唯一内容是数字类和String。如果您尝试添加内容并收到错误,则很可能是Scala认为您使用+添加Strings。例如:

true + true // expected String, got Boolean error
"true" + true // works, the second true is converted to String
true + "true" // works, the first true is converted to String

因此,除非使用数字或字符串,否则请避免使用+

所以,关于制作那个将军......

def nextPermutation[A <% Ordered[A]](n: IndexedSeq[A]): IndexedSeq[A] = {
  val pivot = n.zip(n.tail).lastIndexWhere{ case (first, second) => first < second }
  if (pivot < 0) return n.reverse
  val successor = n.lastIndexWhere{_ > n(pivot)}
  return (n.take(pivot) :+ n(successor)) ++
    ((n.slice(pivot+1, successor):+ n(pivot)) ++ n.drop(successor+1)).reverse
}

简单的部分就是声明IndexedSeq。但是你必须在A上进行参数化,并且必须有一种订购A的方法,以便您可以比较元素(<%表示从AOrdered[A]的隐式转换def nextPermutation[A : Ordering](n: IndexedSeq[A]): IndexedSeq[A] = { val ordering = implicitly[Ordering[A]]; import ordering._ val pivot = n.zip(n.tail).lastIndexWhere{ case (first, second) => first < second } if (pivot < 0) return n.reverse val successor = n.lastIndexWhere{_ > n(pivot)} return (n.take(pivot) :+ n(successor)) ++ ((n.slice(pivot+1, successor):+ n(pivot)) ++ n.drop(successor+1)).reverse } 可用)。声明它的另一种方式是这样的:

A : Ordering

此处,Ordering[A]表示存在可用的隐式<,然后将其获取并导入范围,以便它可以提供隐式转换以使Ordered[A]起作用。 Ordering[A]和{{1}}之间的差异可以在其他问题上找到。

答案 2 :(得分:0)

在Scala 2.8.0中,代码正确编译。您使用的是哪个版本的Scala?

scala> nextPermutation("12354")
res0: String = 12435 

答案 3 :(得分:0)

问题24让我难倒了一段时间:

println("0123456789".permutations.drop(1000000 - 1).next);