Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/fsharp/FSharp.Core/array.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1243,6 +1243,12 @@ namespace Microsoft.FSharp.Collections
elif array.Length = 0 then invalidArg "array" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString
else invalidArg "array" (SR.GetString(SR.inputSequenceTooLong))

[<CompiledName("TryExactlyOne")>]
let tryExactlyOne (array:'T[]) =
checkNonNull "array" array
if array.Length = 1 then Some array.[0]
else None

let transposeArrays (array:'T[][]) =
let len = array.Length
if len = 0 then Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked 0 else
Expand Down
10 changes: 10 additions & 0 deletions src/fsharp/FSharp.Core/array.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,16 @@ namespace Microsoft.FSharp.Collections
[<CompiledName("ExactlyOne")>]
val exactlyOne: array:'T[] -> 'T

/// <summary>Returns the only element of the array or <c>None</c> if array is empty or contains more than one element.</summary>
///
/// <param name="array">The input array.</param>
///
/// <returns>The only element of the array or None.</returns>
///
/// <exception cref="System.ArgumentNullException">Thrown when the input array is null.</exception>
[<CompiledName("TryExactlyOne")>]
val tryExactlyOne: array:'T[] -> 'T option

/// <summary>Returns a new list with the distinct elements of the input array which do not appear in the itemsToExclude sequence,
/// using generic hash and equality comparisons to compare values.</summary>
///
Expand Down
6 changes: 6 additions & 0 deletions src/fsharp/FSharp.Core/list.fs
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,12 @@ namespace Microsoft.FSharp.Collections
| [] -> invalidArg "source" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString
| _ -> invalidArg "source" (SR.GetString(SR.inputSequenceTooLong))

[<CompiledName("TryExactlyOne")>]
let tryExactlyOne (list : list<_>) =
match list with
| [x] -> Some x
| _ -> None

[<CompiledName("Transpose")>]
let transpose (lists : seq<'T list>) =
checkNonNull "lists" lists
Expand Down
8 changes: 8 additions & 0 deletions src/fsharp/FSharp.Core/list.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,14 @@ namespace Microsoft.FSharp.Collections
[<CompiledName("ExactlyOne")>]
val exactlyOne: list:'T list -> 'T

/// <summary>Returns the only element of the list or <c>None</c> if it is empty or contains more than one element.</summary>
///
/// <param name="list">The input list.</param>
///
/// <returns>The only element of the list or None.</returns>
[<CompiledName("TryExactlyOne")>]
val tryExactlyOne: list:'T list -> 'T option

/// <summary>Tests if any element of the list satisfies the given predicate.</summary>
///
/// <remarks>The predicate is applied to the elements of the input list. If any application
Expand Down
13 changes: 13 additions & 0 deletions src/fsharp/FSharp.Core/seq.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1421,6 +1421,19 @@ namespace Microsoft.FSharp.Collections
else
invalidArg "source" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString

[<CompiledName("TryExactlyOne")>]
let tryExactlyOne (source : seq<_>) =
checkNonNull "source" source
use e = source.GetEnumerator()
if e.MoveNext() then
let v = e.Current
if e.MoveNext() then
None
else
Some v
else
None

[<CompiledName("Reverse")>]
let rev source =
checkNonNull "source" source
Expand Down
10 changes: 10 additions & 0 deletions src/fsharp/FSharp.Core/seq.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,16 @@ namespace Microsoft.FSharp.Collections
[<CompiledName("ExactlyOne")>]
val exactlyOne: source:seq<'T> -> 'T

/// <summary>Returns the only element of the sequence or <c>None</c> if sequence is empty or contains more than one element.</summary>
///
/// <param name="source">The input sequence.</param>
///
/// <returns>The only element of the sequence or None.</returns>
///
/// <exception cref="System.ArgumentNullException">Thrown when the input sequence is null.</exception>
[<CompiledName("TryExactlyOne")>]
val tryExactlyOne: source:seq<'T> -> 'T option

/// <summary>Returns true if the sequence contains no elements, false otherwise.</summary>
///
/// <param name="source">The input sequence.</param>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1186,6 +1186,24 @@ type ArrayModule() =
member this.``exactlyOne should fail on arrays with more than one element``() =
CheckThrowsArgumentException(fun () -> Array.exactlyOne [|"1"; "2"|] |> ignore)

[<Test>]
member this.``tryExactlyOne should return the element from singleton arrays``() =
Assert.AreEqual(Some 1, Array.tryExactlyOne [|1|])
Assert.AreEqual(Some "2", Array.tryExactlyOne [|"2"|])
()

[<Test>]
member this.``tryExactlyOne should return None on empty array``() =
Assert.AreEqual(None, Array.tryExactlyOne [||])

[<Test>]
member this.``tryExactlyOne should return None for arrays with more than one element``() =
Assert.AreEqual(None, Array.tryExactlyOne [|"1"; "2"|])

[<Test>]
member this.``tryExactlyOne should fail on null array``() =
CheckThrowsArgumentNullException(fun () -> Array.tryExactlyOne null |> ignore)

[<Test>]
member this.GroupBy() =
let funcInt x = x%5
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,18 @@ let ``exactlyOne is consistent`` () =
smallerSizeCheck exactlyOne<string>
smallerSizeCheck exactlyOne<NormalFloat>

let tryExactlyOne<'a when 'a : comparison> (xs : 'a []) =
let s = runAndCheckErrorType (fun () -> xs |> Seq.tryExactlyOne)
let l = runAndCheckErrorType (fun () -> xs |> List.ofArray |> List.tryExactlyOne)
let a = runAndCheckErrorType (fun () -> xs |> Array.tryExactlyOne)
consistency "tryExactlyOne" s l a

[<Test>]
let ``tryExactlyOne is consistent`` () =
smallerSizeCheck tryExactlyOne<int>
smallerSizeCheck tryExactlyOne<string>
smallerSizeCheck tryExactlyOne<NormalFloat>

let except<'a when 'a : equality> (xs : 'a []) (itemsToExclude: 'a []) =
let s = xs |> Seq.except itemsToExclude |> Seq.toArray
let l = xs |> List.ofArray |> List.except itemsToExclude |> List.toArray
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,20 @@ type ListModule() =
member this.``exactlyOne should fail on lists with more than one element``() =
CheckThrowsArgumentException(fun () -> List.exactlyOne ["1"; "2"] |> ignore)

[<Test>]
member this.``tryExactlyOne should return the element from singleton lists``() =
Assert.AreEqual(Some 1, List.tryExactlyOne [1])
Assert.AreEqual(Some "2", List.tryExactlyOne ["2"])
()

[<Test>]
member this.``tryExactlyOne should return None for empty list``() =
Assert.AreEqual(None, List.tryExactlyOne [])

[<Test>]
member this.``tryExactlyOne should return None for lists with more than one element``() =
Assert.AreEqual(None, List.tryExactlyOne ["1"; "2"])

[<Test>]
member this.TryHead() =
// integer List
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,17 +139,41 @@ type SeqModule2() =
// Empty Seq
let emptySeq = Seq.empty
CheckThrowsArgumentException ( fun() -> Seq.exactlyOne emptySeq)

// non-singleton Seq
let nonSingletonSeq = [ 0 .. 1 ]
CheckThrowsArgumentException ( fun() -> Seq.exactlyOne nonSingletonSeq |> ignore )

// null Seq
let nullSeq:seq<'a> = null
CheckThrowsArgumentNullException (fun () -> Seq.exactlyOne nullSeq)
()

[<Test>]
member this.TryExactlyOne() =
let IntSeq =
seq { for i in 7 .. 7 do
yield i }

Assert.AreEqual(Some 7, Seq.tryExactlyOne IntSeq)

// string Seq
let strSeq = seq ["second"]
Assert.AreEqual(Some "second", Seq.tryExactlyOne strSeq)

// Empty Seq
let emptySeq = Seq.empty
CheckThrowsArgumentException ( fun() -> Seq.exactlyOne [ 0 .. 1 ] |> ignore )

Assert.AreEqual(None, Seq.tryExactlyOne emptySeq)

// non-singleton Seq
let nonSingletonSeq = [ 0 .. 1 ]
Assert.AreEqual(None, Seq.tryExactlyOne nonSingletonSeq)

// null Seq
let nullSeq:seq<'a> = null
CheckThrowsArgumentNullException (fun () ->Seq.exactlyOne nullSeq)
()


CheckThrowsArgumentNullException (fun () -> Seq.tryExactlyOne nullSeq |> ignore)
()

[<Test>]
member this.Init() =

Expand Down
3 changes: 3 additions & 0 deletions tests/FSharp.Core.UnitTests/SurfaceArea.coreclr.fs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ Microsoft.FSharp.Collections.ArrayModule: System.Tuple`3[T1[],T2[],T3[]] Unzip3[
Microsoft.FSharp.Collections.ArrayModule: System.Type GetType()
Microsoft.FSharp.Collections.ArrayModule: T Average[T](T[])
Microsoft.FSharp.Collections.ArrayModule: T ExactlyOne[T](T[])
Microsoft.FSharp.Collections.ArrayModule: Microsoft.FSharp.Core.FSharpOption`1[T] TryExactlyOne[T](T[])
Microsoft.FSharp.Collections.ArrayModule: T FindBack[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], T[])
Microsoft.FSharp.Collections.ArrayModule: T Find[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], T[])
Microsoft.FSharp.Collections.ArrayModule: T Get[T](T[], Int32)
Expand Down Expand Up @@ -373,6 +374,7 @@ Microsoft.FSharp.Collections.ListModule: System.Tuple`3[Microsoft.FSharp.Collect
Microsoft.FSharp.Collections.ListModule: System.Type GetType()
Microsoft.FSharp.Collections.ListModule: T Average[T](Microsoft.FSharp.Collections.FSharpList`1[T])
Microsoft.FSharp.Collections.ListModule: T ExactlyOne[T](Microsoft.FSharp.Collections.FSharpList`1[T])
Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Core.FSharpOption`1[T] TryExactlyOne[T](Microsoft.FSharp.Collections.FSharpList`1[T])
Microsoft.FSharp.Collections.ListModule: T FindBack[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], Microsoft.FSharp.Collections.FSharpList`1[T])
Microsoft.FSharp.Collections.ListModule: T Find[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], Microsoft.FSharp.Collections.FSharpList`1[T])
Microsoft.FSharp.Collections.ListModule: T Get[T](Microsoft.FSharp.Collections.FSharpList`1[T], Int32)
Expand Down Expand Up @@ -507,6 +509,7 @@ Microsoft.FSharp.Collections.SeqModule: System.Tuple`2[System.Collections.Generi
Microsoft.FSharp.Collections.SeqModule: System.Type GetType()
Microsoft.FSharp.Collections.SeqModule: T Average[T](System.Collections.Generic.IEnumerable`1[T])
Microsoft.FSharp.Collections.SeqModule: T ExactlyOne[T](System.Collections.Generic.IEnumerable`1[T])
Microsoft.FSharp.Collections.SeqModule: Microsoft.FSharp.Core.FSharpOption`1[T] TryExactlyOne[T](System.Collections.Generic.IEnumerable`1[T])
Microsoft.FSharp.Collections.SeqModule: T FindBack[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], System.Collections.Generic.IEnumerable`1[T])
Microsoft.FSharp.Collections.SeqModule: T Find[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], System.Collections.Generic.IEnumerable`1[T])
Microsoft.FSharp.Collections.SeqModule: T Get[T](Int32, System.Collections.Generic.IEnumerable`1[T])
Expand Down
3 changes: 3 additions & 0 deletions tests/FSharp.Core.UnitTests/SurfaceArea.net40.fs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ Microsoft.FSharp.Collections.ArrayModule: System.Tuple`3[T1[],T2[],T3[]] Unzip3[
Microsoft.FSharp.Collections.ArrayModule: System.Type GetType()
Microsoft.FSharp.Collections.ArrayModule: T Average[T](T[])
Microsoft.FSharp.Collections.ArrayModule: T ExactlyOne[T](T[])
Microsoft.FSharp.Collections.ArrayModule: Microsoft.FSharp.Core.FSharpOption`1[T] TryExactlyOne[T](T[])
Microsoft.FSharp.Collections.ArrayModule: T FindBack[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], T[])
Microsoft.FSharp.Collections.ArrayModule: T Find[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], T[])
Microsoft.FSharp.Collections.ArrayModule: T Get[T](T[], Int32)
Expand Down Expand Up @@ -360,6 +361,7 @@ Microsoft.FSharp.Collections.ListModule: System.Tuple`3[Microsoft.FSharp.Collect
Microsoft.FSharp.Collections.ListModule: System.Type GetType()
Microsoft.FSharp.Collections.ListModule: T Average[T](Microsoft.FSharp.Collections.FSharpList`1[T])
Microsoft.FSharp.Collections.ListModule: T ExactlyOne[T](Microsoft.FSharp.Collections.FSharpList`1[T])
Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Core.FSharpOption`1[T] TryExactlyOne[T](Microsoft.FSharp.Collections.FSharpList`1[T])
Microsoft.FSharp.Collections.ListModule: T FindBack[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], Microsoft.FSharp.Collections.FSharpList`1[T])
Microsoft.FSharp.Collections.ListModule: T Find[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], Microsoft.FSharp.Collections.FSharpList`1[T])
Microsoft.FSharp.Collections.ListModule: T Get[T](Microsoft.FSharp.Collections.FSharpList`1[T], Int32)
Expand Down Expand Up @@ -494,6 +496,7 @@ Microsoft.FSharp.Collections.SeqModule: System.Tuple`2[System.Collections.Generi
Microsoft.FSharp.Collections.SeqModule: System.Type GetType()
Microsoft.FSharp.Collections.SeqModule: T Average[T](System.Collections.Generic.IEnumerable`1[T])
Microsoft.FSharp.Collections.SeqModule: T ExactlyOne[T](System.Collections.Generic.IEnumerable`1[T])
Microsoft.FSharp.Collections.SeqModule: Microsoft.FSharp.Core.FSharpOption`1[T] TryExactlyOne[T](System.Collections.Generic.IEnumerable`1[T])
Microsoft.FSharp.Collections.SeqModule: T FindBack[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], System.Collections.Generic.IEnumerable`1[T])
Microsoft.FSharp.Collections.SeqModule: T Find[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], System.Collections.Generic.IEnumerable`1[T])
Microsoft.FSharp.Collections.SeqModule: T Get[T](Int32, System.Collections.Generic.IEnumerable`1[T])
Expand Down