I know this is a silly question but I don't know which will be the more performant solution. I have an array of ListItem. ListItem has a boolean value isSelected. I also have Set<ListItem>. I want to change that boolean to true if array has an element which is also inside Set. How can I achieve this with the best performance?
My Set and Array:
var selectedItems = Set<ListItem>()
var array: [ListItem] = []
List Item:
class ListItem: Codable ,Equatable, Hashable {
let wrapperType: String
let kind: String?
let trackId: Int?
let artistId: Int?
let collectionId: Int?
let artistName, collectionName, trackName: String?
let trackViewUrl: String?
let artworkUrl30, artworkUrl60,artworkUrl100: String?
let releaseDate: String?
let primaryGenreName: String?
var isSelected: Bool = false
enum CodingKeys: String, CodingKey {
case wrapperType, kind
case artistId
case collectionId
case trackId
case artistName, collectionName, trackName
case trackViewUrl
case artworkUrl30, artworkUrl60, artworkUrl100, releaseDate, primaryGenreName
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
wrapperType = try container.decode(String.self, forKey: .wrapperType)
print(wrapperType)
kind = try container.decodeIfPresent(String.self, forKey: .kind)
trackId = try container.decodeIfPresent(Int.self, forKey: .trackId)
collectionId = try container.decodeIfPresent(Int.self, forKey: .collectionId)
artistId = try container.decodeIfPresent(Int.self, forKey: .artistId)
artistName = try container.decodeIfPresent(String.self, forKey: .artistName)
collectionName = try container.decodeIfPresent(String.self, forKey: .collectionName)
trackName = try container.decodeIfPresent(String.self, forKey: .trackName)
trackViewUrl = try container.decodeIfPresent(String.self, forKey: .trackViewUrl)
artworkUrl30 = try container.decodeIfPresent(String.self, forKey: .artworkUrl30)
artworkUrl100 = try container.decodeIfPresent(String.self, forKey: .artworkUrl100)
artworkUrl60 = try container.decodeIfPresent(String.self, forKey: .artworkUrl60)
releaseDate = try container.decodeIfPresent(String.self, forKey: .releaseDate)
primaryGenreName = try container.decodeIfPresent(String.self, forKey: .primaryGenreName)
}
static func ==(lhs: ListItem, rhs: ListItem) -> Bool {
return lhs.trackName == rhs.trackName
}
func hash(into hasher: inout Hasher) {
if trackId != nil {
hasher.combine(trackName)
} else if collectionId != nil {
//AudioBooks Doesn't have TrackId
hasher.combine(collectionId)
} else {
print("Both TrackId && Collection Id is null")
}
}
}
Because these are reference types, if it is guaranteed that there is only one instance of any unique ListItem, it would be sufficient to just set isSelected to false for each item in your array, and then set isSelected to true for each item in your selectedItems.
array.forEach { $0.isSelected = false }
selectedItems.forEach { $0.isSelected = true }
If there can be more than one instance of an item, you are going to have to iterate the items in your array and check if the Set contains them. Fortunately, contains is O(1) for a Set:
array.forEach { $0.isSelected = selectedItems.contains($0) }
Note: It is vital that the hashValue be equal for ListItems that are equal, or this all breaks down. Your hash(into:) function is currently using more fields than your == function, so it is possible to generate a different hashValue for equal ListItems. Fix this to ensure that a proper hashValue is generated.
Related
I'm trying to filter out empty and null values from an api in a json format in swift(UIKit).
The full data returns look like below but sometimes can contain null or empty values in the characteristic key. There is always going to be the same amount of keys.
//Cat
{
"breedname": "Persian",
"picture": "https://catimage.random.png",
"characteristic1": "Shy",
"characteristic2": "Hungry all the time"
"characteristic3": "Likes apples"
"characteristic4": "Grey color"
"characteristic5": "likes chin scratches"
}
{
"breedname": "Bengal",
"picture": "https://catimage.random.png",
"characteristic1": "Active",
"characteristic2": "Adventurous"
"characteristic3": ""
"characteristic4": ""
"characteristic5": ""
}
{
"breedname": "ragdoll",
"picture": "https://catimage.random.png",
"characteristic1": "Fiestey",
"characteristic2": "sharp claws"
"characteristic3": null
"characteristic4": null
"characteristic5": null
}
In order to filter null and empty values before showing in the UI, I have a Decodable class like below and a custom init class with the decodeifPresent method which changes null values to nill. However for empty values I just created a method which converts empty string values to nill. I'm not sure if there are better ways to handle empty and null data and filtering them out? I refer to all the Decodable keys in the UI so I cannot simply delete the keys themselves.
struct Cat: Decodable {
let breedName: String
let picture: String
let characteristic1 : String?
let characteristic2 : String?
let characteristic3 : String?
let characteristic4 : String?
let characteristic5 : String?
enum CodingKeys: String, CodingKey {
case breedName
case picture
case characteristic1
case characteristic2
case characteristic3
case characteristic4
case characteristic5
}
func checkEmpty(s: String?) -> String? {
if s == "" {
return nil
}
return s
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.breedName= try container.decode(String.self, forKey: .breedName)
self.picture = try container.decode(String.self, forKey: .picture)
self.characteristic1 = try container.decodeIfPresent(String.self, forKey: .characteristic1)
self.characteristic2 = try container.decodeIfPresent(String.self, forKey: .characteristic2)
self.characteristic3 = try container.decodeIfPresent(String.self, forKey: .characteristic3)
self.characteristic4 = try container.decodeIfPresent(String.self, forKey: .characteristic4)
self.characteristic5 = try container.decodeIfPresent(String.self, forKey: .characteristic5)
self.characteristic1 = checkEmpty(s: self.characteristic1)
self.characteristic2 = checkEmpty(s: self.characteristic2)
self.characteristic3 = checkEmpty(s: self.characteristic3)
self.characteristic4 = checkEmpty(s: self.characteristic4)
self.characteristic5 = checkEmpty(s: self.characteristic5)
One solution is to check for empty in a function defined in an extension to String
extension String {
func emptyAsNil() -> String? {
self.isEmpty ? nil : self
}
}
Then you could do all in one step in the init
self.characteristic1 = try container.decodeIfPresent(String.self, forKey: .characteristic1)?.emptyAsNil()
But perhaps a better solution is to gather all those properties in a collection like an array or a dictionary. Here I have chosen an array
struct Cat: Decodable {
let breedName: String
let picture: String
var characteristics: [String]
}
and then in the init we add only non-nil, non-empty values to the array
if let value = try container.decodeIfPresent(String.self, forKey: .characteristic1), !value.isEmpty {
characteristics.append(value)
}
or another way is to loop over the keys
let keys: [CodingKeys] = [.characteristic1,
.characteristic2,
.characteristic3,
.characteristic4,
.characteristic5]
for key in keys {
if let value = try container.decodeIfPresent(String.self, forKey: key), !value.isEmpty {
characteristics.append(value)
}
}
I have lots of values in my codable struct. I have URLs coming in as ""(empty string) so I need custom Decoder to convert "" as nil. So I made a propertyWrapper to solve this.
For example, I have values like ["https://google.com", "", "https://google.com"] and I want to make it as [URL?]. This works well with my decoder. It's converted as [URL("https://google.com"), nil, URL("https://google.com")]
However, I found a problem that, when I use init(from decoder: Decoder) throws, I also have to initialize all other values in struct. Is there any way to use just courseImages and use other values in struct as set?
struct CabinetCourse: Codable {
let courseId: String
let title: String
let planStartDate: Date
let planEndDate: Date
let companionTypeCd: String
let courseCategory: String
let planId: String
let nickname: String?
let isCabinet: Bool
let isFavorite: Bool?
let course: String
let score: String
let shareCnt: Int
let favoriteCnt: Int
let cabinetCnt: Int
let placeCount: Int
let createDt: Date
let childPlaceCount: Int
let wheelChairPlaceCount: Int
let elderPlaceCount: Int
#OptionalObject
var courseImages: [URL?]
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let emptyURLS = try values.decode([OptionalObject<URL>].self, forKey: .courseImages)
courseImages = emptyURLS.map { $0.wrappedValue }
// => these are the lines I don't want to write
courseId = try values.decode(String.self, forKey: .courseId)
title = try values.decode(String.self, forKey: .title)
planStartDate = try values.decode(Date.self, forKey: .planStartDate)
planEndDate = try values.decode(Date.self, forKey: .planEndDate)
companionTypeCd = try values.decode(String.self, forKey: .companionTypeCd)
courseCategory = try values.decode(String.self, forKey: .courseCategory)
planId = try values.decode(String.self, forKey: .planId)
nickname = try values.decode(String.self, forKey: .nickname)
isCabinet = try values.decode(Bool.self, forKey: .isCabinet)
isFavorite = try values.decode(Bool.self, forKey: .isCabinet)
course = try values.decode(String.self, forKey: .isCabinet)
score = try values.decode(String.self, forKey: .isCabinet)
}
#propertyWrapper
struct OptionalObject<Base: Decodable>: Decodable {
var wrappedValue: Base?
init(from decoder: Decoder) throws {
do {
let container = try decoder.singleValueContainer()
wrappedValue = try container.decode(Base.self)
} catch {
wrappedValue = nil
}
}
}
You are misunderstanding property wrappers. They "decorate" the entire property type - [URL?], not just the array element type URL?. [URL?] doesn't match the type of wrappedValue. [URL]? does (Base == [URL]), but that's not what you want.
One way to create a property wrapper that can be applied to an array of optionals is:
#propertyWrapper
struct OptionalArray<Base: Decodable>: Decodable {
var wrappedValue: [Base?]
...
Now Base == URL matches [URL?], and in init, you have to decode a [Base?]:
init(from decoder: Decoder) throws {
var arr = [Base?]()
var container = try decoder.unkeyedContainer()
for _ in 0..<(container.count ?? 0) {
if let element = try? container.decode(Base.self) {
arr.append(element)
} else {
arr.append(nil)
_ = try container.decode(String.self) // advances the decoder to the next position
}
}
wrappedValue = arr
}
Once you have the property wrapper, you don't need the custom decoding code at all. Swift figures it out.
struct CabinetCourse: Decodable {
let courseId: String
let title: String
let planStartDate: Date
let planEndDate: Date
let companionTypeCd: String
let courseCategory: String
let planId: String
let nickname: String?
let isCabinet: Bool
let isFavorite: Bool?
let course: String
let score: String
let shareCnt: Int
let favoriteCnt: Int
let cabinetCnt: Int
let placeCount: Int
let createDt: Date
let childPlaceCount: Int
let wheelChairPlaceCount: Int
let elderPlaceCount: Int
#OptionalArray
var courseImages: [URL?]
}
// That's it!
For the encoding part, it depends on how you want to encode the nils. But either way, the code is very similar to the decoding code.
Consider the following JSON: I'm trying to decode the "teams" object.
let jsonString = """
{
"Superheroes":{
"Marvel":"107",
"DC":"106"
},
"teams":{
"106":{
"name":"Marvel",
"Superheroes":{
"890":{
"name":"Batman"
}
}
},
"107":{
"name":"DC",
"Superheroes":{
"891":{
"name":"Wonder Woman"
}
}
}
}
}
"""
I have tried something like this:
struct SuperheroResponse: Decodable {
let teams: [Team]
private enum CodingKeys: String, CodingKey {
case teams = "teams"
}
private struct DynamicCodingKeys: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let teamContainer = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.teams)
print(teamContainer.allKeys.count)
let tempArray: [Team] = []
for key in teamContainer.allKeys {
let decodedObject = try teamContainer.decode(Team.self, forKey: DynamicCodingKeys(stringValue: key.stringValue)!)
tempArray.append(decodedObject)
}
teams = tempArray
}
}
struct Team: Decodable {
let name: String
}
I thought that first I would get the teams container, map over the keys and go on from there. Problem is teamContainer.allKeys.count is always zero.
Also the following line, results in following error: Cannot convert value of type 'SuperheroResponse.DynamicCodingKeys' to expected argument type 'SuperheroResponse.CodingKeys'
let decodedObject = try teamContainer.decode(Team.self, forKey: DynamicCodingKeys(stringValue: key.stringValue)!)
Finally I decode it as follows:
let jsonData = Data(jsonString.utf8)
let decodedResult = try! JSONDecoder().decode(SuperheroResponse.self, from: jsonData)
dump(decodedResult)
Any help would be appreciated. Ideally I would like something like SuperheroResponse -> [Team],
Team -> name, [Superhero], Superhero -> name
You just have a couple of minor mistakes. You're almost there.
The team container is keyed by DynamicCodingKeys:
let teamContainer = try container.nestedContainer(keyedBy: DynamicCodingKeys.self, // <=
forKey: .teams)
And the Teams can be decoded as using the key you're given:
let decodedObject = try teamContainer.decode(Team.self, forKey: key)
Also, tempArray needs to be var:
var tempArray: [Team] = []
Or replace that loop with a map:
teams = try teamContainer.allKeys.map {
try teamContainer.decode(Team.self, forKey: $0)
}
All together:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let teamContainer = try container.nestedContainer(keyedBy: DynamicCodingKeys.self, forKey: .teams)
teams = try teamContainer.allKeys.map {
try teamContainer.decode(Team.self, forKey: $0)
}
}
This question already has answers here:
Swift 4 Codable - Bool or String values
(1 answer)
Swift 4 Codable - API provides sometimes an Int sometimes a String
(1 answer)
Closed 2 years ago.
I have a json which looks like so...
{
id = 123456;
isDeleted = 0;
name = testName;
parentId = "<null>"; // This can also be integer
plantId = 1223; // This can also be string
type = 1;
}
In the response above, I can get either a string or an int for both parentId & plantId. How can I handle both the cases..?
This is how my structure looks...
struct Root : Decodable {
let organizations : [Organization1]
}
struct Organization1 : Decodable {
let id: Int
let isDeleted: Bool
let name: String
let parentId: Int?
let type: Int
let plantId: String?
let loggedInUserId: Int?
}
You can do that like this
struct GeneralProduct: Decodable {
let id: Int
let isDeleted: Bool
let name: String
let parentId: String?
let type: Int
let plantId: String?
let loggedInUserId: Int?
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
isDeleted = try container.decode(Bool.self, forKey: .isDeleted)
name = try container.decode(String.self, forKey: .id)
type = try container.decode(Int.self, forKey: .type)
loggedInUserId = try container.decode(Int.self, forKey: .loggedInUserId)
if let value = try? container.decode(Int.self, forKey: .parentId) {
parentId = String(value)
} else {
parentId = try container.decode(String.self, forKey: .id)
}
if let value = try? container.decode(Int.self, forKey: .plantId) {
plantId = String(value)
} else {
plantId = try container.decode(String.self, forKey: .id)
}
}
}
I was going through couchbase-lite to use it in my next iOS app. I have created a model named Surah for now. Definitely, I will have more model classes later.
Basically I have four questions here.
How do I add _id as my primary key in couchbase-lite?
As I will be having more classes how will I handle those? As I am creating
MutableDocument, How will that differentiate each my classes?
As I can see I have to iterate through each of my items to batch insert, won't that become slow for the large datasets?
How do i convert results from a query with large data to a array of Model Class. (in this case of array of Surah)
class Surah: Decodable {
enum Keys: String, CodingKey {
case _id
case index
case englishName
case englishMeaning
case name
case place
case count
}
var _id = ""
var index = 1
var page = 1
var numberOfAyahs = 1
var englishName = ""
var englishMeaning = ""
var name = ""
var place = ""
var isFavorite = false
var dictionary: [String: Any] {
return ["_id": _id, "index": index, "page": page]
}
required init() {}
required init(_id: String, index: Int, name: String, englishName: String, englishMeaning: String, place: String, count: Int) {
self._id = _id
self.index = index
self.name = name
self.englishName = englishName
self.englishMeaning = englishMeaning
self.place = place
self.numberOfAyahs = count
}
required convenience init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Keys.self) // defining our (keyed) container
let _id: String = try container.decode(String.self, forKey: ._id)
let index: Int = try container.decode(Int.self, forKey: .index)
let name: String = try container.decode(String.self, forKey: .name)
let englishName: String = try container.decode(String.self, forKey: .englishName)
let englishMeaning: String = try container.decode(String.self, forKey: .englishMeaning)
let place: String = try container.decode(String.self, forKey: .place)
let count: Int = try container.decode(Int.self, forKey: .count)
self.init(_id: _id, index: index, name: name, englishName: englishName, englishMeaning: englishMeaning, place: place, count: count)
}}
Code for Database Queries
let surahs = try JSONDecoder().decode([Surah].self, from: data!)
DispatchQueue.global(qos: .background).async {
//background code
do {
if let db = App.shared.database {
try db.inBatch {
for item in surahs {
let doc = MutableDocument(data: item.dictionary)
doc.setString("users", forKey: "type")
doc.setValue(Keys._id, forKey: item._id)
// doc.setValue(Keys.englishName, forKey: item.englishName)
try db.saveDocument(doc)
let index = IndexBuilder.valueIndex(items:
ValueIndexItem.expression(Expression.property("_id")), ValueIndexItem.expression(Expression.property("type")))
try db.createIndex(index, withName: "TypeNameIndex")
print("saved user document \(doc.string(forKey: "englishName"))")
}
}
}
} catch let error {
DispatchQueue.main.async {
seal.reject(error)
}
}
DispatchQueue.main.async {
seal.fulfill(surahs)
}
}
Not sure what you mean by "primary key". You can always find a doc
by its id. The name of the field that contains it is Meta.id.
The field's value is mutableDoc.getId(). As you've noticed, you
can also explicitly set the id at creation
Couchbase doesn't store classes, it stores JSON documents. If you
have documents of different types (different internal structures,
analogous to different SQL tables), give them a type field and use
it in your query
Use Database.inBatch()
The same way you would convert any JSON document to a corresponding
class: gson, Jackson, Moshi, etc