I have four list of arrays:
var products: [Product] = []
var addressInfo: [AddressInfo] = []
var favorites: [Favorite] = []
var amounts: [Amount] = []
I want to combine them all into one so I have only one variable:
var combined = [Product/AddressInfo/Favorite/Amount]()
How would I go about merging them into one array var cobmbined = [Combined]() ?
This these are two examples of how my structs look like:
struct Amount {
var amountsDeliveryCharge: Double
var amountsDriverTip: Double
init(
amountsDeliveryCharge: Double,
amountsDriverTip: Double
){
self.amountsDeliveryCharge = amountsDeliveryCharge
self.amountsDriverTip = amountsDriverTip
}
init(data: [String: Any]){
amountsDeliveryCharge = data[DatabaseRef.deliveryCharge] as? Double ?? 0.0
amountsDriverTip = data[DatabaseRef.driverTip] as? Double ?? 0.0
}
static func modelToData(amount: Amount) -> [String: Any] {
let data : [String: Any] = [
DatabaseRef.deliveryCharge : amount.amountsDeliveryCharge,
DatabaseRef.driverTip : amount.amountsDriverTip
]
return data
}
}
This is the other struct:
struct Product {
var price: Double
var priceUnit: String
init(
price: Double,
priceUnit: String
){
self.price = price
self.priceUnit = priceUnit
}
init(data: [String: Any]){
price = data[DatabaseRef.price] as? Double ?? 0.0
priceUnit = data[DatabaseRef.priceUnit] as? String ?? ""
}
static func modelToData(product: Product) -> [String: Any] {
let data : [String: Any] = [
DatabaseRef.price : product.price,
DatabaseRef.priceUnit : product.priceUnit
]
return data
}
}
The other two structs look similar in structure as well.
Assuming the ordering is the same in each of them, create a struct with the appropriate properties
struct Combined {
let product: Product
let addressInfo: AddressInfo
let favourite: Favourite
let amount: Amount
}
and then either loop through the arrays using a counter to index then and create instances using the memberWise initialiser, or use a static factory function to make and return the array:
static func makeFrom(products: [Product], addresses: [AddressInfo], favourites: [Favourite], amounts: [Amount]) -> [Combined] {
guard products.count == addresses.count, addresses.count == favourites.count, favourites.count == amounts.count else {fatalError()}. //handle better in practice
var combined = [Combined]()
for index in 0..<products.count {
combined.append(Combined(product: products[index],
addressInfo: addresses[index],
favourite: favourites[index],
amount: amounts[index])
}
return combined
}
The above checks that all arrays are teh same length and if not calls fatalError(). In reality you'd want to handle the error better than this.
Then use as:
let combined = Combined.makeFrom(products: products, addresses:addressInfo, favourites: favourites, amounts: amounts)
You can cast your arrays as [Any] and add them.
let combinedVals = (products as [Any]) + (addressInfo as [Any]) + (favorites as [Any]) + (amounts as [Any])
Alternatively you can create a protocol and have your structs adhere to it.
protocol Combined {
}
struct Amount: Combined {
var amountsDeliveryCharge: Double
var amountsDriverTip: Double
}
struct Product: Combined {
var price: Double
var priceUnit: String
}
let combinedValues: [Combined] = amounts + products
When you iterate over the array, if you need to check what it is, you can use a switch to check it's type.
combinedValues.forEach { (value) in
switch value {
case let value as Amount:
print(value.amountsDriverTip)
break
case let value as Product:
print(value.price)
break
default:
break
}
}
Related
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"]
I have array of dictionary and the values are as follows:
[["-MXpvzmZdbqzrjND8w9F": {
lid = "-MW6eEidZFCLeeZ0uBk1";
message = hi;
timeStamp = 1617960107264;
title = "Sambhar Dosa";
user = 1QSU0c1q8QNrZzmICXGClC0o86E3;
}, "-MXq5NAyrkk4ZcvRFM7T": {
lid = "-MW6eEidZFCLeeZ0uBk1";
message = "how ru?";
timeStamp = 1617962828647;
title = "Sambhar Dosa";
user = 1QSU0c1q8QNrZzmICXGClC0o86E3;
}], ["-MXqa5-pkC28lY_Q_hpZ": {
lid = "-MWwEpHAhIdhN0i5sltB";
message = "hi nice cycle";
timeStamp = 1617971142820;
title = "Cycle for kids";
user = 1QSU0c1q8QNrZzmICXGClC0o86E3;
}]]
Here there are 2 elements in the array. I want to take the last element in both of the array and sort it based on the timestamp value.
How to do it? Please help me.
From comments, it seems that that the type of the array is:
typealias SomeDataArray = [[String: [String: Any]]]
So we're missing type safety from the beginning. The first thing I'd do is define a struct to represent Any in that definition. For now I'll just use a struct as a wrapper for the inner dictionary, using computed properties for all the fields:
struct SomeData
{
let dict: [String: Any]
var lid: String? { dict["lid"] as? String }
var message: String? { dict["message"] as? String }
var timeStamp: Int { dict["timeStamp"] as? Int ?? 0 }
var title: String? { dict["title"] as? String }
// Skipping user, because I have no idea what to make of its type.
}
Really this should be decoded into some real Swift type, but that's a topic for another day.
So now we redo the typealias
typealias SomeDataArray = [[String: SomeData]]
In chat it was explained what within the "last" element for each of these dictionaries can be defined as the one with the largest timeStamp value. Given that this will give this solution (retaining the keys)
let results = testData.map {
dict in dict.map {
($0.key, SomeData(dict: $0.value))
}.sorted { $0.1.timeStamp < $1.1.timeStamp }.last
}.filter { $0 != nil }.map { $0! }.sorted { $0.1.timeStamp < $1.1.timeStamp }
If you want to transform it back the [[String;Any]] you get from Firebase, then it would be this:
let results = testData.map {
dict in dict.map {
($0.key, SomeData(dict: $0.value))
}.sorted { $0.1.timeStamp < $1.1.timeStamp }.last
}.filter { $0 != nil }.map { $0! }.sorted { $0.1.timeStamp < $1.1.timeStamp }
.map { [$0.0: $0.1.dict as Any] }
My JSON data look like this image below. Now I wanna merge the value of Shop Type and Promotion into one to use as collection view data. How can I do that?
I just filter the response data from the server like this:
var dataBanDau: [SDFilterModel] = []
var quickData: [SDFilterModel] = []
let filters: [SDFilterModel] = data
self.filterEntries = filters
//let nsarray = NSArray(array: self.filterEntries! , copyItems: true)
// self.filterEntriesStoreConstant = nsarray as! Array
self.dataBanDau = filters
for i in 0..<self.dataBanDau.count {
if self.dataBanDau[i].search_key.count == 0 {
self.quickData.append(self.dataBanDau[i])
}
}
self.quickData = self.quickData.filter {
$0.type != "range"
}
DispatchQueue.main.async {
//Note: Reload TableView
self.quickFilterCollection.reloadData()
completed(true)
}
}
the class SDFilterModel:
class SDFilterModel: DSBaseModel {
var name = String()
var type = String()
var is_expanded = Int()
var search_key = String()
var filterEntries : [SDFilterModel]?
override func copy(with zone: NSZone? = nil) -> Any {
// This is the reason why `init(_ model: GameModel)`
// must be required, because `GameModel` is not `final`.
let copy = SDFilterModel(dict: self.dictionary)
if let arrAttribute = NSArray(array: self.value , copyItems: true) as? [AttributeValueModel] {
copy.value = arrAttribute
}
return copy
}
override init(dict: Dictionary<String, Any>) {
super.init(dict: dict);
value = self.valueParse()
name = dict.getString(forKey: "name")
type = dict.getString(forKey: "type")
search_key = dict.getString(forKey: "search_key")
is_expanded = dict.getInt(forKey: "is_expanded")!
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
var value: [AttributeValueModel] = [];
func valueParse()-> [AttributeValueModel] {
guard let childs = (self.dictionary["value"]) as? [Dictionary<String, AnyObject>]
else { return [] }
var output: [AttributeValueModel] = [];
for aDict in childs {
let item = AttributeValueModel(dict:aDict);
// if type == .Range && item.option_id == "0" {
// item.setRangeOptionID(aValue: item.option_name!)
// }
//
output.append(item);
}
return output;
}
Let be Assume you have let myArray = [1,2,3,4,5,6,7,8]
Now you wanted to square of each and every element in the array,
With for loop you do like this
for item in myArray {
print(item * item)
}
Now assume item = $0
With for map you jus do
myArray.map({ $0 * $0 })
Both will gave same output.
map : Use to do same operation on every element of array.
flatmap : It is used to flattern the array of array.
let myArr = [[1,2,3],[4,5,6,7]]
and you want o/p as [1,2,3,4,5,6,7]
So can get above output with myArr.flatMap({$0})
Now back to your question.
let reqArray = myModel.data.map({ $0.value }).flatMap({ $0 })
First, map gaves you array-of-array of key value but you need a single array, so for that you need to use flatmap.
You can take ref : https://medium.com/#Dougly/higher-order-functions-in-swift-sorted-map-filter-reduce-dff60b5b6adf
Create the models like this
struct Option {
let name: String
let searchKey: String
let id: String
}
struct Model {
let type: String
let name: String
let isExpanded: Bool
let value: [Option]
}
You should get the options array values and join all the arrays
let models:[Model] = //...
let collectionViewArray = models.map { $0.value }.reduce([Option](), +)
Using for loop
var collectionViewArray = [Option]()
for model in models {
collectionViewArray.append(contentsOf: model.value)
}
I just want convert an array of Player Names into a dictionary Scoreboard, giving everyone an initial score of 0.
Meaning...
var playerNames = ["Harry", "Ron", "Hermione"]
becomes...
var scoreBoard: [String:Int] = [ "Ron":0, "Harry":0, "Hermione":0 ]
This is my first time asking a question, but I’m totally completely stuck on what feels so simple and all how-to's/questions I've found are off in some way. I have tried using reduce in a variety of ways, but always end up short. Thanks in advance!
Here's a quick one liner that I like to use:
let scoreboard = playerNames.reduce(into: [String: Int]()) { $0[$1] = 0 }
reduce is definitely one of the more difficult builtin functions to use correctly, but it is what you want here.
let names = ["Harry", "Ron", "Hermione"]
let scoreboard: [String: Int] = names.reduce(into: [:], { result, next in
result[next] = 0
})
It takes 2 parameters: the initial value (in our case, an empty dictionary [:]), and a closure that updates the result with each element in the array. This closure has 2 parameters, result and next. We want to update result based on the next element. Our closure does this by setting result[next] to 0.
If the player names are known to be all different then you can do
let playerNames = ["Harry", "Ron", "Hermione", "Ron"]
var scoreBoard = Dictionary(uniqueKeysWithValues: zip(playerNames,
repeatElement(0, count: playerNames.count)))
print(scoreBoard) // ["Harry": 0, "Ron": 0, "Hermione": 0]
Here zip is used to create a sequence of player/score pairs, from which the dictionary is created.
Remark: Originally I had used AnySequence { 0 } to generate the zeros. Using repeatElement() instead was suggested by Alexander and has the advantage that the correct required capacity is passed to the dictionary intializer.
You can use reduce(into:) as you suspected. You simply need to declare the initial value as [String:Int]() to be an empty Dictionary of the required type, then simply set the value of all keys in playerNames to 0.
var playerNames = ["Harry", "Ron", "Hermione"]
var scoreBoard = playerNames.reduce(into: [String:Int](), { currentScores,player in
currentScores[player] = 0
})
Using reduce(into:_:):
var playerNames = ["Harry", "Ron", "Hermione"]
let playerScore = playerNames.reduce(into: [:]) { counts, playerNames in
counts[playerNames, default: 0] += 0
}
print(playerScore)
To keep a count of the players names (eg. duplicate names):
counts[myArray, default: 0] += 1
So for example if Ron had two entries before the game started (score > 0) then you would know.
Without using reduce(into:_:) method and as an extension:
var playerNames = ["Harry", "Ron", "Hermione"]
extension Sequence where Self.Iterator.Element: Hashable {
func freq() -> [Self.Iterator.Element: Int] {
return reduce([:]) {
( iter: [Self.Iterator.Element: Int], element) in
var dict = iter
dict[element] = 0
return dict
}
}
}
print(playerNames.freq())
// ["Harry": 0, "Hermione": 0, "Ron": 0]
keep a count (eg. duplicate names):
dict[element, default: -1 ] += 1
Here is how you can do that:
var playerNames = ["Harry", "Ron", "Hermione"]
var dictionary = [String: Int]()
for player in playerNames {
dictionary[player] = 0
}
Here is another way to do it:
// Implementation
extension Dictionary {
static func from(_ array: [Value], key: KeyPath<Value, Key>) -> Dictionary<Key, Value> {
var dict: Dictionary<Key, Value> = [:]
array.forEach { dict[$0[keyPath: key]] = $0}
return dict
}
}
/// Usage
let array: [String] = ["Banana", "Apple"]
Dictionary.from(array, key: \.self)
// or if you have a more complex object
struct Foo {
let id: Int
}
let array2: [Foo] = [Foo(id: 1), Foo(id: 2)]
Dictionary.from(array2, key: \.id)
Based on jmad8 answer
Details
Swift 5.3
Xcode 12.0.1 (12A7300)
Solution
extension Sequence {
func toDictionary<Key: Hashable, Value>(where closure: (Element) -> (Key, Value)) -> [Key: Value] {
reduce(into: [Key: Value]()) { (result, element) in
let components = closure(element)
result[components.0] = components.1
}
}
func toCompactDictionary<Key: Hashable, Value>(where closure: (Element) -> ((Key, Value)?)) -> [Key: Value] {
reduce(into: [Key: Value]()) { (result, element) in
guard let components = closure(element) else { return }
result[components.0] = components.1
}
}
}
Usage
// Sample 1
print(languages.toDictionary { (string) -> (Character, String) in
return (string.first!, string)
})
print(languages.toCompactDictionary { (string) -> (Character, String)? in
guard let character = string.first, character != Character("J") else { return nil }
return (character, string)
})
// Sample 2
print(languages.enumerated().toDictionary { (data) -> (Int, String) in
return (data.offset, data.element)
})
// Shorter version of sample 2
print(languages.enumerated().toDictionary { ($0.offset, $0.element) })
// Sample 3
struct Order {
let id: Int
let desctiption: String
}
let orders = [
Order(id: 0, desctiption: "Apple"),
Order(id: 1, desctiption: "Banana"),
Order(id: 2, desctiption: "watermelon")
]
print(orders.toDictionary { ($0.id, $0) })
I have a couple of different types of structs (Promo & Event). I'd like to create an empty array which gets populated with an array of each type depending on the outcome of an if statement.
So something like this:
var dataArray:[Any] = [] // see options I've tried below
if myTest == true {
dataArray = [Promo, Promo, Promo]
} else {
dataArray = [Event, Event, Event]
}
I have tried using:
1. var dataArray: [Any] = []
2. var dataArray: [AnyObject] = []
3. var dataArray: [Any] = [Any]()
4. var dataArray: [AnyObject] = [AnyObject]()
but when I try to store an array of Promo Structs in dataArray I get an error Cannot assign value of type '[Promo]' to type '[Any]' etc.
So, how do I initialise an array so that it can store a variety of (unknown) Structs. Or how do I modify my Structs to enable them to be stored in an array?
I'm really struggling to see what I'm doing wrong so any pointers would be v. helpful.
Here are my Structs:
Promo.swift
import Foundation
struct Promo {
// initialise the stored properties for use later
let promoId : Int
let date : NSDate
let title: String
let body: String
let busName : String
let busId : Int
let categoryId: Int
let featured: Bool
// a universal init() method which has optional parameters
init(promoId: Int,
date: NSDate,
title: String,
body: String,
busName: String,
busId: Int,
categoryId: Int,
featured: Bool
){
self.promoId = promoId
self.date = date
self.title = title
self.body = body
self.busName = busName
self.busId = busId
self.categoryId = categoryId
self.featured = featured
}
}
// allow us to compare structs
extension Promo: Equatable {}
func ==(lhs: Promo, rhs: Promo) -> Bool {
return lhs.promoId == rhs.promoId
&& lhs.date == rhs.date
&& lhs.title == rhs.title
&& lhs.body == rhs.body
&& lhs.busName == rhs.busName
&& lhs.busId == rhs.busId
&& lhs.categoryId == rhs.categoryId
&& lhs.featured == rhs.featured
}
Event.swift
import Foundation
struct Event {
// initialise the stored properties for use later
let eventId : Int
let date : NSDate
let title: String
let body: String
let busName : String
let busId : Int
let categoryId: Int
// a universal init() method which has optional parameters
init(eventId: Int,
date: NSDate,
title: String,
body: String,
busName: String,
busId: Int,
categoryId: Int
){
self.eventId = eventId
self.date = date
self.title = title
self.body = body
self.busName = busName
self.busId = busId
self.categoryId = categoryId
}
}
This may not be exactly what you intended, but you can make this a bit cleaner by using classes instead of structs. It appears that a 'Promo' is just an 'Event' with one extra data member (featured)... If that's the case, then you can rename the Promo.promoId field Promo.eventId, and then make it a subclass of Event. Like this:
class Promo : Event {
let featured: Bool
// a universal init() method which has optional parameters
init(eventId: Int,
date: NSDate,
title: String,
body: String,
busName: String,
busId: Int,
categoryId: Int,
featured: Bool
){
self.featured = featured
super.init(eventId: eventId, date: date, title: title, body: body, busName: busName, busId: busId, categoryId: categoryId)
}
}
Then just create the data array like this:
var dataArray = [Event]()
if myTest == true {
dataArray = [promo1, promo2, promo3]
} else {
dataArray = [event1, event2, event3]
}
To use the featured member for a Promo you'll still need to cast like this:
if let thisPromo = dataArray[0] as? Promo {
print(thisPromo.featured)
}
If you are trying to assign to dataArray from [Promo] or [Event] arrays, you could map:
var dataArray:[Any] = []
var promoArray:[Promo] = [Promo(), Promo(), Promo()]
var eventArray:[Event] = [Event(), Event(),Event()]
if myTest == true {
dataArray = promoArray.map { $0 as Any }
} else {
dataArray = eventArray.map { $0 as Any }
}
Or create new Any arrays:
if myTest == true {
dataArray = Array<Any>(arrayLiteral: promoArray)
} else {
dataArray = Array<Any>(arrayLiteral: eventArray)
}