I have an array of sections:
struct Section {
let title: String
var items: [Item]
}
Which contains a list of items:
struct Item {
let title: String
}
I'd like to populate this with an unknown amount of items, and sort the data as efficiently as possible. I fetch the data from a remote source, and parse the data as it comes in. I can't know if the data being sent is sorted or not, so blindly appending to a Section list may not work here.
I'd like the data to be shown to the user as soon as it comes in (in batches), so I can't wait until the end of the fetch operation to perform sorting. For examples sake, lets assume I'm getting the section by:
let item = Item(title: "Foo")
let sectionTitle = item.characters.first!
if let section = sections.filter({ $0.title == String(sectionTitle) }) {
// find
} else {
// create
}
My initial idea, once I've figured out the correct Section, loop through the Section items until sectionItem.title > item.title, then that becomes the insertion point:
var insertionPoint = 0
for (i, val) in array.enumerated() {
insertionPoint = i
if val.title > item.title { break }
}
section.items.insert(item, at: insertionPoint)
This does seem inefficient once the Section has many Item objects. Is there a more appropriate method?
My only other thought would be to keep track of Section items that were touched in this batch, then running section.items.sort() at the end of each batch operation.
For each batch of items:
Add each item to a temp array for a section.
Sort each temp array.
Merge the sorted temp array with the already sorted array for the section. Merging two sorted arrays is a very efficient operation.
Related
This is how my Realm objects look:
class Restaurant: Object {
#objc dynamic var name: String? = nil
let meals = List<Meal>()
}
class Meal: Object {
#objc dynamic var mealName: String? = nil
let tag = RealmOptional<Int>()
}
I'm trying to fetch all meals that have some tags (I know I can filter all Realm objects of type Meal for specific tags), but the goal is to fetch all Restaurant objects and filter it's Meal child objects based on tag values.
I tried filtering like this:
restaurants = realm.objects(Restaurant.self).filter("meals.#tags IN %#", selectedTags)
but this won't work. Is there a way to filter results based on values in child object list?
To clarify the question, this is an example how filtering should work
for selectedTags = [1, 2, 3]
This is the whole Restaurant model that is saved in Realm.
[Restaurant {
name = "Foo"
meals = [
Meal {
mealName = "Meal 1"
tag = 1
},
Meal {
mealName = "Meal 2"
tag = 2
},
Meal {
mealName = "Meal 7"
tag = 7
}
]
}]
Filtering should return this:
[Restaurant {
name = "Foo"
meals = [
Meal {
mealName = "Meal 1"
tag = 1
},
Meal {
mealName = "Meal 2"
tag = 2
}
]
}]
Here's one possible solution - add a reverse refererence to the restaurant for each meal object
class Restaurant: Object {
#objc dynamic var name: String? = nil
let meals = List<Meal>()
}
class Meal: Object {
#objc dynamic var mealName: String? = nil
let tag = RealmOptional<Int>()
#objc dynamic var restaurant: Restaurant? //Add this
}
then query the meals for that restaurant with the tags you want.
let results = realm.objects(Meal.self).filter("restaurant.name == %# AND tag IN %#", "Foo", [1,2])
LinkingObjects could also be leveraged but it depends on what kind of queries will be needed and what the relationships are between Restaurants and Meals - I am assuming 1-Many in this case.
if you want ALL restaurants, then LinkingObjects is the way to go.
Edit:
Thought of another solution. This will work without adding a reference or an inverse relationship and will return an array of restaurants that have meals with the selected tags.
let selectedTags = [1,2]
let results = realm.objects(Restaurant.self).filter( {
for meal in $0.meals {
if let thisTag = meal.tag.value { //optional so safely unwrap it
if selectedTags.contains(thisTag) {
return true //the tag for this meal was in the list, return true
}
} else {
return false //tag was nil so return false
}
}
return false
})
In short, you cannot do what you are asking. Not within a Realm query (and therefore benefit from update notifications if that is important) at least. No doubt you can make some kind of structure containing what you want though via non-Realm filtering.
To better answer, let's first consider what you're trying to produce as a query result. As you say, your attempt above won't work. But you're trying to filter Restaurants by having some Meals matching some criteria; this is probably achievable, but your resulting query on Restaurant type would then produce a list of Restaurants. Each restaurant would still have a natural property of all its Meals, and would require the same filter applied again to the meals.
It makes sense though to add a function (if you need the search criteria to be dynamic, use a computed property if the filter is always the same tags) to the Restaurant class that produces a view of its Meals matching your criteria.
e.g.
extension Restaurant
{
var importantMeals : Results<Meal>
{
return meals.filter(...)
}
}
So I think there are two options.
Iterate through all Restaurant objects, and add it to a data structure of your own (Set or array) if its importantMeals property is not empty. Then use the same property to produce the meal list when needed. Or you could use a non-Realm filter to produce that query for you. E.g. realm.objects(Restaurant.self).compactMap {$0}.filter { !$0.importantMeals.isEmpty }
Alternatively, filter all Meals according to your criteria (realm.objects(Meal.self).filter(...)). You could then add a LinkingObjects property to your Meal class to make the Set of Restaurants with relevant Meals.
The correct approach will depend on how you want to use the results, but I'd suggest approach 1 will see you right. Note that you might want to sort the results produced by queries before using if order is of any importance to you (e.g. for displaying in UITableView) as there is no guarantee that the order of objects will be the same for each query performed.
I have two array, which has the same model.
I'm trying to find the object where it has the same id. I have tried this method which I can find it but how I can make it without for loop?
for item in userList {
let userSelection = user.list.first(where: {$0.id == item.id})
item.approved = userSelection.approved
print(userSelection)
}
Try something like this
let userSelection = user.list.filter({userList.map({$0.id}).contains({$0.id})})
Explanation:
//get all the ids from one list
let ids = userList.map({$0.id})
//filter the second list by including all the users whose id is in the first list
let userSelection = user.list.filter({ids.contains({$0.id})})
If you don't care about performance, you can use set.intersection:
let set1:Set<UserType> = Set(userList)
let set2:Set<UserType> = Set(user.list)
let commonItems = set1.intersection(set2)// Intersection of two sets
Even if your model is not Hashable, you can use sets to perform the validation:
if Set(userList.map{$0.id}).subtracting(user.list.map{$0.id}).count == 0
{
// all entries in userList exist in user.list
}
else
{
// at least one entry of userList is not in user.list
}
How can I check the last row in this loop below. Im using SQlite.swift in order to cycle through all the rows in the table. When I hit the last row, I want to do something.
for data in try dbConnection.prepare(tableName) {
if (thisIsTheLastRow) {
//do something
}
}
Here is the library reference but I couldent find anything about getting specific indexes through the loop
You could either do a for index loop:
let count = try! db.scalar("SELECT count(*) FROM Table") as! Int64
for (index, data) in stmt.enumerated() {
if index == Int(count) {
// Last row
}
}
Or you could in that library make an sqlite statement and get the last row immediately:
func lastRowForTable() -> Data {
let data = try! db.scalar("SELECT * FROM table ORDER BY column DESC LIMIT 1;")
return data
}
SQLite computes result rows on demand. So there is no way to find out whether the current row is the last one except by trying to stepping to the next one.
You have to load all rows before looping over them, or do the "do something" after the loop.
I've setup a table to pull data from a database. The user can manually delete items from the table (and thus the database) via checkbox (table.editing = true, iirc) and a delete button. This can be done one at a time, or all at a time.
Unfortunately, whenever I check everything for deletion, the app crashes with the following error:
fatal error: Array index out of range
This does not happen if I select and delete only one or any number of the table rows, as long as I don't select everything.
Here's my code for the delete button:
func deleteButtonPressed(sender: AnyObject) {
if (self.pureSteamFormView.tableCalibration.editing == true) {
if (self.pureSteamFormView.tableCalibration.indexPathsForSelectedRows!.count >= 1) {
for indexPath in self.pureSteamFormView.tableCalibration.indexPathsForSelectedRows!.sort({ $0.row < $1.row}) {
let calibTable : FormSteamPurityCalibration = self.steamPurityCalibrationTableList[indexPath.row] /* <--- ERROR HERE */
DatabaseManager.getInstance().deleteData("FormSteamPurityCalibration", "ID = \(calibTable.ID)")
self.steamPurityCalibrationTableList.removeAtIndex(indexPath.row)
}
self.pureSteamFormView?.tableCalibration.reloadData()
}
}
}
Near as I can figure, it is attempting to remove the row at an index, an index that may no longer exist (?) due to the previous row also being deleted, but I'm not sure about that.
I tried putting the following code:
self.steamPurityCalibrationTableList.removeAtIndex(indexPath.row)
In its own for-loop block, and the error promptly move there.
I also tried removing the removeAtIndex part completely, relying on the reloadData() to perhaps update the table automatically, but it doesn't work - the data is deleted from the database, but remains on the table (although moving away from that view and going back there updates the table).
Any suggestions please? Thanks.
Your problem here is that you are deleting the lowest indexes before the bigger ones. Let me explain with an example:
Image you have 4 elements in your array:
let array = ["Element1", "Element2", "Element3", "Element4"]
You are trying to remove the elements at index 1 et 3:
for index in [1, 3] {
array.removeAtIndex(index)
}
Your program will first remove element at index 1, leaving you with the following array:
["Element1", "Element3", "Element4"]
On the second pass of the loop it will try to remove the element at index 3. Which does not exist anymore because it has moved to index 2.
One solution to this is to start removing element with the greater index before, so in your code you could change
for indexPath in self.pureSteamFormView.tableCalibration.indexPathsForSelectedRows!.sort({ $0.row < $1.row}) {
to
for indexPath in self.pureSteamFormView.tableCalibration.indexPathsForSelectedRows!.sort({ $0.row > $1.row}) {
A better solution would be to filter your data array to include only the elements you whish to keep, so instead of:
for indexPath in self.pureSteamFormView.tableCalibration.indexPathsForSelectedRows!.sort({ $0.row < $1.row}) {
let calibTable : FormSteamPurityCalibration = self.steamPurityCalibrationTableList[indexPath.row]
DatabaseManager.getInstance().deleteData("FormSteamPurityCalibration", "ID = \(calibTable.ID)")
self.steamPurityCalibrationTableList.removeAtIndex(indexPath.row)
}
you could do:
self.steamPurityCalibrationTableList.filter {
if let index = self.steamPurityCalibrationTableList.indexOf ({ $0 })
{
for indexPath in self.pureSteamFormView.tableCalibration.indexPathsForSelectedRows! {
if indexPath.row == index { return false }
}
return true
}
}
I have got some TCP connection which returns some data for me. Also I have got a structure that represents this data.
type Item struct {
A int32
B int32
}
Item supports Unpacker interface
func (item *Item) Unpack(data []int32) {
item.A = data[0]
item.B = data[1]
return
}
type Unpacker interface {
Unpack([]int32)
}
So I receive some data from Network which represents a bunch of items. Now I want to pass my structure to function and I want to get back a slice of structures filled with data:
func find(packet [][]int32, responseItem Unpacker) (items []Unpacker) {
items = make([]Unpacker, len(packet))
for i, data := range(packet) {
responseItem.Unpack(data)
items[i] = responseItem
}
return
}
Of course in this case I have got a slice with a number of identical items (pointers to same item). But I want to get different items and
items[i] = *responseItem
doesn't work in my case.
Here is link to playground: http://play.golang.org/p/RP4ryxoG2I
I believe I didn't understand how Go works (it is my first time with Go). And also good to be noticed: I don't want to use reflection here if it is possible.
You've got unpacker backwards. You want to create new Item in your loop, Unpack into the item and then assign into your slice. You can do this by making find accept a function returning an Unpacker.
See the following: http://play.golang.org/p/rFoa1eoh4A
Your find function:
type UnpackerMaker func() (Unpacker)
func find(packet [][]int32, makeUnpacker UnpackerMaker) (items []Unpacker) {
items = make([]Unpacker, len(packet))
for i, data := range(packet) {
unpacker := makeUnpacker()
unpacker.Unpack(data)
items[i] = unpacker
}
return
}