Haskell-重命名列表中的重复值

Haskell-重命名列表中的重复值,haskell,duplicates,nested-lists,Haskell,Duplicates,Nested Lists,我有一个字符串列表,例如 [["h","e","l","l","o"], ["g","o","o","d"], ["w","o","o","r","l","d"]] 我想重命名子列表外部的重复值,以便将整个子列表中的所有重复设置为新的随机生成值,这些值不是列表中预先存在的,而是同一子列表中相同的,因此可能的结果可能是: [["h","e","l","l","o"], ["g","t","t","d"], ["w","s","s","r","z","f"]] 我已经有一个函数可以随机生成一个大

我有一个字符串列表,例如

[["h","e","l","l","o"], ["g","o","o","d"], ["w","o","o","r","l","d"]]
我想重命名子列表外部的重复值,以便将整个子列表中的所有重复设置为新的随机生成值,这些值不是列表中预先存在的,而是同一子列表中相同的,因此可能的结果可能是:

[["h","e","l","l","o"], ["g","t","t","d"], ["w","s","s","r","z","f"]]
我已经有一个函数可以随机生成一个大小为1的字符串,名为randomStr:


我认为这肯定会引起更多的问题,而不是答案

import Control.Monad.State
import Data.List
import System.Random

mapAccumLM _ s [] = return (s, [])
mapAccumLM f s (x:xs) = do
  (s', y) <- f s x
  (s'', ys) <- mapAccumLM f s' xs
  return (s'', y:ys)

pick excluded for w = do
  a <- pick' excluded
  putStrLn $ "replacement for " ++ show for ++ " in " ++ show w ++ " excluded: " ++ show excluded ++ " = " ++ show a
  return a

-- | XXX -- can loop indefinitely
pick' excluded = do
  a <- randomRIO ('a','z')
  if elem a excluded
    then pick' excluded
    else return a

transform w = do
  globallySeen <- get
  let go locallySeen ch =
        case lookup ch locallySeen of
          Nothing  -> if elem ch globallySeen
                        then do let excluded = globallySeen ++ (map snd locallySeen)
                                a <- lift $ pick excluded ch w
                                return ( (ch, a):locallySeen, a)
                        else return ( (ch,ch):locallySeen,       ch )
          Just ch' -> return (locallySeen, ch')
  (locallySeen, w') <- mapAccumLM go [] w
  let globallySeen' = w' ++ globallySeen
  put globallySeen'
  return w'

doit ws = runStateT (mapM transform ws) []

main = do
 ws' <- doit [ "hello", "good", "world" ]
 print ws'

假设你想做我在下面的评论中概述的事情,最好把这个问题分成几个小部分,一次解决一个。我还建议利用基本模块和容器中的通用模块,因为这将使代码更简单、更快。特别是,模块Data.Map和Data.Sequence在这种情况下非常有用。我认为Data.Map在这里是最有用的,因为它有一些非常有用的函数,否则很难手工编写。正如您将看到的,序列最终用于提高效率

第一,进口:

import           Data.List      (nub)
import           Data.Map       (Map)
import           Data.Sequence  (Seq, (|>), (<|))
import qualified Data.Map       as Map
import qualified Data.Sequence  as Seq
import           Data.Foldable (toList)
import           System.Random (randomRIO)
import           Control.Monad (forM, foldM)
import           Control.Applicative ((<$>))
我喜欢为这样的函数提供我自己的名称,这样可以使代码更具可读性

现在我们可以获得所有唯一字符,我们希望能够为每个字符分配一个随机字符:

makeRandomLetterMap :: [String] -> IO (Map String String)
makeRandomLetterMap letters
    = fmap Map.fromList
    $ forM (lettersIn letters) $ \l -> do
        newL <- randomRIO ('a', 'z')
        return (l, [newL])
由于我们希望能够使用每个子列表中遇到的新字母更新此映射,根据需要覆盖以前遇到的字母,因此可以使用Data.map.union。既然工会支持它的第一个论点,我们需要把它翻过来:

对于基本情况,只需返回一个空列表

replaceDuplicatesRandomly (first:rest) = do
    m <- makeRandomLetterMap first
它获取当前映射m和到目前为止acc处理的所有子列表以及当前子列表字母的累加,替换所述子列表中的字母,为下一次迭代newM构建新映射,然后返回新映射以及处理的所有内容的累加,即acc |>NEWLETERKS。总之,功能是

replaceDuplicatesRandomly :: [[String]] -> IO [[String]]
replaceDuplicatesRandomly [] = return []
replaceDuplicatesRandomly (first:rest) = do
    m <- makeRandomLetterMap first
    (_, seqTail) <- foldM go (m, Seq.empty) rest
    return $ toList $ first <| seqTail
    where
        go (m, acc) letters = do
            let newLetters = replaceAllLetters m letters
            newM <- updateLetterMap m letters
            return (newM, acc |> newLetters)

最好将不纯净的计算和纯粹的计算分开

您不能替换为列表中已存在的字母,因此您需要获得一个新字母字符串:

fresh :: [String] -> String
fresh xss = ['a'..'z'] \\ foldr union [] xss
此函数用于将字符串中的一个字母替换为另一个字母:

replaceOne :: Char -> Char -> String -> String
replaceOne y y' = map (\x -> if x == y then y' else x)
对于字符串列表中的每个字符串,此函数每次用一个新字母替换一个字母:

