Compare objects within Array and put equal values in seperate new object - ios

In my App I have an array of objects 'class: EventObjects' with several properties like 'date: NSDate?' and 'stuffToDo: String?' that are fetched from a calendar database. What I try to achieve now is putting all EventObjects with the same date property together in another object of 'class: EventsAtSameDate'.
class EventsAtSameDate: NSObject
{
var date:NSDate?
var eventObjects:NSArray<EventObject>?
}
And finally have a nested array with the EventsAtSameDate.
var nestedArrayOfEventsAtSameDateObjects: Array<EventsAtSameDate>?
I know how to search and sort an array with .filter .sort .sortInPlace etc. functions. But how can I compare the dates in the array with each other and put them in another nested Array?

You could instantiate a dictionary to keep track of the dates present while doing one iteration over the initial array of type EventObject. Then iterate the the dictionary to instantiate classes of EventsAtSameDate and append them to the nestedArrayOfEventsAtSameDateObjects: [EventsAtSameDate]
The code would look something like:
var nestedArrayOfEventsAtSameDateObjects = [EventsAtSameDate]()
//instantiate a dictionary that uses Date as the key and an array of EventObject as the associated value
var dictionary = [Date: [EventObject]]()
//iterate through your intial array of EventObjects
for obj in eObjs {
//check if you have already seen the date in the collection
if dictionary[obj.date] != nil {
//if you have - then append it to that date
dictionary[obj.date]?.append(obj)
continue
}
//otherwise, add it to the dictionary for a check later in the loop
dictionary[obj.date] = [obj]
}
//then iterate through the dictionary
for (date, eObjs) in dictionary {
if eObjs.count > 1 {
let sameDate = EventsAtSameDate()
sameDate.date = date
sameDate.eventObjects = eObjs
nestedArrayOfEventsAtSameDateObjects.append(sameDate)
}
}

I would sort the array of your events by date and then iterate over them. Check if the date is newer then the previous one and create another storage if so.
class Event {
var date:Date!
}
class EventsList
{
var date:Date!
var events:[Event]!
}
func createEventsLists(sortedEvents: [Event]) -> [EventsList] {
guard sortedEvents.count > 0 else { return [] }
var currentEventList = EventsList()
var result = [currentEventList]
var lastDate = sortedEvents.first!.date
for event in sortedEvents {
let newDate = event.date.compare(lastDate!) != .orderedDescending
if newDate {
currentEventList = EventsList()
result.append(currentEventList)
}
currentEventList.events.append(event)
lastDate = event.date
}
return result
}

Related

How to map array to a key in dictionary in swift?

I want to create a dictionary with key as date & array of events.A date can have multiple events so i want to map one date as key of dictionary to the array of string.I will be dynamic a date can have no events or a date can have multiple events.I am getting data from array of dates i need to map it with events.
I have tried below code:
func addEventToDictionary(eventModal:CalenderEventModal,date:Date) {
var key:String = self.dateFormatter().string(from: date)
if let val = dict_events[key] {
} else {
dict_events[key] = [Any]()
}
dict_events[key] = eventModal
}
Here Event modal is an Object of Event.
Assuming dict_events is a dictionary with a declared type of [String: [Any]], then I believe all you're missing is appending to this array instead of assigning it. The value portion of the dictionary is Optional, so you need to append the value to a non-Optional array, then assign this back into your dictionary's key. Also, if you know you're only going to be storing CalenderEventModal objects, you could change the type of dict_events to [String: [CalenderEventModal]]. The fix for your code would look like this:
var dict_events: [String: [CalenderEventModal]] = [:]
func addEventToDictionary(eventModal: CalenderEventModal, date: Date) {
var key: String = self.dateFormatter().string(from: date)
if var val = dict_events[key] {
val.append(eventModal)
dict_events[key] = val
} else {
let events = [CalenderEventModal]()
events.append(eventModal)
dict_events[key] = events
}
}

Sort arrays of protocols in Swift?

