I need to query Realm objects using a list of ids, id being the object's primary key. I have tried the following:
// Query a list of Dogs by their _ids
let doggoIds = ["1", "2", "3", "1", "2"]
realm.objects(Dog.self).filter("_id IN %#", doggoIds)
// Result: [Dog1, Dog2, Dog3]
The list of ids contains duplicates, but as you can see the result is a list of unique Dog objects.
I'm wondering if anyone can think of a different way to write this query so that we get a nice Results<Dog> collection from that list of ids that includes duplicates. Thanks!
Realm results contain unique objects so the easiest solution is to query for each dog and add the result to an array. CompactMap does this well as it filters out nil.
let listOfDogsToFind = ["Fido", "Dino", "Sport", "Fido", "Cupid"]
let dogArray = listOfDogsToFind.compactMap { dogName -> DogClass? in
if let result = realm.objects(DogClass.self).filter("dog_name == %#", dogName).first {
return result
}
return nil
}
for dog in dogArray {
print("dog name: \(dog.dog_name)")
}
assume Cupid doesn't exist, here's the output
dog name: Fido
dog name: Dino
dog name: Sport
dog name: Fido
Realm.objects().filter returns a list of "live", managed objects. It will not return duplicates because there is only one object matching the primary key. In order to accomplish what you want, you'll need to create an 'unmanaged' duplicate of each object and make your own array of those unmanaged objects map each returned instance into your own array.
A la:
let doggoIds = [1, 2, 3, 1, 2]
for managedMuttId in doggoIds {
if let managedMutt = realm.object(ofType: Dog.self, forPrimaryKey: managedMuttId) {
// unmanaged.append(Dog(value: managedMutt))
duplicateList.append(managedMutt)
}
}
Related
So, I have the following objects:
struct Person {
let name: String
let birthday: String
}
let Bob = Person(name: "Bob", birthday: "11.12.1987")
let Tim = Person(name: "Tim", birthday: "11.12.1987")
let John = Person(name: "John", birthday: "01.02.1957")
let Jerry = Person(name: "Jerry", birthday: "17.12.2001")
And the following array:
let people = [Bob, Tim, John, Jerry]
My goal is to generate a dictionary from this array, with "birthday" for the key and the "Person" object itself as the value: [String: [Person]]. If there are equal keys the person should be added and form an array as a key. So the result will be the following:
dictionary = ["11.12.1987": [Bob, Tim], "01.02.1957": John, "17.12.2001": Jerry]
What is the best way to achieve this?
Cheers!
You could use Dictionary(grouping:by:) method together with mapValues,
let result = Dictionary(grouping: people, by: \.birthday)
print(result)
Output is,
["01.02.1957": ["John"], "17.12.2001": ["Jerry"], "11.12.1987":
["Bob", "Tim"]]
This would yield you a Dictionary of type Dictionary<String, [Person]>.
From, your question it seems like you want a Person if there is only one Person and array when there are multiple people. You would loose type in that situation and your type would be something like this, Dictionary<String, Any>. You could make small bit of modification to the above code to do that.
let result = Dictionary(grouping: people, by: \.birthday)
.mapValues { people -> Any in
if people.count == 1 {
return people.first!
}
return people
}
print(result)
The output would be similar to what you want,
["01.02.1957": "John", "17.12.2001": "Jerry", "11.12.1987": ["Bob",
"Tim"]]
What about a loop through the dictionary checking if date exists. Otherwise create key with date and assign value.
If key already exists append person to the actual key value:
var dictionary = [String: [Person]]()
// Loop through all person objects in array
for person in people {
// Check if date already exists
if dictionary[person.birthday] != nil {
// If already exists, include new person in array
var array = dictionary[person.birthday]
array!.append(person)
}
// If date does not exists, add new key with date and person
else {
dictionary[person.birthday] = [person]
}
}
I'm trying to query multiple objects from Realm using a List of Primary Key Strings. I know I can do this using a for..in loop but I'd rather use a filter if possible.
primaryKeyArray contains a number of Strings
class Item : Object {
#objc dynamic var itemKey = NSUUID().uuidString
}
var primaryKeyArray : List<String>?
//Assume Realm DB already contains multiple Item Objects
//primaryKeyArray contains "key1", "key2", "key3", etc..
let predicate = NSPredicate(format: "itemKey == %#", primaryKeyArray)
let items = realm.objects(Item.self).filter(predicate)
I know the problem is with my predicate format. Not sure whether to use some form of CONTAINS or what? Any help with the predicate syntax would be greatly appreciated!
I think you are asking how to query Realm for items that have keys that match a set of keys in an array.
So given a DogClass Realm Object
class DogClass: Object {
#objc dynamic var dog_id = NSUUID().uuidString
#objc dynamic var dog_name = ""
override static func primaryKey() -> String? {
return "dog_id"
}
}
and suppose we know we want to retrieve three dogs that match some given primary keys
let keysToMatch = ["302AC133-3980-41F3-95E8-D3E7F639B769", "54ECC485-4910-44E5-98B9-0712BB99783E", "71FE403B-30CD-4E6C-B88A-D6FDBB08C509"]
let dogResults = realm.objects(DogClass.self).filter("dog_id IN %#", keysToMatch)
for dog in dogResults {
print(dog.dog_id, dog.dog_name)
}
Note the use of IN in the filter, which will match any dogs with id's in the given array.
You can also pass in a Realm List Object instead of a Swift array and get the same result.
let listOfKeysToMatch = List<String>()
listOfKeysToMatch.append("302AC133-3980-41F3-95E8-D3E7F639B769")
listOfKeysToMatch.append("54ECC485-4910-44E5-98B9-0712BB99783E")
listOfKeysToMatch.append("71FE403B-30CD-4E6C-B88A-D6FDBB08C509")
let dogResults2 = realm.objects(DogClass.self).filter("dog_id in %#", listOfKeysToMatch)
for dog in dogResults2 {
print(dog.dog_id, dog.dog_name)
}
let predicate = NSPredicate(format: "itemKey IN %#", primaryKeyArray)
In coffeescript, I'm trying to convert an array of objects into a dict, where one of the values of the object is taken as the key and all of the objects in the array with that value and up as being in an array in the dict linked to that key.
I have tried the code suggested here but this results in maximum one object per key. https://coffeescript-cookbook.github.io/chapters/arrays/creating-a-dictionary-object-from-an-array
I couldn't find any other examples that don't just result in one value per key.
So, for example (expanding on the example linked above), I have an array
cats = [
{
name: "Bubbles"
age: 1
},
{
name: "Sparkle"
favoriteFood: "tuna"
age: 2
},
{
name: "Felix"
age: 2
}
]
I want my result to be
catDict = {
1: [
{
name: "Bubbles"
age: 1
}
]
2: [
{
name: "Sparkle"
favoriteFood: "tuna"
age: 2
},
{
name: "Felix"
age: 2
}
]
}
catDict = {}
(catDict[cat.age]?.push(cat) or catDict[cat.age] = [cat]) for cat in cats
I used the accessor variant of the existential operator ?. to soak up null references. When a null reference is encountered the second half of the or kicks in to initialize the array.
It's shorter, but I'm not sure if it's more elegant...
Classic, coming up with a solution immediately after I give up and post the question on StackOverflow, but here's my solution:
addCatToDict = (cat, dict) ->
key = cat.age
if key of dict then dict[key].push(cat)
else dict[key] = [cat]
catDict = {}
for cat in cats
addCatToDict(cat, catDict)
interested to see more elegant solutions
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.
Lets say I have dictionaries like below and wanted an array of red dogs. I figured I need to get an array of all the names of the type "dog" using the first dictionary, and then use the name key and the color to search the final dictionary to get ["Polly,"jake"]. I've tried using loops but can't figure out how to iterate through the dictionary.
var pets = ["Polly" : "dog", "Joey" : "goldfish", "Alex" : "goldfish", "jake" : "dog"]
var petcolor = ["Polly" : "red", "Joey" : "black", "Alex" : "yellow", "jake":red"]
The correct solution would seem to be to create a Pet struct (or class) and collate all of this information into a struct and build either an array or dictionary full of these values.
struct Pet {
let name: String
let type: String
let color: String
init(name: String, type: String, color: String) {
self.name = name
self.type = type
self.color = color
}
}
Now, let's build an array of these pets:
var goodPets = [Pet]()
for (petName, petType) in pets {
guard let petColor = petcolor[petName] else {
// Found this pet's type, but couldn't find its color. Can't add it.
continue
}
goodPets.append(Pet(name: petName, type: petType, color: petColor))
}
Now that we've filled out goodPets, pulling out any particular subset of Pets becomes very easy:
let redDogs = goodPets.filter { $0.type == "dog" && $0.color = "red" }
And although this answer looks like a lot of set up & legwork compared to other answers, the major advantage here is that once we build the goodPets array, any way we want to scoop pets out of there ends up being more efficient. And as we increase the number of properties the pets have, this becomes more and more true compared to the other answers.
If you'd rather store our model objects in a dictionary continuing to use the names as the keys, we can do that as well, but the filter looks a little bit stranger.
Building the dictionary looks mostly the same:
var goodPets = [String : Pet]()
for (petName, petType) in pets {
guard let petColor = petcolor[petName] else {
// Found this pet's type, but couldn't find its color. Can't add it.
continue
}
goodPets[petName] = (Pet(name: petName, type: petType, color: petColor))
}
But the filter is slightly different:
let redDogs = goodPets.filter { $0.1.type = "dog" && $0.1.color = "red" }
Note that in both cases, redDogs has the type [Pet], that is, an array of Pet values.
You can iterate through a dictionary like this:
for key in pets.keys() {
if pets[key] == "Dog" {
}
}
Or:
for (name, pet) in pets {
if pet == "Dog" {
}
}
nhgrif is probably correct about structure but, to answer the literal question:
let dogs = Set(pets.filter { $0.1 == "dog" }.map { $0.0 })
let redAnimals = Set(petscolor.filter { $0.1 == "red" }.map { $0.0 })
let redDogs = dogs.intersect(redAnimals)
Each filter is a block that operates on a (key, value) tuple, testing the value and ultimately creating a dictionary with only the matching (key, value) pairs. Each map then converts that filtered dictionary into an array by discarding the values and just keeping the keys.
Each array is turned into a set to support the intersect operation. The intersect then determines the intersection of the two results.