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

com.twitter.algebird.Semigroup.scala Maven / Gradle / Ivy

/*
Copyright 2012 Twitter, Inc.

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 com.twitter.algebird

import java.lang.{ Integer => JInt, Short => JShort, Long => JLong, Float => JFloat, Double => JDouble, Boolean => JBool }
import java.util.{ List => JList, Map => JMap }

import scala.collection.mutable.{ Map => MMap }
import scala.collection.{ Map => ScMap }
import scala.annotation.{ implicitNotFound, tailrec }

/**
 * Semigroup:
 *   This is a class with a plus method that is associative: a+(b+c) = (a+b)+c
 */
@implicitNotFound(msg = "Cannot find Semigroup type class for ${T}")
trait Semigroup[@specialized(Int, Long, Float, Double) T] extends java.io.Serializable {
  def plus(l: T, r: T): T
  /**
   * override this if there is a faster way to do this sum than reduceLeftOption on plus
   */
  def sumOption(iter: TraversableOnce[T]): Option[T] =
    iter.reduceLeftOption { plus(_, _) }
}

// For Java interop so they get the default sumOption
abstract class AbstractSemigroup[T] extends Semigroup[T]

/**
 * Either semigroup is useful for error handling.
 * if everything is correct, use Right (it's right, get it?), if something goes
 * wrong, use Left.  plus does the normal thing for plus(Right, Right), or plus(Left, Left),
 * but if exactly one is Left, we return that value (to keep the error condition).
 * Typically, the left value will be a string representing the errors.
 */
class EitherSemigroup[L, R](implicit semigroupl: Semigroup[L], semigroupr: Semigroup[R]) extends Semigroup[Either[L, R]] {

  override def plus(l: Either[L, R], r: Either[L, R]) = {
    if (l.isLeft) {
      // l is Left, r may or may not be:
      if (r.isRight) {
        //Avoid the allocation:
        l
      } else {
        //combine the lefts:
        Left(semigroupl.plus(l.left.get, r.left.get))
      }
    } else if (r.isLeft) {
      //l is not a Left value, so just return right:
      r
    } else {
      //both l and r are Right values:
      Right(semigroupr.plus(l.right.get, r.right.get))
    }
  }
}

object Semigroup extends GeneratedSemigroupImplicits with ProductSemigroups {
  // This pattern is really useful for typeclasses
  def plus[T](l: T, r: T)(implicit semi: Semigroup[T]) = semi.plus(l, r)
  // Left sum: (((a + b) + c) + d)
  def sumOption[T](iter: TraversableOnce[T])(implicit sg: Semigroup[T]): Option[T] =
    sg.sumOption(iter)

  def from[T](associativeFn: (T, T) => T): Semigroup[T] = new Semigroup[T] { def plus(l: T, r: T) = associativeFn(l, r) }

  /**
   * Same as v + v + v .. + v (i times in total)
   * requires i > 0, wish we had PositiveBigInt as a class
   */
  def intTimes[T](i: BigInt, v: T)(implicit sg: Semigroup[T]): T = {
    require(i > 0, "Cannot do non-positive products with a Semigroup, try Monoid/Group.intTimes")
    intTimesRec(i - 1, v, 0, (v, Vector[T]()))
  }

  @tailrec
  private def intTimesRec[T](i: BigInt, v: T, pow: Int, vaccMemo: (T, Vector[T]))(implicit sg: Semigroup[T]): T = {
    if (i == 0) {
      vaccMemo._1
    } else {
      /* i2 = i % 2
       * 2^pow(i*v) + acc == 2^(pow+1)((i/2)*v) + (acc + 2^pow i2 * v)
       */
      val half = i / 2
      val rem = i % 2
      val newAccMemo = if (rem == 0) vaccMemo else {
        val (res, newMemo) = timesPow2(pow, v, vaccMemo._2)
        (sg.plus(vaccMemo._1, res), newMemo)
      }
      intTimesRec(half, v, pow + 1, newAccMemo)
    }
  }

  // Returns (2^power) * v = (2^(power - 1) v + 2^(power - 1) v)
  private def timesPow2[T](power: Int, v: T, memo: Vector[T])(implicit sg: Semigroup[T]): (T, Vector[T]) = {
    val size = memo.size
    require(power >= 0, "power cannot be negative")
    if (power == 0) {
      (v, memo)
    } else if (power <= size) {
      (memo(power - 1), memo)
    } else {
      var item = if (size == 0) v else memo.last
      var pow = size
      var newMemo = memo
      while (pow < power) {
        // x = 2*x
        item = sg.plus(item, item)
        pow += 1
        newMemo = newMemo :+ item
      }
      (item, newMemo)
    }
  }

  implicit val nullSemigroup: Semigroup[Null] = NullGroup
  implicit val unitSemigroup: Semigroup[Unit] = UnitGroup
  implicit val boolSemigroup: Semigroup[Boolean] = BooleanField
  implicit val jboolSemigroup: Semigroup[JBool] = JBoolField
  implicit val intSemigroup: Semigroup[Int] = IntRing
  implicit val jintSemigroup: Semigroup[JInt] = JIntRing
  implicit val shortSemigroup: Semigroup[Short] = ShortRing
  implicit val jshortSemigroup: Semigroup[JShort] = JShortRing
  implicit val longSemigroup: Semigroup[Long] = LongRing
  implicit val bigIntSemigroup: Semigroup[BigInt] = BigIntRing
  implicit val jlongSemigroup: Semigroup[JLong] = JLongRing
  implicit val floatSemigroup: Semigroup[Float] = FloatField
  implicit val jfloatSemigroup: Semigroup[JFloat] = JFloatField
  implicit val doubleSemigroup: Semigroup[Double] = DoubleField
  implicit val jdoubleSemigroup: Semigroup[JDouble] = JDoubleField
  implicit val stringSemigroup: Semigroup[String] = StringMonoid
  implicit def optionSemigroup[T: Semigroup]: Semigroup[Option[T]] = new OptionMonoid[T]
  implicit def listSemigroup[T]: Semigroup[List[T]] = new ListMonoid[T]
  implicit def seqSemigroup[T]: Semigroup[Seq[T]] = new SeqMonoid[T]
  implicit def indexedSeqSemigroup[T: Semigroup]: Semigroup[IndexedSeq[T]] = new IndexedSeqSemigroup[T]
  implicit def jlistSemigroup[T]: Semigroup[JList[T]] = new JListMonoid[T]
  implicit def setSemigroup[T]: Semigroup[Set[T]] = new SetMonoid[T]
  implicit def mapSemigroup[K, V: Semigroup]: Semigroup[Map[K, V]] = new MapMonoid[K, V]
  implicit def scMapSemigroup[K, V: Semigroup]: Semigroup[ScMap[K, V]] = new ScMapMonoid[K, V]
  implicit def jmapSemigroup[K, V: Semigroup]: Semigroup[JMap[K, V]] = new JMapMonoid[K, V]
  implicit def eitherSemigroup[L: Semigroup, R: Semigroup]: Semigroup[Either[L, R]] = new EitherSemigroup[L, R]
  implicit def function1Semigroup[T]: Semigroup[Function1[T, T]] = new Function1Monoid[T]
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy