0

I want to simplify replacing specific characters of a string in-situ - with a list comprehension. Attempts so far simply return a list of strings - each list item with each character replaced from the check string.

Advice / solutions?

Inputs:

reveal = "password"
ltrTried = "sr"

Required Output:

return = "**ss**r*"

Getting:

('**ss****', '******r*')
  • 1
    can you add some input and expected output? – Padraic Cunningham Sep 09 '14 at 23:05
  • 2
    List comprehensions aren't meant for situations with side effects, but what you're looking for here is entirely a side effect (since you aren't generating a list you want to keep). What is wrong with your for loop, and why do you want to replace it with a comprehension? – David Robinson Sep 09 '14 at 23:11
  • See [here](http://stackoverflow.com/questions/5753597/is-it-pythonic-to-use-list-comprehensions-for-just-side-effects), [here](http://stackoverflow.com/questions/8068251/why-is-python-list-comprehension-sometimes-frowned-upon) and [here](http://stackoverflow.com/questions/8695488/proper-use-of-list-comprehensions-python) for more on list comprehension side effects. – David Robinson Sep 09 '14 at 23:11
  • As a side note, they're "list comprehensions", not "comprehension lists". It's not a special kind of list, it's a perfectly ordinary one, just built in a special way. – abarnert Sep 10 '14 at 00:04

3 Answers3

2

If you want to do this using a list comprehension, you'd want to replace it letter by letter like this:

reveal = "".join((letter if letter in ltrFound else "*") for letter in reveal)

Notice that

  • We're iterating over your reveal string, not your ltrFound list (or string).
  • Each item is replaced using the ternary operator letter if letter in ltrFound else "*". This ensures that if the letter in reveal is not in ltrFound, it will get replaced with a *.
  • We end by joining together all the letters.
David Robinson
  • 77,383
  • 16
  • 167
  • 187
  • That's what I was missing.... ELSE. Thanks for putting me out of my misery. Probably obvious I've only been using Python for a few weeks. Wanted to explore comprehension lists - but couldn't see how to get it to work this way around. Replacing the letters I had was easy - replacing the ones I didn't was driving me nuts! – alcyone_pleiades Sep 09 '14 at 23:31
  • this is a generator not a list comprehension – Padraic Cunningham Sep 09 '14 at 23:37
  • @alcyone_pleiades: The `if` here is different from the `if` in your original code. You were using an `if` clause in a comprehension, which filters the values, only iterating the ones for which the `if` is true. This answer is using an `if-else` expression, which always evaluates to a value, but one of two different values depending on whether the test is true. They may look similar, but don't mix them up. – abarnert Sep 10 '14 at 00:01
  • @PadraicCunningham: Good point. A generator expression is a kind of comprehension, although a different kind than a list comprehension of course. In this case, it's clearer to use a generator expression because we don't actually need the list for anything, but it's probably worth explaining that in the answer rather than just changing it without comment. – abarnert Sep 10 '14 at 00:02
  • @abarnet - no confusion here - I know they're different. Just wasn't aware you could place ELSE here. – alcyone_pleiades Sep 10 '14 at 00:45
  • @alcyone_pleiades: If that's true, I'm pretty sure you _are_ confused. You _have_ to have an `else` in a conditional expression. So whatever you were thinking of that you didn't think could have an `else`, that's not it. – abarnert Sep 10 '14 at 03:54
2

Just for fun, here's a different way to do this immutably, by using a translation map.

If you wanted to replace everything that was in ltrFound, that would be easy:

tr = str.maketrans(ltrFound, '*' * len(ltrFound))
print(reveal.translate(tr))

But you want to do the opposite, replace everything that's not in ltrFound. And you don't want to build a translation table of all of the 100K+ characters that aren't s. So, what can you do?

You can build a table of the 6 characters that aren't in s but are in reveal:

notFound = ''.join(set(reveal) - set(ltrFound)) # 'adoprw'
tr = str.maketrans(notFound, '*' * len(notFound))
print(reveal.translate(tr))

The above is using Python 3.x; for 2.x, maketrans is a function in the string module rather than a classmethod of the str class (and there are a few other differences, but they don't matter here). So:

import string
notFound = ''.join(set(reveal) - set(ltrFound)) # 'adoprw'
tr = string.maketrans(notFound, '*' * len(notFound))
print(reveal.translate(tr))
abarnert
  • 354,177
  • 51
  • 601
  • 671
  • 1
    hah I typed up most of this same answer and then put the re solution instead ! +1 – Joran Beasley Sep 10 '14 at 02:12
  • @JoranBeasley: I wasn't going to write this until I realized that it's the obvious way to optimize his `replace`-based solution by doing all the `replace`s at once, and not searching for the distinct characters by iterating the string… and then, once I finished, I realized I had no idea how to explain that, and almost deleted it, but people had already upvoted it by then, so I figured I might as well let it stay. – abarnert Sep 10 '14 at 03:53
1

try this

re.sub("[^%s]"%guesses,"*",solution_string)

assuming guesses is a string

Joran Beasley
  • 110,522
  • 12
  • 160
  • 179