我正在寻找以下问题的优雅解决方案。我有两个以下类型的列表:
[Float]
和[[Float]]
第一个列表包含无限的随机值。第二个列表包含我不再关心的值。它的结构是有限的,必须保留。第一个列表的值需要替换第二个列表的值。
显然,由于第一个列表包含随机值,我不想两次使用它们。任何人都可以通过 clear ,简明和简洁的方式帮助我做到这一点吗?
scramble :: [Float] -> [[Float]] -> [[Float]]
给我最好的一击
答案 0 :(得分:9)
使用split包进行拆分:
import Data.List.Split (splitPlaces)
scramble x y = splitPlaces (map length y) x
答案 1 :(得分:8)
这会吗?
flip . (evalState .) . traverse . traverse . const . state $ head &&& tail
编辑:让我扩展建设...
它的基本中心是traverse . traverse
。如果你用足够差的眼镜来盯着这个问题,你会发现它是“用容器容器的元素做些什么”。对于那种事情,traverse
(来自Data.Traversable
)是一个非常有用的小工具(好吧,我有偏见)。
traverse :: (Traversable f, Applicative a) => (s -> a t) -> f s -> a (f t)
或者,如果我改为更长但更具暗示性的类型变量
traverse :: (Traversable containerOf, Applicative doingSomethingToGet) =>
(s -> doingSomethingToGet t) ->
containerOf s -> doingSomethingToGet (containerOf t)
至关重要的是,traverse
保留了它所运行的容器的结构,无论它是什么。如果您将traverse
视为高阶函数,则可以看到它在类型符合其所需元素的运算符类型的容器上返回运算符。这就是说(traverse . traverse)
有意义,并为两个层容器提供了结构保留操作。
traverse . traverse ::
(Traversable g, Traversable f, Applicative a) => (s -> a t) -> g (f s) -> a (g (f t))
因此,我们已经获得了在列表列表中保留结构“执行某些操作”的关键小工具。 length
和splitAt
方法适用于列表(列表的结构由其长度给出),但是启用该方法的列表的基本特征已经被{{1 }。class。
现在我们需要弄清楚如何“做某事”。我们希望用从供应流中连续绘制的新东西替换旧元素。如果我们被允许更新供应的副作用,我们可以说每个元素要做什么:“返回Traversable
供应,用head
更新供应”。 tail
monad(State s
中Control.Monad.State
的实例,来自Applicative
)让我们捕捉到这个想法。类型Control.Applicative
表示在突变类型State s a
的状态时提供类型a
的值的计算。典型的此类计算由此小工具进行。
s
也就是说,给定初始状态,只需计算值和新状态。在我们的示例中,state :: (s -> (a, s)) -> State s a
是一个流,s
获取值,head
获取新状态。 tail
运算符(来自&&&
)是一种很好的方法,可以将两个函数粘合到相同的数据上,以获得一对函数。所以
Control.Arrow
使
head &&& tail :: [x] -> (x, [x])
因此
state $ head &&& tail :: State [x] x
解释了对旧容器的每个元素“做”的内容,即忽略它并从供应流的头部获取一个新元素。
将其提供给const . state $ head &&& tail :: u -> State [x] x
会给我们一个大型的mutatey遍历类型
(traverse . traverse)
其中f (g u) -> State [x] (f (g x))
和f
是任何g
结构(例如列表)。
现在,为了提取我们想要的函数,采用初始供应流,我们需要将状态变异计算解压缩为从初始状态到最终值的函数。这就是它的作用:
Traversable
所以我们最终得到的东西
evalState :: State s a -> s -> a
如果要匹配原始规格,最好翻转。
tl; dr f (g u) -> [x] -> f (g x)
monad是一个现成的工具,用于描述读取和更新输入流的计算。 State [x]
类捕获了一个现成的容器结构保留操作概念。其余的是管道(和/或高尔夫)。
答案 2 :(得分:4)
这是明显的方法,但我认为这不够简洁?
scramble :: [a] -> [[a]] -> [[a]]
scramble _ [] = []
scramble xs (y : ys) = some : scramble rest ys
where (some, rest) = splitAt (length y) xs