Counting unique ids within nested array using Swift - arrays

I have the following nested array and I'm trying to count how many items with unique ids there are. In the array below the count should be 2.
Array is of type List<SolData> it comes from Realm
class SolData: Object {
#objc dynamic var uid = "";
#objc dynamic var id = "";
}
extension SolData: ListDiffable {
func diffIdentifier() -> NSObjectProtocol {
return uid as NSObjectProtocol
}
func isEqual(toDiffableObject object: ListDiffable?) -> Bool {
if let object = object as? SolData {
return uid == object.uid
}
return false
}
}
Print of the array.
(
[0] SolData {
uid = sdasd;
id = jmX3;
},
[1] SolData {
uid = gfd;
id = jmX3;
},
[2] SolData {
uid = hgfd;
id = jmX3;
},
[3] SolData {
uid = terw;
id = jmX3;
},
[4] SolData {
uid = fgg;
id = GFdda;
}
)
I tried to use map in the following way
var count = 0;
var prevId = "";
let uniqueSols = diff.sol.map{ (s) -> Int in
if s.id != prevId {
count = count + 1;
prevId = s.id;
}
return count;
}
print(uniqueSols);
But I get the following error.
SWIFT RUNTIME BUG: unable to demangle type of field '_transform'. mangled type name is 'q_7ElementSTQzc'
2018-10-27 14:26:08.793528+0300 App[23634:611928] SWIFT RUNTIME BUG: unable to demangle type of field '_transform'. mangled type name is 'q_7ElementSTQzc', _transform: ())

To reproduce your code, I am going to mock the SolData class and add an initializer to make instantiation easier:
class SolData {
var uid: String = ""
var id: String = ""
init(uid: String, id: String) {
self.uid = uid
self.id = id
}
}
Let's create a few of instances:
let zero = SolData(uid: "sdasd", id: "jmX3")
let one = SolData(uid: "gfd", id: "jmX3")
let two = SolData(uid: "hgfd", id: "jmX3")
let three = SolData(uid: "terw", id: "jmX3")
let four = SolData(uid: "fgg", id: "GFdda")
And group them in an array:
let array = [zero, one, two, three, four]
To get only instances that have unique ids, let's use reduce(into:) :
let uniqueIds = array.reduce(into: Set<String>(), { $0.insert($1.id)})
The count property of uniqueIds is the number of unique ids in array:
let uniqueIdsCount = uniqueIds.count //2
If you want an array of instances with unique ids, use the following:
let instancesWithUniqueIds = array.reduce(into: [SolData]()) { accumulator, element in
if accumulator.allSatisfy({ $0.id != element.id}) {
accumulator.append(element)
}
}
accumulator.allSatisfy({ $0.id != element.id}) maybe replaced by accumulator.contains(element) and making SolData conform to Hashable.

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"]

How can I merge 2 dictionaries into one array?

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)
}

How to group the same element in array and sum it in Swift?

