Binary operator '==' cannot be applied to operands of type '[String]' and 'String - swift

I am new to coding and I needed some help with answering this challenge.
Instructions:
// Given the two arrays below, write a function that takes a String as an input parameter and returns a Boolean value. The function should return true if the String input is in either array and it should return false if the String input is in neither array.
//
// Examples:
// Call your function and pass in the String "cat" as the input. Your function should return true
// Call your function and pass in the String "cow" as the input. Your function should return false
let array1 = ["dog", "cat", "bird", "pig"]
let array2 = ["turtle", "snake", "lizard", "shark"]
// Write your function below:
Here is where I have written my function:
func test(Animal:String){
let list = array1 + array2
for animal in list {
if list == animal {
print("true")
} else {
print("false")
}
}
}
test(Animal: "dog")
The error I am getting is:
Binary operator '==' cannot be applied to operands of type '[String]' and 'String under the if statement.
Help if you can and I apologize in advance if this is not formatted correctly.
Thanks!

Just a tip, in the future you should try adding a more descriptive question title, that is super vague and could mean anything.
Anyways your issue is with the line:
if list == animal {
The error you got is quite specific and tells you exactly what's going wrong. [String] is an array of strings while String is just a single item. So when you write for animal in list your taking one animal in that list at a time.
Comparing an animal to a list is not allowed. They are not the same type ([String] and String are different types, as the compiler error told you). You can only compare variables of the same type. So in your case a list (String array aka [String]) to a another list, or an animal (String) to an animal.
Now what you want to do is see if a string you got is in either array. There's actually a built in method in arrays that lets you do exactly this: .contains
func test(animal:String){
let list = array1 + array2
if list.contains(animal) {
print("true")
} else {
print("false")
}
}
or if you want to be extra concise with some good code you can try
func test(animal:String){
let list = array1 + array2
print(list.contains(animal)) // will be false if it doesn't so prints false, true if it does so prints true.
}
And another side note.. You want to be very careful doing an if else inside of a for loop where both the if and the else return. You shouldn't be making decisions after looking at only ONE element.
Because typically you want to check EVERY value before printing false, but when you have an if else, you'll return/print after checking ONLY the first value.
if you wanted to do it your way (no built in methods, iterating through the array) you would want to do something like this:
func test(animal:String){
let list = array1 + array2
for candidate in list {
if animal == candidate { // there's a match! we can return true
print("true ")
return // return says to exit the function since we no longer need to keep going this is appropriate
}
} // for loop ends
// we only return false once the ENTIRE for loop has run, because we know if it did exist, we would never get to this point in the code
// this part only runs once EVERY item has been checked and none were a match
print("false")
}
And finally as bilal says, never start variable names with capital letters. Typically capital letters mean its a type (like String). You'll notice I renamed Animal -> animal for you in my examples

Related

Append Values in NSSet To An Array of Type [String]

I am attempting to get the values from an NSSet in core data and append those values to an array of type String.
func addStepsToArray() {
if let steps = entity.steps {
for i in steps {
recipeStep.append(String(i))
}
}
}
entity.steps is the list of steps tied to a core data entity. This is an NSSet. I am trying to copy those values to an array of type String.
#State var recipeStep: [String]
When trying to do this in my for in loop, I receive the following error: No exact matches in call to initializer
If I remove the conversion of "I" to String, I receive the following error:
Cannot convert value of type NSSet.Element (aka Any) to expected argument type String
Any idea on how to get this to work?
NSSet is defined in Objective C, which didn't have generics. It's an untyped collection, so you don't statically know anything about its elements.
As you've noticed, your i variable isn't a String, it's an Any.
You're confusing type coercion ("casting") with type conversion. If i were a Double, you could call String(i) to invoke an initializer which takes a double, and processes into a String.
You tried something similar by calling String(i), where you're making the Swift compiler find an initializer on String with the signitiure init(_: Any).
There is no such initializer. And besides, that's not what you want. You don't want to create a new String from a different kind of value. You already have a string, it's just "hidden" behind an Any reference.
What you're looking for is to do a down-cast, from Any to String:
func addStepsToArray() {
if let steps = entity.steps {
for i in steps {
guard let step = i as? String else {
fatalError("Decide what to do if the value isn't a String.")
}
recipeStep.append(i as String)
}
}
}
I'll warn you though, there are several issues/blemishes with this code:
You're using a for loop to do what is ultimately just a mapping operation
Your computation doesn't return its ouput, and instead indirectly achieves its goal through a side-effect on recipeStep
Your computation doesn't take a its input as a parameter, and instead indirectly achieves its goal through a side-effect on entity
i is conventionally expected to be an integer index of a for loop iterating over a sequence of numbers. Here it's an Any (a String at runtime)
Here's what I would suggest instead:
func getRecipeSteps(from entity: MyEntityType) -> [String] {
guard let steps = entity.steps else { return [] }
return steps.map { step in
guard let stringStep = step as? String else {
fatalError("Decide what to do if the value isn't a String.")
}
return step
}
}
Then in the rest of your code (and your tests), you can write self.recipeSteps = getRecipeSteps(from: myEntity). Elegant!
If you're certain that these entity.steps values can only ever be strings, then you can boil this down to a single map with a force-cast:
func getRecipeSteps(from entity: MyEntityType) -> [String] {
entity.steps?.map { $0 as! String } ?? []
}
Just convert directly:
let set = Set(["1", "2", "3"])
let array = Array(set)
DDLog(set)//Set<String>)
DDLog(array)//[String]

Argument labels do not match any availble overloads

I'm trying to create an anagram tester, and I'm pretty sure the code I have should work, but I'm getting an error 'Argument labels '(_:)' do not match any available overloads' I've looked at the other posts regarding the same error, but I'm still not sure what this means or how to fix it.
var anagram1 : String!
var anagram2 : String!
var failure : Bool = false
var counter : Int = 0
print("Please enter first word: ")
anagram1 = readLine()
print("Please enter Second word: ")
anagram2 = readLine()
if anagram1.count == anagram2.count {
for i in anagram1.characters{
if (!failure){
failure = true
for y in anagram2.characters {
counter += 1
if i == y {
failure = false
anagram2.remove(at: String.Index(counter)) // error here
}
}
}
else {
print("these words are not anagrams")
break;
}
}
if (!failure) {
print("these words ARE anagrams")
}
}
else{
print ("these words aren't even the same length you fucking nonce")
}
To answer your first question: the error message Argument labels '(_:)' do not match any available overloads means that you've given a function parameter names or types that don't match anything Swift knows about.
The compiler is also trying to tell you what parameters to look at. '(_:)' says that you're calling a function with an unlabeled parameter. (That means a value without any parameter name. A common example of a function that would look like this is print("something"). In Swift documentation, this would look like print(_:).
Finally, overloads are ways to call a function with different information. Again using the print function as an example, you can call it multiple ways. A couple of the most common overloads would be:
// print something, followed by a newline character
print("something")
// print something, but stay on the same line
// (end with an empty string instead of the default newline character)
print("something", terminator: "")
Documented, these might look like print(_:) and print(_:, terminator:).
Note: these are broken down for explanation. The actual Swift documentation shows func print(_: Any..., separator: String, terminator: String) which covers a number of different overloads!
Looking at the line where the error occurs, you see a function call and an initializer (which is essentially a function). Documented, the way you've entered the parameters, the functions would look like: remove(at:) and String.Index(_:).
String.Index(_:) matches the parameters of the error message, so that's where your error is. There is no overload of the String.Index initializer that takes an unnamed parameter.
To fix this error, you need to find the correct way to create a String.Index parameter for the remove(at:) function. One way might be to try something like this:
for y in anagram2.characters.enumerated() {
// `y` now represents a `tuple`: (offset: Int, element: Character)
// so, you don't need `counter` anymore; use `offset` instead
if i == y.element { //`i` is a Character, so it can compare to `element`
...
let yIndex: String.Index = anagram2.index(anagram2.startIndex, offsetBy: y.offset)
anagram2.remove(at: yIndex)
...
}
}
However, there are other issues with your code that will cause further errors.
For one, you're looping through a string (anagram2) and trying to change it at the same time - not a good thing to do.
Good luck to you in solving the anagram problem!
Thanks for the help Leo but I found a way of doing it :)
if anagram1.count == anagram2.count {
for i in anagram1.characters{
if (!failure){
counter = -1
failure = true
for y in anagram2.characters {
counter += 1
if i == y {
failure = false
if counter < anagram2.count {
anagram2.remove(at: (anagram2.index(anagram2.startIndex, offsetBy: counter)))
break;
}
}
}
}

How to handle initial nil value for reduce functions

I would like to learn and use more functional programming in Swift. So, I've been trying various things in playground. I don't understand Reduce, though. The basic textbook examples work, but I can't get my head around this problem.
I have an array of strings called "toDoItems". I would like to get the longest string in this array. What is the best practice for handling the initial nil value in such cases? I think this probably happens often. I thought of writing a custom function and use it.
func optionalMax(maxSofar: Int?, newElement: Int) -> Int {
if let definiteMaxSofar = maxSofar {
return max(definiteMaxSofar, newElement)
}
return newElement
}
// Just testing - nums is an array of Ints. Works.
var maxValueOfInts = nums.reduce(0) { optionalMax($0, $1) }
// ERROR: cannot invoke 'reduce' with an argument list of type ‘(nil, (_,_)->_)'
var longestOfStrings = toDoItems.reduce(nil) { optionalMax(count($0), count($1)) }
It might just be that Swift does not automatically infer the type of your initial value. Try making it clear by explicitly declaring it:
var longestOfStrings = toDoItems.reduce(nil as Int?) { optionalMax($0, count($1)) }
By the way notice that I do not count on $0 (your accumulator) since it is not a String but an optional Int Int?
Generally to avoid confusion reading the code later, I explicitly label the accumulator as a and the element coming in from the serie as x:
var longestOfStrings = toDoItems.reduce(nil as Int?) { a, x in optionalMax(a, count(x)) }
This way should be clearer than $0 and $1 in code when the accumulator or the single element are used.
Hope this helps
Initialise it with an empty string "" rather than nil. Or you could even initialise it with the first element of the array, but an empty string seems better.
Second go at this after writing some wrong code, this will return the longest string if you are happy with an empty string being returned for an empty array:
toDoItems.reduce("") { count($0) > count($1) ? $0 : $1 }
Or if you want nil, use
toDoItems.reduce(nil as String?) { count($0!) > count($1) ? $0 : $1 }
The problem is that the compiler cannot infer the types you are using for your seed and accumulator closure if you seed with nil, and you also need to get the optional type correct when using the optional string as $0.

Filter Array if Current Time is within TimeRange

My current function filters the array and returns an array of PFObjects with only "Type" = "Sushi". Now, I am trying to filter the array if current time is within a time range ("OpenHours" and "CloseHours")
The new Function passes dayOfWeek: Int, timeNow: String
"OpenHours" Example: [0, 6] = [sunday, monday]
["0011","0011","0011","0011","0011","0011","0011"]
"CloseHours" Example:
["2350","2350","2350","2350","2350","2350","2350"]
Current Function that filters "Type":
func filterRestaurants(filteredObject: String) {
//func filterOpenNow(dayOfWeek: Int, timeNow: String){
filteredRestaurantArray = unfilteredRestaurantArray.filter() {
if let type = ($0 as PFObject)["Type"] as? String { // Get value of PFObject
return type.rangeOfString("Sushi") != nil
} else {
println("nope")
return false
}
}
Basically I need to filter for objects where current time timeNow is between OpenHours and CloseHours for a given dayOfWeek
Edit:
What I've Tried so far:
I'm unsure how to get the position of a value in the PFObject array. Normal I would check if the timeNow is between OpenNow[dayOfWeek] and CloseNow[dayOfWeek]
Something like this (except with filter):
if OpenNow[dayOfWeek] ... CloseNow[dayOfWeek] ~= timeNow {
println("success")
}
I am not entirely sure what the properties of your PFObject instance are, so I'm going to make some assumptions and you will have to correct the keys to meet your needs.
Working with the function signature you provided is not entirely possible. This is because in order for it to work you would be accessing a list of PFObjects that were not provided to the function. While Swift does not keep you from doing that, it is generally not a wise design choice as you cannot be confident of the results of the function at any given point in time. So, to provide what is called referential transparency we will modify your function to also take in the collection of PFObjects.
With that collection we will then use the native filter function to determine which ones to return from the function. The filter function takes a collection and a closure. The closure takes one parameter, an element of the collection, and return a Bool. The closure is what determines if an element is kept or discarded.
Knowing that, we can build up the functionality you requested. I cannot guarantee there isn't a better way to do the first if let dance at the beginning, but it should get the job done.
func filterAreOpen(restaurants: [PFObject], forTime time: String, onDay day: Int) -> [PFObject] {
let openRests = filter(restaurants) { r in
if let openHours = r["OpenHours"] as AnyObject? as? [String] {
if let closeHours = r["CloseHours"] as AnyObject? as? [String] {
switch (openHours[day].toInt(), closeHours[day].toInt(), time.toInt()) {
case let (.Some(oh), .Some(ch), .Some(t)):
return oh...ch ~= t
default:
return false
}
}
}
return false
}
return openRests
}
And you would use it like this
let rests = [ // example objects that are replaced by your PFObject instances
[
"OpenHours":["0011","0012","0013"],
"CloseHours":["0023","0023","0023"],
"Name":"Restaurant1"
],
[
"OpenHours":["0014","0015","0016"],
"CloseHours":["0020","0020","0020"],
"Name":"Restaurant2"
]
]
let openRestaurants = filterAreOpen(rests, forTime: "0012", onDay: 1)
/* Results
[{
CloseHours = (
0023,
0023,
0023
);
Name = Restaurant1;
OpenHours = (
0011,
0012,
0013
);
}]
*/
Edit:
A quick explanation about the switch inside the closure. In Swift the switch statement is much more powerful than it was in the Objective-C days. It is capable of matching a value against a pattern, not just numbers.
So, in this case, the switch is matching a 3-tuple of Int?, (Int?, Int?, Int?). This is because String's toInt() method returns an Int?. switch is also able to bind matched patterns to local scoped constants. To do that we use the let keyword. To pattern match on an Optional value, you use the .Some(x) pattern. Since we have a 3-tuple we use .Some(x) three time, with the x replaced by some meaningful name. This way we have access to the three values we are interested in if the three toInt() calls evaluated to non-nil values.
If any of the String values could not evaluate to an Int, and so was nil, the default case is used, and returns false.
You can view the language book on conditional statements here.

Mixing for-in and if-let in Swift

Can I merge a for-in and if-let in one statement?
for item in array {
if let f = item as? NSDictionary {
result.addObject(newFile(f))
}
}
array is made by a JSON, so I don't know if each item is a NSDictionary or not. I have to check.
I was looking for something like this:
for item as? NSDictionary in array {
// code
}
Like Python or Ruby.
#nickfalk is on the right track, but we can do better. His result unfortunately returns [AnyObject], which you can't then call newFile with (I assume). But that's ok, we can get the rest of the way pretty easily.
What you want is partial map. That is to say, you want to map some (but possibly not all) of the elements of one list to another list (from AnyObject to File, if we can). So there must be some rule for choosing, and some rule for mapping. Optional let's us combine those. Let's call the function that does that f. Then its type is:
f: T->U?
So there's some magic function that will possibly convert T to U. We want to map with that. Sounds easy:
extension Array {
func partialMap<U>(f: T->U?) -> [U] {
var result = [U]()
for x in self {
if let u = f(x) {
result.append(u)
}
}
return result
}
}
So now we've hidden all the nasty mutation and var and whatnot down deep where we don't have to look at it. We have a function that takes a mapping function from "something" to "maybe something else" and returns a list of "something elses that we could map."
Now everything is nice and immutable and reusable:
let result = array.partialMap { ($0 as? NSDictionary).map(newFile) }
Whoa there. What's that map in the middle? Well, as? returns NSDictionary?. When you map an optional, then if the optional is None, it returns None, otherwise it applies the function to the value and wraps it in Some. So this whole thing takes AnyObject and returns File? just like we wanted. One partialMap later we have our answer.
I would probably just go for something like:
let result = array.filter() { $0 is NSDictionary }
If you need result to be an NSDictionary array, you can just cast it:
let result = array.filter() { $0 is NSDictionary } as [NSDictionary]
If your goal is to reduce an NSArray to an array only containing NSDictionary filter is a very powerful tool. Create the appropriate filtering function:
func filterForNSDictionary(object: AnyObject) -> Bool{
return object.isKindOfClass(NSDictionary)
}
Then simply pass in you array and function to the filter function
let result = filter(array, filterForNSDictionary)
As #RobNapier points out my solution above will end up with a result array being of the type [AnyObject] this can of course easily be remedied:
let result = filter(array, filterForNSDictionary) as [NSDictionary]
This could be considered risky, if you force the array to be of the wrong type. as [NSString] (for instance9 would most likely blow up in your face down the line...
Rob's solution being pure awesome cleverness of course and #MattGibson delivering the perfect shorthand, while exposing me as an absolute beginner in this field.