Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/algorithm/11.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Algorithm Haskell中具有良好性能的简单循环_Algorithm_Performance_Pointers_Haskell_Time Complexity - Fatal编程技术网

Algorithm Haskell中具有良好性能的简单循环

Algorithm Haskell中具有良好性能的简单循环,algorithm,performance,pointers,haskell,time-complexity,Algorithm,Performance,Pointers,Haskell,Time Complexity,我从Haskell开始,对如何为我通常用C或Python编写的简单代码获得匹配的性能感兴趣。考虑下面的问题。 您将获得一个长度为n的1和0的长字符串。我们希望为长度为m的每个子字符串输出该窗口中的1s数。也就是说,输出在0和m之间有不同的可能值 在C语言中,这很容易做到与n成比例的时间,并使用与m位成比例的额外空间(在存储输入所需的空间之上)。您只需计算长度为m的第一个窗口中的1s数,然后保持两个指针,一个指向窗口的开始,一个指向窗口的结束,并根据一个指向1,另一个指向0或相反的情况递增或递减

我从Haskell开始,对如何为我通常用C或Python编写的简单代码获得匹配的性能感兴趣。考虑下面的问题。

您将获得一个长度为
n
的1和0的长字符串。我们希望为长度为
m
的每个子字符串输出该窗口中的1s数。也就是说,输出在
0
m
之间有不同的可能值

在C语言中,这很容易做到与
n
成比例的时间,并使用与
m
位成比例的额外空间(在存储输入所需的空间之上)。您只需计算长度为
m
的第一个窗口中的1s数,然后保持两个指针,一个指向窗口的开始,一个指向窗口的结束,并根据一个指向1,另一个指向0或相反的情况递增或递减

在Haskell中,是否有可能以纯函数的方式获得相同的理论性能

一些可怕的代码:

chunkBits m = helper
  where helper [] = []
        helper xs = sum (take m xs) : helper (drop m xs)

main = print $ chunkBits 5 [0,1,1,0,1,0,0,1,0,1,0,1,1,1,0,0,0,1]

更新

在更仔细地阅读了这个问题之后,我注意到 C程序从数组中读取其输入

这里有一个等价的Haskell“pure”函数,它执行这个任务

 import qualified Data.Vector as V
 import Data.List
 import Control.Monad

 count :: Int -> V.Vector Int -> [Int]
 count m v = 
   let c0 = V.sum (V.take m v)
       n = V.length v
       results = scanl' go c0 [0..n-m-1]
         where go r i = r - (v V.! i) + (v V.! (i+m))
   in results

 test1 = let v = V.fromList [0,0,1,1,1,1,1,0,0,0,0]
         in print $ count 3 v
即使
count
返回一个列表,它也会延迟生成。此外,如果它被另一个列表操作使用,则可以通过各种融合技术之一对其进行优化

原始答案

这是一个很好的练习,但为什么它必须是“纯功能性的”(这意味着什么)

您可以用Haskell编写C算法——虽然没有那么简洁,但它会的 生成基本相同的代码

 import Data.Vector.Unboxed.Mutable as V

 count m = do
   v <- V.replicate m '0'
   let toInt ch = if ch == '1' then 1 else 0
   let loop c i = do
         ch <- getChar
         oldch <- V.read v i
         let c' = c + toInt ch - toInt oldch
         V.write v i ch
         let i' = mod (i+1) m 
         putStrLn $ show c
         loop c' i'
   loop 0 0

 main = count 3
导入Data.Vector.unbox.Mutable作为V
计数m=do
v C代码
下面是您描述的C代码:

int sliding_window(const char * const str, const int n, const int m, int * result){
  const char * back  = str;
  const char * front = str + m;
  int sum = 0;
  int i;

  for(i = 0; i < m; ++i){
     sum += str[i] == '1';
  }

  *result++ = sum;

  for(; i < n; ++i){
    sum += *front++ == '1';
    sum -= *back++  == '1';
    *result++ = sum;
  }
  return n - m + 1;
}
这就是O(n),其中n是列表的长度

