UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Is .map the right method for this task? Using .map to manipulate elements in array, operating within given constraints (projected pay raise of X% every Y years)

Forums > Swift

Purpose of project: Allow an employee to calculate/project future 401k yearly balances based on several future promotions. With promotions come pay raises.

Current challenge to overcome: Manipulate pay rates based on projected pay rate percentage increases at regular intervals.

Example: A company has communicated to its employees an intention to increase all pay rates by 4% every four years initiated on every January 1st.

Within the below arrays for pay rates, each element of the array represents a yearly pay raise. All employees climb the pay rate scale one element every year.

The employee/app user will be able to toggle (Bool) on/off this option of regularly interval increases:

var regularPayRateIncreases: Bool
var regularPayRateIncreasePercentage: Double
var regularPayRateIncreaseYearInterval: Int

...

var charlotte = Employee("Charlotte Grote", 
birthdate: birthdate, 
hiredate: hiredate, 
retirementAge: 65, 
department: .other("Hospitality"), 
monthlyHoursWorked: 160, 
regularPayRateIncreases: true, 
regularPayRateIncreasePercentage: 4.0, 
regularPayRateIncreaseYearInterval: 4)

The full code is found here on GitHub.

However, I am able to fit the entire macOS Line Command Tool code here...

import Foundation

enum Calculations {
    private static let pennyRoundingBehavior = NSDecimalNumberHandler(
        roundingMode: .bankers,
        scale: 2,
        raiseOnExactness: false,
        raiseOnOverflow: true,
        raiseOnUnderflow: true,
        raiseOnDivideByZero: true
    )
    static func computeMonthlyEarnings(hours hoursWorked: Int, atPayRate payRate: Int) -> Decimal {
        let base = Decimal(hoursWorked * payRate) as NSDecimalNumber
        return base.rounding(accordingToBehavior: pennyRoundingBehavior) as Decimal
    }
}
extension Array {
    func chunked(into size: Int) -> [[Element]] {
        return stride(from: 0, to: count, by: size).map {
            Array(self[$0 ..< Swift.min($0 + size, count)])
        }
    }
}
enum Department: RawRepresentable {
    case sales
    case management
    case seniorManagement
    case other(String)

    init?(rawValue: String) {
        switch rawValue {
        case "Sales": self = .sales
        case "Management": self = .management
        case "Senior Management": self = .seniorManagement
        case let dept: self = .other(dept)
        }
    }
    var rawValue: String {
        switch self {
        case .sales: return "Sales"
        case .management: return "Management"
        case .seniorManagement: return "Senior Management"
        case .other(let dept): return dept
        }
    }
    func payRate(forYearsWorked numberOfYears: Int) -> Int {
        let rates: [Int]
        switch self {
        case .sales: rates = [20,22,24,26,28,30,32,34,36,38,40]
        case .management: rates = [30,32,34,36,38,40,42,44,46,48,50]
        case .seniorManagement: rates = [40,42,44,46,48,50,52,54,56,58,60]
        default: rates = [10,12,14,16,18,20,22,24,26,28,30]
        }
        return rates[min(numberOfYears, rates.count) - 1]
    }
}

struct Employee {
    let name: String
    var birthdate: Date
    var hiredate: Date
    var retirementAge: Int
    var department: Department
    var monthlyHoursWorked: Int
    var regularPayRateIncreases: Bool
    var regularPayRateIncreasePercentage: Double
    var regularPayRateIncreaseYearInterval: Int

    private(set) var employmentHistory: [Department]

