F# - 循環的なSeqの前の、次の要素をグループ化する方法

F#で有限シーケンスのような seq [0; 1; 2; 3; 4]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 答え

ここでは、シーケンスのみを使用する単純なソリューションがあります。入力と出力が常にリストになる場合、リストを使用し入力を1回だけトラバースするもう少し複雑ですが高速なソリューションがあります。

// 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
追加された
あなたが最初に行うことがSeq.lengthを呼び出すのであれば、Seqを使って多くの点がありますか?また、Seq.replicate拡張子ですか?
追加された 著者 jk.,
@bytebusterはい、そうです。サンプルの入力があれば、それは問題ではないかもしれません。繰り返し値を持つことができる場合は、私が言及したリストベースのソリューションを実装する方が良いでしょう。
追加された 著者 Jack P.,
@bytebuster今朝もう一度考えてみたら、 Seq.distinct を必要とせず、重複した値の問題にも対処しない、問題を処理するためのより良い方法を考えました。
追加された 著者 Jack P.,
@jkここで Seq を使用する唯一の理由は、この関数を汎用的に保つことです(シーケンス、リスト、配列などで機能するという点で)。しかし、答えで述べたように、入力が常に listarray のようなより具体的なコレクション型であることを知っているなら、この関数をより速く実装する方法があります。 Seq.replicate は、 ExtCore で提供される関数です。
追加された 著者 Jack P.,
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
追加された

ここには良い答えがいくつかありますが、もう一つはここにあります。私にとっては、最も読みやすいように見え、 O(n)の複雑さがあり、エラーチェックもいくつか残っています。

// 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
追加された

これは正解ですが、あなたが最初に持っている要素は最後になりますが、それは問題ではありませんが、3点ごとに角度を見つけることができます。

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
追加された

レイジーを必要としない場合は、中間配列 を使用するとより効率的になります。

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

時間の複雑さはO( n )です。

3
追加された
いいえ、私はそうは思わない。シーケンスの最後の要素を取得するには、少なくとも Seq.toArray だけでなく、 Seq.skip lenResizeArray <_>(s)など)。
追加された 著者 Frank,
あなたのモジュロ演算子が好きです。私は最初からそれの最後の要素を知る必要があるならば、seqはまだ怠惰かもしれませんか?
追加された 著者 Goswin,

私は次のようにします:

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

比較は一般に、モジュロ整数演算よりもはるかに高速です。より高速にするには、シーケンスを使用する代わりに、配列を事前に割り当てて要素を入力します。

2
追加された

Seq.circularWindowed の一般的なソリューションはどのように見えますか?ウィンドウのサイズ n が与えられていれば、最初の n - 1 要素を最前面に消費する必要があります。ソースに n - 1 要素がない場合、空のシーケンスが生成されます。

したがって、キャッシュ用のResizeArrayと、それをすべてつなぎ合わせるシーケンス式です。

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
追加された