NonEmptySet and NonEmptyMap¶
Four types hold at least one element, as NonEmptyVector does for sequences:
| Type | Holds | Order | Total head and last |
|---|---|---|---|
NonEmptySet<A> |
a HashSet |
not defined | no |
NonEmptySortedSet<A> |
a TreeSet |
the comparator's | yes |
NonEmptyMap<K, V> |
a HashMap |
not defined | no |
NonEmptySortedMap<K, V> |
a TreeMap |
the comparator's, on the keys | yes |
Each has every operation of the type it holds, under the same names, and the same costs. The exceptions are the operations that mean nothing on a non-empty collection:
isEmpty,nonEmpty,orElseandtoNonEmptySet(ortoNonEmptyMap, and so on), which would always give the same answer;- the
Optionforms of what is total here:reduceOption,singleOption, and on the sorted onesheadOption,lastOption,tailOptionandinitOption; - on the maps,
removeKeysandremoveValues, which the plain maps keep only as older names ofrejectKeysandrejectValues.
Total operations¶
On a HashSet, max and reduce return an Option or fail, because the set may be empty. Here they return the
value itself. The same holds for min, maxBy, minBy, average on the sets, and head and last on the sorted
variants.
var tags = NonEmptySet.of("java", "scala", "java");
var longest = tags.maxBy(String::length); // String
var total = NonEmptySet.of(1, 2, 3).reduce(Integer::sum); // Integer
// scala, 6
A hash set has no order, so NonEmptySet and NonEmptyMap have no head. On the sorted variants, head and
last follow the comparator, while min and max use the natural order of the elements, as on every set.
The return-type contract¶
The return type tells you whether the result can be empty:
| Returns | When | For example |
|---|---|---|
| the non-empty type | the operation cannot remove every element | add, addAll, union, map, put, putAll, merge, mapValues, replace |
| the plain type | the operation may remove elements | filter, remove, intersect, diff, take, drop, tail |
Option |
you ask for a part that may not exist | find, get, tailNonEmpty(), initNonEmpty() |
addAll, union, putAll and merge accept a collection that may be empty and still return the non-empty type.
map on a set may merge equal results, and map on a map equal keys, but never down to nothing.
On a map, keySet() returns a NonEmptySet (a NonEmptySortedSet on a NonEmptySortedMap) and values() a
NonEmptyVector. groupBy returns a NonEmptyMap whose values are non-empty, and on the sorted variants grouped,
sliding and slideBy return a Vector of them. toMap returns a NonEmptyMap and toSortedMap a
NonEmptySortedMap, on these types and on NonEmptyVector: a non-empty source gives at least one entry.
// NonEmptyMap<Integer, NonEmptySet<String>>
var byLength = NonEmptySet.of("a", "bb", "cc").groupBy(String::length);
// NonEmptyMap<Integer, String>
var index = NonEmptyVector.of("a", "bb").toMap(String::length, word -> word);
// byLength maps 1 to a set of a, and 2 to a set of bb and cc; index is NonEmptyMap((1, a), (2, bb))
var prices = NonEmptySortedMap.of(Tuple.of("pear", 3), Tuple.of("apple", 2));
var first = prices.head(); // Tuple2<String, Integer>
var names = prices.keySet(); // NonEmptySortedSet<String>
var cheap = prices.filterValues(price -> price < 3); // TreeMap<String, Integer>
// (apple, 2), NonEmptySortedSet(apple, pear), TreeMap((apple, 2))
flatMap takes a function that returns the non-empty type, and keeps the result non-empty. flatMapAll takes a
function that returns any Iterable, and returns the plain type.
Construction¶
| Constructor | Returns |
|---|---|
NonEmptySet.of(head, rest...), single(a), fromIterable(head, Iterable tail) |
NonEmptySet<A> |
NonEmptyMap.single(key, value), of(entry, entries...), fromIterable(entry, Iterable entries) |
NonEmptyMap<K, V> |
fromSet(HashSet), fromMap(HashMap), fromIterable(Iterable) |
an Option |
HashSet.toNonEmptySet(), HashMap.toNonEmptyMap() |
an Option |
unsafeFromSet(HashSet), unsafeFromMap(HashMap) |
the non-empty type, or IllegalArgumentException when empty |
The sorted variants have the same constructors, each in two forms: one for the natural order and one that takes a
Comparator first. They wrap a TreeSet or a TreeMap with fromSortedSet, fromSortedMap,
TreeSet.toNonEmptySortedSet() and TreeMap.toNonEmptySortedMap().
var fromInput = HashMap.of("a", 1).toNonEmptyMap(); // Option<NonEmptyMap<String, Integer>>
var fromNothing = HashSet.<String>empty().toNonEmptySet(); // Option<NonEmptySet<String>>
// Some(NonEmptyMap((a, 1))), None
toSet(), toSortedSet(), toMap() and toSortedMap() without arguments return the collection held, without
copying.
Costs¶
| Operation | Cost | Note |
|---|---|---|
contains |
effectively O(1) | effectively O(1), as HashSet.contains. |
add |
effectively O(1) | effectively O(1), as HashSet.add. |
remove |
effectively O(1) | effectively O(1), as HashSet.remove. |
union |
O(m) | O(m) for a set of m elements, each an effectively O(1) insertion, as HashSet.union. |
intersect |
O(n + m) | O(n + m) for a set of m elements, as HashSet.intersect. |
diff |
O(n + m) | O(n + m) for a set of m elements, as HashSet.diff. |
min |
O(n) | O(n), every element compared once. |
max |
O(n) | O(n), every element compared once. |
| Operation | Cost | Note |
|---|---|---|
contains |
O(log n) | O(log n), as TreeSet.contains. |
add |
O(log n) | O(log n), as TreeSet.add. |
remove |
O(log n) | O(log n), as TreeSet.remove. |
union |
O(m log(n + m)) | O(m log(n + m)) for a set of m elements, as TreeSet.union. |
intersect |
O(n + m) | O(n + m) for a set of m elements, as TreeSet.intersect. |
diff |
O(n + m) | O(n + m) for a set of m elements, as TreeSet.diff. |
min |
O(n) | O(n), every element compared once. |
max |
O(n) | O(n), every element compared once. |
head |
O(log n) | O(log n), as TreeSet.head. |
take |
O(log n) | O(log n), as TreeSet.take. |
drop |
O(log n) | O(log n), as TreeSet.drop. |
| Operation | Cost | Note |
|---|---|---|
get |
effectively O(1) | effectively O(1), as HashMap.get. |
containsKey |
effectively O(1) | effectively O(1), as HashMap.containsKey. |
put |
effectively O(1) | effectively O(1), as HashMap.put. |
putAll |
O(m) | O(m) for m entries, as HashMap.putAll; O(n + m) when they are a HashMap, merged part by part. |
remove |
effectively O(1) | effectively O(1), as HashMap.remove. |
keySet |
O(n) | O(n), the keys copied into a new set, as HashMap.keySet. |
values |
O(n) | O(n), as HashMap.values. |
| Operation | Cost | Note |
|---|---|---|
get |
O(log n) | O(log n), as TreeMap.get. |
containsKey |
O(log n) | O(log n), as TreeMap.containsKey. |
put |
O(log n) | O(log n), as TreeMap.put. |
putAll |
O(m log(n + m)) | O(m log(n + m)) for m entries, as TreeMap.putAll. |
remove |
O(log n) | O(log n), as TreeMap.remove. |
keySet |
O(n) | O(n), with no key comparison, as TreeMap.keySet. |
values |
O(n) | O(n), as TreeMap.values. |
head |
O(log n) | O(log n), as TreeMap.head. |
take |
O(log n) | O(log n), as TreeMap.take. |
drop |
O(log n) | O(log n), as TreeMap.drop. |
Every method: complexity page.
Sharp edges¶
- A non-empty set is not a
Setand is not equal to one with the same elements; compare throughtoSet()ortoSortedSet(). The same goes for the maps. ANonEmptySetand aNonEmptySortedSetwith the same elements are equal, as sets are, when the comparator agrees withequals. With one that does not, such as a case-insensitive order, equality can hold one way only, as between a plainHashSetandTreeSet. - They are
Iterable, but notTraversable.union,intersectanddifftake aSet; to pass a non-empty set, useaddAll,retainAllandremoveAll, which take anyIterable. reduceandfoldon aNonEmptySetor aNonEmptyMapsee the elements in hash order: give them an operation where the order does not matter.