Count number of items in an array with a specific property value - ios

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

Related

Swift: How to remove item from a Set of custom objects based on custom condition (higher order function)

Let's assume I have a Set of Custom objects (i.e. Person), something like this:
struct Person: Hashable {
let name: String
let age: Int
}
let persons: Set<Person> = [Person(name: "Willy", age: 25), Person(name: "Jenny", age: 22)]
If I was to remove all items that have a specific name, I cannot find a Set method for this case.
In Array there is such a method called:
#inlinable public mutating func removeAll(where shouldBeRemoved: (Element) throws -> Bool) rethrows
But for some reason I cannot find an equivalent in Set.
So my solution was to perform a quick conversion for the removing procees something like this:
var arr = Array(persons)
arr.removeAll {
$0.name == "Willy"
}
persons = Set(arr)
Is there any direct solution In Set rather than using Array as a middle-man?
set = set.filter { $0.value != 1 }
You can use a higher order filter to create a new set that excludes objects with the name property "Willy":
let persons: Set<Person> = [
Person(name: "Willy", age: 25),
Person(name: "Jenny", age: 22)
]
let set = persons.filter{$0.name != "Willy"}

Swift: Group nested array by multiple criteria - improving algorithm

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)
}

Swift 3: sum value with group by of an array of objects

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

Swift - Compare array with same array in swift and if find similar values then add or combine or sum values

I am working on the project where I have an array for class objects. I want to iterate each object and see if there exist duplicate values in the array then add or sum values into one.
Example:
class SomeClass: NSObject {
internal var displays: Int?
internal var id: String?
}
I have an array of SomeClass, when id is the same then add values of displays into one.
Thanks
This code should work:
class SomeClass: NSObject {
var displays: Int
var id: String
init(_ id: String, _ displays: Int) {
self.id = id
self.displays = displays
}
}
//Example of filtering
let array = [
SomeClass("1", 1),
SomeClass("3", 3),
SomeClass("4", 7),
SomeClass("8", 8),
SomeClass("2", 3),
SomeClass("7", 2),
SomeClass("5", 5),
SomeClass("1", 1),
SomeClass("3", 4),
SomeClass("2", 2),
SomeClass("7", 5),
SomeClass("6", 8)
]
var resultDictionary: [String : Int] = [:]
for element in array {
let lastDisplays = resultDictionary[element.id] ?? 0 //if object with this id is first time counted, the resultDictionary[element.id] will return nil and then lastDisplays will be equal to 0
resultDictionary[element.id] = lastDisplays + element.displays
}
var result = resultDictionary.map { SomeClass($0.key, $0.value) }
In the end the result array will contain SomeClass objects with counted displays.
Because dictionary have only unique keys, we can use it for counting displays sum.
Try this approach:
class SomeClass: NSObject {
internal var displays: Int?
internal var id: String?
init(displays: Int?, id: String?) {
self.displays = displays
self.id = id
}
}
var array = [
SomeClass(displays: 2, id: "123"),
SomeClass(displays: 3, id: "456"),
SomeClass(displays: 4, id: "123"),
]
var counts: [String: Int] = [:]
for obj in array {
if let id = obj.id, displays = obj.displays {
let prevDisplays = counts[id] ?? 0
counts[id] = prevDisplays + displays
}
}
print(counts)
EDIT: If you would like to have more functional version of above, I can only come up with the following:
counts = array.reduce([String: Int]()) { dict, item in
var dict = dict
if let id = item.id, displays = item.displays {
dict.updateValue((dict[id] ?? 0) + displays, forKey: id)
}
return dict
}

Map array of objects to Dictionary in Swift

