How to sort timestamps in the dictionary object - swift

I am trying to write a LRU cache and creates an object and check which item is added first by looking at the timestamps as follows.
import Foundation
class Item : Comparable
{
var timeStamp : Date
var value : Int
init(_ value: Int)
{
self.value = value
self.timeStamp = Date()
}
}
func < (lhs: Item, rhs: Item) -> Bool {
return lhs.timeStamp < rhs.timeStamp
}
func == (lhs: Item, rhs: Item) -> Bool {
return lhs.timeStamp == rhs.timeStamp
}
var item1 = Item (1)
var item2 = Item (2)
var item3 = Item (3)
var items = [1: item1 , 2 : item2, 3: item3]
items = items.sorted(by: {$0.date.compare($1.date) == .orderedDescending})
print(items)
I am getting the following issue:
error: value of tuple type '(key: Int, value: Item)' has no member
'date' items = items.sorted(by: {$0.date.compare($1.date) ==
.orderedDescending})
Then I need to sort and find the earliest timestamps in the dictionary.

You can simply use the < operator to compare Dates.
let sortedItems = items.sorted(by: {$0.value.timeStamp < $1.value.timeStamp})
I wouldn't recommend implementing Comparable, since even though my might want to sort your items based on simply their timestamps in some scenarios, it seems like you should take into consideration the value as well, especially for equality checking in other scenarios.
If you really only need to check timeStamp for comparison, you can make Item conform to Comparable and shorten your closure for sorted.
extension Item: Comparable {
static func == (lhs: Item, rhs: Item) -> Bool {
return lhs.timeStamp == rhs.timeStamp
}
static func < (lhs: Item, rhs: Item) -> Bool {
return lhs.timeStamp < rhs.timeStamp
}
}
let sortedItems = items.sorted(by: {$0.value < $1.value})

Just implement Comparable protocol for Item, or, maybe more useful
[1: Item(1),2: Item(2),2: Item(3),].sorted { $0.value.timeStamp < $1.value.timeStamp }

Related

Sum value of an array of structs in Swift

