F # - Comment regrouper les éléments précédents, ceci et les suivants dans Seq circulaire

En F # Comment convertir au mieux un code semblable à une séquence finie seq [0; 1; 2; 3; 4] dans une séquence de tuples comme seq [4,0,1; 0,1,2; 1,2,3; 2,3,4; 3,4,0] ?

Addition: My Seq represents circular data. In this case the vertices of a closed polyline. I need the neighboring elements to compute the angle of each corner.

4

7 Réponses

Voici une solution simple qui utilise uniquement des séquences. Notez que si l'entrée et la sortie sont toujours une liste, il existe une solution légèrement plus compliquée mais plus rapide qui n'utilise que des listes et parcourt l'entrée une seule fois.

// Example usage: neighbors [0..4]
let neighbors input =
    let inputLength = Seq.length input
    input
   //The sequence needs to be looped three times;
   //the first and last time handle the previous value for the first
   //element in the input sequence and the next value for the last
   //element in the input sequence, respectively.
    |> Seq.replicate 3
   //Start at the previous element of the first element in the input sequence.
    |> Seq.skip (inputLength - 1)
   //Take the same number of elements as the tuple.
    |> Seq.windowed 3
   //Only keep the same number of elements as the original sequence.
    |> Seq.take inputLength
   //Convert the arrays to tuples
    |> Seq.map (fun values ->
        values.[0], values.[1], values.[2])
   //Return the result as a list of tuples.
    |> Seq.toList
8
ajouté
est-il utile d'utiliser Seq si la première chose à faire est d'appeler Seq.length? Seq.replicate est-il aussi une extension?
ajouté l'auteur jk., source
@bytebuster Oui, c'est bien cela. mais compte tenu de l'échantillon, cela n'a peut-être pas d'importance. S'il est possible d'avoir des valeurs répétées, il serait alors préférable de mettre en œuvre la solution basée sur des listes que j'ai mentionnée.
ajouté l'auteur Jack P., source
@bytebuster Après avoir repensé à la question ce matin, j'ai réfléchi à une meilleure façon de traiter le problème, qui n'exige pas Seq.distinct et ne rencontre pas non plus de problèmes de duplication de valeurs.
ajouté l'auteur Jack P., source
@jk La seule raison pour laquelle j'utiliserais Seq ici est de garder cette fonction générique (en ce sens qu'elle pourrait fonctionner avec des séquences, des listes, des tableaux, etc.). Comme je l'ai mentionné dans la réponse, si vous savez que l'entrée sera toujours un type de collection plus spécifique, tel que list ou array , il existe des moyens plus rapides d'implémenter cette fonction. . Seq.replicate est une fonction fournie dans ExtCore .
ajouté l'auteur Jack P., source
let windowedEx n (s: seq<_>) =
  let r = ResizeArray(s)
  if r.Count > 1 then
    let last = r.[r.Count-1]
    r.Add(r.[0])
    r.Insert(0, last)
  Seq.windowed n r
3
ajouté

Il y a quelques bonnes réponses ici, voici encore un autre. Pour moi, il semble le plus lisible, a la complexité de O (n) , et conserve également une vérification d'erreur:

// Returns the last element of a sequence.
// Fails on empty sequence
let last xs =
    let len = Seq.length xs - 1
    if len < 0 then failwith "Sequence must be non-empty"
    xs
    |> Seq.skip len
    |> Seq.head

// Converts an array into a tuple
let toTuple = function
    | [|a; b; c|] -> (a, b, c)
    | _ -> failwith "Must be an array with exactly 3 elements"

let windowedBy3 xs =
    seq {
        yield last xs;
        yield! xs;
        yield Seq.head xs
    }
    |> Seq.windowed 3
    |> Seq.map toTuple

// Usage
Seq.init 5 id
|> windowedBy3
|> Seq.iter (printf "%A; ")
3
ajouté

Cela donne la bonne réponse, bien que l'élément que vous avez en tant que premier arrive maintenant en dernier mais ce n'est pas un problème, vous pouvez toujours trouver l'angle pour chaque ensemble de trois points.

let cycle s =
    Seq.append s (Seq.take 2 s)//append the first two elements to the and
    |> Seq.windowed 3          //create windows of 3
    |> Seq.map (fun a -> (a.[0], a.[1], a.[2]))//create tuples


// test
[0;1;2;3;4] |> cycle

// returns:
>
  val it : seq =
  seq [(0, 1, 2); (1, 2, 3); (2, 3, 4); (3, 4, 0); ...]
3
ajouté

Si vous n’avez pas besoin de lazyness, l’utilisation d’un tableau intermédiaire pourrait être plus efficace, par exemple.

// get items (i-1, i, i+1) from arr; wrap around at the boundaries
let adj3 i (arr: 'a[]) =
   //modulo operator that works correctly
    let inline (%%) x y = ((x % y) + y) % y
    let len = arr.Length
    arr.[(i - 1) %% len], arr.[i], arr.[(i + 1) %% len]

let windowed3 s = seq { 
    let sarr = s |> Seq.toArray    
    for i = 0 to sarr.Length do 
        yield adj3 i sarr }

La complexité temporelle est dans O ( n ).

3
ajouté
Non je ne pense pas. Obtenir le dernier élément d'une séquence nécessite l'énumération/le forçage de la séquence entière au moins une fois (ce qui est ce que Seq.toArray , mais aussi Seq.skip len , ResizeArray <_> (s) etc.
ajouté l'auteur Frank, source
J'aime votre opérateur modulo. Un seq peut-il encore être paresseux si j'ai besoin de connaître le dernier élément dès le début?
ajouté l'auteur Goswin, source

Je le ferais comme ça:

let neighbors xs =
  match Array.ofSeq xs with
  | [||] -> [||]
  | [|x|] -> [|x, x, x|]
  | xs ->
      let n = xs.Length
      [|yield xs.[n-1], xs.[0], xs.[1]
        for i=1 to n-2 do
          yield xs.[i-1], xs.[i], xs.[i+1]
        yield xs.[n-2], xs.[n-1], xs.[0]|]

Les comparaisons sont généralement beaucoup plus rapides que l'arithmétique modulo entière. Pour accélérer le processus, pré-allouez le tableau et remplissez les éléments au lieu d'utiliser une expression de séquence.

2
ajouté

À quoi ressemblerait une solution générale pour Seq.circularWindowed ? Étant donné la taille de la fenêtre n , il devra consommer les premiers éléments n - 1 tout en préservant la paresse pour le reste. Au cas où il n'y aurait pas plus de n - 1 éléments dans la source, cela produirait une séquence vide.

C'est donc un ResizeArray pour le cache et une expression de séquence pour tout assembler.

module Seq =
    let circularWindowed n (xs : seq<_>) =
        let en = xs.GetEnumerator()
        let ra = ResizeArray()
        while ra.Count < n - 1 && en.MoveNext() do
            ra.Add en.Current
        seq{
            if en.MoveNext() then 
                yield! ra
                yield en.Current
                while en.MoveNext() do
                    yield en.Current
                yield! ra }
        |> Seq.windowed n

seq [0; 1; 2; 3; 4]
|> Seq.circularWindowed 3
|> Seq.toList
// val it : int [] list =
//   [[|0; 1; 2|]; [|1; 2; 3|]; [|2; 3; 4|]; [|3; 4; 0|]; [|4; 0; 1|]]
1
ajouté