帮助重写功能风格

时间:2011-02-27 19:29:42

标签: scala functional-programming

我正在学习Scala作为我的第一个功能性语言。作为其中一个问题,我试图找到一种生成序列S到n个位置的功能性方法。定义S使得S(1)= 1,并且S(x)= x出现在序列中的次数。 (我不记得这是什么,但我之前在编程书中看过它。)

在实践中,序列如下所示:

  S = 1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7 ...

我可以使用像这样的命令式样式在Scala中轻松生成此序列:

  def genSequence(numItems: Int) = {
    require(numItems > 0, "numItems must be >= 1")
    var list: List[Int] = List(1)
    var seq_no = 2
    var no     = 2
    var no_nos = 0
    var num_made = 1

    while(num_made < numItems) {
      if(no_nos < seq_no) {
        list = list :+ no
        no_nos += 1
        num_made += 1
      } else if(no % 2 == 0) {
        no += 1
        no_nos = 0
      } else {
        no += 1
        seq_no += 1
        no_nos = 0
      }
    }
    list
  }

但我真的不知道如何在不使用vars和while循环的情况下编写它。

谢谢!

5 个答案:

答案 0 :(得分:10)

到目前为止,帕维尔的答案最接近,但效率也很低。这里有两个flatMap和一个zipWithIndex过度杀伤:)

我对所需输出的理解:

  • 结果包含所有正整数(从1开始)至少一次
  • 每个号码n都会显示在输出(n/2) + 1

正如Pavel正确指出的那样,解决方案是从Stream开始,然后使用flatMap

Stream from 1

这将生成Stream,这是一个永无止境的序列,只能按需生成值。在这种情况下,它生成1, 2, 3, 4...一直到Infinity(理论上)或Integer.MAX_VALUE(在实践中)

Streams可以映射,就像任何其他集合一样。例如:(Stream from 1) map { 2 * _ }生成偶数数据流。

您还可以在Streams上使用flatMap,允许您将每个输入元素映射到零个或多个输出元素;这是解决问题的关键:

val strm = (Stream from 1) flatMap { n => Stream.fill(n/2 + 1)(n) }

那么......这是如何运作的?对于元素3,lambda { n => Stream.fill(n/2 + 1)(n) }将生成输出流3,3。对于前5个整数,你会得到:

1 -> 1
2 -> 2, 2
3 -> 3, 3
4 -> 4, 4, 4
5 -> 5, 5, 5
etc.

因为我们正在使用flatMap,所以这些将被连接起来,产生:

1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 5, ...

流已被记忆,因此一旦计算出给定值,它将被保存以供将来参考。但是,所有前面的值必须至少计算一次。如果你想要完整的序列,那么这不会导致任何问题,但它确实意味着从冷启动生成S(10796)会很慢! (与您的命令式算法共享的问题)。如果您需要这样做,那么到目前为止,没有一个解决方案可能适合您。

答案 1 :(得分:6)

以下代码生成与您的完全相同的序列:

val seq = Stream.from(1)
        .flatMap(Stream.fill(2)(_))
        .zipWithIndex
        .flatMap(p => Stream.fill(p._1)(p._2))
        .tail

但是,如果要生成Golomb sequence(符合定义,但与示例代码结果不同),您可以使用以下内容:

val seq = 1 #:: a(2)
def a(n: Int): Stream[Int] = (1 + seq(n - seq(seq(n - 2) - 1) - 1)) #:: a(n + 1)

您可以查看my article以获取有关如何处理功能样式中的数字序列的更多示例。

答案 2 :(得分:0)

这是Scala tyro的尝试。请记住,我不太了解Scala,我真的不明白这个问题,而且我真的不了解你的算法。

def genX_Ys[A](howMany : Int, ofWhat : A) : List[A] = howMany match {
    case 1 => List(ofWhat)
    case _ => ofWhat :: genX_Ys(howMany - 1, ofWhat)
}

def makeAtLeast(startingWith : List[Int], nextUp : Int, howMany : Int, minimumLength : Int) : List[Int] = {
    if (startingWith.size >= minimumLength) 
      startingWith 
    else 
      makeAtLeast(startingWith ++ genX_Ys( howMany, nextUp), 
                 nextUp +1, howMany + (if (nextUp % 2 == 1) 1 else 0), minimumLength)
}

def genSequence(numItems: Int) =  makeAtLeast(List(1), 2, 2, numItems).slice(0, numItems)

这似乎有效,但重新阅读上面的警告。特别是,我确定有一个执行genX_Ys的库函数,但我找不到它。

编辑可能是

def genX_Ys[A](howMany : Int, ofWhat : A) : Seq[A]  = 
   (1 to howMany) map { x => ofWhat }

答案 3 :(得分:0)

以下是将代码转换为更具功能性的样式:

def genSequence(numItems: Int): List[Int] = {
  genSequenceR(numItems, 2, 2, 0, 1, List[Int](1))
}


def genSequenceR(numItems: Int, seq_no: Int, no:Int, no_nos: Int, numMade: Int, list: List[Int]): List[Int] = {
 if(numMade < numItems){
   if(no_nos < seq_no){
     genSequenceR(numItems, seq_no, no, no_nos + 1, numMade + 1, list :+ no)
   }else if(no % 2 == 0){
     genSequenceR(numItems, seq_no, no + 1, 0, numMade, list)
   }else{
     genSequenceR(numItems, seq_no + 1, no + 1, 0, numMade, list)
   }
  }else{
    list
  }
}

genSequenceR是递归函数,它在列表中累积值,并根据条件使用新值调用函数。与while循环一样,当numMade小于numItems并将列表返回genSequence时,它会终止。

这是您的代码的相当基本的功能翻译。它可以改进,并且通常使用更好的方法。我建议尝试使用模式匹配来改进它,然后在这里使用Stream的其他解决方案。

答案 4 :(得分:0)

以下是对Golomb序列的定义的非常直接的“翻译”:

val it = Iterator.iterate((1,1,Map(1->1,2->2))){ case (n,i,m) =>
    val c = m(n)
    if (c == 1) (n+1, i+1, m + (i -> n) - n)
    else (n, i+1, m + (i -> n) + (n -> (c-1)))
}.map(_._1)

println(it.take(10).toList)

tripel(n,i,m)包含实际数字n,索引i和Map m,其中包含n必须重复的频率。当我们的n的Map中的计数器达到1时,我们增加n(并且可以从地图中删除n,因为它不再需要),否则我们只减少地图中的n的计数器并保持n。在每种情况下,我们添加新对i - &gt; n进入地图,稍后将用作计数器(当后续的n达到当前i的值时)。

<强> [编辑]

考虑到这一点,我意识到我不需要索引甚至不需要查找(因为“计数器”已经处于“正确”顺序),这意味着我可以用队列替换Map: / p>

import collection.immutable.Queue

val it = 1 #:: Iterator.iterate((2, 2, Queue[Int]())){
  case (n,1,q) => (n+1, q.head, q.tail + (n+1))
  case (n,c,q) => (n,c-1,q + n)
}.map(_._1).toStream

Iterator在以2开始时正常工作,因此我必须在开头添加1。第二个元组参数现在是当前n的计数器(取自队列)。当前计数器也可以保留在队列中,所以我们只有一对,但由于复杂的队列处理,它不太清楚是什么:

val it = 1 #:: Iterator.iterate((2, Queue[Int](2))){
  case (n,q) if q.head == 1 => (n+1, q.tail + (n+1))
  case (n,q) => (n, ((q.head-1) +: q.tail) + n)
}.map(_._1).toStream