Sorting/Accessing Arrays nested in Dictionaries - arrays

I have seen how to sort dictionaries nested in dictionaries, but I cannot seem to sort arrays nested in dictionaries.
Here is an example of what I am trying to sort.
var dictionary = [3: ["name1", 30, "birthmonth1", 30.50293], 1: ["name2", 35, "birthmonth2", 25.17633], 10: ["name3", 25, "birthmonth3", 32.49927]]
I have tried various sorting in Swift 5. However, I am more versed in python and javascript.
On example is:
var sortBySecondElement = dictionary.sorted(by: {0[1].value < $1[1].value})
How can I get the sort function to work?

The quick-and-dirty solution is:
let sortedKVPs = dictionary.sorted(by: { ($0.value[1] as! Int) < ($1.value[1] as! Int) })
Note that it is $0.value[1]. $0 is the key value pair, $0.value is the array, and $0.value[1] is the second element of that array. You also need to guarantee to Swift that the second element of the array is indeed an Int with a cast (as! Int).
Your array contains both Strings and Ints, so its type gets inferred to be [Any]. The compiler can't know the type of each element of this array, because you can put literally anything in it. $0.value[1] and $1.value[1] may be of different, uncomparable types, which is why you cannot use < to directly compare them.
Another thing to note is that the sorted result is not a dictionary. It is an array of key value pairs, because dictionaries aren't ordered, only arrays are.
Although the above solution works, it is very fragile. Since each array represents a person, you should instead create a Person struct to represent a person, rather than an array:
struct Person {
let name: String
let age: Int
let birthMonth: Int
let someOtherProperty: Double // I don't know what the fourth element in the array means
}
let dictionary = [
3: Person(name: "name1", age: 30, birthMonth: 1, someOtherProperty: 30.50293),
...
]
Now sorting becomes very easy:
let sortedKVPs = dictionary.sorted { $0.age < $1.age }

Related

Swift: sort array of [Any] by item type

I have an array that contains objects of different types. In my actual code these are custom types that conform to the same protocol, but the principle also applies to the following code:
let anyObjectArray: [Any] = [51, "g", "hello", 1, 30111]
var sortedArray: [Any] = []
for item in anyObjectArray where item is Int {
sortedArray.append(item)
}
for item in anyObjectArray where item is String {
sortedArray.append(item)
}
print(sortedArray)
// 51, 1, 30111, "g", "hello"
As you can see I want to sort the array by item type.
I am wondering if iterating several times over the array is an appropriate approach. Is there a possibility using .map to sort an array by item type, and if so, would that be more efficient?
The arrays in my actual code would contain a maximum of 4-5 elements, sometimes only one, on the other hand there would be more different types than just two, so the number of for in loops will be higher (possibly 4-5)
This is almost certainly a horrible idea, and is almost certainly masking a major type-mistake, but not difficult to implement. You would sort by the types, just as you say. Since types aren't directly comparable, you can sort by their names.
anyObjectArray.sorted { "\(type(of: $0))" < "\(type(of: $1))" }
But this deeply feels like Array was the wrong type in the first place. It feels like you really want a dictionary here.
This will not sort the array but group the elements by type in a dictionary
var map = [String: [Any]]()
anyObjectArray.forEach( {
let key = "\([type(of: $0))"
if map[key] == nil { map[key] = [] }
map[key]?.append($0)
})
Or alternatively for Swift 4.2 (or later) as suggested by Rob Napier
let map = Dictionary(grouping: anyObjectArray, by: { "\(type(of: $0))" })

How do I make two arrays in which first one would be values and second one would be keys for those values?

