尝试学习Haskell,最近我几次遇到这种简单的模式:
让我们说我有一个列表,我想更新子列表中的所有元素,例如从索引i到j。
我能想到的所有解决方案都非常糟糕,就像下面的例子一样:
import qualified Data.Sequence as S
increment :: Int -> Int -> S.Seq Int -> S.Seq Int
increment i j sq | i <= j = increment (i + 1) to (S.adjust (+1) i sq)
| otherwise = sq
我想我用清单会更糟。
有人知道一些简单的方法吗?我尝试过搜索,还查看了标准库(Data.Array,Data.Vector等),但是它的编写方式使我的眼睛有点流血,我需要一些人的建议
答案 0 :(得分:3)
我尝试过搜索,还查看了标准库(Data.Array,Data.Vector等),但是它的编写方式使我的眼睛有点流血,我希望得到一些人的建议
是的,直接使用Data.Array
可以解决涉及索引的问题。对于不可变数组,可以使用accum
函数使用(index, value)
对列表更新数组元素以指定索引和值。
例如,假设我们需要将索引2到4的数组[1,2,3,4,5,6,7,8,9,10]
的元素加1。
首先,从列表[1,2,3,4,5,6,7,8,9,10]
构造一个数组:
testArray = let xs = [1..10] in listArray (0, length xs-1) xs
并将accum
函数应用于数组:
increment' i j ary = accum (+) ary (zip [i..j] (repeat 1))
请注意,如果(zip [i..j] (repeat 1))
给出以下内容,则i = 2 and j = 4
会构建一个配对对列表:
[(2, 1), (3, 1), (4, 1)]
pair的第一个值是数组的索引,第二个是(+)
的第二个参数,accum
检索特定索引的值并将(+1)
应用于该值,即正是我们想要的。要对其进行测试:
elems $ increment' 2 4 testArray
给予
[1,2,4,5,6,6,7,8,9,10]
否则,如果您只想用新值替换数组的旧值,而不必关心旧值是什么。 accum
函数也可以在这种情况下应用。例如,假设将索引2到4的元素替换为0
:
accum (flip const) testArray (zip [2..4] (repeat 0))
答案 1 :(得分:2)
一种更有效的方法是在两个端点处拆分序列,在中间部分使用fmap
,然后将这些部分重新结合在一起。
increment :: Int -> Int -> S.Seq Int -> S.Seq Int
increment i j sq = sq0 S.>< fmap (+ 1) sq1 S.>< sq2
where
(sq01, sq2) = S.splitAt (j + 1) sq
(sq0, sq1) = S.splitAt i sq01
答案 2 :(得分:0)
我们需要从功能上进行思考。如果我们看一下:
S.adjust :: Int -> (a -> a) -> S.Seq a -> S.Seq a
需要一个索引来调整,应用要进行的调整以及一个序列,然后返回一个新序列。要调整一堆元素,我们调整第一个,然后将第二个调整到第一个调整的结果,依此类推。首先,我们可以列出我们要进行的所有调整:
adjustments :: [S.Seq Int -> S.Seq Int]
adjustments = [ S.adjust i (+1) | i <- [lower..upper] ]
(请注意:这是功能列表)。然后我要在种子序列的下一个之后应用一个。因此,如果列表最终被
adjustments = [inc1, inc2, inc3]
我们想要得到
inc1 (inc2 (inc3 seq0))
其中seq0
是初始传入序列。这种方法有意义吗?这足以让您入门吗?