Swift 3 Extracting similar objects - arrays

I have an array of arrays containg int, so let pairs:[[Int]].
I am looking for an elegant way to extract similar elements.
For example my pairs variable could contain something like: [ [1,2], [4,6], [1,2] ]
I would like to extract any array that occurs more than once like [1,2].
In the example [ [1,2], [4,6], [1,2], [3,7], [4,6] ] I would like to extract both [1,2] and [4,6].
This seemed trivial at first, but every go I had at it became very cumbersome with many "helper arrays" and nested "for loops". There surely is a simpler way in Swift, right?
Thanks

Here the way using just one helper Dictionary and one loop:
let pairs = [[1,2], [4,6], [1,2]] // The original array
var pairCount = [NSArray : Int]() // This is helper dictionary. The key is the array, the value is - how much time it appears in the source array. I use NSArray because swift array does not conform to Hashable protocol.
var newPairs = [[Int]]()
for pair in pairs { // Iterate over our pairs
var count: Int = pairCount[pair as NSArray] ?? 0 // If we already have a pair in our dictionary get count of it in array otherwise het 0
count += 1 // increase counter
pairCount[pair as NSArray] = count // save in dictionary
}
let pairsWithCountMoreThanOne = pairCount.flatMap({$1 > 1 ? $0 : nil}) // here we get the result
for pair in pairsWithCountMoreThanOne { // Just print it
print("\(pair)")
}
This code may not be memory efficient for large arrays or large objects but it is really trivial.

Please check the below :
let pairs = [ [1,2], [4,6], [1,2], [3,7], [4,6], [3,5], [4,6] ]
var repeats = [[Int]]()
pairs.forEach { (i) in
let count = (pairs.filter{ return $0 == i }).count
if count > 1 {
if !repeats.contains(where: { (pair) -> Bool in
return pair == i
}) {
repeats.append(i)
}
}
}
print(repeats) // Output is : [[1, 2], [4, 6]]

Related

Filter array with most occurrences of element in swift

I have to filter an array having the most occurrences of element.
Initial Array :
let array1 = [1,2,3,2,4,2,5,3]
let array2 = ["abc", "def", "abc", "ert", "def", "abc"]
After Filtering, Final Array :
let filteredArray1 = [2,2,2]
let filteredArray2 = ["abc","abc","abc"]
I got the idea to get the count of elements from here:
How to count occurrences of an element in a Swift array?
Like getting the count of "abc" :
array2.filter{$0 == "abc"}.count
But is there any way to get the filtered array ?
You can group the items into a dictionary and compare the number of items in each group
let mostFrequent = Dictionary(grouping: array1, by: {$0})
.max(by: {$0.value.count < $1.value.count})?.value ?? []
The issue with the above is that if there are two or more values with the same count only one will be selected.
The below solution handles when there are multiple max counts, I couldn't write it as a single line expression though
let dictionary = Dictionary(grouping: array1, by: {$0})
let max = dictionary.max(by: {$0.value.count < $1.value.count})?.value.count ?? 0
let mostFrequent = dictionary.filter { $0.value.count == max }.values
Using NSCountedSet 🔢
You can define this extension for the Array type
extension Array where Element: Equatable {
func filteredByMostPopular() -> [Element] {
let countedSet = NSCountedSet(array: self)
let mostPopularElement = self.max { countedSet.count(for: $0) < countedSet.count(for: $1) }
return self.filter { $0 == mostPopularElement }
}
}
How does it work?
The extension uses NSCountedSet to find the "most popular" element.
If 2 or more elements are the most popular the first one is choosen.
Then the array is filtered using the most popular element.
Test
array1.filteredByMostPopular() // [2, 2, 2]
array2.filteredByMostPopular() // ["abc", "abc", "abc"]

Randomly select 5 elements from an array list without repeating an element

I am currently trying to make an app for iOS but I can't get some simple code down. Basically I need to randomly select 5 elements from an array list without repeating an element. I have a rough draft, but it only displays one element.
Here is my code:
let array1 = ["salmon", "turkey", "salad", "curry", "sushi", "pizza"]
let randomIndex1 = Int(arc4random_uniform(UInt32(array1.count)))
print(array1[randomIndex1])
You can do it like this:
let array1 = ["salmon", "turkey", "salad", "curry", "sushi", "pizza", "curry", "sushi", "pizza"]
var resultSet = Set<String>()
while resultSet.count < 5 {
let randomIndex = Int(arc4random_uniform(UInt32(array1.count)))
resultSet.insert(array1[randomIndex])
}
let resultArray = Array(resultSet)
print(resultArray)
A set can contain unique elements only, so it can't have the same element more than once.
I created an empty set, then as long as the array contains less than 5 elements (the number you chose), I iterated and added a random element to the set.
In the last step, we need to convert the set to an array to get the array that you want.
SWIFT 5.1
This has been answered, but I dare to come with a more simple solution.
If you take your array and convert it into a Set you will remove any duplicated items and end up with a set of unique items in no particular order since the nature of a Set is unordered.
https://developer.apple.com/documentation/swift/set
If you then convert it back to an array and pick 5 elements you will end up with an array of random items.
But it's just 1 line ;)
Example:
var original = ["A","B","C","C","C","D","E","F","G","H"]
let random = Array(Set(original)).prefix(5)
Example print:
["B", "G", "H", "E", "C"]
The only requirement is your objects must conform to the Hashable protocol. Most standard Swift types do, otherwise, it's often simple to make your own types conform.
https://developer.apple.com/documentation/swift/hashable
If you don't care about changing the original array, the following code will put the picked elements at the end of the array and then it will return the last part of the array as a slice.
This is useful if you don't care about changing the original array, the advantage is that it doesn't use extra memory, and you can call it several times on the same array.
extension Array {
mutating func takeRandomly(numberOfElements n: Int) -> ArraySlice<Element> {
assert(n <= self.count)
for i in stride(from: self.count - 1, to: self.count - n - 1, by: -1) {
let randomIndex = Int(arc4random_uniform(UInt32(i + 1)))
self.swapAt(i, randomIndex)
}
return self.suffix(n)
}
}
Example:
var array = [1,2,3,4]
let a1 = array.takeRandomly(numberOfElements: 2)
let a2 = array.takeRandomly(numberOfElements: 2)
swift-algorithms now includes an extension to Sequence called randomSample.
import Algorithm
var array1 = ["salmon", "turkey", "salad", "curry", "sushi", "pizza"]
array1.randomSample(count: 5)
Just my ¢2:
Moe Abdul-Hameed's solution has one theoretical drawback: if you roll the same randomIndex in every iteration, the while loop will never exit. It's very unlike tho.
Another approach is to create mutable copy of original array and then exclude picked items:
var source = array1
var dest = [String]()
for _ in 1...5 {
let randomIndex = Int(arc4random_uniform(UInt32(source.count)))
dest.append(source[randomIndex])
source.remove(at: randomIndex)
}
print(dest)
var array1 = ["salmon", "turkey", "salad", "curry", "sushi", "pizza"]
while array1.count > 0 {
// random key from array
let arrayKey = Int(arc4random_uniform(UInt32(array1.count)))
// your random number
let randNum = array1[arrayKey]
// make sure the number ins't repeated
array1.removeAtIndex(arrayKey)
}
By removing your picked value from array, prevent's from duplicates to be picked

Check if value is in the array as dictionary value?

I am looking for a way to see if a value (stored in a dictionary) is in an array. The array looks like this: var array = Array<Dictionary<String, Int>>()
I looked around here, on stackoverflow, and found this: How to check if an element is in an array. Only problem is I can't seem to use the contains method by writing arrray.contains.... What am I doing wrong?
You can use contains within contains to check if any of the dictionaries in an array contains the value you are looking for.
For example, to search array a for the value 1:
let a: [[String: Int]] = [["a": 1, "b": 2], ["c": 3], ["d": 4]]
Swift 1.2:
if contains(a, {contains($0.values, 1)}) {
println("it is in there")
}
Swift 2.0:
/* Thanks to #Qbyte for providing this solution */
if a.contains({ $0.values.contains(1) }) {
print("it is in there")
}
This works because each member of array a gets evaluated with the closure {contains($0.values), 1} to see if it is true. The closure takes the dictionary it is passed, gets the array of values from it and then uses contains to see if the value is in there.
You can use this extension:
extension Array {
func contains<T where T : Equatable>(obj: T) -> Bool {
return self.filter({$0 as? T == obj}).count > 0
}
}
Which you can test like:
var array1 = Array<Dictionary<String, Int>>()
array1 = [["zero": 0],["one": 1], ["two": 2]]
array1.contains(["zero": 0]) //true
array1.contains(["five": 5]) //false

Swift - array of strings to multiple subarrays with constant number of strings

Let's say I have this array of strings:
let Vehicles = ["Aeroplane", "Bicycle", "CarVehicle", "Lorry", "Motorbike", "Scooter", "Ship", "Train"]
What I want is this result:
let resultArray = [["Aeroplane", "Bicycle", "CarVehicle", "Lorry"], ["Motorbike", "Scooter", "Ship", "Train"]]
I know I could do this by for but I want to use Higher Order functions in Swift. I mean functions like map, reduce, filter. I think it's possible to do this way and it could be better. Can anyone help me with this? Thanks
A possible solution with map() and stride():
let vehicles = ["Aeroplane", "Bicycle", "CarVehicle", "Lorry", "Motorbike", "Scooter", "Ship", "Train"]
let each = 4
let resultArray = map(stride(from: 0, to: vehicles.count, by: each)) {
vehicles[$0 ..< advance($0, each, vehicles.count)]
}
println(resultArray)
// [[Aeroplane, Bicycle, CarVehicle, Lorry], [Motorbike, Scooter, Ship, Train]]
The usage of advance() in the closure guarantees that the code
works even if the number of array elements is not a multiple of 4
(and the last subarray in the result will then be shorter.)
You can simplify it to
let resultArray = map(stride(from: 0, to: vehicles.count, by: each)) {
vehicles[$0 ..< $0 + each]
}
if you know that the number of array elements is a multiple of 4.
Strictly speaking the elements of resultArray are not arrays
but array slices. In many cases that does not matter, otherwise you
can replace it by
let resultArray = map(stride(from: 0, to: vehicles.count, by: each)) {
Array(vehicles[$0 ..< advance($0, each, vehicles.count)])
}

swift getting an array from something like array[0..<10]

I want to get a range of objects from an array. Something like this:
var array = [1,3,9,6,3,4,7,4,9]
var newArray = array[1...3] //[3,9,6]
The above would access elements from index 1 to 3.
Also this:
newArray = array[1,5,3] // [3,4,6] would be cool
This would retrieve elements from index 1, 5 and 3 respectively.
That last example can be achieved using PermutationGenerator:
let array = [1,3,9,6,3,4,7,4,9]
let perms = PermutationGenerator(elements: array, indices: [1,5,3])
// perms is now a sequence of the values in array at indices 1, 5 and 3:
for x in perms {
// iterate over x = 3, 4 and 6
}
If you really need an array (just the sequence may be enough for your purposes) you can pass it into Array's init method that takes a sequence:
let newArray = Array(perms)
// newArray is now [3, 4, 6]
For your first example - with arrays, that will work as-is. But it looks from your comments like you're trying it with strings as well. Strings in Swift are not random-access (for reasons relating to unicode). So you can't use integers, they have an String-specific bidirectional index type:
let s = "Hello, I must be going"
if let i = find(s, "I") {
// prints "I must be going"
println(s[i..<s.endIndex])
}
This works :
var n = 4
var newArray = array[0..<n]
In any case in
Slicing Arrays in Swift you'll find a very nice sample of the Python slice using a extension to Arrays in Swift.

Resources