What wrong with this code: first appearance of a word - python

I wrote a program (appear) that keeps generating random letters, from a to z, till a given word appears:
import random
def appear(word):
word = list(word)
w = word
l = list('abcdefghijklmnopqrstuvwxyz')
i = 0
while len(w) > 0:
r = int(random.random() * 26)
print(l[r], end='')
if (w[0] == l[r] and i == 1) or (w[0] == l[r] and len(w) == len(word)):
i = 1
del w[0]
else:
i = 0
w = word
For example, appear('car') should produce: ajzkcar.
I tried printing in each loop the value of w, and the problem seems to be that the program fails to reset w to the original word if it doesn't find two consecutive letters, even though I clearly say that it should in the last "else"

I suggest to not delete anything but keep track of the string you have output so far, trim it to the length of word and then check if it actually is word:
import random
def appear(word):
l = list('abcdefghijklmnopqrstuvwxyz')
s = ""
while True:
r = int(random.random() * 26)
print(l[r], end='')
s += l[r] # keep track
s = s[-len(word):] # truncate
if s == word: # compare
return
appear("car")

You need to keep the letters that you have generated in memory, and compare them with the letters of the target word.
The word you're looking for has a fixed length. When you generate a new letter, you need to add that new letter to your memory at the right, and discard the oldest letter from your memory, at the left.
What data structure to use for this? Adding letters to the right and removing letters from the left, so that the total length remains fixed? I immediately think about a fixed-length queue.
The simplest way to get a fixed-length queue in python is to use collections.deque with a maxlen argument.
Also, to choose a letter, I have a preference for random.choice(letters) over letters[int(random.random() * 26)]
from collections import deque
from random import choice, choices
from string import ascii_lowercase as alphabet
def appear(word):
queue = deque(choices(alphabet, k=len(word)), maxlen=len(word))
target = deque(word)
print(''.join(queue), end='')
while queue != target:
new_letter = choice(alphabet)
queue.append(new_letter)
print(new_letter, end='')
appear('a')
# jca
appear('ab')
# zdoxkcnswafzsclmeduwhyhpdfwljujduwvbsxayihtfmlqrjxamlqnestzsncjjzbyfuzaczmuaiddfehckkrcnzfwwgnxfxcaifasaybokkxrqievmwqhisnaqhezcxwxfrstvuvwoedstpsrxkmxbubab

Related

I want to duplicate a random letter of a string 1 time. How can I do?

This is my string:
keyword = "qatarworldcup"
I mean my string should be qqatarworldcup, qatarworldcupp or qatarrworlddcup
This should be pretty easy to do if you break it up into parts.
Select a random letter from the word.
import random
letter_index = random.randint(0, len(keyword)-1)
Split the word into two parts at the letter you picked.
before, after = keyword[:letter_index], keyword[letter_index:]
Join the two parts, adding an extra instance of the selected letter
result = before + keyword[letter_index] + after
If your strings are big enough, or you're doing this multiple times, you could see a speedup from reducing the number of string concatenations, because that's an O(N) operation on account of the immutability of strings. Since the selected letter already exists in the word, you can split it such that the selected letter is the last character of before and the first character of after. Then, you only need a single concatenationThanks to #Mechanic Pig for your comment:
before, after = keyword[:letter_index+1], keyword[letter_index:]
result = before + after
from random import randint
keyword = "qatarwordcup"
idx = randint(0, len(keyword) - 1)
keyword = keyword[:idx] + keyword[idx] + keyword[idx:]
I'd do it like this
import random
#Find random position in string
r = random.randint(0, len(keyword) - 1)
#Create new string with added character at random position
newStr = keyword[:r] + keyword[r] + keyword[r:]
Iteration through index-character pairs, apply the condition on each "term" with the ternary operator and join everything together.
import random
# fix random number generator (for testing only!)
random.seed(190)
keyword = "qatarworldcup"
# random index
r = random.randint(0, len(keyword)-1)
out = ''.join(char * 2 if i == r else char for i, char in enumerate(keyword))
print(out)
#qaatarworldcup

Efficient way to choose random element from list based on length

Apologies if this is the wrong forum - it's my first question. I'm learning python and writing a password generator as an exercise from www.practicepython.org
I've written the following but it can be really slow so I guess i"m doing it inefficiently. I want to select a random word from the dictionary and then add ascii characters to it. I want at least 2 ascii characters in the password so I use a while loop to ensure that the word element contains (length - 2).
This works fine if you say that you want the password to be 10 characters long, but if you constrict to something like 5 I think the while loop has to go through so many iterations it can take up to 30 seconds.
I can't find the answer via searching - guidance appreciated!
import string
import random
import nltk
from nltk.corpus import words
word = words.words()[random.randint(1, len(words.words()))]
ascii_str = (string.ascii_letters + string.digits + string.punctuation)
length = int(input("How long do you want the password to be? "))
while len(word) >= (length - 2):
word = words.words()[random.randint(1, len(words.words()))]
print("The password is: " + word, end="")
for i in range(0, (length - len(word))):
print(ascii_str[random.randint(1, len(ascii_str) - 1)], end="")
Start by calling words.words() just once and store that in a variable:
allwords = words.words()
That saves a lot of work, because now the nltk.corpus library won't try to load the whole list each time you try to get the length of the list or try to select a random word with the index you generated.
Next, use random.choice() to pick a random element from that list. That eliminates the need to keep passing in a list length:
word = random.choice(allwords)
# ...
while len(word) >= (length - 2):
word = random.choice(allwords)
Next, you could group the words by length first:
allwords = words.words()
by_length = {}
for word in allwords:
by_length.setdefault(len(word), []).append(word)
This gives you a dictionary with keys representing the length of the words; the nltk corpus has words between 1 and 24 letters long. Each value in the dictionary is a list of words of the same length, so by_length[12] would give you a list of words that are all exactly 12 characters long.
This allows you to pick words of a specific length:
# start with the desired length, and see if there are words this long in the
# dictionary, but don’t presume that all possible lengths exist:
wordlength = length - 2
while wordlength > 0 and wordlength not in by_length:
wordlength -= 1
# we picked a length, but it could be 0, -1 or -2, so start with an empty word
# and then pick a random word from the list with words of the right length.
word = ''
if wordlength > 0:
word = random.choice(by_length[wordlength])
Now word is the longest random word that'll fit your criteria: at least 2 characters shorter than the required length, and taken at random from the word list.
More importantly: we only picked a random word once. Provided you keep the by_length dictionary around for longer and re-use it in a password-generating function, that's a big win.
Picking the nearest available length from by_length can be done without stepping through every possible length one step at a time if you use bisection, but I’ll leave adding that as an exercise for the reader.
You are looking at random.choice
From the docs:
random.choice(seq)
Return a random element from the non-empty sequence seq.
In [22]: import random
In [23]: random.choice([1,2,3,4,5])
Out[23]: 3
In [24]: random.choice([1,2,3,4,5])
Out[24]: 5
In [25]: random.choice([1,2,3,4,5])
Out[25]: 1
The code can then be simplified to
import string
import random
import nltk
from nltk.corpus import words
#All words assigned to a list first
words = words.words()
#Get a random word
word = random.choice(words)
ascii_str = string.ascii_letters + string.digits + string.punctuation
length = int(input("How long do you want the password to be? "))
while len(word) >= (length - 2):
word = random.choice(words)
#Use random.sample to choose n random samples, and join them all to make a string
password = word + ''.join(random.sample(ascii_str, length))
print("The password is: " + password, end="")
Possible outputs are
How long do you want the password to be? 10
The password is: heyT{7<XEVc!l
How long do you want the password to be? 8
The password is: hiBk-^8t7]
But ofcourse, this is not an optimized solution as noted by #MartjinPieters in the comment, but I will try to provide something along the lines as he pointed in his answer, in a different way as follows
I will use itertools.groupby to create the by_length dictionary, a dictionary with key as word length and values as list of words of that length using itertools.groupby
I will ensure a minimum length restriction for length of password
Use random.sample to choose pass_len random samples, and join them all to make a string, and append the word in front!
import string
import random
from itertools import groupby
#All words assigned to a list first
words = ['a', 'c', 'e', 'bc', 'def', 'ghij' , 'jklmn']
#Get a random word
word = random.choice(words)
ascii_str = string.ascii_letters + string.digits + string.punctuation
#Check for minimum length, and exit the code if it is not
min_length = 8
pass_len = int(input("How long do you want the password to be? Minimum length is {}".format(min_length)))
if pass_len <= min_length:
print('Password is not long enough')
exit()
#Create the by_length dictionary, a dictionary with key as word length and values as list of words of that length using itertools.groupby
by_length = {}
for model, group in groupby(words, key=len):
by_length[model] = list(group)
chosen_word = ''
req_len = pass_length - 2
#Iterate till you find the word of required length of pass_len - 2, else reduce the required length by 1
while req_len > 0:
if req_len in words:
chosen_word = by_length[req_len]
else:
req_len -= 1
#Use random.sample to choose n random samples, and join them all to make a string
password = word + ''.join(random.sample(ascii_str, length))
print("The password is: " + password, end="")

Scrabble cheater: scoring wildcard characters to zero in Python

I'm new to python world, and I made a code of scrabble finder with two wildcards (* and ?) in it. When scoring the word, I would like to score wildcard letters to zero, but it looks like it doesn't work. I'm wondering what is missing here.
When you look into the line after "# Add score and valid word to the empty list", I tried to code if a letter in the word is not in the rack, I removed the letter so that I can only score other characters that are not coming from wildcards and matches with the letter in the rack. For example, if I have B* in my rack and the word is BO, I would like to remove O and only score B so that I can score wildcard to zero.
But the result is not what I expected.
import sys
if len(sys.argv) < 2:
print("no rack error.")
exit(1)
rack = sys.argv[1]
rack_low = rack.lower()
# Turn the words in the sowpods.txt file into a Python list.
with open("sowpods.txt","r") as infile:
raw_input = infile.readlines()
data = [datum.strip('\n') for datum in raw_input]
# Find all of the valid sowpods words that can be made
# up of the letters in the rack.
valid_words = []
# Call each word in the sowpods.txt
for word in data:
# Change word to lowercase not to fail due to case.
word_low = word.lower()
candidate = True
rack_letters = list(rack_low)
# Iterate each letter in the word and check if the letter is in the
# Scrabble rack. If used once in the rack, remove the letter from the rack.
# If there's no letter in the rack, skip the letter.
for letter in word_low:
if letter in rack_letters:
rack_letters.remove(letter)
elif '*' in rack_letters:
rack_letters.remove('*')
elif '?' in rack_letters:
rack_letters.remove('?')
else:
candidate = False
if candidate == True:
# Add score and valid word to the empty list
total = 0
for letter in word_low:
if letter not in rack_letters:
word_strip = word_low.strip(letter)
for letter in word_strip:
total += scores[letter]
valid_words.append([total, word_low])
I'm going to go a slightly different route with my answer and hopefully speed the overall process up. We're going to import another function from the standard library -- permutations -- and then find possible results by trimming the total possible word list by the length of the rack (or, whatever argument is passed).
I've commented accordingly.
import sys
from itertools import permutations # So we can get our permutations from all the letters.
if len(sys.argv) < 2:
print("no rack error.")
exit(1)
rack = sys.argv[1]
rack_low = rack.lower()
# Turn the words in the sowpods.txt file into a Python list.
txt_path = r'C:\\\\\sowpods.txt'
with open(txt_path,'r') as infile:
raw_input = infile.readlines()
# Added .lower() here.
data = [i.strip('\n').lower() for i in raw_input]
## Sample rack of 7 letters with wildcard character.
sample_rack = 'jrnyoj?'
# Remove any non-alphabetic characters (i.e. - wildcards)
# We're using the isalpha() method.
clean_rack = ''.join([i for i in sample_rack if i.isalpha()])
# Trim word list to the letter count in the rack.
# (You can skip this part, but it might make producing results a little quicker.)
trimmed_data = [i for i in data if len(i) <= len(clean_rack)]
# Create all permutations from the letters in the rack
# We'll iterate over a count from 2 to the length of the rack
# so that we get all relevant permutations.
all_permutations = list()
for i in range(2, len(clean_rack) + 1):
all_permutations.extend(list(map(''.join, permutations(clean_rack, i))))
# We'll use set().intersection() to help speed the discovery process.
valid_words = list(set(all_permutations).intersection(set(trimmed_data)))
# Print sorted list of results to check.
print(f'Valid words for a rack containing letters \'{sample_rack}\' are:\n\t* ' + '\n\t* '.join(sorted(valid_words)))
Our output would be the following:
Valid words for a rack containing letters 'jrnyoj?' are:
* jo
* jor
* joy
* no
* nor
* noy
* ny
* on
* ony
* or
* oy
* yo
* yon
If you want to verify that the results are actually in the sowpods.txt file, you can just index the sowpods.txt list by where the word you want to look up is indexed:
trimmed_data[trimmed_data.index('jor')]
When you are totalling the scores you are using the words from the wordlist and not the inputted words:
total=0
for letter in word_low:
...
Rather, this should be:
total=0
for letter in rack_low:
...
Also, You do not need to loop and remove the letters with strip at the end.
you can just have:
total = 0
for letter in rack_low:
if letter not in rack_letters:
try:
total += scores[letter]
except KeyError: # If letter is * or ? then a KeyError occurs
pass
valid_words.append([total, word_low])

Creating a word scrambler but it won't work, need help as a beginner

Beginner python coder here, keep things simple, please.
So, I need this code below to scramble two letters without scrambling the first or last letters. Everything seems to work right up until the scrambler() function.
from random import randint
def wordScramble(string):
stringArray = string.split()
for word in stringArray:
if len(word) >= 4:
letter = randint(1,len(word)-2)
point = letter
while point == letter:
point = randint(1, len(word)-2)
word = switcher(word,letter,point)
' '.join(stringArray)
return stringArray
def switcher(word,letter,point):
word = list(word)
word[letter],word[point]=word[point],word[letter]
return word
print(wordScramble("I can't wait to see how this turns itself out"))
The outcome is always:
I can't wait to see how this turns itself out
Since you are a beginner, I tried to change your code as little as possible. Mostly you are expecting changes to word to change the contents or your list stringArray. The comments mark the changes and reasons.
from random import randint
def wordScramble(myString): # avoid name clashes with python modules
stringArray = myString.split()
for i, word in enumerate(stringArray): # keep the index so we can update the list
if len(word) >= 4:
letter = randint(1,len(word)-2)
point = letter
while point == letter:
point = randint(1, len(word)-2)
stringArray[i] = switcher(word,letter,point) # update the array
return ' '.join(stringArray) # return the result of the join
def switcher(word,letter,point):
word = list(word)
word[letter],word[point]=word[point],word[letter]
return ''.join(word) # return word back as a string
print(wordScramble("I can't wait to see how this turns itself out"))
Because there had to be a cleaner (and better documented) way to do this:
from random import sample
def wordScramble(sentence):
# Split sentence into words; apply switcher to each; rejoin into a sentence
return ' '.join([switcher(x) for x in sentence.split()])
def switcher(word):
if len(word) <= 3: # Don't bother if not enough letters to scramble
return word
# Pick 2 positions from interior of word
a,b = sorted(sample( xrange(1,len(word)-1), 2 ))
# Re-assemble word with out 2 positions swapped using bits before, between & after them
return word[:a] + word[b] + word[a+1:b] + word[a] + word[b+1:]
print wordScramble("I can't wait to see how this turns itself out")

Why isn't this character-counter program not altering the list?

