Output an element the number of times I want

前端 未结 6 1801
-上瘾入骨i
-上瘾入骨i 2021-01-21 03:11

I have this code:

lado ::  [([Char],Int)] -> [[Char]]
lado xs = [a | (a,b) <- xs]

I need to output this:

> lado [("A         


        
相关标签:
6条回答
  • 2021-01-21 03:16

    I really like Aplet123's approach, but I think it can be made a bit more efficient. What's wrong with it as is? nonzero is used twice, which means the program will realize its result list rather than fusing it with the consumers of that list. So each time we go through the recursion, we'll allocate three new lists. Can we fix it? Let's start by assuming that the argument has no zeros in it.

    -- Assumes all the Ints are positive
    lado' :: [(a, Int)] -> [a]
    lado' [] = []
    lado' xns = map fst xns ++ rest
      where
        rest = lado' [(x, n - 1) | (x, n) <- xns, n /= 1]
    

    This is already much better. It only allocates two lists each time. But map fst will allocate a bunch of selector thunks to put in the list. We can fix that with another list comprehension:

    -- Assumes all the Ints are positive
    lado' :: [(a, Int)] -> [a]
    lado' [] = []
    lado' xns = start ++ rest
      where
        start = [x | (x, _) <- xns]
        rest = lado' [(x, n - 1) | (x, n) <- xns, n /= 1]
    

    Nice and clean! But what if the original list has zeros? We can filter it up front:

    lado :: [(a, Int)] -> [a]
    lado = lado' . filter (\(_, n) -> n > 0)
    

    There's still one little inefficiency in the case where there are a good number of non-tiny numbers: the representation of the list of pairs. A small improvement:

    data IPair a = IPair a !Int
    

    A bigger improvement would change the implementation of lists:

    data IPList a
      = Cons a !Int (IPList a)
      | Nil
    

    The downside of IPList is that you'd have to ditch list comprehensions.

    I see you're not allowed to use recursion, which is ... quite a silly requirement. Can you see how to work around that with iterate, takeWhile, and concat?

    0 讨论(0)
  • 2021-01-21 03:21

    If you don't care about efficiency, you can make this work, I guess:

    lado xs =
      [ str
        | i <- [1..maxCount]
        , (str, j) <- xs
        , j >= i ]
      where
        maxCount = maximum (map snd xs)
    
    0 讨论(0)
  • 2021-01-21 03:28

    You already use list comprehensions in your attempt. Use them some more.

    lado ::  [([Char],Int)] -> [[Char]]
    lado xs = [a | (a,b) <- xs, b <- [1..b]]
    

    testing:

    > lado [("A",3),("B",2),("C",1)]
    ["A","A","A","B","B","C"]
    

    As your question is saying,

    I have to output "A" 3 times, then "B" 2 times then "C" 1 time

    But if it is really ["A","B","C","A","B","A"] you want, then

    lado ::  [(a,Int)] -> [a]
    lado []        = []
    lado ((a,1):b) = a : lado b
    lado ((a,n):b) = a : lado (b ++ [(a,n-1))])
    

    which can be coded with unfoldr :: (b -> Maybe (a, b)) -> b -> [a] from Data.List,

    lado ::  [(a,Int)] -> [a]
    lado xs = unfoldr g $ xs
      where
      g []        = Nothing
      g ((a,1):b) = Just (a, b)
      g ((a,n):b) = Just (a, b ++ [(a,n-1)])
    

    which can be emulated with Prelude's iterate :: (a -> a) -> a -> [a] etc., as

    lado ::  [(a,Int)] -> [a]
    lado xs = map (fst . head) . takeWhile ( ... ) . iterate g $ xs
      where
      g []            = []
      g ((a, ... ):b) = b
      g ((a,  n  ):b) = b ++ [(a, ... )]
    

    Testing:

    > lado [("A",3),("B",2),("C",1)]
    ["A","B","C","A","B","A"]
    

    Fill in the blanks ... to make it work.


    as @dfeuer notes, the repeated singleton-appending at list's end is detrimental to overall efficiency. With big thanks to his input and discussion, including the two answers and comments here and even a github gist, this can be remedied by the usual FP paradigm (not to say "trick") of building a list in reverse, as

    lado ::  [(a,Int)] -> [a]
    lado xs = go (filter ((> 0) . snd) xs) []
      where
      go []       []  =  []
      go []        r  =  go (reverse r) []
      go ((a,1):b) r  =  a : go b r
      go ((a,n):b) r  =  a : go b ((a,n-1):r)
    

    With the reverse cost amortized over all the output this will add only a constant overhead per each output item.

    0 讨论(0)
  • 2021-01-21 03:35

    The transpose :: [[a]] -> [[a]] of [["A", "A", "A"], ["B", "B"], ["C"]] is:

    Prelude Data.List> transpose [["A", "A", "A"], ["B", "B"], ["C"]]
    [["A","B","C"],["A","B"],["A"]]
    

    if we then concatenate these with concat :: [[a]] -> [a], we get:

    Prelude Data.List> concat (transpose [["A", "A", "A"], ["B", "B"], ["C"]])
    ["A","B","C","A","B","A"]
    

    if we can thus make a lists of three "A"s, two "B"s, and one "C", then we can generate such list. I leave generating this list as an exercise. You can make use of replicate :: Int -> a -> [a] and use list comprehension, or map :: (a -> b) -> [a] -> [b].

    0 讨论(0)
  • 2021-01-21 03:39

    Will Ness came up with a nice expression in terms of rotations:

    lado ::  [(a,Int)] -> [a]
    lado []        = []
    lado ((a,1):b) = a : lado b
    lado ((a,n):b) = a : lado (b ++ [(a,n-1))])
    

    Unfortunately, this will be quite inefficient because using ++ to add an element to the end of a list takes linear time. This can be fixed by using a queue instead of a list. The queue is used in an ephemeral manner, so it can be a very simple one:

    -- Invariant: The Int is at least 1.
    data IStream a
      = ISCons a !Int (IStream a)
      | ISNil
    
    -- Invariant: The Int is at least 1.
    data IList a
      = ICons a !Int !(IList a)
      | INil
    
    data IQueue a = IQueue !(IStream a) !(IList a)
    
    -- Note: the list may be infinite
    listToIStream :: [(a, Int)] -> IStream a
    listToIStream = foldr go ISNil
      where
        go (a, n) r
          | n <= 0 = r
          | otherwise = ISCons a n r
    
    listToIQueue :: [(a, Int)] -> IQueue a
    listToIQueue xs = IQueue (listToIStream xs) INil
    
    dequeue :: IQueue a -> Maybe (Dequeued a)
    dequeue (IQueue (ISCons a i more) rear) = Just (Dequeued a i (IQueue more rear))
    dequeue (IQueue ISNil INil) = Nothing
    dequeue (IQueue ISNil (ICons a i more)) = Just $! (rotate ISNil a i more)
    
    data Dequeued a = Dequeued a !Int !(IQueue a)
    
    rotate :: IStream a -> a -> Int -> IList a -> Dequeued a
    rotate str a0 i0 INil = Dequeued a0 i0 (IQueue str INil)
    rotate str a0 i0 (ICons a i more) = rotate (ISCons a0 i0 str) a i more
    
    enqueue :: IQueue a -> a -> Int -> IQueue a
    enqueue (IQueue front rear) a i = IQueue front (ICons a i rear)
    

    Now we can define

    lado :: [(a, Int)] -> [a]
    lado = lado' . listToIQueue
    
    lado' :: IQueue a -> [a]
    lado' q = case dequeue q of
      Nothing -> []
      Just (Dequeued a 1 b) -> a : lado' b
      Just (Dequeued a n b) -> a : lado' (enqueue b a (n - 1))
    
    0 讨论(0)
  • 2021-01-21 03:41

    You can use recursion to accomplish this:

    lado :: [(a, Int)] -> [a]
    -- base case
    lado [] = []
    -- take each of the non-zero elements then recurse
    lado xs = map fst nonzero ++ lado subtracted
        where
            -- find elements with non-zero count
            nonzero = filter (\x -> snd x > 0) xs
            -- subtract one from the count for each of those elements
            subtracted = map (\(x, n) -> (x, n - 1)) nonzero
    
    0 讨论(0)
提交回复
热议问题