У меня возникла проблема, которую я не знаю, как решить, и надеюсь, что кто-то здесь может мне помочь. В настоящее время у меня есть строковая переменная, а позже я заменяю буквы в строке символами подчеркивания, как показано ниже:
var str = "Hello playground"
let replace = str.replacingOccurrences(of: "\\S", with: "_", options: .regularExpression)
print(str)
Знайте, я хотел бы, чтобы случайно сгенерировал 25% символов в str (в данном случае 16 * 0,25 = 4), чтобы позже он напечатал что-то вроде этих примеров:
str = "H__l_ ___yg_____"
str = "_____ play______"
str = "__ll_ ____g____d"
Есть ли у кого-нибудь идеи, как это сделать?
Должно ли быть ровно 25%?





Так же, как в Заменить определенные символы в строке, вы можете сопоставить каждый символ и объединить результат в строку. Но теперь вам нужно отслеживать (оставшееся) количество непробельных символов и (оставшееся) количество символов, которые должны отображаться. Для каждого (непробельного) символа случайным образом решается, отображать (сохранять) его или заменять подчеркиванием.
let s = "Hello playground"
let factor = 0.25
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 {
// Keep
m -= 1
n -= 1
return c
} else {
// Replace
n -= 1
return "_"
}
})
print(t) // _e_l_ ______o_n_
Возможное решение:
var str = "Hello playground"
print("Before: \(str)")
do {
let regex = try NSRegularExpression(pattern: "\\S", options: [])
let matches = regex.matches(in: str, options: [], range: NSRange(location: 0, length: str.utf16.count))
//Retrieve 1/4 elements of the string
let randomElementsToReplace = matches.shuffled().dropLast(matches.count * 1/4)
matches.forEach({ (aMatch) in
if randomElementsToReplace.first(where: { $0.range == aMatch.range } ) != nil {
str.replaceSubrange(Range(aMatch.range, in: str)!, with: "_")
} else {
//Do nothing because that's the one we are keeping as such
}
})
print("After: \(str)")
} catch {
print("Error while creating regex: \(error)")
}
Идея, лежащая в основе этого:
Используйте тот же шаблон регулярного выражения, который вы использовали.
Подберите в нем n элементов (в вашем случае 1/4)
Замените всех персонажей, которых нет в этом коротком списке.
Теперь, когда вы поняли идею, можно еще быстрее заменить цикл for на
for aMatch in randomElementsToReplace {
str.replaceSubrange(Range(aMatch.range, in: str)!, with: "_")
}
Спасибо комментарию @Martin R за указание на это.
Вывод (сделано 10 раз):
$>Before: Hello playground
$>After: ____o ___y____n_
$>Before: Hello playground
$>After: _el__ _______u__
$>Before: Hello playground
$>After: _e___ ____g___n_
$>Before: Hello playground
$>After: H___o __a_______
$>Before: Hello playground
$>After: H___o _______u__
$>Before: Hello playground
$>After: __l__ _____ro___
$>Before: Hello playground
$>After: H____ p________d
$>Before: Hello playground
$>After: H_l__ _l________
$>Before: Hello playground
$>After: _____ p____r__n_
$>Before: Hello playground
$>After: H___o _____r____
$>Before: Hello playground
$>After: __l__ ___y____n_
Вы увидите, что есть небольшое отличие от ожидаемого результата, потому что matches.count == 15, так что 1/4 из них должна быть какой? Это зависит от вас, чтобы сделать правильный расчет в соответствии с вашими потребностями (округлить? И т. Д.), Поскольку вы не указали его.
Обратите внимание, что если вы не хотите округлять вверх, вы также можете сделать обратное, использовать случайное значение для того, которое не заменять, и тогда раунд может сыграть в вашу пользу.
Попроще (?): for aMatch in randomElementsToReplace { str.replaceSubrange(Range(aMatch.range, in: str)!, with: "_") }
На самом деле, я был сосредоточен на объяснении идеи, лежащей в основе этого, которую я закодировал как таковую, а не как упрощенную.
Действительно ли for in быстрее, чем .forEach?
Просто я делал first(where:) из matches на каждой итерации, но поскольку у нас уже есть нужный нам NSTextChekingResult, нам просто нужно пройтись по ним.
Другой возможный подход - создать случайные индексы для данной строки и затем заменить символы в этих индексах:
var str = "Hello, playground"
let indexes: [Int] = Array(0..<str.count)
let randomIndexes = Array(indexes.shuffled()[0..<(str.count / 4)])
for index in randomIndexes {
let start = str.index(str.startIndex, offsetBy: index)
let end = str.index(str.startIndex, offsetBy: index+1)
str.replaceSubrange(start..<end, with: "_")
}
print(str)
Если вы поместите это в расширение в String, это будет выглядеть так:
extension String {
func randomUnderscores(factor: Double) -> String {
let indexes: [Int] = Array(0..<count)
let endIndexes = Int(Double(count) * factor)
let randomIndexes = Array(indexes.shuffled()[0..<endIndexes])
var randomized = self
for index in randomIndexes {
let start = randomized.index(startIndex, offsetBy: index)
let end = randomized.index(startIndex, offsetBy: index+1)
randomized.replaceSubrange(start..<end, with: "_")
}
return randomized
}
}
print(str.randomUnderscores(factor: 0.25))
Обратите внимание, что ваш код может допускать замену запятой, пробела, а не только букв (небольшие отличия от требований автора).
Этот метод создает массив bools, который определяет, какие символы будут сохранены, а какие будут заменены с помощью встроенной функции shuffled.
let string = "Hello playground"
let charsToKeep = string.count / 4
let bools = (Array<Bool>(repeating: true, count: charsToKeep)
+ Array<Bool>(repeating: false, count: string.count - charsToKeep)).shuffled()
let output = zip(string, bools).map
{
char, bool in
return bool ? char : "_"
}
print(String(output))
Редактировать Вышеупомянутое неправильно относится к пробелам, но я все равно оставлю его здесь в качестве общего примера.
Вот версия, которая имеет дело с пробелами.
let string = "Hello playground and stackoverflow"
let nonSpaces = string.filter{ $0 != " " }.count
let bools = (Array<Bool>(repeating: true, count: nonSpaces / 4) + Array<Bool>(repeating: false, count: nonSpaces - nonSpaces / 4)).shuffled()
var nextBool = bools.makeIterator()
let output = string.map
{
char in
return char == " " ? " " : (nextBool.next()! ? char : "_")
}
print(String(output))
// Hel__ __________ a__ __a____e____w
// ___l_ _l__g_____ _n_ __a_____r__o_
Я только что придумал следующее решение:
func generateMyString(string: String) -> String {
let percentage = 0.25
let numberOfCharsToReplace = Int(floor(Double(string.count) * percentage))
let generatedString = stride(from: 0, to: string.count, by: 1).map { index -> String in
return string[string.index(string.startIndex, offsetBy: index)] == " " ? " " : "_"
}.joined()
var newString = generatedString
for i in generateNumbers(repetitions: numberOfCharsToReplace, maxValue: string.count - 1) {
var newStringArray = Array(newString)
newStringArray[i] = Array(string)[i]
newString = String(newStringArray)
}
return newString
}
func generateNumbers(repetitions: Int, maxValue: Int) -> [Int] {
guard maxValue >= repetitions else {
fatalError("maxValue must be >= repetitions for the numbers to be unique")
}
var numbers = [Int]()
for _ in 0..<repetitions {
var n: Int
repeat {
n = Int.random(in: 1...maxValue)
} while numbers.contains(n)
numbers.append(n)
}
return numbers
}
Вывод:
let str = "Hello playground"
print(generateMyString(string: str)) // ___lo _l_______d
Я думал о том же решении, но ваше решение не закрывает пробелы. А так как Swift 4.2 случайное число может быть просто n = Int.random(in: 1...maxValue)
@RobertDresler Хороший аргумент в пользу n = Int.random(in: 1...maxValue). Не уверен, что вы имеете в виду, говоря «ваше решение не закрывает пробелы» ... Спасибо.
мои навыки английского ...: D Я имею в виду, ваше решение также заменяет пробелы на подчеркивания. И я думаю, OP этого не хочет.
@RobertDresler Еще раз спасибо :) Отредактировано.
Идея та же, что и у описанных выше методов, только с немного меньшим количеством кода.
var str = "Hello playground"
print(randomString(str))
print(randomString(str))
// counting whitespace as a random factor
func randomString(_ str: String) -> String{
let strlen = str.count
let effectiveCount = Int(Double(strlen) * 0.25)
let shuffled = (0..<strlen).shuffled()
return String(str.enumerated().map{
shuffled[$0.0] < effectiveCount || ($0.1) == " " ? ($0.1) : "_"
})}
//___l_ _l__gr____
//H____ p___g____d
func underscorize(_ str: String) -> String{
let effectiveStrlen = str.filter{$0 != " "}.count
let effectiveCount = Int(floor(Double(effectiveStrlen) * 0.25))
let shuffled = (0..<effectiveStrlen).shuffled()
return String((str.reduce(into: ([],0)) {
$0.0.append(shuffled[$0.1] <= effectiveCount || $1 == " " ? $1 : "_" )
$0.1 += ($1 == " ") ? 0 : 1}).0)
}
print(underscorize(str))
print(underscorize(str))
//__l__ pl__g_____
//___lo _l_______d
Решение, сохраняющее пробелы и пунктуацию без изменений.
Мы найдем их с помощью метода расширения indiciesOfPuntationBlanks() -> [Int]. замена случайно выбранных символов будет выполняться blankOut(percentage: Double) -> String
extension String {
func indiciesOfPuntationBlanks() -> [Int] {
let charSet = CharacterSet.punctuationCharacters.union(.whitespaces)
var indices = [Int]()
var searchStartIndex = self.startIndex
while searchStartIndex < self.endIndex,
let range = self.rangeOfCharacter(from: charSet, options: [], range: searchStartIndex ..< self.endIndex),
!range.isEmpty
{
let index = distance(from: self.startIndex, to: range.lowerBound)
indices.append(index)
searchStartIndex = range.upperBound
}
return indices
}
func blankOut(percentage: Double) -> String {
var result = self
let blankIndicies = result.indiciesOfPuntationBlanks()
let allNonBlankIndicies = Set(0 ..< result.count).subtracting(blankIndicies).shuffled()
let picked = allNonBlankIndicies.prefix(Int(Double(allNonBlankIndicies.count) * percentage))
picked.forEach { (idx) in
let start = result.index(result.startIndex, offsetBy: idx);
let end = result.index(result.startIndex, offsetBy: idx + 1);
result.replaceSubrange(start ..< end, with: "_")
}
return result
}
}
Использование:
let str = "Hello, World!"
for _ in 0 ..< 10 {
print(str.blankOut(percentage: 0.75))
}
Вывод:
____o, _or__!
_e___, __rl_!
_e__o, __r__!
H____, W_r__!
H_l__, W____!
_____, _or_d!
_e_lo, _____!
_____, _orl_!
_____, _or_d!
___l_, W___d!
То же решение, но можно настроить строку для гашения и игнорируемые наборы символов.
extension String {
func indicies(with charSets:[CharacterSet]) -> [Int] {
var indices = [Int]()
let combinedCahrSet: CharacterSet = charSets.reduce(.init()) { $0.union($1) }
var searchStartIndex = self.startIndex
while searchStartIndex < self.endIndex,
let range = self.rangeOfCharacter(from: combinedCahrSet, options: [], range: searchStartIndex ..< self.endIndex),
!range.isEmpty
{
let index = distance(from: self.startIndex, to: range.lowerBound)
indices.append(index)
searchStartIndex = range.upperBound
}
return indices
}
func blankOut(percentage: Double, with blankOutString: String = "_", ignore charSets: [CharacterSet] = [.punctuationCharacters, .whitespaces]) -> String {
var result = self
let blankIndicies = result.indicies(with: charSets)
let allNonBlankIndicies = Set(0 ..< result.count).subtracting(blankIndicies).shuffled()
let picked = allNonBlankIndicies.prefix(Int(Double(allNonBlankIndicies.count) * percentage))
picked.forEach { (idx) in
let start = result.index(result.startIndex, offsetBy: idx);
let end = result.index(result.startIndex, offsetBy: idx + 1);
result.replaceSubrange(start ..< end, with: blankOutString)
}
return result
}
}
Использование:
let str = "Hello, World!"
for _ in 0 ..< 10 {
print(str.blankOut(percentage: 0.75))
}
print("--------------------")
for _ in 0 ..< 10 {
print(str.blankOut(percentage: 0.75, with:"x", ignore: [.punctuationCharacters]))
}
print("--------------------")
for _ in 0 ..< 10 {
print(str.blankOut(percentage: 0.75, with:"*", ignore: []))
}
Вывод:
_el_o, _____!
__llo, _____!
He__o, _____!
_e___, W_r__!
_el_o, _____!
_el__, ___l_!
_e___, __rl_!
_e__o, _o___!
H____, Wo___!
H____, __rl_!
--------------------
xxxlx,xWxrxx!
xxxxx,xxorxd!
Hxxxx,xWxrxx!
xxxxx, xoxlx!
Hxllx,xxxxxx!
xelxx,xxoxxx!
Hxxxx,xWxxxd!
Hxxxo,xxxxxd!
Hxxxx,xxorxx!
Hxxxx, Wxxxx!
--------------------
***l***Wo**d*
*e**o**W**l**
***lo**Wo****
*el*****or***
H****,****ld*
***l*, **r***
*el*o* ******
*e*lo*******!
H*l****W***d*
H****, **r***
извините, я не проголосовал против. можно немного отредактировать. так что я могу проголосовать за это.
@ E.Coms: конечно, без проблем
Вы можете использовать трехэтапный алгоритм, который выполняет следующие действия:
Код может выглядеть примерно так:
func underscorize(_ str: String, factor: Double) -> String {
// making sure we have a factor between 0 and 1
let factor = max(0, min(1, factor))
let nonSpaceIndices = str.enumerated().compactMap { $0.1 == " " ? nil : $0.0 }
let replaceIndices = nonSpaceIndices.shuffled().dropFirst(Int(Double(str.count) * factor))
return String(str.enumerated().map { replaceIndices.contains($0.0) ? "_" : $0.1 })
}
let str = "Hello playground"
print(underscorize(str, factor: 0.25))
Примеры результатов:
____o p_ay______
____o p__y____n_
_el_o p_________
Сначала вам нужно получить индексы вашей строки и отфильтровать те, которые являются буквами. Затем вы можете перетасовать результат и выбрать количество элементов (%) за вычетом количества пробелов в исходной строке, выполнить итерацию по результату, заменив полученные диапазоны знаком подчеркивания. Вы можете расширить протокол RangeReplaceable, чтобы использовать его и с подстроками:
extension StringProtocol where Self: RangeReplaceableCollection{
mutating func randomReplace(characterSet: CharacterSet = .letters, percentage: Double, with element: Element = "_") {
precondition(0...1 ~= percentage)
let indices = self.indices.filter {
characterSet.contains(self[$0].unicodeScalars.first!)
}
let lettersCount = indices.count
let nonLettersCount = count - lettersCount
let n = lettersCount - nonLettersCount - Int(Double(lettersCount) * Double(1-percentage))
indices
.shuffled()
.prefix(n)
.forEach {
replaceSubrange($0...$0, with: Self([element]))
}
}
func randomReplacing(characterSet: CharacterSet = .letters, percentage: Double, with element: Element = "_") -> Self {
precondition(0...1 ~= percentage)
var result = self
result.randomReplace(characterSet: characterSet, percentage: percentage, with: element)
return result
}
}
// mutating test
var str = "Hello playground"
str.randomReplace(percentage: 0.75) // "___lo _l___r____\n"
print(str) // "___lo _l___r____\n"
// non mutating with another character
let str2 = "Hello playground"
str2.randomReplacing(percentage: 0.75, with: "•") // "••••o p••y•••u••"
print(str2) // "Hello playground\n"
Вы можете использовать
NSRegularExpression, получить все совпадения и выбрать в нем 3/4 из них и заменить их на "_".