How to remove character from odd index in Swift String - ios

I am new to swift & I have to remove characters from odd index from a given String
I did try with following code
var myStringObject = "HelloTestString"
myStringObject.enumerated().filter({ index, char in !(char == "1" && index % 2 == 0) })
but I am unable to find the desired result string. Can you please guide me how to remove characters from odd index in String.

You can't filter by two filter types. So you have to move to the old approach.
var myStringObject = "HelloTestString"
var newString = ""
var index = 0
while index < myStringObject.count {
if index % 2 != 0 {
let firstIndex: String.Index = myStringObject.startIndex
let desiredChar: Character = myStringObject[myStringObject.index(firstIndex, offsetBy: index)]
newString = newString + "\(desiredChar)"
}
index = index + 1
}
print(newString) //elTsSrn

Keeping your starting solution:
let couples = myStringObject.enumerated().filter { (arg0) -> Bool in
let (offset, _) = arg0
return offset % 2 == 0
}
print(couples)
or
let couples = myStringObject.enumerated().filter { (offset, _) -> Bool in
return offset % 2 == 0
} //Which is more similar to last version
You have then, an array of Tuples where first element is the offset, and the second the element.
$>[(offset: 0, element: "H"), (offset: 2, element: "l"), (offset: 4, element: "o"), (offset: 6, element: "e"), (offset: 8, element: "t"), (offset: 10, element: "t"), (offset: 12, element: "i"), (offset: 14, element: "g")]
Let's keep only the letters (and back to String, not a String.Element):
let onlyletters = couples.map({ String($0.element) })
Let's get it back into a String.
let result = onlyletters.joined()
In one line:
let oneLine = myStringObject.enumerated().filter({ $0.0 % 2 == 0 }).map({ String($0.element) }).joined()

You can create an auxiliary index and use defer to increase it after each iteration on your collection, this way you don't need to enumerate your string:
let string = "HelloTestString"
var index = 0
let filtered = string.filter { _ in
defer { index += 1 }
return index % 2 == 1
}
print(filtered) // "elTsSrn"
If you need to mutate your original string you can use removeAll with the same approach:
var string = "HelloTestString"
var index = 0
string.removeAll { _ in
defer { index += 1 }
return index % 2 == 0
}
print(string)
Implementing your own method
extending RangeReplaceableCollection to filter or removeAll elements that are in odd or even positions:
extension RangeReplaceableCollection {
var oddIndicesElements: Self {
var position = 0
return filter { _ in
defer { position += 1 }
return position % 2 == 1
}
}
var evenIndicesElements: Self {
var position = 0
return filter { _ in
defer { position += 1 }
return position % 2 == 0
}
}
mutating func removeAllEvenIndicesElements() {
var position = 0
removeAll { _ in
defer { position += 1 }
return position % 2 == 0
}
}
mutating func removeAllOddIndicesElements() {
var position = 0
removeAll { _ in
defer { position += 1 }
return position % 2 == 1
}
}
}
var myStringObject = "HelloTestString"
print(myStringObject.oddIndicesElements) // "elTsSrn"
myStringObject.removeAllEvenIndicesElements()
print(myStringObject) // "elTsSrn"

Related

Gradually and randomly visualize string

