我有一个数据列表,它在运行时从文件读取,并在这样的元组中表示:
[(1,0.1),(2,0.2),(3,0.3)...etc...]
我编写了一个函数,它将一个列表和两个整数作为参数并返回一个double:
f :: [(Int,Double)] -> Int -> Int -> Double
f mylist i j
| j < n = (do some stuff)
| otherwise = max (f mylist (i-1) j) (some other stuff with m_i and p_i)
where m_i = fst $ mylist !! (i-1)
p_i = snd $ mylist !! (i-1)
现在,我是Haskell的新手和纯函数的概念,但由于列表是静态的(它没有改变),我想知道我是否真的需要将我的列表传递给函数?
通过无数层递归传递大名单是不好的做法吗?
鉴于我在运行时阅读了这个列表,我可以设置'两个函数m
和p
来像这样使用它们吗?
f :: Int -> Int -> Double
f i j
| j < n = (do some stuff)
| otherwise = max (f (i-1) j) (some other stuff with m_i and p_i)
where m_i = m (i-1)
p_i = p (i-1)
如果是这样,我如何设置m
和p
函数(纯粹的,对吧?)基本上返回我在运行时从文件中读取的值(这是不正确的,如果我理解正确的话。)
感谢您的帮助!
答案 0 :(得分:4)
虽然有一些问题,但在大名单上进行递归并不是很糟糕的做法。通常,只要您的递归函数具有增量效率,即使列表非常大,您也可以(O(1) memory, O(n) time
)。
在您的情况下,您似乎正在向后读取列表,因为您的索引值向下递归。这意味着您必须将整个列表加载到内存中才能将其反转 - 或许最好将列表反转存储?
如果您正在从文件中读取,那么几乎肯定是。在某些情况下,假装它是纯粹的操作可能是有价值的,但通常最好将这些内容传递给基于IO的早期设置/配置步骤。
例如,我的可执行文件可能看起来像这样
main :: IO ()
main = do bigList <- readFileSomehow -- this is impure
let res = f bigList -- this is pure
print res -- this is impure again
我们首先使用do
语法从不纯文件读取中提取bigList :: [(Int, Double)]
作为第一步。然后我们立即将bigList
传递给纯函数f
并获得纯结果res
。最后,我们对纯print
执行不纯res
操作。
这让我们可以将所有杂质隔离到我们的main
函数,并完全独立地编写f
,而不是像#34;&#34;&#34;我们开始接受那个大输入。
使用您描述的m
和p
函数,可以进一步抽象出此列表的概念。他们所做的只是隐藏列表,只允许通过(!!)
访问它。
inner :: Int -> Int -> (Int -> Int) -> (Int -> Double) -> Something
outer :: [(Int, Double)] -> Int -> Int -> Something
outer bigList i j = inner i j (\ix -> fst $ bigList !! ix) (\ix -> snd $ bigList !! ix)
现在outer
&#34;包裹&#34; inner
阻止它通过这两个索引函数看到bigList
。
最后,这整个方法并没有特别的&#34; Haskelly&#34;感觉不幸。在大列表上使用(!!)
进行显式递归往往会导致速度减慢和代码重复。如果可能的话,尝试用map
和foldr
来表达你的意图几乎总是更好的主意。
答案 1 :(得分:3)
通过无数层递归传递大名单是不好的做法吗?
似乎在概念上你担心制作大型列表的副本并非如此,Haskell将通过递归来处理相同的数据结构。您无需明确解除它。
使用显式(!!)
可能会导致一些问题,其中最少的问题是因为它是一个部分功能。由于您正在对数据执行线性扫描,因此可能值得花时间尝试将问题表示为地图或折叠或其某种组合。如果你能做到这一点,你就可以对编译器进行大量的优化。
还强烈建议您查看vector
库。
答案 2 :(得分:1)
简而言之,没有。
如果您正在从文件中读取列表,那么它将不是纯粹的,并且如果不将其传递给纯函数,将无法从纯函数中使用它。
从文件中读取列表意味着读取和解析它的结果为IO [(Int,Double)]
。你不能从IO中“提取”这个值;您只能将纯函数引入IO,例如,使用fmap
或bind
。
举例说明:
main = do
...
xs <- readFile -- this is IO String
let mylist = parse xs -- whatever conversion
-- from String to [(Int,Double)]
-- mylist cannot live outside this IO
let zs = f mylist i j -- so got to pass it to f
...
答案 3 :(得分:1)
是的,您的列表是不可变的,但在编译时不知道。所以你有两个选择
请注意,这些在概念上是等效的,都涉及您传递一些数据来表示值列表,这只是您如何表示它的问题。
所以p
和m
看起来像
myList = ...
p i = fst $ myList !! i
m i = fst $ myList !! i
这对你来说真的不算什么。
如果它让你感觉更好,10000个元素的列表成本相同,以传递给一个函数(而不是创建)为3的列表。它编译为head +指向其余的指针或nil值。这是因为Haskell有单独链接列表,
List a = Cons a (List a) -- Head, rest of list
| Nil -- End of list
因此,按值传递它对于命令式数组而言是便宜的,但查找时间相应地受到影响。
作为其必然结果,请谨慎对待(!!)
。考虑您的问题是否更好地表示为折叠或地图。使用更高阶的组合器非常Haskelly:)
答案 4 :(得分:1)
如何在每个列表中使用2个函数 - 使用map
并制作例如元组
result :: (a->b) -> (a->c) -> [a] -> [(b,c)]
result m p = map (\elem -> (f elem , p elem) ) list
如果您将列表作为参数传递 - 没什么不好的。如果您不使用它 - 编译器将无法读取它。但算法的复杂性很重要。