帮助以函数式风格重写
我正在学习 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 循环的情况下编写此代码。
谢谢!
I'm learning Scala as my first functional-ish language. As one of the problems, I was trying to find a functional way of generating the sequence S up to n places. S is defined so that S(1) = 1, and S(x) = the number of times x appears in the sequence. (I can't remember what this is called, but I've seen it in programming books before.)
In practice, the sequence looks like this:
S = 1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7 ...
I can generate this sequence pretty easily in Scala using an imperative style like this:
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
}
But I don't really have any idea how to write this without using vars
and the while loop.
Thanks!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
以下是将代码转换为更实用的风格:
genSequenceR 是递归函数,它累积列表中的值并根据条件使用新值调用该函数。与 while 循环一样,当 numMade 小于 numItems 时,它终止并将列表返回到 genSequence。
这是代码的相当基本的功能翻译。它可以改进,并且通常使用更好的方法。我建议尝试通过模式匹配来改进它,然后研究使用 Stream 的其他解决方案。
Here is a translation of your code to a more functional style:
The genSequenceR is the recursive function that accumulates values in the list and calls the function with new values based on the conditions. Like the while loop, it terminates, when numMade is less than numItems and returns the list to genSequence.
This is a fairly rudimentary functional translation of your code. It can be improved and there are better approaches typically used. I'd recommend trying to improve it with pattern matching and then work towards the other solutions that use Stream here.
这是 Scala 新手的一次尝试。请记住,我不太了解 Scala,我不太了解这个问题,而且我也不太了解你的算法。
这似乎有效,但请重新阅读上面的注意事项。特别是,我确信有一个执行
genX_Ys
的库函数,但我找不到它。编辑可能是
Here's an attempt from a Scala tyro. Keep in mind I don't really understand Scala, I don't really understand the question, and I don't really understand your algorithm.
This seems to work, but re-read the caveats above. In particular, I am sure there is a library function that performs
genX_Ys
, but I couldn't find it.EDIT Could be
这是哥伦布数列定义的非常直接的“翻译”:
三元组 (n,i,m) 包含实际数字 n、索引 i 和 Map m,其中包含 n 必须重复的频率。当映射中 n 的计数器达到 1 时,我们增加 n(并且可以从映射中删除 n,因为不再需要它),否则我们只需减少映射中 n 的计数器并保留 n。在每种情况下,我们都会添加新的对 i -> n 进入映射,稍后将用作计数器(当后续 n 达到当前 i 的值时)。
[编辑]
考虑一下,我意识到我不需要索引,甚至不需要查找(因为“计数器”已经处于“正确”的顺序),这意味着我可以替换带队列的映射:
迭代器从 2 开始时可以正常工作,因此我必须在开头添加 1。第二个元组参数现在是当前 n 的计数器(从队列中获取)。当前计数器也可以保留在队列中,因此我们只有一对,但由于复杂的队列处理,不太清楚发生了什么:
Here is a very direct "translation" of the definition of the Golomb seqence:
The tripel (n,i,m) contains the actual number n, the index i and a Map m, which contains how often an n must be repeated. When the counter in the Map for our n reaches 1, we increase n (and can drop n from the map, as it is not longer needed), else we just decrease n's counter in the map and keep n. In every case we add the new pair i -> n into the map, which will be used as counter later (when a subsequent n reaches the value of the current i).
[Edit]
Thinking about it, I realized that I don't need indexes and not even a lookup (because the "counters" are already in the "right" order), which means that I can replace the Map with a Queue:
The Iterator works correctly when starting by 2, so I had to add a 1 at the beginning. The second tuple argument is now the counter for the current n (taken from the Queue). The current counter could be kept in the Queue as well, so we have only a pair, but then it's less clear what's going on due to the complicated Queue handling:
到目前为止,帕维尔的答案最接近,但效率也很低。两个
flatMap
和一个zipWithIndex
在这里是多余的:)我对所需输出的理解:
(n/2) + 1
次正如 Pavel 正确指出的,解决方案是从
Stream
开始,然后使用 < code>flatMap:这会生成一个
Stream
,这是一个可能永无止境的序列,仅根据需要生成值。在本例中,它生成1, 2, 3, 4...
一直到 Infinity(理论上)或Integer.MAX_VALUE
(实际上)Streams 可以与任何其他集合一样被映射。例如:
(Stream from 1) map { 2 * _ }
生成偶数的 Stream。您还可以在 Streams 上使用
flatMap
,允许您将每个输入元素映射到零个或多个输出元素;这是解决您的问题的关键:那么...这是如何工作的?对于元素
3
,lambda{ n =>; Stream.fill(n/2 + 1)(n) }
将生成输出流3,3
。对于前 5 个整数,您将得到:并且因为我们使用的是
flatMap
,所以这些将被连接起来,产生:流被记忆,因此一旦计算出给定值,它将被保存未来参考。然而,所有前面的值必须至少计算一次。如果您想要完整的序列,那么这不会导致任何问题,但这确实意味着从冷启动生成
S(10796)
会很慢! (与您的命令式算法共享的问题)。如果您需要这样做,那么到目前为止没有一个解决方案可能适合您。Pavel's answer has come closest so far, but it's also inefficient. Two
flatMap
s and azipWithIndex
are overkill here :)My understanding of the required output:
n
appears in the output(n/2) + 1
timesAs Pavel has rightly noted, the solution is to start with a
Stream
then useflatMap
:This generates a
Stream
, a potentially never-ending sequence that only produces values on demand. In this case, it's generating1, 2, 3, 4...
all the way up to Infinity (in theory) orInteger.MAX_VALUE
(in practice)Streams can be mapped over, as with any other collection. For example:
(Stream from 1) map { 2 * _ }
generates a Stream of even numbers.You can also use
flatMap
on Streams, allowing you to map each input element to zero or more output elements; this is key to solving your problem:So... How does this work? For the element
3
, the lambda{ n => Stream.fill(n/2 + 1)(n) }
will produce the output stream3,3
. For the first 5 integers you'll get:and because we're using
flatMap
, these will be concatenated, yielding:Streams are memoised, so once a given value has been calculated it'll be saved for future reference. However, all the preceeding values have to be calculated at least once. If you want the full sequence then this won't cause any problems, but it does mean that generating
S(10796)
from a cold start is going to be slow! (a problem shared with your imperative algorithm). If you need to do this, then none of the solutions so far is likely to be appropriate for you.以下代码生成与您的序列完全相同的序列:
但是,如果您想生成 Golomb 序列 (符合定义,但与示例代码结果不同),您可以使用以下内容:
您可以检查 我的文章了解如何以函数式风格处理数字序列的更多示例。
The following code produces exactly the same sequence as yours:
However, if you want to produce the Golomb sequence (that complies with the definition, but differs from your sample code result), you may use the following:
You may check my article for more examples of how to deal with number sequences in functional style.