I have a class called Person:
class Person{
let name: String?
let areas: [Area]
}
And Area consisting of the following:
class Area{
let id: String
let name: String
}
I've created a "people" array and what I want to do is check the "people" array if area.name = "Brooklyn". If it doesn't remove that person from the "people" array.
Here is what I have tried but it doesn't work:
var people = [Person]()
for s in people{
for a in s.preferredArea{
if a.areaName != "Brooklyn"{
let index = people.indexOf(s)
people.removeAtIndex(index!)
}
}
}
Can anyone point me into the right direction please?
To use indexOf, you'll have to make Person conforming the protocol Equatable:
class Person: Equatable {
let name: String
var area: [Area]
init(name: String) {
self.name = name
self.area = []
}
}
func ==(lhs: Person, rhs: Person) -> Bool {
return lhs.name == rhs.name &&
lhs.area == rhs.area // You'll have to make Area equatable too
}
Having this done, your code just works fine. If you don't want to use this method, you can use:
people.enumerate().forEach {
if !$0.1.area.contains({ $0.name == "Brooklyn" }) {
people.removeAtIndex($0.0)
}
}
Or if you can use filter to create a new array:
var newPeople = people.filter { $0.area.contains { $0.name == "Brooklyn" } }
Related
My app consists on an image of various foods, in which the user taps the image and adds this food into a Set<Food>.
I want to show all items from this Set inside the class called Favorites, as a: Text("You like: \(favorites.comidas)") but I can't manage to make it work
class Favorites: ObservableObject {
var foods: Set<Food>
}
class Favorites: ObservableObject {
var foods: Set<Food>
init() {
// load the saved data
foods = []
}
func contains(_ food: Food) -> Bool {
foods.contains(food)
}
func add(_ food: Food) {
objectWillChange.send()
foods.insert(food)
save()
}
func delete(_ food: Food) {
objectWillChange.send()
foods.remove(food)
save()
}
}
struct Food: Identifiable, Hashable {
var id: Int
let name: String
let foodImage: [String]
// Equatable
static func == (lhs: Food, rhs: Food) -> Bool {
lhs.id == rhs.id
}
}
#EnvironmentObject var favorites: Favorites
let food: Food
var body: Some View {
Image(food.foodImage[0])
.onTapGesture {
if favorites.contains(food) {
favorites.delete(food)
} else {
favorites.add(food)
}
}
}
You haven't shown your Food structure, but I will assume it has a property, name.
ListFormatter is your friend with a task like this. Its string(from:[]) function takes an array and returns it in a nicely formatted list. You can use map to get an array of name strings from your set.
For the input array ["pizza","tacos","chocolate"] it will give "pizza, tacos and chocolate"
var favoriteList: String {
let formatter = ListFormatter()
let favorites = formatter.string(from:self.favorites.foods.map{$0.name})
return favourites ?? ""
}
Then you can use this function in a Text view:
Text("You like \(self.favoriteList)")
Note that a Set is unordered, so it might be nice to sort the array so that you get a consistent, alphabetical order:
var favoriteList: String {
let formatter = ListFormatter()
let favorites = formatter.string(from:self.favorites.foods.map{$0.name}.sorted())
return favourites ?? ""
}
Thanks to a tip from Leo Dabus in the comments, in Xcode 13 and later you can just use .formatted -
var favoriteList: String {
return self.favorites.foods.map{$0.name}.sorted().formatted() ?? ""
}
Hi I have a task to implement the Fleet protocol which has two functions:
addNewCar - adds a new car object to the Fleet.
- Parameter car: car to add to the Fleet
- Returns: false if the car with same id already exists in the Fleet, true – otherwise.
deleteCar - Deletes the car with the specified id from the Fleet.
- Returns: true if the car with same id existed in the Fleet, false – otherwise.
listCarsByModel - returns 10 car models containing the specified string.
If there are several cars with the same model, brand's name is added to car's model in the format "brand - car",
otherwise returns simply "car".
listCarsByBrand - returns 10 car models whose brand contains the specified string,
result is ordered by brand.
struct Car {
let id: String; // unique identifier
let model: String;
let brand: String;
}
protocol Fleet {
func addNewCar(car: Car) -> Bool
func deleteCar(id: String) -> Bool
func listCarsByModel(searchString: String) -> Set<String>
func listCarsByBrand(searchString: String) -> [String]
}
class FleetImpl: Fleet {
var cars: [Car] = []
func addNewCar(car: Car) -> Bool {
if let i = cars.firstIndex(where: { $0.id == car.id }) {
print(i)
return false
} else {
cars.append(car)
print(car)
return true
}
}
func deleteCar(id: String) -> Bool {
return true
}
func listCarsByModel(searchString: String) -> Set<String> {
}
func listCarsByBrand(searchString: String) -> [String] {
}
}
I've used method firstIndex(where:). But the function adds new car to the array with existing id. (i.e. two or more cars with the same id)
How can I access that 'id' property of Car struct in order to manipulate that data?
Please help me out
This is the test code:
func test(fleet: Fleet) {
assert(!fleet.deleteCar(id: "1"))
assert(fleet.addNewCar(car: Car(id: "1", model: "1", brand: "Lex")))
assert(!fleet.addNewCar(car: Car(id: "1", model: "any name because we check id only", brand: "any brand")))
assert(fleet.deleteCar(id: "1"))
assert(fleet.addNewCar(car: Car(id: "3", model: "Some Car3", brand: "Some Brand2")))
assert(fleet.addNewCar(car: Car(id: "4", model: "Some Car1", brand: "Some Brand3")))
var byModels: Set<String> = fleet.listCarsByModels(searchString: "Car")
assert(byModels.count == 10)
byModels = fleet.listCarsByModels(searchString: "Some Car")
assert(byModels.count == 4)
assert(byModels.contains("Some Brand3 - Some Car1"))
assert(byModels.contains("Some Car2"))
assert(byModels.contains("Some Car3"))
assert(!byModels.contains("Some Car1"))
assert(byModels.contains("Some Brand1 - Some Car1"))
var byBrand: [String] = fleet.listCarsByBrand(searchString: "Brand")
assert(byBrand.count == 10)
byBrand = fleet.listCarsByBrand(searchString: "Some Brand")
assert(byBrand.count == 4)
assert(byBrand[0] == "Some Car1")
assert(byBrand[1] == "Some Car2" || byBrand[1] == "Some Car3")
assert(byBrand[2] == "Some Car2" || byBrand[2] == "Some Car3")
assert(byBrand[3] == "Some Car1")
}
test(fleet: FleetImpl())
Maybe a set instead of an array would work better in this case as we're dealing with unique elements. Also note that rather than using firstIndex(where) I'm using first(where) which will return the car rather than index.
import UIKit
struct Car: Hashable {
let id: String
let model: String
let brand: String
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
protocol Fleet {
func addNewCar(car: Car) -> Bool
func deleteCar(id: String) -> Bool
func listCarsByModel(searchString: String) -> [String]
func listCarsByBrand(searchString: String) -> [String]
}
class FleetImpl: Fleet {
var cars = Set<Car>()
func addNewCar(car: Car) -> Bool {
guard !cars.contains(where: { $0.id == car.id }) else { return false }
cars.insert(car)
return true
}
func deleteCar(id: String) -> Bool {
guard let car = cars.first(where: { $0.id == id }) else { return false }
cars.remove(car)
return true
}
func listCarsByModel(searchString: String) -> [String] {
let searchedCars = cars.filter { $0.model.lowercased().contains(searchString.lowercased()) }.sorted { $0.brand < $1.brand }
var formattedModels = [String]()
searchedCars.forEach { car in
if let car = searchedCars.first(where: { $0.model == car.model && $0.id != car.id }) {
let carName = [car.model, car.brand].joined(separator: " - ")
formattedModels.append(carName)
} else {
formattedModels.append(car.model)
}
}
return Array(formattedModels.prefix(10))
}
func listCarsByBrand(searchString: String) -> [String] {
let searchedBrands = cars.filter { $0.brand.lowercased().contains(searchString.lowercased()) }.sorted { $0.brand < $1.brand }.map { $0.model }
return Array(searchedBrands.prefix(10))
}
}
I have an array, with custom objects.
I Would like to pop the repeated objects, with the repeated properties:
let product = Product()
product.subCategory = "one"
let product2 = Product()
product2.subCategory = "two"
let product3 = Product()
product3.subCategory = "two"
let array = [product,product2,product3]
in this case, pop the product2 or product3
Here is an Array extension to return the unique list of objects based on a given key:
extension Array {
func unique<T:Hashable>(map: ((Element) -> (T))) -> [Element] {
var set = Set<T>() //the unique list kept in a Set for fast retrieval
var arrayOrdered = [Element]() //keeping the unique list of elements but ordered
for value in self {
if !set.contains(map(value)) {
set.insert(map(value))
arrayOrdered.append(value)
}
}
return arrayOrdered
}
}
using this you can so this
let unique = [product,product2,product3].unique{$0.subCategory}
this has the advantage of not requiring the Hashable and being able to return an unique list based on any field or combination
You can use Swift Set:
let array = [product,product2,product3]
let set = Set(array)
You have to make Product conform to Hashable (and thus, Equatable) though:
class Product : Hashable {
var subCategory = ""
var hashValue: Int { return subCategory.hashValue }
}
func ==(lhs: Product, rhs: Product) -> Bool {
return lhs.subCategory == rhs.subCategory
}
And, if Product was a NSObject subclass, you have to override isEqual:
override func isEqual(object: AnyObject?) -> Bool {
if let product = object as? Product {
return product == self
} else {
return false
}
}
Clearly, modify those to reflect other properties you might have in your class. For example:
class Product : Hashable {
var category = ""
var subCategory = ""
var hashValue: Int { return [category, subCategory].hashValue }
}
func ==(lhs: Product, rhs: Product) -> Bool {
return lhs.category == rhs.category && lhs.subCategory == rhs.subCategory
}
If Product conforms to Equatable, where a product is equal based on it's subcategory (and you don't care about order), you can add the objects to a set, and take an array from that set:
let array = [product,product2,product3]
let set = NSSet(array: array)
let uniqueArray = set.allObjects
or
let array = [product,product2,product3]
let set = Set(array)
let uniqueArray = Array(set)
If your class conforms to protocol Hashable and you would like to keep the original array order you can create an extension as follow:
extension Array where Element: Hashable {
var uniqueElements: [Element] {
var elements: [Element] = []
for element in self {
if let _ = elements.indexOf(element) {
print("item found")
} else {
print("item not found, add it")
elements.append(element)
}
}
return elements
}
}
class Product {
var subCategory: String = ""
}
let product = Product()
product.subCategory = "one"
let product2 = Product()
product2.subCategory = "two"
let product3 = Product()
product3.subCategory = "two"
let array = [product,product2,product3]
extension Product : Hashable {
var hashValue: Int {
return subCategory.hashValue
}
}
func ==(lhs: Product, rhs: Product)->Bool {
return lhs.subCategory == rhs.subCategory
}
let set = Set(array)
set.forEach { (p) -> () in
print(p, p.subCategory)
}
/*
Product one
Product two
*/
if an item is part of set or not doesn't depends on hashValue, it depends on comparation. if your product conform to Hashable, it should conform to Equatable. if you need that the creation of the set depends solely on subCategory, the comparation should depends solely on subCategory. this can be a big trouble, if you need to compare your products some other way
Here is a KeyPath based version of the Ciprian Rarau' solution
extension Array {
func unique<T: Hashable>(by keyPath: KeyPath<Element, T>) -> [Element] {
var set = Set<T>()
return self.reduce(into: [Element]()) { result, value in
guard !set.contains(value[keyPath: keyPath]) else {
return
}
set.insert(value[keyPath: keyPath])
result.append(value)
}
}
}
example usage:
let unique = [product, product2, product3].unique(by: \.subCategory)
I have two arrays, they are of two different objects, and both contain an ID field. What I need to do is display them in order in a table view controller. They share the same basic info, Size and ID, and those are the only pieces of data displayed, in addition to the type of object it is. When the user selects a cell then it moves to a new view that displays the finer details of the object.
Right now, I have two sections in the table, one for TypeA, and the other for TypeB. They sort through all of the items in their respective list, but are out of order for when the item was made. So it looks like:
TypeA
ID 1
ID 2
ID 5
ID 6
TypeB
ID 3
ID 4
ID 7
What I need is for it to sort them all into 1 section, and still open the detail view when selected.
Thoughts
I could put them all into an AnyObject dictionary and when looking at individual items determine if they are of one object type or the other. I feel like that would work, but how would I go about sorting that correctly?
Put all common properties into a protocol, the build and sort an array of that common protocol:
protocol HasID {
var id: Int { get }
}
class TypeA : HasID, CustomStringConvertible {
var id: Int
init(_ id: Int) {
self.id = id
}
var description : String {
return ("TypeA(\(self.id))")
}
}
class TypeB : HasID, CustomStringConvertible {
var id: Int
init(_ id: Int) {
self.id = id
}
var description : String {
return ("TypeB(\(self.id))")
}
}
let typeA = [TypeA(1), TypeA(2), TypeA(5), TypeA(6)]
let typeB = [TypeB(3), TypeB(4), TypeB(7)]
let result: [HasID] = (typeA + typeB).sorted { $0.id < $1.id }
print(result)
[TypeA(1), TypeA(2), TypeB(3), TypeB(4), TypeA(5), TypeA(6), TypeB(7)]
Alternatively to Zoff Dino answer if you do not want to burden TypeA and TypeB classes with HasID protocol then you can define extension to these classes in your view controller:
class TypeA {
var ID: Int
init(_ id: Int) {
self.ID = id
}
}
class TypeB {
var ID: Int
init(_ id: Int) {
self.ID = id
}
}
protocol HasID {
var ID: Int { get }
}
// place this in your view controller
extension TypeA: HasID {
}
extension TypeB: HasID {
}
var arrayA = [TypeA(1), TypeA(3), TypeA(5)]
var arrayB = [TypeB(2), TypeB(4)]
let sortedArray = (arrayA.map { $0 as HasID } + arrayB.map { $0 as HasID })
.sort { $0.ID < $1.ID }
You can do this like so:
class TypeA {
var ID: Int
init(_ id: Int) {
self.ID = id
}
}
class TypeB {
var ID: Int
init(_ id: Int) {
self.ID = id
}
}
struct Wrap {
var ID: Int {
return a?.ID ?? b?.ID ?? 0
}
let a: TypeA?
let b: TypeB?
}
var arrayA = [TypeA(1), TypeA(3), TypeA(5)]
var arrayB = [TypeB(2), TypeB(4)]
let sortedArray = (arrayA.map { Wrap(a: $0, b: nil) } + arrayB.map { Wrap(a: nil, b: $0)})
.sorted { $0.ID < $1.ID }
When row is selected you can determine object with:
if let a = sortedArray[index].a {
// TypeA row selected
} else if let b = sortedArray[index].b {
// TypeB row selected
}
I have this:
class Movies {
Name:String
Date:Int
}
and an array of [Movies]. How do I sort the array alphabetically by name? I've tried:
movieArr = movieArr.sorted{ $0 < $1 }
and
movieArr = sorted(movieArr)
but that doesn't work because I'm not accessing the name attribute of Movies.
In the closure you pass to sort, compare the properties you want to sort by. Like this:
movieArr.sorted { $0.name < $1.name }
or the following in the cases that you want to bypass cases:
movieArr.sorted { $0.name.lowercased() < $1.name.lowercased() }
Sidenote: Typically only types start with an uppercase letter; I'd recommend using name and date, not Name and Date.
Example, in a playground:
class Movie {
let name: String
var date: Int?
init(_ name: String) {
self.name = name
}
}
var movieA = Movie("A")
var movieB = Movie("B")
var movieC = Movie("C")
let movies = [movieB, movieC, movieA]
let sortedMovies = movies.sorted { $0.name < $1.name }
sortedMovies
sortedMovies will be in the order [movieA, movieB, movieC]
Swift5 Update
channelsArray = channelsArray.sorted { (channel1, channel2) -> Bool in
let channelName1 = channel1.name
let channelName2 = channel2.name
return (channelName1.localizedCaseInsensitiveCompare(channelName2) == .orderedAscending)
}
With Swift 3, you can choose one of the following ways to solve your problem.
1. Using sorted(by:) with a Movie class that does not conform to Comparable protocol
If your Movie class does not conform to Comparable protocol, you must specify in your closure the property on which you wish to use Array's sorted(by:) method.
Movie class declaration:
import Foundation
class Movie: CustomStringConvertible {
let name: String
var date: Date
var description: String { return name }
init(name: String, date: Date = Date()) {
self.name = name
self.date = date
}
}
Usage:
let avatarMovie = Movie(name: "Avatar")
let titanicMovie = Movie(name: "Titanic")
let piranhaMovie = Movie(name: "Piranha II: The Spawning")
let movies = [avatarMovie, titanicMovie, piranhaMovie]
let sortedMovies = movies.sorted(by: { $0.name < $1.name })
// let sortedMovies = movies.sorted { $0.name < $1.name } // also works
print(sortedMovies)
/*
prints: [Avatar, Piranha II: The Spawning, Titanic]
*/
2. Using sorted(by:) with a Movie class that conforms to Comparable protocol
However, by making your Movie class conform to Comparable protocol, you can have a much concise code when you want to use Array's sorted(by:) method.
Movie class declaration:
import Foundation
class Movie: CustomStringConvertible, Comparable {
let name: String
var date: Date
var description: String { return name }
init(name: String, date: Date = Date()) {
self.name = name
self.date = date
}
static func ==(lhs: Movie, rhs: Movie) -> Bool {
return lhs.name == rhs.name
}
static func <(lhs: Movie, rhs: Movie) -> Bool {
return lhs.name < rhs.name
}
}
Usage:
let avatarMovie = Movie(name: "Avatar")
let titanicMovie = Movie(name: "Titanic")
let piranhaMovie = Movie(name: "Piranha II: The Spawning")
let movies = [avatarMovie, titanicMovie, piranhaMovie]
let sortedMovies = movies.sorted(by: { $0 < $1 })
// let sortedMovies = movies.sorted { $0 < $1 } // also works
// let sortedMovies = movies.sorted(by: <) // also works
print(sortedMovies)
/*
prints: [Avatar, Piranha II: The Spawning, Titanic]
*/
3. Using sorted() with a Movie class that conforms to Comparable protocol
By making your Movie class conform to Comparable protocol, you can use Array's sorted() method as an alternative to sorted(by:).
Movie class declaration:
import Foundation
class Movie: CustomStringConvertible, Comparable {
let name: String
var date: Date
var description: String { return name }
init(name: String, date: Date = Date()) {
self.name = name
self.date = date
}
static func ==(lhs: Movie, rhs: Movie) -> Bool {
return lhs.name == rhs.name
}
static func <(lhs: Movie, rhs: Movie) -> Bool {
return lhs.name < rhs.name
}
}
Usage:
let avatarMovie = Movie(name: "Avatar")
let titanicMovie = Movie(name: "Titanic")
let piranhaMovie = Movie(name: "Piranha II: The Spawning")
let movies = [avatarMovie, titanicMovie, piranhaMovie]
let sortedMovies = movies.sorted()
print(sortedMovies)
/*
prints: [Avatar, Piranha II: The Spawning, Titanic]
*/
let sortArray = array.sorted(by: { $0.name.lowercased() < $1.name.lowercased() })
For those using Swift 3, the equivalent method for the accepted answer is:
movieArr.sorted { $0.Name < $1.Name }
Most of these answers are wrong due to the failure to use a locale based comparison for sorting. Look at localizedStandardCompare()
Sorted array
Swift 4.2
arrayOfRaces = arrayOfItems.sorted(by: { ($0["raceName"] as! String) < ($1["raceName"] as! String) })
*import Foundation
import CoreData
extension Messages {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Messages> {
return NSFetchRequest<Messages>(entityName: "Messages")
}
#NSManaged public var text: String?
#NSManaged public var date: Date?
#NSManaged public var friends: Friends?
}
//here arrMessage is the array you can sort this array as under bellow
var arrMessages = [Messages]()
arrMessages.sort { (arrMessages1, arrMessages2) -> Bool in
arrMessages1.date! > arrMessages2.date!
}*