Swift Mini-Max Sum One Test Case Failed - HackerRank - swift

Before anything else, I checked if this kind of question fits Stackoverflow, and based on one similar question (javascript) and from this question: https://meta.stackexchange.com/questions/129598/which-computer-science-programming-stack-exchange-sites-do-i-post-on -- it does.
So here it goes. The challenge is pretty simple, in my opinion:
Given five positive integers, find the minimum and maximum values that
can be calculated by summing exactly four of the five integers. Then
print the respective minimum and maximum values as a single line of
two space-separated long integers.
For example, . Our minimum sum is and our maximum sum is . We would
print
16 24
Input Constraint:
1 <= arr[i] <= (10^9)
My solution is pretty simple. This is what I could do best:
func miniMaxSum(arr: [Int]) -> Void {
let sorted = arr.sorted()
let reversed = Array(sorted.reversed())
var minSum = 0
var maxSum = 0
_ = sorted
.filter({ $0 != sorted.last!})
.map { minSum += $0 }
_ = reversed
.filter({ $0 != reversed.last!})
.map { maxSum += $0 }
print("\(minSum) \(maxSum)")
}
As you can see, I have two sorted arrays. One is incrementing, and the other one is decrementing. And I'm removing the last element of the two newly sorted arrays. The way I remove the last element is using filter, which probably creates the problem. But from there, I thought I could get easily the minimum and maximum sum of the 4 elements.
I had 13/14 test cases passed. And my question is, what could be the test case in which this solution will likely to fail?
Problem link: https://www.hackerrank.com/challenges/mini-max-sum/problem

Here
_ = sorted
.filter({ $0 != sorted.last!})
.map { minSum += $0 }
your expectation is that all but the largest element are added. But that is only correct it the largest element is unique. (And similarly for the maximal sum.)
Choosing an array with all identical errors makes the problem more apparent:
miniMaxSum(arr: [1, 1, 1, 1, 1])
// 0 0
A simpler solution would be to compute the sum of all elements once, and then get the result by subtracting the largest respectively smallest array element. I'll leave the implementation to you :)

Here is the O(n) solution:
func miniMaxSum(arr: [Int]) {
var smallest = Int.max
var greatest = Int.min
var sum = 0
for x in arr {
sum += x
smallest = min(smallest, x)
greatest = max(greatest, x)
}
print(sum - greatest, sum - smallest, separator: " ")
}

