Случайная замена с использованием Swift

У меня возникла проблема, которую я не знаю, как решить, и надеюсь, что кто-то здесь может мне помочь. В настоящее время у меня есть строковая переменная, а позже я заменяю буквы в строке символами подчеркивания, как показано ниже:

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"

Есть ли у кого-нибудь идеи, как это сделать?

Вы можете использовать NSRegularExpression, получить все совпадения и выбрать в нем 3/4 из них и заменить их на "_".

Larme 15.01.2019 16:29

Должно ли быть ровно 25%?

JeremyP 15.01.2019 16:46
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
2
436
9
Перейти к ответу Данный вопрос помечен как решенный

Ответы 9

Ответ принят как подходящий

Так же, как в Заменить определенные символы в строке, вы можете сопоставить каждый символ и объединить результат в строку. Но теперь вам нужно отслеживать (оставшееся) количество непробельных символов и (оставшееся) количество символов, которые должны отображаться. Для каждого (непробельного) символа случайным образом решается, отображать (сохранять) его или заменять подчеркиванием.

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: "_") }

Martin R 15.01.2019 16:57

На самом деле, я был сосредоточен на объяснении идеи, лежащей в основе этого, которую я закодировал как таковую, а не как упрощенную.

Larme 15.01.2019 16:58

Действительно ли for in быстрее, чем .forEach?

LinusGeffarth 15.01.2019 17:20

Просто я делал first(where:) из matches на каждой итерации, но поскольку у нас уже есть нужный нам NSTextChekingResult, нам просто нужно пройтись по ним.

Larme 15.01.2019 17:26

Другой возможный подход - создать случайные индексы для данной строки и затем заменить символы в этих индексах:

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))

Обратите внимание, что ваш код может допускать замену запятой, пробела, а не только букв (небольшие отличия от требований автора).

Larme 15.01.2019 17:03

Этот метод создает массив 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)

Robert Dresler 15.01.2019 17:19

@RobertDresler Хороший аргумент в пользу n = Int.random(in: 1...maxValue). Не уверен, что вы имеете в виду, говоря «ваше решение не закрывает пробелы» ... Спасибо.

Ahmad F 15.01.2019 17:25

мои навыки английского ...: D Я имею в виду, ваше решение также заменяет пробелы на подчеркивания. И я думаю, OP этого не хочет.

Robert Dresler 15.01.2019 17:28

@RobertDresler Еще раз спасибо :) Отредактировано.

Ahmad F 15.01.2019 17:36

Идея та же, что и у описанных выше методов, только с немного меньшим количеством кода.

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 15.01.2019 21:55

@ E.Coms: конечно, без проблем

vikingosegundo 15.01.2019 22:16

Вы можете использовать трехэтапный алгоритм, который выполняет следующие действия:

  1. строит список всех непространственных индексов
  2. удаляет первые 25% случайных элементов из этого списка
  3. пройти через все символы и заменить все, чей индекс является частью списка из # 2, подчеркиванием

Код может выглядеть примерно так:

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"

Другие вопросы по теме