Podaj element tyle razy, ile chcę
Mam ten kod:
lado :: [([Char],Int)] -> [[Char]]
lado xs = [a | (a,b) <- xs]
Muszę to wypisać:
> lado [("A",3),("B",2),("C",1)]
["A","B","C","A","B","A"]
Muszę wypisać „A” 3 razy, następnie „B” 2 razy, a następnie „C” 1 raz, ale dostaję tylko to [„A”, „B”, „C”] z tym kodem.
Odpowiedzi
Próbujesz już używać wyrażeń listowych. Użyj ich więcej.
lado :: [([Char],Int)] -> [[Char]]
lado xs = [a | (a,b) <- xs, b <- [1..b]]
testowanie:
> lado [("A",3),("B",2),("C",1)]
["A","A","A","B","B","C"]
Jak mówi twoje pytanie,
Muszę wypisać „A” 3 razy, następnie „B” 2 razy, a następnie „C” 1 raz
Ale jeśli naprawdę tego ["A","B","C","A","B","A"]chcesz, to
lado :: [(a,Int)] -> [a]
lado [] = []
lado ((a,1):b) = a : lado b
lado ((a,n):b) = a : lado (b ++ [(a,n-1))])
które można zakodować z unfoldr :: (b -> Maybe (a, b)) -> b -> [a]od 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)])
które można emulować z Prelude iterate :: (a -> a) -> a -> [a]itp., jak
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, ... )]
Testowanie:
> lado [("A",3),("B",2),("C",1)]
["A","B","C","A","B","A"]
Wypełnij puste pola, ...aby zadziałało.
jak zauważa @dfeuer , powtarzające się dołączanie singletonów na końcu listy jest szkodliwe dla ogólnej wydajności. Dzięki jego wkładowi i dyskusji, w tym dwóm odpowiedziom i komentarzom tutaj, a nawet istocie na githubie , można temu zaradzić za pomocą zwykłego paradygmatu FP (żeby nie powiedzieć „sztuczki”) budowania listy w odwrotnej kolejności, jak
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)
Po zamortyzowaniureverse kosztu całej produkcji spowoduje to dodanie tylko stałego narzutu na każdą pozycję wyjściową.
Aby to osiągnąć, możesz użyć rekurencji:
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
transpose :: [[a]] -> [[a]]Z [["A", "A", "A"], ["B", "B"], ["C"]]IS:
Prelude Data.List> transpose [["A", "A", "A"], ["B", "B"], ["C"]]
[["A","B","C"],["A","B"],["A"]]
jeśli następnie połączymy je z concat :: [[a]] -> [a], otrzymamy:
Prelude Data.List> concat (transpose [["A", "A", "A"], ["B", "B"], ["C"]])
["A","B","C","A","B","A"]
jeśli w ten sposób możemy stworzyć listę trzech "A", dwóch "B"i jednej "C", to możemy wygenerować taką listę. Generowanie tej listy zostawiam jako ćwiczenie. Możesz używać replicate :: Int -> a -> [a]i używać rozumienia list, lub map :: (a -> b) -> [a] -> [b].
Bardzo podoba mi się podejście Aplet123 , ale myślę, że można je nieco usprawnić. Co jest z tym nie tak? nonzerojest używany dwukrotnie, co oznacza, że program zrealizuje swoją listę wyników, zamiast łączyć ją z odbiorcami tej listy. Więc za każdym razem, gdy przejdziemy przez rekursję, przydzielimy trzy nowe listy. Czy możemy to naprawić? Zacznijmy od założenia, że argument nie zawiera zer.
-- 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]
To już jest dużo lepsze. Za każdym razem przydziela tylko dwie listy. Ale map fstprzydzieli kilka opcji selektora do umieszczenia na liście. Możemy to naprawić za pomocą innego zrozumienia listy:
-- 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]
Ładnie i czysto! Ale co, jeśli oryginalna lista zawiera zera? Możemy to odfiltrować z góry:
lado :: [(a, Int)] -> [a]
lado = lado' . filter (\(_, n) -> n > 0)
W przypadku dużej liczby innych niż małe liczby istnieje jeszcze jedna mała nieefektywność: reprezentacja listy par. Mała poprawa:
data IPair a = IPair a !Int
Większe usprawnienie zmieniłoby implementację list:
data IPList a
= Cons a !Int (IPList a)
| Nil
Wadą IPListjest to, że musiałbyś porzucić listy składane.
Widzę, że nie możesz używać rekurencji, co jest ... dość głupim wymaganiem. Można zobaczyć, jak obejść to z iterate, takeWhilei concat?
Jeśli nie zależy Ci na wydajności, możesz to załatwić, myślę, że:
lado xs =
[ str
| i <- [1..maxCount]
, (str, j) <- xs
, j >= i ]
where
maxCount = maximum (map snd xs)
Will Ness wymyślił fajne wyrażenie pod względem rotacji:
lado :: [(a,Int)] -> [a]
lado [] = []
lado ((a,1):b) = a : lado b
lado ((a,n):b) = a : lado (b ++ [(a,n-1))])
Niestety będzie to dość nieefektywne, ponieważ użycie ++dodania elementu na końcu listy wymaga czasu liniowego. Można to naprawić, używając kolejki zamiast listy. Kolejka jest używana w sposób efemeryczny, więc może być bardzo prosta:
-- 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)
Teraz możemy zdefiniować
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))