How to debug my Python dice game? - python

So I recently posted my code for a simple dice program I'm having trouble with. It is supposed to randomly generate 5 numbers in an array, then check if there are any matching values, if there are, it adds to MATCH, so once it's done checking, MATCH+1 is how many 'of a kind' there are(match=1 means two of a kind, match=2 means three of a kind etc.)
It randomly generates and then displays the numbers correctly, and the program seems to check without errors except when the last two playerDice elements match, then it throws an out of bounds error, Why is it doing that? Also it never actually displays the last print line with how many of a kind there are, even when it runs error free, Why is that?
Here is the code:
import random
playerDice = [random.randint(1,6),random.randint(1,6),random.randint(1,6),random.randint(1,6),random.randint(1,6)]
compDice = [random.randint(1,6),random.randint(1,6),random.randint(1,6),random.randint(1,6),random.randint(1,6)]
match = 0
compmatch = 0
#print player dice
print("You rolled: ",end=" ")
a = 0
while a < len(playerDice):
print(str(playerDice[a]) + ", ",end=" ")
a = a + 1
#player check matches
i = 0
while i < len(playerDice):
j = i + 1
if playerDice[i] == playerDice[j]:
match = match + 1
while playerDice[i] != playerDice[j]:
j = j + 1
if playerDice[i] == playerDice[j]:
match = match + 1
i = i + 1
print("Player has: " + str(match + 1) + " of a kind.")

There's a much easier way to look for matches: sort the dice, and then look for runs of repeated dice. You could look for those runs manually, but the standard library has a function for that: itertools.groupby. Here's a short demo.
import random
from itertools import groupby
# Seed the randomizer while testing so that the results are repeatable.
random.seed(7)
def roll_dice(num):
return [random.randint(1,6) for _ in range(num)]
def find_matches(dice):
matches = []
for k, g in groupby(sorted(dice)):
matchlen = len(list(g))
if matchlen > 1:
matches.append('{} of a kind: {}'.format(matchlen, k))
return matches
for i in range(1, 6):
print('Round', i)
player_dice = roll_dice(5)
#comp_dice = roll_dice(5)
print('You rolled: ', end='')
print(*player_dice, sep=', ')
matches = find_matches(player_dice)
if not matches:
print('No matches')
else:
for row in matches:
print(row)
print()
output
Round 1
You rolled: 3, 2, 4, 6, 1
No matches
Round 2
You rolled: 1, 5, 1, 3, 5
2 of a kind: 1
2 of a kind: 5
Round 3
You rolled: 1, 5, 2, 1, 1
3 of a kind: 1
Round 4
You rolled: 4, 4, 1, 2, 1
2 of a kind: 1
2 of a kind: 4
Round 5
You rolled: 5, 4, 1, 5, 1
2 of a kind: 1
2 of a kind: 5
Here's an alternative version of find_matches that doesn't use groupby. It's probably a good idea to run through this algorithm on paper to see exactly how it works.
def find_matches(dice):
matches = []
dice = sorted(dice)
prev = dice[0]
matchlen = 1
# Add a "dummy" entry so we can detect a group at the end of the list
for d in dice[1:] + [0]:
# Compare this die to the previous one
if d == prev:
# We're inside a run of matching dice
matchlen += 1
else:
# The previous run has ended, so see if it's
# long enough to add to the matches list
if matchlen > 1:
matches.append('{} of a kind: {}'.format(matchlen, prev))
# Reset the match length counter
matchlen = 1
# This die will be the previous die on the next loop iteration
prev = d
return matches

Related

How to optimize the search algorithm for large 2d-array?

