Reduce array to tuple of first and last element? - arrays

I have an array that I would like to first sort, then return the first and last element of the sorted array. I thought I can use reduce, but what if I don't have an initial value?
Here is the array I'm trying to work with:
let myNumbers = [4, 9, 6, 2, 3]
How can map this to the first and last of the sorted array to this?:
(2, 9)

Method 1: min()/max()
This is the easiest way:
let input = [4, 9, 6, 2, 3]
let output = (input.min(), input.max())
print(output) //(Optional(2), Optional(9))
If you're certain that the array isn't empty, you can safely force unwrap the optionals:
let input = [4, 9, 6, 2, 3]
let output = (input.min()!, input.max()!) // (2, 9)
This is approach does 2 iterations over the array. It's O(N). Unless a sorted list is required elsewhere, sorting then taking the first/last would be worse, as it would be O(N * log_2(N)).
Method 2: reduce()
If you insist on using reduce, you can do it like this:
let input = [4, 9, 6, 2, 3]
let output = input.reduce((min: Int.max, max: Int.min)){
(min($0.min, $1), max($0.max , $1))
} //(2, 9)
Each reduce iteration sets the accumulator to the new min (the smaller of the old min and current element), and the new max (the larger of the old max and the current).
The initial values of the accumulator are set such that:
Any element in the array compares as smaller than the accumulator's min
Any element in the array compares as larger than the accumulator's max

You don't need an initialValue for reduce, it's optional.
var foo = [1, 40, 20, -20, 50];
var reducer = function(prev, curr, i, arr){return [prev[0] <= curr ? prev[0] : curr, prev[1] >= curr ? prev[1] : curr]};
var baz = foo.reduce(reducer); // [-20, 50]
Or maybe like this:
var foo = [1, 40, 20, -20, 50];
var reducer = function(prev, curr, i, arr){return {min: prev.min <= curr ? prev.min : curr, max: prev.max >= curr ? prev.max : curr}};
var baz = foo.reduce(reducer); // {min: -20, max: 50}
Edit: Just noticed this is for swift and not javascript, whoops lol. I must have been surfing the wrong SO category. I think the principle would be the same in swift except you probably do need to provide some kind of initial value.

Related

Swift returning Array from ArraySlices

I'm working with a sorting function that takes an array of Ints already sorted in descending order and places a new Int in its correct spot. (i.e if my sorted array was [10, 7, 2] and the new int was 5, the function would return [10, 7, 5, 2]). The function for doing this, once it has found the correct spot for the new Int, slices the original array into the items before the new Ints spot and those after, and then combines the slices with the new Int.
The problem I'm running into is that this won't give me an array but rather an array slice.
Code:
func addToSorted(sorted: [Int], new: Int) -> [Int] {
if sorted.count == 0 {
return [new]
} else {
for index in 0..<sorted.count {
let item = sorted[index]
if new > item {
return sorted[..<index] + [new] + sorted[index...]
}
}
}
}
let result = addToSorted(sorted: [10, 7, 2], new: 5)
print(result) // expected [10, 7, 5, 2]
This is a more generic (and efficient) alternative which uses binary search
extension RandomAccessCollection where Element : Comparable {
func descendingInsertionIndex(of value: Element) -> Index {
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.endIndex
}
}
And use it
var array = [10, 7, 5, 2]
let index = array.descendingInsertionIndex(of: 4)
array.insert(4, at: index)
print(array) // [10, 7, 5, 4, 2]
For ascending order replace if value > slice[middle] with if value < slice[middle] and return slice.endIndex with return slice.startIndex
If you use the Swift Algorithms, this insertion is a one-liner:
var arr = [10, 7, 2]
arr.insert(5, at: arr.partitioningIndex {$0 < 5})
print (arr) // [10, 7, 5, 2]
This is very efficient — O(log n) — because your array is already partitioned (sorted) and therefore it uses a binary search.
You would have to promote the slices to arrays:
return Array(sorted[..<index]) + [new] + Array(sorted[index...])
A few other points:
You should make a habit out of using sorted.isEmpty over sorted.count == 0, it's much faster for some collections that don't store their count, such as lazy collections or even String (IIRC).
A better approach would be to just use Array.insert(_:at:):
var sorted = sorted // Make a local mutable copy
sorted.insert(new, at: index)
BTW after your for loop, you need insert at the end of your array (this also removes the need for checking the empty case):
return sorted + [new]
Since this works even when sorted is empty, you can remove that special case.
Since you know your data structure is already sorted, you can use binary search instead of linear search to find the insertion index faster.

