I have an array of messages that I want to sort by whether they are unread or not, and their time stamp. These messages will be displayed in a TableView.
All unread messages should be displayed at the top, and then sorted by their timestamp.
Below is what I have right now; but what seems to be happening is that the items are displayed only by time stamp, without sorting them by unread at the top and then unread messages after.
let channelsSortedByUnreadFirst = dataStore.messages.sorted { $0.isUnread == true && $1.isUnread == false }
let timeSortedItems = channelsSortedByUnreadFirst.sorted(by: { $0.timeStamp > $1.timeStamp })
messagesTableViewSection.items = timeSortedItems
How do I sort these messages by both isUnread and timeStamp?
Bool does not conform to Comparable. What you need is to extend Bool type and convert its value to a type that conforms to Comparable like integer 0 or 1 and sort it using its value:
extension Bool {
var value: Int { self ? 1 : 0 }
}
true.value // 1
false.value // 0
Now you can use the boolean value when sorting your collection:
let channelsSorted = dataStore.messages.sorted { ($0.isUnread.value, $0.timeStamp) > ($1.isUnread.value, $1.timeStamp) }
Another way using Swift Algorithms stablePartition(by:):
import Algorithms
var messages = dataStore.messages
messages.sort { $0.timeStamp > $1.timeStamp }
let firstReadIndex = messages.stablePartition { !$0.isUnread }
Another Way that might be more intuitive but uses multiple passes.
print(messages.filter(\.isUnread).sorted { $0.timeStamp > $1.timeStamp } + messages.filter { !$0.isUnread })
Related
I am building a Quiz and I have a struct with 4 Variables which are countries.
I have around 15 View Controllers with 4 buttons, each button represents one Country and increments the score by 1.
Now at the end of the Quiz, I want to display the country with the highest score and make it open a new View Controller.
import Foundation
var ukScore = 0
var greeceScore = 0
var italyScore = 0
var austriaScore = 0
var finalCountry = ""
struct QuizScore {
func ukScoreIncrement() {
ukScore += 1
}
func greeceScoreIncrement() {
greeceScore += 1
}
func italyScoreIncrement() {
italyScore += 1
}
func austriaScoreIncrement() {
austriaScore += 1
}
func getFinalScore(){
if ukScore == max(ukScore, greeceScore, italyScore, austriaScore) {
finalCountry = "Uk"
} else if greeceScore == max(ukScore, greeceScore, italyScore, austriaScore) {
finalCountry = "greece"
} else if italyScore == max(ukScore, greeceScore, italyScore, austriaScore) {
finalCountry = "italy"
} else if austriaScore == max(ukScore, greeceScore, italyScore, austriaScore) {
finalCountry = "austria"
}
}
}
`
The idea is to figure out which country wins, and then pass the string to my last class which would then prepare the next segue depending on the winning country.
Using an enum with a Dictionary seems like a better way to model this. Using the .max() method on Sequence, you can then easily determine the winner.
enum Country: Hashable {
case uk
case greece
case italy
case austria
}
Using Country as a key, we then store the score for each.
struct Quiz {
private var scores = [Country: Int]()
/// Add 1 to the score of the given `Country`.
mutating func increment(_ country: Country) {
scores[country, default: 0] += 1
}
/// Determine the winner of the quiz.
/// - returns: The winning `Country`, or `nil` if there are no winners.
func winner() -> Country? {
scores.max { first, second in
// get the maximum score, sorting each dictionary element
// by the score (the .value of the dictionary)
first.value < second.value
}.map { (country, _) in
// transform the resultant element to the "key" value
country
}
}
}
You then have code completion for each possible country, and have a maintainable API.
You can then use a switch on the result to trigger a segue (or any other code).
var q = Quiz()
q.increment(.uk)
q.increment(.austria)
q.increment(.austria)
let winner = q.winner() // "austria"
switch winner {
case .uk:
triggerUKSegue()
case .greece:
triggerGreeceSegue()
case .italy:
triggerItalySegue()
case .austria:
triggerAustriaSegue()
case nil:
print("no winner yet!")
}
This is just an example, it may be wise to just have a single "result" view, and pass it the winning Country to display some result.
Additionally, we're not checking if there is more than 1 winner at the moment.
I would create a struct to represent each country and create an array of these. The struct would have a name and a score.
struct Country: Comparable {
let name: String
var score = 0
mutating func incrementScore() {
self.score += 1
}
static func < (lhs: Country, rhs: Country) -> Bool {
return lhs.score < rhs.score
}
}
Then you can create an array of these structs for whichever countries you need:
var countries = [Country(name:"UK"),
Country(name:"Greece"),
Country(name:"Italy"),
Country(name:"Austria")]
You can then increment scores as required
self.countries[0].incrementScore()
Your winner can then easily be found by sorting the array
let winner = self.countries.sorted().last()!
print("The winner is \(winner.name) with a score of \(winner.score)")
If you use a closure to sort the array then you can have the winner in the first element rather than the last.
To find the winner you could also iterate through the array looking for the maximum score. My thought was that the array is fairly small, so sorting is inexpensive and sorting also gives you second place and third place etc.
Note that normally I wouldn't recommend the use of a force unwrap, but in this case you know the array isn't empty.
Using this approach you can easily create your four buttons by iterating over the array and creating a button for each element. If you add additional Country instances to the countries array then your UI would automatically adjust.
I've got a question on property observers. There's some example code below. What I want is for the property Analysis.hasChanged to be updated to true if a.value is changed. Is there a way I can do this?
class Number {
var value: Double
init(numberValue: Double) {
self.value = NumberValue
}
}
class Analysis {
var a: Number
var hasChanged = false
init(inputNumber: Number) {
self.a = inputNumber
}
}
testNumber = Number(numberValue: 4)
testAnalysis = Analysis(inputNumber: testNumber)
print(testAnalysis.hasChanged) // will print "false"
testNumber.value = 10
print(testAnalysis.hasChanged) // will still print "false", but I want it to print "true"
In the end, I want the user to be able to be notified if any of their analyses use numbers that have been changed so that they can update the results of the analyses if they choose.
You can use the built-in property observers provided by Swift.
Every time you set a new value, the didSet will be called. You just need to attach the closure, wrapping the desired behaviour, to the Number class
class Number {
var valueDidChangeClosure: (()->())?
var value: Double {
didSet {
//won't call the valueDidChangeClosure
//if the value was changed from 10 to 10 for example..
if oldValue != value {
valueDidChangeClosure?()
}
}
}
init(numberValue: Double) {
self.value = numberValue
}
}
class Analysis {
var a: Number
var hasChanged = false
init(inputNumber: Number) {
self.a = inputNumber
self.a.valueDidChangeClosure = {
self.hasChanged = true
}
}
}
let testNumber = Number(numberValue: 4)
let testAnalysis = Analysis(inputNumber: testNumber)
print(testAnalysis.hasChanged) // will print "false"
testNumber.value = 10
print(testAnalysis.hasChanged) // will print "true"
I would do something like this, I apologize in advance if I have some syntax wrong (I usually use C/C++, think of this as more psudo code since you'd have to have a way to copy Number classes, etc.).
class Number {
var value: Double
init(numberValue: Double) {
self.value = NumberValue
}
}
class Analysis {
var a: Number
var _a: Number
bool hasChanged() {
if (a != _a) {
_a = a
return true;
}
return false;
}
init(inputNumber: Number) {
self.a = inputNumber
self._a = self.a
}
}
testNumber = Number(numberValue: 4)
testAnalysis = Analysis(inputNumber: testNumber)
print(testAnalysis.hasChanged()) // will print "false"
testNumber.value = 10
print(testAnalysis.hasChanged()) // will still print "false", but I want it to print "true"
In the end, I want the user to be able to be notified if any of their analyses use numbers that have been changed so that they can update the results of the analyses if they choose.
I don't know if this really addresses that question, I based my answer off of the code you provided. So there may be additional functionality if you want there to be some triggering method (instead of calling .hasChanged()).
Comparing doubles (and any other floating point type) with '=' or '!=' is not a good idea.
Use epsilon function instead.
Details: jessesquires.com/blog/floating-point-swift-ulp-and-epsilon/
is there a possibility to get an object from an array with an specific property? Or do i need to loop trough all objects in my array and check if an property is the specific i was looking for?
edit: Thanks for given me into the correct direction, but i have a problem to convert this.
// edit again: A ok, and if there is only one specific result? Is this also a possible method do to that?
let imageUUID = sender.imageUUID
let questionImageObjects = self.formImages[currentSelectedQuestion.qIndex] as [Images]!
// this is working
//var imageObject:Images!
/*
for (index, image) in enumerate(questionImageObjects) {
if(image.imageUUID == imageUUID) {
imageObject = image
}
}
*/
// this is not working - NSArray is not a subtype of Images- so what if there is only 1 possible result?
var imageObject = questionImageObjects.filter( { return $0.imageUUID == imageUUID } )
// this is not working - NSArray is not a subtype of Images- so what if there is only 1 possible result?
You have no way to prove at compile-time that there is only one possible result on an array. What you're actually asking for is the first matching result. The easiest (though not the fastest) is to just take the first element of the result of filter:
let imageObject = questionImageObjects.filter{ $0.imageUUID == imageUUID }.first
imageObject will now be an optional of course, since it's possible that nothing matches.
If searching the whole array is time consuming, of course you can easily create a firstMatching function that will return the (optional) first element matching the closure, but for short arrays this is fine and simple.
As charles notes, in Swift 3 this is built in:
questionImageObjects.first(where: { $0.imageUUID == imageUUID })
Edit 2016-05-05: Swift 3 will include first(where:).
In Swift 2, you can use indexOf to find the index of the first array element that matches a predicate.
let index = questionImageObjects.indexOf({$0.imageUUID == imageUUID})
This is bit faster compared to filter since it will stop after the first match. (Alternatively, you could use a lazy sequence.)
However, it's a bit annoying that you can only get the index and not the object itself. I use the following extension for convenience:
extension CollectionType {
func find(#noescape predicate: (Self.Generator.Element) throws -> Bool) rethrows -> Self.Generator.Element? {
return try indexOf(predicate).map({self[$0]})
}
}
Then the following works:
questionImageObjects.find({$0.imageUUID == imageUUID})
Yes, you can use the filter method which takes a closure where you can set your logical expression.
Example:
struct User {
var firstName: String?
var lastName: String?
}
let users = [User(firstName: "John", lastName: "Doe"), User(firstName: "Bill", lastName: "Clinton"), User(firstName: "John", lastName: "Travolta")];
let johns = users.filter( { return $0.firstName == "John" } )
Note that filter returns an array containing all items satisfying the logical expression.
More info in the Library Reference
Here is a working example in Swift 5
class Point{
var x:Int
var y:Int
init(x:Int, y:Int){
self.x = x
self.y = y
}
}
var p1 = Point(x:1, y:2)
var p2 = Point(x:2, y:3)
var p3 = Point(x:1, y:4)
var points = [p1, p2, p3]
// Find the first object with given property
// In this case, firstMatchingPoint becomes p1
let firstMatchingPoint = points.first{$0.x == 1}
// Find all objects with given property
// In this case, allMatchingPoints becomes [p1, p3]
let allMatchingPoints = points.filter{$0.x == 1}
Reference:
Trailing Closure
Here is other way to fetch particular object by using object property to search an object in array.
if arrayTicketsListing.contains({ $0.status_id == "2" }) {
let ticketStatusObj: TicketsStatusList = arrayTicketsListing[arrayTicketsListing.indexOf({ $0.status_id == "2" })!]
print(ticketStatusObj.status_name)
}
Whereas, my arrayTicketsListing is [TicketsStatusList] contains objects of TicketsStatusList class.
// TicketsStatusList class
class TicketsStatusList {
internal var status_id: String
internal var status_name: String
init(){
status_id = ""
status_name = ""
}
}
I am getting the error:
Immutable value of type 'Array Character>' only has mutating members of name removeAtIndex()
The array should have contents because that removeAtIndex line is in a loop who's condition is if the count > 1
func evaluatePostFix(expression:Array<Character>) -> Character
{
var stack:Array<Character> = []
var count = -1 // Start at -1 to make up for 0 indexing
if expression.count == 0 {
return "X"
}
while expression.count > 1 {
if expression.count == 1 {
let answer = expression[0]
return answer
}
var expressionTokenAsString:String = String(expression[0])
if let number = expressionTokenAsString.toInt() {
stack.append(expression[0])
expression.removeAtIndex(0)
count++
} else { // Capture token, remove lefthand and righthand, solve, push result
var token = expression(count + 1)
var rightHand = stack(count)
var leftHand = stack(count - 1)
stack.removeAtIndex(count)
stack.removeAtIndex(count - 1)
stack.append(evaluateSubExpression(leftHand, rightHand, token))
}
}
}
Anyone have any idea as to why this is? Thanks!
Because all function parameters are implicitly passed by value as "let", and hence are constant within the function, no matter what they were outside the function.
To modify the value within the function (which won't affect the value on return), you can explicitly use var:
func evaluatePostFix(var expression:Array<Character>) -> Character {
...
}
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
}
}
}
}