Skip to content

Commit 61f13d0

Browse files
author
Luka Jacobowitz
committed
Add NonEmptySet
1 parent 3774a19 commit 61f13d0

File tree

3 files changed

+450
-0
lines changed

3 files changed

+450
-0
lines changed
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
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+
}

laws/src/main/scala/cats/laws/discipline/Arbitrary.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ object arbitrary extends ArbitraryInstances0 {
5151
implicit def catsLawsCogenForNonEmptyVector[A](implicit A: Cogen[A]): Cogen[NonEmptyVector[A]] =
5252
Cogen[Vector[A]].contramap(_.toVector)
5353

54+
implicit def catsLawsArbitraryForNonEmptySet[A: Order](implicit A: Arbitrary[A]): Arbitrary[NonEmptySet[A]] =
55+
Arbitrary(implicitly[Arbitrary[SortedSet[A]]].arbitrary.flatMap(fa =>
56+
A.arbitrary.map(a => NonEmptySet(a, fa))))
57+
58+
implicit def catsLawsCogenForNonEmptySet[A: Order: Cogen]: Cogen[NonEmptySet[A]] =
59+
Cogen[SortedSet[A]].contramap(_.toSet)
60+
5461
implicit def catsLawsArbitraryForZipVector[A](implicit A: Arbitrary[A]): Arbitrary[ZipVector[A]] =
5562
Arbitrary(implicitly[Arbitrary[Vector[A]]].arbitrary.map(v => new ZipVector(v)))
5663

0 commit comments

Comments
 (0)