Swift : Performing operations on certain elements of an array

So, something is bugging me with the syntax in Swift for performing operations on Arrays of Ints.
What I wanna do is this : I have an array of Ints which is outputted from a function, its size (count) varies between say 2 and 6 for now, depending on buttons I press in my app.
For each array that is outputted and that contain n ints, I want to create n arrays on which to perform an other action later on.
These "sub" arrays are supposed to be calculated this way :
newArray1's values should be array's values - the value of the first index of newArray1
newArray2's values should be array's values - the value of the second index of newArray2
etc... (I'll automate the number of newArrays according to the array.count)
An other condition applying for those new arrays is that if at a given index the value is negative, I add 12 (so it'll occur for newArray2 at index 1, for newArray3 at indexes 1 & 2, etc... as long as those newArrays are created).
Here's how I wanted to perform that (I created this with dummy arbitrary array in the playground for the sake of testing before inserting the correct stuff in my app code) :
var array : [Int] = [2,4,6,8,9]
var newArray2 = [Int]()
var increment2 = Int()
increment2 = array[1]
newArray2 = array.map {$0 - increment2}
for i in 0..<newArray2.count {
if array[i] < 0 {
newArray2[i] = array[i] + 12
} else {
newArray2[i] = array[i]
}
}
print(array)
print(newArray2)
So of course it doesn't work because I can't seem to figure how to correctly perform operations on Arrays...
Intuitively it seems in my first if statement I'm comparing not the element at index i but i itself, not sure how to reformat that though...
Any help is most welcome, thanks in advance ! :)
[EDIT: I just edited the names of newArray1 to newArray2, same for increments, so that I have negative values and it matches the index value of 1 which is the second element of my main array]
You seem to mean this:
let arr = [2,4,6,8,9]
var results = [[Int]]()
for i in arr.indices {
results.append(arr.map {
var diff = $0-arr[i]
if diff < 0 { diff += 12 }
return diff
})
}
// results is now:
// [[0, 2, 4, 6, 7],
// [10, 0, 2, 4, 5],
// [8, 10, 0, 2, 3],
// [6, 8, 10, 0, 1],
// [5, 7, 9, 11, 0]]

Swift remove objects in Array range

I have an array as a property in a class.
Class Custom {
let objArray: [CustomClass]
}
I want to remove some items in objArray in a range. So I have done below
let newVar = objArray[1...3]
new objects are correctly removed but return value is in newVar since array is value type how I can make the original reflect the same.
Below code gets Index out of bounds as the indexes incremented
for i in 1...3 {
objArray.remove(at: 1)
}
======
What is the best approach for the above issue.
Any hint in right direction would be highly appreciated.
Use removeSubrange method of array. Make a valid range by element location and length.
var array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let range = 1...3
array.removeSubrange(range)
print(array)
Output: [1, 5, 6, 7, 8, 9, 10]
Note: Range should be a valid range I mean it should not be out from array.
Here is yours way (by for loop)
We can not remove objects by their indexes in a loop because every time object removes array's count and objects indexes will be change so out of range crash can come or you might get a wrong output. So you will have to take help of another array. See below example:-
var array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
var newArray: [Int] = []
let minRange = 1
let maxRange = 3
for i in 0..<array.count {
if i >= minRange && i <= maxRange {
/// Avoid
continue
}
newArray.append(array[i])
}
print(newArray)
Output: [1, 5, 6, 7, 8, 9, 10]
If you want to remove items by index in a range you have to inverse the indexes to start with the highest index otherwise you will get the out-of-range exception. Consider also that indexes are zero-based.
That's a safe version which checks also the upper bound of the array.
var array = [1, 2, 3, 4, 5, 6]
for i in (0...3).reversed() where i < array.count {
array.remove(at: i)
}
print(array) // [5, 6]
You can find a more generic and more efficient solution here
This solution also returns the removed values
extension Array {
/**
* ## Examples:
* var arr = [0,1,2,3]
* arr.remove((0..<2)) // 0,1
* arr // 2,3
*/
mutating func remove(_ range: Range<Int>) -> Array {
let values = Array(self[range])
self.removeSubrange(range)
return values
}
}
The issue you are having is that an array index is zero based, which is to say, the first element in an array is accessed bv:
Let firstArrayValue = objArray[0]
So in the case of your for loop, you need to subtact 1 from i to get the proper index value:
for i in 1…3 {
objArray.remove(at: i-1)
}
A better way is to loop through the indices by starting at 0. i = 0 will reference the first value in your objArray:
for i in 0...2 {
objArray.remove(at: i)
}
If you need to remove elements in the middle of the array you must first find their index location then remove. To find the index:
let indexLocation = objArray(indexOf: "Value in Array")
Then remove:
objArray.remove(at: indexLocation)

How to find a random index in array A which value does not appear in array B?

Let's say array A holds this:
[0, 1, 8, 3, 10, 6, 2]
And array B holds this:
[1, 2]
How can I generate a random index in array A which value does not appear in array B? Possible indexes in above example are:
0, 2, 3, 4, 5
But how to do this in Swift?
When you want to work with Array elements and their indices, enumerated() can be a good tool:
var a = [0, 1, 8, 3, 10, 6, 2]
var b = [1, 2]
var possibleIndices = a.enumerated()
.filter{!b.contains($0.element)}
.map{$0.offset}
print(possibleIndices)
//->[0, 2, 3, 4, 5]
(When b can be large, better make it a Set.)
And then:
(When we can assume b never holds all contents of a.)
var randomIndexToPossibleIndices = Int(arc4random_uniform(UInt32(possibleIndices.count)))
var randomIndex = possibleIndices[randomIndexToPossibleIndices]
If the assumption above cannot be satisfied, possibleIndices can be empty. So you'd better make randomIndex Optional:
var randomIndex: Int? = nil
if !possibleIndices.isEmpty {
var randomIndexToPossibleIndices = Int(arc4random_uniform(UInt32(possibleIndices.count)))
randomIndex = possibleIndices[randomIndexToPossibleIndices]
}
Thanks for Martin R.
First, you'd have to generate a diff between the 2 arrays ( unless they're both extremely large, in which case randomly trying recursively might result in better performance ).
Then all you have to do is find a random index you'd like to use and access said element:
#if os(Linux)
let j = Int(random() % ((count-1)))
#else
let j = Int(Int(arc4random()) % ((count-1)))
#endif
Will give you a proper index
If you then use this index and the element to find original element in your array you'll have your result.
If in case your elements are integers, and thus collisions can occur the thing I'd do would be recursively finding it to solve your problem. Remember that this can result in slow performance.
Look into the functional programming part of collections in swift here:
Swift Guide to map filter reduce
For instance you could use filter in the following way ( and I don't know if this is the best way ):
collection.filter {
var found = false;
for element in bCollection {
if element == $0 {
found = true;
}
}
return !found; // Might be better to turn true/false thing around in the above code to slightly improve performance.
}
How about working with sets?
let a = [0, 1, 8, 3, 10, 6, 2]
let b = [1, 2]
var setA = Set(a)
var setB = Set(b)
setA.subtract(setB)
var index: Int? = nil
if let first = setA.first {
index = a.index(of: first)
}
// if index == nil no such index exists

Find min / max value in Swift Array

Given an array of Swift numeric values, how can I find the minimum and maximum values?
I've so far got a simple (but potentially expensive) way:
var myMax = sort(myArray,>)[0]
And how I was taught to do it at school:
var myMax = 0
for i in 0..myArray.count {
if (myArray[i] > myMax){myMax = myArray[i]}
}
Is there a better way to get the minimum or maximum value from an integer Array in Swift? Ideally something that's one line such as Ruby's .min and .max.
Given:
let numbers = [1, 2, 3, 4, 5]
Swift 3:
numbers.min() // equals 1
numbers.max() // equals 5
Swift 2:
numbers.minElement() // equals 1
numbers.maxElement() // equals 5
To calculate an array's min and max values yourself, you can use reduce. This was a key solution prior to .min() and .max() appearing in Swift.
Use the almighty reduce:
let nums = [1, 6, 3, 9, 4, 6];
let numMax = nums.reduce(Int.min, { max($0, $1) })
Similarly:
let numMin = nums.reduce(Int.max, { min($0, $1) })
reduce takes a first value that is the initial value for an internal accumulator variable, then applies the passed function (here, it's anonymous) to the accumulator and each element of the array successively, and stores the new value in the accumulator. The last accumulator value is then returned.
With Swift 5, Array, like other Sequence Protocol conforming objects (Dictionary, Set, etc), has two methods called max() and max(by:) that return the maximum element in the sequence or nil if the sequence is empty.
#1. Using Array's max() method
If the element type inside your sequence conforms to Comparable protocol (may it be String, Float, Character or one of your custom class or struct), you will be able to use max() that has the following declaration:
#warn_unqualified_access func max() -> Element?
Returns the maximum element in the sequence.
The following Playground codes show to use max():
let intMax = [12, 15, 6].max()
let stringMax = ["bike", "car", "boat"].max()
print(String(describing: intMax)) // prints: Optional(15)
print(String(describing: stringMax)) // prints: Optional("car")
class Route: Comparable, CustomStringConvertible {
let distance: Int
var description: String { return "Route with distance: \(distance)" }
init(distance: Int) {
self.distance = distance
}
static func ==(lhs: Route, rhs: Route) -> Bool {
return lhs.distance == rhs.distance
}
static func <(lhs: Route, rhs: Route) -> Bool {
return lhs.distance < rhs.distance
}
}
let routes = [
Route(distance: 20),
Route(distance: 30),
Route(distance: 10)
]
let maxRoute = routes.max()
print(String(describing: maxRoute)) // prints: Optional(Route with distance: 30)
#2. Using Array's max(by:) method
If the element type inside your sequence does not conform to Comparable protocol, you will have to use max(by:) that has the following declaration:
#warn_unqualified_access func max(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows -> Element?
Returns the maximum element in the sequence, using the given predicate as the comparison between elements.
The following Playground codes show to use max(by:):
let dictionary = ["Boat" : 15, "Car" : 20, "Bike" : 40]
let keyMaxElement = dictionary.max(by: { (a, b) -> Bool in
return a.key < b.key
})
let valueMaxElement = dictionary.max(by: { (a, b) -> Bool in
return a.value < b.value
})
print(String(describing: keyMaxElement)) // prints: Optional(("Car", 20))
print(String(describing: valueMaxElement)) // prints: Optional(("Bike", 40))
class Route: CustomStringConvertible {
let distance: Int
var description: String { return "Route with distance: \(distance)" }
init(distance: Int) {
self.distance = distance
}
}
let routes = [
Route(distance: 20),
Route(distance: 30),
Route(distance: 10)
]
let maxRoute = routes.max(by: { (a, b) -> Bool in
return a.distance < b.distance
})
print(String(describing: maxRoute)) // prints: Optional(Route with distance: 30)
The other answers are all correct, but don't forget you could also use collection operators, as follows:
var list = [1, 2, 3, 4]
var max: Int = (list as AnyObject).valueForKeyPath("#max.self") as Int
you can also find the average in the same way:
var avg: Double = (list as AnyObject).valueForKeyPath("#avg.self") as Double
This syntax might be less clear than some of the other solutions, but it's interesting to see that -valueForKeyPath: can still be used :)
You can use with reduce:
let randomNumbers = [4, 7, 1, 9, 6, 5, 6, 9]
let maxNumber = randomNumbers.reduce(randomNumbers[0]) { $0 > $1 ? $0 : $1 } //result is 9
Swift 3.0
You can try this code programmatically.
func getSmallAndGreatestNumber() -> Void {
let numbers = [145, 206, 116, 809, 540, 176]
var i = 0
var largest = numbers[0]
var small = numbers[0]
while i < numbers.count{
if (numbers[i] > largest) {
largest = numbers[i]
}
if (numbers[i] < small) {
small = numbers[i]
}
i = i + 1
}
print("Maximum Number ====================\(largest)")// 809
print("Minimum Number ====================\(small)")// 116
}
With Swift 1.2 (and maybe earlier) you now need to use:
let nums = [1, 6, 3, 9, 4, 6];
let numMax = nums.reduce(Int.min, combine: { max($0, $1) })
For working with Double values I used something like this:
let nums = [1.3, 6.2, 3.6, 9.7, 4.9, 6.3];
let numMax = nums.reduce(-Double.infinity, combine: { max($0, $1) })
In Swift 2.0, the minElement and maxElement become methods of SequenceType protocol, you should call them like:
let a = [1, 2, 3]
print(a.maxElement()) //3
print(a.minElement()) //1
Using maxElement as a function like maxElement(a) is unavailable now.
The syntax of Swift is in flux, so I can just confirm this in Xcode version7 beta6.
It may be modified in the future, so I suggest that you'd better check the doc before you use these methods.
Here's a performance test for the solutions posted here. https://github.com/tedgonzalez/MaxElementInCollectionPerformance
This is the fastest for Swift 5
array.max()
var numbers = [1, 2, 7, 5];
var val = sort(numbers){$0 > $1}[0];
Apple's Swift Algorithms introduced 2021 contains Minima and/or Maxima which is likely highly optimized.
Example from the documentation:
let numbers = [7, 1, 6, 2, 8, 3, 9]
if let (smallest, largest) = numbers.minAndMax(by: <) {
// Work with 1 and 9....
}
The total complexity is O(k log k + nk), which will result in a runtime close to O(n) if k is a small amount. If k is a large amount (more than 10% of the collection), we fall back to sorting the entire array. Realistically, this means the worst case is actually O(n log n).
Updated for Swift 3/4:
Use below simple lines of code to find the max from array;
var num = [11, 2, 7, 5, 21]
var result = num.sorted(){
$0 > $1
}
print("max from result: \(result[0])") // 21
Just curious why you think the way it was taught in school is potentially expensive? You're running a for loop which has the time complexity of O(N). That's actually better than most sorting algorithms and equivalent to the performance of higher-order methods like reduce.
So I would think that in terms of performance, a for loop is as good as it gets. I don't think you'll find anything better than O(N) for finding max.
Of course, just use the .max() method provided by apple is the way to go.
If both minimum and maximum values are desired, an efficient approach could be to use a single reduce operation with a tuple:
let values = [11, 2, 7, 5, 21]
let (minimum, maximum) = values.reduce((Int.max, Int.min)) {
(min($0.0, $1), max($0.1, $1))
}
print(minimum, maximum) // 2, 21
let array: [Int] = [2, -22, -1, -5600, 333, -167]
var min = 0
var max = array[0]
for i in array {
// finding the min
if min > i {
min = i
}
// finding the max
if max < i {
max = i
}
}
print("Minimum: \(min)\nMaximum: \(max)")
You can also sort your array and then use array.first or array.last

Resources