I am currently working on a simple program to gradually and randomly visualize a string in two iteration. Right now I have managed to get the first iteration but I'm not sure how to do the second one. If someone could give any example or advice I would be very grateful. My code looks like this:
let s = "Hello playground"
let factor = 0.25
let factor2 = 0.45
var n = s.filter({ $0 != " " }).count // # of non-space characters
var m = lrint(factor * Double(n)) // # of characters to display
let t = String(s.map { c -> Character in
if c == " " {
// Preserve space
return " "
} else if Int.random(in: 0..<n) < m {
// Replace
m -= 1
n -= 1
return c
} else {
// Keep
n -= 1
return "_"
}
})
print(t) // h_l__ _l_______d
To clarify, I want to use factor2 in the second iteration to print something that randomly add letters on top of t that looks something like this h_l_o pl_g_____d.
Replacing Characters
Starting from #MartinR's code, you should remember the indices that have been replaced. So, I am going to slightly change the code that replaces characters :
let s = "Hello playground"
let factor = 0.25
let factor2 = 0.45
var n = s.filter({ $0 != " " }).count // # of non-space characters
let nonSpaces = n
var m = lrint(factor * Double(n)) // # of characters to display
var indices = Array(s.indices)
var t = ""
for i in s.indices {
let c = s[i]
if c == " " {
// Preserve space
t.append(" ")
indices.removeAll(where: { $0 == i })
} else if Int.random(in: 0..<n) < m {
// Keep
m -= 1
n -= 1
t.append(c)
indices.removeAll(where: { $0 == i })
} else {
// Replace
n -= 1
t.append("_")
}
}
print(t) //For example: _e___ ______ou_d
Revealing Characters
In order to do that, we should calculate the number of characters that we want to reveal:
m = lrint((factor2 - factor) * Double(nonSpaces))
To pick three indices to reveal randomly, we shuffle indices and then replace the m first indices :
indices.shuffle()
var u = t
for i in 0..<m {
let index = indices[i]
u.replaceSubrange(index..<u.index(after: index), with: String(s[index]))
}
indices.removeSubrange(0..<m)
print(u) //For example: _e__o _l__g_ou_d
I wrote StringRevealer struct, that handle all revealing logic for you:
/// Hide all unicode letter characters as `_` symbol.
struct StringRevealer {
/// We need mapping between index of string character and his position in state array.
/// This struct represent one such record
private struct Symbol: Hashable {
let index: String.Index
let position: Int
}
private let originalString: String
private var currentState: [Character]
private let charactersCount: Int
private var revealed: Int
var revealedPercent: Double {
return Double(revealed) / Double(charactersCount)
}
private var unrevealedSymbols: Set<Symbol>
init(_ text: String) {
originalString = text
var state: [Character] = []
var symbols: [Symbol] = []
var count = 0
var index = originalString.startIndex
var i = 0
while index != originalString.endIndex {
let char = originalString[index]
if CharacterSet.letters.contains(char.unicodeScalars.first!) {
state.append("_")
symbols.append(Symbol(index: index, position: i))
count += 1
} else {
state.append(char)
}
index = originalString.index(after: index)
i += 1
}
currentState = state
charactersCount = count
revealed = 0
unrevealedSymbols = Set(symbols)
}
/// Current state of text. O(n) conplexity
func text() -> String {
return currentState.reduce(into: "") { $0.append($1) }
}
/// Reveal one random symbol in string
mutating func reveal() {
guard let symbol = unrevealedSymbols.randomElement() else { return }
unrevealedSymbols.remove(symbol)
currentState[symbol.position] = originalString[symbol.index]
revealed += 1
}
/// Reveal random symbols on string until `revealedPercent` > `percent`
mutating func reveal(until percent: Double) {
guard percent <= 1 else { return }
while revealedPercent < percent {
reveal()
}
}
}
var revealer = StringRevealer("Hello товарищ! 👋")
print(revealer.text())
print(revealer.revealedPercent)
for percent in [0.25, 0.45, 0.8] {
revealer.reveal(until: percent)
print(revealer.text())
print(revealer.revealedPercent)
}
It use CharacterSet.letters inside, so most of languages should be supported, emoji ignored and not-alphabetic characters as well.

Get a random unique element from an Array until all elements have been picked in Swift

I have the following array:
var notebookCovers = ["cover1", "cover2", "cover3", "cover4", "cover4", "cover6", "cover7", "cover8", "cover9", "cover10"]
and a UIButton that when it's pressed it generates a new UIImage with one of the elements of the array.
What I need to do is every time the button is tapped to generate random but unique element from the array (without repeating the elements) until they've all been selected and then restart the array again.
So far, I have it getting a random element but it's repeated and I cannot figure out how to it so it gets a unique image every time
func createNewNotebook() {
let newNotebook = Notebook()
let randomInt = randomNumber()
newNotebook.coverImageString = notebookCovers[randomInt]
notebooks.insert(newNotebook, at: 0)
collectionView.reloadData()
}
func randomNumber() -> Int {
var previousNumber = arc4random_uniform(UInt32(notebookCovers.count))
var randomNumber = arc4random_uniform(UInt32(notebookCovers.count - 1))
notebookCovers.shuffle()
if randomNumber == previousNumber {
randomNumber = UInt32(notebookCovers.count - 1)
}
previousNumber = randomNumber
return Int(randomNumber)
}
Set is a collection type that holds unique elements. Converting your notebooks array to Set also lets you take advantage of its randomElement function
var aSet = Set(notebooks)
let element = aSet.randomElement()
aSet.remove(element)
Copy the array. Shuffle the copy. Now just keep removing the first element until the copy is empty. When it is empty, start over.
Example:
let arr = [1,2,3,4,5]
var copy = [Int]()
for _ in 1...30 { // just to demonstrate what happens
if copy.isEmpty { copy = arr; copy.shuffle() }
let element = copy.removeFirst() ; print(element, terminator:" ")
}
// 4 2 3 5 1 1 5 3 2 4 4 1 2 3 5 1 4 5 2 3 3 5 4 2 1 3 2 4 5 1
If you want to create a looping solution:
let originalSet = Set(arrayLiteral: "a","b","c")
var selectableSet = originalSet
func repeatingRandomObject() -> String {
if selectableSet.isEmpty {
selectableSet = originalSet
}
return selectableSet.remove(selectableSet.randomElement()!)!
}
force unwrapping is kind of safe here, since we know that the result will never be nil. If you don't want to force unwrap:
let originalSet = Set(arrayLiteral: "a","b","c")
var selectableSet = originalSet
func repeatingRandomObject() -> String? {
if selectableSet.isEmpty {
selectableSet = originalSet
}
guard let randomElement = selectableSet.randomElement() else { return nil }
return selectableSet.remove(randomElement)
}
You can try something like this,
var notebookCovers = ["cover1", "cover2", "cover3", "cover4", "cover4", "cover6", "cover7", "cover8", "cover9", "cover10"]
var tappedNotebooks: [String] = []
func tapping() {
let notebook = notebookCovers[Int.random(in: 0...notebookCovers.count - 1)]
if tappedNotebooks.contains(notebook){
print("already exists trying again!")
tapping()
} else {
tappedNotebooks.append(notebook)
print("appended", notebook)
}
if tappedNotebooks == notebookCovers {
tappedNotebooks = []
print("cleared Tapped notebooks")
}
}
It is possible with shuffle:
struct AnantShuffler<Base: MutableCollection> {
private var collection: Base
private var index: Base.Index
public init?(collection: Base) {
guard !collection.isEmpty else {
return nil
}
self.collection = collection
self.index = collection.startIndex
}
public mutating func next() -> Base.Iterator.Element {
let result = collection[index]
collection.formIndex(after: &index)
if index == collection.endIndex {
collection.shuffle()
index = collection.startIndex
}
return result
}
}
fileprivate extension MutableCollection {
/// Shuffles the contents of this collection.
mutating func shuffle() {
let c = count
guard c > 1 else { return }
for (firstUnshuffled, unshuffledCount) in zip(indices, stride(from: c, to: 1, by: -1)) {
let d: Int = numericCast(arc4random_uniform(numericCast(unshuffledCount)))
let i = index(firstUnshuffled, offsetBy: d)
swapAt(firstUnshuffled, i)
}
}
}
Use:
let shuffler = AnantShuffler(collection: ["c1","c2","c3"].shuffled())
shuffler?.next()

Tracking specific data in string using Swift

I'm still new to coding in general, and I'm running into an issue with a Swift exercise.
How do I track the number of times 1 (var numberOfSteps) and the number of times 2 (var numberOfHeartBeats) appears in the string "12221231221"?
I'm given the following hint but not sure how the for-in loop applies:
let activityData = "12221231221"
var numberOfSteps = 0
var numberOfHeartBeats = 0
for character in activityData{
print(character)
}
In the loop, you can check what the current characters is, then increment the variable accordingly:
let activityData = "12221231221"
var numberOfSteps = 0
var numberOfHeartBeats = 0
for character in activityData{
if character == "1" {
numberOfSteps += 1
} else if character == "2" {
numberOfHeartBeats += 1
}
}
Here is another more functional way of doing this:
let dict = Dictionary(grouping: activityData, by: { $0 }).mapValues { $0.count }
dict["1"] is numberOfSteps and dict["2"] is numberOfHeartBeats.
If you want to use a for loop you can do
let activityData = "12221231221"
var numberOfSteps = 0
var numberOfHeartBeats = 0
for character in activityData{
switch character {
case "1":
numberOfSteps += 1
case "2":
numberOfHeartBeats += 1
default:
break
}
}
print("Number of steps: \(numberOfSteps), number of hartbeats: \(numberOfHeartBeats)")
A more condensed solution without a loop
print("Number of steps: \( activityData.filter( {$0 == "1"} ).count), number of hartbeats: \( activityData.filter( {$0 == "2"} ).count)")
You can do this in simple way like this:-
let occurrencesOne = text.characters.filter { $0 == "1" }.count
let occurrencesTwo = text.characters.filter { $0 == "2" }.count
Another solution is to use reduce, this is kinda equivalent to manually looping over the string, but will less and mode concise code, and without the disadvantage of creating intermediary structures (like arrays of dictionaries), which for large inputs can be problematic memory-wise:
let activityData = "12221231221"
let numberOfSteps = activityData.reduce(0) { $1 == "1" ? $0 + 1 : $0 }
let numberOfHeartBeats = activityData.reduce(0) { $1 == "2" ? $0 + 1 : $0 }
print(numberOfSteps, numberOfHeartBeats) // 4, 6
One step further to reduce the duplication would be to add a function to compute the reduce second parameter:
func countIf<T: Equatable>(_ search: T) -> (Int, T) -> Int {
return { $1 == search ? $0 + 1 : $0 }
}
let activityData = "12221231221"
let numberOfSteps: Int = activityData.reduce(0, countIf("1"))
let numberOfHeartBeats = activityData.reduce(0, countIf("2"))
print(numberOfSteps, numberOfHeartBeats)

How can split from string to array by chunks of given size

