Sorting fetched results - ios

I'm fetching some objects from core data. One of the properties is a name identifier.
The names can be either text or a number, so the property is a String type.
What I'd like to be able to do is sort it so that the text objects are first, then the numbers in numerical order.
Currently its putting the numbers first, and the numbers are in the wrong order, ie. 300, 301, 3011, 304, 3041, Blanc, White
let sortDescriptor = NSSortDescriptor(key: "number", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]

Naive version:
let fetchedResults = ["300", "301", "3011", "304", "3041", "Blanc", "White"]
var words = [String]()
var numbers = [String]()
for value in fetchedResults {
if let number = Int(value) {
numbers.append(value)
} else {
words.append(value)
}
}
let result = words + numbers
print(result)
Prints:
["Blanc", "White", "300", "301", "3011", "304", "3041"]

Try this maybe:
var a: [Int] = []
var b: [String] = []
if let value = self[key] as? String {
if let valueAsInt = Int(value) {
a.append(valueAsInt)
} else {
b.append(value)
}
}

Related

Sort by bool values of each object and show in collection view

I have a UITableView, inside that I am using UICollectionView. So I have multiple collection view - where user can scroll horizontally. Now in my each collection view I have some data to show. But each cell object contains the bool value. If bool is false, then I need to show that object title as first in my collection view followed by bool is true.
My JSON as follow:
first obj:({
name =ruler1;
com = " 1";
Dele = "";
isTrue = false
},
{
name =rule2r;
com = " 1";
Dele = "";
isTrue = true
},
{
name =rule3r;
com = " 1";
Dele = "";
isTrue = false
})
sec obj:({
name =ruler name1;
com = " 1";
Dele = "";
isTrue = true
},
{
name =ruler name 2;
com = " 1";
Dele = "";
isTrue = false
},
{
name =ruler name 3;
com = " 1";
Dele = "";
isTrue = false
})
In cellForRowAtIndexPath, I'm just showing the label as normal like :
collectionCell.cellTitleLabel.text = Info["name"] as? String ?? ""
But, How can I sort and show which ever object is false? That needs to be show at first.
For ex:
ruler1 (false), rule3r(false) , rule2r(true) like same.
Are there any helpful solution? I have tried with sort, but didn't help me much.
Update:
I have following code to show label in collectionView at cellForRowAtIndexPath method:
if let gamesList = skillInfo?["data_list"] as? [[String: Any]] {
let gameInfo = gamesList[indexPath.item]
collectionCell.cellTitleLabel.text = gameInfo["name"] as? String ?? ""
}
skillInfo is my JSON list that I have added in my bundle.
You can sort by view step:
Set the JSON array into object model
Then use this function to sort the array
ex. your data array is : arrData
then you use:
arrData.sort { (first, second) -> Bool in
return !first. isTrue
}
this sort the false value object at first place
Use the following code to get shored array of dictionary by a boolean value.
let sortedArray = (myArray as NSArray).sortedArray(using: [NSSortDescriptor(key: "isTrue", ascending: false)]) as! [[String:AnyObject]]
If you want to get true value first then ascending = false and if you want to get false value first then pass ascending = true
See the output sorted an array of dictionary in this screenshot.
This to sort dictionary array.
Were, ascending = true will return data with isTrue = 0 first,
when ascending = false will return data with isTrue = 1 first.
You can achieve your needs with the following a line,Try this code and let me know if you there still any issue.
func sortJSONArray() {
let bundle = Bundle.init(for: StackOverflowViewController.self)
guard let jsonPath = bundle.path(forResource: "StackOVerflow", ofType: "json"),
let jsonData = try? Data(contentsOf: URL(fileURLWithPath: jsonPath)) else {
return
}
if let jsonObjects = (try? JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization
.ReadingOptions.allowFragments)) as? NSArray {
let arrFiltered = jsonObjects.filter{($0 as! [String: Any])["isTrue"] as? Int == 0}
let arrSorted = jsonObjects.sortedArray(using: [NSSortDescriptor(key: "isTrue", ascending: true)]) as! [[String:AnyObject]]
print(arrFiltered)
print(arrSorted)
}
}
This to filter a dictionary array, (If you want to show data which isTrue = 0 only)
let arrFiltered = jsonObjects.filter{($0 as! [String: Any])["status"] as? Int == 0}

How to generate a 2D-array from a 1D-array using map and filter?

I have the desired output using a loop today but how can I exchange this loop with a one line expression using map and filter?
I start with an array of countries. From this one I generate a second array with the initial letters using map. I would now like to build a 2D-array that contains all the countries with the same initial letter in separate arrays using map and filter so that I can get rid of the loop I use today.
let countries = ["Albania", "Algeria", "Angola", "Bahamas", "Bahrain", "Canada"]
var initials = Set(countries.map { $0.prefix(1) }).sorted()
func countriesByInitial() -> [[String]] {
var result = [[String]]()
for initial in initials {
result.append(countries.filter { $0.prefix(1) == initial })
}
return result
}
You can achieve it by group > sort > map combination as:
let countries = ["Algeria", "Albania", "Belarus", "Bahamas", "Canada"]
let groupedDict = Dictionary(grouping: countries, by: { $0.prefix(1) })
let sortedDict = groupedDict.sorted(by: { $0.key < $1.key })
let arr2D = sortedDict.map({ $0.value })
You can write it in a single line:
let arr = (Dictionary(grouping: countries, by: { $0.prefix(1) })).sorted(by: { $0.key < $1.key }).map({ $0.value })
You can use reduce on array as well:
// $0 will be of type [[String]]
// $1 will be of type String
let arr2D: [[String]] = countries.reduce(into: []) {
let checkChar = $1.first
if let idx = $0.index(where: {$0.first?.first == checkChar }) {
$0[idx].append($1)
} else {
$0.append([$1])
}
}
You can use Swift 4 method reduce(into:) if your array it is already sorted, otherwise you just need to sort it before grouping it:
let countries = ["Albania", "Algeria", "Angola", "Bahamas", "Bahrain", "Canada"]
let grouped: [[String]] = countries.reduce(into: []) {
if $0.last?.last?.prefix(1) == $1.prefix(1) {
$0[$0.index(before: $0.endIndex)].append($1)
} else {
$0.append([$1])
}
}
print(grouped) // [["Albania", "Algeria", "Angola"], ["Bahamas", "Bahrain"], ["Canada"]]\n"
You could map the initial set by filtering countries strings' first letter as:
let countries = ["Albania", "Algeria", "Angola", "Bahamas", "Bahrain", "Canada"]
var initials = Set(countries.map { $0.prefix(1) }).sorted()
let array = initials.map { letter -> [String] in
return countries.filter { $0.prefix(1) == letter }
}
therefore, array is an array of arrays of strings ([[String]]), as:
[["Albania", "Algeria", "Angola"], ["Bahamas", "Bahrain"], ["Canada"]]

Swift 1D Dictionary to 2D

