RemoveAtIndex crash from swift array - arrays

I have an array of letters, and want to match the characters against the letters and then do something to that letter (in this case turn it yellow) and then remove that matched character from the characters array.
If I have a word1 like "recruitment" and specialLetters like "ment" the removeAtIndex works fine, but in the below example which includes 2 s's in [ness] I get this crash:
fatal error: Array index out of range
Reading other posts on here suggest it is dangerous to remove items from an array when in use, but how come it works ok with some words and not others? I thought enumerating the array would only give each letter one index? And any suggestion on how to fix it so it works for all types of characters?
var letters = Array(word1) // [r,a,n,d,o,m,n,e,s,s]
var characters = Array(specialLetters) // [n,e,s,s]
// delete the special letters
for (index, element) in enumerate(characters) {
if letter == element {
tile.letterLabel.textColor = UIColor.yellowColor()
// remove that character from the array so can't be matched twice
characters.removeAtIndex(index)
}
}

So you have an array of Character(s):
let letters = Array("randomness")
An array of special Character(s)
let specials = Array("ness")
And you want to remove from letters, the specials right?
Here it is:
let set = Set(specials)
let filtered = letters.filter { !set.contains($0) }
filtered // ["r", "a", "d", "o", "m"]
Update
This version keep also consider the occurrences of a char
let letters = Array("randomness")
let specials = Array("ness")
var occurrencies = specials.reduce([Character:Int]()) { (var dict, char) in
dict[char] = (dict[char] ?? 0) + 1
return dict
}
let filtered = letters.filter {
if let num = occurrencies[$0] where num > 0 {
occurrencies[$0] = num - 1
return false
} else {
return true
}
}
filtered // ["r", "a", "d", "o", "m", "n"]

Yes it is very dangerous to modify the number of items in an array while being enumerated.
Imagine you have an array of three items, the range is 0...2. In the first iteration you delete the first item, then array[2] is now array[1] and there is no item at index 2 any more.
Either you enumerate the array backwards, so the index of the removed item is always equal to or higher than the current index or you use a variable to collect the indexes to be deleted and delete the characters by range.
Of course Swift has more reliable functions to accomplish this task as mentioned by the others therefore it's not needed to use enumerate In this case.

May i suggest a different approach where instead of removing from the initial array you make a new one, from which you can exclude the characters you don't want to appear in your array in the first place.
PS: keep in mind the following code works with Swift 2.0 Xcode 7 beta 6
var letters : [Character] = ["a","b", "c"]
var lettersToBeRemoved : [Character] = ["a"]
var newLetters : [Character] = []
for i in letters
{
for j in lettersToBeRemoved
{
if i == j
{
continue
}
newLetters.append(i)
}
}
print(newLetters)

just fixed it after reading the SDs post here: Swift buttons hidden fatal error: Array index out of range
I updated this question in case it helps anyone else.
I added an if statement to check the index was still in range. Not really sure why this works but it does :-)
var letters = Array(word1) // [r,a,n,d,o,m,n,e,s,s]
var characters = Array(specialLetters) // [n,e,s,s]
// delete the special letters
for (index, element) in enumerate(characters) {
if letter == element {
tile.letterLabel.textColor = UIColor.yellowColor()
// remove that character from the array so can't be matched twice.
// first check that the index is still in range.
if index < characters.count { // added this if statement
characters.removeAtIndex(index)
}
}
}

For this purpose (removing elements of an array), you may want to use an indexing generator. Let me give an example:
var array = [1, 2, 3, 4]
var ig = array.generate()
var index = 0
var element = ig.next()
while element != nil {
if element == 2 {
array.removeAtIndex(index)
} else {
index = index + 1
}
element = ig.next()
}

Related

Swift: Evaluating if the letters in a String are contained within a longer String

Okay so my current code works, but I have a feeling it's incredibly inefficient. Essentially, I need to evaluate if a String contains the letters of a String that is shorter or the same length as the first one. (Imagine trying to use the letters that exist in Word A to spell a new word Word B. Word B can be shorter than Word A or the same length but has to only use the letters from Word A and cannot use the same letter twice.)
My current solution is to sort both strings into an array, then index each letter in the Word B array, check if it appears in the Word A array, then remove that character from the Word A array.
let wordOne = "battle"
let wordTwo = "table"
var wordOneSorted = wordOne.sorted()
var wordTwoSorted = wordTwo.sorted()
for letter in wordTwoSorted {
if wordOneSorted.contains(letter) {
print("Valid Letter")
let idx = wordOneSorted.firstIndex(of:letter)
wordOneSorted.remove(at: idx!)
} else {
print("Invalid Letter")
}
}
Prints:
Valid Letter
Valid Letter
Valid Letter
Valid Letter
Valid Letter
This works but it feels clunky and I wanted to see if I'm making a simple task more complicated than I need it to be. I only need an evaluation of the entire comparison, if all the leters work than "True" and if at least one is invalid than "False".
Thank you!
Your code can give a simple good/bad response as follows:
let wordOne = "battle"
let wordTwo = "table"
var letters = wordOne
var good = true
for letter in wordTwo {
if letters.contains(letter) {
let idx = letters.firstIndex(of:letter)
letters.remove(at: idx!)
} else {
good = false
break
}
}
print(good ? "Good" : "Bad")
There's no need to sort the letters of each word. That doesn't make this approach any more efficient. I add the var letters just so the value can be modified as the loop runs.
Here's an alternate approach using NSCountedSet. This isn't a pure Swift class but is provided by Foundation.
let wordOne = "battle"
let wordTwo = "table"
let set1 = NSCountedSet(array: Array(wordOne))
let set2 = NSCountedSet(array: Array(wordTwo))
let extra = set2.filter { set2.count(for: $0) > set1.count(for: $0) }
print(extra.isEmpty ? "Good" : "Bad")
NSCountedSet is a subclass of Set (really of NSSet and NSMutableSet) that adds a count for each element in the set.
The filter makes sure there are enough of each letter. Anything left in extra means wordTwo had more instances of a letter than in wordOne.
As pointed out, using allSatisfy, rather than filter, would be more efficient (though trivial on such short words). Change the last two lines to:
let good = set2.allSatisfy { set2.count(for: $0) <= set1.count(for: $0) }
print(good ? "Good" : "Bad")
I was wondering how I would, and here is an alternative. It's good to have different ways of thinking an issue.
We create a [Character: (Int, Int)] where - keys will be the letter, and first tuple value the number of occurences in str1, and second tuple value the number of occurrences in str2.
Then, we check if all values present in str1 have at leas the same number of occurrences that in str2.
var counter: [Character: (Int, Int)] = [:]
counter = str1.reduce(counter) {
var values = $0
var tuple: (Int, Int) = $0[$1, default: (0, 0)]
values[$1] = (tuple.0 + 1, tuple.1)
return values
}
counter = str2.reduce(counter) {
var values = $0
var tuple: (Int, Int) = $0[$1, default: (0, 0)]
values[$1] = (tuple.0, tuple.1 + 1)
return values
}
let doesInclude = counter.allSatisfy { _, tuple in
tuple.0 >= tuple.1
}
print("\(str1) includes all letters from \(str2): \(doesInclude)")
The value of counter for "abc" vs "cde":
["d": (0, 1), "b": (1, 0), "c": (1, 1), "e": (0, 1), "a": (1, 0)]
The value of counter for "battle" vs "table":
["t": (2, 1), "e": (1, 1), "a": (1, 1), "b": (1, 1), "l": (1, 1)]
I like #HangarRash answer, but you could also use sort to your advantage, i.e. when letters are sorted you can move in both arrays simultaneously and stop as soon as the first difference was found (no need to remove anything):
func isContained(in word1: String, word word2: String) -> Bool {
var word1Sorted = word1.sorted()
var word2Sorted = word2.sorted()
var c1 = word1Sorted.count - 1
var c2 = word2Sorted.count - 1
while c2 >= 0 && c1 >= 0 {
if word1Sorted[c1] == word2Sorted[c2] {
// Found a match - advance to next character in both arrays
print("Valid Letter \(word2Sorted[c2])")
c1 -= 1
c2 -= 1
} else if word1Sorted[c1] > word2Sorted[c2] {
// Ignoring a letter present in wordOne, but not wordTwo
print("Skipping Letter \(word1Sorted[c1])")
c1 -= 1
} else { // wordOneSorted[c1] < wordTwoSorted[c2]
// the letter was not found in wordOneSorted - no need to continue
print("Invalid Letter \(word2Sorted[c2])")
return false
}
}
// If we finished the loop then the result is:
// - We've got to the beginning of word2, meaning we found all letters of it in word1
// - Or we've got to the beginning of word1, meaning not all letters of word 2 were found
return c2 < 0
}
So for example:
let wordOne = "battle"
let wordTwo = "table"
let good = isContained(in: wordOne, word: wordTwo)
print(good ? "Good" : "Bad")
will run on entire array (of the Word 2 at least):
Valid Letter t
Skipping Letter t
Valid Letter l
Valid Letter e
Valid Letter b
Valid Letter a
Good
While if there's a difference, e.g.
let wordOne = "battle"
let wordTwo = "tables"
let good = isContained(in: wordOne, word: wordTwo)
print(good ? "Good" : "Bad")
it may exit much faster:
Valid Letter t
Skipping Letter t
Invalid Letter s
Bad

How to capitalize every other letter in a string/array in Swift?

I am trying to capitalize every even numbered placeholder in a string in Swift. So the character in [0],[2],[4],[6] all get uppercased.
I have a declared variable:
var str = "This is a test"
I have an array that explodes the variable into an array:
let characters = Array(str) //Creates the array "["T", "h", "i", "s", " ", "i", "s", " ", "a", " ", "t", "e", "s", "t"]\n"
On top of that, I am creating an empty array to input the newly capitalized/lowercased letters
var newCharacters = Array<Character>()
And then declaring index at 0
var index = 0 //declaring the index at 0
I am trying to figure out how to create the for loop that will sniff out the even numbered array item and then capitalize the character found in it.
I have created a for loop that will manipulate the even numbered placeholders in the array, I just do not know the syntax to capitalize the string of every other one:
for letter in characters {
if index % 2 == 0 {
}
}
I am trying to figure out: what the syntax is to capitalize every other letter (even numbers in the array), put them in the new array, and then convert it back to a string.
The end result should be:
"ThIs iS TeSt"
You can combine enumerated, map, and joined in sequence to create the result:
let str = "This is a test"
let result = str.enumerated()
.map { $0.offset % 2 == 0 ? String($0.element).uppercased() : String($0.element) }
.joined()
print(result)
ThIs iS A TeSt
Explanation:
A String can be enumerated as if it were an array of Character. Calling .enumerated() on a String causes it to produces a sequence of (offset: Int, element: Character) tuples.
map takes a sequence and creates an array. The closure following map is called for each element of the sequence in turn and the value that the closure returns becomes the next element in the new array.
$0 is the default name for the value passed to map. In this case, we're passing the (offset: Int, element: Character) tuple. $0.offset is the position of the character in the String, and $0.element is the character.
The ternary operator is used here to return the uppercased() String that is created from the Character if the offset is even or just the String if the offset is odd.
The result of the map is [String], and joined() is then used to join the array of String back into a single String.
One way is using stride:
var str = "This is a test"
var chars = Array(str).map { String($0) }
for i in stride(from: 0, to: chars.count, by: 2) {
chars[i] = chars[i].uppercased()
}
var hiphopcasedStr = chars.joined()
Note that while you're in Unicode land, some characters uppercase to multicharacter strings, so array of Character is not quite appropriate (thus the conversion to array of String).
let str = "This is a test"
let result = str.indices.map {
str.distance(from: str.startIndex, to: $0) % 2 == 0 ?
str[$0...$0].uppercased() :
str[$0...$0].lowercased() }.joined()
print(result) // "ThIs iS A TeSt\n"
or as a mutating method on StringProtocol:
extension StringProtocol where Self: RangeReplaceableCollection {
mutating func capitalizeEveryOther() {
indices.forEach {
replaceSubrange($0...$0, with:
distance(from: startIndex, to: $0) % 2 == 0 ?
self[$0...$0].uppercased() :
self[$0...$0].lowercased() )
}
}
var capitalizingEveryOther: Self {
var result = self
result.capitalizeEveryOther()
return result
}
}
var str5 = "This is a test"
str5.capitalizeEveryOther()
print(str5) // ThIs iS A TeSt
try this code
var str = "This is a test"
var result = [String]()
for (index,element) in str.enumerated() {
var val:String = String(element).lowercased()
if index % 2 == 0 {
val = String(element).uppercased()
}
result.append(val)
}
print(result.joined())
result
ThIs iS A TeSt

How to split a sequential integer array, if any sequence is missing

Supposre I have an input array of integers. I want to split this array in multiple array based on the missing integer and append it in a new Array. I think split can be used here but not sure how to do it. I want arrayFinal only.
myArray = [0,1,2,4,7,8]
Desired Output
arrayOne = [0,1,2]
arrayTwo = [4]
arrayThree = [7,8]
arrayFinal = [[0,1,2], [4], [7,8]]
That's an algorithm you're asking for so there are a dozen different ways to do it. Since you are going to have to walk through the array's contents to find the missing integers, I would just create an array and append the numbers to it as you go, then create a new array whenever you hit a gap.
You'll probably have to adjust this for any special cases you might have. "Will this always start at 0 and move in a positive direction?" etc.
Try this out:
func splitByMissingInteger(array: [Int]) -> [[Int]]? {
var arrayFinal :[[Int]] = [ [Int]() ]
var i = 0
for num in array{
if arrayFinal[i].isEmpty || (arrayFinal[i].last == nil){
arrayFinal[i].append(num)
} else if num == (arrayFinal[i].last! + 1){
arrayFinal[i].append(num)
} else {
i += 1
arrayFinal.append([Int]())
arrayFinal[i].append(num)
}
}
return arrayFinal
}
You can just sort your array and iterate them in order. Check if the element minus one is equal to the last element of your 2D array, if true append otherwise append a new array with the element and increase the index of the subarrays:
extension Collection where Element == Int {
func grouped() -> [[Element]] {
let elements = Set(self).sorted()
guard let first = elements.first else { return [] }
var result = [[first]]
var i = 0
for element in elements.dropFirst() {
if element-1 == result[i].last! {
result[i].append(element)
} else {
result.append([element])
i += 1
}
}
return result
}
}
let myArray = [0,1,2,4,7,8]
let grouped = myArray.grouped() // [[0, 1, 2], [4], [7, 8]]

Changing the size of an array inside of in for-in loop

I was presented this problem where I need to find matching numbers in a given array (socks) and print out how many socks found inside that array. Here is my code:
let numberOfSocks = 9
let socksArray = [10, 20, 20, 10, 10, 30, 50, 10]
func findSocks(numberOfSocks: Int, array: [Int]) {
var arr = array
var uniqueSocks = Array(Set(array))
var matchedPairs = 0
var sockCounter = 0
for i in 0..<uniqueSocks.count { // After the search, remove the element at index
sockCounter = 0
for j in 0..<arr.count {
if uniqueSocks[i] == arr[j] {
sockCounter += 1
if sockCounter % 2 == 0 {
matchedPairs += 1
sockCounter = 0
}
}
}
}
print(matchedPairs)
}
findSocks(numberOfSocks: numberOfSocks, array: socksArray)
Firstly, I have removed all the duplicates in the array so it gives me a list unique socks that I need to search for. However, I wanted to optimize this algorithm by remove the sock that I have already searched for, I have tried arr.remove(at:) but It gave me an out of bound, I have a feeling that arr.count is not updated correctly. Any help is welcome, thanks!
I think that you're overthinking the problem, focusing on small details rather than the big picture. What you want to end up with is essentially a dictionary where the keys are the unique values in the array, and the values are the number of times those values appear in the array. So start with your dictionary:
var counts = [Int : Int]()
There's no need for your arr and numberOfSocks variables. Instead of the latter, just use socksArray.count, which clearly will always be in sync with the true size of the array.
Now loop through your socks. For each sock value, increment its count in the counts dictionary or, if it's not already in the dictionary, add it and give it a count of 1.
for sock in socks {
if !counts.contains(sock) {
counts[sock] = 1
} else {
counts[sock] = counts[sock] + 1
}
}
There are more concise ways to do this, but I think that this one is the easiest to read.

Check each value inside a nested array of a dictionary Swift

I know this question might seem obvious to some, but i can't get around a proper solution.
I have a dictionary
someDict = [String : [Int]]
Also i have an Integer variable and a string
var someNumber = Int()
var someString = String()
My goal is to compare if someString = someDict key and if yes - compare every Int value inside it's nested array to someNumber (check whether it's smaller or bigger and give some output).
Thank you!
First you look for the key in the dictionary that matches the one you're after — so we loop through all the keys.
Then, once we find a match, we loop through all the contents of that keys value. In this case, its our array of numbers.
let someDict = ["matchingString" : [6,7,5,4]]
var someNumber = 5
var someString = "matchingString"
for (someDictKey, numbers) in someDict {
if someDictKey == someString {
// Key Found
for number in numbers {
if number == someNumber {
// Number Found
} else {
// no matching number found
}
}
} else {
// No matching key found
}
}
Try it in a playground.
You can make use of optional chaining, without the need to explicitly loop over the dictionary entries.
var someDict = [String : [Int]]()
someDict["SomeString"] = [Int](1...5)
let someString = "SomeString"
let someNumber = 2
if someDict[someString]?.contains(someNumber) ?? false {
print("Dict value array for key '\(someString)' contains value \(someNumber).")
}
else {
print("Dict value array for key '\(someString)' does not contain value \(someNumber).")
}
/* Prints: Dict value array for key 'SomeString' contains value 2. */
If we're dealing with a huge dictionary, looping over all dictionary entries kind of defeats the purpose of dictionaries O(1) hash value lookup (i.e., just attempting to access the key directly).
Try this:
var someDict = [String : [Int]]()
someDict["a"] = [1, 2, 3]
someDict["b"] = [4, 5, 6]
var str = "a"
var number = 3
for (key, value) in someDict {
if key == str {
for num in value {
if num == number {
print("matched")
} else {
print("Oops")
}
}
} else {
print("nope")
}
}
You can simply ask the dictionary for the key you're interested into, and enumerate within the corresponding object:
// this helps us get rid of having to unwrap someDict["someString"] first
for intVal in someDict["someString"] ?? [Int]() {
print(intVal < someNumber ? "smaller" : "not smaller")
}
Or, if your interested on finding the numbers smaller that someNumber, you can use the filtering support:
let smallerNumbers = (someDict[someString] ?? [Int]()).filter({$0 < someNumber})

Resources