Combining two NSPredicates with independent NSSortDescriptors in Swift - arrays

I am using CoreData and Swift 3, but I think this is more of a general question about NSPredicates, NSSortDescriptors, and NSFetchedResultsController...
I have an entity with a integer attribute that I want to sort on, but I need to split the results based on a passed in integer and sort the two halves independently in descending order and then combine them. All the entities with the attribute <= the splitting value go at the front (descending), and all the entities that are > the splitting value go at the end (descending).
Here's an example:
Normal fetched results sorted by the integer attribute in descending order:
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
Given a splitting value of "5", the final result should be:
[5, 4, 3, 2, 1, 10, 9, 8, 7, 6]
Or with a splitting value of 8, the final result should be:
[8, 7, 6, 5, 4, 3, 2, 1, 10, 9]
What I am doing is creating a roll-over point, the way days a month roll-over at 28, 29, 30, or 31. In my case the roll-over point is dynamic, and I am not working with days.
I cannot figure out how to do this with predicates and sort descriptors. Is this even possible?
I have read about doing multiple fetches, using a "sortOrder" attribute in the entity. Basically, fetching once, the putting the results in an array, sorting the array the way I want, then setting the "sortOrder" attribute on every entity, saving the context, then fetching again, sorting by "sortOrder". I can do that, but I was hoping for something more elegant.
Here's some code that does the proper sorting on an array:
func rolloverSort(withArray: [Int], andIndex index: Int) -> [Int] {
let inputArray = withArray.sorted()
var newArray = [Int]()
var splitIndex: Int = 0
for item in inputArray {
if item <= index {
newArray.insert(item, at: 0)
splitIndex += 1
}
if item > index {
newArray.insert(item, at: splitIndex)
}
}
return newArray
}
The result will be driving a UITableView, so I need to do this within the context of an NSFetchedResultContoller, which is why I was thinking of predicates and sort descriptors.
Here is my NSFetchedResultsController and some surrounding context:
// sortMethod (used in sortDescriptor) is set via an action sheet button.
// Currently it's just a string for the entity attribute to sort on.
// The closure on each button sets self._fetchedRequestController = nil,
// sets sortMethod to the desired sorting string, then calls
// tableView.reloadData().
var fetchedResultsController: NSFetchedResultsController<Entity> {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}
let fetchRequest: NSFetchRequest<Entity> = Entity.fetchRequest()
fetchRequest.fetchBatchSize = 20
let sortDescriptor = NSSortDescriptor(key: sortMethod, ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
let aFetchedResultsController = NSFetchedResultsController(
fetchRequest: fetchRequest,
managedObjectContext: managedObjectContext!,
sectionNameKeyPath: nil,
cacheName: "Master")
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController
do {
try _fetchedResultsController!.performFetch()
} catch {
// FIXME: Replace error handling stub.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
return _fetchedResultsController!
}
var _fetchedResultsController: NSFetchedResultsController<Card>? = nil
That works with simple sorting, but I am struggling to figure out how to slot in the sorting function from A.Jam.

Lets say you have a data store with 10 entities called Number already inserted. The model entity has one attribute called value of type Int32 which you want to sort on (the values in your data store range from 1 to 10).
Use this function to split your data based on your key value and sort them using an instance of NSSortDescriptor:
func fetchSortEntites(basedOn integer: Int32) -> ([Int32], [Number]){
var sortedNumberArray: [Int32] = []
var sortedManagedObjects: [Number] = []
// Phase 1
let predicate1 = NSCompoundPredicate(format: "value <= %ld", integer)
let sortDescriptor1 = NSSortDescriptor(key: "value", ascending: false)
let fetchRequest1 = NSFetchRequest<Number>(entityName: "Number")
fetchRequest1.predicate = predicate1
fetchRequest1.sortDescriptors = [sortDescriptor1]
do{
let managedObjects = try managedObjectContext.fetch(fetchRequest1)
for managedObject in managedObjects{
sortedNumberArray.append(managedObject.value)
sortedManagedObjects.append(managedObject)
}
} catch let error as NSError {
print(error.debugDescription)
fatalError("*** Failed to fetch managed objects from the context!")
}
// Phase 2
let predicate2 = NSCompoundPredicate(format: "value > %ld", integer)
let sortDescriptor2 = NSSortDescriptor(key: "value", ascending: false)
let fetchRequest2 = NSFetchRequest<Number>(entityName: "Number")
fetchRequest2.predicate = predicate2
fetchRequest2.sortDescriptors = [sortDescriptor2]
do{
let managedObjects = try managedObjectContext.fetch(fetchRequest2)
for managedObject in managedObjects{
sortedNumberArray.append(managedObject.value)
sortedManagedObjects.append(managedObject)
}
} catch let error as NSError {
print(error.debugDescription)
fatalError("*** Failed to fetch managed objects from the context!")
}
return (sortedNumberArray, sortedManagedObjects)
}
}
Result:
let myTuple = fetchSortEntites(basedOn: 5)
for number in myTuple.0{
print (number)
}
let myTuple = fetchSortEntites(basedOn: 8)
for number in myTuple.0{
print (number)
}
Hope this helped!

