The check
package of Go helps one to check various conditions for
- slices:
[]int
[]float64
[]string
[]bool
- maps:
map[string]int
map[string]string
map[string]float64
map[string]bool
map[int]int
map[int]string
map[int]float64
map[int]bool
Such checks can be useful in regular code but also in testing. For instance, you can use the check
package to compare obtained and expected slices (or maps) obtained. You will find many testing examples in the package's code in its repository).
You can check the following types of conditions:
- check if all boolean values are
true
(in a slice and in a map) - check if any boolean value is true (in a slice and in a map)
- check which of boolean values is true (in a slice and in a map)
- check if all elements of a slice have the same value
- check if all keys of a map have the same value
- check if two slices are the same (either taking into account the ordering of the slices or ignoring it)
- check if two maps are the same
- check if a value is in a slice
- check if several values are in a slice
- check if any of several values are in a slice
- check which of several values are in a slice
- check if a key-value pair is among a map's key-values
- check if a value is among a map's values
- check if several values from a slice are in a map
- check if any value from a slice are in a map
- check which values from a slice are in a map
- check if all key-value pairs from a map are in a map
- check if any of key-value pairs from a map are in a map
- check which key-value pairs from a map are in a map
In addition to these checks, the package enables one to create a unique slice (that is, with unique values) out of a slice.
Working with
float64
values: All comparisons offloat64
values enable the user to use an epsilon. This means that two floats differ when their absolute difference is higher than the epsilon. If you do not want to use the epsilon, simply make it 0. Always, always pay special attention to working with floats, and make sure what you're doing is what you need.
Many functions resemble one another, but yet there are no generic functions (with the exception of IsUnique
). See here for explanation.
Most function names are long, on purpose: this is to help the user fit the function to his or her needs, without the need to remember too many details. Below are some general rules. Note that ...
represent details about a slice or a map; e.g., AnyInMap...
can be WhichInMapString
or WhichInMapInt
(which are short versions of WhichInMapStringBool
or WhichInMapIntBool
).
Any()
andAll()
, the only short names, work with boolean slices ([]bool
)AnyInMap...
andAllInMap...
andWhichInMap...
functions check if any or all values of a map is/are true (work withmap[int]bool
andmap[string]bool
), or which areIsUnique...Slice
checks whether a slice of particular type has unique values (IsUniqueIntSlice
,IsUniqueStringSlice
andIsUniqueFloat64Slice
); you can useIsUnique
instead, a generic function working with[]int
,[]string
and[]float64
slices, but not without decrease in performanceUnique...Slice
returns a new slice of the same type, with unique values of the original slice (so duplicates are removed)IsUniqueMap...
checks whether a map has unique values; note that the map's keys are ignored (since each map has unique key-value pairs)AreEqualSlices...
compares two slices of type...
(again,int
,string
orfloat64
); the functions compares both values and the slices' orderingAreEqualSortedSlices...
compares two slices of type...
; unlike the above functions, these ignore orderingIsValueIn...Slice
checks if a slice has a particular valueAnyValueIn...Slice
checks if any of values provided as a slice is in another sliceAllValuesIn...Slice
checks if all values provided as a slice are in another sliceWhichValuesIn...Slice
checks which values provided as a slice are in another sliceAreEqualMaps...
compares whether two maps contain the same key-value pairsIsValueInMap...
checks whether a map contains a particular value; here,...
can beStringString
,IntFloat64
and the like (see above the types of maps that thecheck
package works with)AnyValueInMap...
checks whether any of values provided as a slice are among a map's valuesAllValuesInMap...
checks whether all values provided as a slice are among a map's valuesWhichValuesInMap...
checks which values provided as a slice are among a map's valuesAnyKeyValuePairInMap...
checks whether a map contains any of the key-value pairs provided as a mapAllKeyValuePairsInMap...
checks whether a map contains all key-value pairs provided as a mapWhichKeyValuePairsInMap...
checks which key-value pairs provided as a map are in a map
As you see, these functions offer many various types of checks, hence their names were designed in such a way so that a function's name facilitates remembering what the function does. The consequence is long names, but this cost comes with readability.
Most functions starting with Is
, Any
and All
return a boolean value. The only exceptions are functions starting with IsValueInMap
, since they also return the keys for which this value occurs in the map.
The above section shows the types of functions you will find in check
and their general purposes. You will find many simple examples of using the package's functions in the package's documentation and in the docs folder of this repository, in this file. Below, you will find just several examples that aim to shed some light on what the package offers.
What do you mean? I mean, you can compare their values and ignore whether or not the slices are ordered in the same way, or you can take their ordering into account. Let's see:
slice1 := []int{1, 1, 1, 2, 1, 1, 2}
slice2 := []int{1, 1, 2, 1, 1, 1, 2}
Thanks to the check
package, we can check if the slices have the same values, using AreEqualSlicesInt()
function:
AreEqualSortedSlicesInt(slice1, slice2) // true
As the name suggests, the slices are first sorted and only then compared; hence they're the same. If we want take into account that their orderings differ, then they should not differ, and they don't:
AreEqualSlicesInt(slice1, slice2) // false
Imagine you have the following slice:
slice := []string{"This", "is", "a", "string", "slice", "."}
Does slice
contain a word "is"? Let's check:
IsValueInStringSlice("is", slice) // true
Yes, it does! And does it contain "Alice" and "string" and "thing"?
AllValuesInStringSlice([]string{"Alice", "string", "thing"}, slice) // false
No, it does not contain all of them; but perhaps it contains any of them?
AnyValueInStringSlice([]string{"Alice", "string", "thing"}, slice) // true
Yes, this time we do have true
: this slice does contain at least one from among those three strings. Simple, but we do not know which of them - and even how many of them - are in the slice. check
offers you a function to find this:
values, ok := WhichValuesInStringSlice([]string{"Alice", "string", "thing"}, slice)
// ok is true
// values is map[1:string]
As you see, the Which...
functions return two values: a boolean (which is actually the second returned value) that says whether any value from slice1
is in slice2
, and a map providing those values. In map[1:string]
, the key (1
) represents the index of the value ("string"
) in the first slice. Consider another example of a Which...
function:
slice1 := []int{1, 2, 3}
slice2 := []int{1, 1, 2, 1, 1, 1, 2, 7, 33, 12, 33, 67, 90}
values, ok := WhichValuesInIntSlice(slice1, slice2)
// ok is true
// map[1:[0 1 3 4 5] 2:[2 6]]
As mentioned above, be very careful when working with float numbers. To help you do this, all the functions that compare floats have the Epsilon
parameter, which aims to achieve the desired level of accuracy. Of course, another approach could be to round all the values before any comparisons, though not always you will want to round them. Here's an example of how to check if a float64
number is among a map's values.
searchedFloat := .023
Map := map[int]float64{1:.0214, 2:.0212, 3:.0222, 4:.0231}
IsValueInMapIntFloat64(searchedFloat, Map, 0) // false
Here, we used Epsilon = 0
, so we did not decrease the accuracy: what we have is what we got, and in that case, .023
is not among Map
's values. But when we change this level of accuracy, increasing it to, say, .001 (which means that if two values differ by .001 or less than they are considered equal), then
IsValueInMapIntFloat64(searchedFloat, Map, .001) // true
This is becuase .0231 - .023
is equal to Epsilon
(.001
), so the searched float is in fact among the map's values.
Let's compare if two maps are the same:
Map1 := map[int]string{1: "What", 2: "a", 3: "mysterious", 4: "thing"}
Map2 := map[int]string{4: "thing", 1: "What", 2: "a", 3: "mysterious"}
Map1 := map[int]string{1: "What", 2: "a", 3: "mysterious", 4: "thing!"}
AreEqualMapsIntString(Map1, Map2) // true
AreEqualMapsIntString(Map1, Map3) // false
Of course, Map1
and Map2
are the same, since the ordering of a map is random and does not matter. Map1
and Map2
, however, differ from Map3
because of the exclamation mark in "thing!"
of the key 4
of the latter.
You can do so for []int
, []string
and []float64
slices, e.g.,
IsUniqueIntSlice([]int{1, 1, 2, 12, 3, 21, 71}) // false
IsUniqueIntSlice([]int{1, 15, 2, 12, 3, 21, 71}) // true
IsUniqueFloat64Slice([]float64{.0214, .0212, .0222, .0221}, .001) // false
Note that in the latter case, Epsilon = .001
, so .0222
and .0221
are considered equal (their difference is equal to Epsilon). If you increase the accuracy (that is, decrease Epsilon
), the result changes:
IsUniqueFloat64Slice([]float64{.0214, .0212, .0222, .0221}, .0001) // true
As already mentioned, you can create a unique slice (also []float64
, for which you will again have to use the Epsilon
parameter), even if this does not seem to fit the check
package's main purpose. You can do it in the following way:
UniqueIntSlice([]int{1, 1, 2, 2, 3, 21, 1}) // [1 2 3 21]
UniqueStringSlice([]string{"a", "a", "C", "b"}) // [a C b]
UniqueFloat64Slice([]float64{.0021, .0024, .0022, .0031, .00311}, 0) // [.0021, .0024, .0022, .0031, .00311]
UniqueFloat64Slice([]float64{.0021, .0024, .0022, .0031, .00311}, .0001) // [.0021, .0024, .0022, .0031]
Sure you can! And frankly, this is not that difficult to do - but you need to be careful. The check
package, however, offers you an alternative: a one-liner that will say what you're doing instead of five or ten additional lines, and you need not worry about the details. It's like with any package: It can help you save time and energy and work, and it's tested, and so you can use it without worrying that you have made a mistake somewhere there in the code or omitted something important.
Many of the check
functions are similar, so it might be tempting to propose several generic functions. The check
package, however, offers only one such generic function, IsUnique
(working with []int
, []string
and []float64
slices), and it does so only for the representation purpose.
One reason behind no generics is that the functions working with float64
values use the Epsilon
parameter, which is not used by functions working with string
and int
values. Hence to make a generic function to work with different signatures would mean either complicating or simplifying things, something better to be avoided. For instance, IsUnique
sets Epsilon
to 0, a simplification not always preferred.
Another issue could be performance. Generics would require type checking, which could slightly decrease the performance. Nonetheless, this effect is so small that can be considered negligible, unless for very small slices. You can find some benchmarks in this file.
Let's wait for the generics to appear in Go soon, and then we'll see.
You will find the package's code to the package in its github repository. Feel free to contribute if you see any bugs or have an idea for improving the package or adding a new functionality. You can either create an issue or submit a merge request.