Alright, this should not be too difficult, but Sunday morning proves me wrong...
I have an Array with structs, and want to remove only one struct that matches its name property to a String. For example:
struct Person {
let name: String
}
var myPersons =
[Person(name: "Jim"),
Person(name: "Bob"),
Person(name: "Julie"),
Person(name: "Bob")]
func removePersonsWith(name: String) {
myPersons = myPersons.filter { $0.name != name }
}
removePersonsWith(name: "Bob")
print(myPersons)
results in:
[Person(name: "Jim"), Person(name: "Julie")]
But how do I only remove one Bob?
filter filters all items which match the condition.
firstIndex returns the index of the first item which matches the condition.
func removePersonsWith(name: String) {
if let index = myPersons.firstIndex(where: {$0.name == name}) {
myPersons.remove(at: index)
}
}
However the name of the function is misleading. It's supposed to be removeAPersonWith ;-)
Related
I'm working on a project where I am given a user and a birthdate for them, along with a list of movies and dates that the person has gone to. An example string is something like this: "Participant Name: Example name, Birthdate: 01/11/2000, Spiderman 05/15/2021 07/16/2021 08/17/2021 Avengers Infinity War 05/15/2020 07/16/2020 08/17/2020 The Lorax 01/05/2015" and so on. I know which movies the string will contain, and I know the maximum amount of dates per movie, but I don't know the specific number of times the person would have seen the movie. I've done the following for the birthdate, which works only because the request is always formatted the same way in terms of birthdates:
func FindBirthdate(str: String) -> Date{
//I make sure that the text before birthdate is always converted to DOB
//in other functions, and that the string is converted
//to an array seperated by spaces.
let index = str.firstIndex(of: "DOB:")!
let birthdate = str[index+1]
print(birthdate)
let formatter = DateFormatter()
formatter.dateFormat = "MM/dd/yyyy"
formatter.locale = Locale(identifier: "COUNTRY IDENTIFIER")
formatter.timeZone = TimeZone(identifier: "EXAMPLE")
return formatter.date(from: birthdate) ?? Date()
}
However, as I previously stated, I don't know how many times the user would have seen the movie. The movies will all be in the same order. How would I split the characters in between each movie into dates for that movie? Once again, my problem is that I don't know the number of dates, so how would I get each date and return a list? I've looked into a sort of ForEach statement, but I'm not sure how I could integrate that into a string. This answer suggested that I use regexes, however, this solely focuses on the dates, and not the movies. The string isn't solely made up of dates. I've also taken a look at sample date parsing in Swift, but that is about just single dates. My issue isn't date conversion, it's finding and separating the dates in the first place. On Meta someone also suggested that I try splitting. I have looked at Splitting on Apple Developer, and it seems like a good solution, but I'm not sure what I would split by. To show that sample string again, "Participant Name: Example name, Birthdate: 01/11/2000, Spiderman 05/15/2021 07/16/2021 08/17/2021 Avengers Infinity War 05/15/2020 07/16/2020 08/17/2020 The Lorax 01/05/2015". The movie names will always be only these - they will not ever have numbers. The dates will also always be in teh same MM/DD/YYYY format. The names are immediately before the dates, and there is no separator other than a space.
The reason that this question hasn't been asked before is that though other questions may ask about date parsing or finding substrings, I need to find each individual date for each movie and the movie title - this is trying to find each individual date in the string for each movie.
Does this work for you?
I am assuming you are exactly following the text format in your example.
extension String {
func match(_ regex: String) -> [[String]] {
let nsString = self as NSString
return (try? NSRegularExpression(pattern: regex, options: []))?.matches(in: self, options: [], range: NSMakeRange(0, nsString.length)).map { match in
(0..<match.numberOfRanges).map { match.range(at: $0).location == NSNotFound ? "" : nsString.substring(with: match.range(at: $0)) }
} ?? []
}
}
Then:
func getName(text: String) -> String? {
guard let match = text.match("(?<=Participant Name: )(.*)(?=, Birthdate)").first else { return nil }
return match.first
}
func getBirthDay(text: String) -> String? {
guard let match = text.match("(?<=Birthdate: )(.*)(?=, )").first else { return nil }
return match.first
}
func getMovies(text: String) -> [String: [String]] {
var result: [String: [String]] = [:]
guard let moviesString = text.match("(?<=Participant Name: \(getName(text: text)!), Birthdate: \(getBirthDay(text: text)!), )(.*)").first?.first else { return result }
let asArray = moviesString.components(separatedBy: " ")
var key: String = ""
var values = [String]()
var lastKey: String = ""
for item in asArray {
if !isDate(item) {
values.removeAll()
key += key != "" ? (" " + item) : item
lastKey = key
continue
} else {
key = ""
if var existingValues = result[lastKey] {
existingValues.append(item)
result[lastKey] = existingValues
} else {
result[lastKey] = [item]
}
}
}
return result
}
func isDate(_ string: String) -> Bool {
return !string.match("[0-9]{2}(/)[0-9]{2}(/)[0-9]{4}").isEmpty
}
To test:
let text = "Participant Name: Example name, Birthdate: 01/11/2000, Spiderman 05/15/2021 07/16/2021 08/17/2021 Avengers Infinity War 05/15/2020 07/16/2020 08/17/2020 The Lorax 01/05/2015"
let movies = getMovies(text: text)
print(">>>> \(movies["Spiderman"])")
output:
Optional(["05/15/2021", "07/16/2021", "08/17/2021"])
I know I'm doing something wrong but I do not know what..
All I would like to do here is add some pet to my Array<Pet>
What am I doing wrong here?
The error is
Cannot use mutating member on immutable value: function call returns immutable value
import UIKit
struct Person {
let id = UUID()
let name: String
var pets: [String]
}
final class City {
let people = [Person]()
func addPetFor(id: UUID, newPets: [String]) {
self.people.findPerson(by: id).pets.append(contentsOf: newPets) //<-- error Cannot use mutating member on immutable value: function call returns immutable value
}
}
extension Array where Element == Person {
func findPerson(by id: UUID) -> Person {
let foundPerson = self.first { person in
person.id == id
}
return foundPerson!
}
}
Structs have value semantics. When you pass them around/assign them to things, they get copied, and unless it is var, you can't mutate them.
Return values are not vars, you cannot change a return value. You can put it in a var variable first, and then append to pets
var person = self.people.findPerson(by: id)
person.pets.append(contentsOf: newPets)
This makes the compiler error go away, but person here is a copy of the return value, so your modification here doesn't actually change the things in the self.people array. self.people is declared a let constant in the first place, so you better change that too:
var people = [Person]()
One way is to have findPerson return the index of the person found:
extension Array where Element == Person {
func findPerson(by id: UUID) -> Int {
let foundPerson = self.firstIndex { person in
person.id == id
}
return foundPerson!
}
}
This way, you can do:
let index = self.people.findPerson(by: id)
self.people[index].pets.append(contentsOf: newPets)
Note how I am doing things directly on self.people, rather than through another variable. addPetFor also needs to be marked mutating.
Another way is to have findPerson take a closure, and you write in that closure what you want to do with the found person:
extension Array where Element == Person {
mutating func findPerson(by id: UUID, andDo block: (inout Person) -> Void) -> Int {
let index = self.firstIndex { person in
person.id == id
}
block(&self[index!])
}
}
Note how findPerson is now marked mutating, because it now mutates the array (self). self[index] is passed by inout, which is kind of like "by reference".
Then you can do:
self.people.findPerson(by: id) {
$0.pets.append(contentsOf: newPets)
}
As the error tells you, your findPerson method is returning an immutable struct, so you can't edit it.
There are a few solutions to this -- two are included in the code sample below:
final class City {
var people = [Person]()
func addPetFor(id: UUID, newPets: [String]) {
guard let index = self.people.firstIndex(where: { $0.id == id }) else {
assertionFailure("Not found")
return
}
self.people[index].pets.append(contentsOf: newPets)
}
func addPetFor2(id: UUID, newPets: [String]) {
self.people = self.people.map {
if $0.id == id {
var personCopy = $0
personCopy.pets.append(contentsOf: newPets)
return personCopy
} else {
return $0
}
}
}
}
In both cases, the code has a reference to where the Person belongs in the people array, so it can modify it. Note that people also has to be var, not let, for the same reason -- it needs to be mutable.
I'm trying to perform a filter on an array of structs whereby if the element exists return the element, otherwise return the first element in the array. At the moment I have a func to do this, was wondering if there was a more efficient way. Predicate or Array extension?
For example, if this is my array of structures and method:
struct Person {
let name: String
let age: Int
}
let items = [Person(name: "Fred", age: 12),
Person(name: "Bill", age: 14),
Person(name: "Jane", age: 15),
Person(name: "Mary", age: 12)]
// Find the person based on name, if no match, return first item
func filterOrFirst(with name: String? = "") -> Person?
{
if (items.contains(where: {$0.name == name}))
{
return items.first(where: {$0.name == name})
}
return items.first
}
print(filterOrFirst(with: "Bill")) // prints Bill
print(filterOrFirst()) // prints Fred
You can do it like
func filterOrFirst(with name: String? = "") -> Person? {
if let item = items.first(where: {$0.name == name}) {
return item
}
return items.first
}
So you need not to traverse complete array two times. You can use ternary operator here. But it increases compilation time.
You can get idea from this:
var arrData:[String] = ["Cat","Dog","Horse","Sheep"]
func respondTheValue(strSearch:String) -> String{
let stringToSearch = strSearch
if let index = arrData.index(of: stringToSearch){ //Check if data is present or not
return arrData[index]
}else{ // Data not present then return first
return arrData.first!
}
}
And use it Like:
let responseStr = respondTheValue(strSearch: "Dog")
print(responseStr) //Case True : Output -- Dog
let responseStr1 = respondTheValue(strSearch: "Fish")
print(responseStr1) //Case False : Output -- Cat
Hope this helps.
I have an nsobject class with four strings
class Post: NSObject {
var author: String!
var postID: String!
var pathToImage: String!
var userID: String!
}
I also have a separate class viewcontroller which has a function grabbing posts from firebase. I have an array called posts = [Post](), which is filled by a seperate function going through firebase and grabbing data for each photo. I also have an array called removeArray which is array of strings, which the string is the postID of certain posts. Now this is my problem, I am trying to loop through removeArray, check if the each in removeArray = to the each in posts.postID and check if they are equal. Then either I delete that each in posts.postID post, or I create a new array that is posts - posts with postID's in removeArray. Here is my code now that does not work, it just keeps posts as is.
if posts != nil {
if var array = UserDefaults.standard.object(forKey: "removeArray") as? [String] {
for each in posts {
for one in array {
if one == each.postID {
new.append(each)
}
}
}
return self.posts.count
}
}
So if you have any idea how to take a string in an array, check if that string if eqaul to a string in an array of objects.postID, and remove that object from the array if it is equal. I have tried to research a way to filter it, but so far nothing. Please give me some feedback. Thanks
My problem = http://imgur.com/a/m5CiY
var posts = [p1,p2,p3,p4,p5]
let array = ["aaa","bbb"]
var new:Array<Post> = []
for each in posts {
for one in array {
if one == each.postID {
new.append(each)
}
}
}
print("This objects should be remvoed: \(new)")
posts = Array(Set(posts).subtracting(new))
print("After removing matching objects: \(posts)")
You could use reduce(_:_:)!
class Country {
var name: String!
init(name: String) {
self.name = name
}
}
let countries = [Country(name: "Norway"), Country(name: "Sweden"), Country(name: "Denmark"), Country(name: "Finland"), Country(name: "Iceland")]
let scandinavianCountries = ["Norway", "Sweden", "Denmark"]
// Store the objects you are removing here
var nonScandinavianCountries: [Country]?
let scandinavia = countries.reduce([Country](), {
result, country in
// Assign result to a temporary variable since result is immutable
var temp = result
// This if condition works as a filter between the countries array and the result of the reduce function.
if scandinavianCountries.contains(country.name) {
temp.append(country)
} else {
if nonScandinavianCountries == nil {
// We've reached a point where we need to allocate memory for the nonScandinavianContries array. Instantiate it before we append to it!
nonScandinavianCountries = []
}
nonScandinavianCountries!.append(country)
}
return temp
})
scandinavia.count // 3
nonScandinavianCountries?.count // 2
Resouces:
https://developer.apple.com/reference/swift/array/2298686-reduce
I have searched for this issue and I did not find any solution that is working for me using the latest version of Xcode and Swift. I use three arrays:
1. baseArray[Meal]: array filled with every meal. Not locally saved.
2. favoritesArray[Favorite]: with names of all favorite meals, locally saved by the user with NSKeyedArchiver.
3. filteredArray[Meal]: baseArray but filtered for searchterm. In code:
(filteredArray = baseArray.filter { $0.type == searchtext }}
I use the last array in the tableView. If they want to see all meals, the filteredArray is the same as the baseArray.
My question: how can I get the filteredArray that it has all favorite meals (so where Meal.title == Favorite.name). How do i combine three arrays?
I have tried a lot of options in the last week, but none has worked.. I hope You can help me out!!
This does what you want:
struct Meal {
let title: String
}
struct Favorite {
let name: String
}
let meal1 = Meal(title: "Soup")
let meal2 = Meal(title: "Stew")
let meal3 = Meal(title: "Pizza")
let favorite1 = Favorite(name: "Stew")
let baseArray = [meal1, meal2, meal3]
let favoritesArray = [favorite1]
let favoriteNames = favoritesArray.map { $0.name }
let filteredArray = baseArray.filter { favoriteNames.contains($0.title) }
This is the solution for you if I correctly understand your question.
struct Meal {
let name: String
}
struct Favorite {
let name: String
}
let baseArray = [Meal(name: "Meal1"), Meal(name: "Meal2"), Meal(name: "Meal3")]
let favoritesArray = [Favorite(name: "Meal1")]
let searchText = "Meal3"
let filteredArray = baseArray.filter { $0.name == searchText }
print(filteredArray)
// [Meal(name: "Meal3")]
let combinedArray = baseArray.reduce(filteredArray) { array, element in
// First condition check if the current meal (element) in contained in the favorites
// Second checks if the favorite meal isn't already in the filteder array
if favoritesArray.contains(where: { $0.name == element.name }) &&
!filteredArray.contains(where: { $0.name == element.name }) {
return array + [element]
}
return array
}
print(combinedArray)
// [Meal(name: "Meal3"), Meal(name: "Meal1")]