After adding a line of code
pathResult.append(find_max_path(arr, a + 1, b + 1, path))
began to run slowly, but without this code it does not work correctly. How can i optimize the code? The function looks for the path with the maximum number of points in a two-dimensional array where values equal to 100 lie predominantly on the main diagonal. Rows can have the same value equal to 100, but in any column the value 100 is one or none. Full code:
arr = [
[000,000,000,000,000,100,000],
[000,000,000,000,000,000,000],
[000,000,100,000,000,000,000],
[000,100,000,000,000,000,000],
[100,000,000,000,000,100,000],
[000,000,000,000,100,000,000],
[000,000,000,000,000,000,000],
[000,000,000,000,000,000,000]]
def find_max_path(arr, a=0, b=0, path=None):
if path is None:
path = []
while (a < len(arr)) and (b < len(arr[a])):
if arr[a][b] == 100:
path.append({"a": a, "b": b})
b += 1
else:
try:
if arr[a + 1][b + 1] == 100:
a += 1
b += 1
continue
except IndexError:
pass
check = []
for j in range(b + 1, len(arr[a])):
if arr[a][j] == 100:
check.append({"a": a, "b": j})
break
if not check:
a += 1
continue
i = a + 1
while i < len(arr):
if arr[i][b] == 100:
check.append({"a": i, "b": b})
break
i += 1
pathResult = []
for c in check:
pathNew = path[:]
pathNew.append({"a": c["a"], "b": c["b"]})
pathResult.append(find_max_path(arr, c["a"] + 1, c["b"] + 1, pathNew))
pathResult.append(find_max_path(arr, a + 1, b + 1, path))
maximum = 0
maxpath = []
for p in pathResult:
if len(p) > maximum:
maximum = len(p)
maxpath = p[:]
if maxpath:
return maxpath
a += 1
return path
print(find_max_path(arr))
UPDATE1: add two break in inner loops (execution time is halved)
Output:
[{'a': 4, 'b': 0}, {'a': 5, 'b': 4}]
UPDATE2
Usage.
I use this algorithm to synchronize two streams of information. I have words from the text along the lines, about which it is known where they are in the text of the book L_word. By columns, I have recognized words from the audiobook, about which the recognized word itself is known and when it was spoken in the audio stream R_word.
It turns out two arrays of words. To synchronize these two lists, I use something like this
from rapidfuzz import process, fuzz
import numpy as np
window = 50
# L_word = ... # words from text book
# R_word = ... # recognize words from audiobook
L = 0
R = 0
L_chunk = L_word[L:L+window]
R_chunk = R_word[R:R+window]
scores = process.cdist(L_chunk,
R_chunk,
scorer=fuzz.ratio,
type=np.uint8,
score_cutoff=100)
p = find_max_path(scores)
# ... path processing ...
...
as a result of all the work, we get something like this video book with pagination and subtitles synchronized with audio download 3GB
UPDATE3: adding this code reduces the execution time by almost ten times!
try:
if arr[a + 1][b + 1] == 100:
a += 1
b += 1
continue
except IndexError:
pass
Python shows how to do debugging and profiling. Go around the algorithm and time functions to see where the bottleneck is

Formatting unknown output in a table in Python

