Approach to exhaustively test Swift struct conformance to Equatable? - swift

The title almost says it all. My project is in Swift 2.3. Ocassionaly a custom struct model object is introduced. For various reasons, this must conform to Equatable.
Let's for example have a struct with 6 different value-type variables, all from Swift standard library.
struct Address: Equatable {
let firstName: String
let lastName: String
let street: String
let streetNumber: Int
let city: String
let countryCode: Int
}
func ==(lhs: Address, rhs: Address) -> Bool {
return (
lhs.firstName == rhs.firstName &&
lhs.lastName == rhs.lastName &&
lhs.street == rhs.street &&
lhs.streetNumber == rhs.streetNumber &&
lhs.city == rhs.city &&
lhs.countryCode == rhs.countryCode)
}
What is the correct algorithm to ensure I will test ALL the combinations of (in)equality in my unit test?
Permutations/combinations? Perhaps something more programatic?

In this case, exhaustively testing all the combinations is just a flat-out poor strategy. It's unnecessary, and serves only to make unit testing more troublesome and makes you less likely to do it well.
With a very simple equality function like this, there's a good case to be made that you don't need to test it at all. If you must test it, testing one case for equality, and one case for inequality, is good enough. If you want to really go crazy, you can have one test for equality, and six tests for inequality in each of your members.
It's better to write out the test cases individually and manually, because you should be able to have faith in the correctness of a unit test simply by looking at it. If you can't, then you can end up in the rather absurd position of having to unit test your unit tests.
However, if you really absolutely insist on doing things the wrong way and exhaustively testing every possible combination, then you can do so like this:
func makeAddressPair(sames: [Bool]) -> (Address, Address) {
let firstName = ["John", sames[0] ? "John" : "Jane"]
let secondName = ["Smith", sames[1] ? "Smith" : "Williams"]
let street = ["Acacia Avenue", sames[2] ? "Acacia Avenue" : "Privet Close"]
let streetNumber = [10, sames[3] ? 10 : 21]
let city = ["Leeds", sames[4] ? "Leeds" : "Bolton"]
let country = [1, sames[5] ? 1 : 2]
return (Address(firstName: firstName[0], lastName: secondName[0], street: street[0],
streetNumber: streetNumber[0], city: city[0], countryCode: country[0]),
Address(firstName: firstName[1], lastName: secondName[1], street: street[1],
streetNumber: streetNumber[1], city: city[1], countryCode: country[1]))
}
class AddressCompareTests: XCTestCase {
func testAddressCompare() {
var comparesEqual = 0
var comparesInequal = 0
for a in [true, false] {
for b in [true, false] {
for c in [true, false] {
for d in [true, false] {
for e in [true, false] {
for f in [true, false] {
let (first, second) = makeAddressPair(sames: [a, b, c, d, e, f])
if first == second {
comparesEqual += 1
}
else {
comparesInequal += 1
}
}
}
}
}
}
}
XCTAssertEqual(comparesEqual, 1)
XCTAssertEqual(comparesInequal, 63)
}
}
You have 2 ^ 6 = 64 possible combinations, because you have six members, and between two structs, each pair of members can be either equal, or not equal. Here we have a helper function to generate a pair of structs based on a list of which members should be equal between them, and a nested loop to generate all possible combinations of pairs. When you compare all the pairs, exactly one should compare equal, and exactly 63 should compare unequal, so those are the test conditions we assert, since at least then we have some kind of poor cousin to quality control over the correctness of our test.
If instead you tested two or seven cases manually, it would be much simpler, clearer, easier to read and verify, and would take fewer lines of code, than doing it this way.

