1
votes

Comment faire une liste triée de multiples pour plusieurs nombres?

J'ai des problèmes avec un devoir de ma classe Haskell. J'ai déjà résolu un problème partiel de cette tâche: je dois écrire une fonction qui prend un Int et crée une liste infinie avec les multiples de cet Int.

[3,5,6,10,9,15,12,20,15,25]

function d = [y*x | y <- [1..], x <- d]

donne

[3,5,6,9,10,12,15,18,20,21]

Dans la deuxième tâche, je dois étendre la fonction afin qu'elle accepte une liste de nombres et utilise chaque valeur de cette liste comme un facteur (d précédemment). Par exemple:

ghci> take 10 (function [3, 5])

devrait donner

[3,6,9,12,15,18,21,24,27,30]

Déjà essayé une compréhension de liste comme

ghci> take 10 (function 3)


7 commentaires

Essayez d'utiliser filter avec la fonction modulo


Que signifie «forme indésirable»?


@talex Ma solution actuelle donne: ghci> prendre 10 (fonction [3,5]) ---> [3,5,6,10,9,15,12,20,15,25] (1 * 3, 1 * 5, 2 * 3, 2 * 5, 3 * 3, 3 * 5, 4 * 3, 4 * 5, 5 * 3, 5 * 5), mais il doit être trié par ordre croissant comme [3,5,6, 9,10,12,15,18,20,21] (1 * 3, 1 * 5, 2 * 3, 3 * 3, 2 * 5, 4 * 3, 3 * 5, 5 * 3, 4 * 5 , 6 * 3)


Au lieu de commencer par rien et d'ajouter des nombres «corrects», essayez de commencer par tous les nombres et de supprimer les «faux» nombres.


[d * x | x <- [1 ..]] pourrait être simplifié en [d, d + d ..] .


map (d *) [1 ..] est sans doute plus idiomatique (ou du moins je dirais que c'est le cas, mais je suis nouveau dans le langage donc mon jugement stylistique pourrait être faux)


@RobinZigmond C'est moins efficace: pour chaque nouvel élément, vous devez multiplier x à partir de zéro, alors que ma solution n'a qu'à ajouter x au résultat précédent.


5 Réponses :


1
votes

Si vous voulez le faire avec la fonction modulo, vous pouvez définir un simple one-liner

baz ds = O.nub $ foldr O.merge [] [ [d, d+d ..] | d <- ds ]
       = foldr    O.union [] [ [d, d+d ..] | d <- ds ]
       = O.foldt' O.union [] [ [d, d+d ..] | d <- ds ]
       = O.unionAll [ [d, d+d ..] | d <- sort ds ]
       = (O.unionAll . map (iterate =<< (+)) . sort)  ds

ou, sous une forme plus lisible,

import qualified Data.List.Ordered as O
import           Data.List               (sort)

bar :: (Ord a, Num a, Enum a) => [a] -> [a]
bar ds = foldr    O.merge [] [ [d, d+d ..] | d <- ds ]
       = O.foldt' O.merge [] [ [d, d+d ..] | d <- ds ]   -- more efficient, 
       = O.mergeAll [ [d, d+d ..] | d <- sort ds ]       -- tree-shaped folding


0 commentaires

1
votes

La réponse ici montre juste l'idée, ce n'est pas une solution optimisée, il peut exister de nombreuses façons de l'implémenter.

Premièrement, calculez toute la valeur de chaque facteur à partir de la liste saisie:

import Data.Sequence (update, fromList)
import Data.Foldable (toList)

function :: [Int] -> [Int]
function xs = removeDup $ sortMulti $ map (\d->[d*x|x<-[1..]]) xs
    where sortMulti xss = 
            let idx = findMinValueIndex $ zip [0..] xss
            in  head (xss!!idx):sortMulti (updateList idx (tail $ xss!!idx) xss)

removeDup::[Int]->[Int]
removeDup [] = []
removeDup [a] = [a]
removeDup (x:xs) | x == head xs = removeDup xs
                 | otherwise = x:removeDup xs

findMinValueIndex::[(Int, [Int])]->Int
findMinValueIndex xss = minimum $ 
                        map fst $ 
                        filter (\p-> (head $ snd p) == minValue) xss
    where minValue = minimum $ map (head . snd) xss

updateList::Int->[Int]->[[Int]]->[[Int]]
updateList n xs xss = toList $ update n xs $ fromList xss

Par exemple: xs = [3, 5] donne

[[6, 9, ...], [5, 10, 15, ...]]

alors, trouvez la valeur minimale du 1er élément de chaque liste comme:

sortMulti xss = 
            let idx = findMinValueIndex $ zip [0..] xss
            in  head (xss!!idx):sortMulti (updateList idx (tail $ xss!!idx) xss

Une fois que nous avons trouvé la liste, tenir la valeur minimale, renvoyez-la et supprimez la valeur minimale de la liste comme suit:

findMinValueIndex::[(Int, [Int])]->Int
findMinValueIndex xss = minimum $ 
                        map fst $ 
                        filter (\p-> (head $ snd p) == minValue) xss
    where minValue = minimum $ map (head . snd) xss

Donc, par exemple, après trouver la première valeur (c'est-à-dire 3 ) du résultat, les listes pour trouver la valeur suivante est:

[[3, 6, 9, ...], [5, 10, 15, ...]]

répétez les étapes ci-dessus, nous pouvons construire la liste souhaitée. Enfin, supprimez les valeurs dupliquées. Voici le codage terminé:

map (\d->[d*x|x<-[1..]]) xs


1 commentaires

BTW j'ai essayé de simplifier votre fonction et je me suis retrouvé avec function = O.nub. déplier g. map (iterate = << (+)) where {g mss = Just (m, s: r) where {((m: s): r) = sortBy (tête de comparaison) mss}} , ce qui semble être un bon encodage de O.mergeAll dans les fonctions de base :) sous l'hypothèse que la liste d'entrée est courte (et ne contient que des listes infinies, bien sûr), donc < code> trier l'heure n'est pas un problème. O. est l'abréviation de Data.List.Ordered du package data-ordlist . :)



1
votes

Il existe une solution récursive assez intéressante. La fonction

function' :: Int -> [Int]
function' d = [d * x | x <- [1..]]

braid :: [Int] -> [Int] -> [Int]
braid []        bs = bs
braid as        [] = as
braid aa@(a:as) bb@(b:bs) 
  | a < b     = a:braid as bb
  | a == b    = a:braid as bs # avoid duplicates
  | otherwise = b:braid aa bs

function :: [Int] -> [Int]
function ds = foldr braid [] (map function' ds)

tresse crée la liste souhaitée "à la volée" en utilisant uniquement la tête et la paresse de l'entrée


0 commentaires

3
votes

Si vous pensez que d n'est pas un facteur comme

function' ds   
    | null ds   = [1..]
    | otherwise = [ x | x <- [1..], divByAnyIn ds x ]
    where
      divByAnyIn ds x = 
          case ds of
            (d:ds') -> if x `mod` d == 0 then True 
                                         else divByAnyIn ds' x
            _       -> False

mais plutôt

function ds 
    | null ds   = [1..]
    | otherwise = [ x | x <- [1..], qualifies x ]
    where
      qualifies x = any (==0) $ (flip mod) <$> ds <*> [x]

alors vous peut rechercher la compréhension de la liste à partir de la liste [1 ..] et ajouter une fonction de prédicat, par exemple:

y `mod` d == 0,

Une version plus expressive qui est peut-être plus facile à comprendre au début:

y = x * d 


0 commentaires

1
votes

J'ai une ligne unique.

commutative a b = combine [a] [b] == combine [b] [a]

runtime devrait être O (n² + n * d) dans la liste résultante. Le nub s'exécute en O (n²). Ce serait bien de s'en débarrasser.

i xs = foldr1 combine [[x, x+x ..] |x<- sort xs]
    where
      combine l [] = l
      combine [] r = r
      combine l@(x:xs) r@(y:ys)
        | x < y = (x: combine xs r)
        | x > y = (y: combine l ys)
        | otherwise = (x: combine xs ys)

Cela fonctionne assez bien. Il devrait fonctionner en O (n * d). J'ai aussi cette version qui, à mon avis, fonctionne au moins aussi bien que g , mais apparemment, elle fonctionne mieux que f et pire que g .

h xs = [x |x<-[1..], or [x `mod` d == 0 |d<-xs] ]

Je ne sais pas pourquoi, ou est paresseux pour autant que je sache et je ne vois aucune raison pour laquelle il devrait fonctionner Ralentissez. En particulier, il ne s'adapte pas aussi bien lorsque vous augmentez la longueur de la liste d'entrée.

g xs = [x |x<-[1..], let ys = map (mod x) xs in 0 `elem` ys]

Ce n'est plus une seule ligne, mais le plus rapide que j'ai pu trouver. Je ne sais pas à 100% pourquoi cela fait une si grande différence à l'exécution si vous vous pliez à droite ou à gauche et si vous triez la liste d'entrée à l'avance. Mais cela ne devrait pas faire de différence sur le résultat puisque:

import Data.List (nub)

f xs = nub [x|x<-[1..], d<-xs, x `mod` d == 0]

take 10 $ f [3,5] -- [3,5,6,9,10,12,15,18,20,21]

Je trouve complètement insensé de penser à ce problème en termes de pliage d'une fonction récursive sur une liste de listes interminables de multiples de coefficients d'entrée.

Sur mon système, il est toujours un facteur 10 plus lent qu'une autre solution présentée ici en utilisant Data.List.Ordered.


7 commentaires

Vous pouvez considérer ce problème comme "J'ai un tas de flux croissants et je dois en créer un seul flux croissant". La façon dont je pense à cela est "Laissez-moi définir un accumulateur acc et une fonction f qui décident à chaque étape quel élément de tous les flux va dans l'accumulateur". f doit être récursif, car à chaque étape vous faites de même. De ce point de vue, foldr sur une fonction récursive semble être le candidat parfait. Juste partager mes pensées


@steve combine = Data.List.Ordered.union . :) récursif ou non, il combine simplement deux flux en un seul. vous vouliez dire bon "fou", comme excitant, non? quant aux différences de vitesse, vous faites seulement des allusions, pas de description, mais elles ont du sens. voir la discussion sur le pliage "à gauche" vs "à droite" (et en forme d'arbre) sur wiki.haskell. org / Prime_numbers . mais seulement si vous utilisez plus de 2 diviseurs; s'il y en a 2, il ne devrait pas y avoir de différence, car il n'y a pas de structure d'approfondissement.


vous le pliez sur une liste de seulement 2 listes btw (si xs == [2,3] ) (chaque est infinie bien sûr). mais il est également possible de se replier sur n liste infinie. exemples sur la page susmentionnée et dans Data.List.Ordered. avec elle, nous pouvons même appeler O.unionAll [[n, n + n ..] | n <- [1 ..]] . --- le facteur 10x est dû au fait que les fonctions de package sont compilées.


Le facteur 10 est vraiment dû au fait qu'il n'a pas été compilé. Le compiler donne une course au coude à coude. @WillNess, bien sûr, je veux dire le bon "fou"


droite. :) au coude à coude car c'est la même chose, essentiellement. vous pouvez le voir dans le code source.


enfin, pas exactement. Je compare avec j xs = foldr O.union [] [[x, x + x ..] | x <- xs] et plier O.union est toujours plus rapide que ma fonction de combinaison.


mais oui, vous avez absolument raison. C'est fondamentalement la même chose.