You can create wrapper around two NSFetchedResultsController (next resultsController).
First resultsController should fetch values before (<=) the threshold and second resultsController should fetch values after (>) the threshold.
After that wrapper can return required data as union of datas from the requestControllers.
For example:
//Get count cells
int countCells = resultsController1.sections.first.numberOfObjects + resultsController2.sections.first.numberOfObjects;
<...>
//Get value for cell
int value = 0;
if (cellIndex >= <counts_in_first_resultsController>)
{
value = <Get_from_the_second>;
}
else
{
value = <Get_from_the_first>;
}

Related

Merging arrays of dictionaries based on dates

I have 2 arrays, both of kind [[String:Any]] , where each element :
["date":Date,"value":CGFloat] //always looks like this
I might even have more than 2 (!)
I would like to create a single array with the same structure that sums all of them (2 or more) for each date that appears in all of them.
If the date of array1 does not appear on the others(array2, etc) I will simply add 0 to the value at array 1 for this specific date.
Is there a simple efficient way to do so ?
Instead of dictionaries use structs, it's more convenient:
struct MyStruct {
let date: Date
let value: CGFloat
}
Let's create 3 arrays of MyStructs:
let now = Date()
let later = now.addingTimeInterval(3600)
let earlier = now.addingTimeInterval(-3600)
let array1: [MyStruct] = [MyStruct(date: now, value: 1),
MyStruct(date: later, value: 2)]
let array2: [MyStruct] = [MyStruct(date: now, value: 3),
MyStruct(date: later, value: 4)]
let array3: [MyStruct] = [ MyStruct(date: earlier, value: 5),
MyStruct(date: later, value: 6)]
Now, let's group the elements and add the values for the elements with the same date property:
let allArrays = array1 + array2 + array3
let dict = Dictionary(allArrays.map { ($0.date, $0.value) },
uniquingKeysWith: { $0 + $1 })
All you have to do now is convert it back to an array of MyStruct:
let newArray = dict.map { MyStruct(date: $0.key, value: $0.value) }
And you can check the results like so:
for element in newArray {
print("date =", element.date, "value =", element.value)
}
I found a way, assuming data is inside a structure(not a dic) which is a better practice.
I will put all arrays into a single large array, sort it by dates, loop on it and as long as date is equal previous date(or close enough to equality), I will sum the values up. When the next date is different, I will save the date and the sum.
//create a combined array from all given arrays
var combined = [RootData]()
for list in dataSets {combined.append(contentsOf: list)}
//sort it by dates
let sortedArray = combined.sorted { $0.date < $1.date }
//new array - sum of all
var sumData = [RootData]()
var last:Date = sortedArray[0].date //set starting point
var sum:CGFloat = 0
for element in sortedArray
{
//same date - accumulate(same is less than 1 sec difference)
if(abs(element.date.seconds(from: last)) <= 1) {
sum+=element.value
}
//save
else {
sumData.append(RootData(value:sum,date:last))
sum=element.value
}
last=element.date
}
//last object
sumData.append(RootData(value:sum,date:last))
return averageData
Here RootData is a simple structure for the data with :
value:CGFloat
date:Date
Works as expected.
Because dates are not always completely equal , I check equality by assuming 1 second or less is the same date.

