diff --git a/core-test/src/test/scala/org/bitcoins/core/util/SortedVecTest.scala b/core-test/src/test/scala/org/bitcoins/core/util/SortedVecTest.scala new file mode 100644 index 0000000000..9cf1e9db3e --- /dev/null +++ b/core-test/src/test/scala/org/bitcoins/core/util/SortedVecTest.scala @@ -0,0 +1,102 @@ +package org.bitcoins.core.util + +import org.bitcoins.core.util.sorted.SortedVec +import org.bitcoins.crypto.{ECPublicKey, NetworkElement, SchnorrNonce} +import org.bitcoins.testkitcore.util.BitcoinSUnitTest + +class SortedVecTest extends BitcoinSUnitTest { + behavior of "SortedVec" + + implicit val networkElementOrd: Ordering[NetworkElement] = { + case (x: NetworkElement, y: NetworkElement) => + x.bytes.compare(y.bytes) + } + + it should "sort correctly on construction with normal orderings" in { + assert(SortedVec.sort(Vector(1, 2, 3)) == SortedVec(Vector(1, 2, 3))) + assert(SortedVec.sort(Vector(3, 2, 1)) == SortedVec(Vector(1, 2, 3))) + assert( + SortedVec.sort(Vector(3.0, 2.0, 1.123)) == SortedVec( + Vector(1.123, 2.0, 3.0))) + assert( + SortedVec.sort(Vector('c', 'b', 'a')) == SortedVec(Vector('a', 'b', 'c'))) + assert( + SortedVec.sort(Vector("cab", "ceb", "abc")) == SortedVec( + Vector("abc", "cab", "ceb"))) + assert( + SortedVec.sort[SchnorrNonce, NetworkElement](Vector( + SchnorrNonce( + "c4b89873c8753de3f0a9e94c4a6190badaa983513a6624a3469eb4577904bfea"), + SchnorrNonce( + "92efe81609c773d97da2b084eb691f48ef5e926acc6eecd629f80fb1184711bc") + )) == SortedVec[SchnorrNonce, NetworkElement](Vector( + SchnorrNonce( + "92efe81609c773d97da2b084eb691f48ef5e926acc6eecd629f80fb1184711bc"), + SchnorrNonce( + "c4b89873c8753de3f0a9e94c4a6190badaa983513a6624a3469eb4577904bfea") + ))) + } + + it should "fail to construct with unsorted vector under normal orderings" in { + assertThrows[IllegalArgumentException](SortedVec(Vector(3, 2, 1))) + assertThrows[IllegalArgumentException](SortedVec(Vector(3.0, 2.0, 1.123))) + assertThrows[IllegalArgumentException](SortedVec(Vector('c', 'b', 'a'))) + assertThrows[IllegalArgumentException]( + SortedVec(Vector("cab", "ceb", "abc"))) + assertThrows[IllegalArgumentException]( + SortedVec[SchnorrNonce, NetworkElement](Vector( + SchnorrNonce( + "c4b89873c8753de3f0a9e94c4a6190badaa983513a6624a3469eb4577904bfea"), + SchnorrNonce( + "92efe81609c773d97da2b084eb691f48ef5e926acc6eecd629f80fb1184711bc") + ))) + } + + it should "sort correctly on construction with custom orderings" in { + implicit val parityOrd: Ordering[Int] = { case (x: Int, y: Int) => + (math.abs(x) % 2) - (math.abs(y) % 2) + } + + assert(SortedVec.sort(Vector(1, 2, 3, 4)) == SortedVec(Vector(2, 4, 1, 3))) + assert(SortedVec.sort(Vector(2, 4, 1, 3)) == SortedVec(Vector(2, 4, 1, 3))) + + implicit val remainderOrd: Ordering[Double] = { + case (x: Double, y: Double) => + val diff = (x - x.floor) - (y - y.floor) + if (diff > 0) 1 else if (diff == 0) 0 else -1 + } + + assert( + SortedVec.sort(Vector(0.95, 123.123, 5.55)) == SortedVec( + Vector(123.123, 5.55, 0.95))) + } + + it should "fail to construct with unsorted vector under custom orderings" in { + implicit val parityOrd: Ordering[Int] = { case (x: Int, y: Int) => + (math.abs(x) % 2) - (math.abs(y) % 2) + } + + assertThrows[IllegalArgumentException](SortedVec(Vector(1, 2, 3, 4))) + + implicit val remainderOrd: Ordering[Double] = { + case (x: Double, y: Double) => + val diff = (x - x.floor) - (y - y.floor) + if (diff > 0) 1 else if (diff == 0) 0 else -1 + } + + assertThrows[IllegalArgumentException]( + SortedVec(Vector(0.95, 5.55, 123.123))) + } + + it should "correctly handle ordered list" in { + val nonce1 = ECPublicKey.freshPublicKey.schnorrNonce + val nonce2 = ECPublicKey.freshPublicKey.schnorrNonce + val nonce3 = ECPublicKey.freshPublicKey.schnorrNonce + val nonces = Vector(nonce1, nonce2, nonce3) + val wrongOrder = Vector(nonce2, nonce3, nonce1) + + val _ = SortedVec(nonces)(SortedVec.forOrdered(nonces)) + assertThrows[IllegalArgumentException]( + SortedVec(wrongOrder)(SortedVec.forOrdered(nonces))) + } +} diff --git a/core/src/main/scala/org/bitcoins/core/util/sorted/OrderedNonces.scala b/core/src/main/scala/org/bitcoins/core/util/sorted/OrderedNonces.scala new file mode 100644 index 0000000000..30a1b2b0f2 --- /dev/null +++ b/core/src/main/scala/org/bitcoins/core/util/sorted/OrderedNonces.scala @@ -0,0 +1,8 @@ +package org.bitcoins.core.util.sorted + +import org.bitcoins.crypto.SchnorrNonce + +/** Represents an ordered set of SchnorrNonces */ +case class OrderedNonces(vec: Vector[SchnorrNonce]) + extends SortedVec[SchnorrNonce, SchnorrNonce](vec, + SortedVec.forOrdered(vec)) diff --git a/core/src/main/scala/org/bitcoins/core/util/sorted/SortedVec.scala b/core/src/main/scala/org/bitcoins/core/util/sorted/SortedVec.scala new file mode 100644 index 0000000000..ccd306fced --- /dev/null +++ b/core/src/main/scala/org/bitcoins/core/util/sorted/SortedVec.scala @@ -0,0 +1,40 @@ +package org.bitcoins.core.util.sorted + +import org.bitcoins.core.util.SeqWrapper + +/** Wraps a sorted Vector[T] and its Ordering (which may be for a supertype). + * + * For example SortedVec[SchnorrNonce, NetworkElement] would be a + * Vector[SchnorrNonce] sorted as NetworkElements. + */ +abstract class SortedVec[T, B >: T]( + override val wrapped: Vector[T], + ord: Ordering[B]) + extends SeqWrapper[T] { + require( + wrapped.init.zip(wrapped.tail).forall { case (x, y) => ord.lteq(x, y) }, + s"Vector must be sorted. $wrapped") +} + +object SortedVec { + + /** For use with ordered lists so that changing order will not be allowed. + */ + def forOrdered[T](vec: Vector[T]): Ordering[T] = { case (x, y) => + vec.indexOf(x) - vec.indexOf(y) + } + + private case class SortedVecImpl[T, B >: T](vec: Vector[T])(implicit + val ord: Ordering[B]) + extends SortedVec[T, B](vec, ord) + + def sort[T, B >: T](vec: Vector[T])(implicit + ord: Ordering[B]): SortedVec[T, B] = { + SortedVecImpl(vec.sorted[B]) + } + + def apply[T, B >: T](vec: Vector[T])(implicit + ord: Ordering[B]): SortedVec[T, B] = { + SortedVecImpl(vec) + } +}