replaceOnes :: Char -> String -> [String] -> (String, [String])
replaceOnes y = mapAccumL (\(y':ys') xs ->
    if y `elem` xs
       then (ys', replaceOne y y' xs)
       else (y':ys', xs))
比如说

replaceOnes 'o' "ijklmn" ["hello", "good", "world"]
replaceMany "mnpqstuvxyz" "lod" ["hello", "good", "world"]
返回

("lmn",["helli","gjjd","wkrld"])
("vxyz",["hemmp","gqqt","wsrnu"])
有点棘手的一点:

replaceMany :: String -> String -> [String] -> (String, [String])
replaceMany ys' ys xss = runState (foldM (\ys' y -> state $ replaceOnes y ys') ys' ys) xss
对于xss中的每个字符串,此函数每次都将ys中的每个字母替换为ys'中的新字母

比如说

replaceOnes 'o' "ijklmn" ["hello", "good", "world"]
replaceMany "mnpqstuvxyz" "lod" ["hello", "good", "world"]
返回

("lmn",["helli","gjjd","wkrld"])
("vxyz",["hemmp","gqqt","wsrnu"])
i、 e

此函数用于遍历字符串列表,并将列表其余部分的每个字符串的开头的所有字母替换为ys'包含的新字母

replaceDuplicatesBy :: String -> [String] -> [String]
replaceDuplicatesBy ys'  []      = []
replaceDuplicatesBy ys' (ys:xss) = ys : uncurry replaceDuplicatesBy (replaceMany ys' ys xss)
也就是说,它做你想做的,但没有任何随机性——只是从列表中选择新的字母

所有描述的函数都是纯函数。这是一个不纯的例子:

replaceDuplicates :: [String] -> IO [String]
replaceDuplicates xss = flip replaceDuplicatesBy xss <$> shuffle (fresh xss)
印刷品

["hello","gxxd","wcrzy"]
["hello","gyyd","wnrmf"]
["hello","gmmd","wvrtx"]

整个代码没有洗牌:

首先,不要使用unsafePerformIO,它不会像这样工作。如果你想让randomStr函数返回IO字符串,你就必须让它返回IO字符串;randomLetter=getStdRandom randomR'a',z',类似于中的示例。此外,为了澄清您在此处实际要做的事情,每次您在子列表中遇到一个字母时,如果它已经出现在以前的子列表中,您希望用相同的随机值替换该子列表中的每个出现。既然o出现在[h,e,l,l,o]中,[g,o,o,d]中的两个o都被替换为相同的随机字母,我将继续投票。一堆没有讨论甚至没有输入签名的代码肯定会提出问题,而不会回答问题。这很好——每个人都有权发表自己的观点。作为Haskell的初学者,这是一个很大的收获,但解释得很好,而且很有效。非常感谢您抽出时间!语法相当复杂,我需要习惯它。有没有推荐的链接?如果你还没有读过,这是一个开始的好地方。在那之后,你可以阅读现实世界中的哈斯克尔,但它开始变得相当过时了。在那之后,我能推荐的最好的是FPComplete网站上的教程,Gabriel Gonzalez在他的网站上有很多帖子。@SalmaFG老实说,我本可以通过引入州单子来跟上StdGen和当前地图,从而使这段代码更加地道,这将使计算更加纯粹,它所需要的只是一个初始发电机。这实际上使测试变得更容易,因为您可以提供一个固定的发电机,以确保获得正确的输出。然而,我决定现在不把IO之外的额外单子带进这个系统。
("vxyz",["hemmp","gqqt","wsrnu"])
'l's in "hello" are replaced by the first   letter in "mnpqstuvxyz"
'l'  in "world" is  replaced by the second  letter in "mnpqstuvxyz"
'o'  in "hello" is  replaced by the third   letter in "mnpqstuvxyz"
'o's in "good"  are replaced by the fourth  letter in "mnpqstuvxyz"
...
'd'  in "world" is  replaced by the seventh letter in "mnpqstuvxyz"
replaceDuplicatesBy :: String -> [String] -> [String]
replaceDuplicatesBy ys'  []      = []
replaceDuplicatesBy ys' (ys:xss) = ys : uncurry replaceDuplicatesBy (replaceMany ys' ys xss)
replaceDuplicates :: [String] -> IO [String]
replaceDuplicates xss = flip replaceDuplicatesBy xss <$> shuffle (fresh xss)
main = replicateM_ 3 $ replaceDuplicates ["hello", "good", "world"] >>= print
["hello","gxxd","wcrzy"]
["hello","gyyd","wnrmf"]
["hello","gmmd","wvrtx"]