How can I optimize this code that manipulates strings? - swift

I am trying to come up with a faster solution to this Hackerrank problem: https://www.hackerrank.com/challenges/30-review-loop
In short, I have to separate each input string into two strings, the first one having the even index characters of the original string and the second one having odd indexes.
The number of strings to be separated are saved to the numStrings constant, the strings themselves are stored in inputString.
import Foundation
let numStrings = Int(readLine()!)!
func printEvenAndOdd(string: String) {
var firstString = ""
var secondString = ""
var stringIndex = string.index(string.startIndex, offsetBy: 0)
for index in 0..<string.characters.count {
stringIndex = string.index(string.startIndex, offsetBy: index)
if index % 2 == 0 {
firstString += String(string[stringIndex])
} else {
secondString += String(string[stringIndex])
}
}
print(firstString + " " + secondString)
}
for _ in 1...numStrings {
let inputString = readLine()!
printEvenAndOdd(string: inputString)
}
My code works, but fails the last 3 tests due to timeout. Can I make the algorithm quicker?

func index(_ i: String.Index, offsetBy n: String.IndexDistance) -> String.Index
is O(n), which means it gets slower as n increases. So the longer your string, the slower your algorithm will run.
To access the characters in O(1) time, you should just use for char in string.characters to get the characters.
If you use string.characters.enumerated(), you will get a sequence of tuples that hold the index of the character and the character itself. Then your code becomes:
func printEvenAndOdd(string: String) {
var firstString = ""
var secondString = ""
for (index, char) in string.characters.enumerated() {
if index % 2 == 0 {
firstString += String(char)
} else {
secondString += String(char)
}
}
print(firstString + " " + secondString)
}

According to the Swift String reference the index(_, offsetBy) function performs with complexity O(n) leading to a total complexity of O(n^2). The following function should perform better:
func printEvenAndOdd(string: String) {
var firstString = ""
var secondString = ""
var i = 0
for c in string.characters {
if i % 2 == 0 {
firstString += String(c)
} else {
secondString += String(c)
}
i += 1
}
print(firstString + " " + secondString)
}

Related

Leetcode Q. 1528. Shuffle String

Given a string s and an integer array indices of the same length.
The string s will be shuffled such that the character at the ith position moves to indices[i] in the shuffled string.
Return the shuffled string.
Input: s = "codeleet", indices = [4,5,6,7,0,2,1,3]
Output: "leetcode"
Explanation: As shown, "codeleet" becomes "leetcode" after shuffling.
class Solution {
func restoreString(_ s: String, _ indices: [Int]) -> String {
//convert the string into a hash map where all keys are Ints and the values are the Strings.
//Run a for loop through the dictionary and return the key of the value in indices.
//time complexity: O(n)
//Space complexity: O(n)
var newString = s.map{ String($0) }
var y = ""
var count = 0
var dict = [Int:String]()
var z = 0
while count < newString.count {
dict[count] = newString[count]
count += 1
}
while z < indices.count {
y.append(dict[indices[z]]!)
z += 1
}
print(dict)
return y
}
}
The first while loop creates a dictionary and the second while loop finds the values with matching keys and appends into a string. My issue is that my code is outputting two characters in the wrong location.
Input: "codeleet"
[4,5,6,7,0,2,1,3]
Output: "leetcdoe"
Can you please help me explain what I'm missing here.
Its a one to one hashing not a index based hashing which you were doing in above code below is the updated correct version of your code:-
class Solution {
func restoreString(_ s: String, _ indices: [Int]) -> String {
var newString = s.map{ String($0) }
var y = ""
var count = 0
var dict = [Int:String]()
var z = 0
while count < newString.count {
dict[indices[count]] = newString[count]
count += 1
}
while z < indices.count {
y.append(dict[z]!)
z += 1
}
print(dict)
return y
}
}
class Solution {
public String restoreString(String s, int[] indices) {
String s1="";
for(int i=0;i<s.length();i++){
for(int j=0;j<s.length();j++){
if(i==indices[j]){
s1=s1+s.charAt(j);
}
}
}return s1;
}
func restoreString(_ s: String, _ indices: [Int]) -> String {
var letters = [String]()
var result = ""
s.forEach{
letters.append(String($0))
}
for (index,value) in s.enumerated(){
letters[indices[index]] = value.description
}
result = letters.reduce("", +)
return result }

CodingBat string_bits problem solved using swit for loop

Question:
Given a string, return a new string made of every other char starting with the first, so "Hello" yields "Hlo".
string_bits('Hello') → 'Hlo'
string_bits('Hi') → 'H'
string_bits('Heeololeo') → 'Hello'
Solution:
func string_bits(userString: String) ->String{
var myString = ""
for(i, v) in userString.enumerated(){
if i % 2 == 0{
myString.append(v)
}
}
return myString
}
Output: Hello
Now my question:
Is there any I can iterate my index any way in swift like object-c, c, or other programming languages does. For instance:
result = ""
# On each iteration, add the substring of the chars 0..i
for i in range(len(str)):
result = result + str[:i+1]
return result
str[:i+1]
Here, I am adding +1 with the current index and getting the index value. How can I do this in swift.
extension Collection {
func everyNthIndex(n: Int) -> UnfoldSequence<Index,Index> {
sequence(state: startIndex) { index in
guard index < endIndex else { return nil }
defer { index = self.index(index, offsetBy: n, limitedBy: endIndex) ?? endIndex }
return index
}
}
}
let alphabet = "abcdefghijklmnopqrstuvwxyz"
for evenIndex in alphabet.everyNthIndex(n: 2) {
print("evenIndex", evenIndex, "char:", alphabet[evenIndex])
}
for oddIndex in alphabet.dropFirst().everyNthIndex(n: 2) {
print("oddIndex", oddIndex, "char:", alphabet[oddIndex])
}
regular approach using while loop:
var index = alphabet.startIndex
while index < alphabet.endIndex {
defer { index = alphabet.index(index, offsetBy: 1) }
print(alphabet[index])
print(index)
}
or enumerating the string indices:
func string_bits(userString: String) -> String {
var myString = ""
for (offset,index) in userString.indices.enumerated() {
if offset.isMultiple(of: 2) {
myString.append(userString[index])
}
}
return myString
}

How to solve a problem with using the method of branches and borders?

All words of the ternary language consist of only 3 letters: a, b, and c and all have a strictly specified length N. Words that do not contain two identical subsequences of letters in a row are considered correct. For example, abcacb is the correct word, and ababc is not the correct one, since the ab subsequences go there.
I tried to solve the problem with a complete enumeration of all possible combinations and a function that looked for a repeating sequence. However, this turned out to be the wrong decision. The problem needs to be solved somehow using the branch and bound method. I have absolutely no idea how this problem can be solved by this method. I would be very happy if someone provides examples or explains to me. I have already spent six days to solve this problem and am very tired.
My wrong solution:
import Foundation
func findRepetition(_ p: String) -> [String:Int] {
var repDict: [String:Int] = [:]
var p = p
while p.count != 0 {
for i in 0...p.count-1 {
repDict[String(Array(p)[0..<i]), default: 0] += 1
}
p = String(p.dropFirst())
}
return repDict
}
var correctWords = [String]()
var wrongWords = [String]()
func getRepeats(_ p: String) -> Bool {
let p = p
var a = findRepetition(p)
for i in a {
var substring = String(Array(repeating: i.key, count: 2).joined())
if p.contains(substring) {
wrongWords.append(p)
return false
}
}
correctWords.append(p)
return true
}
var counter = 0
func allLexicographicRecur (_ string: [String.Element], _ data: [String], _ last: Int, _ index: Int){
var length = string.count-1
var data = data
for i in 0...length {
data[index] = String(string[i])
if index == last {
if getRepeats(data.joined()) {
counter += 1
}
}else{
allLexicographicRecur(string, data, last, index+1)
}
}
}
func threeLanguage(_ l: Int) {
var alphabet = "abc"
var data = Array(repeating: "", count: l)
allLexicographicRecur(alphabet.sorted(), data, l-1, 0)
print("The specified word length: \(l), the number of correct words: \(counter)\n")
print("Correct words:\n\(correctWords)\n")
print("Wrong words:\n\(wrongWords)")
}
threeLanguage(3)
Example:
abca is the right word.
abab is wrong (ab).
aaaa is also wrong (a).
abcabc is also incorrect (abc).
If I correctly understood your problem, you need to separate you input string to parts N-length and check parts by your rules. Smth like this
let constant: Int = 3
extension String {
private func components(withLength length: Int) -> [String] {
return stride(from: 0, to: count, by: length).map {
let start = index(startIndex, offsetBy: $0)
let end = index(start, offsetBy: length, limitedBy: endIndex) ?? endIndex
return String(self[start ..< end])
}
}
var numberOfValidWords: Int {
var numberOfIncorrectWords = 0
let length = count - constant
let array = components(withLength: constant)
for component in array {
let computedLength = replacingOccurrences(of: component, with: "").count
if computedLength != length {
print("as is lengths are not equal, this part is met in string several times")
numberOfIncorrectWords += 1
continue
}
}
return array.count - numberOfIncorrectWords
}
}
Hope it will be helpful

Need to find consecutive sequence like "6789" or "abcd" in Swift

I need help to find consecutive sequence for example more than 3 characters in ascending order. I've already implemented one solution but It's not universal.
Examples what should be found - "1234", "abcd", "5678".
And what shouldn't be found - "123", "adced", "123abc", "89:;"
Particularly the case "89:;", symbol ":" - is 58 in uniCode and "9" - is 57, that's why my approach does not work in the case.
Implementation should be in swift.
Additional clarification
For now it would be enough to find the sequences only in English letters and numbers.
private func findSequence(sequenceLength: Int, in string: String) -> Bool {
let scalars = string.unicodeScalars
var unicodeArray: [Int] = scalars.map({ Int($0.value) })
var currentLength: Int = 1
var i = 0
for number in unicodeArray {
if i+1 >= unicodeArray.count {
break
}
let nextNumber = unicodeArray[i+1]
if number+1 == nextNumber {
currentLength += 1
} else {
currentLength = 1
}
if currentLength >= sequenceLength {
return true
}
i += 1
}
return false
}
var data = [1,2,5,4,56,6,7,9,6,5,4,5,1,2,5,4,56,6,7,9,8,1,1,2,5,4,56,6,7,9,8,1,1,2,5,4,56,6,7,9,8,1,1,2,5,4,56,6,7,9,8,1,1,2,5,4,56,6,7,9,8,11,2,5,4,56,6,7,9,8,1,2,3]
for i in 0...data.count{
if i+2 < data.count{
if Int(data[i] + data[i+2]) / 2 == data[i+1] && Int(data[i] + data[i+2]) % data[i+1] == 0 && data[i+1] != 1 && data[i] < data[i+1]{
print(data[i] ,data[i+1], data[i+2])
}
}
}
You can check for sequence with CharacterSet
func findSequence(sequenceLength: Int, in string: String) -> Bool {
// It would be better to extract this out of func
let digits = CharacterSet.decimalDigits
let lowercase = CharacterSet(charactersIn: "a"..."z")
let uppercase = CharacterSet(charactersIn: "A"..."Z")
let controlSet = digits.union(lowercase).union(uppercase)
// ---
let scalars = string.unicodeScalars
let unicodeArray = scalars.map({ $0 })
var currentLength: Int = 1
var i = 0
for number in unicodeArray where controlSet.contains(number) {
if i+1 >= unicodeArray.count {
break
}
let nextNumber = unicodeArray[i+1]
if UnicodeScalar(number.value+1) == nextNumber {
currentLength += 1
} else {
currentLength = 1
}
if currentLength >= sequenceLength {
return true
}
i += 1
}
return false
}
I did assumed that "a" ... "z" and "A"..."Z" are consecutive here, to make it in range, but it may be better do explicitly list all the symbols you want.
Or use CharacterSet.alphanumerics, but is not limited to basic latin alphabet.

Find the Range of the Nth word in a String

What I want is something like
"word1 word2 word3".rangeOfWord(2) => 6 to 10
The result could come as a Range or a tuple or whatever.
I'd rather not do the brute force of iterating over the characters and using a state machine. Why reinvent the lexer? Is there a better way?
In your example, your words are unique, and you can use the following method:
let myString = "word1 word2 word3"
let wordNum = 2
let myRange = myString.rangeOfString(myString.componentsSeparatedByString(" ")[wordNum-1])
// 6..<11
As pointed out by Andrew Duncan in the comments below, the above is only valid if your words are unique. If you have non-unique words, you can use this somewhat less neater method:
let myString = "word1 word2 word3 word2 word1 word3 word1"
let wordNum = 7 // 2nd instance (out of 3) of "word1"
let arr = myString.componentsSeparatedByString(" ")
var fromIndex = arr[0..<wordNum-1].map { $0.characters.count }.reduce(0, combine: +) + wordNum - 1
let myRange = Range<String.Index>(start: myString.startIndex.advancedBy(fromIndex), end: myString.startIndex.advancedBy(fromIndex+arr[wordNum-1].characters.count))
let myWord = myString.substringWithRange(myRange)
// string "word1" (from range 36..<41)
Finally, lets use the latter to construct an extension of String as you have wished for in your question example:
extension String {
private func rangeOfNthWord(wordNum: Int, wordSeparator: String) -> Range<String.Index>? {
let arr = myString.componentsSeparatedByString(wordSeparator)
if arr.count < wordNum {
return nil
}
else {
let fromIndex = arr[0..<wordNum-1].map { $0.characters.count }.reduce(0, combine: +) + (wordNum - 1)*wordSeparator.characters.count
return Range<String.Index>(start: myString.startIndex.advancedBy(fromIndex), end: myString.startIndex.advancedBy(fromIndex+arr[wordNum-1].characters.count))
}
}
}
let myString = "word1 word2 word3 word2 word1 word3 word1"
let wordNum = 7 // 2nd instance (out of 3) of "word1"
if let myRange = myString.rangeOfNthWord(wordNum, wordSeparator: " ") {
// myRange: 36..<41
print(myString.substringWithRange(myRange)) // prints "word1"
}
You can tweak the .rangeOfNthWord(...) method if word separation is not unique (say some words are separated by two blankspaces " ").
Also pointed out in the comments below, the use of .rangeOfString(...) is not, per se, pure Swift. It is, however, by no means bad practice. From Swift Language Guide - Strings and Characters:
Swift’s String type is bridged with Foundation’s NSString class. If
you are working with the Foundation framework in Cocoa, the entire
NSString API is available to call on any String value you create when
type cast to NSString, as described in AnyObject. You can also use a
String value with any API that requires an NSString instance.
See also the NSString class reference for rangeOfString method:
// Swift Declaration:
func rangeOfString(_ searchString: String) -> NSRange
I went ahead and wrote the state machine. (Grumble..) FWIW, here it is:
extension String {
private func halfOpenIntervalOfBlock(n:Int, separator sep:Character? = nil) -> (Int, Int)? {
enum State {
case InSeparator
case InPrecedingSeparator
case InWord
case InTarget
case Done
}
guard n > 0 else {
return nil
}
var state:State
if n == 1 {
state = .InPrecedingSeparator
} else {
state = .InSeparator
}
var separatorNum = 0
var startIndex:Int = 0
var endIndex:Int = 0
for (i, c) in self.characters.enumerate() {
let inSeparator:Bool
// A bit inefficient to keep doing this test.
if let s = sep {
inSeparator = c == s
} else {
inSeparator = c == " " || c == "\n"
}
endIndex = i
switch state {
case .InPrecedingSeparator:
if !inSeparator {
state = .InTarget
startIndex = i
}
case .InTarget:
if inSeparator {
state = .Done
}
case .InWord:
if inSeparator {
separatorNum += 1
if separatorNum == n - 1 {
state = .InPrecedingSeparator
} else {
state = .InSeparator
}
}
case .InSeparator:
if !inSeparator {
state = .InWord
}
case .Done:
break
}
if state == .Done {
break
}
}
if state == .Done {
return (startIndex, endIndex)
} else if state == .InTarget {
return (startIndex, endIndex + 1) // We ran off end.
} else {
return nil
}
}
func rangeOfWord(n:Int) -> Range<Index>? {
guard let (s, e) = self.halfOpenIntervalOfBlock(n) else {
return nil
}
let ss = self.startIndex.advancedBy(s)
let ee = self.startIndex.advancedBy(e)
return Range(start:ss, end:ee)
}
}
It's not really clear whether the string has to be considered divided in words by separators it may contains, or if you're just looking for a specific substring occurrence.
Anyway both cases could be addressed in this way in my opinion:
extension String {
func enumerateOccurencies(of pattern: String, _ body: (Range<String.Index>, inout Bool) throws -> Void) rethrows {
guard
!pattern.isEmpty,
count >= pattern.count
else { return }
var stop = false
var lo = startIndex
while !stop && lo < endIndex {
guard
let r = self[lo..<endIndex].range(of: pattern)
else { break }
try body(r, &stop)
lo = r.upperBound
}
}
}
You'll then set stop to true in the body closure once reached the desired occurrence number and capture the range passed to it:
let words = "word1, word1, word2, word3, word1, word3"
var matches = 0
var rangeOfThirdOccurencyOfWord1: Range<String.Index>? = nil
words.enumerateOccurencies(of: "word1") { range, stop in
matches +=1
stop = matches == 3
if stop {
rangeOfThirdOccurencyOfWord1 = range
}
}
Regarding the DFA: recently I've wrote one leveraging on Hashable and using a an Array of Dictionaries as its state nodes, but I've found that the method above is faster, cause maybe range(of:) uses finger-printing.
UPDATE
Otherwise you could also achieve that API you've mentioned in this way:
import Foundation
extension String {
func rangeOfWord(order: Int, separator: String) -> Range<String.Index>? {
precondition(order > 0)
guard
!isEmpty,
!separator.isEmpty,
separator.count < count
else { return nil }
var wordsSoFar = 0
var lo = startIndex
while let r = self[lo..<endIndex].range(of: separator) {
guard
r.lowerBound != lo
else {
lo = r.upperBound
continue
}
wordsSoFar += 1
guard
wordsSoFar < order
else { return lo..<r.lowerBound }
lo = r.upperBound
}
if
lo < endIndex,
wordsSoFar + 1 == order
{
return lo..<endIndex
}
return nil
}
}
let words = "word anotherWord oneMore lastOne"
if let r = words.rangeOfWord(order: 4, separator: " ") {
print(words[r])
} else {
print("not found")
}
Here order parameter refers to the nth order of the word in the string, starting from 1. I've also added the separator parameter to specify a string token to use for finding words in the string (it can also be defaulted to " " to be able to call the function without having to specify it).
Here's my attempt at an updated answer in Swift 5.5:
import Foundation
extension String {
func rangeOfWord(atPosition wordAt: Int) -> Range<String.Index>? {
let fullrange = self.startIndex..<self.endIndex
var count = 0
var foundAt: Range<String.Index>? = nil
self.enumerateSubstrings(in: fullrange, options: .byWords) { _, substringRange, _, stop in
count += 1
if count == wordAt {
foundAt = substringRange
stop = true // Stop the enumeration after the word range is found.
}
}
return foundAt
}
}
let lorem = "Morbi leo risus, porta ac consectetur ac, vestibulum at eros."
if let found = lorem.rangeOfWord(atPosition: 8) {
print("found: \(lorem[found])")
} else {
print("not found.")
}
This solution doesn't make a new array to contain the words so uses less memory (I have not tested but in theory it should use less memory). As much as possible, the build in method is used therefore less chance of bugs.
Swift 5 solution, which allows you to specify the word separator
extension String {
func rangeOfWord(atIndex wordIndex: Int) -> Range<String.Index>? {
let wordComponents = self.components(separatedBy: " ")
guard wordIndex < wordComponents.count else {
return nil
}
let characterEndCount = wordComponents[0...wordIndex].map { $0.count }.reduce(0, +)
let start = String.Index(utf16Offset: wordIndex + characterEndCount - wordComponents[wordIndex].count, in: self)
let end = String.Index(utf16Offset: wordIndex + characterEndCount, in: self)
return start..<end
}
}