I know this isn't codereview.stackexchange.com, but I think some clean up is in order, so I'll start with that.
let reversed = Array(sorted.reversed())
The whole point of the ReversedCollection that is returned by Array.reversed() is that it doesn't cause a copy of elements, and it doesn't take up any extra memory or time to produce. It's merely a wrapper around a collection, and intercepts indexing operations and changes them to immitate a buffer that's been reversed. Asked for .first? It'll give you .last of its wrapped collection. Asked for .last? It'll return .first, etc.
By initializing a new Array from sorted.reversed(), you're causing an unecessary copy, and defeating the point of ReversedCollection. There are some circumstances where this might be necessary (e.g. you want to pass a pointer to a buffer of reversed elements to a C API), but this isn't one of them.
So we can just change that to let reversed = sorted.reversed()
-> Void doesn't do anything, omit it.
sorted.filter({ $0 != sorted.last!}) is inefficient.
... but more than that, this is the source of your error. There's a bug in this. If you have an array like [1, 1, 2, 3, 3], your minSum will be 4 (the sum of [1, 1, 2]), when it should be 7 (the sum of [1, 1, 2, 3]). Similarly, the maxSum will be 8 (the sume of [2, 3, 3]) rather than 9 (the sum of [1, 2, 3, 3]).
You're doing a scan of the whole array, doing sorted.count equality checks, only to discard an element with a known position (the last element). Instead, use dropLast(), which returns a collection that wraps the input, but whose operations mask the existing of a last element.
_ = sorted
.dropLast()
.map { minSum += $0 }
_ = reversed
.dropLast()
.map { maxSum += $0 }
_ = someCollection.map(f)
... is an anti-pattern. The distinguishing feature between map and forEach is that it produces a resulting array that stores the return values of the closure as evaluated with every input element. If you're not going to use the result, use forEach
sorted.dropLast().forEach { minSum += $0 }
reversed.dropLast().forEach { maxSum += $0 }
However, there's an even better way. Rather than summing by mutating a variable and manually adding to it, instead use reduce to do so. This is ideal because it allows you to remove the mutability of minSum and maxSum.
let minSum = sorted.dropLast().reduce(0, +)
let maxSum = reversed.dropLast().reduce(0, +)
You don't really need the reversed variable at all. You could just achieve the same thing by operating over sorted and using dropFirst() instead of dropLast():
func miniMaxSum(arr: [Int]) {
let sorted = arr.sorted()
let minSum = sorted.dropLast().reduce(0, +)
let maxSum = sorted.dropFirst().reduce(0, +)
print("\(minSum) \(maxSum)")
}
Your code assumes the input size is always 5. It's good to document that in the code:
func miniMaxSum(arr: [Int]) {
assert(arr.count == 5)
let sorted = arr.sorted()
let minSum = sorted.dropLast().reduce(0, +)
let maxSum = sorted.dropFirst().reduce(0, +)
print("\(minSum) \(maxSum)")
}
A generalization of your solution uses a lot of extra memory, which you might not have available to you.
This problem fixes the number of summed numbers (always 4) and the number of input numbers (always 5). This problem could be generalized to picking summedElementCount numbers out of any sized arr. In this case, sorting and summing twice is inefficient:
Your solution has a space complexity of O(arr.count)
This is caused by the need to hold the sorted array. If you were allowed to mutate arr in-place, this could reduce to `O(1).
Your solution has a time complexity of O((arr.count * log_2(arr.count)) + summedElementCount)
Derivation: Sorting first (which takes O(arr.count * log_2(arr.count))), and then summing the first and last summedElementCount (which is each O(summedElementCount))
O(arr.count * log_2(arr.count)) + (2 * O(summedElementCount))
= O(arr.count * log_2(arr.count)) + O(summedElementCount) // Annihilation of multiplication by a constant factor
= O((arr.count * log_2(arr.count)) + summedElementCount) // Addition law for big O
This problem could instead be solved with a bounded priority queue, like the MinMaxPriorityQueue in Google's Gauva library for Java. It's simply a wrapper for min-max heap that maintains a fixed number of elements, that when added to, causes the greatest element (according to the provided comparator) to be evicted. If you had something like this available to you in Swift, you could do:
func miniMaxSum(arr: [Int], summedElementCount: Int) {
let minQueue = MinMaxPriorityQueue<Int>(size: summedElementCount, comparator: <)
let maxQueue = MinMaxPriorityQueue<Int>(size: summedElementCount, comparator: >)
for i in arr {
minQueue.offer(i)
maxQueue.offer(i)
}
let (minSum, maxSum) = (minQueue.reduce(0, +), maxQueue.reduce(0, +))
print("\(minSum) \(maxSum)")
}
This solution has a space complexity of only O(summedElementCount) extra space, needed to hold the two queues, each of max size summedElementCount.
This is less than the previous solution, because summedElementCount <= arr.count
This solution has a time complexity of O(arr.count * log_2(summedElementCount))
Derviation: The for loop does arr.count iterations, each consisting of a log_2(summedElementCount) operation on both queues.
O(arr.count) * (2 * O(log_2(summedElementCount)))
= O(arr.count) * O(log_2(summedElementCount)) // Annihilation of multiplication by a constant factor
= O(arr.count * log_2(summedElementCount)) // Multiplication law for big O
It's unclear to me whether this is better or worse than O((arr.count * log_2(arr.count)) + summedElementCount). If you know, please let me know in the comments below!

Try this one accepted:
func miniMaxSum(arr: [Int]) -> Void {
let sorted = arr.sorted()
let minSum = sorted[0...3].reduce(0, +)
let maxSum = sorted[1...4].reduce(0, +)
print("\(minSum) \(maxSum)"
}

Try this-
func miniMaxSum(arr: [Int]) -> Void {
var minSum = 0
var maxSum = 0
var minChecked = false
var maxChecked = false
let numMax = arr.reduce(Int.min, { max($0, $1) })
print("Max number in array: \(numMax)")
let numMin = arr.reduce(Int.max, { min($0, $1) })
print("Min number in array: \(numMin)")
for item in arr {
if !minChecked && numMin == item {
minChecked = true
} else {
maxSum = maxSum + item
}
if !maxChecked && numMax == item {
maxChecked = true
} else {
minSum = minSum + item
}
}
print("\(minSum) \(maxSum)")
}

Try this:
func miniMaxSum(arr: [Int]) -> Void {
let min = arr.min()
let max = arr.max()
let total = arr.reduce(0, +)
print(total - max!, total - min!, separator: " ")
}

Related

Find nearest smaller number in array

I would like to be able to find the nearest smaller value in an array of numbers. For instance, if I have:
[1, 4, 6, 9, 14, 39]
And I'm looking for the nearest value smaller than:
8
The function would return:
6
Additionally, if I pass a number greater than the maximum value in the array, it should return the maximum. If I pass a number smaller than the minimum, it should return nil.
I tried doing this using the first function on arrays, however this on its own doesn't produce the result I'm looking for as I would need something like this:
numbers.first(where: { $0 <= target && $1 < target })
but unfortunately, this isn't valid. Any suggestions? I know this could be done fairly trivially with a while loop, however I was hoping there would be a cleaner, functional way.
Given that the array is sorted , You need
if let value = numbers.last(where: { $0 <= target }) {
print(value)
}
This is a generic solution using binary search. The array must be sorted
extension RandomAccessCollection where Element : Comparable {
func lowerElement(of value: Element) -> Element? {
var slice : SubSequence = self[...]
while !slice.isEmpty {
let middle = slice.index(slice.startIndex, offsetBy: slice.count / 2)
if value < slice[middle] {
slice = slice[..<middle]
} else {
slice = slice[index(after: middle)...]
}
}
return slice.startIndex == self.startIndex ? nil : self[self.index(before: slice.startIndex)]
}
}
let array = [1, 4, 6, 9, 14, 39]
let result = array.lowerElement(of: 8)
print(result)

Get value from array that is closest to but small than another value [duplicate]

I would like to be able to find the nearest smaller value in an array of numbers. For instance, if I have:
[1, 4, 6, 9, 14, 39]
And I'm looking for the nearest value smaller than:
8
The function would return:
6
Additionally, if I pass a number greater than the maximum value in the array, it should return the maximum. If I pass a number smaller than the minimum, it should return nil.
I tried doing this using the first function on arrays, however this on its own doesn't produce the result I'm looking for as I would need something like this:
numbers.first(where: { $0 <= target && $1 < target })
but unfortunately, this isn't valid. Any suggestions? I know this could be done fairly trivially with a while loop, however I was hoping there would be a cleaner, functional way.
Given that the array is sorted , You need
if let value = numbers.last(where: { $0 <= target }) {
print(value)
}
This is a generic solution using binary search. The array must be sorted
extension RandomAccessCollection where Element : Comparable {
func lowerElement(of value: Element) -> Element? {
var slice : SubSequence = self[...]
while !slice.isEmpty {
let middle = slice.index(slice.startIndex, offsetBy: slice.count / 2)
if value < slice[middle] {
slice = slice[..<middle]
} else {
slice = slice[index(after: middle)...]
}
}
return slice.startIndex == self.startIndex ? nil : self[self.index(before: slice.startIndex)]
}
}
let array = [1, 4, 6, 9, 14, 39]
let result = array.lowerElement(of: 8)
print(result)

Calculator is not taking the Average

I was coding a calculator app on swift, and I am very new to swift. So I am lost with the syntax and everything. When I debug my code I get and error of on the code sayign division by 0. I have debugged and everything but I have no idea how to solve it any help would be greatly appreciated, I am just starting out swift and iOS. The application I am making right is for the mac terminal so my program takes the the users input from string and then converts it to an int.
This is the code I am working with
var average = 0;
let count = nums.count - 1
for index in 0...nums.count - 2 {
let nextNum = Int(nums[index])
average += nextNum!
}
return average / count
}
You are subtracting one from the array elements count, I assume due to the idea that zero based numbering affects it, but there is no need in this case.
You should check for an empty array since this will cause a division by zero. Also you can use reduce to simply sum up an array of numbers then divide by the count.
func average(of nums: [Float]) -> Float? {
let count = nums.count
if (count == 0) { return nil }
return nums.reduce(0, +) / Float(count)
}
There might be some reason for divisor be 0. As #MartinR said if there is only 1 object in nums then count = nums.count -1 would be zero and 1 / 0 is an undefined state.
One more issue I found that you are looping as 0...nums.count - 2 but it should be 0...nums.count - 1. You can also write it with less than condition as
0..<nums.count or 0..<count
Use,
var average = 0;
let count = nums.count
for index in 0..<count {
let nextNum = Int(nums[index])
average += nextNum!
}
return average / count
You can use the swift high-order functions for the optimised solution which will return 0 as average even if you do not have any number in your nums array. As:
let count = nums.count
let avg = nums.reduce(0, +) / count
Let try this:
var sum = 0;
let count = nums.count
for index in 0...nums.count - 1 {
let nextNum = Int(nums[index])
sum += nextNum!
}
return count != 0 ? sum/count : 0

Swift how does my function exceed O(n)?

I am trying to work on a leetcode problem that asks for
Given an array of integers where 1 ≤ a[i] ≤ n (n = size of array), some elements appear twice and others appear once.
Find all the elements of [1, n] inclusive that do not appear in this array.
My solution to the problem is:
func findDisappearedNumbers(_ nums: [Int]) -> [Int] {
var returnedArray = [Int]()
if nums.isEmpty == false {
for i in 1...nums.count {
if nums.contains(i) == false {
returnedArray.append(i)
}
}
} else {
returnedArray = nums
}
return returnedArray
}
However, leetcode tells me that my solution is "Time limit exceeded"
Shouldn't my solution be O(n)? I am not sure where did I made it to be greater than O(n).
If I haven't missed anything your algorithm is O(n^2).
First, you iterate over each element of the array which is O(n), but for each element, you call contains which has to iterate over all the elements again and you end up with O(n^2).
I refrain from telling you the solution since it is for leetcode.

How can I search through this array for a specific value then return the other value in the tuple?

I have a large array of (x,y) pairs:
P =
[
(0.0, 500000.09999999998),
(0.001, 18.332777589999999),
(0.002, 18.332221480000001),
(0.0030000000000000001, 18.331665000000001),
(0.0040000000000000001, 18.331108140000001),
(0.0050000000000000001, 18.33055092),
(0.0060000000000000001, 18.32999332),
...
]
I now need to use this in my code. I need to search for a specific x-value and, if the x-value exists, return its corresponding y-value.
Note: If there is a better format I could put my (x,y) pairs in, please feel free to let me know. For example, 2 separate arrays where one holds the x-values and the other holds the y-values. Then I could use the index to find the corresponding y-value or something.
Edit:
A user made a very good point in the comments: how can I reliably compare x == 0.001?
The way I will be using my code is this: I am evaluating a function f(x) at values of x. However, if at a particular value of x there is a y value in the P array, then I need to do an extra subtraction calculation (the details of which are not too important here). The problem, then, is that what if I pass the x value 0.001 in there and the P array does not have a correpsonding y value, but it does have one for 0.001000000009?? Then the code will say there is no value, but in reality it is reasonably close to the intended x value.
I'd suggest to let your array to be an array of CGPoints. It's simply:
A structure that contains a point in a two-dimensional coordinate
system.
However, if you want to get the y values based on searching the x:
let myArray = [
(0.0, 500000.09999999998),
(0.001, 18.332777589999999),
(0.002, 18.332221480000001),
(0.0030000000000000001, 18.331665000000001),
(0.0040000000000000001, 18.331108140000001),
(0.0050000000000000001, 18.33055092),
(0.0060000000000000001, 18.32999332),
]
// this array should contains y values for a given x value
// for example, I search the x value of 0.0
let yValues = myArray.filter { $0.0 == 0.0 }.map { $0.1 }
print(yValues) // [500000.09999999998]
Hope this helped.
A good way of doing this is by declaring this function:
func getValueFromTuples(tupleArr:[(Double,Double)],n:Double)->Double?{
for tuple in tupleArr{
if tuple.0 == n{
return tuple.1
}
}
return nil
}
Then, you can use it like this:
var tupleArray: [(Double,Double)] = [(1.0, 12.0),(2.0,23.0),(3.0,34.0),(4.0,45.0),(5.0,56.0)]
var x:Double = 1.0
print(getValueFromTuples(tupleArr: tupleArray,n:x) ?? "No value found") // 12.0
Where the n argument is the value to be found, the tuple is the key-value pair formed by the numbers and getValueFromTuples returns the value y if x has been found, else nil.
This returns "No value found" if the value does not exist in the array of tuples.
Hope this helps!
Your x value all seem to increase by 0.001. If that is the case, you could also calculate the index and return the y value at this index. This would be a lot more efficient.
func calculateIndex(forX x: Double) -> Int {
let increase = 0.001
return Int(x/0.001)
}
You can use the find method to find the index of the x value and then return the y value. I would multiply your values by 1000 and then compare the Int instead of comparing Double.
func findYValue(forX x: Double) -> Double? {
let multiply = 1000
let x = Int(multiply*x)
if let index = array.index(where: { Int($0.0 * multiply) == x }) {
return array[index].1 //found the y value
}
return nil //x is not in the array
}
Instead of using tuples, I would personally use CGPoint. The class has an x and a y property, which makes your code more readable.
Microsoft gives a very thorough explanation of how to compare 2 doubles. The basic premise is that you need to define a certain level of tolerance. The article the explores how to pick a good tolerance in most cases.
Here's code translated to Swift:
func areEqual(_ lhs: Double, _ rhs: Double, units: Int = 3) -> Bool {
let lValue = Int64(bitPattern: lhs.bitPattern)
let rValue = Int64(bitPattern: rhs.bitPattern)
let delta = lValue - rValue
return abs(delta) <= Int64(units)
}
Test:
var n = 0.0
for _ in 0..<10 {
n += 0.1
}
// n should equal 1 but it does not
print(n == 1.0) // false
print(areEqual(1.0, n)) // true
Back to your problem, it becomes straight forward after you defined how to test for equality in 2 doubless:
let x = 0.003
if let y = p.first(where: { areEqual($0.0, x) })?.1 {
print(y)
}