How to filter a Swift array by another Swift array knowing that the filter element is a sequence? speed matters

I have one Swift array of 10,000 structs:
struct Book {
var id: Int?
var name: String?
var pages: Int?
var words: Int?
}
var firstArray: [Book] = [] // contains 10,000 elements
and I have a second Swift array of type Int, with 5,000 elements:
var secondArray: [Int] = [] // contains 5,000 elements
I want to filter the firstArray by removing all elements in it where the id field (Book.id) is not contained in the secondArray.
Knowing that Book.id is unique for every element in firstArray and also in a sequence (from small to large). eg. 1, 2, 3, 6, 8, 10, 14, 15, 16, 40, 50, 51, etc. (some numbers may be skipped)
The secondArray is also unique and in a sequence (small to large)
What is the fastest way to filter firstArray in Swift 4?
Knowing that the arrays are a sequence the filtering should become quicker as we process the array right? Meaning if we are half-way through the firstArray, we will only be looping through half of firstArray searching for a match in the secondArray. Same for secondArray, as the array will become smaller every time we find a match. Does this all make sense?
Hope someone out there knows how to do this. I've seen this done on Android (Kotlin), but how to do it in Swift?
I think in Kotlin it is like this:
firstArray?.let { dataFirstArray ->
secondArray?.let {
firstArray = ArrayList(dataFirstArray.asSequence().filter { dataSecondArray -> dataSecondArray in it }.toList())
}
}
If id always exists, don't make it optional.
struct Book {
var id: Int
var name: String?
var pages: Int?
var words: Int?
}
The simplest way to filter is a single line:
func filter1(firstArray:[Book],secondArray:[Int]) -> [Book]
{
return firstArray.filter{secondArray.contains($0.id)}
}
I also tried to make use of the fact that the arrays are sorted by doing this:
func filter2(firstArray:[Book],secondArray:[Int]) -> [Book]
{
var j = 0;
return firstArray.filter{
while(j < secondArray.count && secondArray[j] < $0.id)
{
j += 1
}
if(j < secondArray.count && $0.id == secondArray[j])
{
j += 1
return true
}
return false
}
}
As suggested by comment, I also tried using Set:
func filter3(firstSet:Set<Book>,secondSet:Set<Int>) -> Set<Book>
{
return firstSet.filter{secondSet.contains($0.id)}
}
Tested with the following code:
var firstArray: [Book] = (0..<10000).map{Book(id: $0, name: nil, pages: nil, words: nil)}.filter {_ in Int.random(in: 0...1) == 0}
var secondArray: [Int] = (0..<10000).filter{_ in Int.random(in: 0...1) == 0}
var timestamp = Date().timeIntervalSince1970
let result1 = filter1(firstArray: firstArray, secondArray: secondArray)
print(Date().timeIntervalSince1970 - timestamp)
timestamp = Date().timeIntervalSince1970
let result2 = filter2(firstArray: firstArray, secondArray: secondArray)
print(Date().timeIntervalSince1970 - timestamp)
timestamp = Date().timeIntervalSince1970
let result3 = filter3(firstArray: firstArray, secondSet: Set(secondArray))
print(Date().timeIntervalSince1970 - timestamp)
test result:
2.687404155731201
0.0014042854309082031
0.002758026123046875
Hope this help

How to dynamically set index of an array on run time in golang?