I have an array of Person's objects:
class Person {
let name:String
let position:Int
}
and the array is:
let myArray = [p1,p1,p3]
I want to map myArray to be a Dictionary of [position:name] the classic solution is:
var myDictionary = [Int:String]()
for person in myArray {
myDictionary[person.position] = person.name
}
is there any elegant way by Swift to do that with the functional approach map, flatMap... or other modern Swift style
Since Swift 4 you can do #Tj3n's approach more cleanly and efficiently using the into version of reduce It gets rid of the temporary dictionary and the return value so it is faster and easier to read.
Sample code setup:
struct Person {
let name: String
let position: Int
}
let myArray = [Person(name:"h", position: 0), Person(name:"b", position:4), Person(name:"c", position:2)]
Into parameter is passed empty dictionary of result type:
let myDict = myArray.reduce(into: [Int: String]()) {
$0[$1.position] = $1.name
}
Directly returns a dictionary of the type passed in into:
print(myDict) // [2: "c", 0: "h", 4: "b"]
Okay map is not a good example of this, because its just same as looping, you can use reduce instead, it took each of your object to combine and turn into single value:
let myDictionary = myArray.reduce([Int: String]()) { (dict, person) -> [Int: String] in
var dict = dict
dict[person.position] = person.name
return dict
}
//[2: "b", 3: "c", 1: "a"]
In Swift 4 or higher please use the below answer for clearer syntax.
Since Swift 4 you can do this very easily. There are two new initializers that build a dictionary from a sequence of tuples (pairs of key and value). If the keys are guaranteed to be unique, you can do the following:
let persons = [Person(name: "Franz", position: 1),
Person(name: "Heinz", position: 2),
Person(name: "Hans", position: 3)]
Dictionary(uniqueKeysWithValues: persons.map { ($0.position, $0.name) })
=> [1: "Franz", 2: "Heinz", 3: "Hans"]
This will fail with a runtime error if any key is duplicated. In that case you can use this version:
let persons = [Person(name: "Franz", position: 1),
Person(name: "Heinz", position: 2),
Person(name: "Hans", position: 1)]
Dictionary(persons.map { ($0.position, $0.name) }) { _, last in last }
=> [1: "Hans", 2: "Heinz"]
This behaves as your for loop. If you don't want to "overwrite" values and stick to the first mapping, you can use this:
Dictionary(persons.map { ($0.position, $0.name) }) { first, _ in first }
=> [1: "Franz", 2: "Heinz"]
Swift 4.2 adds a third initializer that groups sequence elements into a dictionary. Dictionary keys are derived by a closure. Elements with the same key are put into an array in the same order as in the sequence. This allows you to achieve similar results as above. For example:
Dictionary(grouping: persons, by: { $0.position }).mapValues { $0.last! }
=> [1: Person(name: "Hans", position: 1), 2: Person(name: "Heinz", position: 2)]
Dictionary(grouping: persons, by: { $0.position }).mapValues { $0.first! }
=> [1: Person(name: "Franz", position: 1), 2: Person(name: "Heinz", position: 2)]
How about a KeyPath based solution?
extension Array {
func dictionary<Key, Value>(withKey key: KeyPath<Element, Key>, value: KeyPath<Element, Value>) -> [Key: Value] {
reduce(into: [:]) { dictionary, element in
let key = element[keyPath: key]
let value = element[keyPath: value]
dictionary[key] = value
}
}
}
This is how you use it:
struct HTTPHeader {
let field: String, value: String
}
let headers = [
HTTPHeader(field: "Accept", value: "application/json"),
HTTPHeader(field: "User-Agent", value: "Safari")
]
headers.dictionary(withKey: \.field, value: \.value) // ["Accept": "application/json", "User-Agent": "Safari"]
You may write custom initializer for Dictionary type, for example from tuples:
extension Dictionary {
public init(keyValuePairs: [(Key, Value)]) {
self.init()
for pair in keyValuePairs {
self[pair.0] = pair.1
}
}
}
and then use map for your array of Person:
var myDictionary = Dictionary(keyValuePairs: myArray.map{($0.position, $0.name)})
This is what I have been using
struct Person {
let name:String
let position:Int
}
let persons = [Person(name: "Franz", position: 1),
Person(name: "Heinz", position: 2),
Person(name: "Hans", position: 3)]
var peopleByPosition = [Int: Person]()
persons.forEach{peopleByPosition[$0.position] = $0}
Would be nice if there was a way to combine the last 2 lines so that peopleByPosition could be a let.
We could make an extension to Array that does that!
extension Array {
func mapToDict<T>(by block: (Element) -> T ) -> [T: Element] where T: Hashable {
var map = [T: Element]()
self.forEach{ map[block($0)] = $0 }
return map
}
}
Then we can just do
let peopleByPosition = persons.mapToDict(by: {$0.position})
You can use a reduce function. First I've created a designated initializer for Person class
class Person {
var name:String
var position:Int
init(_ n: String,_ p: Int) {
name = n
position = p
}
}
Later, I've initialized an Array of values
let myArray = [Person("Bill",1),
Person("Steve", 2),
Person("Woz", 3)]
And finally, the dictionary variable has the result:
let dictionary = myArray.reduce([Int: Person]()){
(total, person) in
var totalMutable = total
totalMutable.updateValue(person, forKey: total.count)
return totalMutable
}
Maybe something like this?
myArray.forEach({ myDictionary[$0.position] = $0.name })
extension Array {
func mapToDict<T>(by block: (Element) -> T ) -> [T: Element] where T: Hashable {
var map = [T: Element]()
self.forEach{ map[block($0)] = $0 }
return map
}
}
extension Array {
func toDictionary() -> [Int: Element] {
self.enumerated().reduce(into: [Int: Element]()) { $0[$1.offset] = $1.element }
}
}

Resources