diff --git a/src/fsharp/FSharp.Core/array.fs b/src/fsharp/FSharp.Core/array.fs index 29713557fc9..5531a690038 100644 --- a/src/fsharp/FSharp.Core/array.fs +++ b/src/fsharp/FSharp.Core/array.fs @@ -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)) + [] + 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 diff --git a/src/fsharp/FSharp.Core/array.fsi b/src/fsharp/FSharp.Core/array.fsi index ad9e82f27fb..5ede87a3c6d 100644 --- a/src/fsharp/FSharp.Core/array.fsi +++ b/src/fsharp/FSharp.Core/array.fsi @@ -243,6 +243,16 @@ namespace Microsoft.FSharp.Collections [] val exactlyOne: array:'T[] -> 'T + /// Returns the only element of the array or None if array is empty or contains more than one element. + /// + /// The input array. + /// + /// The only element of the array or None. + /// + /// Thrown when the input array is null. + [] + val tryExactlyOne: array:'T[] -> 'T option + /// 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. /// diff --git a/src/fsharp/FSharp.Core/list.fs b/src/fsharp/FSharp.Core/list.fs index f02ceffcff6..6092567f1f4 100644 --- a/src/fsharp/FSharp.Core/list.fs +++ b/src/fsharp/FSharp.Core/list.fs @@ -671,6 +671,12 @@ namespace Microsoft.FSharp.Collections | [] -> invalidArg "source" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString | _ -> invalidArg "source" (SR.GetString(SR.inputSequenceTooLong)) + [] + let tryExactlyOne (list : list<_>) = + match list with + | [x] -> Some x + | _ -> None + [] let transpose (lists : seq<'T list>) = checkNonNull "lists" lists diff --git a/src/fsharp/FSharp.Core/list.fsi b/src/fsharp/FSharp.Core/list.fsi index d42186e73d9..aba9b3505b7 100644 --- a/src/fsharp/FSharp.Core/list.fsi +++ b/src/fsharp/FSharp.Core/list.fsi @@ -172,6 +172,14 @@ namespace Microsoft.FSharp.Collections [] val exactlyOne: list:'T list -> 'T + /// Returns the only element of the list or None if it is empty or contains more than one element. + /// + /// The input list. + /// + /// The only element of the list or None. + [] + val tryExactlyOne: list:'T list -> 'T option + /// Tests if any element of the list satisfies the given predicate. /// /// The predicate is applied to the elements of the input list. If any application diff --git a/src/fsharp/FSharp.Core/seq.fs b/src/fsharp/FSharp.Core/seq.fs index 3162a0fe594..eab2599b1aa 100644 --- a/src/fsharp/FSharp.Core/seq.fs +++ b/src/fsharp/FSharp.Core/seq.fs @@ -1421,6 +1421,19 @@ namespace Microsoft.FSharp.Collections else invalidArg "source" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString + [] + 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 + [] let rev source = checkNonNull "source" source diff --git a/src/fsharp/FSharp.Core/seq.fsi b/src/fsharp/FSharp.Core/seq.fsi index 045a22b84ab..9ed5bed0965 100644 --- a/src/fsharp/FSharp.Core/seq.fsi +++ b/src/fsharp/FSharp.Core/seq.fsi @@ -558,6 +558,16 @@ namespace Microsoft.FSharp.Collections [] val exactlyOne: source:seq<'T> -> 'T + /// Returns the only element of the sequence or None if sequence is empty or contains more than one element. + /// + /// The input sequence. + /// + /// The only element of the sequence or None. + /// + /// Thrown when the input sequence is null. + [] + val tryExactlyOne: source:seq<'T> -> 'T option + /// Returns true if the sequence contains no elements, false otherwise. /// /// The input sequence. diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule.fs index 725775bf751..2718948cab1 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule.fs +++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule.fs @@ -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) + [] + member this.``tryExactlyOne should return the element from singleton arrays``() = + Assert.AreEqual(Some 1, Array.tryExactlyOne [|1|]) + Assert.AreEqual(Some "2", Array.tryExactlyOne [|"2"|]) + () + + [] + member this.``tryExactlyOne should return None on empty array``() = + Assert.AreEqual(None, Array.tryExactlyOne [||]) + + [] + member this.``tryExactlyOne should return None for arrays with more than one element``() = + Assert.AreEqual(None, Array.tryExactlyOne [|"1"; "2"|]) + + [] + member this.``tryExactlyOne should fail on null array``() = + CheckThrowsArgumentNullException(fun () -> Array.tryExactlyOne null |> ignore) + [] member this.GroupBy() = let funcInt x = x%5 diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/CollectionModulesConsistency.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/CollectionModulesConsistency.fs index c85ff999f67..53897216c2b 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/CollectionModulesConsistency.fs +++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/CollectionModulesConsistency.fs @@ -200,6 +200,18 @@ let ``exactlyOne is consistent`` () = smallerSizeCheck exactlyOne smallerSizeCheck exactlyOne +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 + +[] +let ``tryExactlyOne is consistent`` () = + smallerSizeCheck tryExactlyOne + smallerSizeCheck tryExactlyOne + smallerSizeCheck tryExactlyOne + 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 diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ListModule.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ListModule.fs index e9784237119..538e5a0d155 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ListModule.fs +++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ListModule.fs @@ -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) + [] + member this.``tryExactlyOne should return the element from singleton lists``() = + Assert.AreEqual(Some 1, List.tryExactlyOne [1]) + Assert.AreEqual(Some "2", List.tryExactlyOne ["2"]) + () + + [] + member this.``tryExactlyOne should return None for empty list``() = + Assert.AreEqual(None, List.tryExactlyOne []) + + [] + member this.``tryExactlyOne should return None for lists with more than one element``() = + Assert.AreEqual(None, List.tryExactlyOne ["1"; "2"]) + [] member this.TryHead() = // integer List diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/SeqModule2.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/SeqModule2.fs index 87483598568..700e4582934 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/SeqModule2.fs +++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/SeqModule2.fs @@ -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) + () + + [] + 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) + () + [] member this.Init() = diff --git a/tests/FSharp.Core.UnitTests/SurfaceArea.coreclr.fs b/tests/FSharp.Core.UnitTests/SurfaceArea.coreclr.fs index 841b5092f23..fc3dbbbed59 100644 --- a/tests/FSharp.Core.UnitTests/SurfaceArea.coreclr.fs +++ b/tests/FSharp.Core.UnitTests/SurfaceArea.coreclr.fs @@ -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) @@ -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) @@ -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]) diff --git a/tests/FSharp.Core.UnitTests/SurfaceArea.net40.fs b/tests/FSharp.Core.UnitTests/SurfaceArea.net40.fs index fb3d061f045..daebd2d65fe 100644 --- a/tests/FSharp.Core.UnitTests/SurfaceArea.net40.fs +++ b/tests/FSharp.Core.UnitTests/SurfaceArea.net40.fs @@ -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) @@ -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) @@ -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])