List 在Haskell中平衡列表的更好方法

List 在Haskell中平衡列表的更好方法,list,haskell,optimization,partitioning,List,Haskell,Optimization,Partitioning,我正在通过解决一些在线问题和训练练习来学习Haskell 现在我正试图编写一个函数,它将接受一个列表,并像这样平衡它 [2,3,2,3,2] ---> [[2,2,2], [3,3]] [1,2,3,7,8] ---> [[1,2,7], [3,8]] [1,2,9,10] ---> [[2,9],[1,10]] [1,1,1,1,1,2,3] ---> [[1,1,1,1,1],[2,3]] sumCheck x | sum (take (length x `di

我正在通过解决一些在线问题和训练练习来学习Haskell

现在我正试图编写一个函数,它将接受一个列表,并像这样平衡它

[2,3,2,3,2] ---> [[2,2,2], [3,3]]
[1,2,3,7,8] ---> [[1,2,7], [3,8]]
[1,2,9,10] ---> [[2,9],[1,10]]
[1,1,1,1,1,2,3] ---> [[1,1,1,1,1],[2,3]]
sumCheck x
  | sum (take (length x `div` 2) x) == sum (drop (length x `div` 2) x) = True
  | otherwise = False
parts :: [a] -> [([a], [a])]
parts [] = [([], [])]
parts (x : xs) = let pts = parts xs in
    [(x : ys, zs) | (ys, zs) <- pts] ++ [(ys, x : zs) | (ys, zs) <- pts]
(始终分为两部分,第二部分中的任何一部分都有效)


我想到的方法是使用base Data.List中的函数

用这样的函数过滤出有效的列表

[2,3,2,3,2] ---> [[2,2,2], [3,3]]
[1,2,3,7,8] ---> [[1,2,7], [3,8]]
[1,2,9,10] ---> [[2,9],[1,10]]
[1,1,1,1,1,2,3] ---> [[1,1,1,1,1],[2,3]]
sumCheck x
  | sum (take (length x `div` 2) x) == sum (drop (length x `div` 2) x) = True
  | otherwise = False
parts :: [a] -> [([a], [a])]
parts [] = [([], [])]
parts (x : xs) = let pts = parts xs in
    [(x : ys, zs) | (ys, zs) <- pts] ++ [(ys, x : zs) | (ys, zs) <- pts]
如果列表的长度是偶数。如果不是,那么一个递归函数

(take y x) (drop (y - (y - 0)) x)
(take (y - 1) x) (drop (y - (y - 1)) x) 
等等

或者像这样对它们进行分区

[2,3,2,3,2] ---> [[2,2,2], [3,3]]
[1,2,3,7,8] ---> [[1,2,7], [3,8]]
[1,2,9,10] ---> [[2,9],[1,10]]
[1,1,1,1,1,2,3] ---> [[1,1,1,1,1],[2,3]]
sumCheck x
  | sum (take (length x `div` 2) x) == sum (drop (length x `div` 2) x) = True
  | otherwise = False
parts :: [a] -> [([a], [a])]
parts [] = [([], [])]
parts (x : xs) = let pts = parts xs in
    [(x : ys, zs) | (ys, zs) <- pts] ++ [(ys, x : zs) | (ys, zs) <- pts]
按升序排序,第一个排序最平衡


现在,这对于较小的列表很有效,但正如您可能已经猜到的,对于较大的列表,它只会继续处理


我想这可能会有所帮助,但我宁愿不使用外部软件包,而是尝试自己去做。(在C的帮助下!)

这是一个有趣的算法问题。我鼓励你自己继续思考这个问题;下面是严重的扰流板

这是我的计划:我将计算所有可能的子集之和,以及(只有一个)谁加入和谁退出以实现该和的见证人。然后,我们将检查哪个可能的总和最接近整个列表总和的一半。因此:

import Data.Maybe
import Data.Map (Map)
import qualified Data.Map as M

type Sums a = Map a ([a], [a])

update :: (Num a, Ord a) => a -> Sums a -> Sums a
update n sums = M.union
    (                          (\(i, o) -> (i, n:o)) <$> sums)
    (M.mapKeysMonotonic (n+) $ (\(i, o) -> (n:i, o)) <$> sums)

computeSums :: (Num a, Ord a) => [a] -> Sums a
computeSums = foldr update (M.singleton 0 ([], []))

balance :: (Integral a, Ord a) => [a] -> ([a], [a])
balance xs = snd . fromJust $ M.lookupLE (sum xs `div` 2) (computeSums xs)
它在大型列表上也会相对较快地终止:

> :set +s
> (\(a,b) -> (sum a, sum b)) . balance $ replicate 1001 1
(500,501)
(0.12 secs, 156,255,000 bytes)
> (\(a,b) -> (sum a, sum b)) . balance $ [1..200]
(10050,10050)
(1.25 secs, 752,015,256 bytes)
> (\(a,b) -> (sum a, sum b)) . balance $ take 20 (iterate (2*) 1)
(524287,524288)
(0.92 secs, 532,754,784 bytes)

最后一个示例演示了该算法的指数最坏情况行为(因为两个幂的每个子集给出不同的和)。

这是一个有趣的算法问题。我鼓励你自己继续思考这个问题;下面是严重的扰流板

这是我的计划:我将计算所有可能的子集之和,以及(只有一个)谁加入和谁退出以实现该和的见证人。然后,我们将检查哪个可能的总和最接近整个列表总和的一半。因此:

import Data.Maybe
import Data.Map (Map)
import qualified Data.Map as M

type Sums a = Map a ([a], [a])

update :: (Num a, Ord a) => a -> Sums a -> Sums a
update n sums = M.union
    (                          (\(i, o) -> (i, n:o)) <$> sums)
    (M.mapKeysMonotonic (n+) $ (\(i, o) -> (n:i, o)) <$> sums)

computeSums :: (Num a, Ord a) => [a] -> Sums a
computeSums = foldr update (M.singleton 0 ([], []))

balance :: (Integral a, Ord a) => [a] -> ([a], [a])
balance xs = snd . fromJust $ M.lookupLE (sum xs `div` 2) (computeSums xs)
它在大型列表上也会相对较快地终止:

> :set +s
> (\(a,b) -> (sum a, sum b)) . balance $ replicate 1001 1
(500,501)
(0.12 secs, 156,255,000 bytes)
> (\(a,b) -> (sum a, sum b)) . balance $ [1..200]
(10050,10050)
(1.25 secs, 752,015,256 bytes)
> (\(a,b) -> (sum a, sum b)) . balance $ take 20 (iterate (2*) 1)
(524287,524288)
(0.92 secs, 532,754,784 bytes)

最后一个示例演示了该算法的指数最坏情况行为(因为两个幂的每个子集给出不同的和)。

这看起来像是一个用动态规划解决的问题:创建一个存储两个部分之间差异的列表,迭代元素,每次都用新的平衡来更新列表。为什么不把[1,3,4],[2,6]作为第二个例子?@DavidFletcher事实上是正确的,我从头开始就想出了一个不平衡列表的例子,忘了再检查一遍。增加了更好的例子。@WillemVanOnsem我不确定动态编程在这里有多大帮助。例如,
[2,2,3,5,6]
的平衡是
([2,2,5],[3,6])
。此解决方案似乎与
[2,3,5,6]
(即
([3,5],[2,6])
的平衡无关,与较大的平衡不同,前者将
2
6
放在一起)或
[2,2,3,5]
(即
([5],[2,2,3])
([5,2,3])
,两者都将
2
中的一个从
5
中分离出来。@DanielWagner:它在O(nk)中工作,k是数字的总和,n是元素的数量,通过一些巧妙的优化,k可以减少。它基本上相当于子集和问题,但关键是这里的目标和是动态变化的。与回溯相比,使用动态规划的优势在于,我们不会研究具有相同总和的两个“部分”子集。这似乎是动态规划要解决的问题:创建一个列表,存储两个部分之间的差异,迭代元素,每次都用新的平衡来更新列表。为什么不把[1,3,4],[2,6]作为第二个例子?@DavidFletcher事实上是正确的,我从头开始就想出了一个不平衡列表的例子,忘了再检查一遍。增加了更好的例子。@WillemVanOnsem我不确定动态编程在这里有多大帮助。例如,
[2,2,3,5,6]
的平衡是
([2,2,5],[3,6])
。此解决方案似乎与
[2,3,5,6]
(即
([3,5],[2,6])
的平衡无关,与较大的平衡不同,前者将
2
6
放在一起)或
[2,2,3,5]
(即
([5],[2,2,3])
([5,2,3])
,两者都将
2
中的一个从
5
中分离出来。@DanielWagner:它在O(nk)中工作,k是数字的总和,n是元素的数量,通过一些巧妙的优化,k可以减少。它基本上相当于子集和问题,但关键是这里的目标和是动态变化的。与回溯相比,使用动态规划的优势在于,我们不会研究具有相同和的两个“部分”子集。