Help! I'm a Python beginner given the assignment of displaying the Collatz Sequence from a user-inputted integer, and displaying the contents in columns and rows. As you may know, the results could be 10 numbers, 30, or 100. I'm supposed to use '\t'. I've tried many variations, but at best, only get a single column. e.g.
def sequence(number):
if number % 2 == 0:
return number // 2
else:
result = number * 3 + 1
return result
n = int(input('Enter any positive integer to see Collatz Sequence:\n'))
while sequence != 1:
n = sequence(int(n))
print('%s\t' % n)
if n == 1:
print('\nThank you! The number 1 is the end of the Collatz Sequence')
break
Which yields a single vertical column, rather than the results being displayed horizontally. Ideally, I'd like to display 10 results left to right, and then go to another line. Thanks for any ideas!
Something like this maybe:
def get_collatz(n):
return [n // 2, n * 3 + 1][n % 2]
while True:
user_input = input("Enter a positive integer: ")
try:
n = int(user_input)
assert n > 1
except (ValueError, AssertionError):
continue
else:
break
sequence = [n]
while True:
last_item = sequence[-1]
if last_item == 1:
break
sequence.append(get_collatz(last_item))
print(*sequence, sep="\t")
Output:
Enter a positive integer: 12
12 6 3 10 5 16 8 4 2 1
>>>
EDIT Trying to keep it similar to your code:
I would change your sequence function to something like this:
def get_collatz(n):
if n % 2 == 0:
return n // 2
return n * 3 + 1
I called it get_collatz because I think that is more descriptive than sequence, it's still not a great name though - if you wanted to be super explicit maybe get_collatz_at_n or something.
Notice, I took the else branch out entirely, since it's not required. If n % 2 == 0, then we return from the function, so either you return in the body of the if or you return one line below - no else necessary.
For the rest, maybe:
last_number = int(input("Enter a positive integer: "))
while last_number != 1:
print(last_number, end="\t")
last_number = get_collatz(last_number)
In Python, print has an optional keyword parameter named end, which by default is \n. It signifies which character should be printed at the very end of a print-statement. By simply changing it to \t, you can print all elements of the sequence on one line, separated by tabs (since each number in the sequence invokes a separate print-statement).
With this approach, however, you'll have to make sure to print the trailing 1 after the while loop has ended, since the loop will terminate as soon as last_number becomes 1, which means the loop won't have a chance to print it.
Another way of printing the sequence (with separating tabs), would be to store the sequence in a list, and then use str.join to create a string out of the list, where each element is separated by some string or character. Of course this requires that all elements in the list are strings to begin with - in this case I'm using map to convert the integers to strings:
result = "\t".join(map(str, [12, 6, 3, 10, 5, 16, 8, 4, 2, 1]))
print(result)
Output:
12 6 3 10 5 16 8 4 2 1
>>>

Trying to find how often a dice roll result occurs in a list of randomly rolled n-sided dice

I am trying to find the occurrences of each number for sides going 1 up to the number of sides on a dice roll. I would like the program to find the number of occurrences for each number that is in listRolls.
Example: if there were a 6 sided dice then it would be 1 up to 6 and the list would roll the dice x amount of times and I would like to find how many times the dice rolled a 1 so on and so forth.
I am new to python and trying to learn it! Any help would be appreciated!
import random
listRolls = []
# Randomly choose the number of sides of dice between 6 and 12
# Print out 'Will be using: x sides' variable = numSides
def main() :
global numSides
global numRolls
numSides = sides()
numRolls = rolls()
rollDice()
counterInputs()
listPrint()
def rolls() :
# for rolls in range(1):
###################################
## CHANGE 20, 50 to 200, 500 ##
##
x = (random.randint(20, 50))
print('Ran for: %s rounds' %(x))
print ('\n')
return x
def sides():
# for sides in range(1):
y = (random.randint(6, 12))
print ('\n')
print('Will be using: %s sides' %(y))
return y
def counterInputs() :
counters = [0] * (numSides + 1) # counters[0] is not used.
value = listRolls
# if value >= 1 and value <= numSides :
# counters[value] = counters[value] + 1
for i in range(1, len(counters)) :
print("%2d: %4d" % (i, value[i]))
print ('\n')
# Face value of die based on each roll (numRolls = number of times die is
thrown).
# numSides = number of faces)
def rollDice():
i = 0
while (i < numRolls):
x = (random.randint(1, numSides))
listRolls.append(x)
# print (x)
i = i + 1
# print ('Done')
def listPrint():
for i, item in enumerate(listRolls):
if (i+1)%13 == 0:
print(item)
else:
print(item,end=', ')
print ('\n')
main()
Fastest way (I know of) is using Counter() from collections (see bottom for dict-only replacement):
import random
from collections import Counter
# create our 6-sided dice
sides = range(1,7)
num_throws = 1000
# generates num_throws random values and counts them
counter = Counter(random.choices(sides, k = num_throws))
print (counter) # Counter({1: 181, 3: 179, 4: 167, 5: 159, 6: 159, 2: 155})
collections.Counter([iterable-or-mapping])) is a specialized dictionary that counts the occurences in the iterable you give it.
random.choices(population, weights=None, *, cum_weights=None, k=1) uses the given iterable (a range(1,7) == 1,2,3,4,5,6 and draws k things from it, returning them as list.
range(from,to[,steps]) generates a immutable sequence and makes random.choices perform even better then when using a list.
As more complete program including inputting facecount and throw-numbers with validation:
def inputNumber(text,minValue):
"""Ask for numeric input using 'text' - returns integer of minValue or more. """
rv = None
while not rv:
rv = input(text)
try:
rv = int(rv)
if rv < minValue:
raise ValueError
except:
rv = None
print("Try gain, number must be {} or more\n".format(minValue))
return rv
from collections import Counter
import random
sides = range(1,inputNumber("How many sides on the dice? [4+] ",4)+1)
num_throws = inputNumber("How many throws? [1+] ",1)
counter = Counter(random.choices(sides, k = num_throws))
print("")
for k in sorted(counter):
print ("Number {} occured {} times".format(k,counter[k]))
Output:
How many sides on the dice? [4+] 1
Try gain, number must be 4 or more
How many sides on the dice? [4+] a
Try gain, number must be 4 or more
How many sides on the dice? [4+] 5
How many throws? [1+] -2
Try gain, number must be 1 or more
How many throws? [1+] 100
Number 1 occured 22 times
Number 2 occured 20 times
Number 3 occured 22 times
Number 4 occured 23 times
Number 5 occured 13 times
You are using python 2.x way of formatting string output, read about format(..) and its format examples.
Take a look at the very good answers for validating input from user: Asking the user for input until they give a valid response
Replacement for Counter if you aren't allowed to use it:
# create a dict
d = {}
# iterate over all values you threw
for num in [1,2,2,3,2,2,2,2,2,1,2,1,5,99]:
# set a defaultvalue of 0 if key not exists
d.setdefault(num,0)
# increment nums value by 1
d[num]+=1
print(d) # {1: 3, 2: 8, 3: 1, 5: 1, 99: 1}
You could trim this down a bit using a dictionary. For stuff like dice I think a good option is to use random.choice and just draw from a list that you populate with the sides of the dice. So to start, we can gather rolls and sides from the user using input(). Next we can use the sides to generate our list that we pull from, you could use randint method in place of this, but for using choice we can make a list in range(1, sides+1). Next we can initiate a dictionary using dict and make a dictionary that has all the sides as keys with a value of 0. Now looks like this d = {1:0, 2:0...n+1:0}.From here now we can use a for loop to populate our dictionary adding 1 to whatever side is rolled. Another for loop will let us print out our dictionary. Bonus. I threw in a max function that takes the items in our dictionary and sorts them by their values and returns the largest tuple of (key, value). We can then print a most rolled statement.
from random import choice
rolls = int(input('Enter the amount of rolls: '))
sides = int(input('Enter the amound of sides: '))
die = list(range(1, sides+1))
d = dict((i,0) for i in die)
for i in range(rolls):
d[choice(die)] += 1
print('\nIn {} rolls, you rolled: '.format(rolls))
for i in d:
print('\tRolled {}: {} times'.format(i, d[i]))
big = max(d.items(), key=lambda x: x[1])
print('{} was rolled the most, for a total of {} times'.format(big[0], big[1]))
Enter the amount of rolls: 5
Enter the amound of sides: 5
In 5 rolls, you rolled:
Rolled 1: 1 times
Rolled 2: 2 times
Rolled 3: 1 times
Rolled 4: 1 times
Rolled 5: 0 times
2 was rolled the most, for a total of 2 times

Can we add the same element twice in a python list?

Here is the question:
Here is an array of length M with numbers in the range 1 ... N, where
N is less than or equal to 20. We are to go through it and count how
many times each number is encountered. I.e. it is like Vowel Count
task, but we need to maintain more than one counter. Be sure to use
separate array for them, do not create a lot of separate variables,
one for each counter.
Input data contain M and N in the first line. The second (rather long)
line will contain M numbers separated by spaces. Answer should contain
exactly N values, separated by spaces. First should give amount of
1-s, second - amount of 2-s and so on.
Example:
data input: 10 3 3 2 1 2 3 1 1 1 1 3
answer: 5 2 3
Here is my code for this problem:
# 10 3
# 3 2 1 2 3 1 1 1 1 3
# [1, 0, 0]
# expected: [5, 2, 3]
# Transfer the first line input into number lista:
rawone = input()
stringa = rawone.split()
lista = []
for el in stringa:
lista.append(int(el))
# check the function:
# print (lista)
# Transfer the second line input into number listb:
rawtwo = input()
stringb = rawtwo.split()
listb = []
for ele in stringb:
listb.append(int(ele))
# check the function:
# print (listb)
# initiate a few variables:
t = 0 # the current time
times = lista[1] # the total time
print (times)
d = 1 # detect key
n = 0 # the number of qualified candidate
out = [] # the list of output
elist = []
# method: while (t<times) --> for element in listo: if (el==d) --> n=n+1:
# THIS PART HAS SOME PROBLEMS!!!
while t < times:
n = 0 # reinitiate the n
for elem in listb: # ***WHY THIS FOR LOOP CAN ONLY BE EXCUTE ONCE AND NOT WORK ANY MORE???
if elem == d:
elist += [(elem)]
d = d + 1
out.append(len(elist))
print (elist)
t = t + 1
print (out)
So I have some problem with the formula part and it's not what I expect it would do. And I checked my answer. I am confused why it only add one qualified element in each turn. How can I fix this? Thank you for your generous help!
So the problem has been solved, my final code is like below:
# Transfer the first line input into number lista:
rawone = input()
stringa = rawone.split()
lista = []
for el in stringa:
lista.append(int(el))
# check the function:
# print (lista)
# Transfer the second line input into number listb:
rawtwo = input()
stringb = rawtwo.split()
listb = []
for ele in stringb:
listb.append(int(ele))
# check the function:
# print (listb)
# initiate a few variables:
t = 0 # the current time
times = lista[1] # the total time
# print (times)
d = 1 # detect key
n = 0 # the number of qualified candidate
out = [] # the list of output
elist = []
# method: while (t<times) --> for element in listo: if (el==d) --> n=n+1:
# THIS PART HAS SOME PROBLEMS!!!
while t < times:
n = 0 # reinitiate the n
for elem in listb: # ***WHY THIS FOR LOOP CAN ONLY BE EXCUTE ONCE AND NOT WORK ANY MORE???
if elem == d:
elist.append(elem)
d = d + 1
out.append(len(elist))
elist = [] # reinitiate elist
# print (elist)
t = t + 1
print(" ".join(str(x) for x in out))
I realize this might not count for your assignment, but anyways:
from collections import Counter
N = 3
my_list = [3, 3, 2, 1, 2, 3, 1, 1, 1, 1, 3]
c = Counter(my_list)
print(" ".join(c[i+1] for i in range(N)))
Your code is difficult to follow due to the poor variable names and extra variables. As you continue in programming, please look for examples of better coding.
The main problem you have is that you increment d inside the inner loop. Take the statement d = d + 1 and un-indent it one level. This means that you increment d only once each time you go through the outer while loop.
Even better, just use the outer loop for this. Since you already know how many times you have to execute that, use a for loop on the outside, as well:
for d in range(1, times+1):
n = 0 # reinitiate the n
for elem in listb: # ***WHY THIS FOR LOOP CAN ONLY BE EXCUTE ONCE AND NOT WORK ANY MORE???
if elem == d:
elist += [elem]
out.append(len(elist))
print (elist)
print (out)
This at least gets you to output closer to what you want:
[1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 2, 2]
[1, 1, 1, 1, 1, 2, 2, 3, 3, 3]
[5, 7, 10]
You can now attack the next problem: counting only the entries for that particular number, rather than all the numbers found so far. To do this, be sure to clean out the elist every time, instead of only once:
for d in range(1, times+1):
elist = []
for elem in listb:
...
... which finally gets the output:
[1, 1, 1, 1, 1]
[2, 2]
[3, 3, 3]
[5, 2, 3]
I expect that you can finish from here.
Also note that you can simply set up out as a direct count of the elements you find. If you find a 1, increment the first element of out; if you find a 2, increment the second, and so on. The code for that segment has no outer loop; it just looks like this:
out = [0] * times
for elem in listb:
out[elem-1] += 1
print (out)
That replaces all your code below print (times).
#Prune shows you how to fix your code. I also want to show you how it's possible to solve this problem with much less code (using a list rather than resorting to collections.Counter)
Since you are given M and N, it's a good idea to store them in variables od those names
first_line = [int(x) for x in input().split()]
M, N = first_line
second_line = [int(x) for x in input().split()]
Now create a list (array) with N zeros. These are the counters
result = [0] * N
Remember that list (array) indices start at 0, so we need to subtract 1 from each elem
for elem in second_line:
result[elem - 1] += 1
Show the result
print(result)

Count consecutive characters

How would I count consecutive characters in Python to see the number of times each unique digit repeats before the next unique digit?
At first, I thought I could do something like:
word = '1000'
counter = 0
print range(len(word))
for i in range(len(word) - 1):
while word[i] == word[i + 1]:
counter += 1
print counter * "0"
else:
counter = 1
print counter * "1"
So that in this manner I could see the number of times each unique digit repeats. But this, of course, falls out of range when i reaches the last value.
In the example above, I would want Python to tell me that 1 repeats 1, and that 0 repeats 3 times. The code above fails, however, because of my while statement.
How could I do this with just built-in functions?
Consecutive counts:
You can use itertools.groupby:
s = "111000222334455555"
from itertools import groupby
groups = groupby(s)
result = [(label, sum(1 for _ in group)) for label, group in groups]
After which, result looks like:
[("1": 3), ("0", 3), ("2", 3), ("3", 2), ("4", 2), ("5", 5)]
And you could format with something like:
", ".join("{}x{}".format(label, count) for label, count in result)
# "1x3, 0x3, 2x3, 3x2, 4x2, 5x5"
Total counts:
Someone in the comments is concerned that you want a total count of numbers so "11100111" -> {"1":6, "0":2}. In that case you want to use a collections.Counter:
from collections import Counter
s = "11100111"
result = Counter(s)
# {"1":6, "0":2}
Your method:
As many have pointed out, your method fails because you're looping through range(len(s)) but addressing s[i+1]. This leads to an off-by-one error when i is pointing at the last index of s, so i+1 raises an IndexError. One way to fix this would be to loop through range(len(s)-1), but it's more pythonic to generate something to iterate over.
For string that's not absolutely huge, zip(s, s[1:]) isn't a a performance issue, so you could do:
counts = []
count = 1
for a, b in zip(s, s[1:]):
if a==b:
count += 1
else:
counts.append((a, count))
count = 1
The only problem being that you'll have to special-case the last character if it's unique. That can be fixed with itertools.zip_longest
import itertools
counts = []
count = 1
for a, b in itertools.zip_longest(s, s[1:], fillvalue=None):
if a==b:
count += 1
else:
counts.append((a, count))
count = 1
If you do have a truly huge string and can't stand to hold two of them in memory at a time, you can use the itertools recipe pairwise.
def pairwise(iterable):
"""iterates pairwise without holding an extra copy of iterable in memory"""
a, b = itertools.tee(iterable)
next(b, None)
return itertools.zip_longest(a, b, fillvalue=None)
counts = []
count = 1
for a, b in pairwise(s):
...
A solution "that way", with only basic statements:
word="100011010" #word = "1"
count=1
length=""
if len(word)>1:
for i in range(1,len(word)):
if word[i-1]==word[i]:
count+=1
else :
length += word[i-1]+" repeats "+str(count)+", "
count=1
length += ("and "+word[i]+" repeats "+str(count))
else:
i=0
length += ("and "+word[i]+" repeats "+str(count))
print (length)
Output :
'1 repeats 1, 0 repeats 3, 1 repeats 2, 0 repeats 1, 1 repeats 1, and 0 repeats 1'
#'1 repeats 1'
Totals (without sub-groupings)
#!/usr/bin/python3 -B
charseq = 'abbcccdddd'
distros = { c:1 for c in charseq }
for c in range(len(charseq)-1):
if charseq[c] == charseq[c+1]:
distros[charseq[c]] += 1
print(distros)
I'll provide a brief explanation for the interesting lines.
distros = { c:1 for c in charseq }
The line above is a dictionary comprehension, and it basically iterates over the characters in charseq and creates a key/value pair for a dictionary where the key is the character and the value is the number of times it has been encountered so far.
Then comes the loop:
for c in range(len(charseq)-1):
We go from 0 to length - 1 to avoid going out of bounds with the c+1 indexing in the loop's body.
if charseq[c] == charseq[c+1]:
distros[charseq[c]] += 1
At this point, every match we encounter we know is consecutive, so we simply add 1 to the character key. For example, if we take a snapshot of one iteration, the code could look like this (using direct values instead of variables, for illustrative purposes):
# replacing vars for their values
if charseq[1] == charseq[1+1]:
distros[charseq[1]] += 1
# this is a snapshot of a single comparison here and what happens later
if 'b' == 'b':
distros['b'] += 1
You can see the program output below with the correct counts:
➜ /tmp ./counter.py
{'b': 2, 'a': 1, 'c': 3, 'd': 4}
You only need to change len(word) to len(word) - 1. That said, you could also use the fact that False's value is 0 and True's value is 1 with sum:
sum(word[i] == word[i+1] for i in range(len(word)-1))
This produces the sum of (False, True, True, False) where False is 0 and True is 1 - which is what you're after.
If you want this to be safe you need to guard empty words (index -1 access):
sum(word[i] == word[i+1] for i in range(max(0, len(word)-1)))
And this can be improved with zip:
sum(c1 == c2 for c1, c2 in zip(word[:-1], word[1:]))
If we want to count consecutive characters without looping, we can make use of pandas:
In [1]: import pandas as pd
In [2]: sample = 'abbcccddddaaaaffaaa'
In [3]: d = pd.Series(list(sample))
In [4]: [(cat[1], grp.shape[0]) for cat, grp in d.groupby([d.ne(d.shift()).cumsum(), d])]
Out[4]: [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('a', 4), ('f', 2), ('a', 3)]
The key is to find the first elements that are different from their previous values and then make proper groupings in pandas:
In [5]: sample = 'abba'
In [6]: d = pd.Series(list(sample))
In [7]: d.ne(d.shift())
Out[7]:
0 True
1 True
2 False
3 True
dtype: bool
In [8]: d.ne(d.shift()).cumsum()
Out[8]:
0 1
1 2
2 2
3 3
dtype: int32
This is my simple code for finding maximum number of consecutive 1's in binaray string in python 3:
count= 0
maxcount = 0
for i in str(bin(13)):
if i == '1':
count +=1
elif count > maxcount:
maxcount = count;
count = 0
else:
count = 0
if count > maxcount: maxcount = count
maxcount
There is no need to count or groupby. Just note the indices where a change occurs and subtract consecutive indicies.
w = "111000222334455555"
iw = [0] + [i+1 for i in range(len(w)-1) if w[i] != w[i+1]] + [len(w)]
dw = [w[i] for i in range(len(w)-1) if w[i] != w[i+1]] + [w[-1]]
cw = [ iw[j] - iw[j-1] for j in range(1, len(iw) ) ]
print(dw) # digits
['1', '0', '2', '3', '4']
print(cw) # counts
[3, 3, 3, 2, 2, 5]
w = 'XXYXYYYXYXXzzzzzYYY'
iw = [0] + [i+1 for i in range(len(w)-1) if w[i] != w[i+1]] + [len(w)]
dw = [w[i] for i in range(len(w)-1) if w[i] != w[i+1]] + [w[-1]]
cw = [ iw[j] - iw[j-1] for j in range(1, len(iw) ) ]
print(dw) # characters
print(cw) # digits
['X', 'Y', 'X', 'Y', 'X', 'Y', 'X', 'z', 'Y']
[2, 1, 1, 3, 1, 1, 2, 5, 3]
A one liner that returns the amount of consecutive characters with no imports:
def f(x):s=x+" ";t=[x[1] for x in zip(s[0:],s[1:],s[2:]) if (x[1]==x[0])or(x[1]==x[2])];return {h: t.count(h) for h in set(t)}
That returns the amount of times any repeated character in a list is in a consecutive run of characters.
alternatively, this accomplishes the same thing, albeit much slower:
def A(m):t=[thing for x,thing in enumerate(m) if thing in [(m[x+1] if x+1<len(m) else None),(m[x-1] if x-1>0 else None)]];return {h: t.count(h) for h in set(t)}
In terms of performance, I ran them with
site = 'https://web.njit.edu/~cm395/theBeeMovieScript/'
s = urllib.request.urlopen(site).read(100_000)
s = str(copy.deepcopy(s))
print(timeit.timeit('A(s)',globals=locals(),number=100))
print(timeit.timeit('f(s)',globals=locals(),number=100))
which resulted in:
12.528256356999918
5.351301653001428
This method can definitely be improved, but without using any external libraries, this was the best I could come up with.
In python
your_string = "wwwwweaaaawwbbbbn"
current = ''
count = 0
for index, loop in enumerate(your_string):
current = loop
count = count + 1
if index == len(your_string)-1:
print(f"{count}{current}", end ='')
break
if your_string[index+1] != current:
print(f"{count}{current}",end ='')
count = 0
continue
This will output
5w1e4a2w4b1n
#I wrote the code using simple loops and if statement
s='feeekksssh' #len(s) =11
count=1 #f:0, e:3, j:2, s:3 h:1
l=[]
for i in range(1,len(s)): #range(1,10)
if s[i-1]==s[i]:
count = count+1
else:
l.append(count)
count=1
if i == len(s)-1: #To check the last character sequence we need loop reverse order
reverse_count=1
for i in range(-1,-(len(s)),-1): #Lopping only for last character
if s[i] == s[i-1]:
reverse_count = reverse_count+1
else:
l.append(reverse_count)
break
print(l)
Today I had an interview and was asked the same question. I was struggling with the original solution in mind:
s = 'abbcccda'
old = ''
cnt = 0
res = ''
for c in s:
cnt += 1
if old != c:
res += f'{old}{cnt}'
old = c
cnt = 0 # default 0 or 1 neither work
print(res)
# 1a1b2c3d1
Sadly this solution always got unexpected edge cases result(is there anyone to fix the code? maybe i need post another question), and finally timeout the interview.
After the interview I calmed down and soon got a stable solution I think(though I like the groupby best).
s = 'abbcccda'
olds = []
for c in s:
if olds and c in olds[-1]:
olds[-1].append(c)
else:
olds.append([c])
print(olds)
res = ''.join([f'{lst[0]}{len(lst)}' for lst in olds])
print(res)
# [['a'], ['b', 'b'], ['c', 'c', 'c'], ['d'], ['a']]
# a1b2c3d1a1
Here is my simple solution:
def count_chars(s):
size = len(s)
count = 1
op = ''
for i in range(1, size):
if s[i] == s[i-1]:
count += 1
else:
op += "{}{}".format(count, s[i-1])
count = 1
if size:
op += "{}{}".format(count, s[size-1])
return op
data_input = 'aabaaaabbaaaaax'
start = 0
end = 0
temp_dict = dict()
while start < len(data_input):
if data_input[start] == data_input[end]:
end = end + 1
if end == len(data_input):
value = data_input[start:end]
temp_dict[value] = len(value)
break
if data_input[start] != data_input[end]:
value = data_input[start:end]
temp_dict[value] = len(value)
start = end
print(temp_dict)
PROBLEM: we need to count consecutive characters and return characters with their count.
def countWithString(input_string:str)-> str:
count = 1
output = ''
for i in range(1,len(input_string)):
if input_string[i]==input_string[i-1]:
count +=1
else:
output += f"{count}{input_string[i-1]}"
count = 1
# Used to add last string count (at last else condition will not run and data will not be inserted to ouput string)
output += f"{count}{input_string[-1]}"
return output
countWithString(input)
input:'aaabbbaabbcc'
output:'3a3b2a2b2c'
Time Complexity: O(n)
Space Complexity: O(1)
temp_str = "aaaajjbbbeeeeewwjjj"
def consecutive_charcounter(input_str):
counter = 0
temp_list = []
for i in range(len(input_str)):
if i==0:
counter+=1
elif input_str[i]== input_str[i-1]:
counter+=1
if i == len(input_str)-1:
temp_list.extend([input_str[i - 1], str(counter)])
else:
temp_list.extend([input_str[i-1],str(counter)])
counter = 1
print("".join(temp_list))
consecutive_charcounter(temp_str)

Categories

Resources