I'm looking for some help on how to sum a value within an array of structs.
If I have a struct defined like this:
struct Item {
let value : Float
let name : String
let planDate : String
}
And then an array of these structs like this:
let dataArray = [Item(value:100, name:"apple", planDate:"2020-10-01"),
Item(value:200, name:"lemon", planDate:"2020-10-04"),
Item(value:300, name:"apple", planDate:"2020-10-04"),
Item(value:400, name:"apple", planDate:"2020-10-01")
]
How can I sum the value while grouping by the name and planDate as well as sorting by name and planDate?
Here's what I'd like to return:
let resultArray = [Item(value:500, name:"apple", planDate:"2020-10-01"),
Item(value:300, name:"apple", planDate:"2020-10-04"),
Item(value:200, name:"lemon", planDate:"2020-10-04")
]
The easiest way (well, easy is in the eye of the beholder) is to make a dictionary that groups by a composite of your criteria, name and planDate. Now for each entry in the dictionary you've got an array of all the Items that go together! So just sum their values. Now make the dictionary back into an array and sort it.
let dataArray = [Item(value:100, name:"apple", planDate:"2020-10-01"),
Item(value:200, name:"lemon", planDate:"2020-10-04"),
Item(value:300, name:"apple", planDate:"2020-10-04"),
Item(value:400, name:"apple", planDate:"2020-10-01")
]
let dict = Dictionary(grouping: dataArray) { $0.name + $0.planDate }
let dict2 = dict.mapValues { (arr:[Item]) -> Item in
let sum = arr.reduce(0) {
$0 + $1.value
}
return Item(value:sum, name:arr[0].name, planDate:arr[0].planDate)
}
let dataArray2 = dict2.values.sorted { ($0.name, $0.planDate) < ($1.name, $1.planDate) }
print(dataArray2)
I would take a different approach as well. First make your Item conform to Equatable and Comparable. Then you can reduce your sorted items, check if each item is equal to the last item of the result. If true increase the value otherwise append a new item to the result:
extension Item: Equatable, Comparable {
static func ==(lhs: Self, rhs: Self) -> Bool {
(lhs.name, lhs.planDate) == (rhs.name, rhs.planDate)
}
static func < (lhs: Self, rhs: Self) -> Bool {
(lhs.name, lhs.planDate) < (rhs.name, rhs.planDate)
}
}
let result: [Item] = items.sorted().reduce(into: []) { partial, item in
if item == partial.last {
partial[partial.endIndex-1].value += item.value
} else {
partial.append(item)
}
}
I'll offer another variant using Dictionary(_:uniquingKeysWith:):
let dict = Dictionary(dataArray.map { ($0.name + $0.planDate, $0) },
uniquingKeysWith: {
Item(value: $0.value + $1.value, name: $0.name, planDate: $0.planDate)
})
let result = dict.values.sorted {($0.name, $0.planDate) < ($1.name, $1.planDate)}
For completeness, here's a solution that explicitly makes use of the fact that you want to use name&planDate as both group identifier, and sort key.
You can make use of the Identifiable protocol, and build a struct with name&planDate (structs are almost free in Swift):
extension Item: Identifiable {
struct ID: Hashable, Comparable {
let name: String
let planDate: String
static func < (lhs: Item.ID, rhs: Item.ID) -> Bool {
(lhs.name, lhs.planDate) < (rhs.name, rhs.planDate)
}
}
var id: ID { ID(name: name, planDate: planDate) }
// this will come in handly later
init(id: ID, value: Float) {
self.init(value: value, name: id.name, planDate: id.planDate)
}
}
Then you can destructure the Item struct by its identifier, accumulate the values, and restructure it back:
let valueGroups = dataArray
.reduce(into: [:]) { groups, item in
// here we destructure the Item into id and value, and accumulate the value
groups[item.id, default: 0] += item.value
}
.sorted { $0.key < $1.key } // the key of the dictionary is the id, we sort by that
// and were we restructure it back
let result = valueGroups.map(Item.init(id:value:))
We can take it even further and refine the operations we need by extensing Sequence:
extension Sequence {
/// returns an array made of the sorted elements by the given key path
func sorted<T>(by keyPath: KeyPath<Element, T>) -> [Element] where T: Comparable {
sorted { $0[keyPath: keyPath] < $1[keyPath: keyPath] }
}
/// Accumulates the values by the specified key
func accumulated<K, T>(values: KeyPath<Element, T>,
by key: KeyPath<Element, K>) -> [K:T]
where K: Hashable, T: AdditiveArithmetic {
reduce(into: [:]) { $0[$1[keyPath: key], default: T.zero] += $1[keyPath: values] }
}
}
The two new additions, sort by a key path, and accumulate key paths by using another key, are independent enough to deserve function for they own, as they are generic enough to be reusable in other contexts.
The actual business logic becomes simple as
let result = dataArray
.accumulated(values: \.value, by: \.id)
.map(Item.init(id:value:))
.sorted(by: \.id)
Even if this solution is more verbose than the other one, it has the following advantages:
clear separation of concerns
breaking the code into smaller units, which can be independently unit tested
code reusability
simple caller code, easy to understand and review

Custom comparator for Swift