slidingWindow :: Int -> [Char] -> [Int]
slidingWindow m xs = scanl (+) start (slide m xs)
 where
   start = length (filter (== '1') (take m xs))
这是O(n),与C中相同,因为两者使用相同的算法

警告 在实际应用程序中,您总是使用
Text
ByteString
而不是
String
,因为后者是开销很大的
Char
列表。由于您只使用字符串
'1'
'0'
,因此可以通过testring使用

import           Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as BS
import           Data.List (scanl')

slide :: Int -> ByteString -> [Int]
slide m xs = BS.zipWith f xs (BS.drop m xs)
  where
    f '1' '0' = -1
    f '0' '1' =  1
    f  _   _  =  0

slidingWindow :: Int -> ByteString -> [Int]
slidingWindow m xs = scanl' (+) start (slide m xs)
 where
   start = BS.count '1' (BS.take m xs)

最基本的一层是用手工编写的递归函数来表示循环,从而重新实现基于cool HOF的算法

碰撞模式将参数标记为严格的,因此可以计算简单的值,而不会产生不必要的延迟(例如,在使用
scanl'
时,会隐式地考虑到这一点)。这也表明“指针”只是名称:

{-# LANGUAGE BangPatterns #-}

-- assumes xs has only 0s and 1s
counts :: Int -> [Int] -> [Int]
counts m xs = g 0 m xs
  where
    g !c    0      ys  = h c ys xs
    g !c    _      []  = []                  -- m > |xs|
    g !c    m   (y:ys) = g (c+y) (m-1) ys
    h !c    []     _   = [c]
    h !c (y:ys) (x:xs) = c : h (c+y-x) ys xs
测试

 > counts [1,1,0,0,1,1,0,1] 2
[2,1,0,1,2,1,1]
 > counts [1,1,0,0,1,1,1,1] 3
[2,1,1,2,3,3]

请注意,Haskell使用惰性编程。在某些情况下,例如,如果你只对前五个数字感兴趣,那么时间复杂度可能是O(1)。@WillemVanOnsem在这种情况下,你需要以m+4位来阅读,不是吗?@eleanora:考虑到你对未来的展望,是的。如果回顾过去,它只需要前4个字符(或者,如果m小于m,则需要m)。这不是一件坏事,但您应该从自己的一些实现开始,并演示它们如何不能提供所需的性能。@MattJordan
m
可能不取决于int字符串的长度,但是,如果算法没有将固定的
m
作为其定义的一部分,则它是输入大小的一部分(贡献O(lgm)位)。谢谢。有一个可能有用的定义,但我的意思是只使用纯函数。那篇文章说Haskell是一种纯函数语言。那么这是否意味着任何Haskell函数都是纯函数的呢?我觉得我不是给这个问题一个好答案的合适专家。然而,我可以说,当我问这个问题时,我脑子里有不变性。关于这个问题的问题:看起来C程序从数组中读取输入-这是正确的吗?“产出”意味着什么?结果是放在另一个数组中还是发送到stdout
或…?我很高兴输入可以以任何方便的形式出现,并且输出可以直接发送到stdout。这看起来非常好。谢谢。@d您是说用testring或Text代替
?首先,OP是“从Haskel开始”,我不想引入额外的软件包。其次,您可以使用
ByteString.(Lazy.)Char8
中的变量来交换
zipWith
drop
,这样就完成了,代码几乎保持不变。最后但并非最不重要的一点是,这个问题没有明确说明。结果应该打印出来,还是应该是一个列表?无论哪种方式,我都会添加一些关于
String
@Zeta的内容,不,我的意思是完全没有。为什么不使用
Bool
,或者
data Bit=Zero | One
或者其他什么呢?@dfeuer:“给你一个长度为n的1s和0s的长字符串”。但是是的,一个
Input->BoolString
后跟
slidingWindow::Int->BoolString->……
会更好。对于C程序员来说,“字符串”只是一个8位的数组。OP似乎是一位经验丰富的C程序员。
 > counts [1,1,0,0,1,1,0,1] 2
[2,1,0,1,2,1,1]
 > counts [1,1,0,0,1,1,1,1] 3
[2,1,1,2,3,3]