Filter dictionary of array objects - ios

I am trying to filter my dictionary according to user input in UISearchController. I have following model and array of objects.
struct People {
var name: String
var id: Int
}
let first = People(name: "Atalay", id: 1)
let second = People(name: "Ahmet", id: 2)
let third = People(name: "Mehmet", id: 3)
let fourth = People(name: "Yusuf", id: 4)
let peoples: [People] = [first, second, third, fourth, fifth]
I put them into a dictionary to create section indexed table view with following code.
var dict: [String: [People]] = Dictionary(grouping: peoples, by: { (people) -> String in
return String(people.name.prefix(1))
})
Above code gives me a dictionary with first letter of People names. Now, I would like to filter my array according to user input. However, I tried following code for filtering but it is not working as I expected.
let filteredDict = (dict.filter { $0.1.contains { $0.name.lowercased().contains("ata") } })
It returns all "A" letter section indexes like ["A": People(name: "Atalay", id: 1), People(name: "Ahmet", id: 2)]
How can I achieve filter also my array inside dictionary?

If I'm not mistaken, you want your final dictionary to have all the keys and only the filtered array of items as the values. If that is right, reduce is the tool for that:
let filtered = dict.reduce(into: [String: [People]]()) {
$0[$1.key] = $1.value.filter { $0.name.lowercased().contains("ata") }
}

I decided it was simplest to get this right by using an old fashioned for loop and filter each group separately
var filtered = [String: [People]]()
for (k, v) in dict {
let result = v.filter {$0.name.lowercased().contains("ata")}
if result.count > 0 {
filtered[k] = result
}
}
Note that if you want to keep all the groups in the result dictionary just skip the if result.count > 0 condition

How can I achieve filter also my array inside dictionary?
You should have an array first, you can use flatMap to group all the values in your filteredDict
let array = filteredDict.flatMap { $0.value }
Then you just filter the array as usually
let filteredArray = array.filter { $0.name.lowercased().contains("ata") }

Related

Swift iOS -How to sort array of individual objects into separated arrays based on similar property

I have an array of Super Hero objects. I want to group the superheroes based on the name property into separated arrays and then count how many objects are in each individual separated array
Object:
class SuperHero{
var name: String?
var power: Bool?
}
Array of superheroes (there can be an infinite num of superheroes)
var superHeroes = [SuperHero]()
let superHero1 = SuperHero()
superHero1.name = "SuperMan"
superHero1.power = true
superHeroes.append(superHero1)
let superHero2 = SuperHero()
superHero2.name = "BatMan"
superHero2.power = true
superHeroes.append(superHero2)
let superHero3 = SuperHero()
superHero3.name = "BatMan"
superHero3.power = true
superHeroes.append(superHero3)
let superHero4 = SuperHero()
superHero4.name = "SuperMan"
superHero4.power = true
superHeroes.append(superHero4)
//etc...
Use name property to sort:
let sortedHeros = superHeroes.sort{$0.name < $1.name}
for hero in sortedHeros{
print(hero.name)
/*
prints
BatMan
BatMan
SuperMan
SuperMan
*/
}
How do I put the sorted array into separate arrays then print the count of each separated array?
//this is what I want
separatedArraysOfSuperHeroes = [[superHero2, superHero3], [superHero1, superHero4]]
//subscriprting isn't ideal because i'll never know the exact number of separated arrays
print(separatedArraysOfSuperHeroes[0].count)
print(separatedArraysOfSuperHeroes[1].count)
As per the comments the reason why I want sub arrays is because I want to use them to populate different tableview sections. For i.e. inside my tableview I would now have a 2 sections. The first section would have a header that says "Batman" with 2 Batman objects inside of it and the second section would have a header that says Superman with 2 Superman objects inside of it. The count property would show the number of super hero objects inside each section.
func getSeparatedArrayBasedOnName(superHeroes: [SuperHero]) -> [[SuperHero]] {
guard let superNames = NSOrderedSet.init(array: superHeroes.map { $0.name ?? "" }).array as? [String] else {
print("Something went wrong with conversion")
return [[SuperHero]]()
}
var filteredArray = [[SuperHero]]()
for superName in superNames {
let innerArray = superHeroes.filter({ return $0.name == superName })
filteredArray.append(innerArray)
}
for array in filteredArray {
for hero in array {
print(hero.name ?? "")
}
}
return filteredArray
}

Group elements with the same property value from an array [duplicate]

This question already has answers here:
How to group by the elements of an array in Swift
(16 answers)
Closed 6 years ago.
I have an array [modelA,modelB,modelC,modelD,modelE], each element in the array is an instance of a Struct. The Struct has a property "name". for example...
modelA.name = "abc"
modelB.name = "efg"
modelC.name = "hij"
modelD.name = "abc"
modelE.name = "efg"
How can I group elements with the same property value into a new array? i.e. put modelA and modelD into a new array,and put modelB and modelE into another array.
Assume the original array is large.
You can achieve this by using filter(_:):
Returns an array containing, in order, the elements of the sequence
that satisfy the given predicate.
For example, consider that the structure looks like:
struct Model {
var name: String?
}
And you have an array of models:
let allModelsArray = [Model(name: "abc"), Model(name: "efg"), Model(name: "hij"), Model(name: "abc"), Model(name: "efg"), Model(name: "efg"), Model(name: "hij")]
So, you can get your arrays by doing (assuming that you want to filter based on the value of the name):
let abcModelsArray = allModelsArray.filter { $0.name == "abc" }
// [Model(name: Optional("abc")), Model(name: Optional("abc"))]
let hijModelsArray = allModelsArray.filter { $0.name == "hij" }
// [Model(name: Optional("hij")), Model(name: Optional("hij"))]
ALSO:
You mentioned that:
how can I put element which has the same property value into a new
array, such as put modelA and modelD into a new array, and put modelB
and modelE into a new array, if array is large.
Somehow, you might want to use the lazy version of the collection.
Hope this helped.
I have not performance tested this
struct Model {
var type : String
var name : String
}
var modelA = Model(type: "A", name: "abc")
var modelB = Model(type: "B", name: "efg")
var modelC = Model(type: "C", name: "abc")
var modelD = Model(type: "D", name: "efg")
let models = [modelA,modelB,modelC,modelD]
let names = Set(models.map({return $0.name}))
var groupedModels : [String:[Model]] = [:]
for var name in names {
let elements = models.filter({$0.name == name})
groupedModels[name] = elements
}
.reduce solution:
let a = [modelA, modelB, modelC, modelD, modelE]
let arr = a.reduce([:]) { (result, currentModel) -> [String: [Model]] in
var mutableDic = result
if ((mutableDic[currentModel.name]) != nil) {
mutableDic[currentModel.name]?.append(currentModel)
} else {
mutableDic[currentModel.name] = [currentModel]
}
return mutableDic
}
It will return the same dictionary as #Grimxn response. or got from this for loop
var mutableDic = [String : [Model]]()
for aModel in a {
if ((mutableDic[aModel.name]) != nil) {
mutableDic[aModel.name]?.append(aModel)
} else {
mutableDic[aModel.name] = [aModel]
}
}
The key is to use a Dictionary to track for Model that need to be put in the same array, by comparing to it's .name.

Dictionary inside dictionary

I am trying to use a list that is a value for a dictionary key/pair set, and this dictionary is itself a value in a key/pair set in a dictionary. To explain, this is how I initialize it.
var dictOfEvents = [Int: [Int: [PFObject]]]()
I am trying to add events to the list, with the inner dictionary's key being the day of month and the outer one being the month. For example, an event on May 1 would be:
dictOfEvents[5:[1:[ListOfEvents]]
Where ListOfEvents is an array of PFObjects. Before I added the month functionality, and thus the outer dictionary, the way I added new events was:
` self.dictOfEvents[components.day] = [event]
But now, when I try to extend this with:
self.dictOfEvents[components.month]?[components.day]! = [event]
It does not work. Any explanation on how to create new event lists and access this double layer dictionary would be greatly appreciated.
(Note: I don't know where to put the ! and the ? in the last piece of code so please excuse me if I made a mistake.)
Here is what I think could be a good use of optionals in your case (and should respond to your question):
var dic: [Int: [Int: [String]]] = [:]
dic[5] = [1:["Hello", "World"]]
if let list = dic[5]?[1] {
// your list exist and you can safely use it
for item in list {
println(item)
}
}
I just used String instead of PFObject.
A different approach could be:
/*
Define a struct to encapsulate your Month and Day
Make it Hashable so that you can use it as Dictionary key
*/
public struct MonthDay: Hashable {
let month: Int
let day: Int
public var hashValue: Int { return month * 100 + day }
}
public func ==(lhs: MonthDay, rhs: MonthDay) -> Bool {
return lhs.month == rhs.month && lhs.day == rhs.day
}
var dictOfEvents = [MonthDay :[String]]()
let aMonthAndDay = MonthDay(month: 5, day: 1)
dictOfEvents[aMonthAndDay] = ["Hello", "World"]
if let list = dictOfEvents[aMonthAndDay] {
// your list exist and you can safely use it
for item in list {
println(item)
}
}
U can simple change:
self.dictOfEvents[components.month]?[components.day]! = [event]
to :
self.dictOfEvents[components.month]![components.day]! = [event]
Because Dictionary has subscript, Dictionary? doesn't have subscript.
if U try add Events to Dictionary. I suggest to use this:
var dictOfEvents = [Int: [Int: [PFObject]]]()
var dictOfDayEvents = [Int:[PFObject]]()
dictOfDayEvents.updateValue([PFObject()], forKey: 1)
dictOfEvents.updateValue(dictOfDayEvents, forKey: 5)

Find Object with Property in Array

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 = ""
}
}

Create Dictionary<String, [SomeStruct]> from [SomeStruct] source-array

var sourceEntries: [Entry] = [entry1, ..., entry14]
var myDict: Dictionary<String, [Entry]> = [:]
for entry in sourceEntries {
if var array = myDict[entry.attribute1] { theArray.append(entry) }
else { myDict[entry.attribute1] = [entry] }
}
I am intending to create a Dictionary, which matches all the objects of the struct "Eintrag" with the same attribute from the source-Array "alleEinträge" to a String containing the value of the shared attribute. For some reason my final Dictionary just matches Arrays of one element to the Strings, although some Arrays ought to contain up to four elements.
The problem is that the array is passed by value (i.e. "copied"), so the array you are writing to when you say array.append is not the array that is "inside" the dictionary. You have to write back into the dictionary explicitly if you want to change what's in it.
Try it in a simple situation:
var dict = ["entry":[0,1,2]]
// your code
if var array = dict["entry"] { array.append(4) }
// so what happened?
println(dict) // [entry: [0, 1, 2]]
As you can see, the "4" never got into the dictionary.
You have to write back into the dictionary explicitly:
if var array = dict["entry"] { array.append(4); dict["entry"] = array }
FURTHER THOUGHTS: You got me thinking about whether there might be a more elegant way to do what you're trying to do. I'm not sure whether you will think this is "more elegant", but perhaps it has some appeal.
I will start by setting up a struct (like your Entry) with a name attribute:
struct Thing : Printable {
var name : String
var age : Int
var description : String {
return "{\(self.name), \(self.age)}"
}
}
Now I will create an array like your sourceEntries array, where some of the structs share the same name (like your shared attribute attribute1):
let t1 = Thing(name: "Jack", age: 40)
let t2 = Thing(name: "Jill", age: 38)
let t3 = Thing(name: "Jill", age: 37)
let arr = [t1,t2,t3]
And of course I will prepare the empty dictionary, like your myDict, which I call d:
var d = [String : [Thing]]()
Now I will create the dictionary! The idea is to use map and filter together to do all the work of creating key-value pairs, and then we just build the dictionary from those pairs:
let pairs : [(String, [Thing])] = arr.map {
t in (t.name, arr.filter{$0.name == t.name})
}
for pair in pairs { d[pair.0] = pair.1 }

Resources