This is my code (simplified code):
struct SomeStruct {
let id: Int
let age: Int
}
extension SomeStruct: Hashable {
var hashValue: Int {
return id.hashValue * age.hashValue
}
static func ==(lhs: SomeStruct, rhs: SomeStruct) -> Bool {
return lhs.id == rhs.id && lhs.age == rhs.age
}
}
struct Calculator {
let struct1: [SomeStruct]
let struct2: [SomeStruct]
func uniqueById() {
let struct3 = Set(struct2).union(Set(struct1))
// I want to union it by property 'id' only.
// If the property 'id' is equal for both objects,
// the object in struct2 should be used (since that can have a different age property)
}
}
SomeStruct is a generated struct which I do not want to edit. I want to create a Set for SomeStruct that is based on 1 property: id. For that, I think I need a custom Comparator, just as Java has. Is there any Swifty way? This is the only thing I can come up with, but I am wondering if there is a better way:
struct SomeStructComparatorById: Hashable {
let someStruct: SomeStruct
var hashValue: Int {
return someStruct.id.hashValue
}
static func ==(lhs: SomeStructComparatorById, rhs: SomeStructComparatorById) -> Bool {
return lhs.someStruct.id == rhs.someStruct.id
}
}
First, I don't think this would work in Java. addAll() doesn't take a Comparator (nor does contains, etc.) Comparators are for sorting, not equality. Conceptually this is breaking how Set works in any language. Two items are not "equal" unless they can be swapped in all cases.
That tells us that we don't want a Set here. What you want here is uniqueness based on some key. That's a Dictionary (as Daniel discusses).
You could either just have a "id -> age" dictionary or "id -> struct-of-other-properties" dictionary as your primary data type (rather than using Array). Or you can turn your Array into a temporary Dictionary like this:
extension Dictionary {
init<S>(_ values: S, uniquelyKeyedBy keyPath: KeyPath<S.Element, Key>)
where S : Sequence, S.Element == Value {
let keys = values.map { $0[keyPath: keyPath] }
self.init(uniqueKeysWithValues: zip(keys, values))
}
}
And merge them like this:
let dict1 = Dictionary(struct1, uniquelyKeyedBy: \.id)
let dict2 = Dictionary(struct2, uniquelyKeyedBy: \.id)
let merged = dict1.merging(dict2, uniquingKeysWith: { old, new in old }).values
This leaves merged as [SomeStruct].
Note that this Dictionary(uniquelyKeyedBy:) has the same preconditions as Dictionary(uniqueKeysWithValues:). If there are duplicate keys, it's a programming error and will raise precondition failure.
You could do something like this:
var setOfIds: Set<Int> = []
var struct3 = struct2.filter { setOfIds.insert($0.id).inserted }
struct3 += struct1.filter { setOfIds.insert($0.id).inserted }
The result would be an array of SomeStruct, with all elements with unique ids.
You could define this as a custom operator :
infix operator *>
func *> (lhs: [SomeStruct], rhs: [SomeStruct]) -> [SomeStruct] {
var setOfIds: Set<Int> = []
var union = lhs.filter { setOfIds.insert($0.id).inserted }
union += rhs.filter { setOfIds.insert($0.id).inserted }
return union
}
Your code would then look like this:
func uniqueById() {
let struct3 = struct2 *> struct1
//use struct3
}
The short answer is no. Swift sets do not have any way to accept a custom comparator and if you absolutely must have a Set, then your wrapper idea is the only way to do it. I question the requirement for a set though.
Instead of using Set in your calculator, I recommend using dictionary.
You can use a Dictionary to produce an array where each item has a unique ID...
let struct3 = Dictionary(grouping: struct1 + struct2, by: { $0.id })
.compactMap { $0.value.max(by: { $0.age < $1.age })}
Or you can keep the elements in a [Int: SomeStruct] dictionary:
let keysAndValues = (struct1 + struct2).map { ($0.id, $0) }
let dictionary = Dictionary(keysAndValues, uniquingKeysWith: { lhs, rhs in
lhs.age > rhs.age ? lhs : rhs
})

Swift 3 Equatable Struct optional function

