Cannot append in Array extension - arrays

I have extended the Array class in order to create some methods. However I am not being able to append to the array:
private extension Array
{
mutating func addCharacterWithIndex(char: String, index: Int)
{
let dict = [
"char": char,
"index": index
]
self.append(dict) //Shows error: Cannot invoke 'append' with argument list of type '(NSDictionary)'
}
}
The only way I could make this go away was by using
self.append(dict as! Element)
However the element is not being added to the array at all.
What am I not understanding about this situation? Is this even possible?

Updated
I just realized DictionaryLiteralConvertible can be used here.
extension Array where Element: DictionaryLiteralConvertible, Element.Key == String, Element.Value == Int {
mutating func addCharacterWithIndex(char: String, index: Int)
{
self.append(Element.init(dictionaryLiteral: (char, index)))
}
}
Original answer...
Since Array in Swift is a generic struct, it's not possible (at least right now) to extend the Array with a specific type constraint.
For you question, one way to do is to make it optional.
extension Array
{
mutating func addCharacterWithIndex(char: String, index: Int)
{
if let dict = [
"char": char,
"index": index
] as? Element {
self.append(dict)
}
}
}
So you can use it like this:
var symbols: [[String: AnyObject]] = []
symbols.addCharacterWithIndex("*", index: 9)
However, this is not consistent. For other arrays such as [Int], [String] this method is accessible but does nothing.
Another way, given that you're only using append method, you can extend RangeReplaceableCollectionType instead.
extension RangeReplaceableCollectionType where Generator.Element == NSDictionary {
mutating func addCharacterWithIndex(char: String, index: Int)
{
let dict = [
"char": char,
"index": index
]
self.append(dict)
}
}
And use it like this:
var symbols: [NSDictionary] = []
symbols.addCharacterWithIndex("*", index: 9)

Related

Swift generics and subclasses

my question might be simple, but it got me puzzled a bit:
Imagine I have an array of different objects which all have a common parent class "MyClass".
var values = [MyClass]()
However they all have their specific subclass like for example "MyClassSubclass".
values.append(MyClassSubclass())
I now want to create a generic method returning me the first object inside this array which is of type MyClassSubclass. I would like to prevent casting the object, but instead have a generic method which takes the subclass object as T parameter and returns me the first occurrence of subclass T inside this array.
I thought of something like this (but surely that does not work):
func getFirst<T: MyClass>(_ ofType : T.Type) -> T?
I guess I'm just stuck and I don't know what to search for, so if someone could help me I would greatly appreciate it.
Edit Example based on the above values:
class MyClass {}
class MyClassSubclass : MyClass {}
class MyClassSubclass2 : MyClass{}
var values = [MyClass]()
values.append(MyClassSubclass())
values.append(MyClassSubclass2())
//Return the first class element appearing here as a subclass type
func getFirst<T>(_ ofType : T.Type) -> T?{}
Thanks
One approach is to iterate over the array and use optional binding
to check if an element is of the given type:
func getFirst<T: MyClass>(ofType: T.Type) -> T? {
for elem in values {
if let item = elem as? T {
return item
}
}
return nil
}
This can be simplified using for case with the as pattern:
func getFirst<T: MyClass>(ofType: T.Type) -> T? {
for case let item as T in values {
return item
}
return nil
}
Another approach is to use flatMap to find all items of the given
type and return the first one:
func getFirst<T: MyClass>(ofType: T.Type) -> T? {
return values.flatMap { $0 as? T }.first
}
If the array can be large and you want to avoid the creation of an
intermediate array then you can use lazy:
func getFirst<T: MyClass>(ofType: T.Type) -> T? {
return values.lazy.flatMap { $0 as? T }.first
}
As an array extension method this would be
extension Array {
func getFirst<T>(ofType: T.Type) -> T? {
return flatMap { $0 as? T }.first
}
}
If you'd like to use global function, which is not recommended, try this
func getFirst<T>(ofType: T.Type) -> T? where T: MyClass {
for value in values where value is T {
return value as? T
}
return nil
}
let first = getFirst(ofType: MyClassSubclass2.self)
The first answer should be better for Swift.
This feels a bit like abuse of generics, but here's an extension:
extension Array where Element == MyClass {
func getFirst<T>(_ ofType: T.Type) -> T? {
return self.first(where: { ofType == type(of: $0) }) as? T
}
}
The method can then be called as let first = values.getFirst(MyClassSubclass.self).
I'd personally prefer simply casting inline for clarity:
let first = values.first(where: { type(of: $0) == MyClassSubclass.self }) as? MyClassSubclass

Why can't I pass an array to a vararg in Swift?

I have this function:
func doStuff(stuff: Int...) {
print(stuff)
}
and I call it like this:
let array = [1, 2, 3]
doStuff(array)
And it does not compile!
I mean, this makes no sense, right? The function is supposed to accept a list of things, and I am giving it a list of things. How come this doesn't work?
Here's some background info (you can skip it)
I have this NSManagedObject subclass:
class Entry: NSManagedObject {
override init(entity: NSEntityDescription, insertIntoManagedObjectContext context: NSManagedObjectContext?) {
// irrelevent
}
convenience init(entity: NSEntityDescription, insertIntoManagedObjectContext context: NSManagedObjectContext, title: String, content: String, date: NSDate) {
// irrelevent
}
}
extension Entry {
#NSManaged var content: String?
#NSManaged var date: NSDate?
#NSManaged var title: String?
}
In one of my view controllers, I fetch all the Entrys in viewDidLoad and I stored the fetched stuff in a variable called anyObjs which is of type [AnyObject]
I want to turn this [AnyObject] to a [NSDate: Entry], where the keys are the values' date property. I want it this way in order to easily access an Entry using an NSDate.
So I tried the following:
let literal = anyObjs!.map { (($0 as! Entry).date!, $0 as! Entry) }
entries = [NSDate: Entry](dictionaryLiteral: literal)
And I found out that I can't pass a [(NSDate, Entry)] to (NSDate, Entry)...!
"That's easy" you might say, "just pass all the elements in the array as varargs using the subscript!"
doStuff(array[0], array[1], array[2])
But this doesn't work if I don't know how many items there are.
Another workaround that doesn't work is to create a function that accepts an array:
func doStuff(array: [Int]) {
print(array)
}
This doesn't work either because if I don't know the exact implementation of the function, I cannot rewrite it in the new function.
What can I do?
You are right! There ought to be a method for getting a dictionary from an array of tuples.
extension Dictionary {
init(tuples: [Element]) {
self.init()
for (key, value) in tuples {
self.updateValue(value, forKey: key)
}
}
}
OK, now that's done, let's see.
let tuples = anyObjs!.map { (($0 as! Entry).date!, $0 as! Entry) }
let entries = [NSDate: Entry](tuples: tuples)
Or combine the two lines
let entries = [NSDate: Entry](tuples: anyObjs!.map { (($0 as! Entry).date!, $0 as! Entry) })

Extend Swift Array to Filter Elements by Type

How can a swift array be extended to access members of a particular type?
This is relevant if an array contains instances of multiple classes which inherit from the same superclass. Ideally it would enforce type checking appropriately.
Some thoughts and things that don't quite work:
Using the filter(_:) method works fine, but does enforce type safety. For example:
protocol MyProtocol { }
struct TypeA: MyProtocol { }
struct TypeB: MyProtocol { }
let myStructs:[MyProtocol] = [ TypeA(), TypeA(), TypeB() ]
let filteredArray = myStructs.filter({ $0 is TypeA })
the filteredArray contains the correct values, but the type remains [MyProtocol] not [TypeA]. I would expect replacing the last with let filteredArray = myStructs.filter({ $0 is TypeA }) as! [TypeA] would resolve that, but the project fails with EXEC_BAD_INSTRUCTION which I do not understand. Perhaps type casting arrays is not possible?
Ideally this behavior could be wrapped up in an array extension. The following doesn't compile:
extension Array {
func objectsOfType<T:Element>(type:T.Type) -> [T] {
return filter { $0 is T } as! [T]
}
}
Here there seem to be at least two problems: the type constraint T:Element doesn't seem to work. I'm not sure what the correct way to add a constraint based on a generic type. My intention here is to say T is a subtype of Element. Additionally there are compile time errors on line 3, but this could just be the same error propagating.
SequenceType has a flatMap() method which acts as an "optional filter":
extension SequenceType {
/// Return an `Array` containing the non-nil results of mapping
/// `transform` over `self`.
///
/// - Complexity: O(*M* + *N*), where *M* is the length of `self`
/// and *N* is the length of the result.
#warn_unused_result
#rethrows public func flatMap<T>(#noescape transform: (Self.Generator.Element) throws -> T?) rethrows -> [T]
}
Combined with matt's suggestion to use as? instead of is you
can use it as
let myStructs:[MyProtocol] = [ TypeA(), TypeA(), TypeB() ]
let filteredArray = myStructs.flatMap { $0 as? TypeA }
Now the type of filteredArray is inferred as [TypeA].
As an extension method it would be
extension Array {
func objectsOfType<T>(type:T.Type) -> [T] {
return flatMap { $0 as? T }
}
}
let filteredArray = myStructs.objectsOfType(TypeA.self)
Note: For Swift >= 4.1, replace flatMap by compactMap.
Instead of testing (with is) how about casting (with as)?
let myStructs:[MyProtocol] = [ TypeA(), TypeA(), TypeB() ]
var filteredArray = [TypeA]()
for case let t as TypeA in myStructs {filteredArray.append(t)}
Casting arrays does not work in Swift. This is because arrays in Swift use generics, just like you can't cast a custom class, where only the type T changes. (class Custom<T>, Custom<Int>() as! Custom<String>).
What you can do is create an extension method to Array, where you define a method like this:
extension Array {
func cast<TOut>() -> [TOut] {
var result: [TOut] = []
for item in self where item is TOut {
result.append(item as! TOut)
}
return result
}
}
I think the canonical FP answer would be to use filter, as you are, in combination with map:
let filteredArray = myStructs.filter({ $0 is TypeA }).map({ $0 as! TypeA })
alternatively, you can use reduce:
let filtered2 = myStructs.reduce([TypeA]()) {
if let item = $1 as? TypeA {
return $0 + [item]
} else {
return $0
}
}
or, somewhat less FP friendly since it mutates an array:
let filtered3 = myStructs.reduce([TypeA]()) { ( var array, value ) in
if let item = value as? TypeA {
array.append(item)
}
return array
}
which can actually be shortened into the once again FP friendly flatMap:
let filtered4 = myStructs.flatMap { $0 as? TypeA }
And put it in an extension as:
extension Array {
func elementsWithType<T>() -> [T] {
return flatMap { $0 as? T }
}
}
let filtered5 : [TypeA] = myStructs.elementsWithType()

indexOf of AnyObject array not working with Strings

So I'm having the following code in a playgroung
var array: [AnyObject] = ["", "2", "3"]
let index = array.indexOf("")
And XCode is marking a compiler error
Cannot convert value of type 'String' to expected argument type '#noescape (AnyObject) throws -> Bool'
So my question is how do I get the indexOf an element in an Array in of AnyObjects?
You can also cast to [String] if you're sure it will cast safely
jvar array: [AnyObject] = ["", "2", "3"]
let index = (array as! [String]).indexOf("")
Try this
var array = ["", "2", "3"]
let index = array.indexOf("")
or you can use the NSArray method:
var array: [AnyObject] = ["", "2", "3"]
let index = (array as NSArray).indexOfObject("")
You should never use AnyObject for a placeholder for any type, use Any instead. Reason: AnyObject only works with classes, Swift uses a lot of structs though (Array, Int, String, etc.). Your code actually uses NSStrings instead of Swifts native String type because AnyObject wants a class (NSString is a class).
In more general cases, collectionType.indexOf will work when the object inside an array conforms to Equatable protocol. Since Swift String has already conformed to Equatable, then casts AnyObject to String will remove the error.
How to use indexOf on collection type custom class ? Swift 2.3
class Student{
let studentId: Int
let name: String
init(studentId: Int, name: String){
self.studentId = studentId
self.name = name
}
}
//notice you should implement this on a global scope
extension Student: Equatable{
}
func ==(lhs: Student, rhs: Student) -> Bool {
return lhs.studentId == rhs.studentId //the indexOf will compare the elements based on this
}
func !=(lhs: Student, rhs: Student) -> Bool {
return !(lhs == rhs)
}
Now you can use it like this
let john = Student(1, "John")
let kate = Student(2, "Kate")
let students: [Student] = [john, kate]
print(students.indexOf(John)) //0
print(students.indexOf(Kate)) //1