    init(_ name: String,
         birthdate: Date,
         hiredate: Date,
         retirementAge: Int,
         department: Department = .other("Employee"),
         monthlyHoursWorked: Int,
         regularPayRateIncreases: Bool,
         regularPayRateIncreasePercentage: Double,
         regularPayRateIncreaseYearInterval: Int)
    {
        self.name = name
        self.birthdate = birthdate
        self.hiredate = hiredate
        self.retirementAge = retirementAge
        self.department = department
        self.monthlyHoursWorked = monthlyHoursWorked
        self.regularPayRateIncreases = regularPayRateIncreases
        self.regularPayRateIncreasePercentage = regularPayRateIncreasePercentage
        self.regularPayRateIncreaseYearInterval = regularPayRateIncreaseYearInterval
        self.employmentHistory = []
    }
}
extension Employee {
    mutating func promote(to newDepartment: Department) {
        department = newDepartment
    }
    mutating func work(months: Int) {
        employmentHistory
            .append(contentsOf: Array.init(repeating: department,
                                           count: months))
    }
    mutating func work(years: Int) {
        work(months: years * 12)
    }
    mutating func work(years: Int, months: Int) {
        work(months: years * 12 + months)
    }
    func yearlyEarnings() -> [Decimal] {
        let years = employmentHistory.chunked(into: 12)
        var earnings: [Decimal] = []
        for (index, months) in years.enumerated() {
            let yearEarnings: Decimal = months.reduce(0) { runningTotal, dept in
                let payRate = dept.payRate(forYearsWorked: index + 1)
                let monthlyEarnings = Calculations.computeMonthlyEarnings(
                    hours: monthlyHoursWorked,
                    atPayRate: payRate
                )
                return runningTotal + monthlyEarnings
            }
            earnings.append(yearEarnings)
        }
        return earnings
    }
}
extension Employee {
    func yearlyDepartments() -> [String] {
        let years = employmentHistory.chunked(into: 12)
        let departments: [String] = years.map { months in
            var seenDepts: Set<String> = []
            return months.reduce(into: [String]()) { acc, dept in
                if seenDepts.insert(dept.rawValue).inserted {
                    acc.append(dept.rawValue)
                }
            }.joined(separator: ",")
        }
        return departments
    }
    func yearlyDepartmentsEarnings() -> [(String, Decimal)] {
        let yearlyDepartments = yearlyDepartments()
        let yearlyEarnings = yearlyEarnings()
        let yearlyDepartmentsEarnings: [(departments: String, earnings: Decimal)] =
        Array(zip(yearlyDepartments, yearlyEarnings))
        return yearlyDepartmentsEarnings
    }
}
var birthdate = DateComponents(calendar: .current, year: 1965, month: 7, day: 20).date!
var hiredate = DateComponents(calendar: .current, year: 2021, month: 12, day: 28).date!
var promotion1date = DateComponents(calendar: .current, year: 2023, month: 12, day: 28).date!
var firstDeptMonths: Int { Calendar.current.dateComponents([.month], from: hiredate, to: promotion1date).month! }
var promotion2date = DateComponents(calendar: .current, year: 2025, month: 6, day: 28).date!
var secondDeptMonths: Int { Calendar.current.dateComponents([.month], from: promotion1date, to: promotion2date).month! }
var promotion3date = DateComponents(calendar: .current, year: 2026, month: 12, day: 28).date!
var thirdDeptMonths: Int { Calendar.current.dateComponents([.month], from: promotion2date, to: promotion3date).month! }
var retireAge = 65
let retireDate = Calendar.current.date(byAdding: .year, value: retireAge, to: birthdate)!
var fourthDeptMonths: Int { Calendar.current.dateComponents([.month], from: promotion3date, to: retireDate).month! }

extension Date {
    var age: Int { Calendar.current.dateComponents([.year], from: self, to: Date()).year! }
    var yearGroup: Int { Calendar.current.dateComponents([.year], from: self, to: Date()).year! + 1}
    var totalYearsRemaining: Int { Calendar.current.dateComponents([.year], from: self, to: retireDate).year! }
    var totalMonthsRemaining: Int { Calendar.current.dateComponents([.month], from: self, to: retireDate).month! }
}
//age of employee
let age = birthdate.age
//number of years employee has worked for the company
let yearGroup = hiredate.yearGroup

func printYearlyDepartmentsAndEarnings(_ yearlyDeptsAndEarnings: [(departments: String, earnings: Decimal)]) {
    for (year, deptsAndEarnings) in yearlyDeptsAndEarnings.enumerated() {
        print("Department(s) for Year \(year + yearGroup + 1): \(deptsAndEarnings.departments)")
        print("Earnings for Year \(year + yearGroup + 1): \(deptsAndEarnings.earnings)")
        print("Age: \(year + age)")
        print("")
    }
}
var charlotte = Employee("Charlotte Grote", 
birthdate: birthdate, 
hiredate: hiredate, 
retirementAge: 65, 
department: .other("Hospitality"), 
monthlyHoursWorked: 160, 
regularPayRateIncreases: true, 
regularPayRateIncreasePercentage: 4.0, 
regularPayRateIncreaseYearInterval: 4)

//give our employee some work history
//months in Hospitality
charlotte.work(months: firstDeptMonths)
//ohh, a promotion!
charlotte.promote(to: .sales)
//months in Sales
charlotte.work(months: secondDeptMonths)
//another promotion. way to go, Charlotte!
charlotte.promote(to: .management)
//months in Management
charlotte.work(months: thirdDeptMonths)
//another promotion. way to go, Charlotte!
charlotte.promote(to: .seniorManagement)
//months in Senior Management
charlotte.work(months: fourthDeptMonths)
printYearlyDepartmentsAndEarnings(charlotte.yearlyDepartmentsEarnings())

In simple cases of array manipulation, the map method is a tried and true tool...

let salesPayRate: [Double] = [20,22,24,26,28,30,32,34,36,38,40]
var increasedPayRate = salesPayRate.map { (rate) -> Double in
    return rate * 1.04
}

However, the above salesPayRate.map example does not allow for multiple manipulations at regular intervals (like multiplying by 1.04 every four years / every four elements until retirement). And since the last element of the payRate arrays will be the Charlotte's stagnated pay rate for several years until retirement, if an employee worked enough years to reach the end of the pay scale I need that last pay rate to continue to increase by 4% every four years until retirement.

Is the map method the best tool for this more complex task?

3      

I have reorganized the GitHub repository. It should be much clearer now: "1.1.0 (Use This One)" is the code referenced above.

