I want to write test cases that depend on parameters. My test case should be executed for each parameter and I want to see whether it succeeds or fails for each parameter.
I'm used to writing things like that in Java:
#RunWith(Parameterized.class)
public class FibonacciTest {
#Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] {
{ 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 }
});
}
private int fInput;
private int fExpected;
public FibonacciTest(int input, int expected) {
fInput= input;
fExpected= expected;
}
#Test
public void test() {
assertEquals(fExpected, Fibonacci.compute(fInput));
}
}
How can I achieve something similar with Rust? Simple test cases are working fine, but there are cases where they are not enough.
#[test]
fn it_works() {
assert!(true);
}
Note: I want the parameters as flexible as possible, for example: Read them from a file, or use all files from a certain directory as input, etc. So a hardcoded macro might not be enough.
The built-in test framework does not support this; the most common approach used is to generate a test for each case using macros, like this:
macro_rules! fib_tests {
($($name:ident: $value:expr,)*) => {
$(
#[test]
fn $name() {
let (input, expected) = $value;
assert_eq!(expected, fib(input));
}
)*
}
}
fib_tests! {
fib_0: (0, 0),
fib_1: (1, 1),
fib_2: (2, 1),
fib_3: (3, 2),
fib_4: (4, 3),
fib_5: (5, 5),
fib_6: (6, 8),
}
This produces individual tests with names fib_0, fib_1, &c.
My rstest crate mimics pytest syntax and provides a lot of flexibility. A Fibonacci example can be very neat:
use rstest::rstest;
#[rstest]
#[case(0, 0)]
#[case(1, 1)]
#[case(2, 1)]
#[case(3, 2)]
#[case(4, 3)]
#[case(5, 5)]
#[case(6, 8)]
fn fibonacci_test(#[case] input: u32, #[case] expected: u32) {
assert_eq!(expected, fibonacci(input))
}
pub fn fibonacci(input: u32) -> u32 {
match input {
0 => 0,
1 => 1,
n => fibonacci(n - 2) + fibonacci(n - 1)
}
}
Output:
/home/michele/.cargo/bin/cargo test
Compiling fib_test v0.1.0 (file:///home/michele/learning/rust/fib_test)
Finished dev [unoptimized + debuginfo] target(s) in 0.92s
Running target/debug/deps/fib_test-56ca7b46190fda35
running 7 tests
test fibonacci_test::case_1 ... ok
test fibonacci_test::case_2 ... ok
test fibonacci_test::case_3 ... ok
test fibonacci_test::case_5 ... ok
test fibonacci_test::case_6 ... ok
test fibonacci_test::case_4 ... ok
test fibonacci_test::case_7 ... ok
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Every case is run as a single test case.
The syntax is simple and neat and, if you need, you can use any Rust expression as the value in the case argument.
rstest also supports generics and pytest-like fixtures.
Don't forget to add rstest to dev-dependencies in Cargo.toml.
Probably not quite what you've asked for, but by using TestResult::discard with quickcheck you can test a function with a subset of a randomly generated input.
extern crate quickcheck;
use quickcheck::{TestResult, quickcheck};
fn fib(n: u32) -> u32 {
match n {
0 => 0,
1 => 1,
_ => fib(n - 1) + fib(n - 2),
}
}
fn main() {
fn prop(n: u32) -> TestResult {
if n > 6 {
TestResult::discard()
} else {
let x = fib(n);
let y = fib(n + 1);
let z = fib(n + 2);
let ow_is_ow = n != 0 || x == 0;
let one_is_one = n != 1 || x == 1;
TestResult::from_bool(x + y == z && ow_is_ow && one_is_one)
}
}
quickcheck(prop as fn(u32) -> TestResult);
}
I took the Fibonacci test from this Quickcheck tutorial.
P.S. And of course, even without macros and quickcheck you still can include the parameters in the test. "Keep it simple".
#[test]
fn test_fib() {
for &(x, y) in [(0, 0), (1, 1), (2, 1), (3, 2), (4, 3), (5, 5), (6, 8)].iter() {
assert_eq!(fib(x), y);
}
}
It's possible to construct tests based on arbitrarily complex parameters and any information known at build time (including anything you can load from a file) with a build script.
We tell Cargo where the build script is:
Cargo.toml
[package]
name = "test"
version = "0.1.0"
build = "build.rs"
In the build script, we generate our test logic and place it in a file using the environment variable OUT_DIR:
build.rs
fn main() {
let out_dir = std::env::var("OUT_DIR").unwrap();
let destination = std::path::Path::new(&out_dir).join("test.rs");
let mut f = std::fs::File::create(&destination).unwrap();
let params = &["abc", "fooboo"];
for p in params {
use std::io::Write;
write!(
f,
"
#[test]
fn {name}() {{
assert!(true);
}}",
name = p
).unwrap();
}
}
Finally, we create a file in our tests directory that includes the code of the generated file.
tests/generated_test.rs
include!(concat!(env!("OUT_DIR"), "/test.rs"));
That's it. Let's verify that the tests are run:
$ cargo test
Compiling test v0.1.0 (...)
Finished debug [unoptimized + debuginfo] target(s) in 0.26 secs
Running target/debug/deps/generated_test-ce82d068f4ceb10d
running 2 tests
test abc ... ok
test fooboo ... ok
Without using any additional packages, you can do it like this, since you can write tests that return a Result type
#[cfg(test)]
mod tests {
fn test_add_case(a: i32, b: i32, expected: i32) -> Result<(), String> {
let result = a + b;
if result != expected {
Err(format!(
"{} + {} result: {}, expected: {}",
a, b, result, expected
))
} else {
Ok(())
}
}
#[test]
fn test_add() -> Result<(), String> {
[(2, 2, 4), (1, 4, 5), (1, -1, 0), (4, 2, 0)]
.iter()
.try_for_each(|(a, b, expected)| test_add_case(*a, *b, *expected))?;
Ok(())
}
}
You will even get a nice error message:
---- tests::test_add stdout ----
Error: "4 + 2 result: 6, expected: 0"
thread 'tests::test_add' panicked at 'assertion failed: `(left == right)`
left: `1`,
right: `0`: the test returned a termination value with a non-zero status code (1) which indicates a failure', /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35/library/test/src/lib.rs:194:5
Use https://github.com/frondeus/test-case crate.
Example:
#[test_case("some")]
#[test_case("other")]
fn works_correctly(arg: &str) {
assert!(arg.len() > 0)
}
EDIT: This is now on crates.io as parameterized_test::create!{...} - Add parameterized_test = "0.2.0" to your Cargo.toml file.
Building off Chris Morgan’s answer, here's a recursive macro to create parameterized tests (playground):
macro_rules! parameterized_test {
($name:ident, $args:pat, $body:tt) => {
with_dollar_sign! {
($d:tt) => {
macro_rules! $name {
($d($d pname:ident: $d values:expr,)*) => {
mod $name {
use super::*;
$d(
#[test]
fn $d pname() {
let $args = $d values;
$body
}
)*
}}}}}}}
You can use it like so:
parameterized_test!{ even, n, { assert_eq!(n % 2, 0); } }
even! {
one: 1,
two: 2,
}
parameterized_test! defines a new macro (even!) that will create parameterized tests taking one argument (n) and invoking assert_eq!(n % 2, 0);.
even! then works essentially like Chris' fib_tests!, though it groups the tests into a module so they can share a prefix (suggested here). This example results in two tests functions, even::one and even::two.
This same syntax works for multiple parameters:
parameterized_test!{equal, (actual, expected), {
assert_eq!(actual, expected);
}}
equal! {
same: (1, 1),
different: (2, 3),
}
The with_dollar_sign! macro used above to essentially escape the dollar-signs in the inner macro comes from #durka:
macro_rules! with_dollar_sign {
($($body:tt)*) => {
macro_rules! __with_dollar_sign { $($body)* }
__with_dollar_sign!($);
}
}
I've not written many Rust macros before, so feedback and suggestions are very welcome.
Riffing off that great answer by Chris Morgan above, I offer my use of it here. Apart from minor refactoring, this extension allows for an evaluator function which gathers the "actual" value from the system under test. The output is pretty nice. My VS Code setup automatically expands the macro invocation into a list of tests functions that may be individually invoked within the editor. In any event, since label becomes the corresponding test function name, cargo test does allows easy test selection as in, cargo test length_
macro_rules! test_cases {
($($label:ident: $evaluator:ident $case:expr,)*) => {
$(
#[test]
fn $label() {
let (expected, input) = $case;
assert_eq!(expected, $evaluator(input));
}
)*
}
}
fn get_len(s: &str) -> usize {
s.len()
}
test_cases! {
length_0: get_len (0, ""), //comments are permitted
length_1: get_len (2, "AB"),
length_2: get_len (9, "123456789"),
length_3: get_len (14, "not 14 long"),
}
Output...
running 4 tests
test length_0 ... ok
test length_1 ... ok
test length_2 ... ok
test length_3 ... FAILED
failures:
---- length_3 stdout ----
thread 'length_3' panicked at 'assertion failed: `(left == right)`
left: `14`,
right: `11`', src/lib.rs:17:1
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
length_3
test result: FAILED. 3 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Related
I would like the test runner to continue testing the whole it test even after the assertion fails.
const { assert } = require('chai');
describe('Test suite', () => {
it('Test 1', () => { // test fail
assert.equal(1, 1); // passed assertion
assert.equal(3, 4); // failed assertion
assert.equal(5, 6); // failed assertion
});
it('Test 2', () => { // pass
assert.equal(2, 2);
});
});
If I run this test, Test 1 failed as expected, but it aborts and don't try other assertions after first failure, printing this error message:
Test suite
1) Test 1
✓ Test 2
1 passing (6ms)
1 failing
1) Test suite
Test 1:
AssertionError: expected 3 to equal 4
+ expected - actual
-3
+4
at Context.<anonymous> (test/specs/newspec.spec.js:6:12)
at processImmediate (internal/timers.js:456:21)
I would like to get the failure information for assert.equal(5, 6) as well, can anyone suggest a good way to approach it? Split each assert in each it block is not an option as this was just a dummy example, but IRL the test is more complex.
First of all, this is my first question, you can tell me how to improve it and what tags to use.
What I am trying to do is I have a bunch of objects that have minimal and maximal values by those values you can deduce if two objects have some sort of overlapping value and thus they can be put together in a group
This question might need dynamic programming to solve.
example objects:
1 ( min: 0, max: 2 )
2 ( min: 1, max: 3 )
3 ( min: 2, max: 4 )
4 ( min: 3, max: 5 )
object 1 can be grouped with objects 2, 3
object 2 can be grouped with objects 1, 3, 4
object 3 can be grouped with objects 1, 2, 4
object 4 can be grouped with objects 2, 3
as you can see there are multiple ways to group those elements
[1, 2]
[3, 4]
[1]
[2, 3]
[4]
[1]
[2, 3, 4]
[1, 2, 3]
[4]
now there should be some sort of rule to deduce which of the solutions is the best solution
for example least amount of groups
[1, 2]
[3, 4]
or
[1]
[2, 3, 4]
or
[1, 2, 3]
[4]
or most objects in one group
[1]
[2, 3, 4]
or
[1, 2, 3]
[4]
or any other rule that uses another attribute of said objects to compare the solutions
what I have now:
$objects = [...objects...];
$numberOfObjects = count($objects);
$groups = [];
for ($i = 0; $i < $numberOfObjects; $i++) {
$MinA = $objects[$i]['min'];
$MaxA = $objects[$i]['max'];
$groups[$i] = [$i];
for ($j = $i + 1; $j < $numberOfObjects; $j++) {
$MinB = $objects[$j]['min'];
$MaxB = $objects[$j]['max'];
if (($MinA >= $MinB && $MinA <= $MaxB) || ($MaxA >= $MinB && $MaxA <= $MaxB) || ($MinB >= $MinA && $MinB <= $MaxA)) {
array_push($groups[$i], $j);
}
}
}
this basically creates an array with indexes of objects that can be grouped together
from this point, I don't know how to proceed, how to generate all the solution and then check each of them how good it is, and the pick the best one
or maybe there is even better solution that doesn't use any of this?
PHP solutions are preferred, although this problem is not PHP-specific
When I was first looking at your algorithm, I was impressed by how efficient it is :)
Here it is rewritten in javascript, because I moved away from perl a good while ago:
function setsOf(objects){
numberOfObjects = objects.length
groups = []
let i
for (i = 0; i < numberOfObjects; i++) {
MinA = objects[i]['min']
MaxA = objects[i]['max']
groups[i] = [i]
for (j = i + 1; j < numberOfObjects; j++) {
MinB = objects[j]['min']
MaxB = objects[j]['max']
if ((MinA >= MinB && MinA <= MaxB) || (MaxA >= MinB && MaxA <= MaxB) ||
(MinB >= MinA && MinB <= MaxA)) {
groups[i].push(j)
}
}
}
return groups
}
if you happen to also think well in javascript, you might find this form more direct (it is identical, however):
function setsOf(objects){
let groups = []
objects.forEach((left,i) => {
groups[i]=[i]
Array.from(objects).splice(i+1).forEach((right, j) => {
if ((left.min >= right.min && left.min <= right.max) ||
(left.max >=right.max && left.max <= right.max) ||
(right.min >= left.min && right.min <= left.max))
groups[i].push(j+i+1)
})
})
return groups
}
so if we run it, we get:
a = setsOf([{min:0, max:2}, {min:1, max:3}, {min:2, max:4}, {min:3, max: 5}])
[Array(3), Array(3), Array(2), Array(1)]0: Array(3)1: Array(3)2: Array(2)3: Array(1)length: 4__proto__: Array(0)
JSON.stringify(a)
"[[0,1,2],[1,2,3],[2,3],[3]]"
and it does impressively catch the compound groups :) a weakness is that it is capturing groups containing more objects than necessary, without capturing all available objects. You seem to have a very custom selection criteria. To me, it seems like the groups should either be every last intersecting subset, or only subsets where each element in the group provides unique coverage: [0,1], [0,2], [1,2], [1,3], [2,3], [0,1,3]
the algorithm for that is perhaps more involved. this was my approach, and it is nowhere near as terse and elegant as yours, but it works:
function intersectingGroups (mmvs) {
const min = []
const max = []
const muxo = [...mmvs]
mmvs.forEach(byMin => {
mmvs.forEach(byMax => {
if (byMin.min === byMax.min && byMin.max === byMax.max) {
console.log('rejecting identity', byMin, byMax)
return // identity
}
if (byMax.min > byMin.max) {
console.log('rejecting non-overlapping objects', byMin, byMax)
return // non-overlapping objects
}
if ((byMax.max <= byMin.max) || (byMin.min >= byMax.min)) {
console.log('rejecting non-expansive coverage or inversed order',
byMin, byMax)
return // non-expansive coverage or inversed order
}
const entity = {min: byMin.min, max: byMax.max,
compositeOf: [byMin, byMax]}
if(muxo.some(mv => mv.min === entity.min && mv.max === entity.max))
return // enforcing Set
muxo.push(entity)
console.log('adding', byMin, byMax, muxo)
})
})
if(muxo.length === mmvs.length) {
return muxo.filter(m => 'compositeOf' in m)
// solution
} else {
return intersectingGroups(muxo)
}
}
now there should be some sort of rule to deduce which of the solutions is the best solution
Yeah, so, usually for puzzles or for a specification you are fulfilling, that would be given as part of the problem. As it is, you want a general method that is adaptable. It's probably best to make an object that can be configured with the results and accepts rules, then load the rules you are interested in, and the results from the search, and see what rules match where. For example, using your algorithm and sample criteria:
least amount of groups
start with code like:
let reviewerFactory = {
getReviewer (specification) { // generate a reviewer
return {
matches: [], // place to load sets to
criteria: specification,
review (objects) { // review the sets already loaded
let group
let results = {}
this.matches.forEach(mset => {
group = [] // gather each object from the initial set for each match in the result set
mset.forEach(m => {
group.push(objects[m])
})
results[mset] = this.criteria.scoring(group) // score the match relative to the specification
})
return this.criteria.evaluation(results) // pick the best score
}
}
},
specifications: {}
}
now you can add specifications like this one for least amount of groups:
reviewerFactory.specifications['LEAST GROUPS'] = {
scoring: function (set) { return set.length },
evaluation: function (res) { return Object.keys(res).sort((a,b) => res[a] - res[b])[0] }
}
then you can use that in the evaluation of a set:
mySet = [{min:0, max:2}, {min:1, max:3}, {min:2, max:4}, {min:3, max: 5}]
rf = reviewerFactory.getReviewer(reviewerFactory.specifications['LEAST GROUPS'])
Object {matches: Array(0), criteria: Object, review: function}
rf.matches = setsOf(mySet)
[Array(3), Array(3), Array(2), Array(1)]
rf.review(mySet)
"3"
or, most objects:
reviewerFactory.specifications['MOST GROUPS'] = {
scoring: function (set) { return set.length },
evaluation: function (res) { return Object.keys(res).sort((a,b) => res[a] - res[b]).reverse()[0] }
}
mySet = [{min:0, max:2}, {min:1, max:3}, {min:2, max:4}, {min:3, max: 5}]
reviewer = reviewerFactory.getReviewer(reviewerFactory.specifications['MOST GROUPS'])
reviewer.matches = setsOf(mySet)
reviewer.review(mySet)
"1,2,3"
Of course this is arbitrary, but so are the criteria, by definition in the OP. Likewise, you would have to change the algorithms here to work with my intersectingGroups function because it doesn't return indices. But this is what you are looking for I believe.
I've wrote the following spec
"An IP4 address" should "belong to just one class" in {
val addrs = for {
a <- Gen.choose(0, 255)
b <- Gen.choose(0, 255)
c <- Gen.choose(0, 255)
d <- Gen.choose(0, 255)
} yield s"$a.$b.$c.$d"
forAll (addrs) { ip4s =>
var c: Int = 0
if (IP4_ClassA.unapply(ip4s).isDefined) c = c + 1
if (IP4_ClassB.unapply(ip4s).isDefined) c = c + 1
if (IP4_ClassC.unapply(ip4s).isDefined) c = c + 1
if (IP4_ClassD.unapply(ip4s).isDefined) c = c + 1
if (IP4_ClassE.unapply(ip4s).isDefined) c = c + 1
c should be (1)
}
}
That is very clear in its scope.
The test passes successfully but when I force it to fail (for example by commenting out one of the if statements) then ScalaCheck correctly reports the error but the message doesn't mention correctly the actual value used to evaluate the proposition. More specifically I get:
[info] An IP4 address
[info] - should belong to just one class *** FAILED ***
[info] TestFailedException was thrown during property evaluation.
[info] Message: 0 was not equal to 1
[info] Location: (NetSpec.scala:105)
[info] Occurred when passed generated values (
[info] arg0 = "" // 4 shrinks
[info] )
where you can see arg0 = "" // 4 shrinks doesn't show the value.
I've tried to add even a simple println statement to review the cases but the output appears to be trimmed. I get something like this
192.168.0.1
189.168.
189.
1
SOLUTION
import org.scalacheck.Prop.forAllNoShrink
import org.scalatest.prop.Checkers.check
"An IP4 address" should "belong to just one class" in {
val addrs = for {
a <- Gen.choose(0, 255)
b <- Gen.choose(0, 255)
c <- Gen.choose(0, 255)
d <- Gen.choose(0, 255)
} yield s"$a.$b.$c.$d"
check {
forAllNoShrink(addrs) { ip4s =>
var c: Int = 0
if (IP4.ClassA.unapply(ip4s).isDefined) c = c + 1
if (IP4.ClassB.unapply(ip4s).isDefined) c = c + 1
if (IP4.ClassC.unapply(ip4s).isDefined) c = c + 1
if (IP4.ClassD.unapply(ip4s).isDefined) c = c + 1
if (IP4.ClassE.unapply(ip4s).isDefined) c = c + 1
c == (1)
}
}
}
This is caused by ScalaCheck's test case simplification feature. ScalaCheck just sees that your generator produces a string value. Whenever it finds a value that makes your property false, it tries to simplify that value. In your case, it simplifies it four times until it ends up with an empty string, that still makes your property false.
So this is expected, although confusing, behavior. But you can improve the situation in three different ways.
You can select another data structure to represent your IP addresses. This will make ScalaCheck able to simplify your test cases in a more intelligent way. For example, use the following generator:
val addrs = Gen.listOfN(4, Gen.choose(0,255))
Now ScalaCheck knows that your generator only produces lists of length 4, and that it only contains numbers between 0 and 255. The test case simplification process will take this into account and not create any values that couldn't have been produced by the generator from start. You can do the conversion to string inside your property instead.
A second method is to add a filter directly to your generator, which tells ScalaCheck how an IP address string should look like. This filter is used during test case simplification. Define a function that checks for valid strings and attach it to your existing generator this way:
def validIP(ip: String): Boolean = ...
val validAddrs = addrs.suchThat(validIP)
forAll(validAddrs) { ... }
The third method is to simply disable the test case simplification feature altogether by using forAllNoShrink instead of forAll:
Prop.forAllNoShrink(addrs) { ... }
I should also mention that the two first methods require ScalaCheck version >= 1.11.0 to function properly.
UPDATE:
The listOfN list length is actually not respected by the shrinker any more, due to https://github.com/rickynils/scalacheck/issues/89. Hopefully this can be fixed in a future version of ScalaCheck.
How can I check if x is in an array without iterating over the entire array, using Go? Does the language have a construct for this?
Like in Python:
if "x" in array:
# do something
There is no built-in operator to do it in Go. You need to iterate over the array. You can write your own function to do it, like this:
func stringInSlice(a string, list []string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}
Or in Go 1.18 or newer, you can use slices.Contains (from golang.org/x/exp/slices).
If you want to be able to check for membership without iterating over the whole list, you need to use a map instead of an array or slice, like this:
visitedURL := map[string]bool {
"http://www.google.com": true,
"https://paypal.com": true,
}
if visitedURL[thisSite] {
fmt.Println("Already been here.")
}
Another solution if the list contains static values.
eg: checking for a valid value from a list of valid values:
func IsValidCategory(category string) bool {
switch category {
case
"auto",
"news",
"sport",
"music":
return true
}
return false
}
This is quote from the book "Programming in Go: Creating Applications for the 21st Century":
Using a simple linear search like this is the only option for unsorted
data and is fine for small slices (up to hundreds of items). But for
larger slices—especially if we are performing searches repeatedly—the
linear search is very inefficient, on average requiring half the items
to be compared each time.
Go provides a sort.Search() method which uses the binary search
algorithm: This requires the comparison of only log2(n) items (where n
is the number of items) each time. To put this in perspective, a
linear search of 1000000 items requires 500000 comparisons on average,
with a worst case of 1000000 comparisons; a binary search needs at
most 20 comparisons, even in the worst case.
files := []string{"Test.conf", "util.go", "Makefile", "misc.go", "main.go"}
target := "Makefile"
sort.Strings(files)
i := sort.Search(len(files),
func(i int) bool { return files[i] >= target })
if i < len(files) && files[i] == target {
fmt.Printf("found \"%s\" at files[%d]\n", files[i], i)
}
https://play.golang.org/p/UIndYQ8FeW
Just had a similar question and decided to try out some of the suggestions in this thread.
I've benchmarked best and worst-case scenarios of 3 types of lookup:
using a map
using a list
using a switch statement
Here's the function code:
func belongsToMap(lookup string) bool {
list := map[string]bool{
"900898296857": true,
"900898302052": true,
"900898296492": true,
"900898296850": true,
"900898296703": true,
"900898296633": true,
"900898296613": true,
"900898296615": true,
"900898296620": true,
"900898296636": true,
}
if _, ok := list[lookup]; ok {
return true
} else {
return false
}
}
func belongsToList(lookup string) bool {
list := []string{
"900898296857",
"900898302052",
"900898296492",
"900898296850",
"900898296703",
"900898296633",
"900898296613",
"900898296615",
"900898296620",
"900898296636",
}
for _, val := range list {
if val == lookup {
return true
}
}
return false
}
func belongsToSwitch(lookup string) bool {
switch lookup {
case
"900898296857",
"900898302052",
"900898296492",
"900898296850",
"900898296703",
"900898296633",
"900898296613",
"900898296615",
"900898296620",
"900898296636":
return true
}
return false
}
Best-case scenarios pick the first item in lists, worst-case ones use nonexistent value.
Here are the results:
BenchmarkBelongsToMapWorstCase-4 2000000 787 ns/op
BenchmarkBelongsToSwitchWorstCase-4 2000000000 0.35 ns/op
BenchmarkBelongsToListWorstCase-4 100000000 14.7 ns/op
BenchmarkBelongsToMapBestCase-4 2000000 683 ns/op
BenchmarkBelongsToSwitchBestCase-4 100000000 10.6 ns/op
BenchmarkBelongsToListBestCase-4 100000000 10.4 ns/op
Switch wins all the way, worst case is surpassingly quicker than best case.
Maps are the worst and list is closer to switch.
So the moral is:
If you have a static, reasonably small list, switch statement is the way to go.
The above example using sort is close, but in the case of strings simply use SearchString:
files := []string{"Test.conf", "util.go", "Makefile", "misc.go", "main.go"}
target := "Makefile"
sort.Strings(files)
i := sort.SearchStrings(files, target)
if i < len(files) && files[i] == target {
fmt.Printf("found \"%s\" at files[%d]\n", files[i], i)
}
https://golang.org/pkg/sort/#SearchStrings
This is as close as I can get to the natural feel of Python's "in" operator. You have to define your own type. Then you can extend the functionality of that type by adding a method like "has" which behaves like you'd hope.
package main
import "fmt"
type StrSlice []string
func (list StrSlice) Has(a string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}
func main() {
var testList = StrSlice{"The", "big", "dog", "has", "fleas"}
if testList.Has("dog") {
fmt.Println("Yay!")
}
}
I have a utility library where I define a few common things like this for several types of slices, like those containing integers or my own other structs.
Yes, it runs in linear time, but that's not the point. The point is to ask and learn what common language constructs Go has and doesn't have. It's a good exercise. Whether this answer is silly or useful is up to the reader.
Another option is using a map as a set. You use just the keys and having the value be something like a boolean that's always true. Then you can easily check if the map contains the key or not. This is useful if you need the behavior of a set, where if you add a value multiple times it's only in the set once.
Here's a simple example where I add random numbers as keys to a map. If the same number is generated more than once it doesn't matter, it will only appear in the final map once. Then I use a simple if check to see if a key is in the map or not.
package main
import (
"fmt"
"math/rand"
)
func main() {
var MAX int = 10
m := make(map[int]bool)
for i := 0; i <= MAX; i++ {
m[rand.Intn(MAX)] = true
}
for i := 0; i <= MAX; i++ {
if _, ok := m[i]; ok {
fmt.Printf("%v is in map\n", i)
} else {
fmt.Printf("%v is not in map\n", i)
}
}
}
Here it is on the go playground
In Go 1.18+, you can now declare generic Contains function which is also implemented in the experimental slice function. It works for any comparable type
func Contains[T comparable](arr []T, x T) bool {
for _, v := range arr {
if v == x {
return true
}
}
return false
}
and use it like this:
if Contains(arr, "x") {
// do something
}
// or
if slices.Contains(arr, "x") {
// do something
}
which I found here
try lo: https://github.com/samber/lo#contains
present := lo.Contains[int]([]int{0, 1, 2, 3, 4, 5}, 5)
I ve got the following class and I want to write some Spec test cases, but I am really new to it and I don't know how to start. My class do loke like this:
class Board{
val array = Array.fill(7)(Array.fill(6)(None:Option[Coin]))
def move(x:Int, coin:Coin) {
val y = array(x).indexOf(None)
require(y >= 0)
array(x)(y) = Some(coin)
}
def apply(x: Int, y: Int):Option[Coin] =
if (0 <= x && x < 7 && 0 <= y && y < 6) array(x)(y)
else None
def winner: Option[Coin] = winner(Cross).orElse(winner(Naught))
private def winner(coin:Coin):Option[Coin] = {
val rows = (0 until 6).map(y => (0 until 7).map( x => apply(x,y)))
val cols = (0 until 7).map(x => (0 until 6).map( y => apply(x,y)))
val dia1 = (0 until 4).map(x => (0 until 6).map( y => apply(x+y,y)))
val dia2 = (3 until 7).map(x => (0 until 6).map( y => apply(x-y,y)))
val slice = List.fill(4)(Some(coin))
if((rows ++ cols ++ dia1 ++ dia2).exists(_.containsSlice(slice)))
Some(coin)
else None
}
override def toString = {
val string = new StringBuilder
for(y <- 5 to 0 by -1; x <- 0 to 6){
string.append(apply(x, y).getOrElse("_"))
if (x == 6) string.append ("\n")
else string.append("|")
}
string.append("0 1 2 3 4 5 6\n").toString
}
}
Thank you!
I can only second Daniel's suggestion, because you'll end up with a more practical API by using TDD.
I also think that your application could be nicely tested with a mix of specs2 and ScalaCheck. Here the draft of a Specification to get you started:
import org.specs2._
import org.scalacheck.{Arbitrary, Gen}
class TestSpec extends Specification with ScalaCheck { def is =
"moving a coin in a column moves the coin to the nearest empty slot" ! e1^
"a coin wins if" ^
"a row contains 4 consecutive coins" ! e2^
"a column contains 4 consecutive coins" ! e3^
"a diagonal contains 4 consecutive coins" ! e4^
end
def e1 = check { (b: Board, x: Int, c: Coin) =>
try { b.move(x, c) } catch { case e => () }
// either there was a coin before somewhere in that column
// or there is now after the move
(0 until 6).exists(y => b(x, y).isDefined)
}
def e2 = pending
def e3 = pending
def e4 = pending
/**
* Random data for Coins, x position and Board
*/
implicit def arbitraryCoin: Arbitrary[Coin] = Arbitrary { Gen.oneOf(Cross, Naught) }
implicit def arbitraryXPosition: Arbitrary[Int] = Arbitrary { Gen.choose(0, 6) }
implicit def arbitraryBoardMove: Arbitrary[(Int, Coin)] = Arbitrary {
for {
coin <- arbitraryCoin.arbitrary
x <- arbitraryXPosition.arbitrary
} yield (x, coin)
}
implicit def arbitraryBoard: Arbitrary[Board] = Arbitrary {
for {
moves <- Gen.listOf1(arbitraryBoardMove.arbitrary)
} yield {
val board = new Board
moves.foreach { case (x, coin) =>
try { board.move(x, coin) } catch { case e => () }}
board
}
}
}
object Cross extends Coin {
override def toString = "x"
}
object Naught extends Coin {
override def toString = "o"
}
sealed trait Coin
The e1 property I've implemented is not the real thing because it doesn't really check that we moved the coin to the nearest empty slot, which is what your code and your API suggests. You will also want to change the generated data so that the Boards are generated with an alternation of x and o. That should be a great way to learn how to use ScalaCheck!
I suggest you throw all that code out -- well, save it somewhere, but start from zero using TDD.
The Specs2 site has plenty examples of how to write tests, but use TDD -- test driven design -- to do it. Adding tests after the fact is suboptimal, to say the least.
So, think of the most simple case you want to handle of the most simple feature, write a test for that, see it fail, write the code to fix it. Refactor if necessary, and repeat for the next most simple case.
If you want help with how to do TDD in general, I heartily endorse the videos about TDD available on Clean Coders. At the very least, watch the second part where Bob Martin writes a whole class TDD-style, from design to end.
If you know how to do testing in general but are confused about Scala or Specs, please be much more specific about what your questions are.