I'm writting a program to that counts the number of times each letter appears in a text.
I'm trying to convert that into a graph, by print out the bar graph line by line. Slowly adding the space above the letter when the countdown reaches the # of times it appears.
import string
n=input("Enter Text, I wish you luck:")
list1=[] #Empty List
list2=list(" "*26) #26 space list
for a in string.ascii_uppercase:#from a-z
n1=n.count(a) #counts letters
list1.append(n1) #appends numbers
m=max(list1) #finds most occuring letter
c=m+1
while c!=0:
c=c-1
if c==0:
print(string.ascii_uppercase)
break
for x in list1: #suppose to check every term in list1
if x >c: #x is greater than countdowner
k=list1.index(x) #find term number
list2[k]="*" #replace blank with "*"
elif x==c:
#if x is equal to countdowner
k=list1.index(x) #find term number
list2[k]="*" #replaces that term place in list2
print(''.join(list2))
The code only accepts uppercase letters, and right now it only add letters to the countdown list one at a time. So when count reaches one appearance, and 3 letters appear once, it will only print a * on top of one of those letter.
Sample input: HELLO STACKOVERFLOW
*
* *
* * *
ABCDEFGHIJKLMNOPQRSTUVWXYZ
The problem is that k=list1.index(x) can only find the first occurrence of x in list1. So you could put a loop here, using the extended form of index():
list1.index(x, start, end)
which only looks for an index that lies in the range(start, end)
The loop would have to contain a try: ... except block to handle the ValueError exception.
But there's another way to handle this.
#! /usr/bin/env python
from string import ascii_uppercase
def bargraph(data):
data = data.upper()
print(data)
print(''.join(sorted(list(data))))
#Count number of occurences of each letter in data
counts = [data.count(a) for a in ascii_uppercase]
#A single row of the bar graph, initially full of spaces
row = list(" " * 26)
for c in range(max(counts), 0, -1):
for k in range(26):
if counts[k] == c:
row[k] = "*"
print(''.join(row))
print(ascii_uppercase)
def main():
#data = input("Enter Text, I wish you luck:")
data = "This is a test string for the bar graph function"
bargraph(data)
if __name__ == '__main__':
main()
My version of your program converts the string to upper case, prints it, and then sorts it and prints it again to make it easier to check that the bar printing section is doing what it's supposed to do.
It uses a list comprehension to build the list of character counts. It can be made even shorter by using a list comprehension to construct row.
def bargraph(data):
data = data.upper()
print(data)
print(''.join(sorted(list(data))))
#Count number of occurences of each letter in data
counts = [data.count(a) for a in ascii_uppercase]
for c in range(max(counts), 0, -1):
print(''.join(["*" if counts[k] >= c else " " for k in range(26)]))
print(ascii_uppercase)
output for both versions :)
THIS IS A TEST STRING FOR THE BAR GRAPH FUNCTION
AAABCEEFFGGHHHIIIINNNOOPRRRRSSSSTTTTTTU
*
*
* ***
* ** * ***
* ***** ** ***
*** ***** *** ****
ABCDEFGHIJKLMNOPQRSTUVWXYZ
edit
I should mention that there's a more efficient way to count the occurrences of each letter. The current method has to scan through the data string 26 times, once for each letter. That's a bit inefficient, especially if there's a lot of data to process. So it's better to just scan through the data once, and accumulate the counts. One way to do that is to use a dict.
#! /usr/bin/env python
from string import ascii_uppercase
def bargraph(data):
data = data.upper()
print(data)
print(''.join(sorted(list(data))))
#Count number of occurences of each letter in data
counts = dict(zip(ascii_uppercase, 26*(0,)))
for c in data:
if c.isupper():
counts[c] += 1
highcount = max(counts.values())
for c in range(highcount, 0, -1):
print(''.join([" *"[counts[k] >= c] for k in ascii_uppercase]))
print(ascii_uppercase)
def main():
#data = input("Enter Text, I wish you luck:")
data = "This is a test string for the bar graph function"
bargraph(data)
if __name__ == '__main__':
main()
I've also used a little trick to make the row printing step even more compact.
counts[k] >= c will either be False or True.
But Python lets us use boolean values as if they were int values, with False == 0 and True == 1.
Thus " *"[counts[k] >= c] results in " " if counts[k] >= c is False, and "*" if it's True.

Categories

Resources