This will be a little long winded so please bear with me. I am also a bit of a swift beginner as well. I have an array with a defined struct.
var modelOriginalArray = [model]()
struct model: Equatable {
var modelID = String()
var modelName = String()
var modelNumber = String()
var manufacturer = String()
var manufShort = String()
var phiTypeCode = String()
var phiTypeDesc = String()
init(modelID: String, modelName: String, modelNumber: String, manufacturer: String, manufShort: String, phiTypeCode: String, phiTypeDesc: String) {
self.modelID = modelID
self.modelName = modelName
self.modelNumber = modelNumber
self.manufacturer = manufacturer
self.manufShort = manufShort
self.phiTypeCode = phiTypeCode
self.phiTypeDesc = phiTypeDesc
}
static func == (lhs: model, rhs: model) -> Bool {
return lhs.manufShort == rhs.manufShort && lhs.modelName == rhs.modelName && lhs.modelNumber == rhs.modelNumber
}
}
I load about 5000 records into this array. I then have a need to filter this array based on search criteria. Let's say I am looking for a manufacturer "Sony". There is the possibility of multiple models for Sony so I need to separate all Sony records from the greater 5000.
srchval = "SONY"
var filteredArray = [model]()
var uniqueFilteredArray = [model]()
filteredArray = self.modelOriginalArray.filter { $0.manufShort.range(of: srchval, options: .caseInsensitive) != nil }
This will give me an array with only "Sony" records. However there is a possibility that some of those "Sony" records have duplicate manufShort, modelName, modelNumber values under different modelID's. I need to separate those and only have unique records.
// Find Uniquic options by removing duplicate Model Names
uniqueFilteredArray = unique(models: filteredArray)
func unique(models: [model]) -> [model] {
var uniqueModels = [model]()
for model in models {
if !uniqueModels.contains(model) {
uniqueModels.append(model)
}
}
return uniqueModels
}
This all works ver well. The problem I have is in the filter there are situations where I have to make sure the record is matching on:
static func == (lhs: model, rhs: model) -> Bool {
return lhs.manufShort == rhs.manufShort && lhs.modelName == rhs.modelName && lhs.modelNumber == rhs.modelNumber
}
And in a different situation in the same class I need to match only on the manufShort:
static func == (lhs: model2, rhs: model2) -> Bool {
return lhs.manufShort == rhs.manufShort
}
I have tried creating a separate model i.e.. model2 with this different static function, but I have difficulties moving data from one array to another with a different struct.
Any thoughts or a better way to accomplish this?
Thanks
Since you use two different approaches for defining the "equality" of two models, you should probably consider not using the == operator, as you don't really test equality if the equality predicate is different from case to case. Rather, you have two different custom predicates (which applies to two model instances) that you would like to use in different contexts. Why not use two custom type (static) methods for this, with descriptive names, semantically describing their different meanings?
Thanks! That makes a great deal of sense, how do I call the different (static) methods so that it will hit the right function based on what I am after. An example?
Example setup:
struct Foo {
let id: String
let bar: Int
let baz: Int
let bax: Int
init(_ id: String, _ bar: Int, _ baz: Int, _ bax: Int)
{
self.id = id
self.bar = bar
self.baz = baz
self.bax = bax
}
static func byBarEqualityPredicate(lhs: Foo, rhs: Foo) -> Bool {
return lhs.bar == rhs.bar
}
static func byBazAndBaxEqualityPredicate(lhs: Foo, rhs: Foo) -> Bool {
return lhs.baz == rhs.baz && lhs.bax == rhs.bax
}
}
let fooArr = [Foo("Foo A", 1, 2, 3),
Foo("Foo B", 1, 1, 2),
Foo("Foo C", 3, 1, 2)]
A slightly modified version of your unique method, now supplying a (Foo, Foo) -> Bool predicate among with the foos array:
func unique(foos: [Foo], predicate: (Foo, Foo) -> Bool) -> [Foo] {
var uniqueFoos = [Foo]()
for foo in foos {
if !uniqueFoos.contains(where: { predicate($0, foo) }) {
uniqueFoos.append(foo)
}
}
return uniqueFoos
}
Testing with the two different Foo predicates:
// by 'bar' "equality": Foo A and Foo B will be considered "equal",
// and only Foo A, among these two , will be added to the "unique" array.
let fooWithBarPredicate = unique(foos: fooArr, predicate: Foo.byBarEqualityPredicate)
fooWithBarPredicate.forEach { print($0.id) } // Foo A, Foo C
// by 'baz' && 'bax' "equality": Foo A and Foo C will be considered "equal",
// and only Foo A, among these two, will be added to the "unique" array.
let fooWithBazBaxPredicate = unique(foos: fooArr, predicate: Foo.byBazAndBaxEqualityPredicate)
fooWithBazBaxPredicate.forEach { print($0.id) } // Foo A, Foo B
You can use the following extension on Collection. Not tested.
extension Collection where Iterator.Element: Equatable {
func uniques(by equals: (Iterator.Element, Iterator.Element) -> Bool) -> [Iterator.Element] {
var uniqueElems: [Iterator.Element] = []
for elem in self {
if uniqueElems.index(where: { equals($0, elem) }) == nil {
uniqueElems.append(elem)
}
}
return uniqueElems
}
}
Then you can use
filteredArray.uniques { $0.manufShort == $1.manufShort }
filteredArray.uniques { $0.manufShort == $1.manufShort && $0.modelName == $1.modelName && $0.modelNumber == $1.modelNumber }

