How to calculate averages by date/month/year from Array in Swift3 - arrays

I have an Array of Dictionaries.
The Dictionary is like ["date":"2017-07-03T10:12:00", "amount":2.1].
EDIT
Let's say, I have the following array.
let dic1 = ["date":"2017-07-03T10:12:00", "amount":2.0]
let dic2 = ["date":"2017-07-03T11:15:03", "amount":4.0]
let dic3 = ["date":"2017-07-03T17:05:04", "amount":1.0]
let dic4 = ["date":"2017-07-04T09:05:03", "amount":3.0]
let dic5 = ["date":"2017-07-04T12:01:22", "amount":5.0]
let dic6 = ["date":"2017-07-04T19:01:01", "amount":2.0]
let array = [dic1, dic2, dic3, dic4, dic5, dic6]
Now I'm trying to calculate the following result from Array.
"date":"2017-07-03", "average":3.5
"date":"2017-07-04", "average":5.0
"date":"2017-07", "average":2.82..
"date":"2017", "average":2.82..
I could get the average of all data with the following code but I'm stuck now.
let average = array.flatMap({ $0["amount"] as? Double }).reduce(0, +) / Double(array.count)

Suggestion using a custom struct including a date formatter to convert the ISO8601 string to Date
struct Item {
let date : Date
let amount : Double
static let dateFormatter : ISO8601DateFormatter = {
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withFullDate, .withTime, .withColonSeparatorInTime]
return formatter
}()
init(date: String, amount: Double) {
self.date = Item.dateFormatter.date(from: date)!
self.amount = amount
}
}
Populate the array
let items = [Item(date : "2017-07-03T10:12:00", amount :2.0),
Item(date : "2017-07-03T11:15:03", amount :4.0),
Item(date : "2017-07-03T17:05:04", amount :1.0),
Item(date : "2017-07-04T09:05:03", amount :3.0),
Item(date : "2017-07-04T12:01:22", amount :5.0),
Item(date : "2017-07-04T19:01:01", amount :2.0)]
Create a function to take the array and specific date components as parameters
func average(of items: [Item], matching components: DateComponents) -> Double
{
let filteredDates = items.filter { Calendar.current.date($0.date, matchesComponents: components) }
return filteredDates.map{ $0.amount }.reduce(0.0, +) / Double(filteredDates.count)
}
Now filter the items by date components and return the average value
average(of: items, matching: DateComponents(year: 2017, month: 7, day: 3))
average(of: items, matching: DateComponents(year: 2017, month: 7, day: 4))
average(of: items, matching: DateComponents(year: 2017, month: 7))
average(of: items, matching: DateComponents(year: 2017))

You should filter your array and calculate the average of the filtered values. This example is for the year 2017, but you can apply the same method for other values as well.
let filteredArray = array.filter { Calendar.current.component(.year, from: $0) == 2017 }
let average = (filteredArray.map { $0 as? Double ?? 0 }).reduce(0, +) / Double(filteredArray.count)

Related

How would I parse a list of movies and dates into a list in Swift?

I'm working on a project where I am given a user and a birthdate for them, along with a list of movies and dates that the person has gone to. An example string is something like this: "Participant Name: Example name, Birthdate: 01/11/2000, Spiderman 05/15/2021 07/16/2021 08/17/2021 Avengers Infinity War 05/15/2020 07/16/2020 08/17/2020 The Lorax 01/05/2015" and so on. I know which movies the string will contain, and I know the maximum amount of dates per movie, but I don't know the specific number of times the person would have seen the movie. I've done the following for the birthdate, which works only because the request is always formatted the same way in terms of birthdates:
func FindBirthdate(str: String) -> Date{
//I make sure that the text before birthdate is always converted to DOB
//in other functions, and that the string is converted
//to an array seperated by spaces.
let index = str.firstIndex(of: "DOB:")!
let birthdate = str[index+1]
print(birthdate)
let formatter = DateFormatter()
formatter.dateFormat = "MM/dd/yyyy"
formatter.locale = Locale(identifier: "COUNTRY IDENTIFIER")
formatter.timeZone = TimeZone(identifier: "EXAMPLE")
return formatter.date(from: birthdate) ?? Date()
}
However, as I previously stated, I don't know how many times the user would have seen the movie. The movies will all be in the same order. How would I split the characters in between each movie into dates for that movie? Once again, my problem is that I don't know the number of dates, so how would I get each date and return a list? I've looked into a sort of ForEach statement, but I'm not sure how I could integrate that into a string. This answer suggested that I use regexes, however, this solely focuses on the dates, and not the movies. The string isn't solely made up of dates. I've also taken a look at sample date parsing in Swift, but that is about just single dates. My issue isn't date conversion, it's finding and separating the dates in the first place. On Meta someone also suggested that I try splitting. I have looked at Splitting on Apple Developer, and it seems like a good solution, but I'm not sure what I would split by. To show that sample string again, "Participant Name: Example name, Birthdate: 01/11/2000, Spiderman 05/15/2021 07/16/2021 08/17/2021 Avengers Infinity War 05/15/2020 07/16/2020 08/17/2020 The Lorax 01/05/2015". The movie names will always be only these - they will not ever have numbers. The dates will also always be in teh same MM/DD/YYYY format. The names are immediately before the dates, and there is no separator other than a space.
The reason that this question hasn't been asked before is that though other questions may ask about date parsing or finding substrings, I need to find each individual date for each movie and the movie title - this is trying to find each individual date in the string for each movie.
Does this work for you?
I am assuming you are exactly following the text format in your example.
extension String {
func match(_ regex: String) -> [[String]] {
let nsString = self as NSString
return (try? NSRegularExpression(pattern: regex, options: []))?.matches(in: self, options: [], range: NSMakeRange(0, nsString.length)).map { match in
(0..<match.numberOfRanges).map { match.range(at: $0).location == NSNotFound ? "" : nsString.substring(with: match.range(at: $0)) }
} ?? []
}
}
Then:
func getName(text: String) -> String? {
guard let match = text.match("(?<=Participant Name: )(.*)(?=, Birthdate)").first else { return nil }
return match.first
}
func getBirthDay(text: String) -> String? {
guard let match = text.match("(?<=Birthdate: )(.*)(?=, )").first else { return nil }
return match.first
}
func getMovies(text: String) -> [String: [String]] {
var result: [String: [String]] = [:]
guard let moviesString = text.match("(?<=Participant Name: \(getName(text: text)!), Birthdate: \(getBirthDay(text: text)!), )(.*)").first?.first else { return result }
let asArray = moviesString.components(separatedBy: " ")
var key: String = ""
var values = [String]()
var lastKey: String = ""
for item in asArray {
if !isDate(item) {
values.removeAll()
key += key != "" ? (" " + item) : item
lastKey = key
continue
} else {
key = ""
if var existingValues = result[lastKey] {
existingValues.append(item)
result[lastKey] = existingValues
} else {
result[lastKey] = [item]
}
}
}
return result
}
func isDate(_ string: String) -> Bool {
return !string.match("[0-9]{2}(/)[0-9]{2}(/)[0-9]{4}").isEmpty
}
To test:
let text = "Participant Name: Example name, Birthdate: 01/11/2000, Spiderman 05/15/2021 07/16/2021 08/17/2021 Avengers Infinity War 05/15/2020 07/16/2020 08/17/2020 The Lorax 01/05/2015"
let movies = getMovies(text: text)
print(">>>> \(movies["Spiderman"])")
output:
Optional(["05/15/2021", "07/16/2021", "08/17/2021"])

swift check date is today or tomorrow

