F# - how to group previous, this and next elements in circular Seq

你离开我真会死。 提交于 2019-12-22 09:11:55

问题


In F# How to best convert a finite Sequence-like seq [0; 1; 2; 3; 4] into a Sequence of tuples like 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.


回答1:


I would do it like this:

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]|]

Comparisons are generally much faster than modulo integer arithmetic. To make it faster, pre-allocate the array and fill in the elements instead of using a sequence expression.




回答2:


Here's a simple solution which only uses sequences. Note that if the input and output is always going to be a list, there's a slightly more complicated but faster solution which only uses lists and traverses the input just once.

// 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



回答3:


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



回答4:


There are some good answers here, here's yet another one. To me, it looks most readable, has complexity of O(n), and also it retains some error checking:

// 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; ")



回答5:


This gives the right answer, although the element you have as first now comes last but that's not an issue, you can still find the angle for every set of three point.

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<int * int * int> =
  seq [(0, 1, 2); (1, 2, 3); (2, 3, 4); (3, 4, 0); ...]



回答6:


If you don't require lazyness, using an intermediate array might be more efficient, e.g.

// 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 }

Time complexity is in O(n).




回答7:


What would a general solution for Seq.circularWindowed look like? Given window size n, It would need to consume the first n - 1 elements up front, while preserving lazyness for the rest. In case there are no more than n - 1 elements in the source, it produces an empty sequence.

So it's a ResizeArray for cache and a sequence expression to stitch it all together.

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|]]


来源:https://stackoverflow.com/questions/17238851/f-how-to-group-previous-this-and-next-elements-in-circular-seq

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!