How to filter duplicate category element into different array and count their amount?
This is the format, record is from the core data.
var record = [Record]()
[<Record:...; data: {accountbook = "MyBook";
amount = "10.50";
category = "A";
id = 1;
},<Record:...; data: {accountbook = "MyBook";
amount = "5.50";
category = "B";
id = 2;
},<Record:...; data: {accountbook = "MyBook";
amount = "4.50";
category = "B";
id = 3;
}]
What I want
var category = ["A", "B"] //success
var total = [10.50, 10.00]
This is what I do for finding the category, and it works but how to group the same category and sum the total?
var category =[String]()
for categoryObject in record{
if let categoryItem = categoryObject.category{
category.append(categoryItem)
}
}
//I tried this code to group the same category but fail.
let result = Set(record).map{ category in return record.filter{$0 == category} }
Another way is this. but how if I have A-Z category? it will have very long code..Is there any way can detect the same value and split it to different array so that I can sum it by category.
categoryFilter = record.filter { $0.category!.contains("A") }
First group your record object by category like this
extension Sequence {
func group<GroupingType: Hashable>(by key: (Iterator.Element) -> GroupingType) -> [[Iterator.Element]] {
var groups: [GroupingType: [Iterator.Element]] = [:]
var groupsOrder: [GroupingType] = []
forEach { element in
let key = key(element)
if case nil = groups[key]?.append(element) {
groups[key] = [element]
groupsOrder.append(key)
}
}
return groupsOrder.map { groups[$0]! }
}
}
Then you will get your distinct arrays based on category like this
var records : [Record] = []// your objects
let distinctRecords = records.group(by: {$0. category})
Now you can use reduce to calculate sum of values of that category
for items in distinctRecords{
let sum = items.reduce(0.0){$0.0 + $1. amount ?? 0.0}// assuming you have float values in your amount
print(items)// do whatever you want to do with your distinct array
print(" \(sum)")
}
#Wan Jern I have written a piece of code, you can try this one. Hopefully, it will work.
var category = [String]()
var totalArr = [CGFloat]()
for categoryObject in record{
if let categoryItem = categoryObject.category{
if !category.contains(categoryItem) {
category.append(categoryItem)
totalArr.append(categoryObject.amount)
} else {
let index = category.index(of: categoryItem)
let itemAtIndex = category[index]
let itemAtIndex = itemAtIndex + categoryObject.amount
totalArr.insert(itemAtIndex, at: index)
}
}
}
Do you have your record struct in a class model?
like my data model selected from sqlite:
//Data model
import Foundation
import UIKit
class scoreModel: NSObject {
var lessonName:String = String()
var lessonCode:String = String()
var creditPoint:Double = Double()
var totalStudentNumber:Int = Int()
var teacherName:String = String()
var semesterName:String = String()
var scoreValue:String = String()
var studentCount:Int = Int()
}
If the answer is yes, we can use pointer in array and repeat while loop to do this manually.
Like my code:
let mysql = ""
let dataArray = SQLiteManager.shareInstance.queryDB(sql:mysql)
var i = 0
while i<dataArray.count-1
{
var scoreArray = [Dictionary<String, Int>]()
var range = 0
var test = 0
test = i
//print("pointer i is'\(test)'")
while ((dataArray[test]as! scoreModel).lessonCode == (dataArray[test+range]as! scoreModel).lessonCode && (test+range)<dataArray.count-1)
{
let key = (dataArray[test+range]as! scoreModel).scoreValue
let value = (dataArray[test+range]as! scoreModel).studentCount
var dict: [String: Int] = [String: Int]()
dict[key] = value
scoreArray.append(dict)
//print("working pointer is'\(test+range)'")
range = range+1
}
//transfer array
let model:resultModel = resultModel()
model.lessonName = (dataArray[test]as! scoreModel).lessonName
model.lessonCode = (dataArray[test]as! scoreModel).lessonCode
model.creditPoint = (dataArray[test]as! scoreModel).creditPoint
model.semesterName = (dataArray[test]as! scoreModel).semesterName
model.teacherName = (dataArray[test]as! scoreModel).teacherName
model.totalStudentNumber = (dataArray[test]as! scoreModel).totalStudentNumber
model.scoreArray = scoreArray
resultArray.add(model)
i = i+range
//print("range is'\(range)'")
}
Make the Records as Hashable with "category" as the unique in stubs function
struct Record: Hashable {
var accountbook = ""
var category = ""
var amount = 0.0
// added from stubs of Hashable
var hashValue: Int { return category.hashValue }
static func ==(lhs: Record, rhs: Record) -> Bool {
return lhs.category == rhs.category
}
}
Then filter the unique categories
let categories = Set(record).map { $0.category }
print(categories) // ["B", "A"]
And make a sum of each category
let totals = categories.map { c in
record.filter { $0.category == c }.map{ $0.amount }.reduce(0, +)
}
print(totals) // get the sums as [10.0, 10.5]

Find element in an array of object

I created an array of objects:
var fullMonthlyList = [SimulationMonthly]()
The class here:
class SimulationMonthly {
var monthlyMonthDuration: NSNumber = 0
var monthlyYearDuration: NSNumber = 0
var monthlyFullAmount: NSNumber = 0
var monthlyAmount: Int = 0
init(monthlyMonthDuration: NSNumber, monthlyYearDuration: NSNumber, monthlyFullAmount: NSNumber, monthlyAmount: Int){
self.monthlyMonthDuration = monthlyMonthDuration
self.monthlyYearDuration = monthlyYearDuration
self.monthlyFullAmount = monthlyFullAmount
self.monthlyAmount = monthlyAmount
}
}
I just did append to populate it, now I want to find for example if they're an existing value, for example monthlyAmount equals to "194" by search in the array, how can I do ? I have tried filter and contains but I get errors.
What I've tried:
if self.fullMonthlyList.filter({ $0.monthlyAmount == self.monthlyAmount.intValue }) { ... }
Error:
Cannot invoke 'filter' with an argument list of type '((SimulationMonthly) throws -> Bool)'
You can do:
if let sim = fullMonthlyList.first(where: { $0.monthlyAmount == 194 }) {
// Do something with sim or print that the object exists...
}
This will give you the first element in your array where monthlyAmount equals 194.
If you want all elements with that condition, you can use filter:
let result = fullMonthlyList.filter { $0.monthlyAmount == 194 }
If you don't need the object at all but you just want to know if one exists, then contains would be enough:
let result = fullMonthlyList.contains(where: { $0.monthlyAmount == 194 })
Here's a simple playground example of filtering objects based on matching a property. You should be able to expand it to your situation.
class Item {
var value: Int
init(_ val: Int) {
value = val
}
}
var items = [Item]()
for setting in 0..<5 {
items.append(Item(setting))
}
func select(_ with: Int) -> [Item] {
return items.filter { $0.value == with }
}
let found = select(3)

Swift initialise empty array to store different structs later

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)
}

Resources