I have an integer array which is a collection of today's and tomorrow's dates, I want to separate the integer array based on the type of day
let dateCollection = [
1633722900,
1633730500,
1633754910,
1633758913,
1633820400,
1633824000,
1633827600,
1633831200,
1633834800,
1633838400,
1633842000
]
expected result
let today: [Int] = [
1633722900,
1633730500,
1633754910,
1633758913
]
let tomorrow: [Int] = [
1633820400,
1633824000,
1633827600,
1633831200,
1633834800,
1633838400,
1633842000
]
what should i do to separate them, i have made an extension to convert the integer to date or vice versa, and display it as a time, i already create the date to time extension too
func getTimetringFromINT() -> String {
let date = Date(timeIntervalSince1970: TimeInterval(self))
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "id")
dateFormatter.dateFormat = "HH:mm"
return dateFormatter.string(from: date)
}
selector i made, after convert the date to Time string
You can use Calendar.current.ordinality to compare the day of the year for the various dates. Here's an example that generates dates from today and tomorrow and then filters them back into separate arrays:
let today = Date()
let todayInts = Array(0..<10).map { today.timeIntervalSince1970 + TimeInterval($0) }
print("Today:", todayInts,"\n")
let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: today)!
let tomorrowInts = Array(0..<10).map { tomorrow.timeIntervalSince1970 + TimeInterval($0) }
print("Tomorrow:", tomorrowInts,"\n")
let allInts = todayInts + tomorrowInts
let todayDayOfYear = Calendar.current.ordinality(of: .day, in: .year, for: today)!
let filteredTodayInts = allInts.filter { Calendar.current.ordinality(of: .day, in: .year, for: Date(timeIntervalSince1970: $0)) == todayDayOfYear }
print("Filtered today:", filteredTodayInts,"\n")
let tomorrowDayOfYear = todayDayOfYear + 1
let filteredTomorrowInts = allInts.filter { Calendar.current.ordinality(of: .day, in: .year, for: Date(timeIntervalSince1970: $0)) == tomorrowDayOfYear }
print("Filtered tomorrow:", filteredTomorrowInts,"\n")
An efficient way to do this would be to calculate the int value of midnight between today and tomorrow and then split the array based on that value
let calendar = Calendar.current
var today = [Int]()
var tomorrow = [Int]()
if let midnight = calendar.date(byAdding: .day, value: 1, to: calendar.startOfDay(for: .now))?.timeIntervalSince1970 {
let limit = Int(midnight)
dateCollection.forEach { $0 < limit ? today.append($0) : tomorrow.append($0) }
}
Another approach is to get the start date of the next day (regardless of daylight saving time changes) with the Calendar API and then partition the array
let startOfTomorrow = Calendar.current.nextDate(after: Date(),
matching: DateComponents(hour: 0),
matchingPolicy: .nextTime)!
.timeIntervalSince1970
let splitIndex = dateCollection.partition{ $0 < Int(startOfTomorrow) }
let tomorrowDates = dateCollection[..<splitIndex]
let todayDates = dateCollection[splitIndex...]
To be able to run this declare dateCollection as var
By Getting StartOfDay of any Time, we can classify all Date that shares the same StartOfDay.
extension Date {
var morning: Date { Calendar.current.startOfDay(for: self) }
var nextMorning: Date { Calendar.current.date(byAdding: .day, value: 1, to: self)!.morning }
}
extension Int {
var since1970: Date { Date(timeIntervalSince1970: TimeInterval(self)) }
}
In this case:
let todayMorning = Date().morning // Date().advanced(by: 3600*24 * -19).morning
let tomorrowMorning = Date().nextMorning // Date().advanced(by: 3600*24 * -19).nextMorning
let dateCollection = [ ... ]
let today = dateCollection.filter{ $0.since1970.morning == todayMorning }
let tomorrow = dateCollection.filter{ $0.since1970.morning == tomorrowMorning }

How to find an average for dictionary in Swift?

I have a dictionary, let's say
let gamePrices = [
"Shooter": [40,60,80],
"RTS": [15,35,55]
"RPG": [5,40,70]
]
avgGamePrices(dictionary: [String: Int]) -> Double {
var total = 0;
for int in dictionary {
total += int
}
(i suppose, using of int is wrong here)
And i don't understand how to get each genre avg price, and total avg price (of all 9 values). I don't even know how to get total here, how should i "mention" the ints or strings of a dictionary in for loop?
For default array i'd use
for number in numbers {
total += number
}
let numbersTotal = Int(numbers.count);
let average = total/numbersTotal;
return (Double(average);
To get the average of all prices you need to first get all prices from your dictionary then you can calculate the average. It is not the same as calculating the average of the averages:
let gamePrices = [
"Shooter": [40,60,80],
"RTS": [15,35,55],
"RPG": [5,40,70]]
let allPrices = gamePrices.flatMap(\.value)
let sum = allPrices.reduce(.zero, +)
let average = Double(sum) / Double(allPrices.count) // 44.44444444444444
To get the maximum price for each genre:
let maxPrices: [(genre: String, maxPrice: Int)] = gamePrices.map { ($0, $1.max() ?? .zero)} // [(genre "Shooter", maxPrice 80), (genre "RTS", maxPrice 55), (genre "RPG", maxPrice 70)]
for (genre, max) in maxPrices {
print("Genre:", genre, "Max price:", max)
}
If you search a total AVG, so something like this:
func avgGamePrices(dictionary: [String: [Int]]) -> Double {
var total = 0;
var countElem = 0
for obj in dictionary {
countElem += obj.value.count
for v in obj.value {
total += v
}
}
return Double(total / countElem)
}
calling:
avgGamePrices(dictionary: gamePrices)
otherwise for each key:
func avg(for key: String, dictionary: [String: [Int]]) -> Double {
var total = 0;
let countElem = (dictionary[key] ?? []).count
for v in (dictionary[key] ?? []) {
total += v
}
return Double(total / countElem)
}
and you can call in this way (passing a single key of your dictionary like a param):
avg(for: "RTS", dictionary: gamePrices)

Converting String array into Int Array Swift

After converting the String values into Int values in an array, when I print to the logs, all I get is: [0, 0, 0, 0] when the output should be: ["18:56:08", "18:56:28", "18:57:23", "18:58:01"]
(without the quotations and the : colon).
I'm converting the string array, directly after the values have been added to the string array. I'm assuming that I'm not converting the values at the right time, or that my methods are placed wrong and that's why I get the 0 0 0 0 output.
Here is my ViewController code:
class FeedTableViewController: UITableViewController {
var productName = [String]()
var productDescription = [String]()
var linksArray = [String]()
var timeCreatedString = [String]()
var minuteCreatedString = [String]()
var intArray = Array<Int>!()
override func viewDidLoad() {
super.viewDidLoad()
var query = PFQuery(className: "ProductInfo")
query.findObjectsInBackgroundWithBlock ({ (objects, error) -> Void in
if let objects = objects {
self.productName.removeAll(keepCapacity: true)
self.productDescription.removeAll(keepCapacity: true)
self.linksArray.removeAll(keepCapacity: true)
self.timeCreatedString.removeAll(keepCapacity: true)
for object in objects {
self.productName.append(object["pName"] as! String)
self.productDescription.append(object["pDescription"] as! String)
self.linksArray.append((object["pLink"] as? String)!)
// This is where I'm querying and converting the date:
var createdAt = object.createdAt
if createdAt != nil {
let date = NSDate()
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MM/dd/YYY/HH/mm/ss"
let string = dateFormatter.stringFromDate(createdAt as NSDate!)
var arrayOfCompontents = string.componentsSeparatedByString("/")
self.timeCreatedString.append("\(arrayOfCompontents[0]) \(arrayOfCompontents[1]) \(arrayOfCompontents[2])")
self.minuteCreatedString.append("\(arrayOfCompontents[3]):\(arrayOfCompontents[4]):\(arrayOfCompontents[5])")
self.intArray = self.minuteCreatedString.map { Int($0) ?? 0}
print("INT ARRAY \(self.intArray)")
print(self.minuteCreatedString.map { Int($0) ?? 0})
print(self.minuteCreatedString)
}
self.tableView.reloadData()
}
}
})
}
When I tried this in another ViewController in the viewDidLoad method without Parse/queries happening, I get the correct output: a converted array of Ints. I'm assuming there's an issue of when and where i'm converting the Strings into Ints.
In what order/where should I convert the array of Strings into an array of Ints? Should I convert from Date to Int instead? If so, how do I do that? Am i doing something else wrong? I'm awfully confused....
Any help is very much appreciated!
If you have an NSDate object anyway, you can create the date string with the date formatter
let createdAt = NSDate() // or give date object
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "HH:mm:ss"
let string = dateFormatter.stringFromDate(createdAt) // "18:56:08"
Or if you want the integer values of hours, minutes and seconds, use NSDateComponents:
let comps = NSCalendar.currentCalendar().components([.Hour, .Minute, .Second], fromDate: createdAt)
let hour = comps.hour
let minute = comps.minute
let seconds = comps.second
let intArray = [hour, minute, second]

How to search the minimum positive value in an array?

In my code, checkTheNextTime() function's array contains the strings 00.00 to 23.59. By writing this function I want to find the nearest future time. But when I tried with timeTable(shown in code) it returns 23.30 instead of 23.32(Now is 22.24). I guess the compiler search the array right to left. How can I find the nearest future time?
var timeTable = ["09.00","10.20","10.35","11.55","12.00","12.40","13.20","14.40","14.50", "23.00", "23.30", "23.31", "23.32"]
func checkTheNextTime(array array: Array<String>) -> String{
var nextTime: String?
for index in array {
let generatedString:String = getTimeAsMinToCheck(finalTime: index)
let indexInt = Int(generatedString)
if indexInt > 0{
nextTime = index
}
}
return nextTime!
}
func getTimeAsMinToCheck(finalTime finalTime: String) -> String{
let date = NSDate()
let formatter = NSDateFormatter()
formatter.timeStyle = NSDateFormatterStyle.NoStyle
formatter.dateStyle = NSDateFormatterStyle.ShortStyle
let now = formatter.stringFromDate(date)
formatter.locale = NSLocale.systemLocale()
formatter.dateFormat = "M/dd/yy HH.mm"
let datetofinish = formatter.dateFromString("\(now) \(finalTime)")
let finishDate: NSDate = datetofinish!
let secondsFromNowToFinish = finishDate.timeIntervalSinceNow
let minutes = Int(secondsFromNowToFinish / 60)
return String(minutes)
}
Assuming 23 is the right answer (its not clear from the comments above), here a a solution using swift 2.0 and closures
map your timeTable array into an array of delta's from the current
time (invalid entries are mapped to 0)
add the minimum delta to the time now
let timeNow: Float = 22.24
let timeTable = ["09.00","10.20","10.35","11.55","12.00","12.40","13.20","14.40","14.50", "23.00", "23.30", "23.31", "23.32"]
let minDelta = timeTable
.map { Float(NSNumberFormatter().numberFromString($0) ?? 0.0) - timeNow }
.filter { $0 > 0 }
.minElement()
let nextTime = (minDelta ?? 0) + timeNow
print(nextTime) // 23.0
This code should work for your requirement:
Done in Swift 2.0:
var timeTable = ["09.00","10.20","10.35","11.55","12.00","12.40","13.20","14.40","14.50", "23.00", "23.30", "23.31", "23.32"]
func checkTheNextTime(array array: Array<String>) -> String{
let currentTime:String = getTimeAsMinToCheck(finalTime: "23.24") // set this value by calculating from current time
let currentTimeInt = Int(currentTime)// Int value of currentTime
var nextTime: String? //this will hold the nearest future value
var minDiff: Int = 24*60 //lets start with maximum value
for index in timeTable {
let generatedString:String = getTimeAsMinToCheck(finalTime: index)
let indexInt = Int(generatedString)
if (indexInt > currentTimeInt) { //checking for future time only
let timeDiff = indexInt - currentTimeInt // this will be positive
if (timeDiff < minDiff) {
minDiff = timeDiff //update minDiff as timeDiff is less than minDiff
nextTime = index
}
}
}
return nextTime!
}

Resources