diff --git a/Standard/src/Arrays/Sorted.qs b/Standard/src/Arrays/Sorted.qs new file mode 100644 index 00000000000..e4a83b3e70c --- /dev/null +++ b/Standard/src/Arrays/Sorted.qs @@ -0,0 +1,134 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Arrays { + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Canon; + + /// # Summary + /// Given two sorted arrays, returns a single array containing the + /// elements of both in sorted order. Used internally by merge sort. + internal function Merged<'T>(comparison : (('T, 'T) -> Bool), left : 'T[], right : 'T[]) : 'T[] { + mutable result = new 'T[0]; + mutable l = left; + mutable r = right; + while ((not IsEmpty(l)) and (not IsEmpty(r))) { + if (comparison(Head(l), Head(r))) { + set result += [Head(l)]; + set l = Rest(l); + } else { + set result += [Head(r)]; + set r = Rest(r); + } + } + + // Note that at this point, either or both of l and r are empty, + // such that we can simply append both to our result to get the + // whole merged array. + return result + l + r; + } + + /// # Summary + /// Given an array, returns whether that array is sorted as defined by + /// a given comparison function. + /// + /// # Type Parameters + /// ## 'T + /// The type of each element of `array`. + /// + /// # Input + /// ## comparison + /// A function that compares two elements such that `a` is considered to + /// be less than or equal to `b` if `comparison(a, b)` is `true`. + /// ## array + /// The array to be checked. + /// + /// # Output + /// `true` if and only if for each pair of elements `a` and `b` of + /// `array` occuring in that order, `comparison(a, b)` is `true`. + /// + /// # Remarks + /// The function `comparison` is assumed to be transitive, such that + /// if `comparison(a, b)` and `comparison(b, c)`, then `comparison(a, c)` + /// is assumed. If this property does not hold, then the output of this + /// function may be incorrect. + function IsSorted<'T>(comparison : (('T, 'T) -> Bool), array : 'T[]) : Bool { + return All( + comparison, + Zip(Most(array), Rest(array)) + ); + } + + /// # Summary + /// Given an array, returns the elements of that array sorted by a given + /// comparison function. + /// + /// # Type Parameters + /// ## 'T + /// The type of each element of `array`. + /// + /// # Input + /// ## comparison + /// A function that compares two elements such that `a` is considered to + /// be less than or equal to `b` if `comparison(a, b)` is `true`. + /// ## array + /// The array to be sorted. + /// + /// # Ouput + /// An array containing the same elements as `array`, such that for all + /// elements `a` occuring earlier than elements `b`, `comparison(a, b)` + /// is `true`. + /// + /// # Example + /// The following snippet sorts an array of integers to occur in ascending + /// order: + /// ```Q# + /// let sortedArray = Sorted(LessThanOrEqualI, [3, 17, 11, -201, -11]); + /// ``` + /// + /// # Remarks + /// The function `comparison` is assumed to be transitive, such that + /// if `comparison(a, b)` and `comparison(b, c)`, then `comparison(a, c)` + /// is assumed. If this property does not hold, then the output of this + /// function may be incorrect. + /// + /// As this is a function, the results are completely determinstic, even + /// when two elements are considered equal under `comparison`; + /// that is, when `comparison(a, b)` and `comparison(b, a)` are both `true`. + /// In particular, the sort performed by this function is guaranteed to be + /// stable, so that if two elements `a` and `b` occur in that order within + /// `array` and are considered equal under `comparison`, then `a` will also + /// appear before `b` in the output. + /// + /// For example: + /// ```Q# + /// function LastDigitLessThanOrEqual(left : Int, right : Int) : Bool { + /// return LessThanOrEqualI( + /// left % 10, right % 10 + /// ); + /// } + /// + /// function SortedByLastDigit() : Int[] { + /// return Sorted(LastDigitLessThanOrEqual, [3, 37, 11, 17]); + /// } + /// // returns [11, 3, 37, 17]. + /// ``` + function Sorted<'T>(comparison : (('T, 'T) -> Bool), array : 'T[]) : 'T[] { + if (Length(array) <= 1) { + return array; + } else { + let idxPivot = Length(array) / 2; + let left = array[...idxPivot - 1]; + let right = array[idxPivot...]; + + // Sort each sublist, then merge them back into a single combined + // list and return. + return Merged<'T>( + comparison, + Sorted(comparison, left), + Sorted(comparison, right) + ); + } + } + +} diff --git a/Standard/src/Arrays/Zip.qs b/Standard/src/Arrays/Zip.qs index f698d9ffea9..d02e27fb64d 100644 --- a/Standard/src/Arrays/Zip.qs +++ b/Standard/src/Arrays/Zip.qs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. namespace Microsoft.Quantum.Arrays { diff --git a/Standard/src/Logical/Comparisons.qs b/Standard/src/Logical/Comparisons.qs new file mode 100644 index 00000000000..144fc9affd9 --- /dev/null +++ b/Standard/src/Logical/Comparisons.qs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Logical { + open Microsoft.Quantum.Arrays; + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Canon; + + /// # Summary + /// Given a comparison function, returns a new function that + /// lexographically compares two arrays. + /// + /// # Type Parameters + /// ## 'T + /// The type of the elements of the arrays being compared. + /// + /// # Input + /// ## elementComparison + /// A function that compares two elements `x` and `y` and returns if + /// `x` is less than or equal to `y`. + /// + /// # Output + /// A function that compares two arrays `xs` and `ys` and returns if + /// `xs` occurs before or equal to `ys` in lexographical ordering. + /// + /// # Remarks + /// The lexographic comparison between two arrays `xs` and `ys` is defined + /// by the following procedure. We say that two elements `x` and `y` + /// are equivalent if `elementComparison(x, y)` and `elementComparison(y, x)` + /// are both true. + /// + /// - Both arrays are compared element-by-element until the first pair of + /// elements that are not equivalent. The array containing the element + /// that occurs first according to `elementComparison` is said to occur + /// first in lexographical ordering. + /// - If no inequivalent elements are found, and one array is longer than + /// the other, the shorter array is said to occur first. + /// + /// # Examples + /// ```Q# + /// let arrayComparison = LexographicComparison(LessThanOrEqualD); + /// let data = [ + /// [1.1, 2.2, 3.3], + /// [1.1, 2.2], + /// [0.2, 2.2], + /// [1.1, 2.7] + /// ]; + /// let sorted = Sorted(arrayComparison, data); + /// // sorted: + /// // [ + /// // [0.2, 2.2], + /// // [1.1, 2.2], + /// // [1.1, 2.2, 3.3], + /// // [1.1, 2.7] + /// // ]; + /// ``` + /// + /// # See Also + /// - Microsoft.Quantum.Arrays.Sorted + function LexographicComparison<'T>(elementComparison : (('T, 'T) -> Bool)) : (('T[], 'T[]) -> Bool) { + return LessThanLexographic(elementComparison, _, _); + } + + /// # Summary + /// Used to implement `LexographicComparison`. + internal function LessThanLexographic<'T>(comparison : (('T, 'T) -> Bool), left : 'T[], right : 'T[]) : Bool { + for ((l, r) in Zip(left, right)) { + let lessThanOrEqual = comparison(l, r); + let greaterThanOrEqual = comparison(r, l); + let equal = lessThanOrEqual and greaterThanOrEqual; + if (lessThanOrEqual and not equal) { + return true; + } elif (greaterThanOrEqual and not equal) { + return false; + } + } + + // At this point, all items in the common prefix of both arrays + // are equal to each other under comparison (l ≤ r and r ≤ l). + // Thus, if left is shorter than or equal to right, then left occurs + // at or before right in lexographical ordering. + return Length(left) <= Length(right); + } + +} diff --git a/Standard/tests/Arrays/SortedTests.qs b/Standard/tests/Arrays/SortedTests.qs new file mode 100644 index 00000000000..a0dbc5fdd37 --- /dev/null +++ b/Standard/tests/Arrays/SortedTests.qs @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Arrays { + open Microsoft.Quantum.Random; + open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Logical; + + @Test("QuantumSimulator") + function IntsAreSorted() : Unit { + Fact(IsSorted(LessThanOrEqualI, [1, 10, 100]), "[1, 10, 100] was marked as unsorted."); + } + + @Test("QuantumSimulator") + function DoublesAreSorted() : Unit { + Fact(IsSorted(LessThanOrEqualD, [1.0, 10.1, 100.2]), "[1.0, 10.1, 100.2] was marked as unsorted."); + } + + @Test("QuantumSimulator") + function IntsAreNotSorted() : Unit { + Contradiction(IsSorted(LessThanOrEqualI, [100, 10, 3]), "[100, 10, 3] was marked as sorted."); + } + + @Test("QuantumSimulator") + function SortedIntsAreSorted() : Unit { + Fact(IsSorted(LessThanOrEqualI, + Sorted(LessThanOrEqualI, [100, 10, 3])), + "Sorted(<=, [100, 10, 3]) was marked as unsorted." + ); + } + + @Test("QuantumSimulator") + function SortedDoublesAreSorted() : Unit { + Fact(IsSorted(LessThanOrEqualD, + Sorted(LessThanOrEqualD, [100.0, 10.1, 3.14])), + "Sorted(<=, [100.0, 10.1, 3.14]) was marked as unsorted." + ); + } + + @Test("QuantumSimulator") + operation CheckRandomArraysAreSortedWhenSorted() : Unit { + let nItems = 100; + let nTrials = 10; + let maxItem = 1000; + for (_ in 0..nTrials - 1) { + let data = DrawMany((DiscreteUniformDistribution(0, maxItem))::Sample, nItems, ()); + Fact(IsSorted(LessThanOrEqualI, Sorted(LessThanOrEqualI, data)), $"{data} was not sorted after running Sorted."); + } + } + + @Test("QuantumSimulator") + function LexographicSortIsCorrect() : Unit { + let arrayComparison = LexographicComparison(LessThanOrEqualI); + let data = [ + [1, 2, 3], + [1, 2], + [0, 2], + [1, 3] + ]; + let sorted = Sorted(arrayComparison, data); + + AllEqualityFactI( + sorted[0], [0, 2], "0th item was not correct." + ); + AllEqualityFactI( + sorted[1], [1, 2], "1st item was not correct." + ); + AllEqualityFactI( + sorted[2], [1, 2, 3], "2nd item was not correct." + ); + AllEqualityFactI( + sorted[3], [1, 3], "3rd item was not correct." + ); + } + +} diff --git a/Standard/tests/Logical/ComparisonTests.qs b/Standard/tests/Logical/ComparisonTests.qs new file mode 100644 index 00000000000..9afd776c33d --- /dev/null +++ b/Standard/tests/Logical/ComparisonTests.qs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Tests { + open Microsoft.Quantum.Logical; + open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Math; + open Microsoft.Quantum.Arrays; + + function LexographicComparisonIsCorrect() : Unit { + let lexographicComparison = LexographicComparison(LessThanOrEqualD); + Fact( + lexographicComparison( + [1.1, 2.2], [1.1, 2.2, 3.3] + ), + "Shorter array should have occured first." + ); + Fact( + lexographicComparison( + [0.7, 2.2], [1.1, 2.2] + ), + "Array with smaller first element should have occured first." + ); + Fact( + lexographicComparison( + [1.1, 2.2], [1.1, 2.2] + ), + "Identical arrays should be marked as less than or equal." + ); + Contradiction( + lexographicComparison( + [1.1, 2.7], [1.1, 2.2, 3.3] + ), + "Array with larger second element should have occured second." + ); + } + +}