fm.common.rich.RichTraversableOnce.scala Maven / Gradle / Ivy
/*
* Copyright 2014 Frugal Mechanic (http://frugalmechanic.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package fm.common.rich
import fm.common.{Normalize, TraversableOnce}
import scala.collection.{immutable, mutable}
import scala.concurrent.{ExecutionContext, Future}
final class RichTraversableOnce[A, COL](val self: COL)(implicit toTraversableOnce: COL => TraversableOnce[A]) {
private def selfTraversable: TraversableOnce[A] = toTraversableOnce(self)
def foreachWithIndex[U](f: (A, Int) => U): Unit = {
var i: Int = 0
selfTraversable.foreach{ (elem: A) =>
f(elem, i)
i += 1
}
}
def foreachWithLongIndex[U](f: (A, Long) => U): Unit = {
var i: Long = 0L
selfTraversable.foreach{ (elem: A) =>
f(elem, i)
i += 1
}
}
def minOption[B >: A](implicit ord: Ordering[B]): Option[A] = {
selfTraversable.reduceLeftOption{ (x: A, y: A) => if (ord.lteq(x, y)) x else y }
}
def maxOption[B >: A](implicit ord: Ordering[B]): Option[A] = {
selfTraversable.reduceLeftOption{ (x: A, y: A) => if (ord.gteq(x, y)) x else y }
}
/**
* Same as mkString except if the TraversableOnce is empty then an empty
* string is returned instead of the start and end params.
*/
def mkStringOrBlank(start: String, sep: String, end: String): String = {
var sb: java.lang.StringBuilder = null
for (x <- selfTraversable) {
if (sb == null) {
sb = new java.lang.StringBuilder()
sb.append(start)
} else {
sb.append(sep)
}
sb.append(x.toString)
}
if (null != sb) {
sb.append(end)
sb.toString
} else ""
}
/**
* Like the normal sortBy but will cache the result of calling f on each element
* (i.e. f is only called once for each element). This is useful when f is an
* expensive function and not just a single field access on the element.
*
* If this is call on something that isn't already an IndexedSeq then it will be
* automatically converted before sorting.
*/
def sortByCached[K](f: A => K)(implicit ord: Ordering[K]): IndexedSeq[A] = {
val orig: collection.IndexedSeq[A] = toVector
val len: Int = orig.length
// Yes, this currently has to be an Array[Integer] because the only Arrays.sort method that takes
// a comparator requires an Array[Object] to work.
val idxs: Array[Integer] = new Array(len) // Our indexes into the orig array -- this is what we will sort
val keys: Array[AnyRef] = new Array(len) // Our cached keys that we will base on sorting on
var i: Int = 0
// One pass to populate our idxs and keys arrays
while (i < len) {
idxs(i) = i
keys(i) = f(orig(i)).asInstanceOf[AnyRef]
i += 1
}
// Perform the sorting which will give us an idxs array that has the sorted indexes for the result
java.util.Arrays.sort(idxs.asInstanceOf[Array[Object]], new java.util.Comparator[Integer]{
def compare(a: Integer, b: Integer): Int = ord.compare(keys(a).asInstanceOf[K], keys(b).asInstanceOf[K])
}.asInstanceOf[java.util.Comparator[Object]])
val b: mutable.Builder[A, Vector[A]] = Vector.newBuilder[A]
b.sizeHint(len)
i = 0
while (i < len) {
b += orig(idxs(i))
i += 1
}
b.result()
}
/**
* Like groupBy but returns the count of each key instead of the actual values
*/
def countBy[K](f: A => K): Map[K,Int] = {
var m: immutable.HashMap[K, Int] = immutable.HashMap.empty
for (value <- selfTraversable) {
val key: K = f(value)
m = m.updated(key, m.getOrElse(key, 0) + 1)
}
m
}
/**
* Collapse records that are next to each other by a key
*
* e.g.: This groups evens and odds together:
*
* scala> Vector(2,4,6,1,3,2,5,7).collapseBy{ _ % 2 == 0 }
* res1: IndexedSeq[(Boolean, Seq[Int])] = Vector((true,Vector(2, 4, 6)), (false,Vector(1, 3)), (true,Vector(2)), (false,Vector(5, 7)))
*/
def collapseBy[K](f: A => K): IndexedSeq[(K,Seq[A])] = {
val resBuilder: mutable.Builder[(K, Seq[A]), Vector[(K, Seq[A])]] = Vector.newBuilder
var isFirst: Boolean = true
var curKey: K = null.asInstanceOf[K]
var curBuilder: mutable.Builder[A, Vector[A]] = null
selfTraversable.foreach { (a: A) =>
val key: K = f(a)
if (isFirst) {
curKey = key
curBuilder = Vector.newBuilder[A]
isFirst = false
}
if (key != curKey) {
resBuilder += ((curKey, curBuilder.result()))
curKey = key
curBuilder = Vector.newBuilder
}
curBuilder += a
}
if (!isFirst) resBuilder += ((curKey, curBuilder.result()))
resBuilder.result()
}
/**
* Like groupBy but only allows a single value per key
*/
def uniqueGroupBy[K](f: A => K): immutable.HashMap[K, A] = {
var m: immutable.HashMap[K, A] = immutable.HashMap.empty
for (x <- selfTraversable) {
val key: K = f(x)
require(!m.contains(key), s"Map already contains key: $key Existing Value: ${m(key)} Trying to add value: ${x}")
m = m.updated(key, x)
}
m
}
/**
* A combination of map + find that returns the first Some that is found
* after applying the map operation.
*/
def findMapped[B](f: A => Option[B]): Option[B] = {
selfTraversable.foreach{ (a: A) =>
val b: Option[B] = f(a)
if (b.isDefined) return b
}
None
}
/**
* Like findMapped except works with Futures. Executes the futures one at a time
* until one with a defined result is found.
*/
def findMappedFuture[B](f: A => Future[Option[B]])(implicit ec: ExecutionContext): Future[Option[B]] = {
selfTraversable.foldLeft[Future[Option[B]]](Future.successful(None)){ (prev: Future[Option[B]], next: A) =>
prev.flatMap{ (res: Option[B]) =>
if (res.isDefined) Future.successful(res) else f(next)
}
}
}
/**
* Returns a Vector of this Iterable (if it's not already a Vector)
*/
def toVector: Vector[A] = {
self match {
case vector: Vector[A] => vector
case _ =>
val b = Vector.newBuilder[A]
//b.sizeHint(self)
selfTraversable.foreach{ b += _ }
b.result()
}
}
/**
* Like .toMap but creates an immutable.HashMap
*/
def toHashMap[K, V](implicit ev: A <:< (K, V)): immutable.HashMap[K, V] = {
val b: mutable.Builder[(K,V), immutable.HashMap[K,V]] = immutable.HashMap.newBuilder
for (x <- selfTraversable) b += x
b.result()
}
/**
* Same as .toHashMap but ensures there are no duplicate keys
*/
def toUniqueHashMap[K, V](implicit ev: A <:< (K, V)): immutable.HashMap[K, V] = {
var m: immutable.HashMap[K, V] = immutable.HashMap.empty
for (x <- selfTraversable) {
val key: K = x._1
require(!m.contains(key), s"RichTraversableOnce.toUniqueHashMap - Map already contains key: $key Existing Value: ${m(key)} Trying to add value: ${x._2}")
m += x
}
m
}
// /**
// * Same as .toHashMap but ensures there are no duplicate keys BUT will ignore duplicate keys if they have the same values
// */
// def toUniqueHashMapIgnoreDupes[K, V](implicit ev: A <:< (K, V)): immutable.HashMap[K, V] = toUniqueHashMapImpl(false)
//
// /**
// * Same as .toHashMap but ensures there are no duplicate keys
// */
// def toUniqueHashMap[K, V](implicit ev: A <:< (K, V)): immutable.HashMap[K, V] = toUniqueHashMapImpl(true)
//
// /**
// * Same as .toHashMap but ensures there are no duplicate keys
// */
// private def toUniqueHashMapImpl[K, V](strict: Boolean)(implicit ev: A <:< (K, V)): immutable.HashMap[K, V] = {
// var m = immutable.HashMap.empty[K, V]
//
// for (x <- self) {
// val key: K = x._1
//
// if (m.contains(key) && (strict || m(key) != x._2)) throw new IllegalArgumentException(s"RichTraversableOnce.toUniqueHashMap - Map already contains key: $key Existing Value: ${m(key)} Trying to add value: ${x._2}")
//
// m += x
// }
//
// m
// }
/**
* Alias of uniqueGroupBy
*/
def toUniqueHashMapUsing[K](f: A => K): immutable.HashMap[K, A] = uniqueGroupBy(f)
/**
* Same as toUniqueHashMap but allows you to specify transform functions for the key and value
*/
def toUniqueHashMapWithTransforms[K, V, K2, V2](keyTransform: K => K2, valueTransform: V => V2)(implicit ev: A <:< (K, V)): immutable.HashMap[K2, V2] = {
var m: immutable.HashMap[K2, V2] = immutable.HashMap.empty
for (x <- selfTraversable) {
val key: K2 = keyTransform(x._1)
val value: V2 = valueTransform(x._2)
require(!m.contains(key), s"RichTraversableOnce.toUniqueHashMap - Map already contains key: $key Existing Value: ${m(key)} Trying to add value: $value")
m += ((key, value))
}
m
}
private def identityTransform[T](t: T): T = t
/**
* Same as toUniqueHashMap but allows you to specify a transform function for the key
*/
def toUniqueHashMapWithKeyTransform[K, V, K2](keyTransform: K => K2)(implicit ev: A <:< (K, V)): immutable.HashMap[K2, V] = {
toUniqueHashMapWithTransforms(keyTransform, identityTransform[V])
}
/**
* Like .groupBy but gives you an immutable.HashMap[K, IndexedSeq[A]]
*/
def toMultiValuedMapUsing[K](toKey: A => K): immutable.HashMap[K, IndexedSeq[A]] = {
var m: immutable.HashMap[K, Vector[A]] = immutable.HashMap.empty
for (value <- selfTraversable) {
val key: K = toKey(value)
val values: Vector[A] = m.get(key) match {
case Some(existing) => existing :+ value
case None => Vector(value)
}
m = m.updated(key, values)
}
m
}
/**
* Like .groupBy but gives you an immutable.HashMap[K, IndexedSeq[A]]
*/
def toMultiValuedMapUsingKeys[K](toKeys: A => TraversableOnce[K]): immutable.HashMap[K, IndexedSeq[A]] = {
var m: immutable.HashMap[K, Vector[A]] = immutable.HashMap.empty
for (value <- selfTraversable; key <- toKeys(value)) {
val values: Vector[A] = m.get(key) match {
case Some(existing) => existing :+ value
case None => Vector(value)
}
m = m.updated(key, values)
}
m
}
/**
* Like .toHashMap except allows multiple values per key
*
* TODO: Change this to IndexedSeq so we can switch to ImmutableArray (if we want to)
*/
def toMultiValuedMap[K, V](implicit ev: A <:< (K, V)): immutable.HashMap[K, Vector[V]] = {
var m: immutable.HashMap[K, Vector[V]] = immutable.HashMap.empty
for (x <- selfTraversable) {
val key: K = x._1
val value: V = x._2
val values: Vector[V] = m.get(key) match {
case Some(existing) => existing :+ value
case None => Vector(value)
}
m = m.updated(key, values)
}
m
}
/**
* Same as toMultiValuedMap but allows you to specify transform functions for the key and value
*
* TODO: Change this to IndexedSeq so we can switch to ImmutableArray (if we want to)
*/
def toMultiValuedMapWithTransforms[K, V, K2, V2](keyTransform: K => K2, valueTransform: V => V2)(implicit ev: A <:< (K, V)): immutable.HashMap[K2, Vector[V2]] = {
var m: immutable.HashMap[K2, Vector[V2]] = immutable.HashMap.empty
for (x <- selfTraversable) {
val key: K2 = keyTransform(x._1)
val value: V2 = valueTransform(x._2)
val values: Vector[V2] = m.get(key) match {
case Some(existing) => existing :+ value
case None => Vector(value)
}
m = m.updated(key, values)
}
m
}
/**
* Same as toMultiValuedMap but allows you to specify a transform function for the key
*
* TODO: Change this to IndexedSeq so we can switch to ImmutableArray (if we want to)
*/
def toMultiValuedMapWithKeyTransform[K, V, K2](keyTransform: K => K2)(implicit ev: A <:< (K, V)): immutable.HashMap[K2, Vector[V]] = {
toMultiValuedMapWithTransforms(keyTransform, identityTransform[V])
}
/**
* Same as .toMap but ensures there are no duplicate keys
*/
def toUniqueMap[K, V](implicit ev: A <:< (K, V)): immutable.HashMap[K, V] = toUniqueHashMap(ev)
/**
* Like .toSet but creates an immutable.HashSet
*/
def toHashSet: immutable.HashSet[A] = self match {
case hashSet: immutable.HashSet[A] => hashSet
case _ =>
val builder = immutable.HashSet.newBuilder[A]
selfTraversable.foreach{ builder += _ }
builder.result()
}
/**
* Like .toHashSet but makes sure there are no duplicates
*/
def toUniqueHashSet: immutable.HashSet[A] = self match {
case hashSet: immutable.HashSet[_] => hashSet.asInstanceOf[immutable.HashSet[A]]
case _ =>
var set = immutable.HashSet.empty[A]
for (x <- selfTraversable) {
require(!set.contains(x), "RichTraversableOnce.toUniqueHashSet - HashSet already contains value: "+x)
set += x
}
set
}
/**
* Like .toSet but makes sure there are no duplicates
*/
def toUniqueSet: immutable.Set[A] = self match {
case hashSet: immutable.Set[_] => hashSet.asInstanceOf[immutable.Set[A]]
case _ =>
var set = immutable.Set.empty[A]
for (x <- selfTraversable) {
require(!set.contains(x), "RichTraversableOnce.toUniqueSet - HashSet already contains value: "+x)
set += x
}
set
}
/**
* Like .toSet but returns a scala.collection.immutable.SortedSet instead
*/
def toSortedSet(implicit ord: Ordering[A]): immutable.SortedSet[A] = self match {
case sortedSet: immutable.HashSet[_] => sortedSet.asInstanceOf[immutable.SortedSet[A]]
case _ =>
val builder = immutable.SortedSet.newBuilder[A]
selfTraversable.foreach{ builder += _ }
builder.result()
}
def distinctUsing[B](f: A => B)(implicit ord: Ordering[B]): IndexedSeq[A] = {
val seen: mutable.HashSet[B] = new mutable.HashSet[B]
val res: mutable.Builder[A, Vector[A]] = Vector.newBuilder
selfTraversable.foreach { (v: A) =>
val key: B = f(v)
if (!seen.contains(key)) {
res += v
seen += key
}
}
res.result()
}
def toUniqueLowerAlphaNumericMap[V](implicit ev: A <:< (String,V)): immutable.HashMap[String, V] = {
toUniqueHashMapWithKeyTransform(Normalize.lowerAlphanumeric)
}
def toUniqueURLNameMap[V](implicit ev: A <:< (String,V)): immutable.HashMap[String, V] = {
toUniqueHashMapWithKeyTransform(Normalize.urlName)
}
def toMultiValuedLowerAlphaNumericMap[V](implicit ev: A <:< (String,V)): immutable.HashMap[String, IndexedSeq[V]] = {
toMultiValuedMapWithKeyTransform(Normalize.lowerAlphanumeric)
}
def toMultiValuedURLNameMap[V](implicit ev: A <:< (String,V)): immutable.HashMap[String, IndexedSeq[V]] = {
toMultiValuedMapWithKeyTransform(Normalize.urlName)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy