2
votes

Remplacement aléatoire à l'aide de Swift

Je rencontre un problème que je ne sais pas comment résoudre et j'espère que quelqu'un ici pourra m'aider. Actuellement, j'ai une variable de chaîne et plus tard, je remplace les lettres de la chaîne par des traits de soulignement comme suit:

str = "H__l_ ___yg_____"

str = "_____ play______"

str = "__ll_ ____g____d"

Sachez que je voudrais aléatoirement générer 25% des caractères dans str (Dans ce cas 16 * 0,25 = 4) donc il affiche plus tard quelque chose comme ces exemples:

var str = "Hello playground"

let replace = str.replacingOccurrences(of: "\\S", with: "_", options: .regularExpression)

print(str)

Quelqu'un at-il des idées sur la façon de faire cela?


2 commentaires

Vous pouvez utiliser un NSRegularExpression , obtenir toutes les correspondances, et à l'intérieur, en choisir 3/4 et les remplacer par "_".


Doit-il être exactement 25%?


9 Réponses :


3
votes

De la même façon que dans Remplacer des caractères spécifiques dans la chaîne , vous pouvez mappez chaque caractère et combinez le résultat en une chaîne. Mais maintenant, vous devez garder une trace des nombres (restants) de caractères non-espace et des nombres (restants) de caractères qui doivent être affichés. Pour chaque caractère (sans espace), il est décidé au hasard de l'afficher (de le conserver) ou de le remplacer par un trait de soulignement.

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_


0 commentaires

5
votes

Une solution possible:

$>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_

L'idée derrière cela: Utilisez le même modèle d'expression régulière que celui que vous avez utilisé.
Ramassez n éléments dedans (dans votre cas 1/4)
Remplacez tous les caractères qui ne figurent pas dans cette courte liste.

Maintenant que vous avez l'idée, il est encore plus rapide de remplacer la boucle for par

for aMatch in randomElementsToReplace {
    str.replaceSubrange(Range(aMatch.range, in: str)!, with: "_")
}

Merci au commentaire de @Martin R pour l'avoir signalé. p >

Résultat (fait 10 fois):

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

Vous verrez qu'il y a une petite différence par rapport à votre résultat attendu, c'est parce que correspond.count == 15, donc 1/4 d'entre eux devrait être quoi? A vous là de faire le bon calcul en fonction de vos besoins (arrondir ?, etc.) puisque vous ne l'avez pas précisé.

Notez que si vous ne voulez pas arrondir, vous pourrait aussi faire l'inverse, utiliser l'aléatoire pour celui à ne pas remplacer, et alors le tour pourrait jouer en votre faveur.


4 commentaires

Plus simple (?): for aMatch in randomElementsToReplace {str.replaceSubrange (Range (aMatch.range, in: str) !, with: "_")}


En effet, je me suis concentré sur l'explication de l'idée sous-jacente que je codais comme tel et non pas comme simplifié.


Est-ce que for in est vraiment plus rapide que .forEach ?


C'est juste que j'ai fait un d'abord (où :) des correspondances à chaque itération, mais comme nous avons le NSTextChekingResult que nous voulons déjà, nous besoin de les parcourir.



1
votes

Une autre approche possible consiste à générer des index aléatoires pour la chaîne donnée, puis à remplacer les caractères de ces index:

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

Si vous mettez ceci dans une extension sur String, cela ressemblerait à:

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)


1 commentaires

Notez que votre code peut permettre le remplacement de la virgule, de l'espace, pas seulement des lettres (légères différences par rapport aux besoins de l'auteur).



3
votes

Cette méthode crée un tableau de booléens qui détermine quels caractères seront conservés et lesquels seront remplacés en utilisant la fonction intégrée shuffled .

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_

Modifier Ce qui précède ne traite pas correctement les espaces, mais je vais quand même le laisser ici à titre d'exemple général.

Voici une version qui traite les espaces.

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


0 commentaires

1
votes

Je viens de proposer la solution suivante:

let str = "Hello playground"
print(generateMyString(string: str)) // ___lo _l_______d

Sortie:

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
}


4 commentaires

Je pensais à la même solution, mais votre solution ne couvre pas les espaces. Et comme le nombre aléatoire de Swift 4.2 peut être simplement n = Int.random (in: 1 ... maxValue)


@RobertDresler Bon point pour le n = Int.random (in: 1 ... maxValue) . Je ne sais pas ce que vous entendez par "votre solution ne couvre pas les espaces" ... Merci.


mes compétences en anglais ...: D Je veux dire, votre solution remplace également les espaces par des traits de soulignement. Et je pense que OP ne veut pas de ça.


@RobertDresler Merci encore :) Edité.



0
votes

L'idée est la même que celle des méthodes ci-dessus, avec juste un peu moins de code.

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


0 commentaires

1
votes

Une solution qui garde les espaces et la ponctuation intacts.
Nous les trouverons avec une méthode d'extension indiciesOfPuntationBlanks () -> [Int] . le remplacement des caractères choisis au hasard se fera par blankOut (pourcentage: Double) -> String

_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***

Utilisation:

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: []))
}


2 commentaires

désolé, je ne l'ai pas décliné. vous pouvez le modifier un peu. afin que je puisse le voter.


@ E.Coms: bien sûr, pas de problème



0
votes

Vous pouvez utiliser un algorithme en 3 étapes qui effectue les opérations suivantes:

  1. construit la liste de tous les indices non spatiaux
  2. supprime les 25% premiers éléments aléatoires de cette liste
  3. parcourir tous les caractères et remplacer tous ceux dont l'index fait partie de la liste du n ° 2, par un trait de soulignement

Le code pourrait ressembler à ceci:

____o p_ay______
____o p__y____n_
_el_o p_________

Exemples de résultats:

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


0 commentaires

0
votes

Vous devez d'abord obtenir les indices de votre chaîne et filtrer ceux qui sont des lettres. Ensuite, vous pouvez mélanger le résultat et choisir le nombre d'éléments (%) moins le nombre d'espaces dans la chaîne d'origine, parcourir le résultat en remplaçant les plages résultantes par le trait de soulignement. Vous pouvez étendre le protocole RangeReplaceable pour pouvoir également l'utiliser avec des sous-chaînes:


// 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"

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


0 commentaires