2017-05-10 57 views
6

我需要一個函數,這是否:映射同時顯示中間狀態

>>> func (+1) [1,2,3] 
[[2,2,3],[2,3,3],[2,3,4]] 

我的真實情況更爲複雜,但這個例子顯示了問題的要點。主要區別在於,實際上使用索引是不可行的。 List應該是TraversableFoldable

編輯:這應該是函數的簽名:

func :: Traversable t => (a -> a) -> t a -> [t a] 

而接近我真正想要的是相同的簽名,以traverse但不能找出我要使用的功能,得到想要的結果。

func :: (Traversable t, Applicative f) :: (a -> f a) -> t a -> f (t a) 
+1

在一般情況下這是不可能的,因爲應用函數的輸出不必與輸入類型相同。所以中間結果可能像'[「Foo」,2,3]'。你應該重新制定你的要求。 –

+1

@EugeneSh。我認爲這暗示着'(+1)'函數必須是一個endo,否則就不可能了,正如你所解釋的那樣。 – chi

+4

這是「小丑和小丑」的工作。人們可以選擇更靈活和更精確地說明中間狀態。例如[([],(1,2),[2,3]),([2],(2,3),[3]),([2,3],(3,4),[ ])],其中狀態列表中的每個元素都具有相應的輸入元素「in focus」,我們可以看到我們已經在左邊的輸出,我們還沒有訪問的輸入在右邊,並且爲焦點元素配對。對於這樣的結構,映射函數不一定是內核。 – pigworker

回答

3

看起來@Benjamin Hodgson誤讀了您的問題,並認爲您希望f適用於每個部分結果中的單個元素。正因爲如此,你最終認爲他的方法並不適用於你的問題,但我認爲它確實如此。考慮下面的變型:

import Control.Monad.State 

indexed :: (Traversable t) => t a -> (t (Int, a), Int) 
indexed t = runState (traverse addIndex t) 0 
    where addIndex x = state (\k -> ((k, x), k+1)) 

scanMap :: (Traversable t) => (a -> a) -> t a -> [t a] 
scanMap f t = 
    let (ti, n) = indexed (fmap (\x -> (x, f x)) t) 
     partial i = fmap (\(k, (x, y)) -> if k < i then y else x) ti 
    in map partial [1..n] 

這裏,indexed工作在狀態單子到一個遞增索引添加到可橫越對象的元件(以及得到的長度「免費」,這意味着什麼):

> indexed ['a','b','c'] 
([(0,'a'),(1,'b'),(2,'c')],3) 

,並再次,作爲本所指出的,它也可以使用mapAccumL寫:

indexed = swap . mapAccumL (\k x -> (k+1, (k, x))) 0 

然後,scanMap採用可遍歷的對象,將其映射到前/後對的相似結構,使用indexed來索引它,並應用一系列partial函數,其中partial i爲第一個i元素選擇「afters」,其餘爲「之前」。

> scanMap (*2) [1,2,3] 
[[2,2,3],[2,4,3],[2,4,6]] 

至於從名單推廣這個別的東西,我無法弄清楚你想與你的第二個簽名到底該怎麼做:

func :: (Traversable t, Applicative f) => (a -> f a) -> t a -> f (t a) 

,因爲如果你擅長這個的你得到的列表:

func' :: (Traversable t) => (a -> [a]) -> t a -> [t a] 

並且它不完全清楚你想在這裏做什麼。

+0

啊,你說的沒錯,我誤解了這個問題。這是正確的答案。不過,我認爲代碼比它需要更復雜一些。你不需要'索引'的基礎設施,在這個基礎設施中你可以標記每個元素,然後回過頭來計算結果:你可以保留我的答案的倒數索引想法,只需將'== 0'更改爲' > = 0',將'f'應用於目標索引之前的每個元素,而不僅僅是單個元素本身。 –

+0

我將第二個簽名專門化爲:'(a - > IO a) - > [a] - > IO [a]'但是沒關係,我可以從這裏開始。 –

+0

感謝「scanMap」這個名稱,它非常適合所討論的功能。 –

2

在列表中,我會使用以下內容。如果不想要,可以隨意丟棄第一個元素。

> let mymap f [] = [[]] ; mymap f [email protected](x:xs) = ys : map (f x:) (mymap f xs) 
> mymap (+1) [1,2,3] 
[[1,2,3],[2,2,3],[2,3,3],[2,3,4]] 

這也可以Foldable工作,當然,一個使用toList後的可摺疊轉換到一個列表。儘管如此,我們仍然希望有一個更好的實現來避免這個步驟,特別是如果我們想保留原始的可摺疊類型,而不僅僅是獲取列表。

+0

是的,但我不能從'List'轉換回我的特定結構... –

+0

@DannyNavarro是的,我同意 - 我看到你最後一次的編輯澄清了這一點。我的解決方案過於具體。 – chi

2

我剛纔叫它func,根據你的問題,因爲我想不出一個更好的名字。

import Control.Monad.State 

func f t = [evalState (traverse update t) n | n <- [0..length t - 1]] 
    where update x = do 
      n <- get 
      let y = if n == 0 then f x else x 
      put (n-1) 
      return y 

的想法是,從nupdate倒計數,當它到達0,我們應用f。我們保持n在狀態monad,以便traverse可以通過穿過可穿越的地方穿過n

ghci> func (+1) [1,1,1] 
[[2,1,1],[1,2,1],[1,1,2]] 

你也許可以節省使用mapAccumL,捕捉在狀態單子穿越的圖案上的HOF幾個按鍵。

+0

不幸的是我不能依靠我的真實世界問題的長度。我無法使用任何「List」特定的東西。 –

+0

@DannyNavarro'length :: Foldable t => t a - > Int' –

+0

我沒有意識到'length'現在是用'Foldable'來實現的。這是非常好的。我仍然認爲我不能將其應用於我的問題,但值得探討這種方法。 –

1

這聽起來有點像沒有焦點的拉鍊;也許是這樣的:

data Zippy a b = Zippy { accum :: [b] -> [b], rest :: [a] } 

mapZippy :: (a -> b) -> [a] -> [Zippy a b] 
mapZippy f = go id where 
    go a [] = [] 
    go a (x:xs) = Zippy b xs : go b xs where 
    b = a . (f x :) 

instance (Show a, Show b) => Show (Zippy a b) where 
    show (Zippy xs ys) = show (xs [], ys) 


mapZippy succ [1,2,3] 
-- [([2],[2,3]),([2,3],[3]),([2,3,4],[])] 

(使用差別在這裏列出了效率的緣故)

要轉換到摺疊看上去有點像一個paramorphism

para :: (a -> [a] -> b -> b) -> b -> [a] -> b 
para f b [] = b 
para f b (x:xs) = f x xs (para f b xs) 

mapZippy :: (a -> b) -> [a] -> [Zippy a b] 
mapZippy f xs = para g (const []) xs id where 
    g e zs r d = Zippy nd zs : r nd where 
    nd = d . (f e:) 

對於任意的遍歷,有一個酷時間旅行狀態變壓器稱爲Tardis,讓你通過狀態向前和向後:

mapZippy :: Traversable t => (a -> b) -> t a -> t (Zippy a b) 
mapZippy f = flip evalTardis ([],id) . traverse g where 
    g x = do 
    modifyBackwards (x:) 
    modifyForwards (. (f x:)) 
    Zippy <$> getPast <*> getFuture 
+0

最後,我將使用類似的東西,因爲它更接近我真正需要的東西。不過,我正在標記@K。 A.布爾答案被接受,因爲在我提供的例子中,它的工作得很好。 –