I have a lot of arrays stored in a ViewController of the following:
protocol AlarmClockType {
var alarmLabel: String { get set }
var sound: AlarmSound { get set }
var snooze: Bool { get set }
var alarmType: AlarmType { get set }
var alarmOn: Bool { get set }
var alarm: NSTimer? { get set }
var attributedTitle: NSMutableAttributedString { get }
static var DocumentsDirectory: NSURL { get }
static var ArchiveURL: NSURL { get }
func timeToAlarm(prayerTimes: [String: String]?) -> NSDate
}
}
Each instance has a property alarmTime (which is NSDate). How can I sort the array based on time?
To make it more clear:
It's a TableViewController and the cells display alarm times. Each time the user adds an alarm, the tableView should be sorted by alarm times.
If you want to sort an array by dates, it's probably better if you use a custom data structure that includes an NSDate.
For example, you can create a struct that has properties for your data.
struct Alarm {
var alarmTime: NSDate = NSDate()
var description: String = ""
}
This struct can be used to create an array.
let alarms = [
Alarm(alarmTime: NSDate().dateByAddingTimeInterval(120),
description: "alarm 60 s in the future"),
Alarm(alarmTime: NSDate().dateByAddingTimeInterval(30),
description: "alarm 30 s in the future"),
Alarm(alarmTime: NSDate().dateByAddingTimeInterval(90),
description: "alarm 90 s in the future")
]
The array can be sorted using the sort function and some special syntax for handling NSDates.
let sortedAlarms = alarms.sort(
{ $0.alarmTime.compare($1.alarmTime) == NSComparisonResult.OrderedAscending }
)
Finally, in this example, sortedAlarms contains everything in alarms but it is sorted by the NSDate in alarmTime.
The data in the array can be easily accessed. For example, to get the alarm time from the first alarm in the sorted alarms array, use the following code.
let alarmTime = sortedAlarms[0].alarmTime
After the alarms are sorted, you will want to reload the data in your table view using tableView.reloadData().

Dictionary inside dictionary