Custom hashable struct for my Dictionary

I'd like to build a hashable value to be used for my dictionary's key. It should consist of a structure with two strings and an NSDate. I'm not sure I built my hashValue getter correctly below:
// MARK: comparison function for conforming to Equatable protocol
func ==(lhs: ReminderNotificationValue, rhs: ReminderNotificationValue) -> Bool {
return lhs.hashValue == rhs.hashValue
}
struct ReminderNotificationValue : Hashable {
var notifiedReminderName: String
var notifiedCalendarTitle: String
var notifiedReminderDueDate: NSDate
var hashValue : Int {
get {
return notifiedReminderName.hashValue &+ notifiedCalendarTitle.hashValue &+ notifiedReminderDueDate.hashValue
}
}
init(notifiedReminderName: String, notifiedCalendarTitle: String, notifiedReminderDueDate: NSDate) {
self.notifiedReminderName = notifiedReminderName
self.notifiedCalendarTitle = notifiedCalendarTitle
self.notifiedReminderDueDate = notifiedReminderDueDate
}
}
var notifications: [ReminderNotificationValue : String] = [ : ]
let val1 = ReminderNotificationValue(notifiedReminderName: "name1", notifiedCalendarTitle: "title1", notifiedReminderDueDate: NSDate())
let val2 = ReminderNotificationValue(notifiedReminderName: "name1", notifiedCalendarTitle: "title1", notifiedReminderDueDate: NSDate())
notifications[val1] = "bla1"
notifications[val2] = "bla2"
notifications[val2] // returns "bla2".
notifications[val1] // returns "bla1". But I'd like the dictionary to overwrite the value for this to "bla2" since val1 and val2 should be of equal value.
The problem is not your hashValue implementation, but the == function.
Generally, x == y implies x.hashValue == y.hashValue, but not
the other way around. Different objects can have the same hash value.
Even
var hashValue : Int { return 1234 }
would be an ineffective, but valid hash method.
Therefore in ==, you have to compare the two objects for exact
equality:
func ==(lhs: ReminderNotificationValue, rhs: ReminderNotificationValue) -> Bool {
return lhs.notifiedReminderName == rhs.notifiedReminderName
&& lhs.notifiedCalendarTitle == rhs.notifiedCalendarTitle
&& lhs.notifiedReminderDueDate.compare(rhs.notifiedReminderDueDate) == .OrderedSame
}
Another problem in your code is that the two invocations
of NSDate() create different dates, as NSDate is an absolute
point in time, represented as a floating point number with sub-second
precision.

Compare Day/Month in Swift Struct

I would like sort an array of SpecialDay instances: [struct1, struct2, struct3, ...] using the code below:
struct SpecialDay: Hashable, Comparable {
var day: Int
var month: Int
var hashValue: Int {
return (31 &* day.hashValue &+ month.hashValue)
}
}
func ==(lhs: SpecialDay, rhs: SpecialDay) -> Bool {
return lhs.day == rhs.day && lhs.month == rhs.month
}
func <(lhs: SpecialDay, rhs: SpecialDay) -> Bool {
return lhs.day < rhs.day && lhs.month <= rhs.month
}
Sorting would be done like this:
let newArray = currentArray.sorted({ $0 < $1 })
I think I only need to find the right logic behind the comparable method:
func <(lhs: SpecialDay, rhs: SpecialDay) -> Bool
... but I am pulling my teeth on this one. My currently implemented logic is obviously not sorting correctly.
Any hints would be greatly appreciated.
The problem is the and in this line:
return lhs.day < rhs.day && lhs.month <= rhs.month
If the lhs month is less than the rhs it should always return true not taking into account the day. However, when you and with the comparison to days, you can get a false for the days not being less and a true for the months, which results in a false. You need something just a little more complicated:
func <(lhs: SpecialDay, rhs: SpecialDay) -> Bool {
if lhs.month < rhs.month { return true }
else if lhs.month > rhs.month { return false }
else {
return lhs.day < rhs.day
}
}