我想编写一个函数,它接受一个列表,并根据函数的输出构造一个特定长度列表的子集。
如果我只对排序列表xs的前50个元素感兴趣,那么我会使用fst (splitAt 50 (sort xs)).
然而,问题是我的列表中的元素依赖于同一列表中的其他元素。如果我选择元素p,那么我必须选择元素q和r,即使它们不在我列表的前50个元素中。我正在使用一个函数finderFunc,它从列表xs中获取一个元素a,并返回一个包含元素a及其所有必需元素的列表。 finderFunc工作正常。现在,面临的挑战是编写一个函数,根据finderFunc的多个输出构建一个总长度为50的列表。
以下是我的尝试:
finish :: [a] -> [a] -> [a]
--This is the base case, which adds nothing to the final list
finish [] fs = []
--The function is recursive, so the fs variable is necessary so that finish
-- can forward the incomplete list to itself.
finish ps fs
-- If the final list fs is too small, add elements to it
| length fs < 50 && length (fs ++ newrs) <= 50 = fs ++ finish newps newrs
-- If the length is met, then add nothing to the list and quit
| length fs >= 50 = finish [] fs
-- These guard statements are currently lacking, not the main problem
| otherwise = finish [] fs
where
--Sort the candidate list
sortedps = sort ps
--(finderFunc a) returns a list of type [a] containing a and all the
-- elements which are required to go with it. This is the interesting
-- bit. rs is also a subset of the candidate list ps.
rs = finderFunc (head sortedps)
--Remove those elements which are already in the final list, because
-- there can be overlap
newrs = filter (`notElem` fs) rs
--Remove the elements we will add to the list from the new list
-- of candidates
newps = filter (`notElem` rs) ps
我意识到在某些情况下,上面的if语句不会给我一个确切的50个元素的列表。这不是现在的主要问题。问题是我的功能完成根本无法正常工作。它不仅在输出列表中产生重复的元素,而且有时远远高于我希望在列表中拥有的元素总数。
这是写的方式,我通常用空列表调用它,例如:finish xs []
,这样它构建的列表就作为空列表开始。
答案 0 :(得分:2)
| length fs < 50 && length (fs ++ newrs) <= 50 = fs ++ finish newps newrs
问题就在这里......在递归调用中,newrs
将成为fs
。因此,在下一次递归调用时,它会检查是否newrs < 50
,但您要查看到目前为止累计的总长度(包括“旧”fs
)。
因此,您可能希望更改递归调用为finish newps (fs ++ newrs)
的代码。
答案 1 :(得分:0)
这是使用累加器的一种非常常见的情况。通常的解决方案是定义
finish1 fs = finish [] fs
如果finish只作为finish1的一部分使用,你可以这样做:
finish fs = finish1 [] fs where
finish1 :: [a] -> [a] -> [a]
--This is the base case, which adds nothing to the final list
finish1 [] fs = []
--The function is recursive, so the fs variable is necessary so that finish
-- can forward the incomplete list to itself.
finish1 ps fs = ...
在递归地实现阶乘函数时,请参阅Accumulators in haskell了解相关问题。
至于将长度限制为50个元素,您可以构造一个长列表,然后使用take
函数获取它的50个第一个元素。由于Haskell的具体发展顺序,它是有效的。
答案 2 :(得分:0)
我意识到我没有清楚地陈述我的问题。我需要一个带有列表ps的函数并返回ps的子列表fs。此外,ps中的元素具有先决条件,这是ps中的其他元素。因此,在将元素a添加到列表fs时,该函数还必须将所有先决条件添加到fs。技巧位是确保没有重复项添加到fs。 ps中的不同元素可以具有重叠的先决条件,但fs应该是不同元素的列表。
这最终对我有用:
finish :: [a] -> [a] -> [a]
finish ps fs
| length fs < 50 && length (fs ++ newfs) <= n = finish newps (fs ++ newfs) n
--If adding a new perk and its prerequisites will bring me over the limit, then ignore it and move to the next perk
| length fs < 50 && length (fs ++ newfs) > n = finish (tail (reverse (sort ps))) fs n
| otherwise = fs
where
--This is the interesting value of the given list ps
inter = maximum ps
--A list of all values which might be useful for
maybeNewfs = perkAndPreqs inter
--Whittle that list down to a list of distinctly new elements
newfs = filter (`notElem` fs) maybeNewfs
--Now remove all items added to fs from the candidate list
newps = filter (`notElem` maybeNewfs) ps
与上述函数的主要区别在于我没有将newfs转发给函数的递归,我正在转发(fs ++ newfs)。