I have searched a lot about it but couldn't find the proper solution. what I am trying to do is to create the following as final output using arrays and slices in golang.
[
11 => [1,2,3],
12 => [4,5],
]
what I have implemented is:
type Industries struct {
IndustryId int `json:"industry_id"`
FormIds []int `json:"form_ids"`
}
var IndustrySettings IndustrySettings
_ := json.NewDecoder(c.Request.Body).Decode(&IndustrySettings)
var industryArr []int
for _, val := range IndustrySettings.IndustrySettings {
industryArr = append(industryArr, val.IndustryId)
}
In this IndustrySettings contains following json
{
"industry_settings": [{
"industry_id": 11,
"form_ids": [1, 2, 3]
},
{
"industry_id": 12,
"form_ids": [4, 5]
}
]
}
I want to loop through this json and convert into the array like industry_id as key and form_ids as values.
Can anyone please tell how to accomplish this?
Thanks!
Edit
I mean I need output like
[
11 => [1,2,3],
12 => [4,5],
]
where 11 and 12 are the industry_id as given in the json to be used as key of the array and [1,2,3], [4,5] are the form ids to be set as values in the array.
I think what you might want to do is define a struct that describes the JSON model you are trying to decode, unmarshal the JSON into a slice of that struct, and then loop through each decoded value, placing it in a map.
An example of this is here:
https://play.golang.org/p/Dz8XBnoVos
A more efficient way may be to write a customized JSON decode function and decode it into a map with key as your industry_id. But if you must use an array/slice it can be something on these lines (the first argument to the add function index can be your industry_id - change mystruct definition to whatever you need):
package main
import (
"fmt"
)
type mystruct []int
var ns []mystruct
func main() {
ns = make([]mystruct, 1, 1)
add(1, []int{2222, 24, 34})
add(7, []int{5})
add(13, []int{4,6,75})
add(14, []int{8})
add(16, []int{1, 4, 44, 67, 77})
fmt.Println("Hello, playground", ns)
}
func add(index int, ms mystruct) {
nscap := cap(ns)
nslen := len(ns)
//fmt.Println(nscap, nslen, index, ms)
if index >= nscap {
//Compute the new nslen & nscap you need
//This is just for a sample - doubling it
newnscap := index * 2
newnslen := newnscap - 1
nstemp := make([]mystruct, newnslen, newnscap)
copy(nstemp, ns)
ns = nstemp
fmt.Println("New length and capacity:", cap(ns), len(ns))
nscap = cap(ns)
nslen = len(ns)
}
//Capacity and length may have changed above, check
if index < nscap && index >= nslen {
fmt.Println("Extending slice length", nslen, "to capacity", nscap)
ns = ns[:nscap]
}
ns[index] = ms
}
On playground: https://play.golang.org/p/fgcaM1Okbl

How to arrange a CGPoint array in order of the most frequent points

