Delete some lines from string - swift

I have a string:
first line
second line
first line
first line
second line
first line
How can I remove secondlines from this string? Secondlines are always different, firsts too. Only division between them is \n\n.

import Foundation
let string = "first line\n"
+ "second line\n"
+ "\n"
+ "first line\n"
+ "\n"
+ "first line\n"
+ "second line\n"
+ "\n"
+ "first line"
func removeSecondLines1(string: String) -> String {
let tokens = string.components(separatedBy: "\n")
var deletedString = tokens[0]
for i in 1...tokens.count - 1 {
if tokens[i] == "" || tokens[i - 1] == "" {
deletedString = deletedString + "\n" + tokens[i]
}
}
return deletedString
}
func removeSecondLines2(string: String) -> String {
let tokens = string.components(separatedBy: "\n\n")
var deletedTokens = [String]()
for token in tokens {
deletedTokens.append(token.components(separatedBy: "\n")[0])
}
return deletedTokens.joined(separator: "\n\n")
}
print(removeSecondLines1(string: string))
print(removeSecondLines2(string: string))
Both will output
first line
first line
first line
first line

Just for fun a solution with Regular Expression:
let string = "first line\nsecond line\n\nfirst line\n\nfirst line\nsecond line\n\nfirst line"
let pattern = "\\n[^\\n]+\\n\n"
let result = string.replacingOccurrences(of: pattern, with: "\n\n", options: .regularExpression)
print(result)

Related

Split String into Array keeping delimiter/separator in Swift

Looking for an (elegant) solution for splitting a string and keeping the separator as item(s) in the array
example 1:
"hello world"
["hello", " ", "world"]
example 2:
" hello world"
[" ", "hello", " ", "world"]
thx.
Suppose you are splitting the string by a separator called separator, you can do the following:
let result = yourString.components(separatedBy: separator) // first split
.flatMap { [$0, separator] } // add the separator after each split
.dropLast() // remove the last separator added
.filter { $0 != "" } // remove empty strings
For example:
let result = " Hello World ".components(separatedBy: " ").flatMap { [$0, " "] }.dropLast().filter { $0 != "" }
print(result) // [" ", "Hello", " ", "World", " "]
For people who have a condition for their split, for example: splitting a camelCaseString based on uppercase condition:
extension Sequence {
func splitIncludeDelimiter(whereSeparator shouldDelimit: (Element) throws -> Bool) rethrows -> [[Element]] {
try self.reduce([[]]) { group, next in
var group = group
if try shouldDelimit(next) {
group.append([next])
} else {
group[group.lastIdx].append(next)
}
return group
}
}
}
For example:
"iAmCamelCase".splitIncludeDelimiter(whereSeparator: \.isUppercase)
=>
["i", "Am", "Camel", "Case"]
(If you want the imp of isUppercase)
extension CharacterSet {
static let uppercaseLetters = CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
}
extension Unicode.Scalar {
var isUppercase: Bool {
CharacterSet.uppercaseLetters.contains(self)
}
}
Just for fun, the Swift Algorithms package contains an algorithm called Intersperse
After adding the package and
import Algorithms
you can write
let string = "hello world"
let separator = " "
let result = Array(string
.components(separatedBy: separator)
.interspersed(with: separator))
print(result)
Your second example is barely correct, the result of splitting " hello world" by space is
["", "hello", "world"]
let sample = "a\nb\n\nc\n\n\nd\n\nddddd\n \n \n \n\n"
let sep = "\n"
let result = sample.components(separatedBy: sep).flatMap {
$0 == "" ? [sep] : [$0, sep]
}.dropLast()
debugPrint(result) // ArraySlice(["a", "\n", "b", "\n", "\n", "c", "\n", "\n", "\n", "d", "\n", "\n", "ddddd", "\n", " ", "\n", " ", "\n", " ", "\n", "\n"])

Return a the matched words as string from two different string in Swift

