Skip to content

Instantly share code, notes, and snippets.

@raboof
Forked from agemooij/FinalVersion.scala
Last active August 29, 2015 14:05
Show Gist options
  • Save raboof/a589af9ecce79bb14f9e to your computer and use it in GitHub Desktop.
Save raboof/a589af9ecce79bb14f9e to your computer and use it in GitHub Desktop.
Functional 'add to basket' experiments
def addItem(newItem: BasketItem): BasketState = {
copy(
items.foldRight((false, List.empty[BasketItem])) {
case (item, (_, out)) if (item.matchesProductAndSizeOf(newItem)) ⇒ (true, item.incrementNumberBy(newItem.numberOfProducts) :: out)
case (item, (didSomethingMatch, out)) ⇒ (didSomethingMatch, item :: out)
} match {
case (false, _) ⇒ newItem :: items
case (true, modifiedItems) ⇒ modifiedItems
}
)
}
// Taking advantage of the convention that Left holds an unsuccessful (partial) result and Right a successful (partial) result:
def addItem(newItem: BasketItem, basket: List[BasketItem]) = {
basket.foldRight[Either[List[BasketItem], List[BasketItem]]](Left(List())) {
case (item, Right(items)) ⇒ Right(item :: items)
case (item @ BasketItem(newItem.productNumber, newItem.sizeCode, _, _), Left(items)) ⇒ Right(item.copy(numberOfProducts = item.numberOfProducts + newItem.numberOfProducts) :: items)
case (item, Left(items)) ⇒ Left(item :: items)
}.right.getOrElse(newItem :: basket)
}
case class BasketState(items: List[BasketItem] = Nil) {
def addItem(newItem: BasketItem): BasketState = {
// if (items.exists(item ⇒ item.productNumber == newItem.productNumber && item.sizeCode == newItem.sizeCode)) {
// copy(
// items.map { item ⇒
// if (item.productNumber == newItem.productNumber && item.sizeCode == newItem.sizeCode)
// item.copy(numberOfProducts = item.numberOfProducts + 1)
// else item
// }
// )
// } else {
// copy(newItem :: items)
// }
// copy(
// items.foldRight((false, List.empty[BasketItem])) { (item, out) ⇒
// if (item.productNumber == newItem.productNumber && item.sizeCode == newItem.sizeCode)
// (true, item.copy(numberOfProducts = item.numberOfProducts + 1) :: out._2)
// else (false, item :: out._2)
// } match {
// case (false, _) ⇒ newItem :: items
// case (true, modifiedItems) ⇒ modifiedItems
// }
// )
copy(
items.foldRight((false, List.empty[BasketItem])) {
case (item, (_, out)) if (item.productNumber == newItem.productNumber && item.sizeCode == newItem.sizeCode) ⇒ (true, item.copy(numberOfProducts = item.numberOfProducts + newItem.numberOfProducts) :: out)
case (item, (bool, out)) ⇒ (bool, item :: out)
} match {
case (false, _) ⇒ newItem :: items
case (true, modifiedItems) ⇒ modifiedItems
}
)
// copy(
// items.foldRight((false, List.empty[BasketItem])) {
// case (BasketItem(newItem.productNumber, newItem.sizeCode, numberOfProducts, product), (_, out)) ⇒ (true, BasketItem(newItem.productNumber, newItem.sizeCode, numberOfProducts + newItem.numberOfProducts, product) :: out)
// case (item, (bool, out)) ⇒ (bool, item :: out)
// } match {
// case (false, _) ⇒ newItem :: items
// case (true, modifiedItems) ⇒ modifiedItems
// }
// )
}
}
// The 'classical' functional solution is of course recursive:
def addRecursiveRight(newItem: BasketItem, basket: List[BasketItem]): List[BasketItem] = basket match {
case item :: tail ⇒ item match {
case BasketItem(newItem.productNumber, newItem.sizeCode, _, _) ⇒ item.copy(numberOfProducts = item.numberOfProducts + newItem.numberOfProducts) :: tail
case _ ⇒ item :: addRecursiveRight(newItem, tail)
}
case List() ⇒ List(newItem)
}
//.. but that adds a new item to the right, not to the left, and is not tail-recursive.
// I fooled around a bit to get to a tail-recursive solution that adds to the left, but didn't come up with anything elegant - best I could do was:
def addRecursiveLeft(newItem: BasketItem, basket: List[BasketItem]): List[BasketItem] = addRecursiveImpl(newItem, basket).getOrElse(newItem :: basket)
@tailrec
final def addRecursiveImpl(newItem: BasketItem, basket: List[BasketItem], skippedItems: List[BasketItem] = List()): Option[List[BasketItem]] = {
basket match {
case item :: tail ⇒ item match {
case BasketItem(newItem.productNumber, newItem.sizeCode, _, _) ⇒ Some((item.copy(numberOfProducts = item.numberOfProducts + newItem.numberOfProducts) :: skippedItems).reverse ::: tail)
case _ ⇒ addRecursiveImpl(newItem, tail, item :: skippedItems)
}
case List() ⇒ None
}
}
@agemooij
Copy link

Nice! Finally a good use case for Either ;)

My version of the Either solution, slightly cleaned up for readability:

def addItem(newItem: BasketItem): BasketState = {
  copy(
    items.foldRight[Either[List[BasketItem], List[BasketItem]]](Left(List())) {
      case (item, Left(acc)) if (item.matchesProductAndSizeOf(newItem)) ⇒ Right(item.incrementNumberBy(newItem.numberOfProducts) :: acc)
      case (item, Left(acc))                                            ⇒ Left(item :: acc)
      case (item, Right(acc))                                           ⇒ Right(item :: acc)
    }.right.getOrElse(newItem :: items)
  )
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment