Swift: NSArray to Set? - swift

I am trying to convert an NSArray to a Swift Set.
Not having much luck.
What is the proper way to do so?
For example if I have an NSArray of numbers:
#[1,2,3,4,5,6,7,8]
How do I create a Swift Set from that NSArray?

If you know for sure that the NSArray contains only number objects
then you can convert it to an Swift array of Int (or Double or
NSNumber, depending on your needs) and create a set from that:
let nsArray = NSArray(array: [1,2,3,4,5,6,7,8])
let set = Set(nsArray as! [Int])
If that is not guaranteed, use an optional cast:
if let set = (nsArray as? [Int]).map(Set.init) {
print(set)
} else {
// not an array of numbers
}
Another variant (motivated by #JAL's comments):
let set = Set(nsArray.flatMap { $0 as? Int })
// Swift 4.1 and later:
let set = Set(nsArray.compactMap { $0 as? Int })
This gives a set of all NSArray elements which are convertible
to Int, and silently ignores all other elements.

import Foundation
let nsarr: NSArray = NSArray(array: [1,2,3,4,5])
var set: Set<Int>
guard let arr = nsarr as? Array<Int> else {
exit(-1)
}
set = Set(arr)
print(set.dynamicType)
dump(set)
/*
Set<Int>
▿ 5 members
- [0]: 5
- [1]: 2
- [2]: 3
- [3]: 1
- [4]: 4
*/
with help of free bridging, it should be easy ...

If you know you're working with Ints, you could always iterate through your array and manually add each element to a Set<Int>:
let theArray = NSArray(array: [1,2,3,4,5,6,7,8])
var theSet = Set<Int>()
for number in (theArray as? [Int])! {
theSet.insert(number)
}
print(theSet) // "[2, 4, 5, 6, 7, 3, 1, 8]\n"
I'm trying to work out a more elegant solution with map, I'll update this answer as I make more progress.
Thanks to MartinR's suggestion to use unionInPlace (which takes in the SequenceType returned from map) instead of insert on the Set, this can be accomplished like so:
let theArray = NSArray(array: [1,2,3,4,5,6,7,8])
var mySet = Set<Int>()
mySet.unionInPlace(theArray.map { $0 as! Int })
Note that this may not be the safest solution due to the explicit cast to Int.

var array = [1,2,3,4,5]
var set = Set(array)

Related

<= Operand asking for expected type in Swift?

I'm trying to look for items equal or less than zero in my query like so.....
for zeroItems in snapshot.value as! NSDictionary where zeroItems.value["Value"] as! Int <= 0
I'm getting an Expected type error. Please explain and show me how to correct.
You may want to consider applying a filter to simplify the logic to only include elements less than zero. I've created a working example in the IBM Swift Sandbox.
import Foundation
// Construct the data structure with demo data
struct Snapshot {
let value: NSDictionary
}
let dict: NSDictionary = [ "a" : -1, "b" : 0, "c" : 1]
let snapshot = Snapshot(value: dict)
//Apply a filter
let zeroItems = snapshot.value.filter{Int($0.1 as! NSNumber) <= 0}
//View the result
print(zeroItems)
Almost certainly you've overused Any if you have to use this many as! casts in a single line. Create a custom struct for this type and convert the data to that. If you're passing around NSDictionary and using as! very much, you're fighting the system.
That said, to make this work you probably just need more parentheses. Something like:
for zeroItems in (snapshot.value as! NSDictionary) where (zeroItems.value["Value"] as! Int) <= 0
But this is horrible Swift, so avoid this if possible.
You interpreting snapshot.value as Dictionary, so zeroItems is a tuple of key and value.
As I understand you having array of dictionaries, and you want to filter them by "Value" key, right?
If so then you may use following code:
// dictionary with Value key, and Int value
let dict: [String: Any] = ["Value":-1]
// array of dictionaries
let value: [[String: Any]] = [dict, <**add here as many as you want**>]
// get only array of integeres
let onlyIntegers = value.flatMap { $0["Value"] as? Int }.filter {$0 <= 0 }
print(onlyIntegers)
// get array of dictionaries which passes <=0 check
let onlyLessOrEqualTo0 = value.filter { dict in
if let value = dict["Value"] as? Int {
return value <= 0
}
return false
}
print(onlyLessOrEqualTo0)
First of all, adding some parentheses will help in seeing where the problem is:
for zeroItems in myDict as! NSDictionary where (zeroItems.value["Value"] as! Int) <= 0
Now, we get "Type 'Any' has no subscript members". So the problem is that you haven't told Swift what the type is of zeroItems's value, which I think is a dictionary of , judging from your code:
This compiles for me:
for zeroItems in myDict as! Dictionary<String, Dictionary<String, Int>> where zeroItems.value["Value"]! <= 0
However, this is not pretty. You could probably get more readable code using filter as was suggested in another answer.

A double number's type is Int in Swift

// Swift
import UIKit
let arr = [1, "a", 2.88]
let last = arr.last
if last is Int {
print("The last is Int.") // The last is Int.
} else {
print("The last is not Int.")
}
I can't understand the result printed.
Why it print "The last is Int".
// Swift
import UIKit
let arr = [1, "a", -2]
let last = arr.last
if last is Double {
print("The last is Double.") // The last is Double.
} else {
print("The last is not Double.")
}
And this print "The last is Double".Why?
Could somebody can help me?
Than you vary much.
Swift arrays can only hold one type. When you declared:
let arr = [1, "a", 2.88]
Swift made arr of type [NSObject]. You can verify this by Option-clicking on arr to see its type. This only works because you have Foundation imported (your import UIKit imports Foundation as well). Try removing import UIKit.
Then, the values 1 and 2.88 were converted to NSNumber and "a" to NSString so that they can be stored in that [NSObject] array because Ints, Strings, and Doubles are not NSObjects. NSNumber and NSString are subclasses of NSObject. Swift picks the most restrictive type for the array. Had your array been [1, true, 2.88], the array type would have been [NSNumber].
The interesting thing about an NSNumber is that it is an object container that wraps many different types. You can put an Int in and take out a Double. So, it is misleading then when you test it with is. It responds "true" meaning, "I can be that if you want".
import Foundation
let n: NSNumber = 3.14
print(n is Int) // "true"
print(n is Double) // "true"
print(n is Bool) // "true"
print(n as! Int) // "3"
print(n as! Double) // "3.14"
print(n as! Bool) // "true"
Note: Had you declared your arr to be [Any], then this would have worked as you expected:
let arr:[Any] = [1, "a", 2.88]
let last = arr.last
if last is Int {
print("The last is Int.")
} else {
print("The last is not Int.") // "The last is not Int."
}
But Swift is not going to create an array of type Any for you unless you ask explicitly (because quite frankly it is an abomination in a strongly typed language). You should probably rethink your design if you find yourself using [Any].
The only reason Swift creates [NSObject] when Foundation is imported is to make our lives easier when calling Cocoa and Cocoa Touch APIs. If such an API requires an NSArray to be passed, you can send [1, "a", 2.88] instead of [NSNumber(integer: 1), NSString(string: "a"), NSNumber(double: 2.88)].
The problem is your arr, if you declare
let arr = [1, "a", 2.88] // arr is [NSObject]
so
let arr = [1, "a", 2.88]
let last = arr.last // last is a NSObject?
if last is Int { // is Int or is Double will get the same result
print("The last is Int.") // because the number of NSNumber is larger than the number of NSString
} else {
print("The last is not Int.")
}
if you declare like this:
let arr = [1, "a", "b"]
let last = arr.last
if last is Int {
print("The last is Int.")
} else {
print("The last is not Int.") // Will print because the number of NSString is larger than NSNumber.
}
You can declare the array with type [Any] to get the desired result:
let arr: [Any] = [1, "a", 2.88]
arr.last is Int // false
When you have imported Foundation.framework you inherit a certain magic behaviour with numbers: if you create array literal with numerical literals that can't be represented with an array of that value type, an array is created that wraps the numbers boxed in a NSNumber. If you don't import Foundation (i.e. you rely on just the Swift standard library), then you in fact can't declare the array in such cases just with let arr = [1, "a", 2.88] (whereas for instance let arr = [1,2] would still work – that would produce an [Int]) but need to state let arr:[Any] = [1, "a", 2.88] – at which case no boxing to NSNumber happens (NSNumber is not even available) and the original type is somehow retained.
NSNumber is a box that can be used to wrap all manners of scalar (numerical) basic types, and you can use the as or is operator in Swift to successfully treat any NSNumber as any of the numerical types it can wrap. For instance the following holds:
var a = NSNumber(bool: true)
print("\(a is Bool)") // prints "true"
print("\(a is Int)") // prints "true"
print("\(a is Double)") // prints "true"
print("\(a is Float)") // prints "true"
It is possible to figure out the kind of numerical type you used sometimes, but it's just not exposed for some reason to NSNumber but is only available in the corresponding CoreFoundation type CFNumber:
extension NSNumber {
var isBoolean:Bool {
return CFNumberGetType(self as CFNumber) == CFNumberType.CharType
}
var isFloatingPoint:Bool {
return CFNumberIsFloatType(self as CFNumber)
}
var isIntegral:Bool {
return CFNumberGetType(self as CFNumber).isIntegral
}
}
extension CFNumberType {
var isIntegral:Bool {
let raw = self.rawValue
return (raw >= CFNumberType.SInt8Type.rawValue && raw <= CFNumberType.SInt64Type.rawValue)
|| raw == CFNumberType.NSIntegerType.rawValue
|| raw == CFNumberType.LongType.rawValue
|| raw == CFNumberType.LongLongType.rawValue
}
}
You can read more on this topic over here – there are some caveats.
Note also that the reference documentation also states the following:
… number objects do not necessarily preserve the type they are created
with …
.

Get elements and count of Array of unknown type

Let's say we have an Array, assigned to a variable with the type Any
let something: Any = ["one", "two", "three"]
Let's also assume we don't know if it's an array or something entirely else. And we also don't know what kind of Array.Element we are dealing with exactly.
Now we want to find out if it's an array.
let isArray = something is Array // compiler error
let isArray = (something as? [Any?] != nil) // does not work (array is [String] and not [Any?])
Is there any elegant solution to tickle the following information out of the swift type system:
Is the given object an Array
What's the count of the array
Give me the elements of the array
(bridging to NSArray is not a solution for me, because my array could also be of type [Any?] and contain nil-values)
I love #stefreak's question and his solution. Bearing in mind #dfri's excellent answer about Swift's runtime introspection, however, we can simplify and generalise #stefreak's "type tagging" approach to some extent:
protocol AnySequenceType {
var anyElements: [Any?] { get }
}
extension AnySequenceType where Self : SequenceType {
var anyElements: [Any?] {
return map{
$0 is NilLiteralConvertible ? Mirror(reflecting: $0).children.first?.value : $0
}
}
}
extension Array : AnySequenceType {}
extension Set : AnySequenceType {}
// ... Dictionary, etc.
Use:
let things: Any = [1, 2]
let maybies: Any = [1, nil] as [Int?]
(things as? AnySequenceType)?.anyElements // [{Some 1}, {Some 2}]
(maybies as? AnySequenceType)?.anyElements // [{Some 1}, nil]
See Swift Evolution mailing list discussion on the possibility of allowing protocol extensions along the lines of:
extension<T> Sequence where Element == T?
In current practice, however, the more common and somewhat anticlimactic solution would be to:
things as? AnyObject as? [AnyObject] // [1, 2]
// ... which at present (Swift 2.2) passes through `NSArray`, i.e. as if we:
import Foundation
things as? NSArray // [1, 2]
// ... which is also why this fails for `mabyies`
maybies as? NSArray // nil
At any rate, what all this drives home for me is that once you loose type information there is no going back. Even if you reflect on the Mirror you still end up with a dynamicType which you must switch through to an expected type so you can cast the value and use it as such... all at runtime, all forever outside the compile time checks and sanity.
As an alternative to #milos and OP:s protocol conformance check, I'll add a method using runtime introspection of something (foo and bar in examples below).
/* returns an array if argument is an array, otherwise, nil */
func getAsCleanArray(something: Any) -> [Any]? {
let mirr = Mirror(reflecting: something)
var somethingAsArray : [Any] = []
guard let disp = mirr.displayStyle where disp == .Collection else {
return nil // not array
}
/* OK, is array: add element into a mutable that
the compiler actually treats as an array */
for (_, val) in Mirror(reflecting: something).children {
somethingAsArray.append(val)
}
return somethingAsArray
}
Example usage:
/* example usage */
let foo: Any = ["one", 2, "three"]
let bar: [Any?] = ["one", 2, "three", nil, "five"]
if let foobar = getAsCleanArray(foo) {
print("Count: \(foobar.count)\n--------")
foobar.forEach { print($0) }
} /* Count: 3
--------
one
2
three */
if let foobar = getAsCleanArray(bar) {
print("Count: \(foobar.count)\n-------------")
foobar.forEach { print($0) }
} /* Count: 5
-------------
Optional("one")
Optional(2)
Optional("three")
nil
Optional("five") */
The only solution I came up with is the following, but I don't know if it's the most elegant one :)
protocol AnyOptional {
var anyOptionalValue: Optional<Any> { get }
}
extension Optional: AnyOptional {
var anyOptionalValue: Optional<Any> {
return self
}
}
protocol AnyArray {
var count: Int { get }
var allElementsAsOptional: [Any?] { get }
}
extension Array: AnyArray {
var allElementsAsOptional: [Any?] {
return self.map {
if let optional = $0 as? AnyOptional {
return optional.anyOptionalValue
}
return $0 as Any?
}
}
}
Now you can just say
if let array = something as? AnyArray {
print(array.count)
print(array.allElementsAsOptional)
}
This works for me on a playground:
// Generate fake data of random stuff
let array: [Any?] = ["one", "two", "three", nil, 1]
// Cast to Any to simulate unknown object received
let something: Any = array as Any
// Use if let to see if we can cast that object into an array
if let newArray = something as? [Any?] {
// You now know that newArray is your received object cast as an
// array and can get the count or the elements
} else {
// Your object is not an array, handle however you need.
}
I found that casting to AnyObject works for an array of objects. Still working on a solution for value types.
let something: Any = ["one", "two", "three"]
if let aThing = something as? [Any] {
print(aThing.dynamicType) // doesn't enter
}
if let aThing = something as? AnyObject {
if let theThing = aThing as? [AnyObject] {
print(theThing.dynamicType) // Array<AnyObject>
}
}

How to remove duplicated objects from NSArray?

I have NSArray() which is include names but there's duplicated names how can i remove them ?
After parse query adding the objects to the NSArray and its duplicated
var names = NSArray()
let query = PFQuery(className: "test")
query.whereKey("receivers", equalTo: PFUser.currentUser()!.username!)
query.findObjectsInBackgroundWithBlock {
(objects, error) -> Void in
if error == nil {
self.names = objects!
let set = NSSet(array: self.names as [AnyObject])
print(objects!.count)
// count is 4
// database looks like this (justin , kevin , kevin , joe)
If your names are strings you could create NSSet from array and it will have only different names.
let names = ["John", "Marry", "Bill", "John"]
println(names)
let set = NSSet(array: names)
println(set.allObjects)
prints:
"[John, Marry, Bill, John]"
"[Bill, John, Marry]"
Update #1
With new information in question (code fragment) it may look like this
var set = Set<String>()
for test in objects as [Test] {
set.insert(test.sender)
}
self.names = Array(set)
To expand on John's answer, an NSSet will, by definition, only contain a single copy of each object that hashes to be equal. So, a common pattern is to convert the array to a set and back.
This will work for any object type that has a reasonable implementation of -hash and -isEqual:. As John shows, String is one such object.
You could also do it with pure Swift:
let arrayWithDuplicates = [ "x", "y", "x", "x" ]
let arrayWithUniques = Array(Set(arrayWithDuplicates)) // => [ "y", "x" ]
But, it looks like you're already working with NSArray, so I think the John's example is more applicable.
Also, as my example shows, be aware that the order of the final array is not guaranteed to be in the same order as your original. If you want that, I think you can use NSOrderedSet instead of NSSet.
Here is a more complicated way to approach this that works. You could just run through a loop of the array and create a new one from the original. For example:
var check = 0
let originalArray:NSMutableArray = ["x", "y", "x", "z", "y", "z"]
let newArray: NSMutableArray = []
println(originalArray)
for var int = 0; int<originalArray.count; ++int{
let itemToBeAdded: AnyObject = originalArray.objectAtIndex(int)
for var int = 0; int<newArray.count; ++int{
if (newArray == ""){
break;
}
else if ((newArray.objectAtIndex(int) as! String) != itemToBeAdded as! String){
}
else if ((newArray.objectAtIndex(int) as! String) == itemToBeAdded as! String){
check = 1
break
}
}
if (check == 0){
newArray.addObject(itemToBeAdded)
}
}
Basically I set a check var = 0. for every object in the originalArray, it loops through the newArray to see if it already exists and if it does the check var gets set to 1 and the object does not get added twice.

Swift String to Array

I have a string in Swift that looks like this:
["174580798","151240033","69753978","122754394","72373738","183135789","178841809","84104360","122823486","184553211","182415131","70707972"]
I need to convert it into an NSArray.
I've looked at other methods on SO but it is breaking each character into a separate array element, as opposed to breaking on the comma. See: Convert Swift string to array
I've tried to use the map() function, I've also tried various types of casting but nothing seems to come close.
Thanks in advance.
It's probably a JSON string so you can try this
let string = "[\"174580798\",\"151240033\",\"69753978\",\"122754394\",\"72373738\",\"183135789\",\"178841809\",\"84104360\",\"122823486\",\"184553211\",\"182415131\",\"70707972\"]"
let data = string.dataUsingEncoding(NSUTF8StringEncoding)!
let jsonArray = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions(), error: nil) as! [String]
as the type [String] is distinct you can cast it forced
Swift 3+:
let data = Data(string.utf8)
let jsonArray = try! JSONSerialization.jsonObject(with: data) as! [String]
The other two answers are working, although SwiftStudiers isn't the best regarding performance. vadian is right that your string most likely is JSON. Here I present another method which doesn't involve JSON parsing and one which is very fast:
import Foundation
let myString = "[\"174580798\",\"151240033\",\"69753978\",\"122754394\",\"72373738\",\"183135789\",\"178841809\",\"84104360\",\"122823486\",\"184553211\",\"182415131\",\"70707972\"]"
func toArray(var string: String) -> [String] {
string.removeRange(string.startIndex ..< advance(string.startIndex, 2)) // Remove first 2 chars
string.removeRange(advance(string.endIndex, -2) ..< string.endIndex) // Remote last 2 chars
return string.componentsSeparatedByString("\",\"")
}
toArray(myString) // ["174580798", "151240033", "69753978", ...
You probably want the numbers though, you can do this in Swift 2.0:
toArray(myString).flatMap{ Int($0) } // [174'580'798, 151'240'033, 69'753'978, ...
which returns an array of Ints
EDIT: For the ones loving immutability and functional programming, have this solution:
func toArray(string: String) -> [String] {
return string[advance(string.startIndex, 2) ..< advance(string.endIndex, -2)]
.componentsSeparatedByString("\",\"")
}
or this:
func toArray(string: String) -> [Int] {
return string[advance(string.startIndex, 2) ..< advance(string.endIndex, -2)]
.componentsSeparatedByString("\",\"")
.flatMap{ Int($0) }
}
Try this. I've just added my function which deletes any symbols from string except numbers. It helps to delete " and [] in your case
var myString = "[\"174580798\",\"151240033\",\"69753978\",\"122754394\",\"72373738\",\"183135789\",\"178841809\",\"84104360\",\"122823486\",\"184553211\",\"182415131\",\"70707972\"]"
var s=myString.componentsSeparatedByString("\",\"")
var someArray: [String] = []
for i in s {
someArray.append(deleteAllExceptNumbers(i))
}
println(someArray[0]);
func deleteAllExceptNumbers(str:String) -> String {
var rez=""
let digits = NSCharacterSet.decimalDigitCharacterSet()
for tempChar in str.unicodeScalars {
if digits.longCharacterIsMember(tempChar.value) {
rez += tempChar.description
}
}
return rez.stringByReplacingOccurrencesOfString("\u{22}", withString: "")
}
Swift 1.2:
If as has been suggested you are wanting to return an array of Int you can get to that from myString with this single concise line:
var myArrayOfInt2 = myString.componentsSeparatedByString("\"").map{$0.toInt()}.filter{$0 != nil}.map{$0!}
In Swift 2 (Xcode 7.0 beta 5):
var myArrayOfInt = myString.componentsSeparatedByString("\"").map{Int($0)}.filter{$0 != nil}.map{$0!}
This works because the cast returns an optional which will be nil where the cast fails - e.g. with [, ] and ,. There seems therefore to be no need for other code to remove these characters.
EDIT: And as Kametrixom has commented below - this can be further simplified in Swift 2 using .flatMap as follows:
var myArrayOfInt = myString.componentsSeparatedByString("\"").flatMap{ Int($0) }
Also - and separately:
With reference to Vadian's excellent answer. In Swift 2 this will become:
// ...
do {
let jsonArray = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) as! [String]
} catch {
_ = error // or do something with the error
}