从列表中创建列表列表,同时更改一个始终不同的值 Haskell

Create list of lists from a list while changing one and always different value Haskell

我有一个包含 9 个整数的列表,其值为 1、-1、0,例如:

[-1, 0, 0, 1, -1, -1, 1, 1, 0] 

我想做的是从这个列表创建列表列表,其中每个列表只包含一个更改并且始终不同。对于每个 -1,我想将其更改为 0。

示例:

来自列表:

[-1,0,0,1,-1,-1,1,1,0], 

我要得到结果:

[ [ 0, 0, 0, 1, -1, -1, 1, 1, 0]
, [-1, 0, 0, 1,  0, -1, 1, 1, 0]
, [-1, 0, 0, 1, -1,  0, 1, 1, 0]
]

所以每个列表只更改了一个值,并且每个列表都有不同的值。我什至不知道如何开始。

您始终需要做的第一件事就是找出函数的类型签名。在你的情况下你想要

lister :: [Int] -> [[Int]]

然后,当您想遍历列表但要跟踪已更改的索引时,一种简单的方法是列出列表的列表(很难理解,只需查看代码)然后用它的索引压缩它。然后对于每个列表,您将元素切换到该位置。这是你的代码

lister :: [Int] -> [[Int]]
lister ls = [switch i l | (i,l) <- zip [0..9] (repeat ls)]

然后你需要一个切换函数,根据你的规则切换第i个位置的元素:

switch :: Int -> [Int] -> [Int]
switch 0 ls = ls
switch n ls = [if i == n && x == -1 then 0 else x | (i,x) <- zip [1..] ls]

请注意,这 returns 9 个列表,每个列表对应原始列表中的每个元素。因此它包含一些重复项。您可以使用 Data.List 中的 nub 消除它们,请注意,因为它是 O(n^2)

这是您的完整代码:

import Data.List

lister :: [Int] -> [[Int]]
lister ls = nub [switch i l | (i,l) <- zip [0..9] (repeat ls)]

switch :: Int -> [Int] -> [Int]
switch 0 ls = ls
switch n ls = [if i == n && x == -1 then 0 else x | (i,x) <- zip [1..] ls]

再试一次:

zeros :: [Int] -> [Int] -> [[Int]]
zeros _ []     = []
zeros h (x:xs) = [h ++ newX:xs] ++ zeros nextH xs
    where newX = if x == (-1) then 0 else x
        nextH = h ++ [x]

switch xs = ((filter (/= xs)) . (zeros [])) xs

用法:

main = print $ switch [-1, 0, 0, 1, -1, -1, 1, 1, 0]

显然这是一个非常具体的问题。放眼大局通常很有用:这是什么更普遍的问题的特例?显然,在这里,我们正在查看一个列表,并且可能会以零种或多种方式看到我们希望替换的元素。此外,我们希望看看有多少种方法可以进行有限数量的此类替换。所以,让我们在考虑如何专门化到我们原来的问题之前实现一般情况:

import Control.Applicative (Alternative, empty, (<|>))

replaceNTimes :: Alternative f => (a -> f a) -> Int -> [a] -> f [a]
replaceNTimes _ 0 xs = pure xs
replaceNTimes _ _ [] = empty
replaceNTimes f n (x:xs) = replaceHere <|> keepLooking
  where replaceHere = (:) <$> f x <*> replaceNTimes f (n - 1) xs
        keepLooking = (x:) <$> replaceNTimes f n xs

如果我们有 "budget" 个剩余的零替换,我们只需 return 列表的其余部分。如果我们有剩余预算但列表为空,我们将中止,因为我们未能进行预期数量的替换。否则,我们咨询我们的替换建议函数以查看哪些替换在当前位置是合法的,并选择其中之一并使用较小的 N 递归,或者使用 none 并使用相同的 N 递归。

有了这个工具,原来的问题就很简单了:我们只需将 N 专门化为 1(恰好进行一次替换),并提供一个仅建议将 -1 替换为 0 的替换函数:

replaceSingleNegativeOneWithZero :: [Int] -> [[Int]]
replaceSingleNegativeOneWithZero = replaceNTimes go 1
  where go (-1) = [0]
        go _ = []

并进行测试以确保我们得到预期的输出:

*Main> replaceSingleNegativeOneWithZero [-1,0,0,1,-1,-1,1,1,0]
[ [0,0,0,1,-1,-1,1,1,0]
, [-1,0,0,1,0,-1,1,1,0]
, [-1,0,0,1,-1,0,1,1,0]]