diff --git a/build.sbt b/build.sbt index abbc298..6375a5e 100644 --- a/build.sbt +++ b/build.sbt @@ -8,6 +8,7 @@ lazy val commonSettings = Seq( organizationHomepage := Some(url("https://economicsl.github.io/")), libraryDependencies ++= Seq( "org.scalactic" %% "scalactic" % "3.0.5", + "org.typelevel" %% "cats-core" % "1.1.0", "com.typesafe.akka" %% "akka-actor" % "2.5.6", "org.economicsl" %% "esl-core" % "0.1.0-SNAPSHOT" ), @@ -22,7 +23,8 @@ lazy val commonSettings = Seq( "-language:reflectiveCalls", // needed in order to enable structural (or duck) typing "-Xlint", "-Ywarn-unused-import", - "-Ywarn-dead-code" + "-Ywarn-dead-code", + "-Ypartial-unification" ) ) diff --git a/src/functional/scala/org/economicsl/matching/HouseListing.scala b/src/functional/scala/org/economicsl/matching/HouseListing.scala index fd2f1c5..4c79af2 100644 --- a/src/functional/scala/org/economicsl/matching/HouseListing.scala +++ b/src/functional/scala/org/economicsl/matching/HouseListing.scala @@ -20,7 +20,7 @@ import java.util.UUID import org.economicsl.core.Price -case class HouseListing(uuid: UUID, issuer: Long, price: Price, house: House) +case class HouseListing(uuid: UUID, issuer: UUID, price: Price, house: House) extends Predicate[HousePurchaseOffer] with Preferences[HousePurchaseOffer] { /** Boolean function used to determine whether some `HousePurchaseOffer` is acceptable. diff --git a/src/functional/scala/org/economicsl/matching/HouseMarketSpecification.scala b/src/functional/scala/org/economicsl/matching/HouseMarketSpecification.scala index f5d418d..83c4b48 100644 --- a/src/functional/scala/org/economicsl/matching/HouseMarketSpecification.scala +++ b/src/functional/scala/org/economicsl/matching/HouseMarketSpecification.scala @@ -15,64 +15,64 @@ limitations under the License. */ package org.economicsl.matching +import cats.data.State import org.economicsl.core.Price import org.economicsl.matching.onetoone.DeferredAcceptanceAlgorithm import org.scalacheck.{Gen, Prop, Properties} -import scala.collection.immutable.HashSet - object HouseMarketSpecification extends Properties("housing-market") { // this generator should exist in esl-core! - val priceGen: Gen[Price] = { + def price: Gen[Price] = { for { - value <- Gen.choose(100000, 1000000) + value <- Gen.posNum[Long] } yield Price(value) } - val houseGen: Gen[House] = { + val house: Gen[House] = { for { - id <- Gen.uuid - quality <- Gen.choose(1, 100) - } yield House(id, quality) + uuid <- Gen.uuid + quality <- Gen.posNum[Int] + } yield House(uuid, quality) } - val houseListingGen: Gen[HouseListing] = { + val houseListing: Gen[HouseListing] = { for { - id <- Gen.uuid - issuer <- Gen.posNum[Long] - price <- priceGen - house <- houseGen - } yield HouseListing(id, issuer, price, house) + uuid <- Gen.uuid + issuer <- Gen.uuid + price <- price + house <- house + } yield HouseListing(uuid, issuer, price, house) } - val housePurchaseOfferGen: Gen[HousePurchaseOffer] = { + val housePurchaseOffer: Gen[HousePurchaseOffer] = { for { - id <- Gen.uuid - issuer <- Gen.posNum[Long] - price <- priceGen - } yield HousePurchaseOffer(id, issuer, price) + uuid <- Gen.uuid + issuer <- Gen.uuid + price <- price + } yield HousePurchaseOffer(uuid, issuer, price) } - val housePurchaseOffers: Gen[HashSet[HousePurchaseOffer]] = Gen.sized { - n => Gen.containerOfN[HashSet, HousePurchaseOffer](n, housePurchaseOfferGen) - } + def unMatched: Gen[(Set[HousePurchaseOffer], Set[HouseListing])] = Gen.sized { + size => for { + offers <- Gen.containerOfN[Set, HousePurchaseOffer](size, housePurchaseOffer) + listing <- Gen.containerOfN[Set, HouseListing](size, houseListing) + } yield (offers, listing) - val houseListings: Gen[HashSet[HouseListing]] = Gen.sized { - n => Gen.containerOfN[HashSet, HouseListing](n, houseListingGen) } - property("matching should be incentive-compatible") = Prop.forAll(housePurchaseOffers, houseListings) { - case (offers, listings) => - val ((_, _), matching) = (new DeferredAcceptanceAlgorithm[HousePurchaseOffer, HouseListing])(offers, listings) - matching.matches.forall{ case (offer, listing) => offer.price >= listing.price } + property("matching should be incentive-compatible") = Prop.forAll(unMatched) { unmatched => + val result = State(new DeferredAcceptanceAlgorithm[HousePurchaseOffer, HouseListing]).run(unmatched) + val ((_, _), matching) = result.value + matching.matches.forall{ case (offer, listing) => offer.price >= listing.price } } - property("matching should be stable") = Prop.forAll(housePurchaseOffers, houseListings) { - case (offers, listings) => - val ((_, _), matching) = (new DeferredAcceptanceAlgorithm[HousePurchaseOffer, HouseListing])(offers, listings) - offers.forall(o => listings.forall(l => !matching.isBlockedBy(l -> o))) + property("matching should be stable") = Prop.forAll(unMatched) { unmatched => + val result = State(new DeferredAcceptanceAlgorithm[HousePurchaseOffer, HouseListing]).run(unmatched) + val ((_, _), matching) = result.value + val (offers, listings) = unmatched + offers.forall(o => listings.forall(l => !matching.isBlockedBy(l -> o))) } } diff --git a/src/functional/scala/org/economicsl/matching/HousePurchaseOffer.scala b/src/functional/scala/org/economicsl/matching/HousePurchaseOffer.scala index 1810c35..e927b02 100644 --- a/src/functional/scala/org/economicsl/matching/HousePurchaseOffer.scala +++ b/src/functional/scala/org/economicsl/matching/HousePurchaseOffer.scala @@ -20,7 +20,7 @@ import java.util.UUID import org.economicsl.core.Price -case class HousePurchaseOffer(uuid: UUID, issuer: Long, price: Price) +case class HousePurchaseOffer(uuid: UUID, issuer: UUID, price: Price) extends Proposer with Predicate[HouseListing] with Preferences[HouseListing] { /** Boolean function used to determine whether some `HouseListing` is acceptable. diff --git a/src/main/scala/org/economicsl/matching/manytoone/DeferredAcceptanceAlgorithm.scala b/src/main/scala/org/economicsl/matching/manytoone/DeferredAcceptanceAlgorithm.scala index f8d814d..f238197 100644 --- a/src/main/scala/org/economicsl/matching/manytoone/DeferredAcceptanceAlgorithm.scala +++ b/src/main/scala/org/economicsl/matching/manytoone/DeferredAcceptanceAlgorithm.scala @@ -27,67 +27,68 @@ import scala.collection.immutable.TreeSet */ class DeferredAcceptanceAlgorithm[M <: Proposer with Predicate[W] with Preferences[W], W <: Predicate[M] with Preferences[M] with Quota] - extends ((Set[M], Set[W]) => ((Set[M], Set[W]), ManyToOneMatching[W, M])) { + extends (((Set[M], Set[W])) => ((Set[M], Set[W]), ManyToOneMatching[W, M])) { - /** Compute a stable matching between two sets. - * - * @param ms set of proposers to be matched. - * @param ws set of proposees to be matched. - * @return - * @note A matching will be called "stable" unless there is a pair each of which strictly prefers the other - * to its partner in the matching. This algorithm produces a weakly stable matching in `O(n^2)` time where `n` - * is the size of the inputs sets. - */ - def apply(ms: Set[M], ws: Set[W]): ((Set[M], Set[W]), ManyToOneMatching[W, M]) = { + /** Compute a stable matching between two sets. + * + * @param unmatched + * @return + * @note A matching will be called "stable" unless there is a pair each of which strictly prefers the other + * to its partner in the matching. This algorithm produces a weakly stable matching in `O(n^2)` time where `n` + * is the size of the inputs sets. + */ + def apply(unmatched: (Set[M], Set[W])): ((Set[M], Set[W]), ManyToOneMatching[W, M]) = { + val (ms, ws) = unmatched - @annotation.tailrec - def accumulate(unMatched: Set[M], toBeMatched: Set[M], matches: Map[W, TreeSet[M]], rejected: Map[M, Set[W]]): (Set[M], Set[W], Map[W, TreeSet[M]]) = { - toBeMatched.headOption match { - case Some(toBeMatchedM) => - val previouslyRejected = rejected.getOrElse(toBeMatchedM, Set.empty) - val acceptableWs = ws.diff(previouslyRejected) - if (acceptableWs.isEmpty) { - accumulate(unMatched + toBeMatchedM, toBeMatched - toBeMatchedM, matches, rejected) - } else { - val mostPreferredW = acceptableWs.max(toBeMatchedM.ordering) - matches.get(mostPreferredW) match { - case Some(matchedMs) if mostPreferredW.isAcceptable(toBeMatchedM) => - matchedMs.headOption match { - case Some(_) if matchedMs.size < mostPreferredW.quota => - val updatedToBeMatchedMs = toBeMatched - toBeMatchedM - val updatedMatches = matches.updated(mostPreferredW, matchedMs + toBeMatchedM) - accumulate(unMatched, updatedToBeMatchedMs, updatedMatches, rejected) - case Some(leastPreferredMatchedM) if mostPreferredW.ordering.lt(leastPreferredMatchedM, toBeMatchedM) => - val updatedToBeMatchedMs = toBeMatched - toBeMatchedM + leastPreferredMatchedM - val updatedMatches = matches.updated(mostPreferredW, matchedMs - leastPreferredMatchedM + toBeMatchedM) - val updatedRejected = rejected.updated(leastPreferredMatchedM, rejected.getOrElse(leastPreferredMatchedM, Set.empty) + mostPreferredW) - accumulate(unMatched, updatedToBeMatchedMs, updatedMatches, updatedRejected) - case Some(leastPreferredMatchedM) if mostPreferredW.ordering.gteq(leastPreferredMatchedM, toBeMatchedM) => - val updatedRejected = rejected.updated(toBeMatchedM, previouslyRejected + mostPreferredW) - accumulate(unMatched, toBeMatched, matches, updatedRejected) - case None => - val updatedToBeMatchedMs = toBeMatched - toBeMatchedM - val updatedMatches = matches.updated(mostPreferredW, matchedMs + toBeMatchedM) - accumulate(unMatched, updatedToBeMatchedMs, updatedMatches, rejected) - } - case Some(_) if mostPreferredW.isNotAcceptable(toBeMatchedM) => - val updatedRejected = rejected.updated(toBeMatchedM, previouslyRejected + mostPreferredW) - accumulate(unMatched, toBeMatched, matches, updatedRejected) - case None if mostPreferredW.isAcceptable(toBeMatchedM) => - val updatedMatches = matches + (mostPreferredW -> TreeSet(toBeMatchedM)(mostPreferredW.ordering)) - accumulate(unMatched, toBeMatched - toBeMatchedM, updatedMatches, rejected) - case None if mostPreferredW.isNotAcceptable(toBeMatchedM) => - val updatedRejected = rejected.updated(toBeMatchedM, previouslyRejected + mostPreferredW) - accumulate(unMatched, toBeMatched, matches, updatedRejected) - } + @annotation.tailrec + def accumulate(unMatched: Set[M], toBeMatched: Set[M], matches: Map[W, TreeSet[M]], rejected: Map[M, Set[W]]): (Set[M], Set[W], Map[W, TreeSet[M]]) = { + toBeMatched.headOption match { + case Some(toBeMatchedM) => + val previouslyRejected = rejected.getOrElse(toBeMatchedM, Set.empty) + val acceptableWs = ws.diff(previouslyRejected) + if (acceptableWs.isEmpty) { + accumulate(unMatched + toBeMatchedM, toBeMatched - toBeMatchedM, matches, rejected) + } else { + val mostPreferredW = acceptableWs.max(toBeMatchedM.ordering) + matches.get(mostPreferredW) match { + case Some(matchedMs) if mostPreferredW.isAcceptable(toBeMatchedM) => + matchedMs.headOption match { + case Some(_) if matchedMs.size < mostPreferredW.quota => + val updatedToBeMatchedMs = toBeMatched - toBeMatchedM + val updatedMatches = matches.updated(mostPreferredW, matchedMs + toBeMatchedM) + accumulate(unMatched, updatedToBeMatchedMs, updatedMatches, rejected) + case Some(leastPreferredMatchedM) if mostPreferredW.ordering.lt(leastPreferredMatchedM, toBeMatchedM) => + val updatedToBeMatchedMs = toBeMatched - toBeMatchedM + leastPreferredMatchedM + val updatedMatches = matches.updated(mostPreferredW, matchedMs - leastPreferredMatchedM + toBeMatchedM) + val updatedRejected = rejected.updated(leastPreferredMatchedM, rejected.getOrElse(leastPreferredMatchedM, Set.empty) + mostPreferredW) + accumulate(unMatched, updatedToBeMatchedMs, updatedMatches, updatedRejected) + case Some(leastPreferredMatchedM) if mostPreferredW.ordering.gteq(leastPreferredMatchedM, toBeMatchedM) => + val updatedRejected = rejected.updated(toBeMatchedM, previouslyRejected + mostPreferredW) + accumulate(unMatched, toBeMatched, matches, updatedRejected) + case None => + val updatedToBeMatchedMs = toBeMatched - toBeMatchedM + val updatedMatches = matches.updated(mostPreferredW, matchedMs + toBeMatchedM) + accumulate(unMatched, updatedToBeMatchedMs, updatedMatches, rejected) + } + case Some(_) if mostPreferredW.isNotAcceptable(toBeMatchedM) => + val updatedRejected = rejected.updated(toBeMatchedM, previouslyRejected + mostPreferredW) + accumulate(unMatched, toBeMatched, matches, updatedRejected) + case None if mostPreferredW.isAcceptable(toBeMatchedM) => + val updatedMatches = matches + (mostPreferredW -> TreeSet(toBeMatchedM)(mostPreferredW.ordering)) + accumulate(unMatched, toBeMatched - toBeMatchedM, updatedMatches, rejected) + case None if mostPreferredW.isNotAcceptable(toBeMatchedM) => + val updatedRejected = rejected.updated(toBeMatchedM, previouslyRejected + mostPreferredW) + accumulate(unMatched, toBeMatched, matches, updatedRejected) } - case None => - (unMatched, matches.keySet.diff(ws), matches) - } + } + case None => + (unMatched, matches.keySet.diff(ws), matches) } - val unacceptableWs = ms.foldLeft(Map.empty[M, Set[W]])((z, m) => z + (m -> ws.filter(m.isAcceptable))) - val (unMatchedMs, unMatchedWs, matches) = accumulate(Set.empty, ms, Map.empty, unacceptableWs) - ((unMatchedMs, unMatchedWs), ManyToOneMatching(matches)) } + val unacceptableWs = ms.foldLeft(Map.empty[M, Set[W]])((z, m) => z + (m -> ws.filter(m.isAcceptable))) + val (unMatchedMs, unMatchedWs, matches) = accumulate(Set.empty, ms, Map.empty, unacceptableWs) + ((unMatchedMs, unMatchedWs), ManyToOneMatching(matches)) } + +} diff --git a/src/main/scala/org/economicsl/matching/onetoone/DeferredAcceptanceAlgorithm.scala b/src/main/scala/org/economicsl/matching/onetoone/DeferredAcceptanceAlgorithm.scala index 9da8091..9cbfb9e 100644 --- a/src/main/scala/org/economicsl/matching/onetoone/DeferredAcceptanceAlgorithm.scala +++ b/src/main/scala/org/economicsl/matching/onetoone/DeferredAcceptanceAlgorithm.scala @@ -17,24 +17,25 @@ package org.economicsl.matching.onetoone import org.economicsl.matching.{Predicate, Preferences, Proposer} + /** Class implementing the deferred acceptance algorithm. * * @tparam M the type of proposer. * @tparam W the type of proposee. */ class DeferredAcceptanceAlgorithm[M <: Proposer with Predicate[W] with Preferences[W], W <: Predicate[M] with Preferences[M]] - extends ((Set[M], Set[W]) => ((Set[M], Set[W]), OneToOneMatching[W, M])) { + extends (((Set[M], Set[W])) => ((Set[M], Set[W]), OneToOneMatching[W, M])) { /** Compute a weakly stable matching between two sets. * - * @param ms set of proposers to be matched. - * @param ws set of proposees to be matched. + * @param unmatched * @return * @note A matching will be called "weakly stable" unless there is a pair each of which strictly prefers the other * to its partner in the matching. This algorithm produces a weakly stable matching in `O(n^2)` time where `n` * is the size of the inputs sets. */ - def apply(ms: Set[M], ws: Set[W]): ((Set[M], Set[W]), OneToOneMatching[W, M]) = { + def apply(unmatched: (Set[M], Set[W])): ((Set[M], Set[W]), OneToOneMatching[W, M]) = { + val (ms, ws) = unmatched @annotation.tailrec def accumulate(unMatchedMs: Set[M], toBeMatchedMs: Set[M], matches: Map[W, M], rejected: Map[M, Set[W]]): (Set[M], Set[W], Map[W, M]) = { diff --git a/src/main/scala/org/economicsl/matching/onetoone/StableMarriageAlgorithm.scala b/src/main/scala/org/economicsl/matching/onetoone/StableMarriageAlgorithm.scala index 80f300e..8bcb380 100644 --- a/src/main/scala/org/economicsl/matching/onetoone/StableMarriageAlgorithm.scala +++ b/src/main/scala/org/economicsl/matching/onetoone/StableMarriageAlgorithm.scala @@ -38,15 +38,15 @@ import scala.util.{Failure, Success, Try} * [[http://www.eecs.harvard.edu/cs286r/courses/fall09/papers/galeshapley.pdf ''Gale and Shapley (1962)'']]. */ class StableMarriageAlgorithm[M <: Proposer with Preferences[W], W <: Preferences[M]] - extends ((Set[M], Set[W]) => Try[((Set[M], Set[W]), OneToOneMatching[W, M])]) { + extends (((Set[M], Set[W])) => ((Set[M], Set[W]), Try[OneToOneMatching[W, M]])) { /** Compute a stable matching between two sets of equal size. * - * @param ms set of proposers to be matched. - * @param ws set of proposees to be matched. + * @param unmatched * @return a stable matching between proposees (`ws`) and proposers (`ms`). */ - def apply(ms: Set[M], ws: Set[W]): Try[((Set[M], Set[W]), OneToOneMatching[W, M])] = { + def apply(unmatched: (Set[M], Set[W])): ((Set[M], Set[W]), Try[OneToOneMatching[W, M]]) = { + val (ms, ws) = unmatched @annotation.tailrec def accumulate(unMatchedMs: Set[M], matches: Map[W, M], rejected: Map[M, Set[W]]): (Set[M], Set[W], Map[W, M]) = { @@ -74,10 +74,10 @@ class StableMarriageAlgorithm[M <: Proposer with Preferences[W], W <: Preference if (ms.size == ws.size) { val (unMatchedMs, unMatchedWs, matches) = accumulate(ms, Map.empty, Map.empty) - Success(((unMatchedMs, unMatchedWs), OneToOneMatching(matches))) + ((unMatchedMs, unMatchedWs), Success(OneToOneMatching(matches))) } else { - Failure(new IllegalArgumentException(s"The size of ms is ${ms.size} which does not equal the size of ws which is ${ws.size}!")) + ((ms, ws), Failure(new IllegalArgumentException(s"The size of ms is ${ms.size} which does not equal the size of ws which is ${ws.size}!"))) } } diff --git a/src/performance/scala/org/economicsl/matching/DeferredAcceptanceMicroBenchmark.scala b/src/performance/scala/org/economicsl/matching/DeferredAcceptanceMicroBenchmark.scala index be05c03..da0f674 100644 --- a/src/performance/scala/org/economicsl/matching/DeferredAcceptanceMicroBenchmark.scala +++ b/src/performance/scala/org/economicsl/matching/DeferredAcceptanceMicroBenchmark.scala @@ -19,11 +19,11 @@ object DeferredAcceptanceMicroBenchmark extends Bench.OnlineRegressionReport { } def randomHouseListing(): HouseListing = { - HouseListing(UUID.randomUUID(), Random.nextLong(), Price(Random.nextLong()), randomHouse()) + HouseListing(UUID.randomUUID(), UUID.randomUUID(), Price(Random.nextLong()), randomHouse()) } def randomHousePurchaseOffer(): HousePurchaseOffer = { - HousePurchaseOffer(UUID.randomUUID(), Random.nextLong(), Price(Random.nextLong())) + HousePurchaseOffer(UUID.randomUUID(), UUID.randomUUID(), Price(Random.nextLong())) } def randomHousePurchaseOffers(size: Int): HashSet[HousePurchaseOffer] = { @@ -66,8 +66,8 @@ object DeferredAcceptanceMicroBenchmark extends Bench.OnlineRegressionReport { // } measure method "weaklyStableMatching" in { - using(unMatchedParticipants) in { case (buyers, sellers) => - (new StableMarriageAlgorithm[HousePurchaseOffer, HouseListing]())(buyers, sellers) + using(unMatchedParticipants) in { unmatched => + (new StableMarriageAlgorithm[HousePurchaseOffer, HouseListing]())(unmatched) } } diff --git a/src/test/scala/org/economicsl/matching/manytoone/SchoolChoiceSpecification.scala b/src/test/scala/org/economicsl/matching/manytoone/SchoolChoiceSpecification.scala index 3f834a2..15d6fab 100644 --- a/src/test/scala/org/economicsl/matching/manytoone/SchoolChoiceSpecification.scala +++ b/src/test/scala/org/economicsl/matching/manytoone/SchoolChoiceSpecification.scala @@ -15,6 +15,7 @@ limitations under the License. */ package org.economicsl.matching.manytoone +import cats.data.State import org.scalacheck.{Gen, Prop, Properties} @@ -45,14 +46,17 @@ object SchoolChoiceSpecification extends Properties("school-choice") { } property("no school accepts more students than its quota allows") = Prop.forAll(unMatched) { - case (students, schools) => - val ((_, _), matching) = (new DeferredAcceptanceAlgorithm[Student, School])(students, schools) + unmatched => + val result = State(new DeferredAcceptanceAlgorithm[Student, School]).run(unmatched) + val ((_, _), matching)= result.value matching.matches.forall{ case (school, matchedStudents) => matchedStudents.size <= school.quota } } property("matching should be stable") = Prop.forAll(unMatched) { - case (students, schools) => - val ((_, _), matching) = (new DeferredAcceptanceAlgorithm[Student, School])(students, schools) + unmatched => + val result = State(new DeferredAcceptanceAlgorithm[Student, School]).run(unmatched) + val ((_, _), matching)= result.value + val (students, schools) = unmatched students.forall(student => schools.forall(school => !matching.isBlockedBy(school -> student))) } diff --git a/src/test/scala/org/economicsl/matching/onetoone/MarriageMarketSpecification.scala b/src/test/scala/org/economicsl/matching/onetoone/MarriageMarketSpecification.scala index 64a8119..2da934a 100644 --- a/src/test/scala/org/economicsl/matching/onetoone/MarriageMarketSpecification.scala +++ b/src/test/scala/org/economicsl/matching/onetoone/MarriageMarketSpecification.scala @@ -15,6 +15,8 @@ limitations under the License. */ package org.economicsl.matching.onetoone + +import cats.data.State import org.scalacheck.{Gen, Prop, Properties} import scala.util.{Failure, Success} @@ -46,22 +48,24 @@ object MarriageMarketSpecification extends Properties("marriage-market") { } property("all men and women are matched") = Prop.forAll(randomUnmatchedSets) { - case (ms, ws) => - val result = (new StableMarriageAlgorithm[Man, Woman])(ms, ws) - result match { - case Success(((unMatchedMs, unMatchedWs), matching)) => - (ms.size == ws.size) && unMatchedMs.isEmpty && unMatchedWs.isEmpty && (matching.size == ms.size) + case unmatched @ (ms, ws) => + val result = State(new StableMarriageAlgorithm[Man, Woman]).run(unmatched) + val ((unMatchedMs, unMatchedWs), matching) = result.value + matching match { + case Success(oneToOneMatching) => + (ms.size == ws.size) && unMatchedMs.isEmpty && unMatchedWs.isEmpty && (oneToOneMatching.size == ms.size) case Failure(_) => ms.size != ws.size } } property("matching should be stable") = Prop.forAll(randomUnmatchedSets) { - case (ms, ws) => - val result = (new StableMarriageAlgorithm[Man, Woman])(ms, ws) - result match { - case Success(((_, _), matching)) => - ms.forall(m => ws.forall(w => !matching.isBlockedBy(w -> m))) + case unmatched @ (ms, ws) => + val result = State(new StableMarriageAlgorithm[Man, Woman]).run(unmatched) + val ((_, _), matching) = result.value + matching match { + case Success(oneToOneMatching) => + ms.forall(m => ws.forall(w => !oneToOneMatching.isBlockedBy(w -> m))) case Failure(_) => ms.size != ws.size } diff --git a/src/test/scala/org/economicsl/matching/onetoone/StableMarriageAlgorithmSpec.scala b/src/test/scala/org/economicsl/matching/onetoone/StableMarriageAlgorithmSpec.scala index 7c88de6..e28ba59 100644 --- a/src/test/scala/org/economicsl/matching/onetoone/StableMarriageAlgorithmSpec.scala +++ b/src/test/scala/org/economicsl/matching/onetoone/StableMarriageAlgorithmSpec.scala @@ -25,20 +25,23 @@ import scala.util.{Failure, Success} class StableMarriageAlgorithmSpec extends FlatSpec { "The stable marriage algorithm applied to two empty sets" should "successfully return an empty matching." in { - val result = (new StableMarriageAlgorithm[Man, Woman])(Set.empty[Man], Set.empty[Woman]) + val unmatched = (Set.empty[Man], Set.empty[Woman]) + val result = (new StableMarriageAlgorithm[Man, Woman])(unmatched) result match { - case Success(((_, _), matching)) => + case ((_, _), Success(matching)) => assert(matching.isEmpty) - case Failure(_) => + case ((_,_), Failure(_)) => false } } "The stable matching algorithm applied to sets of different sizes" should "return a failure." in { - val result1 = (new StableMarriageAlgorithm[Man, Woman])(Set(Man(UUID.randomUUID(), 42, Man.womanByQuality)), Set.empty[Woman]) + val unmatched1 = (Set(Man(UUID.randomUUID(), 42, Man.womanByQuality)), Set.empty[Woman]) + val ((_, _), result1) = (new StableMarriageAlgorithm[Man, Woman])(unmatched1) assert(result1.isFailure) - val result2 = (new StableMarriageAlgorithm[Man, Woman])(Set.empty[Man], Set(Woman(UUID.randomUUID(), 42, Woman.manByQuality))) + val unmatched2 = (Set.empty[Man], Set(Woman(UUID.randomUUID(), 42, Woman.manByQuality))) + val ((_, _), result2) = (new StableMarriageAlgorithm[Man, Woman])(unmatched2) assert(result2.isFailure) }