在F#中如何最好地将类似序列的seq [0; 1; 2; 3; 4]
转换为像seq [4,0,1 ; 0,1,2 ; 1,2,3 ; 2,3,4 ; 3,4,0]
这样的元组序列?
增加: 我的Seq代表循环数据。在这种情况下,闭合折线的顶点。我需要相邻的元素来计算每个角的角度。
答案 0 :(得分:8)
这是一个只使用序列的简单解决方案。请注意,如果输入和输出始终是一个列表,则会有一个稍微复杂但更快的解决方案,它只使用列表并遍历输入一次。
// 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
答案 1 :(得分: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<int * int * int> =
seq [(0, 1, 2); (1, 2, 3); (2, 3, 4); (3, 4, 0); ...]
答案 2 :(得分: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
答案 3 :(得分: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; ")
答案 4 :(得分: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 )中。
答案 5 :(得分:2)
我会这样做:
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]|]
比较通常比模数整数运算快得多。为了加快速度,预先分配数组并填充元素而不是使用序列表达式。
答案 6 :(得分:1)
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|]]