I've got a little problem in understanding two-dimensional dictionaries. My function has to return dictionary for UITableView with sections. 1 template type can have multiple template strings. So when in fetchedData there are 2 or more texts with similar types, they have to be in array [String] with 1 key - String.
The code below is absolutely correct from the complier point of view. As for me smth is wrong, but nice auto-completions make me think that everything is OK.
Obviously it returns an empty dictionary [:]
func fetchTemplates() -> Dictionary<String, [String]> {
var templates: Dictionary<String, [String]> = [:]
let fetchRequest: NSFetchRequest<Template> = Template.fetchRequest()
fetchRequest.sortDescriptors = [SortDescriptor.init(key: "templateType", ascending: true)]
let fetchedData = try! context.fetch(fetchRequest)
if (!fetchedData.isEmpty) {
for templateItem in fetchedData {
templates[templateItem.templateType!]?.append(templateItem.templateText!)
}
return templates
}
else {
return templates
}
}
P.S. fetchedData returns:
<Template: 0x003281h4> (entity: Template; id: 0x003281h4 <x-coredata:///Template/> ; data: {
templateText = "Example";
templateType = "First";
})
The issue lies on this line:
templates[templateItem.templateType!]?.append(templateItem.templateText!)
templates was initialized with this line: var templates: Dictionary<String, [String]> = [:]. At this point, templates is an empty dictionary.
Let's break that line down into the steps that happen, in chronological order:
templateItem.templateType is accessed, and force unwrapped. A crash will occur if it's nil.
templateItem.templateType! is used as a key into the templates dictionary. This will always return nil. The dictionary is empty, thus is has no values for any keys, including this one.
?.append() is called, on the condition that it's not being called on nil. If it's called on nil, nothing will happen.
3 is the cause of your issue. You need to initialize a new array if one doesn't exist for the key yet:
func fetchTemplates() -> Dictionary<String, [String]> {
var templates: Dictionary<String, [String]> = [:]
let fetchRequest: NSFetchRequest<Template> = Template.fetchRequest()
fetchRequest.sortDescriptors = [SortDescriptor.init(key: "templateType", ascending: true)]
let fetchedData = try! context.fetch(fetchRequest)
if (!fetchedData.isEmpty) { //see note 2
for templateItem in fetchedData {
let type = templateItem.templateType!
var array = templates[type] ?? [] //see note 1
array!.append(templateItem.templateText!)
templates[type] = array
}
return templates
}
else {
return templates
}
}
This function can be simplified:
func fetchTemplates() -> [String : [String]] {
let fetchRequest = Template.fetchRequest()
fetchRequest.sortDescriptors = [SortDescriptor(key: "templateType", ascending: true)]
let fetchedData = try! context.fetch(fetchRequest)
var templates = [String, [String]]()
for templateItem in fetchedData {
let type = templateItem.templateType!
templates[type] = (templates[text] ?? []) + [templateItem.templateText!]
}
return templates
}
and reduce can be used instead:
func fetchTemplates() -> [String : [String]] { //see note 3
let fetchRequest = Template.fetchRequest() //see note 4
fetchRequest.sortDescriptors = [SortDescriptor(key: "templateType", ascending: true)] //see note 5
let fetchedData = try! context.fetch(fetchRequest)
return fetchedData.reduce([String, [String]]()){templates, templateItem in
(templates[templateItem.tempalteText!] ?? []) + [templateItem.templateText!]
} //see note 6
}
Notes
if template[text] is not nil, it's assigned to array. Otherwise, a new array ([]) is assigned to `array.
this check is unnecessary
Dictionary<String, [String]> can be written as just [String : [String]]
No need for an explicit type signiture
X.init() can be written as just X()
The emptiness check is unnecessary, and the whole for loop can be changed into a reduce call.
The issue is:
templates[templateItem.templateType!] is always nil because the dictionary is empty.
Therefore nothing can be appended.

How to use filteredArrayUsingPredicate with NSDictionary?

Now I'm using
let array = (displayNames as NSArray).filteredArrayUsingPredicate(searchPredicate)
where displayNames is
var displayNames[String]()
But I want to use it with:
var displayNames[String: UIImage]()
How can I use .filteredArrayUsingPredicate with displayNames string part in NSDictionary?
Get the keys from the dictionary using the allKeys property and perform the filter on that. Try that.
RE-EDIT: Try something like this
let theOriginalDictionary = [String : UIImage]()
let searchPredicate = NSPredicate(format: "", argumentArray: nil)
let otherDictionary = NSDictionary(dictionary: theOriginalDictionary)
let arrayOfKeys = NSArray(array: otherDictionary.allKeys)
let filteredArray = arrayOfKeys.filteredArrayUsingPredicate(searchPredicate)
If you're using Swift, why don't you use built-in functions like filter?
var displayNames = [String: UIImage]()
let result = displayNames.filter {key,image in
return true // here add your predicate to filter, true means returning all
}
It sounds like you're trying to filter the dictionary, while keeping the dictionary type after the filter. There's a few ways to do this, but maybe the easiest is to extend dictionary:
extension Dictionary {
func filter(#noescape includeElement: (Key, Value) throws -> Bool) rethrows -> [Key:Value] {
var result: [Key:Value] = [:]
for (k,v) in self where try includeElement(k,v) {
result[k] = v
}
return result
}
}
Then, if you wanted to filter a dictionary based on the keys, you can do something like this:
let dict = ["a": 1, "b": 2, "c": 3]
let filtered = dict.filter { (k,_) in k != "a" }
// ["b": 2, "c": 3]

