How to deal out 2 different length arrays by the value? - arrays

Think of a company, employees work in different capacities. Employees will be employed according to their capacities. There should be similar capacities for every day.
I want the array1's values ​​in the days array to be equally distributed.
For example: Thursday -> 2 item and 5 value, Friday -> 1 item and 5 value
Saturday -> 1 item and 6 value, Sunday -> 2 item and 6 value.
Let it be distributed with similar numbers.
I have 2 array like this:
let days = ["Thursday","Friday","Saturday","Sunday"]
var array1: [T] = [T(name: "a", value: 3),T(name: "b", value: 2),T(name: "c", value: 5),T(name: "d", value: 1),T(name: "e", value: 6),T(name: "f", value: 4)]
How can I deal out array1 objects to days array?
I tried this:
var arrayValueTotal = 0
for t in array1{
arrayValueTotal = arrayValueTotal + t.value
}
var upperLimit = Double(Double(arrayValueTotal)/Double(days.count)) // This will get value per day. 22/4 = 5.5
for i in (0...days.count-1){ //Handling the days
for j in array1{
if j.value >= Int(upperLimit){ //handling the array1's value
}else if j.value < Int(upperLimit){
}
}
}
Desired result like:
let combinedResult = [("Thursday": [T(name: "a", value: 3),
T(name: "b", value: 2)]),
("Friday": [T(name: "c", value: 5)]),
("Saturday": [T(name: "e", value: 6)]),
("Sunday": [T(name: "f", value: 5),
T(name: "d", value: 1)])]
Here is the Class T:
class T {
var name:String
var value:Int
init(name:String, value:Int) {
self.name = name
self.value = value
}
}
How can I get that? Thanks in advance.

Ok, if I understood correctly, let's start by separating array1 according to limit (that you calculated yourself):
var array1: [T] = [T(name: "a", value: 3),
T(name: "b", value: 2),
T(name: "c", value: 5),
T(name: "d", value: 1),
T(name: "e", value: 6),
T(name: "f", value: 4)]
let reducedArray = array1.reduce(into: [[T]]()) { result, current in
guard var last = result.last else { result.append([current]); return }
if last.reduce(0, { $0 + $1.value }) < limit {
last.append(current)
result[result.count - 1] = last
} else {
result.append([current])
}
}
Then reducedArray is:
[
[T(name:a, value: 3), T(name:b, value: 2)],
[T(name:c, value: 5)],
[T(name:d, value: 1), T(name:e, value: 6)],
[T(name:f, value: 4)]
]
Then
var final: [(String, [T])] = []
for i in 0..<min(days.count, reducedArray.count) { //If the count aren't equal, what to do with the left values?
final.append((days[i], reducedArray[i]))
}
print("Final: \(final)")
With final being:
[
("Thursday", [T(name:a, value: 3), T(name:b, value: 2)]),
("Friday", [T(name:c, value: 5)]),
("Saturday", [T(name:d, value: 1), T(name:e, value: 6)]),
("Sunday", [T(name:f, value: 4)])
]

Related

How to get sum from an object array by its ID - Swift

I want to get a sum of each object which will be classified by its id.
So, my model is:
struct MyObject {
let id: String
var amount: Double
}
And my data are:
var myObjectArray = [
MyObject(id: "A", amount: 1.0),
MyObject(id: "B", amount: 0.2),
MyObject(id: "A", amount: 0.4),
MyObject(id: "B", amount: 0.8),
MyObject(id: "C", amount: 2.1)
]
The results should be something like this:
myObjectArrayResults = [
MyObject(id: "A", amount: 1.4),
MyObject(id: "B", amount: 1.0),
MyObject(id: "C", amount: 2.1)
]
I tried something to do like this, but it didn't work.
for (index, object2) in newObjectArray.enumerated() {
for object in myObjectArray {
if object2.id == object.id {
newObjectArray[index].amount = newObjectArray[index].amount + object.amount
} else {
newObjectArray.append(object)
}
}
}
What might be wrong?
Thank you in advance for your contribution.
You can use reduce(into:) to calculate the sums using an interim dictionary object and then map the result back to MyModel
let result = myObjectArray
.reduce(into: [:]) { $0[$1.id, default: 0] += $1.amount }
.map(MyObject.init)
You wrote:
for (index, object2) in newObjectArray.enumerated() {
for object in myObjectArray {
if object2.id == object.id {
newObjectArray[index].amount = newObjectArray[index].amount + object.amount
} else {
newObjectArray.append(object)
}
}
}
But at start newObjectArray is empty, no? So it won't work. Then, the logic rest has be re-checked again.
You can do it like that with a for loop:
var newObjectArray = [MyObject]()
for object in myObjectArray {
if let existingIndex = newObjectArray.firstIndex(where: { $0.id == object.id }) {
newObjectArray[existingIndex].amount += object.amount
} else {
newObjectArray.append(object)
}
}
The idea, is to iterate over all the objects in myObjectArray, then find if it already exists, in which case we sum, or else we just append.
With reduced(into:_:), keeping the same kind of logic:
let reduced = myObjectArray.reduce(into: [MyObject]()) { partialResult, current in
if let existingIndex = partialResult.firstIndex(where: { $0.id == current.id }) {
partialResult[existingIndex].amount += current.amount
} else {
partialResult.append(current)
}
}

Fetch data from an array with elements of second array

I have one array "users" with all user data and and second array "userIds" having user's id. I have to fetch User from "users" array using "userIds" array
struct User {
let name: String
let id: Int
}
let users: [User] = [User(name: "a", id: 1),
User(name: "b", id: 2),
User(name: "c", id: 3),
User(name: "d", id: 4),
User(name: "d", id: 5)]
let userIds = [2,3,2,5]
result array that I want is :
[User(name: "b", id: 2),
User(name: "c", id: 3),
User(name: "b", id: 2),
User(name: "d", id: 5)]
so it can have duplicate data according to the data in "userIds".
Now I tried using Higher order function filter:
let result = users.filter { (user) -> Bool in
return userIds.contains(user.id)
}
but this removes the duplicate data and the output is :
[User(name: "b", id: 2),
User(name: "c", id: 3),
User(name: "d", id: 5)]
One approach that I tried is using for loop :
var result = [User]()
for i in userIds {
result.append(users.filter({ $0.id == i }).first!)
}
which gives the desired output but if there is a better approach please suggest.
You can solve this using first(where:) to search through users:
let result = userIds.compactMap { desiredDataValue in
users.first(where: { $0.id == desiredDataValue })
}
But if you're doing this a lot, it would probably speed things up if you built a datastructure that allows for fast lookup by the "id" value. You should compare the performance for yourself, and see if you do this enough/frequently enough for it to be worthwhile:
let dictsByData = Dictionary(uniqueKeysWithValues:
users
.lazy
.map { dict in
(key: dict.id, value: dict)
}
)
let result = userIds.compactMap { desiredDataValue in dictsByData[desiredDataValue]! }
result.forEach { print($0) }
Well after digging few more and with the help of this blog:
https://medium.com/#abhimuralidharan/higher-order-functions-in-swift-filter-map-reduce-flatmap-1837646a63e8
I tried doing like this:
let results = userIds.compactMap { (int) -> User? in
var matchedUser: User?
if users.contains(where: { (user) -> Bool in
if user.id == int {
matchedUser = user
}
return user.id == int
}) {
return matchedUser
}
return nil
}
and in playground I checked the count the code was executed :
and it seems like the count is less comparing to "for" loop.

Swift - Find Specific Differences in Arrays of Structs