I'm a relative newbie on TDD but this what I have learned so far:
Helper function
func performNotEqualTestWithAddresssProperties(firstNameF: String, firstNameS: String, LastNameF: String, LastNameS: String, streetF: String, streetS: String streetNumberF: Int, streetNumberS: Int, cityF: String, cityS: String, countryCodeF : Int, countryCodeS : Int){
let AddressFirst = Address(firstName: firstNameF, LastName:LastNameF, street: streetF, streetNumber: streetNumberF, city : cityF, countryCode: countryCodeF)
let AddressSecond = Address(firstName: firstNameS, LastName:LastNameS, street: streetS, streetNumber: streetNumberS, city : cityS, countryCode: countryCodeS)
XCTAssertNotEqual(AddressFirst, AddressSecond, "Addresses are not equal")
// 'F' means first, 'S' means second, I just avoided to not have long lines
// The helper function takes 2 sets of Addresses and Asserts if they ARE equal
}
actual test
func testWhenAddressDiffers_SHouldNotBeEqual() {
performNotEqualTestWithAddresssProperties(firstNameF: "Earl", firstNameS: "Earl", LastNameF: "Grey", LastNameS: "Grey", streetF: "south", streetS: "south", streetNumberF: 23, streetNumberS: 23, cityF: "silicon valley", cityS: "silicon valley", countryCodeF: 24, countryCodeS: 29)
//write another and switch firstName,another and switch LastName, ...
Here I only tested countryCode. All other elements were the same.
to fully test your Address struct equality, each time you populate all properties of Address identical except for one. So basically you have to write this function 6 times.
You have 6 equalities to test. They are independent expressions and need to be treated independently. This is the best programmatic way I know of.

Related

Replace between substrings in swift?

I have a string like so:
let someString = "The (randomcharacters)(someknowncharacters) are playing in the NBA Finals"
I want to replace everything between the strings The and (some with the string Warriors. I have looked into using replacingOcurrences but that doesn't do what I want.
Here is another attempt using replaceOccurencesOf
func replace(_ original: String, between firstPart: String, and secondPart: String, with: String ) -> String {
let pattern = "\(firstPart) .* \(secondPart)"
let replacement = "\(firstPart) \(with) \(secondPart)"
return original.replacingOccurrences(of: pattern, with: replacement, options: .regularExpression)
}
It might need some adjustment in regard to handle space around the replaced word.
Example
let newString = replace("The Raptors are playing in the NBA Finals", between: "The", and: "are", with: "Warriors")
Using some "somerandomcharacters" would mean that you're essentially using a String as a (really shitty) vehicle to transport multiple pieces of data. But we already have a way to do that, with data types.
We can create a struct that holds the necessary information to describe a Basketball game. We can pass this data around throughout our app, with very easy access to its important components. Only our UI layer needs a string, so we only ever generate a string description of this struct at the very last moment, right at the UI layer.
struct BasketballTeam {
var name: String
}
struct BasketballGame: CustomStringConvertible {
let homeTeam: BasketballTeam
let awayTeam: BasketballTeam
let eventName: String
var description: String {
return "The \(homeTeam.name) are playing the \(awayTeam.name) in the \(eventName)."
}
}
let game = BasketballGame(
homeTeam: BasketballTeam(name: "Toronto Raptors"),
awayTeam: BasketballTeam(name: "Golden State Warriors"),
eventName: "NBA Finals"
)
print(game.description) // => The Toronto Raptors are playing the Golden State Warriors in the NBA Finals.

Objects with 4 optional String? properties with weighted value, how to return an object with the best match to a set of properties

So at first this seemed like a pretty straight-forward problem to solve, but there are some little catches i found, and my current end-solution is really ugly, so I'm curious to see what you guys can come up with
I have a class, OptionalObject.
It has four properties,
let prop1: String?
let prop2: String?
let prop3: String?
let prop4: String?
I then have an array of a thousand of these OptionalObject objects, and I'm guaranteed that there are no two objects with the exact same properties.
EX: (OptionalObject: prop1, prop2, prop3, prop4
Obj1: ABC, 123, Hello, Goodbye
Obj2: DEF, 456, nil, nil
Obj3: nil, nil, Hello, Goodbye
Obj4: nil, nil, Hello, nil
Obj5: ABC, nil, nil, nil
Obj6: ABC, nil, Hello, Goodbye
Obj7: DEF, 123, nil, Goodbye
Obj8: DEF, nil, nil, nil
...
and then I have a singular object of another class that has all 4 Strings (non-optional)
(ConcreteObject: arg1: String, arg2: String, arg3: String, arg4: String)
I want to find the best matching OptionalObject to my ConcreteObject, based on these four properties.
Which I can think of two ways to do it, one is to use filter on all OptionalObjects, the other is to manually enumerate over all OptionalObjects, have nested if statements to check properties (which is effectively the same as filter anyways)
If I were to filter, it could be something like this:
let matchingOptionalObject = optionalObjectsArray.filter {return $0.prop1 == arg1 && $0.prop2 == arg2 && $0.prop3 == arg3 && $0.prop4 == arg4}
and boom I now have one object that matches... but only if it's an exact match.
If my ConcreteObject has (DEF, 123, Hello, Goodbye)
I wouldn't get any matches, because no OptionalObjects match exactly.
I would expect a return of Obj3, Obj4, Obj7, Obj8
So then naturally I think OK, let's first look at which arguments we have as non-nil, and then construct a query accordingly.
For this example, I'm going to pretend there's only two properties so it's easier to understand:
let matchingOptionalObject = optionalObjectsArray.filter {
if $0.prop1 != nil {
if $0.prop2 != nil {
return $0.prop1 == arg1 && $0.prop2 == arg2
} else {
return $0.prop1 == arg1
}
} else {
if $0.prop2 != nil {
return $0.prop2 == arg2
} else {
return false
}
}
}
But then the problem is that because there are 4 properties, there are like at least 10 different possibilities of unique nil arguments that we need to cover, and this becomes a really really ugly
Which is what I currently have, I have a bunch of ugly if statements checking the combination of nil arguments, and then constructing a filter query accordingly...
Which OK, we need to move on, so I guess we can deal with it and open an issue for figuring out a better solution later, but then there is one more requirement that makes this solution not work...
Which is that each property has a different weight.
Two Rules for picking the best match when there are multiple matches:
1) Pick the match that has the most properties that match
2) If the matches have the same number of properties that match, pick the match that has the highest weight.
Prop1 has the highest weight
Prop2 has the lowest
So with the example of ConcreteObject: (DEF, 123, Hello, Goodbye)
Matched OptionalObjects:
Obj3: nil, nil, Hello, Goodbye
Obj4: nil, nil, Hello, nil
Obj7: DEF, 123, nil, Goodbye
Obj8: DEF, nil, nil, nil
We would pick Obj7 as the best match because it has 3 matching properties
But let's say for some other example with a new ConcreteObject and a new set of OptionalObjects, we have this match:
Our new Matched OptionalObjects:
New1: nil, 999, nil, NiHao
New2: XYZ, 999, nil, nil
We would pick New2, because even though New1 and New2 both have 2 matching properties, New2 has the matching properties of higher weight.
So, there's the dilemma.
I'm hoping that I'm just not remembering some key concepts years ago in my undergrad algorithms class, and that there's some clean solution (maybe even something that Swift provides), but I'm near my wit's end-- so really any suggestions or insight anybody has is more than welcome
Here's one reasonable solution. See code comments for details.
struct OptionalObject {
let prop1: String?
let prop2: String?
let prop3: String?
let prop4: String?
}
struct ConcreteObject {
let prop1: String
let prop2: String
let prop3: String
let prop4: String
// Determine the score.
// "matches" counts the number of matching properties.
// "weight" gives 8 for the 1st property, 4 for the 2nd, 2 for the 3rd, 1 for the 4th. Adjust to suit your needs
func score(for opt: OptionalObject) -> (matches: Int, weight: Int) {
var matches = 0
var weight = 0
if opt.prop1 == self.prop1 { matches += 1; weight += 8 }
if opt.prop2 == self.prop2 { matches += 1; weight += 4 }
if opt.prop3 == self.prop3 { matches += 1; weight += 2 }
if opt.prop4 == self.prop4 { matches += 1; weight += 1 }
return (matches, weight)
}
// Compares two OptionalObject by getting the score of each
// against "self".
func compare(lhs: OptionalObject, rhs: OptionalObject) -> Bool {
let scoreL = score(for: lhs)
let scoreR = score(for: rhs)
// If the number of matches are the same, compare the weight
return scoreL > scoreR
}
}
// Test ConcreteObject
let concrete = ConcreteObject(prop1: "DEF", prop2: "123", prop3: "Hello", prop4: "Goodbye")
// List of OptionalObject
var optionals: [OptionalObject] = [
OptionalObject(prop1: nil, prop2: nil, prop3: "Hello", prop4: nil),
OptionalObject(prop1: "DEF", prop2: "456", prop3: nil, prop4: nil),
OptionalObject(prop1: "ABC", prop2: "123", prop3: "Hello", prop4: "Goodbye"),
OptionalObject(prop1: nil, prop2: nil, prop3: "Hello", prop4: "Goodbye"),
OptionalObject(prop1: "DEF", prop2: "456", prop3: "Hello", prop4: "Goodbye"),
//OptionalObject(prop1: nil, prop2: nil, prop3: nil, prop4: nil),
]
// Sort the list based on the ConcreteObject
let sorted = optionals.sorted { concrete.compare(lhs: $0, rhs: $1) }
print(sorted)
The results are sorted in the desired order. The first object in sorted has the highest score.
Mathematics, and specifically basic algebra, gives you the answer.
You need to define a binary relation on your OptionalObject instances, that is antisymmetric, transitive but not connex. Like < for integers.
Here is the signature:
func myRelation(_ o1: OptionalObject, _ o2: OptionalObject) -> Bool {
// compare the 4 member properties of o1 and o2, using weights if not nil
// return true if o1 matches best, otherwise return false
}
In your question, you have specified the rules you need to implement in this function. BUT NOTE THAT if the rules do not lead to an antisymmetric, transitive and not connex binary relation, your problem is not well defined: you need to have rules that define a total order, for a solution being available, mathematically speaking.
Now, your set of OptionalObject instances must be ordered using the Swift standard func sort(by: (Element, Element) -> Bool):
optionalObjectsArray.sort(by: myRelation)
Finally, the first object in the returned collection is the one you were looking for:
let myBestMatch = OptionalObjectsArray.sort(by: myRelation).first()

error: instance member 'tomato' cannot be used on type 'hamburger'

1.what I code
class hamburger {
var tomato: String
var patty: String
var bread: String
var number: Int
init(_ tomato: String, _ patty: String, _ bread: String, _ number: Int) {
self.tomato = tomato
self.patty = patty
self.bread = bread
self.number = number
}
init() {
self.tomato = "tomato"
self.patty = "patty"
self.bread = "bread"
self.number = 10
}
}
let sandwich = hamburger("texas" , "iii" , "iii" , 10)
print(hamburger.tomato)
2.error message
Playground execution failed:
error: dotinstall.playground:342:7: error: instance member 'tomato'
cannot be used on type 'hamburger'
print(hamburger.tomato)
^~~~~~~~~ ~~~~~~
3.The sample I followed
enter code here// Class
class User {
let name: String // property
var score: Int
init(name: String, score: Int) {
init(_ name: String, _ score: Int) {
self.name = name
self.score = score
}
init() {
self.name = "bob"
self.score = 30
}
}
//let tom = User(name: "tom", score: 23)
let tom = User("tom", 23)
print(tom.name)
print(tom.score)
let bob = User()
print(bob.name)
print(bob.score)
I have coded like 1 following 3, but I got a error message like 2.
what I did to solve
・anyway follow this sample to be like same
・studied a basic of class syntax, initializer, instance on website
・was looking for a mistypes
・I checked the order of property
I don't why it is not worked even if I just follow the sample code.
please give me tips on the solution.
thanks
You're making a mistake regarding object oriented programming. With hamburger.tomato you try to access the property tomato of the Class Hamburger, not the Object, which is sandwich here. So the solution would be:
print(sandwich.tomato)
In the future, what you might want to do is take a look at styling your code better. Classes(Hamburger) are written starting with an uppercased letter, while the objects, or instances(sandwich), of the Classes are written starting with a lowercased letter.

Counting number of instances inside a struct in swift

I'm very new to Swift and I can't figure this one out. I need to count the number of instances created inside a struct. Since I created 3 instances, how can I get the program to tell me there are three? I tried the exNames.count at the end, but that doesn't work... Thanks!
struct People {
let name: String
var age: Int
let sex: Character
}
var heather = People(name: "Heather", age: 32, sex: "F")
var peter = People(name: "Peter", age: 34, sex: "M")
var scott = People(name: "Scott", age: 27, sex: "M")
let exNames = [People]()
exNames.count
You want to use a static variable on the People struct. However, this does require overriding the default initializer.
struct People
{
static var instances = 0
let name:String
var age:Int
let sex:Character
init(name:String, age:Int, sex:Character)
{
self.name = name
self.age = age
self.sex = sex
People.instances += 1
}
}
var heather = People(name: "Heather", age: 32, sex: "F")
var peter = People(name: "Peter", age: 34, sex: "M")
var scott = People(name: "Scott", age: 27, sex: "M")
let exNames = [People]()
/* exNames.count only gives the number of People that are
contained in this particular array, which is zero. */
print(People.instances) // 3
If you want to decrement the count when the structs go out of scope, you need to upgrade to a class which provides a deinitializer deinit {}.
Note that the “proper” use cases for a static counter are exceedingly limited. It is very likely that the problem you are actually trying to solve would be better served by a different hammer.
By the way, you really shouldn’t be using Character to represent sex, as Character in Swift is very closely tied to strings, and so they are built and optimized for lexical purposes, not for flagging. It also opens the door for a lot of potential bugs, as Swift won’t be able to verify valid input as well (what if someone accidentally passes a sex value of "#"?) Instead, use the built in Bool type, or a custom enum if you need more functionality.
Looks like you wanted to create an array of people, in that case:
struct People {
let name: String
var age: Int
let sex: Character
}
var heather = People(name: "Heather", age: 32, sex: "F")
var peter = People(name: "Peter", age: 34, sex: "M")
var scott = People(name: "Scott", age: 27, sex: "M")
//This should be a var, because you are going to modify it
var exNames = [People]()
exNames.append(heather)
exNames.append(peter)
exNames.append(scott)
exNames.count

Swift dictionary all containing

Lets say I have dictionaries like below and wanted an array of red dogs. I figured I need to get an array of all the names of the type "dog" using the first dictionary, and then use the name key and the color to search the final dictionary to get ["Polly,"jake"]. I've tried using loops but can't figure out how to iterate through the dictionary.
var pets = ["Polly" : "dog", "Joey" : "goldfish", "Alex" : "goldfish", "jake" : "dog"]
var petcolor = ["Polly" : "red", "Joey" : "black", "Alex" : "yellow", "jake":red"]
The correct solution would seem to be to create a Pet struct (or class) and collate all of this information into a struct and build either an array or dictionary full of these values.
struct Pet {
let name: String
let type: String
let color: String
init(name: String, type: String, color: String) {
self.name = name
self.type = type
self.color = color
}
}
Now, let's build an array of these pets:
var goodPets = [Pet]()
for (petName, petType) in pets {
guard let petColor = petcolor[petName] else {
// Found this pet's type, but couldn't find its color. Can't add it.
continue
}
goodPets.append(Pet(name: petName, type: petType, color: petColor))
}
Now that we've filled out goodPets, pulling out any particular subset of Pets becomes very easy:
let redDogs = goodPets.filter { $0.type == "dog" && $0.color = "red" }
And although this answer looks like a lot of set up & legwork compared to other answers, the major advantage here is that once we build the goodPets array, any way we want to scoop pets out of there ends up being more efficient. And as we increase the number of properties the pets have, this becomes more and more true compared to the other answers.
If you'd rather store our model objects in a dictionary continuing to use the names as the keys, we can do that as well, but the filter looks a little bit stranger.
Building the dictionary looks mostly the same:
var goodPets = [String : Pet]()
for (petName, petType) in pets {
guard let petColor = petcolor[petName] else {
// Found this pet's type, but couldn't find its color. Can't add it.
continue
}
goodPets[petName] = (Pet(name: petName, type: petType, color: petColor))
}
But the filter is slightly different:
let redDogs = goodPets.filter { $0.1.type = "dog" && $0.1.color = "red" }
Note that in both cases, redDogs has the type [Pet], that is, an array of Pet values.
You can iterate through a dictionary like this:
for key in pets.keys() {
if pets[key] == "Dog" {
}
}
Or:
for (name, pet) in pets {
if pet == "Dog" {
}
}
nhgrif is probably correct about structure but, to answer the literal question:
let dogs = Set(pets.filter { $0.1 == "dog" }.map { $0.0 })
let redAnimals = Set(petscolor.filter { $0.1 == "red" }.map { $0.0 })
let redDogs = dogs.intersect(redAnimals)
Each filter is a block that operates on a (key, value) tuple, testing the value and ultimately creating a dictionary with only the matching (key, value) pairs. Each map then converts that filtered dictionary into an array by discarding the values and just keeping the keys.
Each array is turned into a set to support the intersect operation. The intersect then determines the intersection of the two results.