String replacement in list not working [duplicate] - python

This question already has answers here:
Why doesn't calling a string method (such as .replace or .strip) modify (mutate) the string?
(3 answers)
Closed 4 years ago.
You can see in my code below that I am creating a list of strings that essentially forms as many circles defined by the planes list. The issue is associated with the hover list. Essentially, if click_x and click_y are within a certain region defined by the strings then it should replace all the strings within that region but for some reason text.replace(...) isn't doing anything and by that I mean after "replacing", the hover list is still the same. All help is appreciated.
Also I can't use np.where since I get a TypeError for some reason.
import numpy as np
planes = [2, 3, 4, 5]
edge = planes[-1]
x = np.linspace(-edge, edge, 50)
y = np.linspace(-edge, edge, 50)
regions = []
hover = []
# Normal Display
for i in x:
row = []
text_row = []
for j in y:
# TODO: Make what_region function
if np.sqrt(i ** 2 + j ** 2) < planes[0]:
row.append(7) # <- Arbitrary number to adjust color
text_row.append('Region 1')
if np.sqrt(i ** 2 + j ** 2) > planes[-1]:
row.append(5) # <- Arbitrary number to adjust color
text_row.append('Region {}'.format(len(planes) + 1))
for k in range(len(planes) - 1):
if planes[k] < np.sqrt(i ** 2 + j ** 2) < planes[k + 1]:
row.append(k * 3) # <- Arbitrary number to adjust color
text_row.append('Region {}'.format(k + 2))
regions.append(row)
hover.append(text_row)
# indices = np.where(np.array(hover) == "Region 1")
# for a in range(len(indices[0])):
# hover[indices[0][a]][indices[1][a]] = ("New Region")
click_x = np.random.uniform(-planes[-1], planes[-1])
click_y = np.random.uniform(-planes[-1], planes[-1])
# Change graph on Click TODO: Not filling region correctly (MUST FIX)
if np.sqrt(click_x ** 2 + click_y ** 2) < planes[0]:
print('1', True)
for row_ in hover:
for text in row_:
print(text)
text.replace('Region 1', 'New Region')
if np.sqrt(click_x ** 2 + click_y ** 2) > planes[-1]:
print('2', True)
for row_ in hover:
for text in row_:
print(text)
text.replace('Region {}'.format(len(planes)+1), 'Newer Region')
for k in range(len(planes) - 1):
if planes[k] < np.sqrt(click_x ** 2 + click_y ** 2) < planes[k + 1]:
print('3', True)
for row_ in hover:
for text in row_:
print(text)
text.replace('Region {}'.format(k+2), 'Newest Region')
print(hover)

Strings in python are immutable. This means that they cannot ever be edited, only replaced.
x = "silly sam"
x.replace("silly", "smart")
x is still "silly sam" because the replaced version of the string was not assigned to a variable and therefore discarded.
x = "silly sam"
x = x.replace("silly", "smart")
Now x has the value "smart"

Related

How to print two columns with different range using for loops?

I need some very basic help with Python. I'm trying to get a better understanding of formatting using a for loop and I want to print out vertical Histogram.
Here is what I've tried:
tot_progress = 2
tot_trailer = 4
tot_retriever = 3
tot_exclude = 4
# Vertical Histogram
print("Progress", " Trailing", " Retriever", " Excluded")
a = '*'
for i,j,k,l in zip(range(0, tot_progress, 1), range(0, tot_trailer, 1), range(0, tot_retriever, 1), range(0, tot_exclude, 1)):
print('{}\t\t\t{}\t\t\t{}\t\t\t{}'.format(a, a, a, a))
The output I got:
Progress Trailing Retriever Excluded
* * * *
* * * *
what I want:
Progress Trailing Retriever Excluded
* * * *
* * * *
* * *
* *
User itertools longest to iterate to maximum and update your loop with conditions and print accordingly
import itertools
for i,j,k,l in itertools.zip_longest(range(0, tot_progress, 1), range(0, tot_trailer, 1), range(0, tot_retriever, 1), range(0, tot_exclude, 1)):
ai="*" if not i == None else " "
aj="*" if not j == None else " "
ak="*" if not k == None else " "
al="*" if not l == None else " "
print('{}\t\t\t{}\t\t\t{}\t\t\t{}'.format(ai, aj, ak, al))
In order to have vertical histograms you can format each row to have the same "length" and then zip, so that you can transpose it properly. Used string formatting layout, see here for details.
Each bar of the histogram will be centered wrt to the label of the column.
col_labels = {'Progress statussss': 2, "Trailing": 8, "Retriever": 3, "Excluded": 4}
SYMBOL = '*'
PADDING = 2 # add extra space between two columns
col_sizes = list(map(len, col_labels))
row_template = [(('{S:^' + f'{size + PADDING}' + '}' + ' ') *v)[:-1].split() + [' ' * (size+PADDING) ]*(size - v) for size, v in zip(col_sizes, col_labels.values())]
# column names
print(''.join(('{:^' + f'{col_sizes[i] + PADDING}' + '}').format(v) for i, v in enumerate(col_labels)))
# bars
for l in (''.join(i) for i in zip(*row_template)):
print(l.format(S=SYMBOL))
Output
Progress statussss Trailing Retriever Excluded
* * * *
* * * *
* * *
* *
*
*
*
*

Replace characters in a list by column rather than by row

Currently I have the following lists:
counter = [13]
instruments = ['3\t ---', '2\t / \\', '1\t / \\', '0\t--- \\ ---', '-1\t \\ /', '-2\t \\ /', '-3\t ---']
score = ['|*************|']
What I am trying to do is to replace the characters in the instruments list with the characters from the score list (excluding the |).
I am currently experiencing the following issues
The characters are being replaced row by row, rather than column by column.
Instrument List:
3 ---
2 / \
1 / \
0 --- \ ---
-1 \ /
-2 \ /
-3 ---
Score List:
|*************|
EXPECTED OUTPUT:
3 ***
2 * *
1 * *
0 *** *
-1 *
-2 *
-3
Current Output:
3 ***
2 * *
1 * *
0 *** * **
-1
-2
-3
This is how I am currently replacing the characters in the instruments list:
for elements in counter:
current_counter = elements
count = 0
for elements in instrument_wave:
amplitude, form = elements.split('\t')
for characters in form:
if characters in ['-', '/', '\\']:
form = form.replace(characters, '*', 1)
count += 1
if count == current_counter:
break
for characters in form:
if characters in ['-', '/', '\\']:
form = form.replace(characters, '')
if '-' not in amplitude:
amplitude = ' ' + amplitude
new_wave = amplitude + "\t" + form
waveform.append(new_wave)
Any help would be appreciated, especially with regards to how I should fix my replace character to make it go column by column rather than row by row.
To solve your first issue, you need to iterate via columns.
If you zip the lists (via itertools.zip_longest(), as they are not all the same length), you can then go through them in order and truncate the result:
import itertools
cols = list(itertools.zip_longest(*lst, fillvalue=" "))
for i in range(3, 17): # skip negative signs
cols[i] = "".join(cols[i]).replace('-', '*', 1)
cols[i] = "".join(cols[i]).replace('/', '*', 1)
cols[i] = "".join(cols[i]).replace('\\', '*', 1)
fixed = map("".join, zip(*cols[:17])) # no need to zip longest
for l in fixed:
print(l)
See a working example on repl.it, which outputs:
3 ***
2 * *
1 * *
0 *** *
-1 *
-2 *
-3
Note it does pad the lists out with spaces, so you may want to .strip() the results if it isn't just for printing. Adapting that to your score input I'll leave up to you.
Another option, which is probably clearer:
def convert_and_truncate(lst, cutoff):
result = []
for str in lst:
str = str[0] + str[1:].replace('-', '*') # skip the negative signs
str = str.replace('/', '*')
str = str.replace('\\', '*')
result.append(str[:cutoff]) # truncate
return result
Because we're truncating the rest of the list, it doesn't matter that replace is changing them all.
Without itertools, instead self padding to longest part in list:
counter = [16]
instruments = ['3\t ---', '2\t / \\', '1\t / \\', '0\t--- \\ ---', '-1\t \\ /', '-2\t \\ /', '-3\t ---']
score = ['|*************|']
# get longes part list
maxL = max ( len(p) for p in instruments)
#enlarge all to max length
instrum2 = [k + ' '* (maxL-len(k)) for k in instruments]
# mask out leading - to ~ (we reverse it later)
instrum3 = [k if k[0] != '-' else '~'+''.join(k[1:]) for k in instrum2]
# transpose and join to one lengthy sentence, #### are where we later split again
trans = '####'.join(map(''.join,zip(*instrum3)))
# replace the right amount of /-\ with * after that, replace with space instead
cnt = 0
maxCnt = score[0].count('*')
result = []
for t in trans:
if t in '/-\\':
if cnt < maxCnt:
result.append('*')
cnt+=1
else:
result.append(' ')
else:
result.append(t)
# resultlist back to string and split into columns again
result2 = ''.join(result)
trans2 = result2.split('####')
# transpose back to rows and make - correct
trans3 = [''.join(k).replace('~','-') for k in zip(*trans2 )]
for p in trans3:
print(p)
Output:
3 ***
2 * *
1 * *
0 *** *
-1 *
-2 *
-3