The current print using a Command Line Tool project is as follows:

Department(s) for Year 3: Hospitality
Earnings for Year 3: 19200
Age: 57

Department(s) for Year 4: Hospitality
Earnings for Year 4: 23040
Age: 58

Department(s) for Year 5: Sales
Earnings for Year 5: 46080
Age: 59

Department(s) for Year 6: Sales,Management
Earnings for Year 6: 59520
Age: 60

Department(s) for Year 7: Management
Earnings for Year 7: 72960
Age: 61

Department(s) for Year 8: Senior Management
Earnings for Year 8: 96000
Age: 62

Department(s) for Year 9: Senior Management
Earnings for Year 9: 99840
Age: 63

Department(s) for Year 10: Senior Management
Earnings for Year 10: 103680
Age: 64

I appreciate any guidance you can provide to incorporate a regular pay raise function using a .map method or some other device. Thank you!

2      

Hi, The only way i could think of is if the years until retirement is larger than the pay rate scale then append the last number X ammount of times.

let yearsUntilRetirement = 16
let repeatingCount = yearsUntilRetirement - salesPayRate.count

if repeatingCount > 0 {
    salesPayRate.append(contentsOf:(Array(repeating: salesPayRate[salesPayRate.count - 1], count: repeatingCount)))
}

and to multiply every 4th element of the array using .map i would use an index.

var index = 1
var increasedPayRate = salesPayRate.map { (rate) -> Double in
    if index.isMultiple(of: 4) {
        index += 1
        return rate * 1.04
    } else {
        index += 1
        return rate
    }
}

So the hole thing would look like this:

var salesPayRate: [Double] = [20,22,24,26,28,30,32,34,36,38,40]

let yearsUntilRetirement = 16
let repeatingCount = yearsUntilRetirement - salesPayRate.count

if repeatingCount > 0 {
    salesPayRate.append(contentsOf:(Array(repeating: salesPayRate[salesPayRate.count - 1], count: repeatingCount)))
}

var index = 1
var increasedPayRate = salesPayRate.map { (rate) -> Double in
    if index.isMultiple(of: 4) {
        index += 1
        return rate * 1.04
    } else {
        index += 1
        return rate
    }
}

Hopefully that's what your looking for.

2      

@Hectorcrdna

Thank you! This is a step in the right direction but there is major problem. The pay raise is only applied to the 4th, 8th, 12th, etc. The pay raise needs set a new standard. Meaning the 4% pay raise does not only apply to the 4th year but also all the years following.

When we put your code in the Playground, it transforms the array from:

[20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 40, 40, 40, 40, 40]

to:

[20, 22, 24, 27.04, 28, 30, 32, 35.36, 36, 38, 40, 41.6, 40, 40, 40, 41.6]

Notice how the fifth element is 28 in both arrays. The fifth element should be 28 1.04. The sixth element should be 30 1.04. The eighth element should be 34 1.04 1.04.

2      

Ok, i think i understand, try with this one.

var salesPayRate: [Double] = [20,22,24,26,28,30,32,34,36,38,40]

let yearsUntilRetirement = 16
let repeatingCount = yearsUntilRetirement - salesPayRate.count

if repeatingCount > 0 {
    salesPayRate.append(contentsOf:(Array(repeating: salesPayRate[salesPayRate.count - 1], count: repeatingCount)))
}

var index = 1
var payRaise = 0
var increasedPayRate = salesPayRate.map { (rate) -> Double in
    if index.isMultiple(of: 4) {
        index += 1
        payRaise += 1
        var newRate = rate
        for _ in 0..<payRaise {
            newRate *= 1.04
        }
        return newRate

    } else {
        index += 1
        var newRate = rate
        for _ in 0..<payRaise {
            newRate *= 1.04
        }
        return newRate
    }
}

2      

@Hectorcrdna

Yes!! Perfect! Thank you!

Now how do I apply this into my code that is in my original post?

2      

Uff... I'm not sure, i think you would need to re-structure your code, turn the code above into a function and some how link the employees to this func, but i think you should Implement this in to the ios app instead of the command line since you're making the transition.

func increasedPayRate(forDepartment department: Department, percentage: Double, yearInterval: Int, yearsUntilRetirement: Int) -> [Double] {
    var payRate: [Double] = [20,22,24,26,28,30,32,34,36,38,40] // Replace this with department's pay rate.

    let repeatingCount = yearsUntilRetirement - payRate.count

    if repeatingCount > 0 {
        payRate.append(contentsOf:(Array(repeating: payRate[payRate.count - 1], count: repeatingCount)))
    }

    var index = 1
    var payRaise = 0
    var increasedPayRate = payRate.map { (rate) -> Double in
        if index.isMultiple(of: yearInterval) {
            payRaise += 1
        }
        index += 1
        var newRate = rate
        for _ in 0..<payRaise {
            newRate *= (percentage / 100) + 1
        }
        return Double(String(format: "%.2f", newRate)) ?? newRate
    }

    return increasedPayRate
}

2      

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.