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

com.daodecode.scalax.collection.extensions.package.scala Maven / Gradle / Ivy

The newest version!
package com.daodecode.scalax.collection

import scala.annotation.nowarn
import scala.collection.immutable.SeqOps
import scala.collection.{BuildFrom, IterableOps, MapOps, View, immutable, mutable}

package object extensions {

  /**
    * @define coll sequence
    */
  implicit class SeqOpsExtension[A, C](val seqOps: SeqOps[A, Seq, C]) extends AnyVal {

    /** Builds a new $coll from this $coll without any duplicate elements (as
      * determined by `==` after applying transforming function `f`).
      * Function `takeFirst` defines which of duplicates will be preserved.
      * If it returns `true` first of compared duplicates will be kept, second one otherwise.
      *
      * Example:
      * {{{
      *   scala> List(1 -> "one", 1 -> "ten", 2 -> "two", 2 -> "twenty").
      *        | distinctByUsing(_._1, takeFirst = _._2.length > _._2.length)
      *   res1: List[(Int, String)] = List((1,ten), (2,twenty))
      * }}}
      *
      * @return  A new $coll which contains selected occurrence of every element of this $coll.
      * @since 0.1.1
      */
    def distinctByUsing[B](f: A => B, takeFirst: (A, A) => Boolean)(implicit bf: BuildFrom[C, A, C]): C = {
      val seen = mutable.LinkedHashMap.empty[B, A]
      for (x <- seqOps) {
        val fx = f(x)
        seen.get(fx) match {
          case Some(a) => seen += fx -> (if (takeFirst(a, x)) a else x)
          case _ => seen += fx -> x
        }
      }
      bf.fromSpecific(seqOps.repr)(seen.values): @nowarn("cat=deprecation")
    }
  }

  /**
    * @define coll iterable
    */
  implicit class IterableOpsExtension[A, CC[_], C <: IterableOps[A, CC, C]](val iterableOps: IterableOps[A, CC, C])
      extends AnyVal {

    /** Applies a binary operator to a start value and all elements of this $coll while predicate `p` is satisfied,
      * going left to right.
      *
      * Example:
      * {{{
      *   scala> Iterable(List(1,2,3), List(4,5), List(6,7,8,9)).
      *        | foldLeftWhile(List.empty[Int])(_.size < 4){ case (acc, l) => acc ++ l }
      *   res1: List[Int] = List(1, 2, 3, 4, 5)
      * }}}
      *
      * @param   z    the start value.
      * @param   p    the predicate used to test if folding should continue.
      * @param   op   the binary operator.
      * @tparam  B    the result type of the binary operator.
      * @return  the result of inserting `op` between consecutive elements of this $coll while predicate `p` is satisfied,
      *          going left to right with the start value `z` on the left.
      * @since 0.1.0
      */
    def foldLeftWhile[B](z: B)(p: B => Boolean)(op: (B, A) => B): B = {
      var result = z
      val it = iterableOps.iterator
      while (it.hasNext && p(result)) {
        result = op(result, it.next())
      }
      result
    }

    /** Applies a binary operator to all elements of this $coll and a start value while predicate `p` is satisfied,
      * going right to left.
      *
      * Example:
      * {{{
      *   scala> Iterable(List(1,2,3), List(4,5), List(6,7,8,9)).
      *        | foldRightWhile(List.empty[Int])(_.size < 5){ case (l, acc) =>  l ::: acc }
      *   res1: List[Int] = List(4, 5, 6, 7, 8, 9)
      * }}}
      *
      * @param   z    the start value.
      * @param   p    the predicate used to test if folding should continue.
      * @param   op   the binary operator.
      * @tparam  B    the result type of the binary operator.
      * @return  the result of inserting `op` between consecutive elements of this $coll while predicate `p` is satisfied,
      *          going right to left with the start value `z` on the right.
      * @since 0.1.0
      */
    def foldRightWhile[B](z: B)(p: B => Boolean)(op: (A, B) => B): B =
      if (iterableOps.isEmpty) z
      else {
        val result = iterableOps.tail.foldRightWhile(z)(p)(op)
        if (p(result)) op(iterableOps.head, result) else result
      }

    /**
      * Converts this $coll to a map. This method is unavailable unless
      * the elements are members of Tuple2[K, V].
      * The method is different from `toMap` in the way that values for
      * duplicate keys will be collected into $coll,
      * so all the values will be in the result map.
      *
      * Example:
      * {{{
      *   scala> List(1 -> "1", 2 -> "2", 1 -> "11").toCompleteMap
      *   res1: scala.collection.immutable.Map[Int,List[String]] = Map(1 -> List(1, 11), 2 -> List(2))
      * }}}
      *
      * @return a map of type `immutable.Map[K, That]` where `That` is a $coll of `V`
      * @since 0.1.1
     **/
    def toCompleteMap[K, V](implicit ev: A <:< (K, V)): immutable.Map[K, CC[V]] =
      iterableOps.groupMap(_._1)(_._2)

    /**
      * Converts this $coll to an immutable map using provided function `f`
      * to generate Map keys and values from elements of this $coll.
      * It can be seen as more efficient combination of `map() and toMap`
      *
      * @param f Function to generate key and value from element of this $coll
      * @tparam K Key type of the result Map
      * @tparam V Value type of the result Map
      * @return Immutable Map. It's possible to loose some of the original elements
      *         if function `f` returns same key for more than one element
      * @since 0.2.0
      */
    def mapToMap[K, V](f: A => (K, V)): immutable.Map[K, V] = {
      val b = immutable.Map.newBuilder[K, V]
      for (a <- iterableOps)
        b += f(a)
      b.result()
    }

    /**
      * Converts this $coll to a map where key is derived from each item using function `f`
      * @param f Function to build a key for the result Map
      * @tparam K Key type of the result Map
      * @return Immutable Map with original elements as values.
      *         It's possible to loose some of the original elements
      *         if function `f` returns same key for more than one element
      * @since 0.2.0
      */
    def toMapWithKey[K](f: A => K): immutable.Map[K, A] = mapToMap(a => f(a) -> a)

    /**
      * Converts this $coll to a map where value is derived from each item using function `f`
      * @param f Function to build a value for the result Map
      * @tparam V Value type of the result Map
      * @return Immutable Map with original elements as keys.
      *         It's possible to loose some of the original elements, as Maps don't allow duplicate keys
      * @since 0.2.0
      */
    def toMapWithValue[V](f: A => V): immutable.Map[A, V] = mapToMap(a => a -> f(a))

    /**
      * Creates a new immutable `Map` with unique elements of this $coll as keys and
      * count of duplicates ((as determined by `==`) (frequency) as values.
      *
      * Example:
      * {{{
      *   scala> List("a", "b", "c", "a", "b", "d").withFrequency
      *   res1: scala.collection.immutable.Map[String,Int] = Map(a -> 2, b -> 2, c -> 1, d -> 1)
      * }}}
      *
      * @return a map of type immutable.Map[A, Int] where Int represents a frequency of key A in original $coll
      * @since 0.1.1
      */
    def withFrequency: immutable.Map[A, Int] = withFrequencyBy(identity)

    /**
      * Creates a new immutable `Map` with unique elements resulting from applying function `f`
      * to elements of this $coll as keys and
      * count of duplicates ((as determined by `==`) (frequency) as values.
      *
      * Example:
      * {{{
      *   scala> List("ab", "bc", "cd", "ab", "bc", "de").withFrequencyBy(_.head)
      *   res1: scala.collection.immutable.Map[Char  ,Int] = Map(a -> 2, b -> 2, c -> 1, d -> 1)
      * }}}
      *
      * @return a map of type immutable.Map[B, Int] where Int represents a frequency of keys,
      *         obtained from original $coll by applying function `f` to elements
      * @since 0.2.0
      */
    def withFrequencyBy[B](f: A => B): immutable.Map[B, Int] = iterableOps.groupMapReduce(f)(_ => 1)(_ + _)


    /**
      * Finds the element where function `f` returns largest value according to `ord`.
      * Returns result wrapped in `Option` or `None` if $coll is empty.
      *
      * Example:
      * {{{
      *   scala> List(1,2,1).maxOptionBy(_ * -1)
      *   res1: Option[Int] = Some(1)
      *
      *   scala> List.empty[Int].maxOptionBy(_ * -1)
      *   res2: Option[Int] = None
      * }}}
      *
      * @param f Transforming function that applied to elements produces results to be compared.
      * @tparam B The type over which the ordering is defined.
      * @return the largest element (as identified by `ord` after applying `f`)
      *         wrapped in `Option` or `None` if $coll is empty
      * @since 0.1.2
      */
    @deprecated("Use maxByOption from standard library", since = "0.3.0")
    def maxOptionBy[B: Ordering](f: A => B): Option[A] =
      iterableOps.maxByOption(f)

    /**
      * Finds the element where function `f` returns smallest value according to `ord`.
      * Returns result wrapped in `Option` or `None` if $coll is empty.
      *
      * Example:
      * {{{
      *   scala> List(1,2,1).minOptionBy(_ * -1)
      *   res1: Option[Int] = Some(2)
      *
      *   scala> List.empty[Int].minOptionBy(_ * -1)
      *   res2: Option[Int] = None
      * }}}
      *
      * @param f Transforming function that applied to elements produces results to be compared.
      * @tparam B The type over which the ordering is defined.
      * @return the smallest element (as identified by `ord` after applying `f`)
      *         wrapped in `Option` or `None` if $coll is empty
      * @since 0.1.2
      */
    @deprecated("Use minByOption from standard library", since = "0.3.0")
    def minOptionBy[B: Ordering](f: A => B): Option[A] =
      iterableOps.minByOption(f)


    /** Converts this iterable of Tuple4 into four collections of the first, second,
      *  third and fourth element of each Tuple4.
      *
      * Example:
      *    {{{
      *    scala> List((1, "one", '1', 1d), (2, "two", '2', 2d), (3, "three", '3', 3d), (4, "four", '4', 4d)).unzip4
      *    res1: (List[Int], List[String], List[Char], List[Double]) = (List(1, 2, 3, 4),List(one, two, three, four),List(1, 2, 3, 4),List(1.0, 2.0, 3.0, 4.0))
      *    }}}
      *
      *  @tparam A1       the type of the first member of the Tuple4 element
      *  @tparam A2       the type of the second member of the Tuple4 element
      *  @tparam A3       the type of the third member of the Tuple4 element
      *  @tparam A4       the type of the fourth member of the Tuple4 element
      *  @param asTuple4  an implicit conversion which asserts that the element type
      *                   of this iterable is a Tuple4.
      *  @return          a Tuple4 of iterables, containing the first, second, third and forth
      *                   member of each Tuple4 element of this iterable.
      *  @since 0.3.2
      */
    def unzip4[A1, A2, A3, A4](implicit asTuple4: A => (A1, A2, A3, A4)): (CC[A1], CC[A2], CC[A3], CC[A4]) = (
      iterableOps.iterableFactory.from(new View.Map[A, A1](iterableOps, asTuple4(_)._1)),
      iterableOps.iterableFactory.from(new View.Map[A, A2](iterableOps, asTuple4(_)._2)),
      iterableOps.iterableFactory.from(new View.Map[A, A3](iterableOps, asTuple4(_)._3)),
      iterableOps.iterableFactory.from(new View.Map[A, A4](iterableOps, asTuple4(_)._4))
    )

    /** Converts this iterable of Tuple5 into five collections of the first, second,
      *  third, fourth and fifth element of each Tuple5.
      *
      * Example:
      *    {{{
      *    scala> List((1, "one", '1', 1d, 1L), (2, "two", '2', 2d, 2L), (3, "three", '3', 3d, 3L), (4, "four", '4', 4d, 4L), (5, "five", '5', 5d, 5L)).unzip5
      *    res1: (List[Int], List[String], List[Char], List[Double], List[Long]) = (List(1, 2, 3, 4, 5),List(one, two, three, four, five),List(1, 2, 3, 4, 5),List(1.0, 2.0, 3.0, 4.0, 5.0),List(1, 2, 3, 4, 5))
      *    }}}
      *
      *  @tparam A1       the type of the first member of the Tuple5 element
      *  @tparam A2       the type of the second member of the Tuple5 element
      *  @tparam A3       the type of the third member of the Tuple5 element
      *  @tparam A4       the type of the fourth member of the Tuple5 element
      *  @tparam A5       the type of the fifth member of the Tuple5 element
      *  @param asTuple5  an implicit conversion which asserts that the element type
      *                   of this iterable is a Tuple5.
      *  @return          a Tuple5 of iterables, containing the first, second, third,
      *                   forth and fifth member of each Tuple5 element of this iterable.
      *  @since 0.3.2
      */
    def unzip5[A1, A2, A3, A4, A5](implicit asTuple5: A => (A1, A2, A3, A4, A5)): (CC[A1], CC[A2], CC[A3], CC[A4], CC[A5]) = (
      iterableOps.iterableFactory.from(new View.Map[A, A1](iterableOps, asTuple5(_)._1)),
      iterableOps.iterableFactory.from(new View.Map[A, A2](iterableOps, asTuple5(_)._2)),
      iterableOps.iterableFactory.from(new View.Map[A, A3](iterableOps, asTuple5(_)._3)),
      iterableOps.iterableFactory.from(new View.Map[A, A4](iterableOps, asTuple5(_)._4)),
      iterableOps.iterableFactory.from(new View.Map[A, A5](iterableOps, asTuple5(_)._5))
    )
    /** Converts this iterable of Tuple6 into five collections of the first, second,
      *  third, fourth, fifth and sixth element of each Tuple6.
      *
      * Example:
      *    {{{
      *    scala> List((1, "one", '1', 1d, 1L, true), (2, "two", '2', 2d, 2L, false), (3, "three", '3', 3d, 3L, true), (4, "four", '4', 4d, 4L, false), (5, "five", '5', 5d, 5L, true), (6, "six", '6', 6d, 6L, false)).unzip6
      *    res0: (List[Int], List[String], List[Char], List[Double], List[Long], List[Boolean]) = (List(1, 2, 3, 4, 5, 6),List(one, two, three, four, five, six),List(1, 2, 3, 4, 5, 6),List(1.0, 2.0, 3.0, 4.0, 5.0, 6.0),List(1, 2, 3, 4, 5, 6),List(true, false, true, false, true, false))
      *    }}}
      *
      *  @tparam A1       the type of the first member of the Tuple6 element
      *  @tparam A2       the type of the second member of the Tuple6 element
      *  @tparam A3       the type of the third member of the Tuple6 element
      *  @tparam A4       the type of the fourth member of the Tuple6 element
      *  @tparam A5       the type of the fifth member of the Tuple6 element
      *  @tparam A6       the type of the sixth member of the Tuple6 element
      *  @param asTuple6  an implicit conversion which asserts that the element type
      *                   of this iterable is a Tuple6.
      *  @return          a Tuple6 of iterables, containing the first, second, third,
      *                   forth, fifth and sixth member of each Tuple6 element of this iterable.
      *  @since 0.3.2
      */
    def unzip6[A1, A2, A3, A4, A5, A6](implicit asTuple6: A => (A1, A2, A3, A4, A5, A6)): (CC[A1], CC[A2], CC[A3], CC[A4], CC[A5], CC[A6]) = (
      iterableOps.iterableFactory.from(new View.Map[A, A1](iterableOps, asTuple6(_)._1)),
      iterableOps.iterableFactory.from(new View.Map[A, A2](iterableOps, asTuple6(_)._2)),
      iterableOps.iterableFactory.from(new View.Map[A, A3](iterableOps, asTuple6(_)._3)),
      iterableOps.iterableFactory.from(new View.Map[A, A4](iterableOps, asTuple6(_)._4)),
      iterableOps.iterableFactory.from(new View.Map[A, A5](iterableOps, asTuple6(_)._5)),
      iterableOps.iterableFactory.from(new View.Map[A, A6](iterableOps, asTuple6(_)._6))
    )

  }

  implicit class MapOpsExtension[K, V, CC[_, _] <: IterableOps[_, Any, _], C](val mapOps: MapOps[K, V, CC, C])
      extends AnyVal {

    /**
      * Merges this map with `another` using function `f`
      * to calculate result value for duplicate keys.
      * Value from this map is the first argument to `f`
      *
      * Returns a new map with all keys present in this and `another` maps
      * The type of result map is same as this map
      *
      * Example:
      * {{{
      *   scala> Map("1" -> 1, "2" -> 2).mergedWith(Map("1" -> 1, "2" -> 2))(_ + _)
      *   res1: scala.collection.immutable.Map[String,Int] = Map(1 -> 2, 2 -> 4)
      * }}}
      *
      * @since 0.1.2
      *
      */
    def mergedWith(another: scala.collection.Map[K, V])(f: (V, V) => V): CC[K, V] =
      if (another.isEmpty) mapOps.mapFactory.from(mapOps)
      else {
        val mapBuilder = mapOps.mapFactory.newBuilder[K,V]
        another.foreach {
          case (k, v) =>
            mapOps.get(k) match {
              case Some(ev) => mapBuilder += k -> f(ev, v)
              case _ => mapBuilder += k -> v
            }
        }
        mapBuilder.result()
      }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy