How to separate an variable array into smaller arrays using swift - swift

I have a question I cannot figure out. Is there an easier way to separate an array into other arrays. In the code below I do a Trace route and filter out everything except the ping rates.
Then I break down that array into individual nodes. The only downside is if it only takes 2 hops then I get an error, "fatal error: Index out of range." because I have it set up to create 15 node arrays. I have also tried putting ..
let count = pingRate.count
if count < 15 {
aNodeArray += pingRate[14]
}
Is there an easier way? I wish there was a way to do a .count and then populate the Int the is required on each node.
let task = Process()
task.launchPath = "/bin/sh"
task.arguments = ["-c", "traceroute -nm 15 -q 1 8.8.8.8"]
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = NSString(data: data, encoding: String.Encoding.utf8.rawValue) as! String
var array = output.components(separatedBy: " ")
array = array.filter({ $0.contains(".")})
let pingRate: [[Double]] = array.enumerated().filter({ index, _ in
index % 2 != 0
}).map { [Double($0.1)!] }
let aNode = pingRate[0]
let bNode = pingRate[1]
let cNode = pingRate[2]
let dNode = pingRate[3]
let eNode = pingRate[4]
aNodeArray += aNode
bNodeArray += bNode
cNodeArray += cNode
dNodeArray += dNode
eNodeArray += eNode
Once I get that done then I sort and find the min, max, and average.
let sorted = aNodeArray.sorted()
let numbers = sorted
print(numbers)
let min = numbers[0]
var sum = 0 as Double
for number in numbers {
sum += number
}
let mean = Double(sum) /
Double (numbers.count)
let x = mean
let avg = round(1000.0 * x) / 1000.0
let maxBR = numbers.last! as Double
let max = round(1000.0 * maxBR) / 1000.0
print(min)
print(max)
print(avg)
Edit
I did stumble upon chunks and stride from...
Swift: what is the right way to split up a [String] resulting in a [[String]] with a given subarray size?
I am trying to figure a way to implement that for my needs
Edit
And looking at that code. Can anyone tell me why I get the Color spinning wheel until the process() is finished?

I know that there are more efficient algorithms to calculate your ping rate statistics but the code below should do the job.
let task = Process()
task.launchPath = "/bin/sh"
task.arguments = ["-c", "traceroute -nm 15 -q 1 8.8.8.8"]
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
guard
let pingRates = String(data: data, encoding: .utf8)?
.components(separatedBy: " ")
.filter({ $0.contains(".") })
.flatMap({ Double($0) }),
pingRates.count > 0
else {
return
}
print("ping rate(min) = \(pingRates.min()!.rounded())")
print("ping rate(max) = \(pingRates.max()!.rounded())")
print("ping rate(avg) = \((pingRates.reduce(0, +) / Double(pingRates.count)).rounded())")
The trick to eliminating the IP addresses in your output is to let Double.init fail on them and then flatMap will remove those nil values.

Related

How can I execute code outside of my for loop (which needs to wait until the loop has finished retrieving data from Firebase Database)?

