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.)
Related
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 }
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))" })
I'm going through the Swift Standard Library, and I came across the method elementsEqual for comparing sequences.
I'm not really seeing the value of this function because it will only return true if the order is exactly the same. I figured this would have some use if it could tell me if two sequences contained the same elements, they just happen to be in a different order, as that would save me the trouble of sorting both myself.
Which brings me to my question:
Is there any difference between using elementsEqual and '==' when comparing two sequences? Are there pros and cons for one vs the other?
I am in my playground, and have written the following test:
let values = [1,2,3,4,5,6,7,8,9,10]
let otherValues = [1,2,3,4,5,6,7,8,9,10]
values == otherValues
values.elementsEqual(otherValues)
both of these checks result in true, so I am not able to discern a difference here.
After playing with this for a while to find a practical example for the below original answer I found a much more simple difference: With elementsEqual you can compare collections of different types such as Array, RandomAccessSlice and Set, while with == you can't do that:
let array = [1, 2, 3]
let slice = 1...3
let set: Set<Int> = [1, 2, 3] // remember that Sets are not ordered
array.elementsEqual(slice) // true
array.elementsEqual(set) // false
array == slice // ERROR
array == set // ERROR
As to what exactly is different, #Hamish provided links to the implementation in the comments below, which I will share for better visibility:
elementsEqual
==
My original answer:
Here's a sample playground for you, that illustrates that there is a difference:
import Foundation
struct TestObject: Equatable {
let id: Int
static func ==(lhs: TestObject, rhs: TestObject) -> Bool {
return false
}
}
// TestObjects are never equal - even with the same ID
let test1 = TestObject(id: 1)
let test2 = TestObject(id: 1)
test1 == test2 // returns false
var testArray = [test1, test2]
var copiedTestArray = testArray
testArray == copiedTestArray // returns true
testArray.elementsEqual(copiedTestArray) // returns false
Maybe someone knows for sure, but my guess is that == computes something like memoryLocationIsEqual || elementsEqual (which stops evaluating after the memory location is indeed equal) and elementsEqual skips the memory location part, which makes == faster, but elementsEqual more reliable.
Hey I gotta problem when I declared and created an array of my class.
I declared an array like this
var _mScoringData = [ScoringData]()
And here's 'ScoringData' class
class ScoringData{
let num : Int!
let question : String!
var yes : Bool!
var no : Bool!
init(num:Int,question:String){
self.num = num
self.question = question
self.no = false
self.yes = false
}
}
I made a function to create an array of instances, following code is an implementation of the function
func createScoringDatas(){
for var index = 0; index < scoreDataCount; ++index{
_mScoringData.append(ScoringData(num:index,question: _mQuestionData.Questions[index]))
}
}
Build was good however the array does not created and when I debugged nothing was on the heap.
I want to know how to solve this problem.
thanks.
It’s impossible to tell without your full code, but chances are the reason is because your variable scoreDataCount is zero. If I take your code, and make sure scoreDataCount is non-zero, it works fine.
Is scoreDataCount just a count of the number of questions in _mQuestionData.Questions? If so, you may want to consider the following alternative to the C-style for and array subscripting:
for (index, question) in enumerate(_mQuestionData.Questions) {
_mScoringData.append(ScoringData(num: index, question: question))
}
enumerate(someSequence) takes a sequence (such as an array), and creates a new sequence of pairs, an index starting at zero, and the elements of the sequence. You can then loop over that with for…in which can look a lot cleaner and help avoid bugs (like the one you’ve hit).
Once you understand this, it’s a short step from there to scrap the for loop altogether and replace it with a map:
_mScoringData = map(enumerate(_mQuestionData.Questions)) {
index, question in
ScoringData(num: index, question: question)
}
Note, this creates a map, rather than appending to an existing one, so you may no longer need your initial creation line.
Whenever you have a sequence of things, and you want to transform each element and put it in an array, that’s a case for map. You could argue that the for…in is clearer (especially if you’re not used to seeing map) but the benefit of map is it makes it completely obvious what your intent is - to transform the input into a new array.
The other benefit of this is, you could now declare _mScoringData with let instead of var if it turns out you never need to change it once it’s first created.
P.S. you probably don’t need those ! after all the member variables in ScoringData. Those are creating implicitly unwrapped optionals, and are only needed in very specific circumstances. You might see them sometimes elsewhere when they’re needed but you don’t need to copy that on every member variable.
I'm not sure why you're seeing the problem you're having. I took the code and pasted it into a playground to run it and it works fine. I'm pasting my slightly modified version of the code below. My only changes were to declare the scoreDataCount value and to change the source of the questions to an array of strings.
class ScoringData{
let num : Int!
let question : String!
var yes : Bool!
var no : Bool!
init(num:Int,question:String){
self.num = num
self.question = question
self.no = false
self.yes = false
}
}
let scoreDataCount = 5
var _mScoringData = [ScoringData]()
let questions = ["One", "two", "three", "four", "five" ]
func createScoringDatas() {
for var index = 0; index < scoreDataCount; ++index{
_mScoringData.append(ScoringData(num:index,question: questions[index]))
}
}
createScoringDatas()
print(_mScoringData.count)
print(_mScoringData[2].question)
The last 2 print statements output 5 and "three" respectively which is what I would expect.
I'm just wondering if there's a more readable/less verbose way to write the following:
let rankMap: Dictionary<Int,String> = [1:"Ace", 11:"Jack", 12:"Queen", 13:"King"]
func getCardImage(suit: String, rank: Int) -> NSImage {
var imgRank: String
if rankMap[rank] == nil {
imgRank = rank.description
} else {
imgRank = (rankMap[rank]! as NSString).substringToIndex(1)
}
let imgSuit = (suit as NSString).substringToIndex(1)
//let imgRank: String = rankMap[rank] ?? rank.description
return NSImage(named: imgRank + imgSuit)
}
In particular, I'm interested in the part where rankMap is checked vs nil. Whatever is returned from the array needs to have every character except the first chopped off. I'm unable to write something like this:
let imgRank: String = (rankMap[rank] as NSString).substringToIndex(1) ?? rank.description
..due to the semantics around nil coalescing. Is there anything else I can do aside from the "hard way" of just doing an "if nil" check?
I removed the NSImage part, just so it's easier to test as a filename, but this should behave similarly.
func cardImage(suit: String, rank: Int) -> String {
let imgRankFull = rankMap[rank] ?? rank.description
let imgRank = first(imgRankFull) ?? " "
let imgSuit = first(suit) ?? " "
return imgRank + imgSuit
}
This shows how to use ??, but it still has lots of little problems IMO. I would recommend replacing suit and rank with enums. The current scheme has lots of ways it can lead to an error, and you don't do any good error checking. I've dodged by returning " ", but really these should be either fatalError or an optional, or a Result (an object that carries a value or an error).
You don't need substringToIndex to get the first character of a Swift string. String is a CollectionType, to which this generic function from the standard library applies:
func first<C : CollectionType>(x: C) -> C.Generator.Element?
So you might start by trying something like this:
let imgRank = first(rankMap[rank]) ?? rank.description // error
That doesn't work, for two reasons:
String.Generator.Element is a Character, not a String, so if you're going to put it in a ?? expression, the right operand of ?? needs to also be a Character. You can construct a Character from a String (with Character(rank.description)), but that'll crash if that string has more than one character in it.
rankMap[rank] is an optional, so you can't pass it directly to first. But if you try to force-unwrap it immediately (with first(rankMap[rank]!), you'll crash upon looking up something not in the dictionary.
You could try solving these problems together by putting the ?? inside the first call, but you probably want a two-character string for rank 10.
It'd be nice if we could treat String like an Array, which provides var first for getting its first element. (And have a version of first for strings that returns a String.) Then we could take advantage of optional chaining to put a whole expression to the left of the ??, and just write:
let imgRank = rankMap[rank]?.first ?? rank.description
Well, let's write it in an extension!
extension String {
var first : String? {
// we're overloading the name of the global first() function,
// so access it with its module name
switch Swift.first(self) {
case let .Some(char):
return String(char)
case .None:
return nil
}
}
}
Now you can be nice and concise about it:
let imgRank = rankMap[rank]?.first ?? String(rank)
(Notice I'm also changing rank.description to String(rank). It's good to be clear -- description gives you a string representation of a value that's appropriate for printing in the debugger; String() converts a value to a string. The former is not guaranteed to always match the latter, so use the latter if that's what you really want.)
Anyhow, #RobNapier's advice stands: you're probably better off using enums for these. That gets you a lot less uncertainty about optionals and invalid lookups. But seeing what you can do with the standard library is always a useful exercise.