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
Related
I am working removing the duplicate Dictionaries in an array of Dictionaries in swift 3.0
The below is the
let Dict1 : [String : String] = ["messageTo":"Madhu"]
let Dict2 : [String : String] = ["messageTo":"Kiran"]
let Dict3 : [String : String] = ["messageTo":"Raju"]
var arrOfDict = [[String:String]]()
arrOfDict.append(Dict1)
arrOfDict.append(Dict2)
arrOfDict.append(Dict1)
arrOfDict.append(Dict3)
arrOfDict.append(Dict2
print(arrOfDict)
//prints [["messageTo": "Madhu"], ["messageTo": "Kiran"], ["messageTo": "Madhu"], ["messageTo": "Raju"], ["messageTo": "Kiran"]]
As you can see there are 2 duplicate dictionaries in the arrOfDict.
can any one help me out in filtering the duplicates using Set or any other approach
Dictionaries does not conform to Hashable (or Equatable), so using Set is not an available approach in this case. For dictionaries where Key and Value types are Equatable, however, we have access to the == operator for readily performing a uniqueness filtering of the array of dictionaries:
public func ==<Key : Equatable, Value : Equatable>(
lhs: [Key : Value], rhs: [Key : Value]) -> Bool
E.g. as follows (O(n^2))
arrOfDict = arrOfDict.enumerated()
.filter { (idx, dict) in !arrOfDict[0..<idx].contains(where: {$0 == dict}) }
.map { $1 }
print(arrOfDict)
// [["messageTo": "Madhu"], ["messageTo": "Kiran"], ["messageTo": "Raju"]]
// or ...
arrOfDict = arrOfDict.enumerated()
.flatMap { (idx, dict) in !arrOfDict[0..<idx].contains(where: {$0 == dict}) ? dict : nil }
func removeDuplicates(_ arrayOfDicts: [[String: String]]) -> [[String: String]] {
var removeDuplicates = [[String: String]]()
var arrOfDict = [String]()
for dict in arrayOfDicts {
if let name = dict["messageTo"], ! arrOfDict.contains(name) {
removeDuplicates.append(dict)
arrOfDict.append(name)
}
}
return removeDuplicates
}
The reason why it is duplicated, because you add Dict1 and Dict2 2 times. May i ask why?
Set would not work for you, because Dictionary type does not conform to the Hashable protocol, only its key property.
You can do a validation on the dictionary, if there is already a value like that, do not append it to arrOfDict.
you can try this :
var set = NSSet(array: arrOfDict)
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) })
I am trying to decode data from JSON into a very generic struct using Argo (https://github.com/thoughtbot/Argo):
struct ValueBox<T: Decodable where T == T.DecodedType> {
let value: T
}
extension ValueBox: Decodable {
static func decode(json: JSON) -> Decoded<ValueBox> {
let r = curry(ValueBox.init)
<^> json <| "value"
return r
}
}
extension Array: Decodable {
public typealias DecodedType = Array<Element>
}
extension Array {
public static func decode(json: JSON) -> Decoded<Array<Element>> {
return Decoded<Array>.customError("not implemented")
}
}
This compiles. I know it would not be able to decode ValueBox in case of T being an array. But that is a second problem.
If I now try to use Argo for decoding:
func testExample() {
let jsonDict_Int: [String : AnyObject] = [
"value" : 5
]
let jsonDict_IntArray: [String : AnyObject] = [
"value" : [5]
]
let intBox: Decoded<ValueBox<Int>> = decode(jsonDict_Int)
let intArrayBox: Decoded<ValueBox<Array<Int>>> = decode(jsonDict_IntArray)
}
I get a compiler error that "Array<Int> does not conform to protocol 'Decodable'". But why? I provided the extension to make it conform, or am I missing something obvious?
For the second example, I think you must use <^> json <|| "value" instead of <^> json <| "value"
When you use an Array of value you must use the operator <||
a great link to the detailed Argo's documentation
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)
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()