10
votes

F #: Comment puis-je séparer une séquence en une séquence de séquences?

arrière-plan: strong>

J'ai une séquence de données contiguës, timbrées temporelles. La séquence de données a une lacune dans laquelle les données ne sont pas contiguës. Je veux créer une méthode pour diviser la séquence en une séquence de séquences afin que chaque ultérieur contienne des données contiguës (divisez la séquence d'entrée aux lacunes). P>

Contraintes: strong>

  • La valeur de retour doit être une séquence de séquences pour vous assurer que les éléments ne sont produits que selon les besoins forts> (impossible d'utiliser la liste / la matrice / la mise en cache) li>
  • La solution ne doit pas être O (n ^ 2), probablement excluant un motif SEQ.TAKE - SEQ.SKIP (cf. Brian's Post) Li>
  • points bonus pour une approche fonctionnellement idiomatique (puisque je souhaite devenir plus compétente à la programmation fonctionnelle), mais ce n'est pas une exigence. Li> ul>

    Signature de la méthode forte> p> xxx pré>

    sur la face de celui-ci, le problème semblait trivial pour moi, mais même l'emplacement de la SEQ.AIRE IEnumerator <_>, compréhensions de séquence et déclarations de rendement, la solution me échappe. Je suis sûr que c'est parce que je manque toujours d'expérience avec la combinaison de F # -idioms, ou éventuellement parce que certaines constructions de langue ne sont pas encore exposées. P>

    // Test data
    let numbers = {1.0..1000.0}
    let baseTime = DateTime.Now
    let contiguousTimeStamps = seq { for n in numbers ->baseTime.AddMinutes(n)}
    
    let dataWithOccationalHoles = Seq.zip contiguousTimeStamps numbers |> Seq.filter (fun (dateTime, num) -> num % 77.0 <> 0.0) // Has a gap in the data every 77 items
    
    let timeBetweenContiguousValues = (new TimeSpan(0,1,0))
    
    dataWithOccationalHoles |> groupContiguousDataPoints timeBetweenContiguousValues |> Seq.iteri (fun i sequence -> printfn "Group %d has %d data-points: Head: %f" i (Seq.length sequence) (snd(Seq.hd sequence)))
    


1 commentaires

Référence croisée: ici est la même question , mais pour les listes.


8 Réponses :


1
votes

Vous semblez vouloir une fonction qui a une signature xxx

i.e. une fonction et une séquence, puis romprent la séquence d'entrée dans une séquence de séquences en fonction du résultat de la fonction.

mise en cache des valeurs dans une collection qui implémente iEnumerable serait probablement la plus simple (bien que pas vraiment puriste , mais éviter d'itération de l'entrée plusieurs fois. Il perdra une grande partie de la paresse de l'entrée): xxx

une mise en œuvre alternative pourrait passer la collection de cache (comme SEQ < 'A> ) à la fonction afin qu'il puisse voir plusieurs éléments pour choisir les points de rupture.


7 commentaires

Richard, j'espérais pouvoir éviter d'utiliser un cache pour les séquences intérieures.


De plus, le plus interne ne semble pas être scopé uniquement dans la déclaration IF. Avez-vous l'intention de rendre la cache mutable?


@Treeefrog: Oups Oui, il devrait s'agir d'une liste de références <'a>, va corriger cela.


@Treeefrog: Je ne pense pas que cela puisse être fait sans mise en cache: SEQ <'A> est une interface, vous avez besoin d'un type de béton pour produire des instances.


Alexey, pourriez-vous élaborer comment on utiliserait un flux de travail SEQ imbriqué?


Pour utiliser un flux de travail de SEQ interne tout en conservant l'itération unique de l'entrée, il faudrait directement à l'aide d'un ienumerable à partir de la SEQ (c'est-à-dire aller sur le ienumerator sous-jacent Le SEQ et l'appel getenumerator () directement). En effet, crée un SEQ.TAKEWHIIHIILE qui permet à un autre à l'épreuve à utiliser sur le reste de la séquence.


@Richard Droite. Haskell's est-ce gratuit, mais avec SEQ vous avez une difficulté.



1
votes

une solution de haskell, car je ne connais pas bien F # Syntaxe, mais il devrait être assez facile de traduire: xxx pré>

Il y a une fonction groupby :: (A - > a -> bool) -> [a] -> [[a]] code> dans le prélude: p>

La fonction de groupe prend une liste et renvoie une liste de listes telles que la concaténation du résultat est égale à l'argument. De plus, chaque sous-liste dans le résultat ne contient que des éléments égaux. Par exemple, P>

groupBy1 :: (a -> a -> Bool) -> [a] -> [[a]]
groupBy1 _    []            = [[]]
groupBy1 _    [x]           = [[x]]
groupBy1 comp (x : xs@(y : _))
  | comp x y  = (x : firstGroup) : otherGroups
  | otherwise = [x] : groups
  where groups@(firstGroup : otherGroups) = groupBy1 comp xs


1 commentaires

Alexey, vous travaillez sur une liste d'entrées et produisant une sortie de liste des listes. Comme je l'ai expliqué dans ma question, je dois opérer sur une séquence de séquences plutôt qu'une liste de listes, car dans les séquences F # sont générées au besoin, par opposition aux listes immédiatement construites en mémoire (qui est un problème pour très grand. ensembles de données)



2
votes

J'ai traduit le haskell d'Alexey à F #, mais ce n'est pas joli en F #, et toujours un élément trop désireux.

J'attends qu'il y ait une meilleure façon, mais je devrai réessayer plus tard. < Pré> xxx


2 commentaires

Brian, moi, lorsque j'essaie de FSI votre code, je reçois le message d'erreur ci-dessous, même si j'ai Fshaarp.Powerpack.dll référencée. (Je peux même trouver la lazyliste dans le powerpack à l'aide du navigateur d'objet) "Le type" Lazyliste "n'est pas défini. Une construction avec ce nom a été trouvée dans fsharp.powerpack.dll, qui contient des modules et des types qui ont été implicitement référencés dans certains Les versions précédentes de F #. Vous devrez peut-être ajouter une référence explicite à cette DLL afin de compiler ce code. "


FSI ne peut pas voir des références dans le projet; vous devez dire #r "fshaarp.powerpack.dll" ;; dans la fenêtre FSI pour obtenir cette référence.



0
votes

ci-dessous est un code qui fait ce que je pense que vous voulez. Ce n'est pas idiomatique f #.

(Cela peut être similaire à la réponse de Brian, bien que je ne puisse pas dire parce que je ne connaissais pas la sémantique de la luzyliste.)

mais cela ne correspond pas exactement à votre spécification de test: SEQ. la longueur énumère toute son entrée. Votre "code de test" appelle seq.length puis appelle seq.hd . Cela générera un énumérateur deux fois, et comme il n'y a pas de mise en cache, les choses se gâtent. Je ne sais pas s'il y a un moyen propre pour permettre à plusieurs énumérateurs sans mise en cache. Franchement, SEQ > n'est peut-être pas la meilleure structure de données pour ce problème.

Quoi qu'il en soit, voici le code: xxx


5 commentaires

Je pense que votre utilisation du mot clé utilise provoque les problèmes d'énumérant deux fois vos séquences. En dehors de la main, je ne suis pas sûr s'il y a un moyen facile de disposer de l'énumérateur correctement tout en permettant toujours plusieurs travers.


@KVB, pouvez-vous élaborer? Je n'ai pas essayé d'exécuter ce code, mais en un coup d'œil, ça va bien pour moi. Y a-t-il une reproduction qui échoue?


Il semble que les problèmes rencontrent des solutions et d'autres solutions (itérant le deuxième SEQ avant la première ait été entièrement itérale) provenant d'une spécification erronée ou d'une sous-spécification du problème initial: il ne demande aucune mise en cache. Par conséquent, si le consommateur commence à consommer le 2e SEQ avant de finir de consommer le 1er, quel est le producteur (ce code que nous essayons tous d'écrire) censé céder le 2e SEQ? ...


... Si le 2e SEQ donne l'élément actuel et s'allume, le 1er SEQ est maintenant invalide (demandez-vous, que devrait-il (1er SEQ) céder si le consommateur reprend-le itération?). Si le 2e ne cédit pas l'élément actuel, que devrait-il faire à la place?


Fondamentalement, SEQ > Permet au consommateur de faire des choses (comme Skip PasqS SEQS intérieurs) qui n'ont aucun sens compte tenu de la nature des données sous-jacentes et de l'obligation de ne pas être mise en cache.



1
votes

(Modifier: Cela souffre d'un problème similaire à la solution de Brian, en ce sens que itérant la séquence extérieure sans itération sur chaque séquence intérieure gâchera mal les choses,

Voici une solution qui nie les expressions de séquence. La nature impératif de .NET ''s> iEnumerable est assez apparente ici, ce qui rend un peu plus difficile d'écrire un code idiomatique F # pour ce problème, mais j'espère qu'il est toujours clair ce qui se passe. xxx


2 commentaires

KVB, je suis impressionné par la fonctionnalité que vous avez réussi à faire ceci (en utilisant une seule cellule REF). Je vais l'étudier pour améliorer ma compréhension de la programmation fonctionnelle (la récursion en fait un peu difficile de suivre). Merci pour votre effort!


Ha, j'étais sur le point de commenter les problèmes similaires à la solution de Brian :-) Cela se transforme en un véritable cerveau - Twister (pas Brian-Twister).



0
votes

OK, voici une réponse que je ne suis pas malheureuse.

(éditer: je suis malheureux - c'est faux! Pas de temps pour essayer de réparer cependant. P>

Il utilise un peu d'un État impératif, mais il n'est pas trop difficile de suivre (à condition que vous vous souvienne de rappeler que "!" Est l'opérateur F # Dréréférence, et pas "non"). C'est aussi paresseux que possible et prend une SEQ comme entrée et retourne un SEQ de SEQS comme sortie. P>

let N = 20
let data =  // produce some arbitrary data with holes
    seq {
        for x in 1..N do
            if x % 4 <> 0 && x % 7 <> 0 then
                printfn "producing %d" x
                yield x
    }
let rec GroupBy comp (input:seq<_>) = seq {
    let doneWithThisGroup = ref false
    let areMore = ref true
    use e = input.GetEnumerator()
    let Next() = areMore := e.MoveNext(); !areMore
    // deal with length 0 or 1, seed 'prev'
    if not(e.MoveNext()) then () else
    let prev = ref e.Current
    while !areMore do
        yield seq {
            while not(!doneWithThisGroup) do
                if Next() then
                    let next = e.Current 
                    doneWithThisGroup := not(comp !prev next)
                    yield !prev 
                    prev := next
                else
                    // end of list, yield final value
                    yield !prev
                    doneWithThisGroup := true } 
        doneWithThisGroup := false }
let result = data |> GroupBy (fun x y -> y = x + 1)
printfn "Consuming..."
for group in result do
    printfn "about to do a group"
    for x in group do
        printfn "  %d" x


6 commentaires

Brian, c'est ce que je cherchais :-) Ma propre tentative de résolution du problème a utilisé une approche très similaire (complètes de séquence imbriquée), mais a produit des résultats erratiques. Au début, je pensais que cela était dû aux fermetures de compréhension de la séquence qui capturent toutes les mêmes cellules REF, mais je découvre tout simplement que l'erreur était due à des données de test erronées. Il me semble avoir apporté plusieurs appels à "DateTime.NOW" où un seul était destiné, ce qui entraîne l'échec de la comparaison ultérieure de DateTime. BTW - le "sinon (e.moVoVersionext ()) puis () sinon ..." semble être équivalent au plus simple "si e.movenext () alors ..."?


Plus j'utilise des expressions de séquence, moins je les comprends ... Pourquoi Seq.length (groupeby (amusement _ _ -> vrai) [1]) va dans une boucle infinie?


En outre, il semble que rien ne soit nécessaire de déclarer groupeby "Rec" car il n'est pas récursif :-)


Je reçois aussi une boucle infinie dans le "tandis que! Aremore faire". C'est comme si la déclaration de «rendement SEQ» n'est jamais entrée.


Ouais; Cette solution est totalement fausse, Argh. Si le consommateur exige des éléments de la SEQ extérieure, mais ne consomme pas d'éléments de la SEQ interne, par exemple, les effets ne se produisent jamais et aucun progrès n'a jamais été apporté à la liste d'origine.


Gardez à l'esprit que SEQ {...} est une expression qui revient toujours immédiatement. Jusqu'à ce que quelqu'un reçoit l'énumérateur de l'objet SEQ résultant et appelle MOVENNTEXT, il n'exécute aucun du code à l'intérieur du bloc SEQ. Ma solution suppose que le consommateur énumérera entièrement chaque SEQ interne avant MOVENEXT-ING vers le prochain extérieur.



1
votes

D'accord, essayez à nouveau. Atteindre la quantité optimale de la paresse s'avère un peu difficile dans F # ... du côté lumineux, c'est un peu plus fonctionnel que ma dernière tentative, en ce sens qu'elle n'utilise aucune cellule refanche.

let groupBy cmp (sq:seq<_>) =
  let en = sq.GetEnumerator()
  let next() = if en.MoveNext() then Some en.Current else None
  (* this function returns a pair containing the first sequence and a lazy option indicating the first element in the next sequence (if any) *)
  let rec seqStartingWith start =
    match next() with
    | Some y when cmp start y ->
        let rest_next = lazy seqStartingWith y // delay evaluation until forced - stores the rest of this sequence and the start of the next one as a pair
        seq { yield start; yield! fst (Lazy.force rest_next) }, 
          lazy Lazy.force (snd (Lazy.force rest_next))
    | next -> seq { yield start }, lazy next
  let rec iter start =
    seq {
      match (Lazy.force start) with
      | None -> ()
      | Some start -> 
          let (first,next) = seqStartingWith start
          yield first
          yield! iter next
    }
  Seq.cache (iter (lazy next()))


3 commentaires

Cela ne dispose pas de l'énumérateur. En un coup d'œil, vous pourriez probablement faire cela dans la branche «autre» de la prochaine ().


Je reçois une exception avec ce qui suit (à l'aide de VS2010 beta 1): "Erreur FS0193: Erreur interne: le module / l'espace de noms" Microsoft.fsharp.control "de l'unité de compilation" FSHARP.CORE "n'a pas contenu le Val 'Lazy`1. Force.1 '"Des idées?


@Treeefrog - Je n'ai pas de VS2010 sur cet ordinateur, mais je ne reçois pas cette erreur en utilisant F # 1.9.6.16 ... Le bit "Erreur interne" le fait ressembler à un bug de compilateur pour moi; peut-être le signaler à fsbugs@microsoft.com et voyez ce qu'ils disent?



3
votes

Je pense que cela fait ce que vous voulez XXX

Merci d'avoir demandé cette question cool


0 commentaires