我有以下列表(这是一个长度为2的列表,但在我的作业中我有一个长度+ n列表)
xxs = [(11,22,[(33,33,33),(44,44,44)]),(55,66,[(77,77,77),(88,88,88)])]
我正试图通过列表索引(n
)和子列表索引(p
“替换”一个3元组(p1或p2或p3或p4来自下图) )。
该功能最后应该是:
fooo newtuple n p = (…)
例如:(将p3替换为(98,98,98):
fooo (98,98,98) 2 1
[(11, 22, [(33,33,33) , (44,44,44)]) , (55, 66, [(98,98,98),(88,88,88)])]
我按照以下步骤计划了代码:
访问我想要更改的pn。我设法通过以下方式实现它:
fob n p = ((aux2 xxs)!!n)!!p
where aux2 [] = []
aux2 ((_,_,c):xs) = c:aux2 xs
“替换”3元组。我真的需要一些帮助。我被卡住了。我做过的最好的代码(在我的脑海里有点意义):(记住:请不要对我的代码太糟糕,我只研究Haskell只有5周)
foo n p newtuple = fooAux newtuple fob
where fooAux _ [] = []
fooAux m ((_):ds) = m:ds
fob n p = ((aux2 xxs)!!n)!!p
where aux2 [] = []
aux2 ((_,_,c):xs) = c:aux2 xs
最后,我将使用splitAt
将所有内容重新组合在一起。
我对这个问题的处理方法是否正确?我真的很感激第2步的一些帮助。
答案 0 :(得分:7)
我对Haskell也有点新意,但是让我们看看我们是不是想出一个像样的方法。
因此,从根本上说,我们要做的是修改列表中的内容。使用函数式编程我想保持它有点一般,所以让我们创建一个函数update
。
update :: Int -> (a -> a) -> [a] -> [a]
update n f xs = pre ++ (f val) : post
where (pre, val:post) = splitAt n xs
现在将采用索引,函数和列表,并将列表中的nth
元素替换为应用于其的函数的结果。
然而,在我们更大的问题中,我们需要在嵌套的上下文中进行更新。幸运的是,我们的update
函数将函数作为参数,因此我们也可以在该函数内调用update
!
type Triple a = (a,a,a)
type Item = (Int, Int, [Triple Int])
fooo :: Triple Int -> Int -> Int -> [Item] -> [Item]
fooo new n p = update (n-1) upFn
where upFn (x,y,ps) = (x,y, update (p-1) objFn ps)
objFn _ = new
所有fooo
所要做的就是调用更新两次(一次在另一个调用中)并执行一些“内务”工作(将结果正确地放入元组中)。 (n-1)
和(p-1)
是因为您似乎从1
开始编制索引,而Haskell从0
开始。
让我们看看它是否适用于我们的测试用例:
*Main> fooo (98,98,98) 2 1 [(11,22,[(33,33,33),(44,44,44)]),(55,66,[(77,77,77),(88,88,88)])]
[(11,22,[(33,33,33),(44,44,44)]),(55,66,[(98,98,98),(88,88,88)])]
答案 1 :(得分:4)
所以你尝试使用一些现成的函数(!!)
。它可以为您访问列表中的项目,但忘记了它的位置,因此无法更新。你有一个解决方案,使用另一个现成的函数split
,将列表分成两部分,(++)
将它们粘合成一部分。
但为了真正感受到它,我怀疑你的任务首先是针对(很容易忘记一个功能名称,而且同样容易为自己写一个新的),您可以尝试自己编写第一个(!!)
。然后你会发现修改它真的很容易,所以它也可以更新列表。
要编写函数,最好将其视为等价方程:
myAt 1 (x:xs) = x
myAt n (x:xs) | n > 1 = ...
当n
为零时,我们只是拿掉头元素。如果不是,我们该怎么办?我们试图接近零。你可以填空。
所以在这里我们返回了找到的元素。如果我们想要更换它怎么办?用什么代替? - 这会调用另一个参数,
myRepl 1 (x:xs) y = (y:xs)
myRepl n (x:xs) y | n > 1 = x : myRepl ...
现在你可以完成剩下的工作了。
最后,Haskell是一种懒惰的语言。这意味着它最终只会调用所需的列表元素。如果你更换第7个元素怎么办,但后来只要求前3个?使用split
的代码实际上会要求7个元素,因此在以后要求它们时它可以返回前3个元素。
现在,您希望以嵌套方式替换,并且用替换旧值的值取决于旧值:newVal = let (a,b,ls)=oldVal in (a,b,myRepl p ls newtuple)
。所以你确实需要使用函数而不是值重写(以便之前使用y
,const y
会去):
myUpd 1 (x:xs) f = (f x:xs)
myUpd n ... = ...
,您的整个通话都会变为myUpd n xxs (\(a,b,c)->(a,b,myUpd ... (const ...) ))
。
答案 2 :(得分:4)
首先,我们需要一个通用函数来映射列表的某个元素,例如:
mapN :: (a -> a) -> Int -> [a] -> [a]
mapN f index list = zipWith replace list [1..] where
replace x i | i == index = f x
| otherwise = x
对于外部列表和内部列表,我们可以使用此功能两次。由于内部列表是元组的一部分,因此有一点复杂,所以我们需要另一个辅助函数:
mapTuple3 :: (c -> c) -> (a,b,c) -> (a,b,c)
mapTuple3 f (x,y,z) = (x,y,f z)
现在我们已经拥有了将replace函数应用于我们的用例所需的一切:
fooo :: Int -> Int -> (Int,Int,Int) -> [(Int,Int,[(Int,Int,Int)])]
fooo n p newTuple = mapN (mapTuple3 (mapN (const newTuple) p)) n xxs
当然在内部列表中,我们不需要考虑旧值,因此我们可以使用const :: a -> (b -> a)
来忽略该参数。