I have: str1 = "this is the first day in my work" and str2 = "this is a great day" and I want to return the matched words as string from the previous two strings str1 & str2 and then store them in a new variable
The new variable str3: String should have this text "this is day"
I have found this in my searching but i need to return a string with matches ..
func isAnagram() -> Bool {
let str1 = "this is the first day in my work"
let str2 = "this is a great day"
func countedSet(string: String) -> NSCountedSet {
let array = string.map { (character) -> String in
return String(character)
}
return NSCountedSet(array: array)
}
return countedSet(string: str1).isEqual(countedSet(string: str2))
}
If order in the final string doesn't matter, this would be an easy solution:
let str1 = "this is the first day in my work"
let str2 = "this is a great day"
let words1 = Set(str1.split(separator: " "))
let words2 = Set(str2.split(separator: " "))
let str3 = words1.intersection(words2).reduce("") { $0 + $1 + " "}
If order matters:
...
let str3 = words1.intersection(words2).sorted {
words1.index(of: $0)! < words1.index(of: $1)!
}.reduce("") { $0 + $1 + " "}
You can use String method enumerateSubstrings(in: Range) using .byWords options to get the words in your string sentences and use filter to remove the words no contained in the second string:
extension StringProtocol where Index == String.Index {
var words: [String] {
var result: [String] = []
enumerateSubstrings(in: startIndex..., options: .byWords) { (substring, _, _, _) in
result.append(substring!)
}
return result
}
func matchingWords(in string: String) -> [String] {
return string.words.filter(words.contains)
}
}
Note that this preserves the order of occurrences and doesn't fail if there is punctuation in the string:
let str1 = "this is the first day in my work"
let str2 = "this is a great day"
let matchingWords = str1.matchingWords(in: str2) // ["this", "is", "day"]
let str3 = matchingWords.joined(separator: " ") // "this is day"

Reversing Words Functionally in Swift

I can reverse every word in a string functionally without using a loop, but when I try to reverse EVERY OTHER WORD. I run into problems. I can do it with a loop but not functionally. What am I not seeing here?
Functionally (every word):
import UIKit
let input = "This is a sample sentence"
func reverseWords(input: String) -> String {
let parts = input.components(separatedBy: " ")
let reversed = parts.map { String($0.reversed()) }
return reversed.joined(separator: " ")
}
reverseWords(input: input)
With loop (EVERY OTHER WORD):
var sampleSentence = "This is a sample sentence"
func reverseWordsInSentence(sentence: String) -> String {
let allWords = sampleSentence.components(separatedBy:" ")
var newSentence = ""
for index in 0...allWords.count - 1 {
let word = allWords[index]
if newSentence != "" {
newSentence += " "
}
if index % 2 == 1 {
let reverseWord = String(word.reversed())
newSentence += reverseWord
} else {
newSentence += word
}
}
return newSentence
}
reverseWordsInSentence(sentence: sampleSentence)
With a slight modification of your reverseWords you can reverse every other word. Use enumerated() to combine a word with its position, and then use that to reverse odd words:
let input = "one two three four five"
func reverseOddWords(input: String) -> String {
let parts = input.components(separatedBy: " ")
let reversed = parts.enumerated().map { $0 % 2 == 0 ? String($1.reversed()) : $1 }
return reversed.joined(separator: " ")
}
print(reverseOddWords(input: input))
eno two eerht four evif
Or you could pattern your function after Swift's sort and pass the filter closure to the reverseWords function:
let input = "one two three four five"
func reverseWords(_ input: String, using filter: ((Int) -> Bool) = { _ in true }) -> String {
let parts = input.components(separatedBy: " ")
let reversed = parts.enumerated().map { filter($0) ? String($1.reversed()) : $1 }
return reversed.joined(separator: " ")
}
// default behavior is to reverse all words
print(reverseWords("one two three four five"))
eno owt eerht ruof evif
print(reverseWords("one two three four five", using: { $0 % 2 == 1 }))
one owt three ruof five
print(reverseWords("one two three four five", using: { [0, 3, 4].contains($0) }))
eno two three ruof evif
let everyThirdWord = { $0 % 3 == 0 }
print(reverseWords("one two three four five", using: everyThirdWord))
eno two three ruof five
Use stride() to generate a sequence of indexes of every other word.
Then use forEach() to select each index in the stride array and use it to mutate the word at that index to reverse it.
import UIKit
let string = "Now is the time for all good programmers to babble incoherently"
var words = string.components(separatedBy: " ")
stride(from: 0, to: words.count, by: 2)
.forEach { words[$0] = String(words[$0].reversed()) }
let newString = words.joined(separator: " ")
print(newString)
The output string is:
"woN is eht time rof all doog programmers ot babble yltnerehocni"

Swift split string to array with exclusion

I write Swift application that parse log file.
log file string:
substr1 substr2 "substr 3" substr4
I need to get array: [substr1, substr2, substr 3, substr4]
But if I use something like:
print(stringLine.components(separatedBy: " "))
I got: [substr1, substr2, "substr, 3", substr4].
How to receive array: [substr1, substr2, substr 3, substr4]?
One of the possible solutions is to use map:
let testSting = "substr1 substr2 \"substr3\" substr4"
let mappedString = testString.components(separatedBy: " ").map({$0.replacingOccurrences(of: "\"", with: "")})
print(mappedString) //["substr1", "substr2", "substr3", "substr4"]
This case of the issue is required to use regular expression but this example is provided. So to solve problem in you're case it is possible to go in this way:
var testStingArray = testSting.replacingOccurrences(of: "\"", with: "").components(separatedBy: " ")
var arr = [String]()
var step = 0
while step < testStingArray.count {
var current = testStingArray[step]
var next = step + 1
if next < testStingArray.count {
if testStingArray[next].characters.count == 1 {
current += " " + testStingArray[next]
testStingArray.remove(at: next)
}
}
arr.append(current)
step += 1
}
print(arr)//["substr1", "substr2", "substr 3", "substr4"]
You'd better work with regular expression:
let pattern = "([^\\s\"]+|\"[^\"]+\")"
let regex = try! NSRegularExpression(pattern: pattern, options: [])
let line = "substr1 substr2 \"substr 3\" substr4"
let arr = regex.matches(in: line, options: [], range: NSRange(0..<line.utf16.count))
.map{(line as NSString).substring(with: $0.rangeAt(1)).trimmingCharacters(in: CharacterSet(charactersIn: "\""))}
print(arr) //->["substr1", "substr2", "substr 3", "substr4"]
Alternatively you could split the string based on a CharacterSet and then filter out the empty occurrences:
let stringLine = "substr1 substr2 \"substr3\" substr4"
let array = stringLine.components(separatedBy: CharacterSet(charactersIn: "\" ")).filter { !$0.isEmpty }
print (array)
Output: ["substr1", "substr2", "substr3", "substr4"]
But this will not work correctly if there is a " somewhere in one of the 'substrings', then that specific substring will also be split.
Or, simply iterate over the characters and maintain state about the quoted parts:
//: Playground - noun: a place where people can play
import UIKit
extension String {
func parse() -> [String] {
let delimiter = Character(" ")
let quote = Character("\"")
var tokens = [String]()
var pending = ""
var isQuoted = false
for character in self.characters {
if character == quote {
isQuoted = !isQuoted
}
else if character == delimiter && !isQuoted {
tokens.append(pending)
pending = ""
}
else {
pending.append(character)
}
}
// Add final token
if !pending.isEmpty {
tokens.append(pending)
}
return tokens
}
}
print ("substr1 substr2 \"substr 3\" substr4".parse()) // ["substr1", "substr2", "substr 3", "substr4"]
print ("\"substr 1\" substr2 \"substr 3\" substr4".parse()) // ["substr 1", "substr2", "substr 3", "substr4"]
print ("a b c d".parse()) // ["a", "b", "c", "d"]
Note: this code doesn't take into account that double quotes "" might be used to escape a single quote. But I don't know if that's a possibility in your case.
https://tburette.github.io/blog/2014/05/25/so-you-want-to-write-your-own-CSV-code/

Swift: Separate String into String Array and Append Separator

How would I split a string to include the separators?
Lets say I had a string such as...
let myString = "apple banana orange grapes"
If I used
let separatedString = myString.componentsSeparatedByString(" ")
my resulting array would be
["apple","banana","orange","grapes"]
How would I achieve a result of
["apple ","banana ","orange ","grapes"]
array.map lets you process the resulting array an add the space back in.
let separatedString = myString
.componentsSeparatedByString(" ")
.map { "\($0) " }
That last line iterates over all strings in the split up array and puts them in $0, and returns a new string with the space added back in which gets used as the replacement for the original string.
Alternative using regular expression:
let myString = "apple banana orange grapes"
let pattern = "\\w+\\s?"
let regex = try! NSRegularExpression(pattern: pattern, options: [])
let matches = regex.matchesInString(myString, options:[], range: NSMakeRange(0, myString.characters.count))
.map { (myString as NSString).substringWithRange($0.range)}
print(matches) // -> ["apple ", "banana ", "orange ", "grapes"]
Solution
Since you updated your question, it looks now you no longer want a new space on the last word.
So here's my updated code
let text = "apple banana orange grapes"
let chunks: [String] = text
.componentsSeparatedByString(" ")
.reverse()
.enumerate()
.map { $0.element + ( $0.index == 0 ? "" : " ") }
.reverse()
print(chunks) // ["apple ", "banana ", "orange ", "grapes"]
Multiple separators
Thank to #vadian for the suggestion
let text = "apple banana\norange grapes"
let chunks: [String] = text
.componentsSeparatedByCharactersInSet(.whitespaceAndNewlineCharacterSet())
.reverse()
.enumerate()
.map { $0.element + ( $0.index == 0 ? "" : " ") }
.reverse()
print(chunks) // ["apple ", "banana ", "orange ", "grapes"]