如何编写从数据列表中查找的Haskell函数

时间:2013-09-25 19:22:15

标签: haskell

我有一个数据列表,它在运行时从文件读取,并在这样的元组中表示:

[(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的新手和纯函数的概念,但由于列表是静态的(它没有改变),我想知道我是否真的需要将我的列表传递给函数?

通过无数层递归传递大名单是不好的做法吗?

鉴于我在运行时阅读了这个列表,我可以设置'两个函数mp来像这样使用它们吗?

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)

如果是这样,我如何设置mp函数(纯粹的,对吧?)基本上返回我在运行时从文件中读取的值(这是不正确的,如果我理解正确的话。)

感谢您的帮助!

5 个答案:

答案 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;我们开始接受那个大输入。


使用您描述的mp函数,可以进一步抽象出此列表的概念。他们所做的只是隐藏列表,只允许通过(!!)访问它。

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;感觉不幸。在大列表上使用(!!)进行显式递归往往会导致速度减慢和代码重复。如果可能的话,尝试用mapfoldr来表达你的意图几乎总是更好的主意。

答案 1 :(得分:3)

  

通过无数层递归传递大名单是不好的做法吗?

似乎在概念上你担心制作大型列表的副本并非如此,Haskell将通过递归来处理相同的数据结构。您无需明确解除它。

使用显式(!!)可能会导致一些问题,其中最少的问题是因为它是一个部分功能。由于您正在对数据执行线性扫描,因此可能值得花时间尝试将问题表示为地图或折叠或其某种组合。如果你能做到这一点,你就可以对编译器进行大量的优化。

还强烈建议您查看vector库。

http://hackage.haskell.org/package/vector-0.10.0.1

答案 2 :(得分:1)

简而言之,没有。

如果您正在从文件中读取列表,那么它将不是纯粹的,并且如果不将其传递给纯函数,将无法从纯函数中使用它。

从文件中读取列表意味着读取和解析它的结果为IO [(Int,Double)]。你不能从IO中“提取”这个值;您只能将纯函数引入IO,例如,使用fmapbind

举例说明:

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)

是的,您的列表是不可变的,但在编译时不知道。所以你有两个选择

  1. 通过列表
  2. 传递一个知道此列表的函数,并在index
  3. 处给出它的值

    请注意,这些在概念上是等效的,都涉及您传递一些数据来表示值列表,这只是您如何表示它的问题。

    所以pm看起来像

     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

如果您将列表作为参数传递 - 没什么不好的。如果您不使用它 - 编译器将无法读取它。但算法的复杂性很重要。