I am trying to use a list that is a value for a dictionary key/pair set, and this dictionary is itself a value in a key/pair set in a dictionary. To explain, this is how I initialize it.
var dictOfEvents = [Int: [Int: [PFObject]]]()
I am trying to add events to the list, with the inner dictionary's key being the day of month and the outer one being the month. For example, an event on May 1 would be:
dictOfEvents[5:[1:[ListOfEvents]]
Where ListOfEvents is an array of PFObjects. Before I added the month functionality, and thus the outer dictionary, the way I added new events was:
` self.dictOfEvents[components.day] = [event]
But now, when I try to extend this with:
self.dictOfEvents[components.month]?[components.day]! = [event]
It does not work. Any explanation on how to create new event lists and access this double layer dictionary would be greatly appreciated.
(Note: I don't know where to put the ! and the ? in the last piece of code so please excuse me if I made a mistake.)
Here is what I think could be a good use of optionals in your case (and should respond to your question):
var dic: [Int: [Int: [String]]] = [:]
dic[5] = [1:["Hello", "World"]]
if let list = dic[5]?[1] {
// your list exist and you can safely use it
for item in list {
println(item)
}
}
I just used String instead of PFObject.
A different approach could be:
/*
Define a struct to encapsulate your Month and Day
Make it Hashable so that you can use it as Dictionary key
*/
public struct MonthDay: Hashable {
let month: Int
let day: Int
public var hashValue: Int { return month * 100 + day }
}
public func ==(lhs: MonthDay, rhs: MonthDay) -> Bool {
return lhs.month == rhs.month && lhs.day == rhs.day
}
var dictOfEvents = [MonthDay :[String]]()
let aMonthAndDay = MonthDay(month: 5, day: 1)
dictOfEvents[aMonthAndDay] = ["Hello", "World"]
if let list = dictOfEvents[aMonthAndDay] {
// your list exist and you can safely use it
for item in list {
println(item)
}
}
U can simple change:
self.dictOfEvents[components.month]?[components.day]! = [event]
to :
self.dictOfEvents[components.month]![components.day]! = [event]
Because Dictionary has subscript, Dictionary? doesn't have subscript.
if U try add Events to Dictionary. I suggest to use this:
var dictOfEvents = [Int: [Int: [PFObject]]]()
var dictOfDayEvents = [Int:[PFObject]]()
dictOfDayEvents.updateValue([PFObject()], forKey: 1)
dictOfEvents.updateValue(dictOfDayEvents, forKey: 5)

How to remove duplicates values and create a table view section in Swift?

Thanks for the help guys, I have a dictionary with an array of dictionary.
My objective is to sort out an array that is read from a plist file, out of one inner dictionary value. The values are String type and returns an array with duplicate values. This returned array has a total count of 30 values (elements) that can be categorized in 6 values. I need to somehow create an array out these 6 values so I can use it on my tableView Data Source, numberOfSectionsInTableView:
I looked into map() and filter() but could not walk away with any solution. My codes are;
// setupLoading called from viewDidLoad()
func setupLoading() {
let path = NSBundle.mainBundle
let bundlePath = path.pathForResource("KempfRef", ofType: "plist")
let dict: NSDictionary? = NSDictionary(contentsOfFile: bundlePath!)
let plistArray: NSArray = dict!["KempfRef"] as NSArray
for innerDict in plistArray as Array {
let kempf: KempfFiber = KempfFiber() // Model
kempf.name = innerDict["Name"] as String
kempf.detail = innerDict["Detail"] as String
kempf.imageName = innerDict["ImageName"] as String
kempf.specGrav = innerDict["SpecGrav"] as String
kempf.toughness = innerDict["Toughness"] as String
kempf.pdfFile = innerDict["PDF"] as String
//println("DESCRIPTION: \(kempf.description())")
var theType = innerDict["Type"] as String
var kempfTypes = [String](arrayLiteral: theType)
println("theType")
}
// Here are the println of theType
The Types are:
BERKempf
BERKempf
BERKempf
BERKempf
CHRYBERKempf
CHRYBERKempf
CHRYBERKempf
CORUNKempf
CORUNKempf
CORUNKempf
CORUNKempf
GARNKempf
GARNKempf
GARNKempf
GARNKempf
GARNKempf
GARNKempf
GARNKempf
OTHERKempf
OTHERKempf
OTHERKempf
OTHERKempf
OTHERKempf
OTHERKempf
OTHERKempf
SPINEKempf
SPINEKempf
SPINEKempf
SPINEKempf
Thanks guys
I would create another dictionary with a string key (which will be your type) and an array of KempfFiber as the values. Then when you get a type you retrieve the array from the dictionary. If it is nil then create a new array, add the kempf object to it, otherwise just add the kempf object to the existing array. You can then get the keys as an array for your sections.
Something like this (where kempfData and sectionKeys are properties of your current class -
self.kempfData=[String:[KempfFiber]]
for innerDict in plistArray as Array {
let kempf: KempfFiber = KempfFiber() // Model
kempf.name = innerDict["Name"] as String
kempf.detail = innerDict["Detail"] as String
kempf.imageName = innerDict["ImageName"] as String
kempf.specGrav = innerDict["SpecGrav"] as String
kempf.toughness = innerDict["Toughness"] as String
kempf.pdfFile = innerDict["PDF"] as String
var theType = innerDict["Type"] as String
var kempfArray=self.kempfData[theType]
if (kempfArray ==nil) {
kempfArray =[Kempf]()
}
kempfArray!.append(kempf)
self.kempfData[theType]= kempfArray
}
self.sectionKeys=Array(self.kempfData.keys)
Now you can return sectionKeys.count as your section count.
Your numberOfRowsInSection will be
let sectionData=self.kempfData[self.sectionKeys[indexPath.section]]
if (sectionData != nil) {
return sectionData!.count
}
else {
return 0
}
Similarly, in you cellForRowAtIndexPath you can access the data using
let sectionData=self.kempfData[self.sectionKeys[indexPath.section]]
if (sectionData != nil) {
let kempf=sectionData[indexPath.row]
// Configure your cell...
}
else {
// Something bad happened
}
You may want to sort the self.sectionKeys array after you create it, so that your sections are in alphabetical order.

Combining queries in Realm?

I have these two objects in my model:
Message:
class Message: Object {
//Precise UNIX time the message was sent
dynamic var sentTime: NSTimeInterval = NSDate().timeIntervalSince1970
let images = List<Image>()
}
Image:
class Image: Object {
dynamic var mediaURL: String = ""
var messageContainingImage: Message {
return linkingObjects(Message.self, forProperty: "images")[0]
}
}
I want to form a query which returns messages and images, messages sorted by sentTime and images sorted by their messageContainingImage's sent time. They'd be sorted together.
The recommended code for a query is this:
let messages = Realm().objects(Message).sorted("sentTime", ascending: true)
This returns a Result<Message> object. A Result doesn't have a way to be joined to another Result. There are other issues in my way too, such as, if I could combine them, how would I then perform a sort.
Additional thoughts:
I could also add a property to Image called sentTime, then once they're combined I'd be able to call that property on both of them.
I could make them both subclass from a type which has sentTime. The problem is, doing Realm().objects(Message) would only returns things which are messages, and not subclasses of Message.
How would I be able to do this?
My end goal is to display these message and image results in a tableview, messages separately from their attached image.
I think, inheritance is not the right solution here, this introduces more drawbacks by complicating your object schema, than it's worth for your use case.
Let's go back to what you wrote is your end goal: I guess you want to display messages and images together in one table view as separated rows, where the images follow their message. Do I understand that correctly?
You don't need to sort both, sorting the messages and accessing them and their images in a suitable way will ensure that everything is sorted correctly. The main challenge is more how to enumerate / random-access this two-dimensional data structure as an one-dimensional sequence.
Depending on the amount of data, you query, you have to decide, whether you can go a simple approach by keeping them all in memory at once, or introducing a view object on top of Results, which takes care of accessing all objects in order.
The first solution could just look like this:
let messages = Realm().objects(Message).sorted("sentTime", ascending: true)
array = reduce(messages, [Object]()) { (var result, message) in
result.append(message)
result += map(message.images) { $0 }
return result
}
While the latter solution is more complex, but could look like this:
// Let you iterate a list of nodes with their related objects as:
// [a<list: [a1, a2]>, b<list: [b1, b2, b3]>]
// in pre-order like:
// [a, a1, a2, b, b1, b2, b3]
// where listAccessor returns the related objects of a node, e.g.
// listAccessor(a) = [a1, a2]
//
// Usage:
// class Message: Object {
// dynamic var sentTime = NSDate()
// let images = List<Image>()
// }
//
// class Image: Object {
// …
// }
//
// FlattenedResultsView(Realm().objects(Message).sorted("sentTime"), listAccessor: { $0.images })
//
class FlattenedResultsView<T: Object, E: Object> : CollectionType {
typealias Index = Int
typealias Element = Object
let array: Results<T>
let listAccessor: (T) -> (List<E>)
var indexTransformVectors: [(Int, Int?)]
var notificationToken: NotificationToken? = nil
init(_ array: Results<T>, listAccessor: T -> List<E>) {
self.array = array
self.listAccessor = listAccessor
self.indexTransformVectors = FlattenedResultsView.computeTransformVectors(array, listAccessor)
self.notificationToken = Realm().addNotificationBlock { note, realm in
self.recomputeTransformVectors()
}
}
func recomputeTransformVectors() {
self.indexTransformVectors = FlattenedResultsView.computeTransformVectors(array, listAccessor)
}
static func computeTransformVectors(array: Results<T>, _ listAccessor: T -> List<E>) -> [(Int, Int?)] {
let initial = (endIndex: 0, array: [(Int, Int?)]())
return reduce(array, initial) { (result, element) in
var array = result.array
let list = listAccessor(element)
let vector: (Int, Int?) = (result.endIndex, nil)
array.append(vector)
for i in 0..<list.count {
let vector = (result.endIndex, Optional(i))
array.append(vector)
}
return (endIndex: result.endIndex + 1, array: array)
}.array
}
var startIndex: Index {
return indexTransformVectors.startIndex
}
var endIndex: Index {
return indexTransformVectors.endIndex
}
var count: Int {
return indexTransformVectors.count
}
subscript (position: Index) -> Object {
let vector = indexTransformVectors[position]
switch vector {
case (let i, .None):
return array[i]
case (let i, .Some(let j)):
return listAccessor(array[i])[j]
}
}
func generate() -> GeneratorOf<Object> {
var arrayGenerator = self.array.generate()
var lastObject: T? = arrayGenerator.next()
var listGenerator: GeneratorOf<E>? = nil
return GeneratorOf<Object> {
if listGenerator != nil {
let current = listGenerator!.next()
if current != nil {
return current
} else {
// Clear the listGenerator to jump back on next() to the first branch
listGenerator = nil
}
}
if let currentObject = lastObject {
// Get the list of the currentObject and advance the lastObject already, next
// time we're here the listGenerator went out of next elements and we check
// first whether there is anything on first level and start over again.
listGenerator = self.listAccessor(currentObject).generate()
lastObject = arrayGenerator.next()
return currentObject
} else {
return nil
}
}
}
}

Resources