我正在学习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循环的情况下编写它。
谢谢!
答案 0 :(得分:10)
flatMap
和一个zipWithIndex
过度杀伤:)
我对所需输出的理解:
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