Checking within a range of field of fields

I wasn't quite sure how to word the title, sorry if it doesn't make sense/is misleading.
Note - a "boat" is 3 O's next to each other in an array, so
_|O|_ _|_|_
_|O|_ O|O|O
|O| | |
are boats.
So I have an a list of lists (n x n) (working with lists) in which I generate n boats at random spaces. I don't want the boats to be next to each other, touch by corners, or be on top of each other.
A tried checking if a boat would end up on top of another boat in the vein of this:
if board[y - 2][x] == 'O' or board[y + 2][x] == 'O' ...
and so on, which ended up being unsurprisingly long.
I also got index out of range errors, since I was sometimes checking for coordinates not in the field.
So, is there a way I can check for boats in every direction without going out of index range?
Better yet, any ideas on how to make the boats not generate next to each other?
The code for boat generation is here:
from random import *
side = int(input())
game_state = []
def generate_initial_state():
for i in range(side):
game_state.append([])
for j in range(side):
game_state[i].append('.')
for i in range(side):
# Generate boat origin on random coordinates within the game board,
# if there's a boat already, generate new ones
y_cor = randint(0, side-1)
x_cor = randint(0, side-1)
while game_state[y_cor][x_cor] == 'O':
y_cor = randint(0, side - 1)
x_cor = randint(0, side - 1)
# Direct chooses if the boat will be generated up, down, or sideways
direct = randint(1, 4)
cycle = 0
while cycle < 3:
# Generates a boat going from origin in one direction,
# if the boat would end outside the board, chooses a different direction
if direct == 1:
if y_cor + 2 >= side:
direct = randint(1, 4)
else:
game_state[y_cor + cycle][x_cor] = 'O'
cycle += 1
elif direct == 2:
if x_cor + 2 >= side:
direct = randint(1, 4)
else:
game_state[y_cor][x_cor + cycle] = 'O'
cycle += 1
elif direct == 3:
if y_cor - 2 < 0:
direct = randint(1, 4)
else:
game_state[y_cor - cycle][x_cor] = 'O'
cycle += 1
elif direct == 4:
if x_cor - 2 < 0:
direct = randint(1, 4)
else:
game_state[y_cor][x_cor - cycle] = 'O'
cycle += 1
for i in range(side):
print(*game_state[i])
First I would only use two directions (horizontal and vertical), which shouldn't change the probabilities (with your model, a boat can be generated in two ways).
This allows index overflow to only occur by exceeding the allowable indices, which provokes an IndexError that can be intercepted (using a negative index doesn't and that could mess up your generator).
Secondly, using a flag could help you do the trick.
I added a few other modifications :
EDIT : I just realized that my code was rejecting perfectly valid boats if they were on the border, so here is a version that doesn't.
Update : some explanations
We use a boolean flag boat_built to track the suitability of a randomly chosen position of the boat: once all tests are made, this variable decides if the choice was suitable (True) or if obstructions were encountered while tests were carried out (False).
Using
boat_built &= (game_state[x_cor + k*dx][y_cor + k*dy] != "0")
we update the flag for every test : if boat_built was False before the test, it will remain False regardless of the test's result (False & a = False): this is intended, because it means that an obstruction has already been encountered and the boat is invalid.
On the other hand, if boat_built was True before the test, it will contain the result of the test afterwards (True & a = a): this is also intended, since failing the new test means we now have found an obstruction.
Note that all 15 tests are carried out for every boat, even if an obstruction is encountered early on.
from random import *
side = int(input())
game_state = [['.' for i in range(side)] for j in range(side)]
l_dir = [(1, 0), (0, 1)]
def generate_initial_state():
for i in range(side):
boat_built = False
while not boat_built:
boat_built = True
y_cor = randrange(side)
x_cor = randrange(side)
dx, dy = l_dir[randrange(2)]
try:
# check that the three required cells are empty
for k in range(3):
boat_built &= (game_state[x_cor + k*dx][y_cor + k*dy] != "0")
except IndexError:
# if any is out of range, choice is invalid
boat_built = False
for k in range(5):
for l in [-1, 1]:
try:
# check if neighbours on the long sides are empty
boat_built &= (game_state[x_cor + (k-1)*dx + l*dy][y_cor + l*dx + (k-1)*dy] != "0")
except IndexError:
# if we're out of range, no obstruction
pass
for k in [-1, 3]:
try:
# check if neighbours on the short sides are empty
boat_built &= (game_state[x_cor + k*dx][y_cor + k*dy] != "0")
except IndexError:
# again, if we're out of range, no obstruction
pass
# if we reach this point, a valid position has been found
for k in range(3):
game_state[x_cor + k*dx][y_cor + k*dy] = "0"
generate_initial_state()
for i in range(side):
print(*game_state[i])
Your code has, as you complain, too much tedious repeated logic to inspect a point's neighbors. You could turn four tests into one with something like this:
offset = [
(0, -1),
(-1, 0), (1, 0),
(0, 1),
]
for xoff, yoff in offset:
if game_state[x + xoff * cycle][y + yoff * cycle] == 'O':
report_collision(x, y)
Additionally, you could mark the grid with both 'O' for "boat" and 'o' for "bordering a boat", to simplify detecting adjacent boats.
You can try the following class and see if it solves your problem:
#! /usr/bin/env python3
import collections
import enum
import random
def main():
board = Board(10, 10)
print(board)
board.place_boats([2, 3, 3, 4, 5])
print('\n' + '=' * 21 + '\n')
print(board)
Point = collections.namedtuple('Point', 'x, y')
# noinspection PyArgumentList
Orientation = enum.Enum('Orientation', 'HORIZONTAL, VERTICAL')
class Board:
def __init__(self, width, height):
self.__width = width
self.__height = height
self.__matrix = [[False] * height for _ in range(width)]
self.__available = {Point(x, y)
for x in range(width)
for y in range(height)}
def __str__(self):
width = self.__width * 2 + 1
height = self.__height * 2 + 1
grid = [[' '] * width for _ in range(height)]
for yo, xo, character in (0, 1, '|'), (1, 0, '-'), (1, 1, '+'):
for y in range(yo, height, 2):
for x in range(xo, width, 2):
grid[y][x] = character
for x, column in enumerate(self.__matrix):
for y, cell in enumerate(column):
if cell:
grid[y << 1][x << 1] = '#'
return '\n'.join(''.join(row) for row in grid)
# noinspection PyAssignmentToLoopOrWithParameter
def place_boats(self, sizes, patience=10):
matrix_backup = [column.copy() for column in self.__matrix]
available_backup = self.__available.copy()
for _ in range(patience):
# try to place all the boats
for size in sizes:
for _ in range(patience):
# try to place boat of current size
point = random.choice(tuple(self.__available))
method = random.choice(tuple(Orientation))
try:
# try to place a boat; does not mangle the matrix
self.make_boat(point, size, method)
except RuntimeError:
pass
else:
# break out of inner patience loop; go to next size
break # on success
else:
# break to outer patience loop; start from beginning
self.__matrix = [column.copy() for column in matrix_backup]
self.__available = available_backup.copy()
break # on failure
else:
# break out of outer patience loop; all sizes were placed
break # on success
else:
raise RuntimeError('could not place the requested boats')
def make_boat(self, point, size, method):
backup = [column.copy() for column in self.__matrix]
unusable = set()
for offset in range(size):
if method is Orientation.HORIZONTAL:
block = self.mark_cell(point, x_offset=offset)
elif method is Orientation.VERTICAL:
block = self.mark_cell(point, y_offset=offset)
else:
raise ValueError('method was not understood')
if block:
unusable.update(block)
else:
self.__matrix = backup
raise RuntimeError('cannot place boat')
self.__available -= unusable
def mark_cell(self, point, *, x_offset=0, y_offset=0):
target = Point(point.x + x_offset, point.y + y_offset)
if target in self.__available and \
0 <= target.x < self.__width and \
0 <= target.y < self.__height:
self.__matrix[target.x][target.y] = True
return {Point(target.x + xo, target.y + yo)
for xo in range(-1, 2)
for yo in range(-1, 2)}
if __name__ == '__main__':
main()

leading number groups between two numbers

(Python) Given two numbers A and B. I need to find all nested "groups" of numbers:
range(2169800, 2171194)
leading numbers: 21698XX, 21699XX, 2170XX, 21710XX, 217110X, 217111X,
217112X, 217113X, 217114X, 217115X, 217116X, 217117X, 217118X, 2171190X,
2171191X, 2171192X, 2171193X, 2171194X
or like this:
range(1000, 1452)
leading numbers: 10XX, 11XX, 12XX, 13XX, 140X, 141X, 142X, 143X,
144X, 1450, 1451, 1452
Harder than it first looked - pretty sure this is solid and will handle most boundary conditions. :) (There are few!!)
def leading(a, b):
# generate digit pairs a=123, b=456 -> [(1, 4), (2, 5), (3, 6)]
zip_digits = zip(str(a), str(b))
zip_digits = map(lambda (x,y):(int(x), int(y)), zip_digits)
# this ignores problems where the last matching digits are 0 and 9
# leading (12000, 12999) is same as leading(12, 12)
while(zip_digits[-1] == (0,9)):
zip_digits.pop()
# start recursion
return compute_leading(zip_digits)
def compute_leading(zip_digits):
if(len(zip_digits) == 1): # 1 digit case is simple!! :)
(a,b) = zip_digits.pop()
return range(a, b+1)
#now we partition the problem
# given leading(123,456) we decompose this into 3 problems
# lows -> leading(123,129)
# middle -> leading(130,449) which we can recurse to leading(13,44)
# highs -> leading(450,456)
last_digits = zip_digits.pop()
low_prefix = reduce(lambda x, y : 10 * x + y, [tup[0] for tup in zip_digits]) * 10 # base for lows e.g. 120
high_prefix = reduce(lambda x, y : 10 * x + y, [tup[1] for tup in zip_digits]) * 10 # base for highs e.g. 450
lows = range(low_prefix + last_digits[0], low_prefix + 10)
highs = range(high_prefix + 0, high_prefix + last_digits[1] + 1)
#check for boundary cases where lows or highs have all ten digits
(a,b) = zip_digits.pop() # pop last digits of middle so they can be adjusted
if len(lows) == 10:
lows = []
else:
a = a + 1
if len(highs) == 10:
highs = []
else:
b = b - 1
zip_digits.append((a,b)) # push back last digits of middle after adjustments
return lows + compute_leading(zip_digits) + highs # and recurse - woohoo!!
print leading(199,411)
print leading(2169800, 2171194)
print leading(1000, 1452)
def foo(start, end):
index = 0
is_lower = False
while index < len(start):
if is_lower and start[index] == '0':
break
if not is_lower and start[index] < end[index]:
first_lower = index
is_lower = True
index += 1
return index-1, first_lower
start = '2169800'
end = '2171194'
result = []
while int(start) < int(end):
index, first_lower = foo(start, end)
range_end = index > first_lower and 10 or int(end[first_lower])
for x in range(int(start[index]), range_end):
result.append(start[:index] + str(x) + 'X'*(len(start)-index-1))
if range_end == 10:
start = str(int(start[:index])+1)+'0'+start[index+1:]
else:
start = start[:index] + str(range_end) + start[index+1:]
result.append(end)
print "Leading numbers:"
print result
I test the examples you've given, it is right. Hope this will help you
This should give you a good starting point :
def leading(start, end):
leading = []
hundreds = start // 100
while (end - hundreds * 100) > 100:
i = hundreds * 100
leading.append(range(i,i+100))
hundreds += 1
c = hundreds * 100
tens = 1
while (end - c - tens * 10) > 10:
i = c + tens * 10
leading.append(range(i, i + 10))
tens += 1
c += tens * 10
ones = 1
while (end - c - ones) > 0:
i = c + ones
leading.append(i)
ones += 1
leading.append(end)
return leading
Ok, the whole could be one loop-level deeper. But I thought it might be clearer this way. Hope, this helps you...
Update :
Now I see what you want. Furthermore, maria's code doesn't seem to be working for me. (Sorry...)
So please consider the following code :
def leading(start, end):
depth = 2
while 10 ** depth > end : depth -=1
leading = []
const = 0
coeff = start // 10 ** depth
while depth >= 0:
while (end - const - coeff * 10 ** depth) >= 10 ** depth:
leading.append(str(const / 10 ** depth + coeff) + "X" * depth)
coeff += 1
const += coeff * 10 ** depth
coeff = 0
depth -= 1
leading.append(end)
return leading
print leading(199,411)
print leading(2169800, 2171194)
print leading(1000, 1453)
print leading(1,12)
Now, let me try to explain the approach here.
The algorithm will try to find "end" starting from value "start" and check whether "end" is in the next 10^2 (which is 100 in this case). If it fails, it will make a leap of 10^2 until it succeeds. When it succeeds it will go one depth level lower. That is, it will make leaps one order of magnitude smaller. And loop that way until the depth is equal to zero (= leaps of 10^0 = 1). The algorithm stops when it reaches the "end" value.
You may also notice that I have the implemented the wrapping loop I mentioned so it is now possible to define the starting depth (or leap size) in a variable.
The first while loop makes sure the first leap does not overshoot the "end" value.
If you have any questions, just feel free to ask.

