Im learning swift and am having a problem Iterating through an array.
Here is what I'm trying to do:
func orderStringByOccurence(stringArray: [String]) -> [String: Int]{
var stringDictionary: [String: Int] = [:]
for i in 0...stringArray.count {
if stringDictionary[stringArray[i]] == nil {
stringDictionary[stringArray[i]] = 1
stringDictionary
} else {
stringDictionary[stringArray[i]]! += 1
}
}
return stringDictionary
}
I don't get an error until I try to call this function. Then I get this error:
EXC_BAD_INSTRUCTION (code=EXC_1386_INVOP, subcode=0x0)
I have tried debugging and found that i get the same error when i try this:
for i in 0...arrayFromString.count{
print(arrayFromString[i])
}
So how do I iterate through this array?
Thanks for helping out a new
You need to change
for i in 0...arrayFromString.count
to
for i in 0..<arrayFromString.count
As it is now, you iterate through the array and then one past the end.
You can also use a different style of for loop, which is perhaps a little better:
func orderStringByOccurence(stringArray: [String]) -> [String: Int] {
var stringDictionary: [String: Int] = [:]
for string in stringArray {
if stringDictionary[string] == nil {
stringDictionary[string] = 1
} else {
stringDictionary[string]! += 1
}
}
return stringDictionary
}
Also, you can simplify your logic a bit:
for string in stringArray {
stringDictionary[string] = stringDictionary[string] ?? 0 + 1
}
Update - For the sake of completeness, I thought I'd add a reduce example here as well. Note that as of Swift 5.1 return statements in single line functions can be implied (SE-0255).
func orderStringByOccurence(stringArray: [String]) -> [String: Int] {
stringArray.reduce([:]) { result, string in result.merging([string: 1], uniquingKeysWith: +)}
}
A few more approaches:
let array = ["1", "2", "3"]
You can use forEach with trailing closure syntax:
array.forEach { item in
print(item)
}
You can use the $0 shorthand:
array.forEach {
print($0)
}
And if you need the indexes, you can use enumerate():
array.enumerate().forEach { itemTuple in
print("\(itemTuple.element) is at index \(itemTuple.index)")
}
It seems you're out of index. A more swift-like approach would be in my opinion not to use the count but to do range-based.
var stringArray = ["1", "2", "3"]
for string in stringArray
{
print(string)
}
Related
I am building a project that tells me the unique words in a piece of text.
I have my orginal string scriptTextView which I have added each word into the array scriptEachWordInArray
I would now like to create an array called scriptUniqueWords which only includes words that appear once (in other words are unique) in scriptEachWordInArray
So I'd like my scriptUniqueWords array to equal = ["Silent","Holy"] as a result.
I don't want to create an array without duplicates but an array that has only values that appeared once in the first place.
var scriptTextView = "Silent Night Holy Night"
var scriptEachWordInArray = ["Silent", "night", "Holy", "night"]
var scriptUniqueWords = [String]()
for i in 0..<scriptEachWordInArray.count {
if scriptTextView.components(separatedBy: "\(scriptEachWordInArray[i]) ").count == 1 {
scriptUniqueWords.append(scriptEachWordInArray[i])
print("Unique word \(scriptEachWordInArray[i])")}
}
You can use NSCountedSet
let text = "Silent Night Holy Night"
let words = text.lowercased().components(separatedBy: " ")
let countedSet = NSCountedSet(array: words)
let singleOccurrencies = countedSet.filter { countedSet.count(for: $0) == 1 }.flatMap { $0 as? String }
Now singleOccurrencies contains ["holy", "silent"]
Swift
lets try It.
let array = ["1", "1", "2", "2", "3", "3"]
let unique = Array(Set(array))
// ["1", "2", "3"]
Filtering out unique words without preserving order
As another alternative to NSCountedSet, you could use a dictionary to count the the number of occurrences of each word, and filter out those that only occur once:
let scriptEachWordInArray = ["Silent", "night", "Holy", "night"]
var freqs: [String: Int] = [:]
scriptEachWordInArray.forEach { freqs[$0] = (freqs[$0] ?? 0) + 1 }
let scriptUniqueWords = freqs.flatMap { $0.1 == 1 ? $0.0 : nil }
print(scriptUniqueWords) // ["Holy", "Silent"]
This solution, however (as well as the one using NSCountedSet), will not preserve the order of the original array, since a dictionary as well as NSCountedSet is an unordered collection.
Filtering out unique words while preserving order
If you'd like to preserve the order from the original array (removing element which appear more than once), you could count the frequencies of each word, but store it in a (String, Int) tuple array rather than a dictionary.
Making use of the Collection extension from this Q&A
extension Collection where Iterator.Element: Hashable {
var frequencies: [(Iterator.Element, Int)] {
var seen: [Iterator.Element: Int] = [:]
var frequencies: [(Iterator.Element, Int)] = []
forEach {
if let idx = seen[$0] {
frequencies[idx].1 += 1
}
else {
seen[$0] = frequencies.count
frequencies.append(($0, 1))
}
}
return frequencies
}
}
// or, briefer but worse at showing intent
extension Collection where Iterator.Element: Hashable {
var frequencies: [(Iterator.Element, Int)] {
var seen: [Iterator.Element: Int] = [:]
var frequencies: [(Iterator.Element, Int)] = []
for elem in self {
seen[elem].map { frequencies[$0].1 += 1 } ?? {
seen[elem] = frequencies.count
return frequencies.append((elem, 1))
}()
}
return frequencies
}
}
... you may filter out the unique words of your array (while preserving order) as
let scriptUniqueWords = scriptEachWordInArray.frequencies
.flatMap { $0.1 == 1 ? $0.0 : nil }
print(scriptUniqueWords) // ["Silent", "Holy"]
you can filter the values that are already contained in the array:
let newArray = array.filter { !array.contains($0) }
I'm trying to extend an array to return only objects at certain indexes. The map function seemed to be the best choice for me here.
extension Array {
func objectsAtIndexes(indexes: [Int]) -> [Element?]? {
let elements: [Element?] = indexes.map{ (idx) in
if idx < self.count {
return self[idx]
}
return nil
}.filter { $0 != nil }
return elements
}
}
let arr = ["1", "2", "3", "4", "5"]
let idx = [1,3,5]
let x = arr.objectsAtIndexes(idx) //returns: [{Some "2"}, {Some "4"}]
I'm getting a EXC_BAD_INSTRUCTION error when I try to cast the result to a string array:
let x = arr.objectsAtIndexes(idx) as? [String]
Is there any way I can return an array of non-optionals? I've tried to return [Element]? from the extension function.
This throws the same error.
The following code solves the problem for Swift2.1 using the flatmap function.
extension Array {
func objectsAtIndexes(indexes: [Int]) -> [Element] {
let elements: [Element] = indexes.map{ (idx) in
if idx < self.count {
return self[idx]
}
return nil
}.flatMap{ $0 }
return elements
}
}
I am trying to compare the values of two arrays in Swift. If a value of array2 is not found in array1 all the array2 found values need to be list and deleted.
I was trying to use the code below but its not working anymore in Swift 2:
let array1 = [["aaa","12"],["bbb","349"],["ccc","91"],["ddd","143"]]
let array2 = ["aaa","SSS","bbb","ccc","QQQ","ZZZ","ddd"]
let notNeededValues = filter(enumerate(zip(array1,array2))) { $1.0 == $1.1 }.map{ $0.0 }
print(notNeededValues)
Not sure if I understand your problem correctly, but the problem seems to be, that your code needs a simple conversion to Swift 2 syntax:
let array1 = [["aaa","12"],["bbb","349"],["ccc","91"],["ddd","143"]]
let array2 = ["aaa","SSS","bbb","ccc","QQQ","ZZZ","ddd"]
let notNeededValues = zip(array1, array2).enumerate().filter { $1.0 == $1.1 }.map { $0.0 }
print(notNeededValues)
Swift is moving away from globally defined functions, like filter and enumerate once were, and is using dot-syntax instead. This change was made possible by protocol extensions, and makes code more readable.
Update:
I assume this is what you mean(?):
let notNeededValues = array2.filter { !array1.map { $0[0] }.contains($0) }
// or like this:
let array1FirstElements = array1.map { $0[0] }
let notNeededValues = array2.filter { !array1FirstElements.contains($0) }
How about this:
let array1 = [["aaa","12"],["bbb","349"],["ccc","91"],["ddd","143"]]
let array2 = ["aaa","SSS","bbb","ccc","QQQ","ZZZ","ddd"]
extension Array where Element: Equatable {
func removeObject(object: Element) -> [Element] {
return filter {$0 != object}
}
}
var filteredArray2 = array2.reduce(array2) {
if array1.flatMap({$0}).contains($1) {
return $0.removeObject($1)
}
return $0
}
print(filteredArray2)
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()
I would like to know how can i fill my label from an Array
func metaDataUpdated(metaData : NSString){
var listItems: NSArray = [metaData.componentsSeparatedByString(";")]
if ([listItems.count] > 0){
println([listItems.objectAtIndex(0)])
titleSong.text = [listItems.objectAtIndex(0)]
}
}
I don't really know how to convert an array to string.
Direct conversion to Swift:
func metaDataUpdated(metaData : String) {
let listItems = metaData.componentsSeparatedByString(";")
if listItems.count > 0 {
print(listItems[0])
titleSong.text = listItems[0]
}
}
Nicer Swift:
func metaDataUpdated(metaData : String) {
let listItems = metaData.componentsSeparatedByString(";")
if let first = listItems.first {
print(first)
titleSong.text = first
}
}
Even nicer Swift, without using Foundation and without the function needing to get every component separated by ";", but only the first one (recommended):
func metaDataUpdated(metaData : String) {
if let index = metaData.characters.indexOf(";") {
let first = metaData[metaData.startIndex ..< index]
print(first)
titleSong.text = first
}
}
you cannot assign NSArray to NSString therefore you need to cast the value of this first index into a string
change this
titleSong.text = [listItems.objectAtIndex(0)]
to
titleSong.text = "\(listItems.objectAtIndex(0))"
or
titleSong.text = listItems[0] as! String
and also change this line to ([listItems.count > 0]) to (listItems.count > 0)
your code will look like this:
Note this not obj-c so remove all []
func metaDataUpdated(metaData : NSString){
var listItems: NSArray = metaData.componentsSeparatedByString(";")
if (listItems.count > 0)
{
println(listItems.objectAtIndex(0))
titleSong.text = listItems.objectAtIndex(0) as! String
}
}
Better use Swift types and objects now: Array instead of NSArray, Dictionary instead of NSDictionary, etc.
func metaDataUpdated(metaData : NSString) {
var listItems = metaData.componentsSeparatedByString(";")
if listItems.count > 0 {
print(listItems[0])
titleSong.text = listItems[0]
}
}
Here componentsSeparatedByString returns an array of strings: [String]. We then use simple index subscripting to retrieve its first value.
Note: I suppose you were trying to adapt code from Objective-C because your example was ridden with [] everywhere...
Put your to string item in "\\()".
For instance:
titleSong.text = "\\([listItems.objectAtIndex(0)])"
Not sure you need the [] brackets though