So this is just something I'm doing for fun in the IBM Swift sandbox. I did a L-system algae in Scala recently and though it would be cool to do it in Swift to see how the language compares.
https://github.com/I2obiN/L-System-Algorithms-and-Fractals/blob/master/algae.scala
That's the Scala one to show you what I'm aiming for, and this is what I have in Swift;
/**
* Created by t.hood on 26/01/16
* L-System Algae
*
*/
import Foundation
// Mapping function for string
func stringmap(x: String) -> String {
var str = x;
// Replace characters in string
str = x.stringByReplacingOccurrencesOfString("A", withString: "AB") +
str.stringByReplacingOccurrencesOfString("B", withString: "A");
// Return mapped string;
return str;
}
func lsys() {
// Declarations
var iteration : Int = 2;
var x = 0;
var lsystem: String = "A";
let charA: Character = "A";
let charB: Character = "B";
while(x != iteration) {
print(lsystem)
// Iterate through characters in string
for chars in lsystem.characters {
lsystem = stringmap(lsystem);
}
// Inc count ..
x+=1
}
}
// Run ..
lsys();
The problem I'm having is in my mapping function. I need it to map x, print the result, then do the next map on str. The problem is I have the operator + between both maps and I can't get swift to print str after the first operation.
Anyone any ideas on how to get around this? If I could print str after the first replacements I think it would work.
I created a project in Xcode so I could use the debugger to see what's going on. Here's what I found.
I moved your print statement into the for loop so it would show the value of lsystem every time the loop executed.
The while loop executes twice, when x == 0 and 1
The first time through the while loop lsystem == 'A', so the for loop executes once. In stringmap(), the 'A' becomes 'ABA'.
'ABA' is printed.
The second time through the while loop, the for loop gets executed three times.
For loop 1: 'ABA' is sent to stringmap() and 'ABBABAAA' is returned.
For loop 2: 'ABBABAAA' is sent to stringmap() and 'ABBBABBABABABAAAAAAAA' is returned.
For loop 3: 'ABBBABBABABABAAAAAAAA' is sent to stringmap() and 'ABBBBABBBABBABBABBABABABABABABABABAAAAAAAAAAAAAAAAAAAAA' is returned.
I modified your stringmap() function to iterate through the string character by character and apply the grammar to each character, appending each change to a new string that was then returned to lsys().
Seven iterations of the while loop returned this, which agrees with what I see in the Wikipedia article about L-system.
Iteration 0: A
Iteration 1: AB
Iteration 2: ABA
Iteration 3: ABAAB
Iteration 4: ABAABABA
Iteration 5: ABAABABAABAAB
Iteration 6: ABAABABAABAABABAABABA
Iteration 7: ABAABABAABAABABAABABAABAABABAABAAB
Here's the code. This worked in a playground on my iMac, I'd expect it to work in the IBM Swift sandbox too.
func stringmap(x: String) -> String
{
var returnString = ""
for char in x.characters
{
switch (char)
{
case "A" :
returnString += "AB"
break
case "B":
returnString += "A"
break
default:
break
}
}
return returnString
}
func lsys()
{
// Declarations
let iteration : Int = 7;
var x = 0;
var lsystem: String = "A";
print("Iteration \(x): \(lsystem)")
while(x != iteration)
{
lsystem = stringmap(lsystem)
x++
print("Iteration \(x): \(lsystem)")
}
}
lsys()
Related
Posting a question here as ~find all ~4 letter words in a string ~algorithm site:stackoverflow.com google did not return any positive results.
The problem:
Write a function that takes a string of words and returns the number of 4 letter ones. The input argument 'sentence' is a string with words separated by spaces without punctuation.
So far I have a code like this:
func fourLetters(sentence: String) -> Int {
var targetNames = 0
var letterCount = 0
for letter in sentence {
if letter != " " {
letterCount += 1
} else {
print ("space found") // tech line
print (letterCount) // tech line
if letterCount == 4 {
targetNames += 1
letterCount = 0
}
letterCount = 0
}
}
print(targetNames) // tech line
return targetNames
}
The issue:
This algorithm now does not take into account the last part of the string giving the invalid number of 4 letter words. Consider we have the sentence: "Good Night Lil Peep" would return 1, although there are obviously two 4 teller words. What am I missing? Seems like the loop completely ignores the last word.
repl.it link for convenience and runs: https://repl.it/#DmitryAksyonov/4-lettered-names
Thank you for the help!
Regards
func findWords(ofLenght lenght: Int, in string: String) -> [Substring] {
let words = string.split{ $0.isWhitespace }
return words.filter { $0.count == lenght }
}
let input = "abcd word some string"
let result = findWords(ofLenght: 4, in: input)
print(result.count)
Output: 3
The problem is that the incrementation code for targetNames is encountered only if you find a space in the input string, which is not the case at the end of the string. You may be lucky if the last character of the string is a space.
So your code will fail even in the case where your sentence contains just one 4-letter word.
A possible solution is that you modify the condition of your first if statement by adding another condition to it which returns false if the loop has reached the end of the string, so that the else part of your code will run at that time and check if that last word is a 4-letter word.
Your algorithm doesn’t work because you don’t check for a 4-letter word at the end of the text.
You have to add another else clause and an index to check for end of text.
Of course there are more efficient ways to do that, it's just the answer to the question why the last item is not considered.
func fourLetters(sentence: String) -> Int {
var targetNames = 0
var letterCount = 0
var index = sentence.startIndex
for character in sentence {
sentence.formIndex(after: &index)
if character == " " { // check space
print ("space found") // tech line
print (letterCount) // tech line
if letterCount == 4 {
targetNames += 1
}
letterCount = 0
} else if index == sentence.endIndex { // check end of text
print ("end of text found") // tech line
letterCount += 1 // increment letterCount
if letterCount == 4 {
targetNames += 1
}
} else {
letterCount += 1
}
}
print(targetNames) // tech line
return targetNames
}
fourLetters(sentence: "Good Night Lil Peep") // 2
I am trying to solve code fights interview practice questions, but I am stuck on how to solve this particular problem in swift. My first thought was to use a dictionary with the counts of each character, but then I would have to iterate over the string again to compare, so that doesn't work per the restrictions. Any help would be good. Thank you. Here is the problem and requirements:
Note: Write a solution that only iterates over the string once and uses O(1) additional memory, since this is what you would be asked to do during a real interview.
Given a string s, find and return the first instance of a non-repeating character in it. If there is no such character, return '_'
Here is the code I started with (borrowed from another post)
func firstNotRepeatingCharacter(s: String) -> Character {
var countHash:[Character:Int] = [:]
for character in s {
countHash[character] = (countHash[character] ?? 0) + 1
}
let nonRepeatingCharacters = s.filter({countHash[$0] == 1})
let firstNonRepeatingCharacter = nonRepeatingCharacters.first!
return firstNonRepeatingCharacter
}
firstNotRepeatingCharacter(s:"abacabad")
You can create a dictionary to store the occurrences and use first(where:) method to return the first occurrence that happens only once:
Swift 4
func firstNotRepeatingCharacter(s: String) -> Character {
var occurrences: [Character: Int] = [:]
s.forEach{ occurrences[$0, default: 0] += 1 }
return s.first{ occurrences[$0] == 1 } ?? "_"
}
Swift 3
func firstNotRepeatingCharacter(s: String) -> Character {
var occurrences: [Character:Int] = [:]
s.characters.forEach{ occurrences[$0] = (occurrences[$0] ?? 0) + 1}
return s.characters.first{ occurrences[$0] == 1 } ?? "_"
}
Another option iterating the string in reversed order and using an array of 26 elements to store the characters occurrences
func firstNotRepeatingCharacter(s: String) -> Character {
var chars = Array(repeating: 0, count: 26)
var characters: [Character] = []
var charIndex = 0
var strIndex = 0
s.characters.reversed().forEach {
let index = Int(String($0).unicodeScalars.first!.value) - 97
chars[index] += 1
if chars[index] == 1 && strIndex >= charIndex {
characters.append($0)
charIndex = strIndex
}
strIndex += 1
}
return characters.reversed().first { chars[Int(String($0).unicodeScalars.first!.value) - 97] == 1 } ?? "_"
}
Use a dictionary to store the character counts as well as where they were first encountered. Then, loop over the dictionary (which is constant in size since there are only so many unique characters in the input string, thus also takes constant time to iterate) and find the earliest occurring character with a count of 1.
func firstUniqueCharacter(in s: String) -> Character
{
var characters = [Character: (count: Int, firstIndex: Int)]()
for (i, c) in s.characters.enumerated()
{
if let t = characters[c]
{
characters[c] = (t.count + 1, t.firstIndex)
}
else
{
characters[c] = (1, i)
}
}
var firstUnique = (character: Character("_"), index: Int.max)
for (k, v) in characters
{
if v.count == 1 && v.firstIndex <= firstUnique.index
{
firstUnique = (k, v.firstIndex)
}
}
return firstUnique.character
}
Swift
Use dictionary, uniqueCharacter optional variable with unique characters array to store all uniquely present characters in the string , every time duplication of characters found should delete that character from unique characters array and same time it is the most first character then should update the dictionary with its count incremented , refer following snippet , how end of the iteration through all characters gives a FIRST NON REPEATED CHARACTER in given String. Refer following code to understand it properly
func findFirstNonRepeatingCharacter(string:String) -> Character?{
var uniqueChars:[Character] = []
var uniqueChar:Character?
var chars = string.lowercased().characters
var charWithCount:[Character:Int] = [:]
for char in chars{
if let count = charWithCount[char] { //amazon
charWithCount[char] = count+1
if char == uniqueChar{
uniqueChars.removeFirst()
uniqueChar = uniqueChars.first
}
}else{
charWithCount[char] = 1
uniqueChars.append(char)
if uniqueChar == nil{
uniqueChar = char
}
}
}
return uniqueChar
}
// Use
findFirstNonRepeatingCharacter(string: "eabcdee")
I am trying to take a hex string and insert dashes between every other character (e.g. "b201a968" to "b2-01-a9-68"). I have found several ways to do it, but the problem is my string is fairly large (8066 characters) and the fastest I can get it to work it still takes several seconds. These are the ways I have tried and how long they are taking. Can anyone help me optimize this function?
//42.68 seconds
func reformatDebugString(string: String) -> String
{
var myString = string
var index = 2
while(true){
myString.insert("-", at: myString.index(myString.startIndex, offsetBy: index))
index += 3
if(index >= myString.characters.count){
break
}
}
return myString
}
//21.65 seconds
func reformatDebugString3(string: String) -> String
{
var myString = ""
let length = string.characters.count
var first = true
for i in 0...length-1{
let index = string.index(myString.startIndex, offsetBy: i)
let c = string[index]
myString += "\(c)"
if(!first){
myString += "-"
}
first = !first
}
return myString
}
//11.37 seconds
func reformatDebugString(string: String) -> String
{
var myString = string
var index = myString.characters.count - 2
while(true){
myString.insert("-", at: myString.index(myString.startIndex, offsetBy: index))
index -= 2
if(index == 0){
break
}
}
return myString
}
The problem with all three of your approaches is the use of index(_:offsetBy:) in order to get the index of the current character in your loop. This is an O(n) operation where n is the distance to offset by – therefore making all three of your functions run in quadratic time.
Furthermore, for solutions #1 and #3, your insertion into the resultant string is an O(n) operation, as all the characters after the insertion point have to be shifted up to accommodate the added character. It's generally cheaper to build up the string from scratch in this case, as we can just add a given character onto the end of the string, which is O(1) if the string has enough capacity, O(n) otherwise.
Also for solution #1, saying myString.characters.count is an O(n) operation, so not something you want to be doing at each iteration of the loop.
So, we want to build the string from scratch, and avoid indexing and calculating the character count inside the loop. Here's one way of doing that:
extension String {
func addingDashes() -> String {
var result = ""
for (offset, character) in characters.enumerated() {
// don't insert a '-' before the first character,
// otherwise insert one before every other character.
if offset != 0 && offset % 2 == 0 {
result.append("-")
}
result.append(character)
}
return result
}
}
// ...
print("b201a968".addingDashes()) // b2-01-a9-68
Your best solution (#3) in a release build took 37.79s on my computer, the method above took 0.023s.
As already noted in Hamish's answer, you should avoid these two things:
calculate each index with string.index(string.startIndex, offsetBy: ...)
modifying a large String with insert(_:at:)
So, this can be another way:
func reformatDebugString4(string: String) -> String {
var result = ""
var currentIndex = string.startIndex
while currentIndex < string.endIndex {
let nextIndex = string.index(currentIndex, offsetBy: 2, limitedBy: string.endIndex) ?? string.endIndex
if currentIndex != string.startIndex {
result += "-"
}
result += string[currentIndex..<nextIndex]
currentIndex = nextIndex
}
return result
}
I'm currently converting C++ code to Swift and I've gotten stuck on one part. The parameter passed into the function is a string and the area where I'm stuck is when attempting to set a variable based on the second to last character of a string to check for a certain character.
The error shows up on this line:
line[i-1]
I've tried casting this value to an Int but this didn't work:
Int(line[i - 1])
I've also tried to see if the string's startIndex function which takes a Int would work but it didn't:
line.startIndex[i - 1]
Here is the full function:
func scanStringForSpecificCharacters(line: String){
var maxOpen: Int = 0;
var minOpen: Int = 0;
minOpen = 0;
maxOpen = 0;
var i = 0
while i < line.characters.count {
for character in line.characters {
//var c: Character = line[i];
if character == "(" {
maxOpen += 1;
if i == 0 || line[i - 1] != ":" {
minOpen += 1;
}
}
else if character == ")"{
minOpen = max(0,minOpen-1);
if i == 0 || line[i-1] != ":"{
maxOpen -= 1;
}
if maxOpen < 0{
break;
}
}
}
if maxOpen >= 0 && minOpen == 0{
print("YES")
}else{
print("NO")
}
}
}
Strings in Swift aren't indexed collections and instead you can access one of four different views: characters, UTF8, UTF16, or unicodescalars.
This is because Swift supports unicode, where an individual characters may actually be composed of multiple unicode scalars.
Here's a post that really helped me wrap my head around this: https://oleb.net/blog/2016/08/swift-3-strings/
Anyway, to answer you question you'll need to create an index using index(after:), index(before:), or index(_, offsetBy:).
In your case you'd want to do something like this:
line.index(line.endIndex, offsetBy: -2) // second to last character
Also, you'll probably find it easier to iterate directly using a String.Index type rather than Int:
let line = "hello"
var i = line.startIndex
while i < line.endIndex {
print(line[i])
i = line.index(after: i)
}
// prints ->
// h
// e
// l
// l
// o
Working with Strings in Swift was changed several times during it's evolution and it doesn't look like C++ at all. You cannot subscript string to obtain individual characters, you should use index class for that. I recommend you read this article:
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html
As already pointed out in the other answers, the compiler error
is caused by the problem that you cannot index a Swift String with
integers.
Another problem in your code is that you have a nested loop which is
probably not intended.
Actually I would try to avoid string indexing at all and only
enumerate the characters, if possible. In your case, you can
easily keep track of the preceding character in a separate variable:
var lastChar: Character = " " // Anything except ":"
for char in line.characters {
if char == "(" {
maxOpen += 1;
if lastChar != ":" {
minOpen += 1;
}
}
// ...
lastChar = char
}
Or, since you only need to know if the preceding character is
a colon:
var lastIsColon = false
for char in string.characters {
if char == "(" {
maxOpen += 1;
if !lastIsColon {
minOpen += 1;
}
}
// ...
lastIsColon = char == ":"
}
Another possible approach is to iterate over the string and a shifted
view of the string in parallel:
for (lastChar, char) in zip([" ".characters, line.characters].joined(), line.characters) {
// ...
}
As others have already explained, trying to index into Swift strings is a pain.
As a minimal change to your code, I would recommend that you just create an array of the characters in your line up front:
let linechars = Array(line.characters)
And then anywhere you need to index into the line, use linechars:
This:
if i == 0 || line[i-1] != ":" {
becomes:
if i == 0 || linechars[i-1] != ":" {
Do these comments make any sense ?
Trying to figure out why it removes the character of my reversed given string, instead of the regular given string in this case.
import Foundation
extension String {
func reverseWords() -> String {
var result = ""
let words = self.componentsSeparatedByString(" ")
for thisWord in words.reverse() {
result += thisWord + " "
}
print("..\(result)..")
// Result is the words array ( contains self ) reversed with seperator " "
print("..\(self)..")
result.removeAtIndex(self.endIndex.predecessor())
// So here i am checking self within result?, and am removing the last index
// of my currently reversed given string inside result?
// I do result in my intended last space removal with result.endIndex but i'm
// wondering what happens in this case with the self.endIndex :)
return result
}
}
var str = "This string contains a few elements"
str.reverseWords()
self still refers to the original unreversed String.
The correct code would be:
result.removeAtIndex(result.endIndex.predecessor())
You should never use a String index for another string. If you are indexing result, you shouldn't use an index from self.
With a simple string you won't seem a difference but if you start adding multi-byte characters, e.g. emojis, your application can crash.
For example, using result += thisWord + "😀" would result in:
elements😀few😀a😀contains😀string�This😀
with self.endIndex
and
elements😀few😀a😀contains😀string😀This
with result.endIndex.
endIndex is the index past the end of the String. It works the same as count for arrays. count in arrays doesn't represent the last element, count - 1 represents the last element.
If your aim is to change the original String, you have to declare the method as mutating and assign to self, e.g.:
mutating func reverseWords() {
var result = ""
let words = self.componentsSeparatedByString(" ")
for thisWord in words.reverse() {
result += thisWord + " "
}
self = result
self.removeAtIndex(self.endIndex.predecessor())
}
although that's rather uncommon in functional programming.
Using self.endIndex.predecessor() to remove from result won't effect self (the "given" String).
The index is just an abstraction of a number. That number is based off the string self, rather than result
But ultimately, that index is used to remove from result, not from self.
So this is what's happening, which indeed results into the conclusion that it's quite idiotic to use the index of self.. ?
result.removeAtIndex(self.endIndex.predecessor()) // <-- Stupid move
// self.endIndex = 35
// self.endIndex predecessor = 34 ( last index in self )
// Removes index 34 out of result
// result.endIndex = 36
// results.endIndex predecessor = 35 ( the " " )
// Removes the one before the " " inside results