优化可变数组状态重操作代码

Dul*_*gon 2 arrays algorithm haskell mutable

我一直试图及时完成对黑客的这项练习.但由于超时,我的以下Haskell解决方案因测试案例13到15而失败. 在此输入图像描述

我的Haskell解决方案

import           Data.Vector(Vector(..),fromList,(!),(//),toList)
import           Data.Vector.Mutable
import qualified Data.Vector as V 
import           Data.ByteString.Lazy.Char8 (ByteString(..))
import qualified Data.ByteString.Lazy.Char8 as L
import Data.ByteString.Lazy.Builder
import Data.Maybe
import Control.Applicative
import Data.Monoid
import Prelude hiding (length)

readInt' = fst . fromJust . L.readInt 
toB []     = mempty
toB (x:xs) = string8 (show x) <> string8 " " <> toB xs

main = do 
  [firstLine, secondLine] <- L.lines <$> L.getContents
  let [n,k] = map readInt' $ L.words firstLine
  let xs = largestPermutation n k $ fromList $ map readInt' $ Prelude.take n $ L.words secondLine
  L.putStrLn $ toLazyByteString $ toB $ toList xs


largestPermutation n k v
  | i >= l || k == 0 = v 
  | n == x           = largestPermutation (n-1) k v
  | otherwise        = largestPermutation (n-1) (k-1) (replaceOne n x (i+1) (V.modify (\v' -> write v' i n) v))
        where l = V.length v 
              i = l - n
              x = v!i

replaceOne n x i v
  | n == h = V.modify (\v' -> write v' i x ) v
  | otherwise = replaceOne n x (i+1) v
    where h = v!i 
Run Code Online (Sandbox Code Playgroud)

我发现的最佳解决方案不断更新2个阵列.一个数组是主要目标,另一个数组用于快速索引查找.

更好的Java解决方案

public static void main(String[] args) {
  Scanner input = new Scanner(System.in);
  int n = input.nextInt();
  int k = input.nextInt();
  int[] a = new int[n];
  int[] index = new int[n + 1];
  for (int i = 0; i < n; i++) {
      a[i] = input.nextInt();
      index[a[i]] = i;
  }
  for (int i = 0; i < n && k > 0; i++) {
      if (a[i] == n - i) {
          continue;
      }
      a[index[n - i]] = a[i];
      index[a[i]] = index[n - i];
      a[i] = n - i;
      index[n - i] = i;
      k--; 
  }
  for (int i = 0; i < n; i++) {
      System.out.print(a[i] + " ");
  }
}
Run Code Online (Sandbox Code Playgroud)

我的问题是

  1. 这个算法在Haskell中的优雅和快速实现是什么?
  2. 有没有比Java解决方案更快的方法来解决这个问题?
  3. 我应该如何在Haskell中优雅而高效地处理大量数据更新?

beh*_*uri 7

您可以对可变数组进行的一项优化是根本不使用它们.特别是,您链接的问题有一个正确的折叠解决方案.

这个想法是你折叠列表并贪婪地交换右边最大值的项目并维护已经在下面创建的掉期Data.Map:

import qualified Data.Map as M
import Data.Map (empty, insert)

solve :: Int -> Int -> [Int] -> [Int]
solve n k xs = foldr go (\_ _ _ -> []) xs n empty k
    where
    go x run i m k
        -- out of budget to do a swap or no swap necessary
        | k == 0 || y == i = y : run (pred i) m k
        -- make a swap and record the swap made in the map
        | otherwise        = i : run (pred i) (insert i y m) (k - 1)
        where
        -- find the value current position is swapped with
        y = find x
        find k = case M.lookup k m of
            Just a  -> find a
            Nothing -> k
Run Code Online (Sandbox Code Playgroud)

在上面,run给出反向索引 i,当前映射m和剩余交换预算的函数是k向前解决列表的其余部分.通过反向索引,我的意思是反向列表的索引:n, n - 1, ..., 1.

折叠功能go,run通过更新值来构建每个步骤的功能i,m并将k其传递给下一步.最后,我们呼吁与初始参数此功能i = n,m = empty并初步交换预算k.

find可以通过维护反向映射来优化递归搜索,但这已经比您发布的java代码执行得快得多.


编辑:以上解决方案仍然支付树访问的对数成本.这是使用可变STUArray和一元折叠的替代解决方案foldM_,实际上比上面执行速度更快:

import Control.Monad.ST (ST)
import Control.Monad (foldM_)
import Data.Array.Unboxed (UArray, elems, listArray, array)
import Data.Array.ST (STUArray, readArray, writeArray, runSTUArray, thaw)

-- first 3 args are the scope, which will be curried
swap :: STUArray s Int Int -> STUArray s Int Int -> Int
     -> Int -> Int -> ST s Int
swap   _   _ _ 0 _ = return 0  -- out of budget to make a swap
swap arr rev n k i = do
    xi <- readArray arr i
    if xi + i == n + 1
    then return k -- no swap necessary
    else do -- make a swap, and reduce budget
        j <- readArray rev (n + 1 - i)
        writeArray rev xi j
        writeArray arr j  xi
        writeArray arr i (n + 1 - i)
        return $ pred k

solve :: Int -> Int -> [Int] -> [Int]
solve n k xs = elems $ runSTUArray $ do
    arr <- thaw (listArray (1, n) xs :: UArray Int Int)
    rev <- thaw (array (1, n) (zip xs [1..]) :: UArray Int Int)
    foldM_ (swap arr rev n) k [1..n]
    return arr
Run Code Online (Sandbox Code Playgroud)

  • @DulguunOtgon [本页](https://wiki.haskell.org/Foldl_as_foldr)或[this one](https://wiki.haskell.org/Foldl_as_foldr_alternative)应该有所帮助.到目前为止,你可以将`foldr` _lean推向右边,然后它又回来了:).该页面解释了如何完成此操作(通过调整折叠功能). (2认同)