How to search the minimum positive value in an array? - arrays

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

Related

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)

How can I compare times in an array?

I am building a bus arrival time application. It needs a function to which two arguments are passed: a current stop which the user is at, and a destination stop which the user wants to go to. The arrival times are hard-coded, and there are no "live arrival" times of any sort.
The problem I am having is trying to compare the times and work out when the next bus is arriving. The times are stored in an array and cannot be changed.
For example, if the array is as follows: ["08:00", "23:00", "01:00", "04:00"]
and also, let us say the current time is "16:00", the time returned by the function is "23:00". Simple, right? I have coded this bit already with an extension class which can be found in my Pastebin.
However, the problem arises when the time passes into "the next day", so if the time is "00:00", I don't know how to return "01:00", since my function will only return the first time in the array ("08:00") since "00:00" is lower than "08:00" .
import UIKit
// Replace the variable currentTime with a value of "00:00" and see how my function returns "08:17" which is wrong. I want the function to return "00:03" since it is the next time in the array. Or if the current time is "01:00" it should return "01:03". BUT, if the current time is "01:04" or greater, it should return the first time in the array "08:17"
// Hard coded bus arrival times
let array: [String] = ["08:17", "08:37", "08:57", "09:21", "09:51", "10:21", "10:51", "11:21", "11:51", "12:21", "12:51", "13:21", "13:51", "14:21", "14:51", "15:21", "15:51", "16:21", "16:51", "17:21", "17:51", "18:21", "18:51", "19:21", "19:51", "21:03", "22:03", "23:03", "00:03", "01:03"]
// Date object stuff
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
let date = Date()
let calender = Calendar.current
let components = calender.dateComponents([.year, .month, .day, .hour, .minute, .second], from: date)
let year = components.year
let month = components.month
let day = components.day
let hour = components.hour
let minute = components.minute
let second = components.second
// Returns current time in a "HH:mm:ss" format
func getTimeString() -> String {
var minuteAsString = String(minute!)
if minute! < 10 {
minuteAsString = "0\(String(minute!))"
}
let timeString = String(hour!) + ":" + minuteAsString
return timeString
}
func getNextBus(_ currentStop: String,_ destinationStop: String) -> String {
var listToUse: [String] = []
let currentTime = getTimeString()
print(currentTime)
switch (currentStop, destinationStop) {
case ("stop1", "stop2"):
listToUse = array
default: ()
}
print(listToUse)
for busTime in listToUse {
if currentTime < busTime {
return busTime
}
}
return "Error! No time found."
}
print(getNextBus("stop1", "stop2"))
// Time class which allows times to be compared and equated
class Time: Comparable, Equatable {
init(_ date: Date) {
//get the current calender
let calendar = Calendar.current
//get just the minute and the hour of the day passed to it
let dateComponents = calendar.dateComponents([.hour, .minute], from: date)
//calculate the seconds since the beggining of the day for comparisions
let dateSeconds = dateComponents.hour! * 3600 + dateComponents.minute! * 60
//set the varibles
secondsSinceBeginningOfDay = dateSeconds
hour = dateComponents.hour!
minute = dateComponents.minute!
}
init(_ hour: Int, _ minute: Int) {
//calculate the seconds since the beggining of the day for comparisions
let dateSeconds = (hour * 3600 + minute * 60)
//set the variables
secondsSinceBeginningOfDay = dateSeconds
self.hour = hour
self.minute = minute
}
var hour : Int
var minute: Int
var date: Date {
//get the current calender
let calendar = Calendar.current
//create a new date components.
var dateComponents = DateComponents()
dateComponents.hour = hour
dateComponents.minute = minute
return calendar.date(byAdding: dateComponents, to: Date())!
}
/// the number or seconds since the beginning of the day, this is used for comparisions
public let secondsSinceBeginningOfDay: Int
static func < (lhs: Time, rhs: Time) -> Bool {
return lhs.secondsSinceBeginningOfDay < rhs.secondsSinceBeginningOfDay
}
}```
Suppose we have this array:
let array = ["08:00", "23:00", "01:00", "04:00"]
A more convenient way of dealing with "bus times" would be to define a struct like so:
struct BusTime: Comparable, CustomStringConvertible {
let hour : Int
let minute : Int
static func < (lhs: BusTime, rhs: BusTime) -> Bool {
return (lhs.hour, lhs.minute) < (rhs.hour, rhs.minute)
}
var description: String {
get {
let formatter = NumberFormatter()
formatter.minimumIntegerDigits = 2
return formatter.string(for: hour)! + ":" + formatter.string(for: minute)!
}
}
}
N.B: In the rest of the answer I'll be force-unwrapping for brevity)
Let's create a sorted array of BusTimes:
let busTimes: [BusTime] = array.map { str in
return BusTime(hour: Int(str.prefix(2))!, minute: Int(str.suffix(2))!)
}
var sortedBusTimes = busTimes.sorted()
Let's also define a variable nextBus which represents the next bus time:
var nextBus: BusTime = sortedBusTimes[0]
Now, let's create a time that corresponds to say the current date:
let nowComps = Calendar.current.dateComponents([.hour, .minute], from: Date())
let now = BusTime(hour: nowComps.hour!, minute: nowComps.minute!)
With binary search, we'll be able to find the next bus time in O(log(n)):
var low = sortedBusTimes.startIndex
var high = sortedBusTimes.endIndex
while low < high {
let middle = low + (high - low)/2
let middleTime = sortedBusTimes[middle]
if middleTime == now {
low = middle
break
} else if middleTime < now {
low = middle + 1
} else if now < middleTime {
high = middle
}
}
if low != sortedBusTimes.endIndex, high != 0 {
nextBus = sortedBusTimes[low]
}
The definition middle could be simpler this way:
let middle = low + (high - low)/2
But take this article into consideration.
Finally, let's check:
print(nextBus)
At the time of writing this answer, it is 17:52. So the result printed in the console is:
23:00

Swift - Joining two tuple arrays based on their values

I've a scenario where I have two arrays of tuples.
tuple1 = [(score1, index1), (score2, index2), (score3, index3)]
tuple2 = [(date1, index1), (date2, index2), (date3, index4)]
I want to get the scores and dates from these tuples and create a new array of tuples such that it contains the score and date having the same index like this:
tuple3 = [(score1, date1), (score2, date2)]
How can I implement this? What is the best practice to follow in this scenario?
Note: The arrays can be of different sizes
My implementation for the scenario is as follows:
var tuple3 = [(Double, Date)]()
for (i,psa) in tuple1.enumerated() {
let date = tuple2.filter({ $0.1 == i })
if date.count == 1 {
let newTuple = (tuple1.0, date[0].0)
tuple3.append(newTuple)
}
}
Is this a right way to do it or is there a better one?
You can try
let v1 = [("1","2"),("3","4")]
let v2 = [("1A","2A"),("3A","4A")]
let res = zip(v1,v2).map { ($0.0 , $1.0) } // [("1", "1A"), ("3", "3A")]
print(res)
let tuple1 = [("score1", "index1"), ("score2", "index2"), ("score3", "index3")]
let tuple2 = [("date1", "index1"), ("date2", "index2"), ("date3", "index4")]
let t2Dict = tuple2.reduce(into: [String:String]()) { (dict, args) in
let (date, index) = args
dict[index] = date
}
let tuple3 = tuple1.compactMap { args -> (String, String)? in
let (score, index) = args
guard let date = t2Dict[index] else { return nil }
return (score, date)
}
It's not as pretty as the others, but it's far more efficient to collapse one of the tuples into a dictionary first.
This should do the trick:
let tuple3 = tuple1.compactMap({ (scoreInTuple1, indexInTuple1) -> (String, String)? in
if let tupleIn2 = tuple2.first(where: { (scoreInTuple2, index2InTuple2) in index2InTuple2 == indexInTuple1 }){
return (scoreInTuple1, tupleIn2.0)
}
return nil
})
(String, String) should be change to the real type/class of score & date1.
Also, index2InTuple2 == indexInTuple1 might be changed also if it's custom type/class which might not be Equatable.
With sample code before:
let tuple1 = [("score1", "index1"), ("score2", "index2"), ("score3", "index3")]
let tuple2 = [("date1", "index1"), ("date2", "index2"), ("date3", "index4")]
Debug log after:
print("tuple3: \(tuple3)")
Output:
$> tuple3: [("score1", "date1"), ("score2", "date2")]
This might be what you're looking for:
let tuple1 = [("score1", "index1"), ("score2", "index2"), ("score3", "index3")]
let tuple2 = [("date1", "index1"), ("date2", "index2"), ("date3", "index4")]
let filtered = tuple1.filter {tuple2.map{$0.1}.contains($0.1)}
let result = filtered.map {tuple in
return (tuple.0, tuple2.first(where: {$0.1 == tuple.1})!.0)
}
print (result) // [("score1", "date1"), ("score2", "date2")]
I'm using Strings for simplicity, just make sure you are using Equatable objects in your tuples.

Swift display previous entered item in array

I am trying to display the pervious entered exercise attributes in the current workout. If I go to the pervious workout everything shows up but when I go to my current workout the previous exercise attributes don't show and the date label only shows today's date and not the previous workout date. Here are the two functions for the issue. Let me know if i need to post more.
func lastWorkout() -> Workout? {
if let client = currentClient(), let currentWorkout = currentWorkout(), let workouts = client.workouts as? Set<Workout> {
// get all this client's workouts in cronological order
let sortedWorkouts = workouts.sorted { (one, two) -> Bool in
let scheduledTimeOfOne = one.appointment?.scheduled ?? Date(timeIntervalSince1970: 0)
let scheduledTimeOfTwo = two.appointment?.scheduled ?? Date(timeIntervalSince1970: 0)
return scheduledTimeOfOne > scheduledTimeOfTwo
}
// get the index of this workout
let indexOfTodaysWorkout = sortedWorkouts.index(of: currentWorkout) ?? 0
// go back one workout to find the last workout completed
let lastWorkout: Workout? = (indexOfTodaysWorkout - 1) < 0 ? nil : sortedWorkouts[indexOfTodaysWorkout - 1]
// and return
return lastWorkout
}
return nil
}
/// Last Exercise Info to load previous exercise data
func lastExercise() -> Exercise? {
guard let selectedExercise = currentExerciseInfo() else{
return nil
}
if let exercises = lastWorkout()?.exercises as? Set<Exercise>, let pastExercise = exercises.first(where: { $0.exerciseInfo == selectedExercise }) {
return pastExercise
}
return nil
}
So the array count was off in last workout function. Here is what the working function looks like. I am still not displaying the proper date. it just gives today's date.
func lastWorkout() -> Workout? {
if let client = currentClient(), let currentWorkout = currentWorkout(), let workouts = client.workouts as? Set<Workout> {
// get all this client's workouts in cronological order
let sortedWorkouts = workouts.sorted { (one, two) -> Bool in
let scheduledTimeOfOne = one.appointment?.scheduled ?? Date(timeIntervalSince1970: 0)
let scheduledTimeOfTwo = two.appointment?.scheduled ?? Date(timeIntervalSince1970: 0)
return scheduledTimeOfOne > scheduledTimeOfTwo
}
// get the index of this workout
let indexOfTodaysWorkout = sortedWorkouts.index(of: currentWorkout) ?? 0
// go back one workout to find the last workout completed
let lastWorkout: Workout? = sortedWorkouts.count < 2 ? nil : sortedWorkouts[indexOfTodaysWorkout + 1]
// and return
return lastWorkout
}
return nil
}

Count Items in an Array of Arrays?

If I have an object that is declared as
let compoundArray = [Array<String>]
is there a property that would give me the number of strings in all the arrays contained in compoundArray?
I can do it by adding up all the items in each array within:
var totalCount = 0
for array in compoundArray {
totalCount += array.count }
//totalCount = total items in all arrays within compoundArray
But that seems clunky and it seems that swift would have a property/method of Array to do this, no?
Thanks!
You can add the nested array counts with
let count = compoundArray.reduce(0) { $0 + $1.count }
Performance comparison for large arrays (compiled and run on a MacBook Pro in Release configuration):
let N = 20_000
let compoundArray = Array(repeating: Array(repeating: "String", count: N), count: N)
do {
let start = Date()
let count = compoundArray.joined().count
let end = Date()
print(end.timeIntervalSince(start))
// 0.729196012020111
}
do {
let start = Date()
let count = compoundArray.flatMap({$0}).count
let end = Date()
print(end.timeIntervalSince(start))
// 29.841913998127
}
do {
let start = Date()
let count = compoundArray.reduce(0) { $0 + $1.count }
let end = Date()
print(end.timeIntervalSince(start))
// 0.000432014465332031
}
You can use joined or flatMap for that.
Using joined
let count = compoundArray.joined().count
Using flatMap
let count = compoundArray.flatMap({$0}).count
Since you are asking for a property, I thought I'd point out how to create one (for all collections, while we're at it):
extension Collection where Iterator.Element: Collection {
var flatCount: Int {
return self.reduce(0) { $0 + $1.count } // as per Martin R
}
}
Making this recursive seems to be an interesting exercise.

Resources