I'm tasked with creating a function that takes two parameters of a string and int that will return a string that repeats the string parameter the int's number of times. This is what I came up with but getting an error. BTW, this is on CodeCardio that I do at work so I haven't been able to test it out in Xcode (my work sucks and uses Windows)
func repeater(aString: String, withNumber: Int) -> String {
let repeatedString = String(count: withNumber, repeatedValue: aString)
return repeatedString
}
String(count: withNumber, repeatedValue: aString)
Is used to instantiate an string with a repeated character: Does Swift init(count:, repeatedValue:) work?
Instead do
func repeater(string: String, withNumber number: Int) -> String {
var repeatedString = String()
for i in 0..<number {
repeatedString += string
}
return repeatedString
}
As of Swift 3, init(repeating:count:) can take String arguments as well.
var repeatingString = String(repeating: "ab", count: 7)
print(repeatingString) // ababababababab
Try this:
func repeater(aString: String, withNumber: Int) -> String {
let repeatedString = String(repeating: aString, count: withNumber)
return repeatedString
}
The type of argument repeatedValue of String(count: repeatedValue: aCharacter) is a single Character rather than String.
You could use Array(count: withNumber, repeatedValue: aString) and join the items
func repeater(aString: String, withNumber: UInt) -> String {
let repeatedArray = Array(count: Int(withNumber), repeatedValue: aString)
return repeatedArray.joinWithSeparator("")
}
You can just do this:
func repeater(aString string: String, withNumber number: Int) -> String {
return [String](count: number, repeatedValue: string).reduce("", combine: +)
}
I slightly modify Tanguy's answer. Now, the function will not crash if the second parameter is less than 0, but returns an empty string
func repeate(string: String, times: Int) -> String {
var repeatedString = ""
var n = times
while n > 0 {
repeatedString += string
n -= 1
}
return repeatedString
}
the same issue has Vadian's answer, checking the parameter and returning early with "" is probably the best solution. I personally prefer Vadian's approach.
func repeatAString(anyString: String, numberOfTimes: Int) ->String
{
var returnString: String = String()
for _ in 1...numberOfTimes
{
returnString = returnString.stringByAppendingString(anyString)
}
return returnString
}
Hi I have used stringByappendingString to solve this one. I see you have plenty of answers but no-one has suggested that so thought Id put it out there. Cheers!
Swift 5:
extension String {
func repeater(by count: Int) -> String {
return String(repeating: self, count: count)
}
}
Calling: "testString".repeater(by: counter)
func repeater(aString: String, withNumber: Int) -> String
{
var repeatedString=""
for var index = 0; index < withNumber; ++index
{
repeatedString=repeatedString+aString
}
return repeatedString
}
for Function Call i use:
print(repeater("String",withNumber: 5))
Related
I want to check a String to see if it contains a single letter. Here's the code:
func CheckLetter(letter:String,word:String) -> String{
var checkFlag = false
var tempWord = [""]
for n in 0...(word.count-1){
if tempWord[n] == letter[0]{
}
}
}
And the error is:
'subscript(_:)' is unavailable: cannot subscript String with an Int, use a String.Index instead.
Checkout Swift String Cheat Sheet, by Keith Harrison
You can use:
func checkLetter(letter: String, word: String) -> String {
return word.contains(letter).description
}
try this !!!
func CheckLetter(letter:String,word:String) -> Bool{
var checkFlag = false
if word.contains(letter) {
checkFlag = true
}
return checkFlag
}
Can the new Swift 5 shuffle() method be used (directly, or in a more complicated incantation) to randomly shuffle the characters in a Swift string variable? (of length greater than 1)
You can simply try this nifty code
extension String {
func shuffleString(minLength : Int) -> String{
return self.count > minLength ? String(self.shuffled()) : self
}
}
var string = "Whatever is your string"
print(string.shuffleString(minLength: 1))
You can maybe try something like this.
var str = "Hello"
var shuffledString = String(str.shuffled())
try this function
func shuffleString(word: String) -> String {
var chars = Array(word.characters)
var result = ""
while chars.count > 0 {
let index = Int(arc4random_uniform(UInt32(chars.count - 1)))
chars[index].writeTo(&result)
chars.removeAtIndex(index)
}
return result
}
Try this one
var str = "Shuffle me please"
var shuffledStr: [Character]
if !str.isEmpty {
shuffledStr = str.shuffled()
print(String(shuffledStr))
}
In Swift 4 I had a function to dump Range to debugger output defined as below:
extension Range where Bound == String.Index {
public func dump() -> String {
return "[\(lowerBound.encodedOffset)..<\(upperBound.encodedOffset)] (\(length))"
}
public var length: Int {
return upperBound.encodedOffset - lowerBound.encodedOffset
}
}
Since in Swift 5 encodedOffset is deprecated I changed implementation as below:
extension Range where Bound == String.Index {
public func dump(string: String) -> String {
let lower = lowerBound.utf16Offset(in: string)
let upper = upperBound.utf16Offset(in: string)
let result = "[\(lower)..<\(upper)] (\(upper - lower))"
return result
}
}
Test working as expected after implementation update:
class RangeTests: LogicTestCase {
func test_dump() {
let string = "Hello!"
let range = string.range(of: "ello")
Assert.equals(range?.dump(string: string), "[1..<5] (4)")
}
}
But function dump works correctly even if empty string is passed:
class RangeTests: LogicTestCase {
func test_dump() {
let string = "Hello!"
let range = string.range(of: "ello")
Assert.equals(range?.dump(string: ""), "[1..<5] (4)")
}
}
Shouldn't, for instance, call to lowerBound.utf16Offset(in: "") throw exception because we passing empty string, while range itself not empty?
UPDATE 1:
With the trick, with lowerBound.utf16Offset(in: "") mentioned above, the following two versions of sample index shifting function works identically:
extension String.Index {
// Deprecated due `encodedOffset`
public func shifting(by offset: Int) -> String.Index {
return String.Index(encodedOffset: encodedOffset + offset)
}
// Possible Swift 5 replacement to achieve index manipulation
// without reference to string.
public func shifting(by offset: Int) -> String.Index {
let newOffset = utf16Offset(in: "") + offset
let referenceString = String(repeating: " ", count: newOffset)
let result = String.Index(utf16Offset: newOffset, in: referenceString)
return result
}
}
I am trying to solve the Palindrome Partitioning Question. You can find the question in https://leetcode.com/problems/palindrome-partitioning/.
And I came up with the solution:
func partition(_ s: String) -> [[String]] {
var result: [[String]] = []
func dfs(string: String, partiton: [String]) {
if string.characters.count == 0 {
result.append(partiton)
return
}
for length in 1...string.characters.count {
let endIndex = string.index(string.startIndex, offsetBy: length-1)
let part = string[string.startIndex...endIndex]
if isPalindrome(part) {
let leftPart = string[string.index(after: endIndex)..<string.endIndex]
print("string: \(string) part: \(part) leftpart: \(leftPart)")
dfs(string: leftPart, partiton: partiton + [part])
}
}
}
func isPalindrome(_ s: String) -> Bool {
if String(s.characters.reversed()) == s {
return true
} else {
return false
}
}
dfs(string: s, partiton: [])
return result
}
But the performance is Bad. Time Limit Exceeded.
But the same idea with Python implementation can pass:
def partition(self, s):
res = []
self.dfs(s, [], res)
return res
def dfs(self, s, path, res):
if not s:
res.append(path)
return
for i in range(1, len(s)+1):
if self.isPal(s[:i]):
self.dfs(s[i:], path+[s[:i]], res)
def isPal(self, s):
return s == s[::-1]
It make me wonder that how to improve the swift implementation and why the swift implementation is slower than python.
A Swift String is a collection of Characters, and a Character represents a single extended grapheme cluster, that can be one or more
Unicode scalars. That makes some index operations like "skip the first N characters" slow.
But the first improvement is to "short-circuit" the isPalindrome()
function. Instead of building the reversed string completely, compare
the character sequence with its reversed sequence and stop as soon
as a difference is found:
func isPalindrome(_ s: String) -> Bool {
return !zip(s.characters, s.characters.reversed()).contains { $0 != $1 }
}
s.characters.reversed() does not create a new collection in reverse
order, it just enumerates the characters from back to front.
With String(s.characters.reversed()) as in your method however,
you force the creation of a new collection for the reversed string,
that makes it slow.
For the 110-character string
let string = String(repeating: "Hello world", count: 10)
this reduces the computation time from about 6 sec to 1.2 sec in my test.
Next, avoid index calculations like
let endIndex = string.index(string.startIndex, offsetBy: length-1)
and iterate over the character index itself instead:
func partition(_ s: String) -> [[String]] {
var result: [[String]] = []
func dfs(string: String, partiton: [String]) {
if string.isEmpty {
result.append(partiton)
return
}
var idx = string.startIndex
repeat {
string.characters.formIndex(after: &idx)
let part = string.substring(to: idx)
if isPalindrome(part) {
let leftPart = string.substring(from: idx)
dfs(string: leftPart, partiton: partiton + [part])
}
} while idx != string.endIndex
}
func isPalindrome(_ s: String) -> Bool {
return !zip(s.characters, s.characters.reversed()).contains { $0 != $1 }
}
dfs(string: s, partiton: [])
return result
}
Computation time is now 0.7 sec.
The next step is to avoid string indexing totally, and work with
an array of characters, because array indexing is fast. Even better,
use array slices which are fast to create and reference the original
array elements:
func partition(_ s: String) -> [[String]] {
var result: [[String]] = []
func dfs(chars: ArraySlice<Character>, partiton: [String]) {
if chars.isEmpty {
result.append(partiton)
return
}
for length in 1...chars.count {
let part = chars.prefix(length)
if isPalindrome(part) {
let leftPart = chars.dropFirst(length)
dfs(chars: leftPart, partiton: partiton + [String(part)])
}
}
}
func isPalindrome(_ c: ArraySlice<Character>) -> Bool {
return !zip(c, c.reversed()).contains { $0 != $1 }
}
dfs(chars: ArraySlice(s.characters), partiton: [])
return result
}
Computation time is now 0.08 sec.
If your string contains only characters in the "basic multilingual plane" (i.e. <= U+FFFF) then you can work with UTF-16 code points instead:
func partition(_ s: String) -> [[String]] {
var result: [[String]] = []
func dfs(chars: ArraySlice<UInt16>, partiton: [String]) {
if chars.isEmpty {
result.append(partiton)
return
}
for length in 1...chars.count {
let part = chars.prefix(length)
if isPalindrome(part) {
let leftPart = chars.dropFirst(length)
part.withUnsafeBufferPointer {
dfs(chars: leftPart, partiton: partiton + [String(utf16CodeUnits: $0.baseAddress!, count: length)])
}
}
}
}
func isPalindrome(_ c: ArraySlice<UInt16>) -> Bool {
return !zip(c, c.reversed()).contains { $0 != $1 }
}
dfs(chars: ArraySlice(s.utf16), partiton: [])
return result
}
Computation time is now 0.04 sec for the 110 character test string.
So some tips which potentially can improve the performance when working with Swift strings are
Iterate over the characters/indices sequentially. Avoid "jumping"
to the n'th position.
If you need "random" access to all characters, convert the string
to an array first.
Working with the UTF-16 view of a string can be faster than working
with the characters view.
Of course it depends on the actual use-case. In this application,
we were able to reduce the computation time from 6 sec to 0.04 sec,
that is a factor of 150.
Previously, when you wanted to see if your Swift string contained another string, you would cast it to a NSString and call .containsString. Apple, in their infinite wisdom, made this version-aware, so if you try it under S2 it will demand a #available wrapper even if your target platform does support it (which I guess is a bug).
So the best solution appears to be this:
extension String {
func contains(substr: String) -> Bool {
if #available(OSX 10.10, *) {
return NSString(string: self).containsString(substr)
} else {
return self.rangeOfString(substr) != nil
}
}
}
and now to check it, instead of this:
if NSString(string: line).containsString(" ")...
you get to use the much nicer looking:
if line.contains(" ")...
This no longer complains about the version, and (IMHO) looks better too. You almost certainly want this too:
extension String {
var length: Int {
return self.characters.count
}
}
Apple keeps changing the way you get length, and I hope that any future changes to the API will be #available-able, at which point .length can be easily modified. And these are just for sanity:
extension String {
subscript (r: Range<Int>) -> String {
get {
let subStart = advance(self.startIndex, r.startIndex, self.endIndex)
let subEnd = advance(subStart, r.endIndex - r.startIndex, self.endIndex)
return self.substringWithRange(Range(start: subStart, end: subEnd))
}
}
func substring(from: Int) -> String {
let end = self.characters.count
return self[from..<end]
}
func substring(from: Int, length: Int) -> String {
let end = from + length
return self[from..<end]
}
}
extension String {
func trim() -> String {
return self.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
}
func trim(withSet: NSCharacterSet) -> String {
return self.stringByTrimmingCharactersInSet(withSet)
}
}