All Downloads are FREE. Search and download functionalities are using the official Maven repository.

pimpathon.list.scala Maven / Gradle / Ivy

The newest version!
package pimpathon

import scala.annotation.tailrec
import scala.collection.{mutable ⇒ M, GenTraversable, GenTraversableLike}
import scala.collection.immutable._

import pimpathon.any._
import pimpathon.boolean._
import pimpathon.function._
import pimpathon.multiMap._
import pimpathon.option._
import pimpathon.tuple._


object list extends genTraversableLike[List] {
  implicit def listPimps[A](list: List[A]): ListPimps[A] = new ListPimps[A](list)
  implicit def matrixPimps[A](matrix: List[List[A]]): MatrixPimps[A] = new MatrixPimps[A](matrix)

  class ListPimps[A](list: List[A]) {
    def tapEmpty[Discarded](empty: ⇒ Discarded): List[A] = tap(empty, _ ⇒ {})
    def tapNonEmpty[Discarded](nonEmpty: List[A] ⇒ Discarded): List[A] = tap({}, nonEmpty)
    def tap[Discarded](empty: ⇒ Discarded, nonEmpty: List[A] ⇒ Discarded): List[A] = { uncons(empty, nonEmpty); list }

    def emptyTo(alternative: ⇒ List[A]): List[A] = uncons(alternative, _ ⇒ list)

    def zipToMap[B](values: List[B]): Map[A, B] = zip(values).toMap
    def zipWith[B, C](values: List[B])(f: ((A, B)) ⇒ C): List[C] = zip(values).map(f).toList

    def fraction(p: Predicate[A]): Double = countWithSize(p).map(_.to[Double].calc(_ / _)).getOrElse(Double.NaN)

    def countWithSize(p: Predicate[A]): Option[(Int, Int)] = calcIfNonEmpty(_.foldLeft((0, 0)) {
      case ((passed, size), elem) ⇒ (passed + p(elem).asInt, size + 1)
    })

    def sizeGT(value: Int): Boolean = uncons(empty = value < 0, nonEmpty = _.tail.sizeGT(value - 1))

    def duplicates: List[A] = duplicatesBy(identity[A])
    def duplicatesBy[B](f: A ⇒ B): List[A] = (countBy(f) - 1).multiMap.values
    def distinctBy[B](f: A ⇒ B): List[A] = list.map(equalBy(f)).distinct.map(_.a)

    def countBy[B](f: A ⇒ B): MultiMap[List, Int, A] =
      list.asMultiMap[List].withKeys(f).multiMap.mapEntries(_ ⇒ values ⇒ (values.size, values))

    def batchBy[B](f: A ⇒ B): List[List[A]] = list.unconsC(empty = Nil, nonEmpty = head ⇒ tail ⇒ {
      val (_, lastBatch, allBatches) = tail.foldLeft((f(head), M.ListBuffer(head), M.ListBuffer[List[A]]())) {
        case ((currentKey, batch, batches), a) ⇒ f(a).cond(_ == currentKey,
          ifTrue  = key ⇒ (key, batch += a,      batches),
          ifFalse = key ⇒ (key, M.ListBuffer(a), batches += batch.toList)
        )
      }

      (allBatches += lastBatch.toList).toList
    })

    def onlyOrThrow(f: List[A] ⇒ Exception): A = onlyOption.getOrThrow(f(list))
    def onlyEither: Either[List[A], A] = onlyOption.toRight(list)
    def onlyOption: Option[A] = unconsC(None, head ⇒ tail ⇒ tail.headOption.invert(head))

    def headTail: (A, List[A]) = headTailOption.getOrThrow("headTail of empty list")
    def initLast: (List[A], A) = initLastOption.getOrThrow("initLast of empty list")

    def headTailOption: Option[(A, List[A])] = unconsC(None, head ⇒ tail ⇒ Some((head, tail)))
    def initLastOption: Option[(List[A], A)] = uncons(None, _ ⇒ Some(list.init, list.last))

    def tailOption: Option[List[A]] = uncons(None, nonEmpty ⇒ Some(nonEmpty.tail))

    def calcIfNonEmpty[B](f: List[A] ⇒ B): Option[B] = list.calcIf(_.nonEmpty)(f)
    def mapIfNonEmpty[B](f: A ⇒ B): Option[List[B]] = list.calcIf(_.nonEmpty)(_.map(f))

    def amass[B](pf: PartialFunction[A, List[B]]): List[B] = list.flatMap(a ⇒ pf.lift(a).getOrElse(Nil))

    def uncons[B](empty: ⇒ B, nonEmpty: List[A] ⇒ B): B = if (list.isEmpty) empty else nonEmpty(list)

    def unconsC[B](empty: ⇒ B, nonEmpty: A ⇒ List[A] ⇒ B): B = list match {
      case Nil          ⇒ empty
      case head :: tail ⇒ nonEmpty(head)(tail)
    }

    def unsnocC[B](empty: ⇒ B, nonEmpty: List[A] ⇒ A ⇒ B): B = initLastOption match {
      case None               ⇒ empty
      case Some((init, last)) ⇒ nonEmpty(init)(last)
    }

    def const[B](elem: B): List[B] = list.map(_ ⇒ elem)

    def prefixPadTo(len: Int, elem: A): List[A] = List.fill(len - list.length)(elem) ++ list

    def sharedPrefix(other: List[A])(implicit compare: A ⇒ A ⇒ Boolean = equalC[A]): (List[A], List[A], List[A]) = {
      @tailrec def recurse(lefts: List[A], rights: List[A], acc: List[A]): (List[A], List[A], List[A]) = {
        (lefts, rights) match {
          case (left :: lhs, right :: rhs) if compare(left)(right) ⇒ recurse(lhs, rhs, left :: acc)
          case _                                                   ⇒ (acc.reverse, lefts, rights)
        }
      }

      recurse(list, other, Nil)
    }

    def zipExact[B](bs: List[B]): (List[(A, B)], Option[Either[List[A], List[B]]]) = zipExactWith(bs)((a, b) ⇒ (a, b))

    def zipExactWith[B, C](other: List[B])(f: (A, B) ⇒ C): (List[C], Option[Either[List[A], List[B]]]) = {
      @tailrec
      def recurse(la: List[A], lb: List[B], cs: List[C]): (List[C], Option[Either[List[A], List[B]]]) = (la, lb) match {
        case (a :: as, b :: bs) ⇒ recurse(as, bs, f(a, b) :: cs)
        case (Nil, Nil)         ⇒ (cs.reverse, None)
        case (as, Nil)          ⇒ (cs.reverse, Some(Left(as)))
        case (Nil, bs)          ⇒ (cs.reverse, Some(Right(bs)))
      }

      recurse(list, other, Nil)
    }

    private def equalBy[B](f: A ⇒ B)(a: A): EqualBy[A, B] = new EqualBy(f(a))(a)
    private def zip[B](other: List[B]): Iterator[(A, B)] = list.iterator.zip(other.iterator)
  }

  class MatrixPimps[A](value: List[List[A]]) {
    def cartesianProduct: List[List[A]] = value.foldRight(List(Nil): List[List[A]]) {
      case (item, acc) ⇒ for { a ← item; b ← acc } yield a :: b
    }
  }

  protected def toGTL[A](l: List[A]): GenTraversableLike[A, GenTraversable[A]] = l
}

case class EqualBy[A, B](b: B)(val a: A)




© 2015 - 2025 Weber Informatics LLC | Privacy Policy