From a09ae2b2c53dce9c8606449cae4cf35c35cbc66d Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Fri, 18 Sep 2020 15:33:57 -0700 Subject: [PATCH 1/4] Add new Sorted fn for merge sort. --- Standard/src/Arrays/Sorted.qs | 134 +++++++++++++++++++++++++++ Standard/src/Arrays/Zip.qs | 2 +- Standard/tests/Arrays/SortedTests.qs | 38 ++++++++ 3 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 Standard/src/Arrays/Sorted.qs create mode 100644 Standard/tests/Arrays/SortedTests.qs diff --git a/Standard/src/Arrays/Sorted.qs b/Standard/src/Arrays/Sorted.qs new file mode 100644 index 00000000000..590a4744e65 --- /dev/null +++ b/Standard/src/Arrays/Sorted.qs @@ -0,0 +1,134 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// 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# + /// 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 caa785a2ba5..6a0b899d1d3 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/tests/Arrays/SortedTests.qs b/Standard/tests/Arrays/SortedTests.qs new file mode 100644 index 00000000000..6d9d0df7586 --- /dev/null +++ b/Standard/tests/Arrays/SortedTests.qs @@ -0,0 +1,38 @@ +// 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 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") + 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."); + } + } + +} From 44052383aebdef9bc47cd58c0e991fb4e4966d08 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Fri, 18 Sep 2020 15:40:58 -0700 Subject: [PATCH 2/4] =?UTF-8?q?Fix=20=E2=93=92=20header.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Standard/src/Arrays/Sorted.qs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Standard/src/Arrays/Sorted.qs b/Standard/src/Arrays/Sorted.qs index 590a4744e65..5c852d0d849 100644 --- a/Standard/src/Arrays/Sorted.qs +++ b/Standard/src/Arrays/Sorted.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 { From 358eacd95bf5893cb0950bafc664256082d91d07 Mon Sep 17 00:00:00 2001 From: Chris Granade Date: Sun, 20 Sep 2020 12:06:27 -0700 Subject: [PATCH 3/4] Update Standard/src/Arrays/Sorted.qs Co-authored-by: Mathias Soeken --- Standard/src/Arrays/Sorted.qs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Standard/src/Arrays/Sorted.qs b/Standard/src/Arrays/Sorted.qs index 5c852d0d849..e4a83b3e70c 100644 --- a/Standard/src/Arrays/Sorted.qs +++ b/Standard/src/Arrays/Sorted.qs @@ -83,7 +83,7 @@ namespace Microsoft.Quantum.Arrays { /// The following snippet sorts an array of integers to occur in ascending /// order: /// ```Q# - /// Sorted(LessThanOrEqualI, [3, 17, 11, -201, -11]); + /// let sortedArray = Sorted(LessThanOrEqualI, [3, 17, 11, -201, -11]); /// ``` /// /// # Remarks From c7eaf1b80d7e444b14daf8ef64de4a1a8a4baae3 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Mon, 21 Sep 2020 09:53:29 -0700 Subject: [PATCH 4/4] Added lexographic comparison, new tests. --- Standard/src/Logical/Comparisons.qs | 85 +++++++++++++++++++++++ Standard/tests/Arrays/SortedTests.qs | 38 ++++++++++ Standard/tests/Logical/ComparisonTests.qs | 38 ++++++++++ 3 files changed, 161 insertions(+) create mode 100644 Standard/src/Logical/Comparisons.qs create mode 100644 Standard/tests/Logical/ComparisonTests.qs 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 index 6d9d0df7586..a0dbc5fdd37 100644 --- a/Standard/tests/Arrays/SortedTests.qs +++ b/Standard/tests/Arrays/SortedTests.qs @@ -11,6 +11,11 @@ namespace Microsoft.Quantum.Arrays { 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."); @@ -24,6 +29,14 @@ namespace Microsoft.Quantum.Arrays { ); } + @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; @@ -35,4 +48,29 @@ namespace Microsoft.Quantum.Arrays { } } + @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." + ); + } + +}