I saw this post
which showed how to get the most frequent value of an array for say, integers in the following way:
let myArray = [4, 4, 4, 3, 3, 3, 4, 6, 6, 5, 5, 2]
// Create dictionary to map value to count
var counts = [Int: Int]()
// Count the values with using forEach
myArray.forEach { counts[$0] = (counts[$0] ?? 0) + 1 }
// Find the most frequent value and its count with max(isOrderedBefore:)
if let (value, count) = counts.max(isOrderedBefore: {$0.1 < $1.1}) {
print("\(value) occurs \(count) times")
}
I want to achieve the same result for an array of CGPoints, this is a bit different. I tried using the same code and got an error:
Type 'CGPoint' does not conform to protocol 'Hashable'
at the line
var counts = [CGPoint: Int]()
and an error
Value of type 'CGPoint' has no member '1'
at the line
if let (value, count) = counts.max(isOrderedBefore: {$0.1 < $1.1}) {
How can I arrange the CGPoint array in order of frequency and print say, a tuple with the value and the number of time it appears?
What this line of error means :
Type 'CGPoint' does not conform to protocol 'Hashable'
is that you can't use CGPoint objects as keys to a dictionary.
The workaround Leo Dabus mentioned in comments should work well: use the debug description (String) of your CGPoint objects as keys to the dictionary of counts:
var counts = [String: Int]()
myArray.forEach { counts[$0.debugDescription] = (counts[$0.debugDescription] ?? 0) + 1 }
if let (value, count) = counts.max(by: {$0.value < $1.value}) {
print("\(value) occurs \(count) times")
}

How to create a streak from an array of tuples in Swift

I have an array of tuples that looks like:
[("07-20-2016", 2), ("07-22-2016", 5.0), ("07-21-2016", 3.3333333333333335)]
It consists of a String that is a date and a Double that is a mood (Moods less than or equal to 3 means that day was a good day).
I am trying to figure out how to create a streak of good days.
So what I think I need to do first is sort the array of tuples to put them in order by date.
Then I need to count the tuples that are below the mood value 3 if they are in a row. In this case the result would be 1 because there is only 1 day that is below 3.
If I had an array of tuples like:
[("07-22-2016", 5.0), ("07-21-2016", 3), ("07-20-2016", 2), ("07-19-2016", 1), ("07-18-2016", 1), ("07-16-2016", 2), ("07-15-2016", 3)]
It would have a result of 4 because the highest number of days in a row that are below 3 is 4. The reason it wouldn't be 6 is because a date of "07-17-2016" doesn't exist.
Please help me solve this problem. I know there are many users on here that are smarter then me. Can you solve this?
First, as matt suggested, make a nicer datatype. The language makes this super convenient with structs. We need each entry to have a date and a mood score. Easy-peasy.
import Foundation
struct JournalEntry {
let date: NSDate
let score: Double
var isGoodDay: Bool { return self.score <= 3.0 }
}
(The computed isGoodDay property will make life even easier later on.)
Now we need to turn the raw list of tuples into a list of these structs. That's straightforward. Create a date parser, and use map to convert between the tuple type and the struct:
typealias RawEntry = (String, Double)
func parseRawEntries<S: SequenceType where S.Generator.Element == RawEntry>
(rawEntries: S)
-> [JournalEntry]
{
let parser = NSDateFormatter()
parser.dateFormat = "MM-dd-yyyy"
// Use guard and flatMap because dateFromString returns an Optional;
// you might prefer to throw an error to indicate that the data are
// incorrectly formatted
return rawEntries.flatMap { rawEntry in
guard let date = parser.dateFromString(rawEntry.0) else {
return nil
}
return JournalEntry(date: date, score: rawEntry.1)
}
}
After converting we'll need to go through the list testing two conditions: first, that days are "good" and then that pairs of good days are consecutive. We have that helper method in the data structure itself to test the former. Let's create another helper function for the consecutive part:
/* N.B. for brevity this assumes first and second are already sorted! */
func entriesConsecutive(first: JournalEntry, _ second: JournalEntry) -> Bool {
let calendar = NSCalendar.currentCalendar()
let dayDiff = calendar.components(.Day,
fromDate: first.date,
toDate: second.date,
options: NSCalendarOptions(rawValue: 0))
return dayDiff.day == 1
}
At each enumeration step, we'll have the current entry, but we also need either the previous or the next, so that we can test for consecutivity. We also obviously want to keep track of the streaks' counts as we find them. Let's make another datatype for that. (The reason for lastEntry rather than nextEntry will be clear in a moment.)
struct Streak {
let lastEntry: JournalEntry
let count: UInt
}
This is the fun part: iterating over the array, constructing Streaks. This can be done with a for loop. But consuming sequences by calculation and agglomeration on each new item is a known pattern. It's called "folding"; Swift uses the alternate term reduce().
Reducing a sequence into groups, or while keeping track of something, is also a well-known pattern: the combined value is itself a collection. At each step, you inspect the new item along with the most recent value in the reduction. The new value for the reduction -- what you return from the combining function -- is still the collection, either with the previous value updated or a new value appended.
The Streak datatype from above will make the elements in the reduction: that's why it uses the last entry. Each step, we will save the JournalEntry and inspect it on the next.
For streaks, we only care about "good" days. Let's make life simple and filter the list right off the bat: let goodDays = journal.filter { $0.isGoodDay } We'll need the first entry so that there's something to compare to on the first step. This is also a chance to bail out if for some reason there are no good days.
guard let firstEntry = goodDays.first else {
return []
}
That first entry goes into a Streak object, which goes into the collection that will become the reduction: let initial = [Streak(lastEntry: firstEntry, count: 1)], and we're ready to go. The full function looks like this (I'll let the comments explain the procedure inside the reduce):
func findStreaks(in journal: [JournalEntry]) -> [Streak] {
let goodDays = journal.filter { $0.isGoodDay }
guard let firstEntry = goodDays.first else {
return []
}
let initial = [Streak(lastEntry: firstEntry, count: 1)]
return
// The first entry is already used; skip it.
goodDays.dropFirst()
.reduce(initial) {
(reduction: [Streak], entry: JournalEntry) in
// Get most recent Streak for inspection
let streak = reduction.last! // ! We know this exists
let consecutive = entriesConsecutive(streak.lastEntry,
entry)
// If this is the same streak, increment the count by
// replacing the previous value.
// Otherwise add a new Streak with count 1.
let newCount = consecutive ? streak.count + 1 : 1
let streaks = consecutive ?
Array(reduction.dropLast()) :
reduction
return streaks +
[Streak(lastEntry: entry, count: newCount)]
}
}
Now, back at the top level, things are very simple. Raw data:
let rawEntries = [("07-22-2016", 5.0), ("07-21-2016", 3), ("07-20-2016", 2), ("07-19-2016", 1), ("07-18-2016", 1), ("07-16-2016", 2), ("07-15-2016", 3)]
Convert that into the relevant datatype, immediately sorting by date while we're at it.
let journal = parseRawEntries(rawEntries).sort { $0.date < $1.date }
Calculate streaks:
let streaks = findStreaks(in: journal)
And the result you want is then let bestStreakLen = streaks.map({ $0.count }).maxElement()
(Small note: comparing NSDate using < like that requires a new function:
func <(lhs: NSDate, rhs: NSDate) -> Bool {
let earlier = lhs.earlierDate(rhs)
return earlier == lhs
}
)
Here is the answer I found.
func datesToStreaks(input: [NSDate]) -> [[NSDate]] {
var ranges:[[NSDate]] = [[]]
input.forEach { currentDay in
var lastRange = ranges.last ?? []
if let lastDate = lastRange.last {
if lastDate.dateByAddingTimeInterval(86400).isEqualToDate(currentDay) {
lastRange.append(currentDay)
ranges.removeLast()
ranges.append(lastRange)
}
else {
ranges.append([currentDay])
}
}
else {
lastRange.append(currentDay)
ranges.removeLast()
ranges.append(lastRange)
}
}
return ranges
}
let input = [("07-22-2016", 5.0), ("07-21-2016", 3), ("07-20-2016", 2), ("07-19-2016", 1), ("07-18-2016", 1), ("07-16-2016", 2), ("07-15-2016", 3)]
let sortedDays:[NSDate] = input
.filter { (date, mood) in
mood <= 3.0}
.map { (date, mood) in
return date}
.flatMap{ date in
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MM/dd/yyyy"
return dateFormatter.dateFromString(date)
}
.sort{$0.0.earlierDate($0.1).isEqualToDate($0.0)}
let maxStreak = datesToStreaks(sortedDays)
.map{$0.count}
.reduce(0, combine: {return max($0,$1)})
print(maxStreak) // 4
You can first build an array of streaks of good and bad moods, and then filter or reduce it to analyse the streeks:
let dateData = [("07-22-2016", 5.0), ("07-21-2016", 3), ("07-20-2016", 2), ("07-19-2016", 1), ("07-18-2016", 1), ("07-16-2016", 2), ("07-15-2016", 3)]
var streaks:[(String,Bool,Int)] = []
for (date,value) in dateData
{
if let goodMood = streaks.last?.1
where goodMood == (value < 3)
{ streaks[streaks.count-1].2 += 1 }
else
{ streaks += [(date, value<3, 1)] }
}
// at this point streaks contains an array of tupples with counts of consecutive days of a given mood
//
// [("07-22-2016", false, 2), ("07-20-2016", true, 4), ("07-15-2016", false, 1)]
//
// you can then process that as you need
let goodStreaks = streaks.filter{$0.1}.map{($0.0,$0.2)} // [("07-20-2016", 4)]
let badStreaks = streaks.filter{!$0.1}.map{($0.0,$0.2)} // [("07-22-2016", 2), ("07-15-2016", 1)]
let longestGoodStreak = streaks.filter{$0.1}.reduce(0, combine: { max($0,$1.2) }) // 4

Resources