How can I simplify an if..else statement when parsing an array - arrays

I have a function that filters an array of objects and reduces to one String. The array has this format:
[Person(destination: “city”, surname: [“sur”, “name”]]
This is the function that filters the persons to find the surname by a certain name.
And I have three states of name: "john", "paul", "james". I want to verify if each name exists and to do something with his surname. How can I do this without using if...else. I don't like how it looks with all this if's.
enum Destination: String, RawRepresentable {
case city
case rural
case both
}
func findPerson(person: persons, type: Destination) -> String? {
let surname = persons.filter{ $0.destination == type.rawValue}.reduce("") { id, element in
return element.details.joined(separator: " ")
}
return surname
}
func findPersons(person: persons) {
// Also I want to verify if is not null the string that i receive
if let city = self.findPerson(person: person, type: .city) {
self.handleCity(type: city)
}
if let rural = self.findPerson(person: person, type: .rural) {
self.handleRural(type: rural)
}
if let both = self.findPerson(person: person, type: .both) {
self.handleBoth(type: both)
}
}

enum Destination: String, RawRepresentable {
case city
case rural
case both
static let all : [Destination] = [
.city,
.rural,
.both
]
func findPersons(person: persons) {
// Also I want to verify if is not null the string that i receive
for destination in Destination.all{
if let findPerson(person, type: destination){
self.handleCity(type: destination.rawValue)
}
}
}

Related

Searching and Editing Values in Swift Array or Dictionary

I have a method which is supposed to return a Set of Strings. Here is a method description:
Returns: 10 product names containing the specified string.
If there are several products with the same name, producer's name is added to product's name in the format "<producer> - <product>",
otherwise returns simply "<product>".
Can't figure out how to check if there are duplicate names in the array and then edit them as required
What I've got so far:
struct Product {
let id: String; // unique identifier
let name: String;
let producer: String;
}
protocol Shop {
func addNewProduct(product: Product) -> Bool
func deleteProduct(id: String) -> Bool
func listProductsByName(searchString: String) -> Set<String>
func listProductsByProducer(searchString: String) -> [String]
}
class ShopImpl: Shop {
private var goodsInTheShopDictionary: [String: Product] = [:]
func addNewProduct(product: Product) -> Bool {
let result = goodsInTheShopDictionary[product.id] == nil
if result {
goodsInTheShopDictionary[product.id] = product
}
return result
}
func deleteProduct(id: String) -> Bool {
let result = goodsInTheShopDictionary[id] != nil
if result {
goodsInTheShopDictionary.removeValue(forKey: id)
}
return result
}
func listProductsByName(searchString: String) -> Set<String> {
var result = Set<String>()
let searchedItems = goodsInTheShopDictionary.filter{ $0.value.name.contains(searchString) }
let resultArray = searchedItems.map{ $0.value }
result = Set(searchedItems.map{ $0.value.name })
if result.count > 10 {
result.removeFirst()
}
return result
}
}
If you want to achieve this you would need to iterate over you resultArray and save producer and product into another array. On each iteration you would need to check if the array allready contains either the product name itself or an allready modified version.
A possible implementation would look like this:
var result = [(producer: String, product: String)]()
// iterate over the first 10 results
for item in resultArray.prefix(10){
if let index = result.firstIndex(where: { _ , product in
product == item.name
}){
// the result array allready contains the exact product name
// so we need to convert the name allready in the list
let oldProduct = (producer: result[index].producer, product: "\(result[index].producer) \(result[index].product)")
result[index] = oldProduct
// add the new one
result.append((producer: item.producer, product: "\(item.producer) \(item.name)"))
}
else if !result.filter({ $0.product.components(separatedBy: " ").contains(item.name)}).isEmpty {
// if the result array allready contains a modified version of the name
result.append((producer: item.producer, product: "\(item.producer) \(item.name)"))
} else{
// if the result array does not contain the product yet
result.append((producer: item.producer, product: "\(item.name)"))
}
}
let productNames = result.map{ $0.product}
Please be aware: As you are using a [String: Product], which is a unsorted dictionary, to hold your values this will yield different results (if the resultArray collection is larger than 10) each time you search.
Tested with searchString = name1:
var goodsInTheShopDictionary: [String: Product] = Dictionary(uniqueKeysWithValues: (0...20).map { index in
("\(index)",Product(id: "", name: "name\(index)", producer: "producer\(index)"))
})
goodsInTheShopDictionary["100"] = Product(id: "11", name: "name1", producer: "producer11")
goodsInTheShopDictionary["101"] = Product(id: "12", name: "name1", producer: "producer12")
Result:
["name13", "producer12 name1", "name10", "name19", "producer11 name1",
"name17", "name14", "name18", "producer1 name1", "name16"]

Reduce an array of objects based on object field

I have a Country object and a City object
struct Country: {
let name: String
let countryCode: String
let cities: [City]
let population: Int
init(name: String, countryCode: String, cities: [City], population: Int) {
self.name = name
self.countryCode = countryCode
self.cities = cities
self.population = population
}
}
struct City {
let id: Int
let name: String
let latitude: Double
let longitude: Double
let countryCode: String
let population: Int
}
Incoming JSON data looks like this which decodes into [City] array
{
"cities":[
{
"id":1,
"name":"Paris",
"latitude":0,
"logitude":0,
"country_code":"FR",
"population":0
},
{
"id":2,
"name":"Nice",
"latitude":0,
"logitude":0,
"country_code":"FR",
"population":0
},
{
"id":3,
"name":"Berlin",
"latitude":0,
"logitude":0,
"country_code":"DE",
"population":0
},
{
"id":4,
"name":"Munich",
"latitude":0,
"logitude":0,
"country_code":"DE",
"population":0
},
{
"id":5,
"name":"Amsterdam",
"latitude":0,
"logitude":0,
"country_code":"NL",
"population":0
},
{
"id":6,
"name":"Leiden",
"latitude":0,
"logitude":0,
"country_code":"NL",
"population":0
}
]
}
How would I create [Country] array from [City] array efficiently? I've tried to use reduce:into: but not sure that's what I have to use.
I know I could go with an empty array and add/create Countries one by one then search if there is one already and add City to it. That creates awful looking code as for me. I feel like there is an elegant solution to this problem using map or reduce functions.
reduce:into: code I've tried so far
func transformArrayOf(_ cities: [City]) -> [Country] {
let empty: [Country] = []
return cities.reduce(into: empty) { countries, city in
let existing = countries.filter { $0.countryCode == city.countryCode }.first
countries[existing].cities.append(city)
}
}
EDIT:
The function only gets [City] array. So countries must be created only from that.
Dictionary(grouping:by:) with map(_:) works perfectly! Two lines instead on nested for loops and if statements :)
And Country name can be parsed from a country code
Use Dictionary(grouping:by:) and map(_:) combined to get the expected result.
let countries = Dictionary(grouping: cities, by: { $0.countryCode }).map { (countryCode, cities) -> Country in
return Country(name: "", countryCode: countryCode, countryName: "", cities: cities, population: cities.reduce(0) { $0 + $1.population })
}
Since the values for name and countryName are unknown, I've used empty String ("") for both.
This is what Dictionary(grouping:by:) is for:
let citiesByCountryCode = Dictionary(grouping: cities, by: \.countryCode)
But you'll need separate logic to create the countries, because they contain data that isn't derived from the cities like name, countryName (how are those different?), etc.

Swift 4 Array Find or First

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.

How to break and return in a loop

I try to access a value in an array of dictionary. The dataArray looks like this:
struct Person {
let name: String
let lastName: String
let age: Int
}
let person1: Person = Person(name: "Steven", lastName: "miller", age: 23)
let person2: Person = Person(name: "jana", lastName: "drexler", age: 31)
let person3: Person = Person(name: "hanna", lastName: "montana", age: 56)
var dataArray = [Person]()
dataArray.append(person1)
dataArray.append(person2)
dataArray.append(person3)
Now I want to access the age of jana. If I´m doing this:
func getAge() ->Int {
var age: Int = 0
for items in dataArray {
while items.name == "jana" {
age = items.age
return age
break // This break will never be executed because of return.
}
break // setting the break here, the loop will break after first round
}
return age
}
the loop will stop after the first round. (it works only for steven, because he is in the first round of the lopp.) The array is quite long, so i need to stop the loop after the first match. Setting break after return, it will not be executed because of return. Setting return after break, it´s the same. Any suggestions?
For vadian:
class AGE {
func getAge() -> Int? {
dataArray.append(person1)
dataArray.append(person2)
dataArray.append(person3)
// Cannot call value of non-function type `Person`
return dataArray.first(where: { $0.name == "john" }?.age
}
}
How about this? No need for the inner loop (which was never looping anyway).
func getAge() ->Int {
for item in dataArray {
if item.firstName == "jana" {
return item.age
}
}
return 0
}
As a side note, a dictionary might be a better way to store the data to make lookups more efficient.
There is no need for the while loop or the break statements.
If you want to iterate the loop looking for the age of a specific person, all you need is:
func getAge() -> Int? {
for items in dataArray {
if items.name == "jana" {
return items.age
}
}
return nil
}
Note the updated return value to be an optional. A nil return value means the person wasn't found. Don't use 0 as a magic value.
It's also bad to hardcode the name. Pass it as a parameter. And use better variable names:
func getAge(of name: String) -> Int? {
for person in dataArray {
if person.name == name {
return person.age
}
}
return nil
}
if let age = getAge(of: "jana") {
print("age is \(age)")
} else {
print("Name not found")
}

Append a struct inside a property which is an array inside other struct in Swift

Hi All I need to append a struct inside an array which is a property of an other struct, I'm using Swift 3, however is not appending anything and I have no idea why, any help please?
import Foundation
struct Product {
var objectId: String
var name: String
var price: Double
var qty: Int
var img: String
var desc: String
var note: String
}
struct Order {
var objectId: String
var name: String
var detail: [Product]
mutating func addToDetail(_ product: Product) {
detail.append(product)
}
}
class OrderManager: NSObject {
static let shared = OrderManager()
var order: Order?
var orderCreated: Bool {
return self.order != nil
}
var partnerInOrder: String? {
return self.order?.objectId
}
func createOrderIfNeeded(partnerId: String, name: String) {
if self.order == nil {
self.order = Order(objectId: partnerId, name: name, detail: [Product]())
}
}
func addProduct(objectId: String, name: String, price: Double, qty: Int, img: String = "", desc: String = "", note: String = "") {
guard var order = self.order else {
return
}
let hasProduct = order.detail.contains(where: { $0.objectId == objectId })
if !hasProduct {
order.addToDetail(Product(objectId: objectId, name: name, price: price, qty: qty, img: img, desc: desc, note: note))
}
print("has product \(hasProduct)")
print("the whole order is \(self.order)")
}
func addOption() {}
func getProduct() {}
func getOption() {}
fileprivate func storeOrder() {}
}
Is getting inside the addToDetail func of the struct, but always my array is empty
In the following guard statement (in method addProduct of OrderManager)
guard var order = self.order else { /* ... */ }
you create a copy of self.order (given that it is not nil), due to the value semantics of structures in Swift. The subsequent call to addToDetail on this copy will not append a Product instance to the instance variable order of self, but only to the copy which goes out of scope as addProduct goes out of scope.
You could test this theory by replacing the optional binding clause in the guard statement above with a simple nil check (as #MartinR points out below, we don't really need a guard statement (after the fix: no binding), but can just perform an early return in case self.order is nil)
if self.order == nil { return }
Or, remove the explicit nil check altogether, and use optional chaining to decide whether or not to add a product to the order instance (combining the nil check and hasProduct in a single optional chaining clause):
func addProduct(objectId: String, name: String, price: Double, qty: Int, img: String = "", desc: String = "", note: String = "") {
if !(self.order?.detail.contains(where: { $0.objectId == objectId }) ?? true) {
order?.addToDetail(Product(objectId: objectId, name: name, price: price, qty: qty, img: img, desc: desc, note: note))
}
// ... remove the logging
}
This does a poorer job showing the intent of the code, though.

Resources