Array of dictionary comparision - swift3 - ios

I have 2 array of dictionaries. I want to write a function which compare these 2 arrays.
Function should return true only if main array contains sub array element.
Else it should return false.
Here is my logic-
let mainArry = [ ["id":"1","products":["pid": 1, "name": "A", "price": "$5"]], ["id":"3","products":["pid": 3, "name": "B", "price": "$1"]], ["id":"2","products":["pid": 14, "name": "C", "price": "$15"]]]
let array1 = [ ["id":"1","products":["pid": 1, "name": "A", "price": "$5"]], ["id":"3","products":["pid": 3, "name": "B", "price": "$1"]]]
let array2 = [ ["id":"1","products":["pid": 1, "name": "A", "price": "$5"]], ["id":"3","products":["pid": 4, "name": "B", "price": "$1"]]]
func compareDictionary(mainArry:[[String: Any]], arr2: [[String: Any]])-> Bool{
let itemsId = arr2.map { $0["id"]! } // 1, 3, 14
let filterPredicate = NSPredicate(format: "id IN %#", itemsId)
let filteredArray = mainArry.filter{filterPredicate.evaluate(with:$0) }
if filteredArray.count != arr2.count {
return false
}
for obj in filteredArray {
let prd = obj as Dictionary<String, Any>
let str = prd["id"] as! String
let searchPredicate = NSPredicate(format: "id == %#", str )
let filteredArr = arr2.filter{searchPredicate.evaluate(with:$0) }
if filteredArr.isEmpty {
return false
}
if !NSDictionary(dictionary: obj["products"] as! Dictionary<String, Any>).isEqual(to: filteredArr.last!["products"] as! [String : Any]) {
return false
}
}
return true
}
let result1 = compareDictionary(mainArry: mainArry, arr2: array1)
let result2 = compareDictionary(mainArry: mainArry, arr2: array2)
print("Result1 = \(result1)") // true
print("Result2 = \(result2)") //false
It is working. But I want to know the best way to achieve this.
Instead of using for-loop for comparision.
I want to use filter like this
let arrayC = filteredArray.filter{
let dict = $0
return !arr2.contains{ dict == $0 }
}
if arrayC is empty that means both arrays are equal.

I got it Finally!
We don't need to write big function.
let mainArry = [ ["id":"1","products":["pid": 1, "name": "A", "price": "$5"]], ["id":"3","products":["pid": 3, "name": "B", "price": "$1"]], ["id":"2","products":["pid": 14, "name": "C", "price": "$15"]]]
let array1 = [ ["id":"1","products":["pid": 1, "name": "A", "price": "$5"]], ["id":"3","products":["pid": 3, "name": "B", "price": "$1"]]]
let array2 = [ ["id":"1","products":["pid": 1, "name": "A", "price": "$5"]], ["id":"3","products":["pid": 4, "name": "B", "price": "$1"]]]
let result = array2.filter{
let dict = $0
return !mainArry.contains{
return NSDictionary(dictionary: dict).isEqual(to: $0)
}
}
if result.isEmpty {
print("Same key values")
} else {
print("Diff key values")
}

Related

How to pass a value while parsing a nested JSON in Swift

