-
-
Save waynejo/0d22e5e022f18f898ba4bdae5c6cbab7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import scala.collection.mutable.HashSet | |
import java.util.Objects | |
import scala.collection.mutable | |
object Main extends App { | |
// var container1 = new Container() | |
// container1.addWater(10) | |
// println(container1) | |
// var container2 = new Container() | |
// container2.addWater(20) | |
// println(container2) | |
// container1.connectTo(container2) | |
// println(container1) | |
var container1 = new Container2() | |
container1.addWater(10) | |
println(container1) | |
var container2 = new Container2() | |
container2.addWater(20) | |
println(container2) | |
container1.connectTo(container2) | |
println(container1) | |
} | |
class Container { | |
private var amount: Double = 0 | |
private var group = mutable.HashSet[Container](this) | |
def getAmount: Double = { | |
amount | |
} | |
def connectTo(other: Container): Unit = { // Pre-condition | |
Objects.requireNonNull(other, "Cannot connect to a null container.") | |
if (group eq other.group) return | |
val size1 = group.size | |
val size2 = other.group.size | |
val tot1 = amount * size1 | |
val tot2 = other.amount * size2 | |
val newAmount = (tot1 + tot2) / (size1 + size2) | |
group.addAll(other.group) | |
for (x <- other.group) { | |
x.group = group | |
} | |
for (x <- group) { | |
x.amount = newAmount | |
} | |
assert(invariantsArePreservedByConnectTo(other), "connectTo broke an invariant!") | |
} | |
private def invariantsArePreservedByConnectTo(other: Container) = { | |
group == other.group && | |
isGroupNonNegative && | |
isGroupBalanced && | |
isGroupConsistent | |
} | |
def addWater(amount: Double) = { | |
val amountPerContainer = amount / group.size | |
// Pre-condition | |
if (this.amount + amountPerContainer < 0) | |
throw new IllegalArgumentException( | |
"Not enough water to match the addWater request."); | |
for (element <- group) { | |
element.amount += amountPerContainer | |
} | |
assert(invariantsArePreservedByAddWater, "addWater broke an invariant!") | |
} | |
def invariantsArePreservedByAddWater: Boolean = { | |
return isGroupNonNegative && isGroupBalanced | |
} | |
def isGroupNonNegative: Boolean = { // Invariant I1 | |
!group.exists { _.amount < 0} | |
} | |
def isGroupConsistent: Boolean = { // Invariants I2, I3 | |
!group.exists { _.group != group} | |
} | |
def isGroupBalanced: Boolean = { // Invariant I4 | |
!group.exists { _.amount != amount} | |
} | |
def toString(histories: Set[Container]): String = { | |
val groupText = group.map { element => | |
if (element eq this) { | |
"self" | |
} else if (histories.contains(this)) { | |
"..." | |
} else { | |
element.toString(histories + this).split("\n").map(" " + _).mkString("\n") | |
} | |
}.mkString("\n ") | |
s"""Container { | |
| amount = $amount, | |
| group = $groupText | |
|}""".stripMargin | |
} | |
override def toString: String = { | |
toString(Set()) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.Objects | |
import scala.collection.mutable | |
private class ConnectPostData { | |
var group1: mutable.HashSet[Container2] = mutable.HashSet[Container2]() | |
var group2: mutable.HashSet[Container2] = mutable.HashSet[Container2]() | |
var amount1 = .0 | |
var amount2 = .0 | |
} | |
class Container2 { | |
private var amount: Double = 0 | |
private var group = mutable.HashSet[Container2](this) | |
def getAmount: Double = { | |
amount | |
} | |
def connectTo(other: Container2): Unit = { // Pre-condition check | |
Objects.requireNonNull(other, "Cannot connect to a null container.") | |
if (group eq other.group) { | |
return | |
} | |
val postData = saveConnectPostData(other) | |
assert(postData != null) | |
val size1 = group.size | |
val size2 = other.group.size | |
val tot1 = amount * size1 | |
val tot2 = other.amount * size2 | |
val newAmount = (tot1 + tot2) / (size1 + size2) | |
group.addAll(other.group) | |
for (x <- other.group) { | |
x.group = group | |
} | |
for (x <- group) { | |
x.amount = newAmount | |
} | |
assert(postConnect(postData), "connectTo failed its post-condition!") | |
} | |
private def saveConnectPostData(other: Container2) = { | |
val data = new ConnectPostData | |
data.group1 = group.clone() | |
data.group2 = other.group.clone() | |
data.amount1 = amount | |
data.amount2 = other.amount | |
data | |
} | |
private def postConnect(postData: ConnectPostData): Boolean = { | |
areGroupMembersCorrect(postData) && | |
isGroupAmountCorrect(postData) && | |
isGroupBalanced && | |
isGroupConsistent | |
} | |
private def areGroupMembersCorrect(postData: ConnectPostData): Boolean = { | |
postData.group1.diff(group).isEmpty && | |
postData.group2.diff(group).isEmpty && | |
group.size == postData.group1.size + postData.group2.size | |
} | |
private def isGroupAmountCorrect(postData: ConnectPostData) = { | |
val size1 = postData.group1.size | |
val size2 = postData.group2.size | |
val tot1 = postData.amount1 * size1 | |
val tot2 = postData.amount2 * size2 | |
val newAmount = (tot1 + tot2) / (size1 + size2) | |
almostEqual(amount, newAmount) | |
} | |
def addWater(amount: Double): Unit = { | |
val amountPerContainer = amount / group.size | |
if (this.amount + amountPerContainer < 0) { | |
throw new IllegalArgumentException("Not enough water to match the addWater request.") | |
} | |
val oldTotal = groupAmount // for the post-condition | |
assert(oldTotal >= 0) | |
for (c <- group) { | |
c.amount += amountPerContainer | |
} | |
assert(postAddWater(oldTotal, amount), "addWater failed its post-condition!") | |
} | |
private def postAddWater(oldTotal: Double, addedAmount: Double) = { | |
isGroupBalanced && almostEqual(groupAmount, oldTotal + addedAmount) | |
} | |
private def almostEqual(x: Double, y: Double) = { | |
val EPSILON = 1E-4 | |
Math.abs(x - y) < EPSILON | |
} | |
def isGroupConsistent: Boolean = { // Invariants I2, I3 | |
!group.exists { _.group != group} | |
} | |
def isGroupBalanced: Boolean = { // Invariant I4 | |
!group.exists { _.amount != amount} | |
} | |
def groupAmount: Int = { | |
group.map(_.amount).sum.toInt | |
} | |
def toString(histories: Set[Container2]): String = { | |
val groupText = group.map { element => | |
if (element eq this) { | |
"self" | |
} else if (histories.contains(this)) { | |
"..." | |
} else { | |
element.toString(histories + this).split("\n").map(" " + _).mkString("\n") | |
} | |
}.mkString("\n ") | |
s"""Container { | |
| amount = $amount, | |
| group = $groupText | |
|}""".stripMargin | |
} | |
override def toString: String = { | |
toString(Set()) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Container { | |
amount = 10.0, | |
group = self | |
} | |
Container { | |
amount = 20.0, | |
group = self | |
} | |
Container { | |
amount = 15.0, | |
group = self | |
Container { | |
amount = 15.0, | |
group = self | |
Container { | |
amount = 15.0, | |
group = self | |
... | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment