I have an array of Objects with multiple properties. I need to group it by those properties into sections. I've already wrote al algorithm which does that. However, I'd like to have a more succinct and reusable one, so that I can group items in a different manner.
Given an array of objects:
#objcMembers class Object: NSObject {
let name: UUID = UUID()
let value1: Int = Int(arc4random_uniform(6) + 1)
let value2: Int = Int(arc4random_uniform(6) + 1)
let value3: Int = Int(arc4random_uniform(6) + 1)
static func == (lhs: Object, rhs: Object) -> Bool {
lhs.name == rhs.name
}
}
[
Object1 {4, 4, 1},
Object2 {1, 3, 2},
...
Object99 {3, 4, 2},
]
... and given two data structure, Group and Section:
struct Group {
let title: String?
let sections: [Section]
}
struct Section {
let title: String?
let items: [Object]
}
I need to get the following result:
Value1: 1 // Group
Value2: 1 - Value3: 1 // Section
Object1
Object2
Object3
Value2: 1 - Value3: 2 // Section
Object1
Value2: 2 - Value3: 1 // Section
Object1
Object2
Object3
Value1: 2 // Group
Value2: 1 - Value3: 5 // Section
Object1
Value2: 4 - Value3: 1 // Section
Object1
Value2: 4 - Value3: 2 // Section
Object1
Object2
Object3
So, that the objects are grouped into sections by their Value3 and Value2 and sorted in ascending order.
Then, these sections are grouped into groups by their Value1 and, again, sorted in ascending order.
My current algorithm implemented in a basic imperative approach and I'm sure has a lot of points to be improved.
I've already tried to use Swift's Dictionary.init(grouping:by:) initialiser and then Dictionary.mapValues method to group entries further. However, Swift's dictionaries are not ordered, so I have to do a deep sort again.
Currently, my algorithm looks like this:
// Sort the array
let value1BasedDescriptors = [
NSSortDescriptor(keyPath: \Object.value1, ascending: true),
NSSortDescriptor(keyPath: \Object.value2, ascending: true),
NSSortDescriptor(keyPath: \Object.value3, ascending: true),
]
let sorted = (Array(objects) as NSArray).sortedArray(using: value1BasedDescriptors) as! [Object]
// Keep the previous object to find when one of the properties change
var previousObject: Object?
// Keep the group to be filled with sections
var currentGroup = [Section]()
// Keep the section to be filled with objects
var currentSection = [Object]()
// All the groups to be returned by the function
var groups = [Group]()
// Iterate over each object
for object in sorted {
// If it's a first time in a loop, set a previous object and skip
if previousObject == nil {
previousObject = object
// Append to the current section
currentSection.append(object)
continue
}
// If one of the value3 or value2 is different from the previously visited object -> Create a new section with the appropriate title
if object.value3 != previousObject?.value3 || object.value2 != previousObject?.value2 {
let section = Section(title: "Value2: \(previousObject?.value2) - Value3: \(previousObject?.value3)", items: currentSection)
// Add it to current group
currentGroup.append(section)
// Empty the section
currentSection.removeAll()
}
// If Value1 is different, group all the objects into group
if object.value1 != previousObject?.value1 {
let group = Group(title: "Value1: \(previousObject?.value1)", sections: currentGroup)
groups.append(group)
currentGroup.removeAll()
}
// Always add a visited object to a current section
currentSection.append(object)
// And mark as previous
previousObject = object
}
// since the last group & section won't be added in a loop, we have to add them manually
let section = Section(title: "Value2: \(previousObject?.value2) - Value3: \(previousObject?.value3)", items: currentSection)
currentGroup.append(section)
let group = Group(title: "Value1: \(previousObject?.value1)", sections: currentGroup)
groups.append(group)
debugPrint(groups)
It does exactly what I need to achieve, however, here are the limitations:
What if I want to group the objects in the following order: Value2 -> Value1 -> Value3 ? Or any other order? Then I'll have to write the same algorithm, but changing the properties
If I have to write the same algorithm multiple times, how can I make it shorter, e.g. utilising Functional or OOP methods?
Full code listing (copy-paste to Playground or the AppDelegate.swift file):
struct Group {
let title: String?
let sections: [Section]
}
struct Section {
let title: String?
let items: [Object]
}
#objcMembers class Object: NSObject {
let name: UUID = UUID()
let value1: Int = Int(arc4random_uniform(6) + 1)
let value2: Int = Int(arc4random_uniform(6) + 1)
let value3: Int = Int(arc4random_uniform(6) + 1)
static func == (lhs: Object, rhs: Object) -> Bool {
lhs.name == rhs.name
}
}
// Create a lot of objects
var objects = Set<Object>()
for i in 0...100 {
objects.insert(Object())
}
// Sort the array
let value1BasedDescriptors = [
NSSortDescriptor(keyPath: \Object.value1, ascending: true),
NSSortDescriptor(keyPath: \Object.value2, ascending: true),
NSSortDescriptor(keyPath: \Object.value3, ascending: true),
]
let sorted = (Array(objects) as NSArray).sortedArray(using: value1BasedDescriptors) as! [Object]
// Keep the previous object to find when one of the properties change
var previousObject: Object?
// Keep the group to be filled with sections
var currentGroup = [Section]()
// Keep the section to be filled with objects
var currentSection = [Object]()
// All the groups to be returned by the function
var groups = [Group]()
// Iterate over each object
for object in sorted {
// If it's a first time in a loop, set a previous object and skip
if previousObject == nil {
previousObject = object
// Append to the current section
currentSection.append(object)
continue
}
// If one of the value3 or value2 is different from the previously visited object -> Create a new section with the appropriate title
if object.value3 != previousObject?.value3 || object.value2 != previousObject?.value2 {
let section = Section(title: "Value2: \(previousObject?.value2) - Value3: \(previousObject?.value3)", items: currentSection)
// Add it to current group
currentGroup.append(section)
// Empty the section
currentSection.removeAll()
}
// If Value1 is different, group all the objects into group
if object.value1 != previousObject?.value1 {
let group = Group(title: "Value1: \(previousObject?.value1)", sections: currentGroup)
groups.append(group)
currentGroup.removeAll()
}
// Always add a visited object to a current section
currentSection.append(object)
// And mark as previous
previousObject = object
}
// since the last group & section won't be added in a loop, we have to add them manually
let section = Section(title: "Value2: \(previousObject?.value2) - Value3: \(previousObject?.value3)", items: currentSection)
currentGroup.append(section)
let group = Group(title: "Value1: \(previousObject?.value1)", sections: currentGroup)
groups.append(group)
debugPrint(groups)
Here's how I would do this. I would use Dictionary(_:groupingBy:) to produce a groups, and then take that dictionary as the input to a mapping process, transforming the key:value pairs into Group objects. The mapping itself involves another process, calling Dictionary(_:groupingBy:) to group by value2, mapping those key:value pairs into Section objects.
To add the customization you're looking for, you can replace this nesting of these repeating Dictionary(_:groupingBy:), map and sorted calls can be replaces with recursion, by taking an array of keypaths (which represent the values by which you want the various layers grouped by)
import Foundation
struct Object: Equatable {
// let name: UUID = UUID()
let value1 = Int.random(in: 1...6)
let value2 = Int.random(in: 1...6)
let value3 = Int.random(in: 1...6)
static func == (lhs: Object, rhs: Object) -> Bool {
return (lhs.value1, lhs.value2, lhs.value3) == (rhs.value1, rhs.value2, rhs.value3)
}
}
extension Object: Comparable {
static func < (lhs: Object, rhs: Object) -> Bool {
return (lhs.value1, lhs.value2, lhs.value3) < (rhs.value1, rhs.value2, rhs.value3)
}
}
struct Group: CustomDebugStringConvertible {
let title: String
let sections: [Section]
var debugDescription: String {
let sectionText = self.sections
.map { "\t" + $0.debugDescription }
.joined(separator: "\n")
return "Group: \(self.title)\n\(sectionText)"
}
}
struct Section: CustomDebugStringConvertible {
let title: String
let items: [Object]
var debugDescription: String {
let itemText = self.items
.map { "\t\t" + String(describing: $0) }
.joined(separator: "\n")
return "Section: \(self.title)\n\(itemText)"
}
}
let input = (0...100).map { _ in Object() }.sorted()
let groups = Dictionary(grouping: input, by: { $0.value1 })
.map { (arg: (key: Int, rawSections: [Object])) -> Group in
let (key, rawSections) = arg
let sections = Dictionary(grouping: rawSections, by: { $0.value2 })
.map { key, objects in
Section(title: String(key), items: objects.sorted { $0.value3 < $1.value3 })
}
.sorted { $0.title < $1.title }
return Group(title: String(key), sections: sections)
}
.sorted(by: { $0.title < $1.title })
for group in groups {
debugPrint(group)
}
Related
Could someone point me in the right direction on how to solve this issue, please?
I am creating table cells with the values from the structure below. The cells are created and the data is displayed in the cells by the time they were created which works fine.
The issue is some of the cells have the same name and I have an individual id for each cell from the struc Data but I need the user to know which one of the duplicates was created first within the duplicates. Kind of like a sub-number.
For example: 1:apple -1 , 2:pear -1, 3:apple -2
1(position in all the cell) - Apple (name of the cell) - 1 (value based on how many cells are named apple)
The func idName() I created tells us how many occurrences of a name happens but how could I break this down so the data would display like above?
struct Data {
var id: Int
var name: String
var color: String
var time: TimeInterval
var sessionId: Int
var userId: Int
}
func idName () {
let idElement = elements //another variable is used to grab the Array
var counts: [String: Int] = [:]
var idValue: Int
for id in idElement {
counts[id.name] = (counts[id.name] ?? 0) + 1
for (key, value) in counts {
print("\(key) occurs \(value) time(s)")
}
}
}
"I need the user to know which one of the duplicates was created first."
How a bout adding a date to each item when it is created?
var date: Date = Date()
So you can sort them...
myObjects = myObjects.sorted(by: {
$0.date.compare($1.date) == .orderedDescending
})
Another way is to add a UUID, this will give you a unique Identifier to reference:
var uuid: UUID = UUID()
var someObjectID = myObject.uuid.uuidString
Update:
When an Element (your data struct) is created, you should be checking your array of elements prior to adding one of the same name, if one of the same name exists then you can increment a counter not stored as a property of the struct.
You can use map and Dictionary(_:uniquingKeysWith:).
to return an array of mapped elements (an array of your data structs).
let mappedElements = elements.map($0.name, 1)
then, count duplicates and create a dictionary containing the number of matching items.
let counts = Dictionary(mappedElements, uniquingKeysWith: +)
this will result in ["apple": 3, "pear": 2, "peach": 1] etc.
I Just added nameCounter property in Data which will indicate the occurrence of particular name. Like this -
struct Data1 {
var id: Int
var name: String
var nameCOunter: Int? = 1
init(id: Int, name: String) {
self.id = id
self.name = name
}
static func addTestData() ->[Data1] {
var arr = [Data1]()
let model = Data1(id: 1, name: "apple")
let model1 = Data1(id: 2, name: "peer")
let model2 = Data1(id: 3, name: "apple")
let model3 = Data1(id: 4, name: "orange")
let model4 = Data1(id: 5, name: "grape")
let model5 = Data1(id: 6, name: "peer")
let model6 = Data1(id: 7, name: "apple")
arr = [model,model1,model2,model3,model4,model5,model6]
return arr
}
}
func idName() {
let idElement = Data1.addTestData()
var countedElement = [Data1]()
var nameArr = [String]()
for var dataModel in idElement {
nameArr.append(dataModel.name)
let count = nameArr.filter{$0 == dataModel.name}.count
dataModel.nameCOunter = count
countedElement.append(dataModel)
}
print(countedElement)
}
I have this code in my viewController
var myArray :Array<Data> = Array<Data>()
for i in 0..<mov.count {
myArray.append(Data(...))
}
class Data {
var value :CGFloat
var name :String=""
init({...})
}
My input of Data is as:
10.5 apple
20.0 lemon
15.2 apple
45
Once I loop through, I would like return a new array as:
sum(value) group by name
delete last row because no have name
ordered by value
Expected result based on input:
25.7 apple
20.0 lemon
and nothing else
I wrote many rows of code and it is too confused to post it. I'd find easier way, anyone has a idea about this?
First of all Data is reserved in Swift 3, the example uses a struct named Item.
struct Item {
let value : Float
let name : String
}
Create the data array with your given values
let dataArray = [Item(value:10.5, name:"apple"),
Item(value:20.0, name:"lemon"),
Item(value:15.2, name:"apple"),
Item(value:45, name:"")]
and an array for the result:
var resultArray = [Item]()
Now filter all names which are not empty and make a Set - each name occurs one once in the set:
let allKeys = Set<String>(dataArray.filter({!$0.name.isEmpty}).map{$0.name})
Iterate thru the keys, filter all items in dataArray with the same name, sum up the values and create a new Item with the total value:
for key in allKeys {
let sum = dataArray.filter({$0.name == key}).map({$0.value}).reduce(0, +)
resultArray.append(Item(value:sum, name:key))
}
Finally sort the result array by value desscending:
resultArray.sorted(by: {$0.value < $1.value})
---
Edit:
Introduced in Swift 4 there is a more efficient API to group arrays by a predicate, Dictionary(grouping:by:
var grouped = Dictionary(grouping: dataArray, by:{$0.name})
grouped.removeValue(forKey: "") // remove the items with the empty name
resultArray = grouped.keys.map { (key) -> Item in
let value = grouped[key]!
return Item(value: value.map{$0.value}.reduce(0.0, +), name: key)
}.sorted{$0.value < $1.value}
print(resultArray)
First of all, you should not name your class Data, since that's the name of a Foundation class. I've used a struct called MyData instead:
struct MyData {
let value: CGFloat
let name: String
}
let myArray: [MyData] = [MyData(value: 10.5, name: "apple"),
MyData(value: 20.0, name: "lemon"),
MyData(value: 15.2, name: "apple"),
MyData(value: 45, name: "")]
You can use a dictionary to add up the values associated with each name:
var myDictionary = [String: CGFloat]()
for dataItem in myArray {
if dataItem.name.isEmpty {
// ignore entries with empty names
continue
} else if let currentValue = myDictionary[dataItem.name] {
// we have seen this name before, add to its value
myDictionary[dataItem.name] = currentValue + dataItem.value
} else {
// we haven't seen this name, add it to the dictionary
myDictionary[dataItem.name] = dataItem.value
}
}
Then you can convert the dictionary back into an array of MyData objects, sort them and print them:
// turn the dictionary back into an array
var resultArray = myDictionary.map { MyData(value: $1, name: $0) }
// sort the array by value
resultArray.sort { $0.value < $1.value }
// print the sorted array
for dataItem in resultArray {
print("\(dataItem.value) \(dataItem.name)")
}
First change your data class, make string an optional and it becomes a bit easier to handle. So now if there is no name, it's nil. You can keep it as "" if you need to though with some slight changes below.:
class Thing {
let name: String?
let value: Double
init(name: String?, value: Double){
self.name = name
self.value = value
}
static func + (lhs: Thing, rhs: Thing) -> Thing? {
if rhs.name != lhs.name {
return nil
} else {
return Thing(name: lhs.name, value: lhs.value + rhs.value)
}
}
}
I gave it an operator so they can be added easily. It returns an optional so be careful when using it.
Then lets make a handy extension for arrays full of Things:
extension Array where Element: Thing {
func grouped() -> [Thing] {
var things = [String: Thing]()
for i in self {
if let name = i.name {
things[name] = (things[name] ?? Thing(name: name, value: 0)) + i
}
}
return things.map{$0.1}.sorted{$0.value > $1.value}
}
}
Give it a quick test:
let t1 = Thing(name: "a", value: 1)
let t2 = Thing(name: "b", value: 2)
let t3 = Thing(name: "a", value: 1)
let t4 = Thing(name: "c", value: 3)
let t5 = Thing(name: "b", value: 2)
let t6 = Thing(name: nil, value: 10)
let bb = [t1,t2,t3,t4,t5,t6]
let c = bb.grouped()
// ("b",4), ("c",3) , ("a",2)
Edit: added an example with nil for name, which is filtered out by the if let in the grouped() function
I'v created a struct and I want to populate it with my data.
My struct:
struct CrimeNameSection {
var firstChar: Character
var name: [String]
var detail: [String]
var time: [String]
init(firstLetter: Character, object1: [String], object2: [String], object3: [String]) {
firstChar = firstLetter // First letter of 'name'
name = object1
detail = object2
time = object3
}
The first value of my struct ('firstChar') should hold the first letter in 'name' to create an alphabetic sections in tableView, the rest ('name','detail','time') should hold the data from my database (three columns: name, detail, time).
My code:
var marrCrimesData : NSMutableArray! // Hold the database
func getSectionsFromData() -> [CrimeNameSection] {
guard marrCrimesData != nil else {
return []
}
var sectionDictionary = [CrimeNameSection]()
for crime in marrCrimesData {
let crime = crime as! CrimesInfo
let firstChar = CrimeNameSection(firstLetter: crime.name[crime.name.startIndex], object1: [crime.name], object2: [crime.detail], object3: [crime.time])
if var names = firstChar {
names.append(crime.name)
sectionDictionary[firstChar] = names
} else {
sectionDictionary[firstChar] = [crime.name]
}
}
let sections = sectionDictionary.map { (key, value) in
return CrimeNameSection(firstLetter: key, name: value)
}
let sortedSections = sections.sorted { $0.firstLetter < $1.firstLetter }
return sortedSections
}
I get errors all over the place, I need help with storing the data inside my struct and sort it alphabetically.
Thank you all
Consider
struct Crime {
let name: String
let detail: String
let time: String
}
let crimes = [
Crime(name: "Foo", detail: "detail 1", time: "9am"),
Crime(name: "Bar", detail: "detail 2", time: "10am"),
Crime(name: "Baz", detail: "detail 3", time: "11am"),
Crime(name: "Qux", detail: "detail 4", time: "12am")
]
One approach is to just build an dictionary indexed by the first character and then sort it:
var crimeIndex = [Character: [Crime]]()
for crime in crimes {
if let firstCharacter = crime.name.characters.first {
if crimeIndex[firstCharacter] == nil {
crimeIndex[firstCharacter] = [crime]
} else {
crimeIndex[firstCharacter]?.append(crime)
}
}
}
let sortedIndex = crimeIndex.sorted { $0.0 < $1.0 }
The advantage of the above is that we can use the dictionary to efficiently find the section. If you really want to use your custom "name section" structure, I'd first make it to use an array of Crime objects (having disjointed arrays of the properties of a Crime can be fragile, e.g. if you later decide to add sorting of the crimes). So it might look like:
struct CrimeNameSection {
let firstCharacter: Character
var crimes: [Crime]
}
And because we've lost some of the Dictionary efficiency for finding the index and have manually iterate through looking for the section, and I'll go ahead and do an insertion sort at the time, saving me from having to do a separate sort later:
var crimeSections = [CrimeNameSection]()
for crime in crimes {
if let firstCharacter = crime.name.characters.first {
var hasBeenAdded = false
for (index, crimeIndex) in crimeSections.enumerated() {
if firstCharacter == crimeIndex.firstCharacter { // if we found section, add to it
crimeSections[index].crimes.append(crime)
hasBeenAdded = true
break
}
if firstCharacter < crimeIndex.firstCharacter { // if we've passed where the section should have been, insert new section
crimeSections.insert(CrimeNameSection(firstCharacter: firstCharacter, crimes: [crime]), at: index)
hasBeenAdded = true
break
}
}
// if we've gotten to the end and still haven't found section, add new section to end
if !hasBeenAdded {
crimeSections.append(CrimeNameSection(firstCharacter: firstCharacter, crimes: [crime]))
}
}
}
First of all you could not instantiate an Array and map over it like a dictionary
var sectionDictionary = [CrimeNameSection]() // Here you are init an Array
For a dictionary you have also to specify the key, for instance if the key is a string:
var sectionDictionary = [String: CrimeNameSection]() // Dictionary init
But be aware that the key have to be unique so that the dict would work properly.
Another problem here is the constructor in your .map function because you have not created a constructor for your CrimeNameSection that only takes two parameters:
init(firstLetter: Character, object1: [String], object2: [String], object3: [String]) {
firstChar = firstLetter // First letter of 'name'
name = object1
detail = object2
time = object3
}
// Another constructor with 2 arguments
init(firstLetter: Character, object1: [String]) {
firstChar = firstLetter // First letter of 'name'
name = object1
detail = []()
time = []()
}
If you don't want to use another constructor then you have to provide default values to object2 and object3 in your initial constructor.
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 }
I have a Person() class:
class Person : NSObject {
var firstName : String
var lastName : String
var imageFor : UIImage?
var isManager : Bool?
init (firstName : String, lastName: String, isManager : Bool) {
self.firstName = firstName
self.lastName = lastName
self.isManager = isManager
}
}
I have an array of Person()
var peopleArray = [Person]()
I want to count the number of people in the array who have
isManager: true
I feel this is out there, but I can;t find it, or find the search parameters.
Thanks.
Use filter method:
let managersCount = peopleArray.filter { (person : Person) -> Bool in
return person.isManager!
}.count
or even simpler:
let moreCount = peopleArray.filter{ $0.isManager! }.count
You can use reduce as follows:
let count = peopleArray.reduce(0, combine: { (count: Int, instance: Person) -> Int in
return count + (instance.isManager! ? 1 : 0) }
)
or a more compact version:
let count = peopleArray.reduce(0) { $0 + ($1.isManager! ? 1 : 0) }
reduce applies the closure (2nd parameter) to each element of the array, passing the value obtained for the previous element (or the initial value, which is the 0 value passed as its first parameter) and the current array element. In the closure you return count plus zero or one, depending on whether the isManager property is true or not.
More info about reduce and filter in the standard library reference
⚠️ count(where:) was removed from Swift 5 in Xcode 10.2 beta 4.
With Swift 5 and Xcode 10.2 beta 3, you can use Array's count(where:) method if you want to count the number of elements in an array that match a given predicate. count(where:) has the following declaration:
func count(where predicate: (Element) throws -> Bool) rethrows -> Int
Returns the number of elements in the sequence that satisfy the given predicate.
The following Playground sample code shows how to use count(where:):
struct Person {
let name: String
let isManager: Bool
}
let array = [
Person(name: "Jane", isManager: true),
Person(name: "Bob", isManager: false),
Person(name: "Joe", isManager: true),
Person(name: "Jill", isManager: true),
Person(name: "Ted", isManager: false)
]
let managerCount = array.count(where: { (person: Person) -> Bool in
return person.isManager
})
// let managerCount = array.count { $0.isManager } // also works
print(managerCount) // prints: 3