I would like to pass the parent area Id to it's children areas while parsing the nested JSON structure as per the attached response, Here I would like to insert 'parentId' for each children which will link to it's immediate parent area,
{
"areas": [
{
"id": "271341877549072423",
"name": "Breeze Office Tower",
"children": [
{
"id": "271341877549072424",
"name": "100 flinders street",
"position": 0,
"children": []
},
{
"id": "271341877549130929",
"name": "100 flinders street",
"position": 1,
"children": []
},
{
"id": "271341877549072425",
"name": "100 Flinder Stree",
"position": 2,
"children": [
{
"id": "271341877549072426",
"name": "Büro",
"position": 0,
"children": [
{
"id": "271341877549072427",
"name": "Dachgeschoß",
"position": 0,
"children": []
}
]
}
]
},
{
"id": "271341877549130931",
"name": "100 Flinder Stree",
"position": 3,
"children": [
{
"id": "271341877549130933",
"name": "Büro",
"position": 0,
"children": [
{
"id": "271341877549130935",
"name": "Dachgeschoß",
"position": 0,
"children": []
}
]
}
]
}
]
}
]
}
My JSON Codable model struct looks like,
struct AreaModel: Decodable {
var areas: [NestedAreaModel]?
}
struct NestedAreaModel: Codable {
let areaId: String
let areaName: String
let children: [NestedAreaModel]
let hasChildren: Bool
var areaPosition: Int16?
var parentId: String?
var projectId: String?
enum CodingKeys: String, CodingKey {
case areaId = "id"
case areaName = "name"
case areaPosition = "position"
case children
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.areaId = try values.decode(String.self, forKey: .areaId)
self.children = try values.decode([NestedAreaModel].self, forKey: .children)
self.areaName = try values.decode(String.self, forKey: .areaName)
self.projectId = ORAUserDefaults.selectedProjectId()
self.areaPosition = try values.decodeIfPresent(Int16.self, forKey: .areaPosition)
if !self.children.isEmpty {
self.hasChildren = true
self.parentId = self.areaId
} else {
self.hasChildren = false
}
}
}
Here I am not able to set the parent Id, its pointing its own id always.
As I allready pointed out in the comments you would need to iterate over the decoded children and set their respective parentId to the current areaId.
One possible Solution would be:
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.areaId = try values.decode(String.self, forKey: .areaId)
// decode the children to a local var
var children = try values.decode([NestedAreaModel].self, forKey: .children)
self.areaName = try values.decode(String.self, forKey: .areaName)
self.projectId = ORAUserDefaults.selectedProjectId()
self.areaPosition = try values.decodeIfPresent(Int16.self, forKey: .areaPosition)
// if there are children loop over them and assign your id
if !children.isEmpty {
self.hasChildren = true
for (index, _ ) in children.enumerated(){
children[index].parentId = areaId
}
} else {
self.hasChildren = false
}
// assign to self
self.children = children
}
Tested with the given JSON:
let decoded = try JSONDecoder().decode(AreaModel.self, from: data)
print(decoded.areas?[0].areaId)
print(decoded.areas?[0].children[0].parentId)
Result:
Optional("271341877549072423")
Optional("271341877549072423")
Remarks:
Regarding your comment on complexity:
children[index].parentId = areaId runs exactly once per child no matter of the level deapth. So this function is (O)n.

Find Scalable solution to print data from Array in Swift