iOS Swift: Sort array into multidimensional array

I have an array of CKRecords. Each record has startTime and a Name, among other values. What I would like to do is sort the records first by unique startTime and then within each startTime sort by unique Name.
The end result would be an array that looks like this (I think): records = [Date: [Name: [CKRecord]]]
Here is what I have right now:
func buildIndex(records: [CKRecord]) -> [[CKRecord]] {
var dates = [NSDate]()
var result = [[CKRecord]]()
for record in records {
var date = record.objectForKey("startTime") as! NSDate
if !contains(dates, date) {
dates.append(date)
}
}
for date in dates {
var recordForDate = [CKRecord]()
for (index, exercise) in enumerate(exercises) {
let created = exercise.objectForKey("startTime") as! NSDate
if date == created {
let record = exercises[index] as CKRecord
recordForDate.append(record)
}
}
result.append(recordForDate)
}
return result
}
let records = self.buildIndex(data)
Why not use sorted? Like this.
// A simplified version of your `CKRecord` just for demonstration
struct Record {
let time: NSDate
let name: String
}
let records = [
Record(time: NSDate(timeIntervalSince1970: 1), name: "a"),
Record(time: NSDate(timeIntervalSince1970: 2), name: "b"),
Record(time: NSDate(timeIntervalSince1970: 1), name: "c"),
Record(time: NSDate(timeIntervalSince1970: 3), name: "d"),
Record(time: NSDate(timeIntervalSince1970: 3), name: "e"),
Record(time: NSDate(timeIntervalSince1970: 2), name: "f"),
]
func buildIndex(records: [Record]) -> [[Record]] {
var g = [NSDate: [Record]]()
for e in records {
if (g[e.time] == nil) {
g[e.time] = []
}
g[e.time]!.append(e) // grouping by `time`
}
return sorted(g.keys) { (a: NSDate, b: NSDate) in
a.compare(b) == .OrderedAscending // sorting the outer array by 'time'
}
// sorting the inner arrays by `name`
.map { sorted(g[$0]!) { $0.name < $1.name } }
}
println(buildIndex(records))
First of all, you're not really trying to sort an array here, you're trying to order a dictionary, which isn't built to be iterated over sequentially. In fact even if you do sort the array first and then build the dictionary like this:
var sortedRecords = [NSDate: [String: CKRecord]]()
records.sort { return $0.date.timeIntervalSinceDate($1.date) < 0 }
for record in records {
if sortedRecords[record.date] != nil {
sortedRecords[record.date] = [String: CKRecord]()
}
sortedRecords[record.date]![record.name] = record
}
The order isn't guaranteed when you iterate over it in the future. That said, a dictionary is essentially a look up table, and elements can be accessed in O(log n) time. What you'll really want to do is either drop the dictionary is favor of an array of [CKRecord] and then sort like this:
records.sort { $0.date.timeIntervalSinceDate($1.date) == 0 ? $0.name < $1.name : $0.date.timeIntervalSinceDate($1.date) < 0 }
Or, depending on what your end goal is, iterate across a range of dates, plucking the entries from the dictionary as you go.
You could execute the CloudKit query and make sure that you get the array returned in the correct sort order like this:
query.sortDescriptors = [NSSortDescriptor(key: "startTime", ascending: true), NSSortDescriptor(key: "Name", ascending: true)]
And then if you go to the detail view, you could use the filter for getting the records for that day like this:
var details = records.filter { (%0.objectForKey("startTime") As! NSDate) == selectedDate }

Resources