2011-12-30 68 views
9

「慣用」Scala中表達此功能的一些想法是什麼?或者更確切地說,有沒有一種方法可以在不犧牲可讀性的情況下去除局部變量?強制性代碼的習慣性Scala解決方案

def solve(threshold: Int)(f: Int => Int): Int = { 
    var sum = 0 
    var curr = 0 
    while(sum < threshold) { 
    sum += f(curr) 
    curr += 1 
    } 
    curr 
} 

我能想出的唯一的東西就是這個,但是在我看來,它更長,更不可讀。

def solve2(threshold: Int)(f: Int => Int): Int = { 
    val resultIterator = Iterator.iterate (0, 0) { case (curr, sum) => 
    (curr + 1, sum + f(curr)) 
    } 
    (resultIterator find (_._2 >= threshold)).get._1 
} 
+0

這是很難決定要_correct_,因爲所有的人都很好,所以我挑,似乎最直觀的一個哪一個我 – 2011-12-31 17:49:57

+0

@丹伯頓的解決方案給了我工具箱的最新技巧。 – 2011-12-31 18:06:58

回答

10

最直接的方法是將while循環變成嵌套的尾遞歸函數。

def solve(threshold: Int)(f: Int => Int): Int = { 
    def solveLoop(sum: Int, curr: Int): Int = if (sum < threshold) { 
     solveLoop(sum + f(curr), curr + 1) 
    } else { 
     curr 
    } 
    solveLoop(0,0) 
} 

這是標準的「功能性」循環方式。

+0

經過反思,這只是我的「直到」解決方案的非抽象版本。 :) – 2011-12-31 15:35:26

13
def solve(threshold: Int)(f: Int => Int): Int = { 
    Iterator.from(0).map(f).scanLeft(0)(_ + _).indexWhere(threshold <=) 
} 

在我看來,循環版本更清晰。

7

你可以

def solve(threshold: Int, i: Int = 0)(f: Int => Int) = { 
    if (threshold <= 0) i else solve(threshold - f(i), i+1)(f) 
} 

,但我不知道這其實更清晰。請注意,它實際上是比while循環的壓縮版本更多的字符:

def solve(threshold: Int)(f: Int => Int) = { 
    var s,i = 0; while (s < threshold) { s += f(i); i += 1 }; i 
} 

循環使用可變的變量並不總是壞「習慣」或沒有。只要保持可變狀態安全地包含在函數中,並且所有其他人都可以看到是一個無狀態函數。

順便說一句,雖然sum是一個明智的變量名,curr是有問題的。 i有什麼問題?它被廣泛用作指數變量,無論如何,有一個變量是一件令人討厭的事情;關鍵是你需要採取一些措施,不管它是什麼,每次一步,然後返回。這是邏輯流程,而不是名稱,它告訴你(和其他人)它是什麼。

+0

我發現'curr'也分散注意力。 – huynhjl 2011-12-30 21:07:46

+0

你是對的變量命名。我只能說,在原來的情況下,這個名字更有意義。 – 2011-12-31 17:44:06

1

我總是想知道人們在談論「慣用」scala時。因爲在我看來,每個人都有自己的慣用感。如果您正在尋找功能性解決方案,我想建議您查看「迭代器模式的本質」。實際上有在這個斯卡拉一個很好的博客帖子看看這裏:http://etorreborre.blogspot.com/2011/06/essence-of-iterator-pattern.html

+0

你可以用一些代碼來展示'遍歷'在這裏如何? – missingfaktor 2011-12-31 04:49:12

+0

那麼,如果你看:閾值,總和和當前。他對這些價值觀做了什麼?他使用這些來產生一個有限的「數據流」,而不是他的功能。相信?:) – AndreasScheinert 2011-12-31 10:34:15

+0

那麼,如果你看:閾值,總和和當前。他對這些價值觀做了什麼?他使用這些來產生一個有限的「數據流」,而不是他的功能。相信?實際上,我更喜歡scanLeft實現。習慣性非常主觀,有些人指的是簡潔或什麼。在穿透狀態時生成一個序列並應用一個函數。這取決於你如何表達這一點。 – AndreasScheinert 2011-12-31 10:47:52

4

這裏是我會怎麼做它在Haskell:

solve threshold f = snd $ until test step (0, 0) 
    where test (sum, i) = sum >= threshold 
     step (sum, i) = (sum + f i, succ i) 

這顯然標誌着teststep,初始值,就像命令式的版本一樣。我不知道如果Scala有在某處庫until,卻是微不足道的定義:

def until[A](test: A => Boolean)(f: A => A)(v: A): A = { 
    if (test(v)) { 
    v 
    } else { 
    until(test)(f)(f(v)) 
    } 
} 

def solve(threshold: Int)(f: Int => Int): Int = { 
    def test = (sum: Int, i: Int) => sum >= threshold 
    def step = (sum: Int, i: Int) => (sum + f(i), i + 1) 
    until(test.tupled)(step.tupled)((0, 0))._2 
} 
+2

+1,這看起來像一個非常方便的功能。你應該添加一個等效的Scala到你的答案IMO。 – missingfaktor 2011-12-31 07:14:41

+0

@missingfaktor完成:)我昨晚在我的手機上寫了原文,並且懶得在Scala中測試它。我不確定自己是否喜歡使用currying,這與Scala的tail call優化是一致的,但是我能說什麼,我喜歡Haskell的currying。 – 2011-12-31 15:22:13