I have an array starting from 1 to 100 and I have to print element if the number is divisible by 4 it should print the letter "A" and if the number is divisible by 5 it should print the letter "B" and if it is divisible by both then "AB" I want to make a scalable solution if in future I want to add number divisible by 8 should print "C" and divisible by 4 & 8 should print "AC", by 5&8 should print "BC" and if all three then "ABC"
desired output:
1
2
3
A
B
6
7
C
9
B
11
AB
13
14
...
I wrote this
for number in 1...100 {
if number.isMultiple(of: 4) && !number.isMultiple(of: 5){
print("A"
} else if !number.isMultiple(of: 4) && number.isMultiple(of: 5){
print("B")
} else if number.isMultiple(of: 4) && number.isMultiple(of: 5){
print("AB")
} else {
print(number)
}
}
Please provide a scalable solution to keep adding If-else is not a good option.
You were pretty close but you don't need the else conditions. Just add the character to the string if it matches another condition:
for number in 1...100 {
var string = ""
if number.isMultiple(of: 4) { string.append("A") }
if number.isMultiple(of: 5) { string.append("B") }
if number.isMultiple(of: 8) { string.append("C") }
print(string.isEmpty ? number : string)
}
Using a dictionary to store the characters:
let dict = [
4: "A",
5: "B",
8: "C"
]
for number in 1...100 {
var string = ""
for (key, character) in dict where number.isMultiple(of: key) {
string.append(character)
}
print(string.isEmpty ? number : string)
}
Note that dictionary is an unordered collection. If you need the characters to be sorted you would need to sort the dictionary by its values before iterating its key value pairs:
let sortedDict = dict.sorted(by: { $0.value < $1.value })
for number in 1...100 {
var string = ""
for (key, character) in sortedDict where number.isMultiple(of: key) {
string.append(character)
}
print(string.isEmpty ? number : string)
}
Here it is, instead of using if-else, you can just add up whenever you need
var stringArray = [String]()
for number in 0...100 {
stringArray.append(String(number))
}
// stringArray = ["0","1", "2", "3",....,"99", "100"]
// Adding a zero before to compare with the index
stringArray = stringArray.enumerated().map({ index, item in
var value = item
if index % 4 == 0 {
value = Int(item) == nil ? item + "A": "A"
}
return value
})
stringArray = stringArray.enumerated().map({ index, item in
var value = item
if index % 5 == 0 {
value = Int(item) == nil ? item + "B": "B"
}
return value
})
stringArray = stringArray.enumerated().map({ index, item in
var value = item
if index % 8 == 0 {
value = Int(item) == nil ? item + "C": "C"
}
return value
})
stringArray.removeFirst()
print(stringArray)
Result::
"1", "2", "3", "A", "B", "6", "7", "AC", "9", "B", "11", "A", "13", "14", "B", "AC", "17", "18", "19", "AB", "21", "22", "23", "AC", "B", "26", "27", "A", "29", "B", "31", "AC", "33", "34", "B", "A", "37", "38", "39", "ABC", "41", "42", "43", "A", "B", "46", "47", "AC", "49", "B", "51", "A", "53", "54", "B", "AC", "57", "58", "59", "AB", "61", "62", "63", "AC", "B", "66", "67", "A", "69", "B", "71", "AC", "73", "74", "B", "A", "77", "78", "79", "ABC", "81", "82", "83", "A", "B", "86", "87", "AC", "89", "B", "91", "A", "93", "94", "B", "AC", "97", "98", "99", "AB"
if you just want [Any] type then just
var resultArray = [Any]()
resultArray = stringArray.map({ number in
if let num = Int(number) { return num }
else { return number }
})
print(resultArray)

How to group array of objects with same key value pair

I have an array of dictionaries with same key value pairs.
[
{ "amount": "10" },
{ "amount": "20" },
{ "amount": "30" },
{ "amount": "20" },
{ "amount": "10" },
{ "amount": "10" }
]
I need to group this based on same key values.
Expected sample result:
There are 3x 10's, 2x 20's and 1x 30's
How do I achieve this?
let array = [ ["amount": "10"], ["amount": "20"], ["amount": "30"], ["amount": "20"], ["amount": "10"], ["amount": "10"] ]
var result: [String: Int] = [:]
let key = "amount"
array.forEach {
guard let value = $0[key] else { return }
result[value, default: 0] += 1
}
print("\(result["10"])") // 3

swift 4 split array and filter

i will build a UICollectionView with sections.
The sections are based on the return value from json.category.
the json format is like:
[{"id":"1",
"name":"Apple",
"category":"Fruits"},
{"id":"2",
"name":"Pie",
"category":"Fruits"},
{"id":"3",
"name":"Tomato",
"category":"Vegetable"}]
I need a array filter hat the array is something like: (for sectionsItems and sectionNames)
CategorieNames[STRING] = ["Fruits","Vegetable"] // the section names from json.category
Fruits = [STRING] = ["Apple","Pie"]
Vegetables = [STRING] = ["Tomato"]
Categories.append[Fruits]
Categories.append[Vegetables]
Categories[[STRING]] = [[Fruits],[Vegetable]]
Try bellow code.
let arrData = [["id": "1",
"name": "Apple",
"category": "Fruit"],
["id": "2",
"name": "Pie",
"category": "Fruit"],
["id": "3",
"name": "Tomato",
"category": "Vegetable"]]
let categorieNames = Array(Set(arrData.map({$0["category"]!})))
var arrResult:[[String]] = []
for i in 0..<categorieNames.count {
let categories = arrData.filter({$0["category"] == categorieNames[i]}).map({$0["name"]!})
arrResult.append(categories)
}
print("result : \(arrResult)")
result : [["Apple", "Pie"], ["Tomato"]]
you can do it as follows:
let arrData = [["id": "1",
"name": "Apple",
"category": "Fruit"],
["id": "2",
"name": "Pie",
"category": "Fruit"],
["id": "3",
"name": "Tomato",
"category": "Vegetable"]]
var categorys = [[String]]()
var fruits = [String]()
var vegetable = [String]()
for data in arrData {
if let category = data["category"] {
if category == "Fruit"{
if let aFruit = data["name"] {
fruits.append(aFruit)
}
}
else if category == "Vegetable" {
if let aVeggeie = data["name"] {
vegetable.append(aVeggeie)
}
}
}
}
categorys.append(fruits)
categorys.append(vegetable)

Use Swifty JSON to parse array

This is my json to parse (example):
[
{
"id": 1,
"name": "Team name",
"shower": {
"id": 1,
"status": 1,
"startLocation": {
"id": 1,
"name": "abc 16"
}
}
},
{
"id": 2,
"name": "Team name",
"shower": {
"id": 2,
"status": 1,
"startLocation": {
"id": 1,
"name": "efg 16"
}
}
}
]
paste it this json viewer to view it easily.
as you can see, it is an array (of teams).
I need to get each team and do something with it.
After many attempts, I tried using SwiftyJSON, because I thought it will be easier. But, it did not worked for me.
This is what I tried:
let array = JSON(response)
// print each subJSON in array
for team in array.arrayValue {
print(team)
}
But the loop does not work. It does not go in to the loop at all.
Maybe it does not understand that my json is an array.
I can see the array object in the debugger. It looks like this:
How can I get these sub-JSONs?
Thanks.
I think you should use
let array = JSON(parseJSON: response)
instead of
let array = JSON(response)
SwiftyJSON contains methods to parse JSON string into a JSON object, check documentation
/**
Parses the JSON string into a JSON object
- parameter json: the JSON string
- returns: the created JSON object
*/
public init(parseJSON jsonString: String) {
if let data = jsonString.data(using: .utf8) {
self.init(data)
} else {
self.init(NSNull())
}
}
/**
Creates a JSON from JSON string
- parameter string: Normal json string like '{"a":"b"}'
- returns: The created JSON
*/
#available(*, deprecated: 3.2, message: "Use instead `init(parseJSON: )`")
public static func parse(json: String) -> JSON {
return json.data(using: String.Encoding.utf8)
.flatMap{ JSON(data: $0) } ?? JSON(NSNull())
}
or alternatively you can convert son string into son object like
Swift 3:
let dataFromString = response.data(using: .utf8)
let jsonArray = JSON(data: dataFromString!)
In the following example, I save team names in an array. I've tested it.
var names = [String]()
if let array = json.array {
for i in 0..<array.count {
let name = array[i]["name"]
names.append(name.stringValue)
}
}
print(names) // ["Team name", "Team name"]
Here is the answer for Swift 5. In My case data response is something like below :
[
{
"Name": "Some Type",
"Data": [
{
"ParentId": 111,
"Code": "Personal",
"SortOrder": 1,
"Name": "Personal",
"Id": 323
},
{
"ParentId": null,
"Code": "Work",
"SortOrder": 2,
"Name": "Work",
"Id": 324
}
],
"KeyType": "Integer"
},
{
"Name": "Phone Type",
"Data": [
{
"ParentId": null,
"Code": "Phone",
"SortOrder": 1,
"Name": "Phone",
"Id": 785
},
{
"ParentId": null,
"Code": "Cell",
"SortOrder": 2,
"Name": "Cell",
"Id": 786
},
{
"ParentId": null,
"Code": "Fax",
"SortOrder": 3,
"Name": "Fax",
"Id": 787
},
{
"ParentId": null,
"Code": "Home",
"SortOrder": 4,
"Name": "Home",
"Id": 788
},
{
"ParentId": null,
"Code": "Office",
"SortOrder": 5,
"Name": "Office",
"Id": 789
}
],
"KeyType": "Integer"
}
]
I was handled it with following code.
struct responseObjectClass:BaseModel {
var responsearray: [arrayData]? = nil
init(json: JSON) {
responsearray = json.arrayValue.map { arrayData(json: $0) }
}
}
struct arrayData:BaseModel {
let Name: String?
var DataValue: [DataLookup]? = nil
let KeyType: String?
init(json: JSON) {
Name = json["Name"].stringValue
DataValue = json["Data"].arrayValue.map { DataLookup(json: $0) }
KeyType = json["KeyType"].stringValue
}
}
struct DataLookup:BaseModel {
let ParentId: Any?
let Code: String?
let SortOrder: Int?
let Name: String?
let Id: Int?
init(json: JSON) {
ParentId = json["ParentId"]
Code = json["Code"].stringValue
SortOrder = json["SortOrder"].intValue
Name = json["Name"].stringValue
Id = json["Id"].intValue
}
}
BaseModel is Optional it's just used for init Json.
protocol BaseModel {
init(json: JSON)
}
Without SwiftyJSON
Below is the valid JSON
data.json File
[{
"id": 1,
"name": "Team name",
"shower": {
"id": 1,
"status": 1,
"startLocation": {
"id": 1,
"name": "abc 16"
}
}
}, {
"id": 2,
"name": "Team name",
"shower": {
"id": 2,
"status": 1,
"startLocation": {
"id": 1,
"name": "efg 16"
}
}
}]
Below is the code to read your json.
if let path = Bundle.main.path(forResource: "data", ofType: "json") {
let url = URL(fileURLWithPath: path)
do {
let data = try Data(contentsOf: url)
if let jsonArray = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? NSArray {
for (_, item) in jsonArray.enumerated() {
let itemDict = item as! NSDictionary
let id = itemDict["id"] as! Int
let name = itemDict["name"] as! String
let shower = itemDict["shower"] as! NSDictionary
let showerId = shower["id"] as! Int
let showerStatus = shower["status"] as! Int
let startLocation = shower["startLocation"] as! NSDictionary
let startLocationId = startLocation["id"] as! Int
let startLocationName = startLocation["name"] as! String
}
}
} catch {
print("Error: \(error.localizedDescription)")
}
}
this is what worked for me:
// Convert JSON to Array
func JSONToArray(_ json: String) -> Array<Any>? {
if let data = json.data(using: String.Encoding.utf8) {
do {
return try JSONSerialization.jsonObject(with: data, options: []) as? Array
} catch let error as NSError {
print(error)
}
}
return nil
}
After using this function i could loop trough the sub JSONs.
Thanks.

Resources