Matplotlib draw boxes

I have a set of data, where each value has a (x, y) coordinate. Different values can have the same coordinate. And I want to draw them in a rectangular collection of boxes.
For example, if I have the data:
A -> (0, 0)
B -> (0, 1)
C -> (1, 2)
D -> (0, 1)
I want to get the following drawing:
0 1 2
+++++++++++++
0 + A + B + +
+ + D + +
+++++++++++++
1 + + + C +
+++++++++++++
2 + + + +
+++++++++++++
How can I do it in Python using Matplotlib?
THANKS!
Just thought, maybe what you actually wanted to know was just this:
def drawbox(list,x,y):
# write some graphics code to draw box index x,y containing items 'list'
[[drawbox(u,x,y) for u in X.keys() if X[u]==(y,x)] for x in range(0,3) for y in range(0,3)]
# enter the data like this
X={'A':(0,0),'B':(0,1),'C':(1,2),'D':(0,1)}
# size of grid
xi=map(tuple.__getitem__,X.values(),[1]*len(X))
yi=map(tuple.__getitem__,X.values(),[0]*len(X))
xrng = (min(xi), max(xi)+1)
yrng = (min(yi), max(yi)+1)
for y in range(*yrng): # rows
print '+' * ((xrng[1]-xrng[0])*3) + '+'
k={} # each item k[x] is list of elements in xth box in this row
for x in range(*xrng):
# list of items in this cell
k[x]=[u for u in X.keys() if X[u]==(y,x)]
h=max(map(len, k.values())) # row height
for v in range(h): # lines of row
c=[]
for x in range(*xrng): # columns
if k[x]:
c.append(k[x][0])
del k[x][0]
else: c.append(' ') # shorter cell
s="+ " + "+ ".join(c) + "+"
print s
print "+" * ((xrng[1]-xrng[0])*3) + '+'
Perhaps it would be better to use the ReportLab.
Example

Categories

Resources