I want to split string by chunks of given size 2
Example :
String "1234567" and output should be ["12", "34", "56","7"]
You can group your collection elements (in this case Characters) every n elements as follow:
extension Collection {
func unfoldSubSequences(limitedTo maxLength: Int) -> UnfoldSequence<SubSequence,Index> {
sequence(state: startIndex) { start in
guard start < self.endIndex else { return nil }
let end = self.index(start, offsetBy: maxLength, limitedBy: self.endIndex) ?? self.endIndex
defer { start = end }
return self[start..<end]
}
}
func subSequences(of n: Int) -> [SubSequence] {
.init(unfoldSubSequences(limitedTo: n))
}
}
let numbers = "1234567"
let subSequences = numbers.subSequences(of: 2)
print(subSequences) // ["12", "34", "56", "7"]
edit/update:
If you would like to append the exceeding characters to the last group:
extension Collection {
func unfoldSubSequencesWithTail(lenght: Int) -> UnfoldSequence<SubSequence,Index> {
let n = count / lenght
var counter = 0
return sequence(state: startIndex) { start in
guard start < endIndex else { return nil }
let end = index(start, offsetBy: lenght, limitedBy: endIndex) ?? endIndex
counter += 1
if counter == n {
defer { start = endIndex }
return self[start...]
} else {
defer { start = end }
return self[start..<end]
}
}
}
func subSequencesWithTail(n: Int) -> [SubSequence] {
.init(unfoldSubSequencesWithTail(lenght: n))
}
}
let numbers = "1234567"
let subSequencesWithTail = numbers.subSequencesWithTail(n: 2)
print(subSequencesWithTail) // ["12", "34", "567"]
var testString = "abcdefghijklmnopqrstu"
var startingPoint: Int = 0
var substringLength: Int = 1
var substringArray = [AnyHashable]()
for i in 0..<(testString.count ?? 0) / substringLength {
var substring: String = (testString as NSString).substring(with: NSRange(location: startingPoint, length: substringLength))
substringArray.append(substring)
startingPoint += substringLength
}
print("\(substringArray)")
OutPut :
(
a,
b,
c,
d,
e,
f,
g,
h,
i,
j,
k,
l,
m,
n,
o,
p,
q,
r,
s,
t,
u
)
try this
func SplitString(stringToBeSplitted:String, By:Int) -> [String]
{
var newArray = [String]()
var newStr = String()
for char in stringToBeSplitted
{
newStr += String(char)
if newStr.count == By
{
newArray.append(newStr)
newStr = ""
}
}
return newArray
}
Swift 5
extension Array {
func chunks(size: Int) -> [[Element]] {
return stride(from: 0, to: count, by: size).map {
Array(self[$0 ..< Swift.min($0 + size, count)])
}
}
}
extension String {
func chunks(size: Int) -> [String] {
map { $0 }.chunks(size: size).compactMap { String($0) }
}
}
let s = "1234567"
print(s.chunks(size: 2)) // ["12", "34", "56", "7"]
extension String {
func split(len: Int) -> [String] {
var currentIndex = 0
var array = [String]()
let length = self.characters.count
while currentIndex < length {
let startIndex = self.startIndex.advancedBy(currentIndex)
let endIndex = startIndex.advancedBy(len, limit: self.endIndex)
let substr = self.substringWithRange(Range(start: startIndex, end: endIndex))
array.append(substr)
currentIndex += len
}
return array
}
}
"123456789".split(2)
//output: ["12", "34", "56", "78", "9"]
I have write one method in objective c as below,
-(NSMutableArray*)splitString : (NSString*)str withRange : (int)range{
NSMutableArray *arr = [[NSMutableArray alloc]init];
NSMutableString *mutableStr = [[NSMutableString alloc]initWithString:str];
int j = 0;
int counter = 0;
for (int i = 0; i < str.length; i++) {
j++;
if (range == j) {
j = 0;
if (!(i == str.length - 1)) {
[mutableStr insertString:#"$" atIndex:i+1+counter];
}
counter++;
}
}
arr = (NSMutableArray*)[mutableStr componentsSeparatedByString:#"$"];
NSLog(#"%#",arr);
return arr;
}
You can call this method like,
[self splitString:#"123456" withRange:2];
and result will be,
(
12,
34,
56
)
You can also try below code:
var arrStr: [Substring] = []
let str = "1234567"
var i = 0
while i < str.count - 1 {
let index = str.index(str.startIndex, offsetBy: i)
//Below line gets current index and advances by 2
let substring = str[index..<str.index(index, offsetBy: 2)]
arrStr.append(substring)
i += 2
}
if str.count % 2 == 1 {
arrStr.append(str.suffix(1))
}
print(arrStr)
There's a stupid way, you can think about the rules of the data model.
var strOld = "123456"
print("The original string:\(strOld)")
strOld.insert("、", at: strOld.index(before: strOld.index(strOld.startIndex, offsetBy: 3)))
strOld.insert("、", at: strOld.index(before: strOld.index(strOld.startIndex, offsetBy: 6)))
print("After inserting:\(strOld)")
let str = strOld
let splitedArray = str.components(separatedBy: "、")
print("After the split of the array:\(splitedArray)")
let splitedArrayOther = str.split{$0 == "、"}.map(String.init)
print("After break up the array (method 2):\(splitedArrayOther)")
The results:
The original string:123456
After inserting:12、34、56
After the split of the array:["12", "34", "56"]
After break up the array (method 2):["12", "34", "56"]
Here's a short (and clean) solution, thanks to recursion:
extension Collection {
func chunks(ofSize size: Int) -> [SubSequence] {
// replace this by `guard count >= size else { return [] }`
// if you want to omit incomplete chunks
guard !isEmpty else { return [] }
return [prefix(size)] + dropFirst(size).chunks(ofSize: size)
}
}
The recursion should not pose a performance problem, as Swift has support for tail call optimization.
Also if Swift arrays are really fast when it comes to prepending or appending elements (like the Objective-C ones are), then the array operations should be fast.
Thus you get both fast and readable code (assuming my array assumptions are true).

'Generator' is not a member type of 'Self.SubSequence'

I'm trying to convert the below Swift 2 extension method to Swift 3.
extension CollectionType {
func chunk(withDistance distance: Index.Distance) -> [[SubSequence.Generator.Element]] {
var index = startIndex
let generator: AnyGenerator<Array<SubSequence.Generator.Element>> = anyGenerator {
defer { index = index.advancedBy(distance, limit: self.endIndex) }
return index != self.endIndex ? Array(self[index ..< index.advancedBy(distance, limit: self.endIndex)]) : nil
}
return Array(generator)
}
}
The Xcode conversion tool left me with this.
extension Collection {
func chunk(withDistance distance: Int) -> [[SubSequence.Iterator.Element]] {
var index = startIndex
let generator: AnyGenerator<Array<SubSequence.Generator.Element>> = anyGenerator {
defer { index = index.advancedBy(distance, limit: self.endIndex) }
return index != self.endIndex ? Array(self[index ..< index.advancedBy(distance, limit: self.endIndex)]) : nil
}
return Array(generator)
}
}
Now I'm getting the above error at line, let generator: AnyGenerator<Array<SubSequence.Generator.Element>> = anyGenerator {. I can't figure out how to fix this.
There are several problems:
Generator has been renamed to Iterator in Swift 3, and consequently
AnyGenerator to AnyIterator.
IndexDistance is the associated type of Collection which
represents the number of steps between indices.
In Swift 3, "Collections move their index", see
A New Model for Collections and Indices on Swift evolution.
Putting it all together, your method could look in Swift 3 like this:
extension Collection {
func chunk(withDistance distance: IndexDistance) -> [[SubSequence.Iterator.Element]] {
var pos = startIndex
let iterator: AnyIterator<Array<SubSequence.Iterator.Element>> = AnyIterator {
// Already at the end?
if pos == self.endIndex { return nil }
// Compute `pos + distance`, but not beyond `self.endIndex`:
let endPos = self.index(pos, offsetBy: distance, limitedBy: self.endIndex) ?? self.endIndex
// Return chunk and advance `pos`:
defer { pos = endPos }
return Array(self[pos..<endPos])
}
return Array(iterator)
}
}
let a = [1, 2, 3, 4, 5, 6, 7]
let c = a.chunk(withDistance: 2)
print(c) // [[1, 2], [3, 4], [5, 6], [7]]
I have renamed the local index variable to pos, to avoid
confusion with the
public func index(_ i: Self.Index, offsetBy n: Self.IndexDistance, limitedBy limit: Self.Index) -> Self.Index?
method of Collection which is used to advance the index.
Of course you could achieve the same result with a while loop
instead of an Iterator:
extension Collection {
func chunk(withDistance distance: IndexDistance) -> [[SubSequence.Iterator.Element]] {
var result: [[SubSequence.Iterator.Element]] = []
var pos = startIndex
while pos != endIndex {
let endPos = self.index(pos, offsetBy: distance, limitedBy: self.endIndex) ?? self.endIndex
result.append(Array(self[pos..<endPos]))
pos = endPos
}
return result
}
}

Resources