How can I tell what the index of an item in a multidimensional array would be if the array were flattened?

I have a multidimensional array with some repeated elements in the deepest level:
[
["taco","burrito"],
["chalupa","taco","bread"]
["pizza","lasagna"],
["pizza","taco","burrito"]
["salad","sandwich"],
["meat","turkey"]
["cups","chicken"],
["rabbit","taco", "chicken", "salad"]
]
I have flattened this array into...
[
"taco",
"burrito",
"chalupa",
"taco",
"bread",
"pizza",
"lasagna",
etc...
]
I have found the second occurrence of the word "taco" in the multidimensional array and know its index.
How can I translate that index to an index in the flattened array?
So in this example it would be...
multiDimensionalArray[0][1][1]
=
flatArray[3]
Swift doesn't have native support for flattening arrays. You could roll this out yourself, or you could use ExSwift which has a flatten method. If you flatten the array successfully, obviously you could then determine which index the value is at using indexOfObject. Since you have multiple identical objects, you may want to use indexesOfObjectsPassingTest, which will give you an index set for all indexes matching the test you provide.
First of all you need a function to flatten the array into an array of strings in order to gain the true indices of the elements.
public func flattenArray(array: Array<AnyObject>) -> Array<String> {
var flattened: Array<String> = []
for object: AnyObject in array {
if object is Array<AnyObject> {
for string in flattenArray(object as Array<AnyObject>) {
flattened.append(string)
}
} else if object is String {
flattened.append(object as String)
}
}
return flattened
}
Then you need to write a function that will obtain the index of an element in an array at a certain number of occurrences
public func indexOfString(string: String, inArray array: Array<String>, occurance: Int = 1) -> Int? {
var occurancesSoFar = 0
var index: Int? = nil
var currentIndex = 0
for object in array {
if object == string {
if (++occurancesSoFar == occurance) {
index = currentIndex
}
}
++currentIndex
}
return index
}
Calling the above functions in order gives the correct index value:
let array = [
["taco","burrito"],
["chalupa","taco","bread"],
["pizza","lasagna"],
["pizza","taco","burrito"],
["salad","sandwich"],
["meat","turkey"],
["cups","chicken"],
["rabbit","taco", "chicken", "salad"]
]
let flat = flattenArray(array)
let index = indexOfString("taco", inArray: flat, occurance: 2)
println(index) // Optional(3)
Using these methods you could encapsulate them into a single function perhaps called
func flattenAndFindElement(element: String,
inArray array: Array<AnyObject>,
atOccurrence occurrence: Int) -> Int?
or something similar.
Hope that helps answer your question.
Here is what I ended up doing:
func findFlattenedIndex(array: Array<Array<String>>, firstIndex: Int, secondIndex: Int) -> Int {
var flatIndex = 0
for (index1,secondTier) in enumerate(array) {
for element in secondTier {
if index1 != firstIndex{
println(element)
flatIndex += 1
}
}
if index1 == firstIndex {
flatIndex += secondIndex
return flatIndex
break
}
}
return flatIndex
}
Of course this could be modified to deal with any number of dimensions.
Thanks for everyone's help with this.

Resources