Find index of Custom List - Kotlin - arrays

I have a list
val SongList = [SongInfo(name="Dark Star", rawId="2131689475", time=1:30), SongInfo(name="Can't let go", rawId="2131689474", time=1:24), , SongInfo(name="Big Digits", rawId="2131689473", time=0:49), SongInfo(name="What's Mine", rawId=2131689478]
and to get the song name I do this
val song = songList[0].name
shuffling song
songList.shuffle()
how do I find an index of song, after shuffling the list?

My code isn't the best solution but it finds the index, and return the song name by index.
data class SongInfo(var name: String, var rawId: String, var time: String)
fun main() {
val songList = listOf(
SongInfo(name = "Dark Star", rawId = "2131689475", time = "1:30"),
SongInfo(name = "Can't let go", rawId = "2131689474", time = "1:24"),
SongInfo(name = "Big Digits", rawId = "2131689473", time = "0:49"),
SongInfo(name = "What's Mine", rawId = "2131689478", time = "22:31")
)
val song = songList[0].name
val shuffledSongList = songList.shuffled()
println(shuffledSongList)
var index = 0
while (true) {
if (shuffledSongList[index].name == song) break
index++
}
println(shuffledSongList[index].name)
}

Related

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

How to group the same element in array and sum it in Swift?

How to filter duplicate category element into different array and count their amount?
This is the format, record is from the core data.
var record = [Record]()
[<Record:...; data: {accountbook = "MyBook";
amount = "10.50";
category = "A";
id = 1;
},<Record:...; data: {accountbook = "MyBook";
amount = "5.50";
category = "B";
id = 2;
},<Record:...; data: {accountbook = "MyBook";
amount = "4.50";
category = "B";
id = 3;
}]
What I want
var category = ["A", "B"] //success
var total = [10.50, 10.00]
This is what I do for finding the category, and it works but how to group the same category and sum the total?
var category =[String]()
for categoryObject in record{
if let categoryItem = categoryObject.category{
category.append(categoryItem)
}
}
//I tried this code to group the same category but fail.
let result = Set(record).map{ category in return record.filter{$0 == category} }
Another way is this. but how if I have A-Z category? it will have very long code..Is there any way can detect the same value and split it to different array so that I can sum it by category.
categoryFilter = record.filter { $0.category!.contains("A") }
First group your record object by category like this
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 you will get your distinct arrays based on category like this
var records : [Record] = []// your objects
let distinctRecords = records.group(by: {$0. category})
Now you can use reduce to calculate sum of values of that category
for items in distinctRecords{
let sum = items.reduce(0.0){$0.0 + $1. amount ?? 0.0}// assuming you have float values in your amount
print(items)// do whatever you want to do with your distinct array
print(" \(sum)")
}
#Wan Jern I have written a piece of code, you can try this one. Hopefully, it will work.
var category = [String]()
var totalArr = [CGFloat]()
for categoryObject in record{
if let categoryItem = categoryObject.category{
if !category.contains(categoryItem) {
category.append(categoryItem)
totalArr.append(categoryObject.amount)
} else {
let index = category.index(of: categoryItem)
let itemAtIndex = category[index]
let itemAtIndex = itemAtIndex + categoryObject.amount
totalArr.insert(itemAtIndex, at: index)
}
}
}
Do you have your record struct in a class model?
like my data model selected from sqlite:
//Data model
import Foundation
import UIKit
class scoreModel: NSObject {
var lessonName:String = String()
var lessonCode:String = String()
var creditPoint:Double = Double()
var totalStudentNumber:Int = Int()
var teacherName:String = String()
var semesterName:String = String()
var scoreValue:String = String()
var studentCount:Int = Int()
}
If the answer is yes, we can use pointer in array and repeat while loop to do this manually.
Like my code:
let mysql = ""
let dataArray = SQLiteManager.shareInstance.queryDB(sql:mysql)
var i = 0
while i<dataArray.count-1
{
var scoreArray = [Dictionary<String, Int>]()
var range = 0
var test = 0
test = i
//print("pointer i is'\(test)'")
while ((dataArray[test]as! scoreModel).lessonCode == (dataArray[test+range]as! scoreModel).lessonCode && (test+range)<dataArray.count-1)
{
let key = (dataArray[test+range]as! scoreModel).scoreValue
let value = (dataArray[test+range]as! scoreModel).studentCount
var dict: [String: Int] = [String: Int]()
dict[key] = value
scoreArray.append(dict)
//print("working pointer is'\(test+range)'")
range = range+1
}
//transfer array
let model:resultModel = resultModel()
model.lessonName = (dataArray[test]as! scoreModel).lessonName
model.lessonCode = (dataArray[test]as! scoreModel).lessonCode
model.creditPoint = (dataArray[test]as! scoreModel).creditPoint
model.semesterName = (dataArray[test]as! scoreModel).semesterName
model.teacherName = (dataArray[test]as! scoreModel).teacherName
model.totalStudentNumber = (dataArray[test]as! scoreModel).totalStudentNumber
model.scoreArray = scoreArray
resultArray.add(model)
i = i+range
//print("range is'\(range)'")
}
Make the Records as Hashable with "category" as the unique in stubs function
struct Record: Hashable {
var accountbook = ""
var category = ""
var amount = 0.0
// added from stubs of Hashable
var hashValue: Int { return category.hashValue }
static func ==(lhs: Record, rhs: Record) -> Bool {
return lhs.category == rhs.category
}
}
Then filter the unique categories
let categories = Set(record).map { $0.category }
print(categories) // ["B", "A"]
And make a sum of each category
let totals = categories.map { c in
record.filter { $0.category == c }.map{ $0.amount }.reduce(0, +)
}
print(totals) // get the sums as [10.0, 10.5]

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

Swift: using a for loop to insert items. At the end, when i query it shows only the value which was inserted last at each index

In Swift playground, I'm using a for loop to insert items. During the loop it seems to be adding the right values. At the end of the for loop, when the array is all filled up, when I query, it shows only the value which was inserted last at each index.
New to swift, so I guess am doing something really stupid here... Need help...
The code is as below:
class card {
var suit: String = ""
var rank: String = ""
}
var card1 = card()
var deck = [card]()
var playingCard = card()
var suits = ["♠︎","♣︎","♥︎","♦︎"]
var ranks = ["A","K","Q","J","10","9","8","7","6","5","4","3","2"]
let countRanks = ranks.count
let countSuits = suits.count
var ctr = 0
for i in 0..<countSuits {
for j in 0..<countRanks {
playingCard.rank = ranks[j]
playingCard.suit = suits[i]
deck.insert(playingCard, atIndex: 0)
println("\(deck[ctr].suit)"+"\(deck[ctr].rank)")
ctr++
}
}
let x = deck.count
for i in 0..<x {
println("\(deck[i].rank)"+"\(deck[i].suit)")
}
class instances are reference types. You need to create a new card instance for each iteration. Move the line var playingCard = card() right after the for j in 0..<countRanks line and change var to let
You need to initialise a new instance of playingCard each iteration:
for i in 0..<countSuits {
for j in 0..<countRanks {
var playingCard = card()
...
}
}
I would also suggest you add an init method for your card class to make it easier to initialise:
class Card {
var suit: String
var rank: String
init(suit: String, rank: String) {
self.suit = suit
self.rank = rank
}
}
...
var playingCard = Card(suit: suits[i], rank: ranks[j])

Swift How to apply toggle sort (ascending, descending) both of array of custom objects

I have a class named student and it contains three properties.
class Student {
var name = String()
var age = Int()
var school = String()
}
var studentrecord : Array = []
var student = Student()
student.name = "John"
student.age = 15
student.school = "abc"
var student1 = Student()
student1.name = "Tom"
student1.age = 14
student1.school = "pqr"
var student2 = Student()
student2.name = "Alex"
student2.age = 16
student2.school = "xyz"
studentrecord.append(student)
studentrecord.append(student1)
studentrecord.append(student2)
How can I sort student record array by "name" ascending and descending both (Toggle Sort)?
You can control the sorting order by passing in a different sorting function:
func acs(s1:Student, s2:Student) -> Bool {
return s1.name < s2.name
}
func des(s1:Student, s2:Student) -> Bool {
return s1.name > s2.name
}
var n1 = sorted(studentrecord, acs) // Alex, John, Tom
var n2 = sorted(studentrecord, des) // Tom, John, Alex
You must write your GUI code such that it can decide which sorting function to use when a user toggles the button.
The simplest way in my opinion is:
items.sort(ascending ? {$0.name < $1.name} : {$0.name > $1.name})
Here is a version which works just like sort method,
extension Array{
func toggleSort(predicate: (T, T) -> Bool, reverse: Bool ) -> [T]{
var a = self
a.sort({
(obj1, obj2) in
if reverse{
return !predicate(obj1, obj2)
}
return predicate(obj1, obj2)
})
return a
}
}
var sorted = studentrecord.toggleSort({ $0.name < $1.name}, reverse: false)
var reverseSorted = studentrecord.toggleSort({ $0.name < $1.name}, reverse: true)

Resources