Answer:
Using higher-order function:
import Foundation
// Function to calculate min, max, and average transaction amounts
func calculateTransactionStats(transactions: [Double]) -> (min: Double?, max: Double?, average: Double?) {
guard !transactions.isEmpty else {
return (nil, nil, nil) // Handle empty array case
}
let minTransaction = transactions.min()
let maxTransaction = transactions.max()
let averageTransaction = transactions.reduce(0, +) / Double(transactions.count)
return (minTransaction, maxTransaction, averageTransaction)
}
Without using higher-order function (optimised code in terms of memory allocation and execution time):
import Foundation
// Function to calculate min, max, and average without using higher-order functions
func calculateTransactionStats(transactions: [Double]) -> (min: Double?, max: Double?, average: Double?) {
guard !transactions.isEmpty else {
return (nil, nil, nil) // Handle empty array case
}
var minTransaction = transactions[0]
var maxTransaction = transactions[0]
var total = 0.0
for transaction in transactions {
if transaction < minTransaction {
minTransaction = transaction
}
if transaction > maxTransaction {
maxTransaction = transaction
}
total += transaction
}
let averageTransaction = total / Double(transactions.count)
return (minTransaction, maxTransaction, averageTransaction)
}
// Example usage
let transactions = [1200.5, 450.0, 2300.0, 560.0, 890.75]
let stats = calculateTransactionStats(transactions: transactions)
print("Minimum Transaction: \(stats.min ?? 0)")
print("Maximum Transaction: \(stats.max ?? 0)")
print("Average Transaction: \(stats.average ?? 0)")
Optimisations Explained:
Single Loop:
- In the original implementation, no redundant loops are present since everything is already computed in a single loop. This is efficient and does not require optimization.
Avoid Extra Memory Allocation:
- We are only using three variables (
minTransaction
, maxTransaction
, and total
) to store intermediate values. - No extra arrays or data structures are created during computation.
Direct Access:
- We directly access elements of the array using a
for
loop without calling methods like min()
, max()
, or reduce()
. This avoids any extra function calls or higher-order function overhead.
Efficient Guard Clause:
- The
guard
statement ensures the function exits early if the input array is empty, avoiding unnecessary computations.
Additional Suggestions for Large Datasets:
Use Parallel Processing for Large Datasets:
- If the dataset is very large, you can divide the array into chunks and process each chunk in parallel using concurrency (e.g., GCD or Swift's
async/await
) to compute partial min, max, and sum. Combine these partial results in the end. - However, note that this may only be beneficial when the array size is extremely large, as parallelism introduces its own overhead.
Precision Control:
- For very large datasets or high precision requirements, consider using floating-point types like
Decimal
instead of Double
to avoid precision errors.
Memory Optimization:
- If you don’t need to keep the original array after processing, you can work directly with the same array, reducing memory usage (e.g., use
inout
for array processing).
Final Notes on Performance:
- The optimized function already computes the required results in O(n) time complexity with O(1) additional memory usage.
- These optimizations ensure that the function is fast and uses minimal resources for typical use cases.