I am trying to show a table view which shows institutions by car travel times.
The query loops through a list of schools in the database to retrieve their coordinates
Calculate the distance from the user's current location
If the distance is less than 20 miles, calculate the eta time using MKDirections()
Once the eta times have been calculated, append the relevant information to the table view's array, sort the array by eta times and only keep the top 10
The issue here is, there are often many institutions within 20 miles of the user's current location and MKDirections() limits the amount of requests you can make to calculate eta times in a given timeframe. So, quite often my table view is unable to show the eta times.
The obvious solution for me is to sort and trim the array before I calculate the eta times (thus avoiding the too many calculate eta requests issue).
What I tried:
Append the data inside the for loop to 'Array1'
Sort and trim Array1
Outside/after the for loop, I loop through Array1 to calculate eta times and then append this data to Array2 which will be the array that feeds the tableview
I found that this doesn't work because the code outside/after the for loop is executed before the for loop is finished.
let schoolDB = Database.database().reference().child("Schools")
schoolDB.observe(.value, with: { (snapshot) in
self.subjectTimesArray.removeAll()
for child in snapshot.children {
let schoolSnapshot = child as! DataSnapshot
let approvalString = schoolSnapshot.childSnapshot(forPath: "Approved").value as! String
if approvalString == "Yes" {
let schoolID = schoolSnapshot.key
let schoolName = schoolSnapshot.childSnapshot(forPath: "Name").value as! String
let schoolLatitude = schoolSnapshot.childSnapshot(forPath: "Latitude").value as! Double
let schoolLongitude = schoolSnapshot.childSnapshot(forPath: "Longitude").value as! Double
let schoolLocation = CLLocation(latitude: schoolLatitude, longitude: schoolLongitude)
let distance = schoolLocation.distance(from: self.userLocation) / 1600
if distance < 20 {
let subjectDB = Database.database().reference().child("Subject/" + schoolID + "/" + date)
subjectDB.observeSingleEvent(of: .value, with: { (subjectSnapshot) in
let startTime = subjectSnapshot.childSnapshot(forPath: "Maths/Start").value as! String
let userPlacemark = MKMapItem(placemark: MKPlacemark(coordinate: userLocation.coordinate))
let schoolPlacemark = MKMapItem(placemark: MKPlacemark(coordinate: schoolLocation.coordinate))
let directionsRequest = MKDirections.Request()
directionsRequest.source = userPlacemark
directionsRequest.destination = schoolPlacemark
directionsRequest.requestsAlternateRoutes = false
directionsRequest.transportType = .automobile
let directions = MKDirections(request: directionsRequest)
directions.calculateETA(completionHandler: { (response, error) in
if let seconds = response?.expectedTravelTime {
let minutes = seconds / 60
self.subjectTimesArray.append(.init(schoolID: schoolID, schoolName: schoolName, distance: distance, eta: Int(minutes), subjectStart: startTime))
}
self.subjectTimesArray.sort(by: {$0.eta < $1.eta})
if self.subjectTimesArray.count > 10 {
self.subjectTimesArray.removeLast(self.subjectTimesArray.count - 10)
}
self.subjectTimesTableView.reloadData()
})
})
}
}
}
})
You can make a function that calculates the distance with a parameter as the school and call it when the distance is less than 20 miles, or append each school in the database to a new array if its under 20 miles and calculate off that array. If you do the second option it would only be looping through schools that are under 20 miles, I would recommend the first option.

swift replacingOccurrences regular expression with number calculation

I have [size=some_number%]some_text[/size], and I want to replace it with
<font size="some_number*font_size">sometext</font>
where font_size is some int variableI know how to extract some_number and some_text with regular expression, but how can I do the multiplie calculation ? Is there a way to do it in swift justing using replacingOccurrences?
#"\[size=(d+)%\]([\s\S]*?)\[\/size\]"#
Is there a way to do it in swift justing using replacingOccurrences?
ICU regular expressions don't do math. You'll have to deal with these one at a time, doing the search, performing the calculation, and then doing the replace for that occurrence, repeating that in a loop. Loop in reverse to avoid index-shifting issues.
For instance:
var s = """
yo[size=6%]ooo[/size]heyho[size=10%]yyy[/size]ha
"""
let font_size = 10
let reg = try! NSRegularExpression(pattern: "\\[size=(\\d+)%\\]([\\s\\S]*?)\\[\\/size\\]", options: [])
let matches = reg.matches(in: s, options: [], range: NSRange(location: 0, length: s.utf16.count))
let rev = matches.reversed() // work backwards for replacement
for match in rev {
let r = match.range
let size = s[Range(match.range(at:1), in:s)!]
let text = s[Range(match.range(at:2), in:s)!]
let prefix = "<font size=\""
let num = String(Int(size)!*font_size)
let rest = "\">" + text + "</font>"
s = s.replacingCharacters(in: Range(r, in:s)!, with: prefix + num + rest)
}
print(s)
Now s is
yo<font size="60">ooo</font>heyho<font size="100">yyy</font>ha