I am creating two arrays.
First one contains images, second one contains equal amount of numbers.
How do I make second array of integers correspond to first array, so I would have a specific integer value for each picture?
Like i said in my comment. You could apply a convention, then use an extension on Int to return the image the number describes:
let arrInt = [0, 1, 2, 3] //any number of integers
let cardnames = ["AceOfSpades", "OneOfSpades", "TwoOfSpades"] //etc...
extension Int {
var cardName : UIImage? {
guard self <= 51 else { return nil } //because any number greater than this would not represent a card in your 53-card deck
return UIImage(named: cardNames[self])
}
}
You would essentially use it as:
func AllImagesInTheDeck() -> [UIImage] {
var images : [UIImage] = []
for integer in arrInt {
if let image = integer.cardName {
images.append(image)
}
}
return images
}
From then on, you can manipulate the arrInt itself, of individual integer arrays for each player, and to return their card images, you could either call the AllImages method (which shouldn't be a hard hit on performance since array length shouldn't be too long for this case. Or, you can directly manipulate the integer arrays from there on out, and add more methods to determine which new card images are added, etc. This is while if any struct or class must be avoided, otherwise if you can, incorporating this approach with CRD's class example would work well too.
After the comments you could start with:
class PairedArrays
{
// instance variables - arrays for the images and values, start of empty
var images : [NSImage] = []
var values : [Int] = []
// add a new image/value pair
func addPair(image : NSImage, values : Int) -> Void { ... }
// retrieve integer associated with image
func getAssociatedInt(key : NSImage) -> Int { ... }
}
Now you need to:
Read up on classes in Swift
Fill in the ...
Make it generic so it works with types other than images and integers (optional, but useful knowledge)
Having used it as a learning tool try using an array of struct which will guarantee images & values are always added in pairs (think struct PlayingCard { ... })
Now try it with a dictionary
Compare the last two and decide which suits your application model the best
HTH

Opposite of Swift `zip` — split tuple into two arrays

I have an array of key-value pairs:
let arr = [(key:"hey", value:["ho"]), (key:"ha", value:["tee", "hee"])]
I'm splitting it into two arrays, like this:
let (keys, values) = (arr.map{$0.key}, arr.map{$0.value})
Effectively, that's the opposite of zip — I'm turning an array of tuples into two arrays.
But I don't like the fact that I'm calling map twice, because that means I'm looping through the array twice. Yet neither do I want to declare the two target arrays beforehand as empty arrays and loop once while appending, e.g. with forEach. Is there some wonderful Swifty idiom for unzipping my array of tuples into two arrays?
In Swift 4, you can use reduce(into:):
let (keys, values) = arr.reduce(into: ([String](), [[String]]())) {
$0.0.append($1.key)
$0.1.append($1.value)
}
You said:
Yet neither do I want to declare the two target arrays beforehand as empty arrays and loop once while appending, e.g. with forEach.
Personally, that's precisely what I would do. I would just write a function that does this (that way you're not sprinkling your code with that pattern). But I think the following is much more clear and intuitive than the reduce pattern, but doesn't suffer the inefficiency of the dual-map approach.
/// Unzip an `Array` of key/value tuples.
///
/// - Parameter array: `Array` of key/value tuples.
/// - Returns: A tuple with two arrays, an `Array` of keys and an `Array` of values.
func unzip<K, V>(_ array: [(key: K, value: V)]) -> ([K], [V]) {
var keys = [K]()
var values = [V]()
keys.reserveCapacity(array.count)
values.reserveCapacity(array.count)
array.forEach { key, value in
keys.append(key)
values.append(value)
}
return (keys, values)
}
Or, if you feel compelled to make it an extension, you can do that, too:
extension Array {
/// Unzip an `Array` of key/value tuples.
///
/// - Returns: A tuple with two arrays, an `Array` of keys and an `Array` of values.
func unzip<K, V>() -> ([K], [V]) where Element == (key: K, value: V) {
var keys = [K]()
var values = [V]()
keys.reserveCapacity(count)
values.reserveCapacity(count)
forEach { key, value in
keys.append(key)
values.append(value)
}
return (keys, values)
}
}
Implement this however you'd like, but when you have it in a function, you can favor clarity and intent.
Swift 4
reduce(into:) is great, but don't forget to reserveCapacity to prevent reallocation overhead:
extension Array {
func unzip<T1, T2>() -> ([T1], [T2]) where Element == (T1, T2) {
var result = ([T1](), [T2]())
result.0.reserveCapacity(self.count)
result.1.reserveCapacity(self.count)
return reduce(into: result) { acc, pair in
acc.0.append(pair.0)
acc.1.append(pair.1)
}
}
}
Prior to Swift 4
I would apply the KISS principle:
extension Array {
func unzip<T1, T2>() -> ([T1], [T2]) where Element == (T1, T2) {
var result = ([T1](), [T2]())
result.0.reserveCapacity(self.count)
result.1.reserveCapacity(self.count)
for (a, b) in self {
result.0.append(a)
result.1.append(b)
}
return result
}
}
let arr = [
(key: "hey", value: ["ho"]),
(key: "ha", value: ["tee", "hee"])
]
let unzipped = (arr as [(String, [String])]).unzip()
print(unzipped)
Not pretty but the only thing I could come up with right now: using reduce:
let (keys, values) = arr.reduce(([], [])) { ($0.0.0 + [$0.1.key], $0.0.1 + [$0.1.value]) }
Would be a lot prettier without having to specify the initial values which add a lot of noise and make the code not easily.
Generified it already looks a bit cleaner:
func unzip<K,V>(_ array : [(K,V)]) -> ([K], [V]) {
return array.reduce(([], [])) { ($0.0 + [$1.0], $0.1 + [$1.1])}
}
let (keys, values) = unzip(arr)

Adding arrays of different types

Is there more elegant way of adding two arrays of different types in Swift?
My first try was to downcast an array to Any and just add it.
class BasicClass {
var name : String = ""
var number : Int = -1
init(name : String, number : Int){
self.name = name
self.number = number
}
}
let basicClass1 = BasicClass(name: "bc1", number: 1)
let basicClass2 = BasicClass(name: "bc2", number: 2)
let basicClass3 = BasicClass(name: "bc3", number: 3)
let basicClasses : [BasicClass] = [basicClass1, basicClass2, basicClass3]
for bc in basicClasses{
print(bc.name)
}
let strings : [String] = ["one", "two", "three"]
var anyArray : [Any] = strings as [Any] + basicClasses as [Any]
I'm asking about this last line.
Creating [Any] this way is a mistake in Swift. There are places that [Any] pops up because of bridging to Cocoa, but you should never create them independently. It is incredibly rare that you mean "an array of absolutely anything at all." If you don't mean "absolutely anything at all is fine," you don't mean Any. (For an example of something that means that, consider print, which correctly accepts "absolutely anything at all.")
If you want "an array of strings or BasicClasses" then there's a type for that. It's an enum:
enum Element {
case basicClass(BasicClass)
case string(String)
}
let combined = strings.map(Element.string) + basicClasses.map(Element.basicClass)
This wraps your strings and you BasicClasses in the Element type, and then creates an [Element].
(Note that I've used + here to combine the arrays because it's just so convenient in this case, but it can create significant problems with compilation time, and it's inefficient because it often forces too many copies. You generally should avoid using + for anything but numbers. But in this example it's just so much prettier than the alternative using .extend.)

2D Array extension Swift 3.1.1

I am trying to make an Array extension in Swift 3.1.1 that supports the addition of an object to a certain index in a 2D Array even if the array hasn't been populated yet. The extension should also provide the ability to get an object at certain indexPath. I have the code for this in Swift 2 but I don't seem to be able to migrate it to Swift 3. This is the Swift 2 code:
extension Array where Element: _ArrayProtocol, Element.Iterator.Element: Any {
mutating func addObject(_ anObject : Element.Iterator.Element, toSubarrayAtIndex idx : Int) {
while self.count <= idx {
let newSubArray = Element()
self.append(newSubArray)
}
var subArray = self[idx]
subArray.append(anObject)
}
func objectAtIndexPath(_ indexPath: IndexPath) -> Any {
let subArray = self[indexPath.section]
return subArray[indexPath.row] as Element.Iterator.Element
}
}
The code is taken from this answer.
As Martin says in his answer here, _ArrayProtocol is no longer public in Swift 3.1, therefore meaning that you cannot use it as a constraint in your extension.
A simple alternative in your case is to instead constrain the Array's Element to being a RangeReplaceableCollection – which both defines an init() requirement meaning "empty collection", and an append(_:) method in order to add elements to the collection.
extension Array where Element : RangeReplaceableCollection {
typealias InnerCollection = Element
typealias InnerElement = InnerCollection.Iterator.Element
mutating func fillingAppend(
_ newElement: InnerElement,
toSubCollectionAtIndex index: Index) {
if index >= count {
append(contentsOf: repeatElement(InnerCollection(), count: index + 1 - count))
}
self[index].append(newElement)
}
}
Note also that we're doing the append as a single call (using append(contentsOf:), ensuring that we only have to resize the outer array at most once.
For your method to get an element from a given IndexPath, you can just constrain the inner element type to being a Collection with an Int Index:
// could also make this an extension on Collection where the outer Index is also an Int.
extension Array where Element : Collection, Element.Index == Int {
subscript(indexPath indexPath: IndexPath) -> Element.Iterator.Element {
return self[indexPath.section][indexPath.row]
}
}
Note that I've made it a subscript rather than a method, as I feel it fits better with Array's API.
You can now simply use these extensions like so:
var arr = [[Int]]()
arr.fillingAppend(6, toSubCollectionAtIndex: 3)
print(arr) // [[], [], [], [6]]
let indexPath = IndexPath(row: 0, section: 3)
print(arr[indexPath: indexPath]) // 6
Although of course if you know the size of the outer array in advance, the fillingAppend(_:toSubCollectionAtIndex:) method is redundant, as you can just create your nested array by saying:
var arr = [[Int]](repeating: [], count: 5)
which will create an [[Int]] array containing 5 empty [Int] elements.
There's no need to limit all these ideas to the concrete Array type.
Here's my solution. This discussion was great in that I just learned about RangeReplaceableCollection. Merging (what I think is) the best of both worlds, I pushed all the operations down (up?) the Type hierarchy as far as possible.
Subscript works on much more than Array as #Hamish says. But also, there's no need to constrain the index type, so we have to get rid of IndexPath. We can always sugar this with typealias Index2d = ...
extension Collection where Self.Element: Collection {
subscript(_ indexTuple: (row: Self.Index, column: Self.Element.Index)) -> Self.Element.Element {
get {
return self[indexTuple.row][indexTuple.column]
}
}
}
Why not have a mutable version at the most generic possible level (between Collection and RangeReplaceableCollection) (unfortunately I don't think the getter can be inherited when we redefine subscript):
extension MutableCollection where Self.Element: MutableCollection {
subscript(_ indexTuple: (row: Self.Index, column: Self.Element.Index)) -> Self.Element.Element {
get {
return self[indexTuple.row][indexTuple.column]
}
set {
self[indexTuple.row][indexTuple.column] = newValue
}
}
}
Then, if you want to initialize lazily, avoid using init:repeatedValue and revise set to have auto-initialization semantics. You can trap bounds overflow and add missing empty elements in both dimensions by integrating the accepted answer's fillingAppend idea.
And when creating a 2D initializer, why not extend the idea of repeating in the natural way:
extension RangeReplaceableCollection where Element: RangeReplaceableCollection {
init(repeating repeatedVal: Element.Element, extents: (row: Int, column: Int)) {
let repeatingColumn = Element(repeating: repeatedVal, count: extents.column)
self.init(repeating: repeatingColumn, count: extents.row)
}
}
Example Usage:
enum Player {
case first
case second
}
class Model {
let playerGrid: Array<Array<Player>> = {
var p = [[Player]](repeating: .first, extents: (row: 10, column: 10))
p[(3, 4)] = .second
print("Player at 3, 4 is: \(p[(row: 3, column: 4)])")
return p
}()
}

Resources