【问题标题】:Haskell: stock span algorithmHaskell:股票跨度算法
【发布时间】:2018-04-02 14:01:31
【问题描述】:

我正在尝试在 Haskell 中实现“stock span problem”。这是我想出的解决方案。想看看是否有任何其他惯用的方式来做到这一点。这是 O(n^2) 算法(?),使用堆栈,我们可以使它成为 O(n)。任何指向可以使用的其他高阶函数的指针都值得赞赏。

import Data.List (inits)

type Quote = Int
type Quotes = [Quote]
type Span = [Int]

stockSpanQuad :: Quotes -> Span
    stockSpanQuad [] = []
    stockSpanQuad xs = map spanQ (map splitfunc  (tail $ inits xs))
      where
          spanQ (qs, q) = 1 + (length $ takeWhile (\a -> a <= q) (reverse qs))
          splitfunc xs = (init xs, last xs)

【问题讨论】:

  • 你可以用一个列表来表示一个栈——push是x : xs作为一个表达式,pop是一个模式。

标签: algorithm haskell


【解决方案1】:

您提供的链接包含一个使用堆栈数据结构的解决方案。每种语言中的示例都会改变堆栈,使用带有索引的数组并通过索引访问数组的元素。所有这些操作在 Haskell 中并不常见。

让我们考虑以下解决方案:

type Quote = Int
type Quotes = [Quote]
type Span = [Int]

stockSpanWithStack :: Quotes -> Span
stockSpanWithStack quotes = calculateSpan quotesWithIndexes []
  where
    quotesWithIndexes = zip quotes [0..]
    calculateSpan [] _ = []
    calculateSpan ((x, index):xs) stack =
      let
        newStack = dropWhile (\(y, _) -> y <= x) stack
        stockValue [] = index + 1
        stockValue ((_, x):_) = index - x
      in
        (stockValue newStack) : (calculateSpan xs ((x, index):newStack))

让我们将其与 Python 解决方案进行比较:

# Calculate span values for rest of the elements
for i in range(1, n):

    # Pop elements from stack whlie stack is not
    # empty and top of stack is smaller than price[i]
    while( len(st) > 0 and price[st[0]] <= price[i]):
        st.pop()

    # If stack becomes empty, then price[i] is greater
    # than all elements on left of it, i.e. price[0],
    # price[1], ..price[i-1]. Else the price[i]  is
    # greater than elements after top of stack
    S[i] = i+1 if len(st) <= 0 else (i - st[0])

    # Push this element to stack
    st.append(i)

对于堆栈解决方案,我们需要带有索引的元素。可以这样模仿:

quotesWithIndexes = zip quotes [0..]

引号列表是递归迭代的,而不是在每次循环迭代中修改堆栈,我们可以使用修改后的值调用函数:

calculateSpan ((x, index):xs) stack =

Python中的以下行(堆栈元素的弹出,小于当前值):

while( len(st) > 0 and price[st[0]] <= price[i]):
    st.pop()

在 Haskell 中可以重写为:

newStack = dropWhile (\(y, _) -> y <= x) stack

以及股票价值的计算:

S[i] = i+1 if len(st) <= 0 else (i - st[0])

可以解释为:

stockValue [] = index + 1
stockValue ((_, x):_) = index - x

下面说过,修改变量的状态并不常见,例如S[i] = ...st.append(i)。但是我们可以用新的堆栈值递归调用函数并将当前结果添加到它之前:

(stockValue newStack) : (calculateSpan xs ((x, index):newStack))

从技术上讲,我们推入列表的开头并删除列表的第一个元素,因为这是在 Haskell 中处理列表的惯用方式和更高效的方式。

【讨论】:

  • 感谢伊戈尔的详细解释。我认为,我发布的答案与您上面解释的类似。您能否评论一下“生产”Haskell 中的代码是如何编写的?您的答案似乎更具可读性和性能更好?谢谢。
  • @user3169543 老实说,我对“生产”Haskell 并不熟悉,因为我从未在其中编写过商业软件。但我认为,有时将一段逻辑移动到具有描述性名称的单独函数中有助于提高可读性(但请原谅我的 xs 和 ys)
【解决方案2】:

我想出了以下内容,但我不确定它是否“惯用”。我认为这类似于@Igor 的回答。请发表评论。

{-# LANGUAGE BangPatterns #-}
stockSpanLinear :: Quotes -> Span
stockSpanLinear = reverse.snd.(foldl func ([],[]))


type Stack = [(Quote, Int)]

func ::  (Stack, Span)-> Quote -> (Stack, Span)
func ([], []) q             = ([(q, 1)], [1])
func p@((_, !i):pis, span) q = go p q (i+1)
   where
       go :: (Stack, Span) -> Quote -> Int -> (Stack, Span)
       go (stack,span) q index = let ys = dropWhile (\(p, _) -> p <= q) stack
                          in case ys of
                               []      -> ((q, index):ys, index+1:span)
                               (_,i):_ -> ((q, index):ys, index-i:span)

【讨论】:

  • 只能使用 BangPatterns 扩展,因为在 GHCI :trace 中单步执行代码时,索引未得到评估。没有其他原因。
猜你喜欢
  • 1970-01-01
  • 2019-10-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多