If I have a structure, say:
struct Subject {
var subjectID: String?
var name: String?
var note: String?
}
And I have two arrays of this structure: Array1 and Array2.
For example:
Array1 = [(subjectID = "T", name = "H", note = "DF"), (subjectID = "F", name = "H", note = "SD")]
Array2 = [(subjectID = "T", name "G", note = "DF"), (subjectID = "R", name = "F", note = "SDF")]
I want to return a new array, which consists of a subset of elements from Array2 that match the subjectID field of Array1 but have different name and/or note elements.
In the example above, the returned array would be:
[(subjectID = "T", name "G", note = "DF")]
As it contains the same subjectID (in this case T) as in Array1 but the name field is different. Note that the fields for this new returned array should be original values from Array2 (ex: you don't need to correct them to match Array1)
Is there an easy way (ex: one-two lines of code) to do this without brute forcing it?
Thanks!
There are good answers here, I prefer to keep the test simple.
First the setup
struct Subject {
var subjectID: String?
var name: String?
var note: String?
}
let array1 = [Subject(subjectID: "T", name: "H", note: "DF"), Subject(subjectID: "F", name: "H", note: "SD")]
let array2 = [Subject(subjectID: "T", name: "G", note: "DF"), Subject(subjectID: "R", name: "F", note: "SDF")]
Now lets look at the actual algorithm. array2.filter returns an array of Subjects in the array2 in which the block returns true. array1.contains returns true if any of the Subjects in array1 returns true. The test itself is exactly what you described. Are the subject id equal and does either the name or the note differ.
let result = array2.filter { s2 in
array1.contains { s1 in
s1.subjectID == s2.subjectID && (s1.name != s2.name || s1.note != s2.note)
}
}
You can do it like this:
let subjectsByID = Dictionary(grouping: array1, by: { $0.subjectID })
let diff = array2.filter { subject in
if let other = subjectsByID[subject.subjectID]?.first {
return subject.name != other.name || subject.note != other.note
} else {
return false
}
}
It groups the subjects in the first array by ID and then filters the second array based on whether or not there is an entry for that ID with a different name or note. You didn't specify what to do if there are multiple entries in the first array with the same ID so it just looks at the first one.
I used a forEach and filter in combination to find the requested elements
var result = [Subject]()
arr1.forEach( { subject in
result.append(contentsOf: arr2.filter( { $0.subjectID == subject.subjectID &&
($0.name != subject.name ||
$0.note != subject.note) }))
})
To get a little cleaner code the check could be made into a function in the struct
struct Subject {
...
func isModifiedComparedTo(_ subject: Subject) -> Bool {
return self.subjectID == subject.subjectID && (self.name != subject.name || self.note != subject.note)
}
}
var result = [Subject]()
arr1.forEach( { subject in
result.append(contentsOf: arr2.filter({$0.isModifiedComparedTo(subject)}))
})
You could filter the second array elements based on the first array elements, as:
let Array1 = [Subject(subjectID: "T", name: "H", note: "DF"), Subject(subjectID: "F", name: "H", note: "SD")]
let Array2 = [Subject(subjectID: "T", name: "G", note: "DF"), Subject(subjectID: "R", name: "F", note: "SDF")]
let result = Array2.filter { subject -> Bool in
for s in Array1 {
if subject.subjectID == s.subjectID && subject.name != s.name && subject.note != s.subjectID { return true }
}
return false
}
result should contains what are you asking for. Keep in mind that it has the complexity of nested iteration (O(n²)).

Sort Array of Objects With Some Conditions

I have an array of Users objects:
Class Users {
var firstName:String!
var lastName:String!
var status:Int!
override init(fn: String, ln: String, s: Int) {
self.firstName = fn
self.lastName = ln
self.status = s
}
}
var users:[Users] = [Users]()
users.append(Users(fn:"a", ln:"b", s:1))
users.append(Users(fn:"c", ln:"d", s:3))
users.append(Users(fn:"e", ln:"f", s:2))
users.append(Users(fn:"g", ln:"h", s:1))
users.append(Users(fn:"i", ln:"j", s:1))
users.append(Users(fn:"k", ln:"l", s:2))
I know the method to sort this array on status like
users = users.sorted(by: {$0.status > $1.status})
But, how could I sort users array on status with 2 on top then 3 and in the last 1 i.e [2,2,3,1,1,1]
Consider refactoring:
Class to struct:
struct User {
var firstName:String!
var lastName:String!
var status:Status
}
Status enum:
enum Status: Int {
case online, busy, offline
}
Population:
var users: [User] = [
User(firstName: "a", lastName: "b", status: .online),
User(firstName: "c", lastName: "d", status: .offline),
User(firstName: "e", lastName: "f", status: .busy),
User(firstName: "g", lastName: "h", status: .online),
User(firstName: "i", lastName: "j", status: .online),
User(firstName: "k", lastName: "l", status: .busy)
]
Sort:
users = users.sorted(by:{$0.status.rawValue < $1.status.rawValue })
users.forEach { print($0) }
Output:
User(firstName: a, lastName: b, status: .online)
User(firstName: g, lastName: h, status: .online)
User(firstName: i, lastName: j, status: .online)
User(firstName: e, lastName: f, status: .busy)
User(firstName: k, lastName: l, status: .busy)
User(firstName: c, lastName: d, status: .offline)
Try this. This will sort based on the order array. If any status is missing will be moved to bottom of the array.
let order = [2,3,1]
users = users.sorted(by: { order.index(of: $0.status) ?? Int.max < order.index(of: $1.status) ?? Int.max })
// 2,2,3,1,1,1
Lets say if 3 is missing in order array
let order = [2,1]
users = users.sorted(by: { order.index(of: $0.status) ?? Int.max < order.index(of: $1.status) ?? Int.max })
// 2,2,1,1,1,3
Use a lookup for your sort order.
let sortPriority: [Int: Int] = [ 1: 3, 2: 1, 3: 2]
Also you can use the mutating sort to sort the array in place instead of sorted which returns a copy since it looks like you are discarding the original array:
users.sort(by: {sortPriority [$0.status, default: Int.max] < sortPriority [$1.status, default: Int.max]})
print(users.flatMap{$0.status})
output:
[2, 2, 3, 1, 1, 1]

Group elements of an array by some property

I have an array of objects with property date.
What I want is to create array of arrays where each array will contain objects with the same date.
I understand, that I need something like .filter to filter objects, and then .map to add every thing to array.
But how to tell .map that I want separate array for each group from filtered objects and that this array must be added to "global" array and how to tell .filter that I want objects with the same date ?
It might be late but new Xcode 9 sdk dictionary has new init method
init<S>(grouping values: S, by keyForValue: (S.Element) throws -> Key) rethrows where Value == [S.Element], S : Sequence
Documentation has simple example what this method does.
I just post this example below:
let students = ["Kofi", "Abena", "Efua", "Kweku", "Akosua"]
let studentsByLetter = Dictionary(grouping: students, by: { $0.first! })
Result will be:
["E": ["Efua"], "K": ["Kofi", "Kweku"], "A": ["Abena", "Akosua"]]
improving on oriyentel solution to allow ordered grouping on anything:
extension Sequence {
func group<GroupingType: Hashable>(by key: (Iterator.Element) -> GroupingType) -> [[Iterator.Element]] {
var groups: [GroupingType: [Iterator.Element]] = [:]
var groupsOrder: [GroupingType] = []
forEach { element in
let key = key(element)
if case nil = groups[key]?.append(element) {
groups[key] = [element]
groupsOrder.append(key)
}
}
return groupsOrder.map { groups[$0]! }
}
}
Then it will work on any tuple, struct or class and for any property:
let a = [(grouping: 10, content: "a"),
(grouping: 20, content: "b"),
(grouping: 10, content: "c")]
print(a.group { $0.grouping })
struct GroupInt {
var grouping: Int
var content: String
}
let b = [GroupInt(grouping: 10, content: "a"),
GroupInt(grouping: 20, content: "b"),
GroupInt(grouping: 10, content: "c")]
print(b.group { $0.grouping })
With Swift 5, you can group the elements of an array by one of their properties into a dictionary using Dictionary's init(grouping:by:) initializer. Once done, you can create an array of arrays from the dictionary using Dictionary's values property and Array init(_:) initializer.
The following Playground sample code shows how to group the elements of an array by one property into a new array of arrays:
import Foundation
struct Purchase: CustomStringConvertible {
let id: Int
let date: Date
var description: String {
return "Purchase #\(id) (\(date))"
}
}
let date1 = Calendar.current.date(from: DateComponents(year: 2010, month: 11, day: 22))!
let date2 = Calendar.current.date(from: DateComponents(year: 2015, month: 5, day: 1))!
let date3 = Calendar.current.date(from: DateComponents(year: 2012, month: 8, day: 15))!
let purchases = [
Purchase(id: 1, date: date1),
Purchase(id: 2, date: date1),
Purchase(id: 3, date: date2),
Purchase(id: 4, date: date3),
Purchase(id: 5, date: date3)
]
let groupingDictionary = Dictionary(grouping: purchases, by: { $0.date })
print(groupingDictionary)
/*
[
2012-08-14 22:00:00 +0000: [Purchase #4 (2012-08-14 22:00:00 +0000), Purchase #5 (2012-08-14 22:00:00 +0000)],
2010-11-21 23:00:00 +0000: [Purchase #1 (2010-11-21 23:00:00 +0000), Purchase #2 (2010-11-21 23:00:00 +0000)],
2015-04-30 22:00:00 +0000: [Purchase #3 (2015-04-30 22:00:00 +0000)]
]
*/
let groupingArray = Array(groupingDictionary.values)
print(groupingArray)
/*
[
[Purchase #3 (2015-04-30 22:00:00 +0000)],
[Purchase #4 (2012-08-14 22:00:00 +0000), Purchase #5 (2012-08-14 22:00:00 +0000)],
[Purchase #1 (2010-11-21 23:00:00 +0000), Purchase #2 (2010-11-21 23:00:00 +0000)]
]
*/
Abstracting one step, what you want is to group elements of an array by a certain property. You can let a map do the grouping for you like so:
protocol Groupable {
associatedtype GroupingType: Hashable
var grouping: GroupingType { get set }
}
extension Array where Element: Groupable {
typealias GroupingType = Element.GroupingType
func grouped() -> [[Element]] {
var groups = [GroupingType: [Element]]()
for element in self {
if let _ = groups[element.grouping] {
groups[element.grouping]!.append(element)
} else {
groups[element.grouping] = [element]
}
}
return Array<[Element]>(groups.values)
}
}
Note that this grouping is stable, that is groups appear in order of appearance, and inside the groups the individual elements appear in the same order as in the original array.
Usage Example
I'll give an example using integers; it should be clear how to use any (hashable) type for T, including Date.
struct GroupInt: Groupable {
typealias GroupingType = Int
var grouping: Int
var content: String
}
var a = [GroupInt(grouping: 1, content: "a"),
GroupInt(grouping: 2, content: "b") ,
GroupInt(grouping: 1, content: "c")]
print(a.grouped())
// > [[GroupInt(grouping: 2, content: "b")], [GroupInt(grouping: 1, content: "a"), GroupInt(grouping: 1, content: "c")]]
Rapheal's solution does work. However, I would propose altering the solution to support the claim that the grouping is in fact stable.
As it stands now, calling grouped() will return a grouped array but subsequent calls could return an array with groups in a different order, albeit the elements of each group will be in the expected order.
internal protocol Groupable {
associatedtype GroupingType : Hashable
var groupingKey : GroupingType? { get }
}
extension Array where Element : Groupable {
typealias GroupingType = Element.GroupingType
func grouped(nilsAsSingleGroup: Bool = false) -> [[Element]] {
var groups = [Int : [Element]]()
var groupsOrder = [Int]()
let nilGroupingKey = UUID().uuidString.hashValue
var nilGroup = [Element]()
for element in self {
// If it has a grouping key then use it. Otherwise, conditionally make one based on if nils get put in the same bucket or not
var groupingKey = element.groupingKey?.hashValue ?? UUID().uuidString.hashValue
if nilsAsSingleGroup, element.groupingKey == nil { groupingKey = nilGroupingKey }
// Group nils together
if nilsAsSingleGroup, element.groupingKey == nil {
nilGroup.append(element)
continue
}
// Place the element in the right bucket
if let _ = groups[groupingKey] {
groups[groupingKey]!.append(element)
} else {
// New key, track it
groups[groupingKey] = [element]
groupsOrder.append(groupingKey)
}
}
// Build our array of arrays from the dictionary of buckets
var grouped = groupsOrder.flatMap{ groups[$0] }
if nilsAsSingleGroup, !nilGroup.isEmpty { grouped.append(nilGroup) }
return grouped
}
}
Now that we track the order that we discover new groupings, we can return a grouped array more consistently than just relying on a Dictionary's unordered values property.
struct GroupableInt: Groupable {
typealias GroupingType = Int
var grouping: Int?
var content: String
}
var a = [GroupableInt(groupingKey: 1, value: "test1"),
GroupableInt(groupingKey: 2, value: "test2"),
GroupableInt(groupingKey: 2, value: "test3"),
GroupableInt(groupingKey: nil, value: "test4"),
GroupableInt(groupingKey: 3, value: "test5"),
GroupableInt(groupingKey: 3, value: "test6"),
GroupableInt(groupingKey: nil, value: "test7")]
print(a.grouped())
// > [[GroupableInt(groupingKey: 1, value: "test1")], [GroupableInt(groupingKey: 2, value: "test2"),GroupableInt(groupingKey: 2, value: "test3")], [GroupableInt(groupingKey: nil, value: "test4")],[GroupableInt(groupingKey: 3, value: "test5"),GroupableInt(groupingKey: 3, value: "test6")],[GroupableInt(groupingKey: nil, value: "test7")]]
print(a.grouped(nilsAsSingleGroup: true))
// > [[GroupableInt(groupingKey: 1, value: "test1")], [GroupableInt(groupingKey: 2, value: "test2"),GroupableInt(groupingKey: 2, value: "test3")], [GroupableInt(groupingKey: nil, value: "test4"),GroupableInt(groupingKey: nil, value: "test7")],[GroupableInt(groupingKey: 3, value: "test5"),GroupableInt(groupingKey: 3, value: "test6")]]
+1 to GolenKovkosty answer.
init<S>(grouping values: S, by keyForValue: (S.Element) throws -> Key) rethrows where Value == [S.Element], S : Sequence
More Examples:
enum Parity {
case even, odd
init(_ value: Int) {
self = value % 2 == 0 ? .even : .odd
}
}
let parity = Dictionary(grouping: 0 ..< 10 , by: Parity.init )
Equilvalent to
let parity2 = Dictionary(grouping: 0 ..< 10) { $0 % 2 }
In your case:
struct Person : CustomStringConvertible {
let dateOfBirth : Date
let name :String
var description: String {
return "\(name)"
}
}
extension Date {
init(dateString:String) {
let formatter = DateFormatter()
formatter.timeZone = NSTimeZone.default
formatter.dateFormat = "MM/dd/yyyy"
self = formatter.date(from: dateString)!
}
}
let people = [Person(dateOfBirth:Date(dateString:"01/01/2017"),name:"Foo"),
Person(dateOfBirth:Date(dateString:"01/01/2017"),name:"Bar"),
Person(dateOfBirth:Date(dateString:"02/01/2017"),name:"FooBar")]
let parityFields = Dictionary(grouping: people) {$0.dateOfBirth}
Output:
[2017-01-01: [Foo, Bar], 2017-02-01: [FooBar] ]
This is a clean way to perform group by:
let grouped = allRows.group(by: {$0.groupId}) // Dictionary with the key groupId
Assuming you have array of contacts like :
class ContactPerson {
var groupId:String?
var name:String?
var contactRecords:[PhoneBookEntry] = []
}
To achieve this, add this extension:
class Box<A> {
var value: A
init(_ val: A) {
self.value = val
}
}
public extension Sequence {
func group<U: Hashable>(by key: (Iterator.Element) -> U) -> [U: [Iterator.Element]] {
var categories: [U: Box<[Iterator.Element]>] = [:]
for element in self {
let key = key(element)
if case nil = categories[key]?.value.append(element) {
categories[key] = Box([element])
}
}
var result: [U: [Iterator.Element]] = Dictionary(minimumCapacity: categories.count)
for (key, val) in categories {
result[key] = val.value
}
return result
}
}

Resources