|
| 1 | +/* |
| 2 | + * Copyright (c) 2018 Luka Jacobowitz |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + * you may not use this file except in compliance with the License. |
| 6 | + * You may obtain a copy of the License at |
| 7 | + * |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | + * |
| 10 | + * Unless required by applicable law or agreed to in writing, software |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + * See the License for the specific language governing permissions and |
| 14 | + * limitations under the License. |
| 15 | + */ |
| 16 | + |
| 17 | +package cats |
| 18 | +package data |
| 19 | + |
| 20 | +import cats.instances.sortedSet._ |
| 21 | +import cats.kernel._ |
| 22 | +import cats.{Always, Eq, Eval, Foldable, Later, Now, Reducible, SemigroupK, Show} |
| 23 | + |
| 24 | +import scala.collection.immutable._ |
| 25 | + |
| 26 | +final class NonEmptySet[A] private (val set: SortedSet[A]) extends AnyVal { |
| 27 | + |
| 28 | + private implicit def ordering: Ordering[A] = set.ordering |
| 29 | + private implicit def order: Order[A] = Order.fromOrdering |
| 30 | + |
| 31 | + def +(a: A): NonEmptySet[A] = new NonEmptySet(set + a) |
| 32 | + def ++(as: NonEmptySet[A]): NonEmptySet[A] = concat(as) |
| 33 | + |
| 34 | + def concat(as: NonEmptySet[A]): NonEmptySet[A] = new NonEmptySet(set ++ as.set) |
| 35 | + |
| 36 | + def -(a: A): SortedSet[A] = set - a |
| 37 | + def map[B: Order](f: A ⇒ B): NonEmptySet[B] = |
| 38 | + new NonEmptySet(SortedSet(set.map(f).to: _*)(Order[B].toOrdering)) |
| 39 | + |
| 40 | + def toNonEmptyList: NonEmptyList[A] = NonEmptyList.fromListUnsafe(set.toList) |
| 41 | + |
| 42 | + def head: A = set.head |
| 43 | + def tail: SortedSet[A] = set.tail |
| 44 | + def last: A = set.last |
| 45 | + |
| 46 | + def apply(a: A): Boolean = set(a) |
| 47 | + def contains(a: A): Boolean = set.contains(a) |
| 48 | + |
| 49 | + |
| 50 | + def union(as: NonEmptySet[A]): NonEmptySet[Ag] = new NonEmptySet(set.union(as.toSet)) |
| 51 | + def size: Int = set.size |
| 52 | + def forall(p: A ⇒ Boolean): Boolean = set.forall(p) |
| 53 | + def exists(f: A ⇒ Boolean): Boolean = set.exists(f) |
| 54 | + def find(f: A ⇒ Boolean): Option[A] = set.find(f) |
| 55 | + def collect[B](pf: PartialFunction[A, B]): Set[B] = set.collect(pf) |
| 56 | + def filter(p: A ⇒ Boolean): SortedSet[A] = set.filter(p) |
| 57 | + def filterNot(p: A ⇒ Boolean): SortedSet[A] = filter(t => !p(t)) |
| 58 | + |
| 59 | + |
| 60 | + /** |
| 61 | + * Left-associative fold using f. |
| 62 | + */ |
| 63 | + def foldLeft[B](b: B)(f: (B, A) => B): B = |
| 64 | + set.foldLeft(b)(f) |
| 65 | + |
| 66 | + /** |
| 67 | + * Right-associative fold using f. |
| 68 | + */ |
| 69 | + def foldRight[B](lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = |
| 70 | + Foldable[SortedSet].foldRight(set, lb)(f) |
| 71 | + |
| 72 | + /** |
| 73 | + * Left-associative reduce using f. |
| 74 | + */ |
| 75 | + def reduceLeft(f: (A, A) => A): A = |
| 76 | + set.reduceLeft(f) |
| 77 | + |
| 78 | + def reduceLeftTo[B](f: A => B)(g: (B, A) => B): B = { |
| 79 | + tail.foldLeft(f(head))((b, a) => g(b, a)) |
| 80 | + } |
| 81 | + |
| 82 | + /** |
| 83 | + * Left-associative reduce using f. |
| 84 | + */ |
| 85 | + def reduceRight(f: (A, Eval[A]) => Eval[A]): Eval[A] = |
| 86 | + reduceRightTo(identity)(f) |
| 87 | + |
| 88 | + def reduceRightTo[B](f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[B] = |
| 89 | + Always((head, tail)).flatMap { case (a, ga) => |
| 90 | + Foldable[SortedSet].reduceRightToOption(ga)(f)(g).flatMap { |
| 91 | + case Some(b) => g(a, Now(b)) |
| 92 | + case None => Later(f(a)) |
| 93 | + } |
| 94 | + } |
| 95 | + |
| 96 | + /** |
| 97 | + * Reduce using the Semigroup of A |
| 98 | + */ |
| 99 | + def reduce[AA >: A](implicit S: Semigroup[AA]): AA = |
| 100 | + S.combineAllOption(set).get |
| 101 | + |
| 102 | + def toSet: SortedSet[A] = set |
| 103 | + |
| 104 | + /** |
| 105 | + * Typesafe stringification method. |
| 106 | + * |
| 107 | + * This method is similar to .toString except that it stringifies |
| 108 | + * values according to Show[_] instances, rather than using the |
| 109 | + * universal .toString method. |
| 110 | + */ |
| 111 | + def show(implicit A: Show[A]): String = |
| 112 | + s"NonEmpty${Show[SortedSet[A]].show(set)}" |
| 113 | + |
| 114 | + /** |
| 115 | + * Typesafe equality operator. |
| 116 | + * |
| 117 | + * This method is similar to == except that it only allows two |
| 118 | + * NonEmptySet[A] values to be compared to each other, and uses |
| 119 | + * equality provided by Eq[_] instances, rather than using the |
| 120 | + * universal equality provided by .equals. |
| 121 | + */ |
| 122 | + def ===(that: NonEmptySet[A]): Boolean = |
| 123 | + Eq[SortedSet[A]].eqv(set, that.toSet) |
| 124 | + |
| 125 | + def length: Int = size |
| 126 | + |
| 127 | + /** |
| 128 | + * Zips this `NonEmptySet` with another `NonEmptySet` and applies a function for each pair of elements. |
| 129 | + * |
| 130 | + * {{{ |
| 131 | + * scala> import cats.data.NonEmptySet |
| 132 | + * scala> val as = NonEmptySet.of(1, 2, 3) |
| 133 | + * scala> val bs = NonEmptySet.of("A", "B", "C") |
| 134 | + * scala> as.zipWith(bs)(_ + _) |
| 135 | + * res0: cats.data.NonEmptySet[String] = NonEmptySet(1A, 2B, 3C) |
| 136 | + * }}} |
| 137 | + */ |
| 138 | + def zipWith[B, C: Order](b: NonEmptySet[B])(f: (A, B) => C): NonEmptySet[C] = |
| 139 | + new NonEmptySet(SortedSet((set, b.toSet).zipped.map(f).to: _*)(Order[C].toOrdering)) |
| 140 | + |
| 141 | + def zipWithIndex: NonEmptySet[(A, Int)] = |
| 142 | + new NonEmptySet(set.zipWithIndex) |
| 143 | +} |
| 144 | + |
| 145 | +private[data] sealed abstract class NonEmptySetInstances { |
| 146 | + implicit val catsDataInstancesForNonEmptySet: SemigroupK[NonEmptySet] with Reducible[NonEmptySet] = |
| 147 | + new SemigroupK[NonEmptySet] with Reducible[NonEmptySet] { |
| 148 | + |
| 149 | + def combineK[A](a: NonEmptySet[A], b: NonEmptySet[A]): NonEmptySet[A] = |
| 150 | + a ++ b |
| 151 | + |
| 152 | + override def size[A](fa: NonEmptySet[A]): Long = fa.length.toLong |
| 153 | + |
| 154 | + override def reduceLeft[A](fa: NonEmptySet[A])(f: (A, A) => A): A = |
| 155 | + fa.reduceLeft(f) |
| 156 | + |
| 157 | + override def reduce[A](fa: NonEmptySet[A])(implicit A: Semigroup[A]): A = |
| 158 | + fa.reduce |
| 159 | + |
| 160 | + def reduceLeftTo[A, B](fa: NonEmptySet[A])(f: A => B)(g: (B, A) => B): B = fa.reduceLeftTo(f)(g) |
| 161 | + |
| 162 | + def reduceRightTo[A, B](fa: NonEmptySet[A])(f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[B] = |
| 163 | + fa.reduceRightTo(f)(g) |
| 164 | + |
| 165 | + override def foldLeft[A, B](fa: NonEmptySet[A], b: B)(f: (B, A) => B): B = |
| 166 | + fa.foldLeft(b)(f) |
| 167 | + |
| 168 | + override def foldRight[A, B](fa: NonEmptySet[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = |
| 169 | + fa.foldRight(lb)(f) |
| 170 | + |
| 171 | + override def foldMap[A, B](fa: NonEmptySet[A])(f: A => B)(implicit B: Monoid[B]): B = |
| 172 | + B.combineAll(fa.toSet.iterator.map(f)) |
| 173 | + |
| 174 | + override def fold[A](fa: NonEmptySet[A])(implicit A: Monoid[A]): A = |
| 175 | + fa.reduce |
| 176 | + |
| 177 | + override def find[A](fa: NonEmptySet[A])(f: A => Boolean): Option[A] = |
| 178 | + fa.find(f) |
| 179 | + |
| 180 | + override def forall[A](fa: NonEmptySet[A])(p: A => Boolean): Boolean = |
| 181 | + fa.forall(p) |
| 182 | + |
| 183 | + override def exists[A](fa: NonEmptySet[A])(p: A => Boolean): Boolean = |
| 184 | + fa.exists(p) |
| 185 | + |
| 186 | + override def toList[A](fa: NonEmptySet[A]): List[A] = fa.toSet.toList |
| 187 | + |
| 188 | + override def toNonEmptyList[A](fa: NonEmptySet[A]): NonEmptyList[A] = |
| 189 | + NonEmptyList(fa.head, fa.tail.toList) |
| 190 | + } |
| 191 | + |
| 192 | + implicit def catsDataEqForNonEmptySet[A: Order]: Eq[NonEmptySet[A]] = |
| 193 | + new Eq[NonEmptySet[A]]{ |
| 194 | + def eqv(x: NonEmptySet[A], y: NonEmptySet[A]): Boolean = x === y |
| 195 | + } |
| 196 | + |
| 197 | + implicit def catsDataShowForNonEmptySet[A](implicit A: Show[A]): Show[NonEmptySet[A]] = |
| 198 | + Show.show[NonEmptySet[A]](_.show) |
| 199 | + |
| 200 | + implicit def catsDataBandForNonEmptySet[A]: Band[NonEmptySet[A]] = new Band[NonEmptySet[A]] { |
| 201 | + def combine(x: NonEmptySet[A], y: NonEmptySet[A]): NonEmptySet[A] = x ++ y |
| 202 | + } |
| 203 | +} |
| 204 | + |
| 205 | +object NonEmptySet extends NonEmptySetInstances { |
| 206 | + def fromSet[A: Order](as: SortedSet[A]): Option[NonEmptySet[A]] = |
| 207 | + if (as.nonEmpty) Option(new NonEmptySet(as)) else None |
| 208 | + |
| 209 | + def fromSetUnsafe[A: Order](set: SortedSet[A]): NonEmptySet[A] = |
| 210 | + if (set.nonEmpty) new NonEmptySet(set) |
| 211 | + else throw new IllegalArgumentException("Cannot create NonEmptySet from empty set") |
| 212 | + |
| 213 | + |
| 214 | + def of[A: Order](a: A, as: A*): NonEmptySet[A] = |
| 215 | + new NonEmptySet(SortedSet(a)(Order[A].toOrdering) ++ SortedSet(as: _*)(Order[A].toOrdering) + a) |
| 216 | + def apply[A: Order](head: A, tail: SortedSet[A]): NonEmptySet[A] = new NonEmptySet(SortedSet(head)(Order[A].toOrdering) ++ tail) |
| 217 | + def one[A: Order](a: A): NonEmptySet[A] = new NonEmptySet(SortedSet(a)(Order[A].toOrdering)) |
| 218 | +} |
0 commit comments