When rounding swift double it shows different numbers

When I got two numbers, like 5.085 and 70.085. My code rounds the first number to 5.09, but the second one it goes to 70.08. For some reason, when making let aux1 = aux * 100 the value goes to 7008.49999999. Any one have the solution to it?
Here is my code:
let aux = Double(value)!
let aux1 = aux * 100
let aux2 = (aux1).rounded()
let number = aux2 / 100
return formatter.string(from: NSNumber(value: number))!
If you want to format the Double by rounding it's fraction digits. Try't:
First, implement this method
func formatDouble(_ double: Double, withFractionDigits digits: Int) -> String{
let formatter = NumberFormatter()
formatter.maximumFractionDigits = digits
let string = formatter.string(from: (NSNumber(floatLiteral: double)))!
return string
/*if you want a Double instead of a String, change the return value and uncomment the bellow lines*/
//let number = formatter.number(from: string)!
//return number.doubleValue
}
after, you can call't that way
let roundedNumber = formatDouble(Double(value)!, withFractionDigits: 2)

Swift read large file into an array

I'm trying to read a file into an array of strings in the Playground
let path = XCPlaygroundSharedDataDirectoryURL.appendingPathComponent("test.txt")
let data = try Data(contentsOf: path!)
let returnData = String(data: data, encoding: .utf8)
var matrixData = returnData!.components(separatedBy: "\n").first!
let rows = Int ( matrixData.components(separatedBy: " ").first! )!
let columns = Int( matrixData.components(separatedBy: " ")[1] )!
let realData = returnData!.components(separatedBy: "\n").dropFirst().joined()
realData.count
let inputString = realData.components(separatedBy: " ")
The first two numbers in the input file indicate that it is a grid of A * B.
The "real data" indicates that the array has begun.
For a small input file (4 * 4) realdata.count reveals 16 (correct)
For a large input file (1000 * 1000) realdata.count is 999001 which is WRONG.
Why?
Is it to do with a limitation on array of Strings?
For this to work I stripped the input string of \n first as I don't use them and then proceeded from there.
The only strange issue was an extra character at the end of the string, must have been a \n at the end of the input file.
let stripped = returnData!.replacingOccurrences(of: "\n", with: " ")
var inputString = stripped.components(separatedBy: " ").dropFirst(2)
inputString = inputString.dropLast()
//inputString is now the correct length

How to offset a Range<T> in Swift?

Imagine we have an arbitrary Range<T> and we want to create a new range with startIndex and endIndex advanced by 50 units.
My first thought was to do this:
let startIndex = advance(range.startIndex, 50)
let endIndex = advance(range.endIndex, 50)
var newRange = startIndex..<endIndex
But this gives "fatal error: can not increment endIndex". (Well, it does with Range<String.Index>. I haven't tried it with other generic parameters.) I've tried quite a few permutations of this, including assigning range.startIndex and range.endIndex to new variables, etc. Nothing works.
Let me stress that I'm looking for a solution that works with any T. GoZoner gave an answer below that I haven't tried with Int, but I wouldn't be surprised if it worked. However, no permutation of it I tried will work when T is String.Index
So, how can I do this?
There’s a second version of advance that takes a maximum index not to go beyond:
let s = "Hello, I must be going."
let range = s.startIndex..<s.endIndex
let startIndex = advance(range.startIndex, 50, s.endIndex)
let endIndex = advance(range.endIndex, 50, s.endIndex)
var newRange = startIndex..<endIndex
if newRange.isEmpty {
println("new range out of bounds")
}
Try:
1> var r1 = 1..<50
r1: Range<Int> = 1..<50
2> var r2 = (r1.startIndex+50)..<(r1.endIndex+50)
r2: Range<Int> = 51..<100