The check package of Go helps one to check various conditions for
- slices:
[]int[]float64[]string[]bool
- maps:
map[string]intmap[string]stringmap[string]float64map[string]boolmap[int]intmap[int]stringmap[int]float64map[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
float64values: All comparisons offloat64values 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]boolandmap[string]bool), or which areIsUnique...Slicechecks whether a slice of particular type has unique values (IsUniqueIntSlice,IsUniqueStringSliceandIsUniqueFloat64Slice); you can useIsUniqueinstead, a generic function working with[]int,[]stringand[]float64slices, but not without decrease in performanceUnique...Slicereturns 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,stringorfloat64); the functions compares both values and the slices' orderingAreEqualSortedSlices...compares two slices of type...; unlike the above functions, these ignore orderingIsValueIn...Slicechecks if a slice has a particular valueAnyValueIn...Slicechecks if any of values provided as a slice is in another sliceAllValuesIn...Slicechecks if all values provided as a slice are in another sliceWhichValuesIn...Slicechecks 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,IntFloat64and the like (see above the types of maps that thecheckpackage 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) // trueYes, it does! And does it contain "Alice" and "string" and "thing"?
AllValuesInStringSlice([]string{"Alice", "string", "thing"}, slice) // falseNo, it does not contain all of them; but perhaps it contains any of them?
AnyValueInStringSlice([]string{"Alice", "string", "thing"}, slice) // trueYes, 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) // falseHere, 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) // trueThis 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) // falseOf 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) // falseNote 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) // trueAs 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.