给出这样的矩阵
matrix_table =
[[ 0, 0, 0, 0]
,[ 0, 0, 0, 0]
,[ 0, 0, 0, 0]
,[ 0, 0, 0, 0]
]
和列表position_list = [2, 3, 2, 10]
函数的输出
distribute_ones :: [[Int]] -> [Int] -> [[Int]]
distribute_ones matrix_table position_list
应该看起来像这样
[[ 0, 1, 0, 1] -- 2 '1's in the list
,[ 0, 1, 1, 1] -- 3 '1's in the list
,[ 0, 1, 0, 1] -- 2 '1's in the list
,[ 1, 1, 1, 1] -- Since 10 > 4, all '1's in the list
]
我尝试过的事情:
我生成了列表列表,基本矩阵为
replicate 4 (replicate 4 0)
然后用chunksOf
库中的Data.List.Split
划分内部列表,以制作4 - (position_list !! nth)
的片段。
最后像这样1
附加和连接
take 4 . concat . map (1 :)
尽管我认为这并不是最好的方法。 有更好的方法吗?
答案 0 :(得分:6)
对于均匀分布的元素,我建议使用Bjorklund的算法。 Bjorklund的算法需要两个序列合并,然后重复:
在代码中:
bjorklund :: [[a]] -> [[a]] -> [a]
bjorklund xs ys = case zipMerge xs ys of
([], leftovers) -> concat leftovers
(merged, leftovers) -> bjorklund merged leftovers
zipMerge :: [[a]] -> [[a]] -> ([[a]], [[a]])
zipMerge [] ys = ([], ys)
zipMerge xs [] = ([], xs)
zipMerge (x:xs) (y:ys) = ((x++y):merged, leftovers) where
~(merged, leftovers) = zipMerge xs ys
以下是ghci中的一些示例:
> bjorklund (replicate 2 [1]) (replicate 2 [0])
[1,0,1,0]
> bjorklund (replicate 5 [1]) (replicate 8 [0])
[1,0,0,1,0,1,0,0,1,0,0,1,0]
如果愿意,您可以编写一个小的包装程序,该包装程序只接受您关心的参数。
ones len numOnes = bjorklund
(replicate ((-) len numOnes) [0])
(replicate (min len numOnes) [1])
在ghci中:
> map (ones 4) [2,3,2,10]
[[0,1,0,1],[0,1,1,1],[0,1,0,1],[1,1,1,1]]
答案 1 :(得分:0)
这是另一种算法,可在一行中的itemCount
个单元格中分布rowLength
个项目。将currentCount
初始化为0。然后对于每个单元格:
itemCount
添加到currentCount
。currentCount
小于rowLength
,请使用单元格的原始值。currentCount
至少为rowLength
,请减去rowLength
,然后将单元格的值加1。此算法从您提供的输入中产生期望的输出。
我们可以将所需的状态写为简单的数据结构:
data Distribution = Distribution { currentCount :: Int
, itemCount :: Int
, rowLength :: Int
} deriving (Eq, Show)
在算法的每个步骤中,我们都需要知道是否要发出输出(并递增值),以及下一个状态值是什么。
nextCount :: Distribution -> Int
nextCount d = currentCount d + itemCount d
willEmit :: Distribution -> Bool
willEmit d = (nextCount d) >= (rowLength d)
nextDistribution :: Distribution -> Distribution
nextDistribution d = d { currentCount = (nextCount d) `mod` (rowLength d) }
要使其保持运行状态,我们可以将其打包在the State
monad中。然后,我们可以将上面的“对于每个单元格”列表写为一个函数:
distributeCell :: Int -> State Distribution Int
distributeCell x = do
emit <- gets willEmit
modify nextDistribution
return $ if emit then x + 1 else x
要在整个行上运行此命令,我们可以使用标准库中的traverse
函数。这需要某种“容器”和一个将单个值映射到monadic结果,并在同一monad中创建结果的“容器”的函数。这里“容器”类型为[a]
,“单子”类型为State Distribution a
,因此traverse
的专用类型签名为
traverse :: (Int -> State Distribution Int)
-> [Int]
-> State Distribution [Int]
我们实际上并不关心最终状态,我们只想要结果[Int]
,这就是evalState
所做的。这将产生:
distributeRow :: [Int] -> Int -> [Int]
distributeRow row count =
evalState
(traverse distributeCell row :: State Distribution [Int])
(Distribution 0 count (length row))
将其应用于整个矩阵是zipWith
的简单应用(给定两个列表和一个函数,使用两个列表中的项目对重复调用该函数,返回结果列表):
distributeOnes :: [[Int]] -> [Int] -> [[Int]]
distributeOnes = zipWith distributeRow