Can someone explain how this loop is printing this tuple? - python

I'm trying to understand the solutions to question 5 from pythonchallenge, but I don't understand how the for loop is printing that data from the tuple. The solution is from here
Data contains a list of tuples, eg. data = [[(' ', 95)], [(' ', 14), ('#', 5), (' ', 70), ('#', 5), (' ', 1) ...]]
for line in data:
print("".join([k * v for k, v in line]))
What should be printed out is an ASCII graphic made up of '#'.

This one is sneaky. It's a list of lists of tuples. The inner list is a row on the terminal, and each tuple is a character followed by the number of times that
character should be printed.
It looks like it's iterating through the list, and for each tuple,
printing out tuple[0] tuple[1]-times.
It prints '' 95 times, then '' 14 times, then '#' 5 times, etc, inserting newlines
inbetween each inner list.

Consider:
>>> line = [(' ', 3), ('#', 5), (' ', 3), ('#', 5)]
>>> strs = [k * v for k, v in line]
Then:
>>> strs
[' ', '#####', ' ', '#####']
Furthermore:
>>> ''.join(strs)
' ##### #####'

Related

checking for a down diagonal win in tic tac toe?

sorry if this is really basic! i'm in first year computer science.
i am trying to write a function to check if there is a win on a tictactoe board of NxN size (so i can't hardcode any values); the win has to be from the top left, to the bottom right.
i've already written a function for the upwards diagonal, so i'ved based it around that, but with a list like this: [ [ 'X' , ' ' ] , [ ' ' , ' ' ] ] the function returns True - which it definitely should not.
here's what i have right now, but i have tried many things:
#these can be anything but here's just an example of what i'm using to test
cur = [['X',' '],[' ',' ']]
player= 'X'
def check_down_diag(cur, player):
columncount = 0
for row in cur:
if row[columncount] != player:
columncount += 1
return False
else:
return True
The first step to figure out is the logic. You have a player symbol (here 'X') and a Tic-Tac-Toe size ('N', usually 3 for TTT). So to see if there is a diagonal winner, you would check the two diagonals:
(0, 0), (1, 1), (2, 2), ... (N-1, N-1)
and:
(N-1, 0), (N-2, 1), (N-3, 2) ... (0, N-1)
which are the indeces into the fields to check.
So now you know the two sets of locations to check, and you know which symbol to check for. So write a function which accepts an N for size/dimension and a symbol for which player, and you should be good to go. You can check for both diagonals in one loop, or you can check them in separate loops to start. If you do one loop, you must go through the whole loop once until both checks fail. If you do one loop/diagonal at a time, you should break from the loop as soon as you find a single failure condition, to improve performance.
A while back I wrote a function for pattern finding in 2d arrays specifically for something like this:
def find_pattern(array, pattern, value):
for row in range(len(array)):
for column in range(len(array[0])):
try:
for y, x in pattern:
_value = array[y + row][x + column]
if value != _value:
break
else:
return pattern, (pattern[0][0] + row, pattern[0][1] + column)
except IndexError:
break
return None
The patterns are formed like so (these specific ones are for the standard tic tac toe board (all win conditions)):
win_patterns = [
((0, 0), (0, 1), (0, 2)),
((0, 0), (1, 0), (2, 0)),
((0, 0), (1, 1), (2, 2)),
((0, 2), (1, 1), (2, 0)),
]
The way it works is simple, it is just a tuple of (x, y) coordinates in the grid, starting from 0, so the first pattern would search for straight lines horizontally, second for vertical lines, third and fourth for both diagonals.
Then you can loop over the patterns, pass them to the function along with the 2d array of the board and the value that has to be found in this pattern (' ' values don't mean anything they are there so that the array is more readable):
board = [
['x', ' ', 'x', ' ', ' '],
[' ', 'x', ' ', ' ', ' '],
[' ', ' ', 'x', ' ', 'x'],
[' ', ' ', ' ', ' ', ' '],
[' ', ' ', 'x', ' ', ' '],
]
for pattern in win_patterns:
found = find_pattern(board, pattern, 'x')
if found:
print(found)
break
For the main diagonal, check that all the values that are at the same row and column (i.e. the main diagonal) are equal to the player's symbol:
def check_down_diag1(cur, player):
return all( cur[i][i]==player for i in range(len(cur)) )
For the other diagonal, simply invert one of the dimensions:
def check_down_diag2(cur, player):
return all( cur[i][-1-i]==player for i in range(len(cur)) )
You could also check both diagonals at the same time:
def check_down_diags(cur, player):
return all( cur[i][i]==player for i in range(len(cur)) ) \
or all( cur[i][-1-i]==player for i in range(len(cur)) )

Delete spaces in dictionary values python

I'm reading the data from an outsource. The data has "Name" and "Value with warnings" so I put those in a dictionary in a manner as
d[data[i:i+6]] = data[i+8:i+17], data[i+25:i+36]
Thus at the end I have my dict as;
{'GPT-P ': ('169 ', 'H '), 'GOT-P ': ('47 ', ' '), .....
As seen above both keys and values might have unnecessary spaces.
I was able to overcome spaces in keys with;
d = {x.replace(' ',''): v
for x, v in d.items()}
but can't seem to manage similar for values. I tried using d.values() but it trims the key name and also works only for 1 of the values.
Can you help me understand how I can remove space for several values (2 values in this particular case) and end up with something like;
{'GPT-P': ('169', 'H'), 'GOT-P ': ('47', ''), .....
Thanks. Stay safe and healthy
You will need to do the space replacement in your v values also but
it seems that in your case the values in your dictionary are tuples.
I guess you will want to remove spaces in all elements of each tuple so you will need a second iteration here. You can do something like this:
d = {'GPT-P ': ('169 ', 'H '), 'GOT-P ': ('47 ', ' ')}
{x.replace(' ', ''): tuple(w.replace(' ', '') for w in v) for x, v in d.items()}
Which returns:
{'GPT-P': ('169', 'H'), 'GOT-P': ('47', '')}
Notice that there is list (or tuple) comprehension tuple(w.replace(' ', '') for w in v) within the dictionary comprehension.
Given:
DoT={'GPT-P ': ('169 ', 'H '), 'GOT-P ': ('47 ', ' ')}
Since you have tuples of strings as your values, you need to apply .strip() to each string in the tuple:
>>> tuple(e.strip() for e in ('47 ', ' '))
('47', '')
Apply that to each key, value in a dict comprehension and there you are:
>>> {k.strip():tuple(e.strip() for e in t) for k,t in DoT.items()}
{'GPT-P': ('169', 'H'), 'GOT-P': ('47', '')}
You use .replace(' ','') in your attempt. That will replace ALL spaces:
>>> ' 1 2 3 '.replace(' ','')
'123'
It is more typical to use one of the .strips():
>>> ' 1 2 3 '.strip()
'1 2 3'
>>> ' 1 2 3 '.lstrip()
'1 2 3 '
>>> ' 1 2 3 '.rstrip()
' 1 2 3'
You can use .replace or any of the .strips() in the comprehensions that I used above.

I need to remove duplicates from a list but add the numeric value in them

I have a list that looks like this:
[('A54', 'ItemName1 ', '18'), ('B52', 'ItemName2 ', '51'), ('C45', 'ItemName3 ', '3'), ('A54', ' ItemName1', '15'), ('G22', ' ItemName5, '78')]
The first item in each list represents an item number, the second one is the item name and the third one is the quantity.
What would be the best way to remove duplicate instances from the list while adding the total quantity of items to them?
I've tried sorting the list by alphabetical order using list() but for some reason, it doesn't work.
My sorting attempt looks like this:
L = [('A54', 'ItemName1 ', '18'), ('B52', 'ItemName2 ', '51'), ('C45', 'ItemName3 ', '3'), ('A54', ' ItemName1', '15'), ('G22', ' ItemName5', '78')]
L.sort()
print (L)
The result is always None.
you're probably doing L = L.sort()... which explains the None result (classical issue Why does "return list.sort()" return None, not the list?)
Anyway, sorting+grouping (for instance by using itertools.groupby) isn't the best way. Bad complexity: O(n*log(n)) + O(n)
Instead, create a collections.defaultdict and "count" your items (collections.Counter doesn't work here as the count depends from the value of the third argument converted as integer).
Then rebuild the triplets by unpacking the dictionary keys & values.
import collections
L = [('A54', 'ItemName1', '18'), ('B52', 'ItemName2', '51'),('C45', 'ItemName3', '3'),('A54', 'ItemName1', '15'), ('G22', 'ItemName5', '78')]
d = collections.defaultdict(int)
for a,b,c in L:
d[a,b] += int(c)
newlist = [(a,b,c) for (a,b),c in d.items()]
result:
>>> newlist
[('B52', 'ItemName2', 51),
('C45', 'ItemName3', 3),
('A54', 'ItemName1', 33),
('G22', 'ItemName5', 78)]
>>>
complexity is then O(n)
Note that your original data seems to contain trailing/leading spaces. Not an issue to strip them when creating the new dictionary (else grouping would not work), for instance like:
d[a,b.strip()] += int(c)
I think it might be a good idea to implement a dictionary, since you seem to be regarding the first item of each tuple as a key. I personally would sort them like this
from collections import OrderedDict
L = [('A54', 'ItemName1 ', '18'), ('B52', 'ItemName2 ', '51'), ('C45', 'ItemName3 ', '3'), ('A54', ' ItemName1', '15'), ('G22', ' ItemName5', '78')]
sorted_L = OrderedDict()
for item in L:
if item[0] in sorted_L.keys():
sorted_L[item[0]] += int(item[2])
else:
sorted_L[item[0]] = int(item[2])
print(sorted_L)
Which results in
OrderedDict([('A54', 33), ('B52', 51), ('C45', 3), ('G22', 78)])
But maintains the order of your list, by using an OrderedDict instead of a normal dictionary.

Align numbers in sublist

I have a set of numbers that I want to align considering the comma:
10 3
200 4000,222 3 1,5
200,21 0,3 2
30000 4,5 1
mylist = [['10', '3', '', ''],
['200', '4000,222', '3', '1,5'],
['200,21', '', '0,3', '2'],
['30000', '4,5', '1', '']]
What I want is to align this list considering the comma:
expected result:
mylist = [[' 10 ', ' 3 ', ' ', ' '],
[' 200 ', '4000,222', '3 ', '1,5'],
[' 200,21', ' ', '0,3', '2 '],
['30000 ', ' 4,5 ', '1 ', ' ']]
I tried to turn the list:
mynewlist = list(zip(*mylist))
and to find the longest part after the comma in every sublist:
for m in mynewlist:
max([x[::-1].find(',') for x in m]
and to use rjust and ljust but I don't know how to ljust after a comma and rjust before the comma, both in the same string.
How can I resolve this without using format()?
(I want to align with ljust and rjust)
Here's another approach that currently does the trick. Unfortunately, I can't see any simple way to make this work, maybe due to the time :-)
Either way, I'll explain it. r is the result list created before hand.
r = [[] for i in range(4)]
Then we loop through the values and also grab an index with enumerate:
for ind1, vals in enumerate(zip(*mylist)):
Inside the loop we grab the max length of the decimal digits present and the max length of the word (the word w/o the decimal digits):
l = max(len(v.partition(',')[2]) for v in vals) + 1
mw = max(len(v if ',' not in v else v.split(',')[0]) for v in vals)
Now we go through the values inside the tuple vals and build our results (yup, can't currently think of a way to avoid this nesting).
for ind2, v in enumerate(vals):
If it contains a comma, it should be formatted differently. Specifically, we rjust it based on the max length of a word mw and then add the decimal digits and any white-space needed:
if ',' in v:
n, d = v.split(',')
v = "".join((n.rjust(mw),',', d, " " * (l - 1 - len(d))))
In the opposite case, we simply .rjust and then add whitespace:
else:
v = "".join((v.rjust(mw) + " " * l))
finally, we append to r.
r[ind1].append(v)
All together:
r = [[] for i in range(4)]
for ind1, vals in enumerate(zip(*mylist)):
l = max(len(v.partition(',')[2]) for v in vals) + 1
mw = max(len(v if ',' not in v else v.split(',')[0]) for v in vals)
for ind2, v in enumerate(vals):
if ',' in v:
n, d = v.split(',')
v = "".join((n.rjust(mw),',', d, " " * (l - 1 - len(d))))
else:
v = "".join((v.rjust(mw) + " " * l))
r[ind1].append(v)
Now, we can print it out:
>>> print(*map(list,zip(*r)), sep='\n)
[' 10 ', ' 3 ', ' ', ' ']
[' 200 ', '4000,222', '3 ', '1,5']
[' 200,21', ' ', '0,3', '2 ']
['30000 ', ' 4,5 ', '1 ', ' ']
Here's a bit different solution that doesn't transpose my_list but instead iterates over it twice. On the first pass it generates a list of tuples, one for each column. Each tuple is a pair of numbers where first number is length before comma and second number is length of comma & everything following it. For example '4000,222' results to (4, 4). On the second pass it formats the data based on the formatting info generated on first pass.
from functools import reduce
mylist = [['10', '3', '', ''],
['200', '4000,222', '3', '1,5'],
['200,21', '', '0,3', '2'],
['30000', '4,5', '1', '']]
# Return tuple (left part length, right part length) for given string
def part_lengths(s):
left, sep, right = s.partition(',')
return len(left), len(sep) + len(right)
# Return string formatted based on part lengths
def format(s, first, second):
left, sep, right = s.partition(',')
return left.rjust(first) + sep + right.ljust(second - len(sep))
# Generator yielding part lengths row by row
parts = ((part_lengths(c) for c in r) for r in mylist)
# Combine part lengths to find maximum for each column
# For example data it looks like this: [[5, 3], [4, 4], [1, 2], [1, 2]]
sizes = reduce(lambda x, y: [[max(z) for z in zip(a, b)] for a, b in zip(x, y)], parts)
# Format result based on part lengths
res = [[format(c, *p) for c, p in zip(r, sizes)] for r in mylist]
print(*res, sep='\n')
Output:
[' 10 ', ' 3 ', ' ', ' ']
[' 200 ', '4000,222', '3 ', '1,5']
[' 200,21', ' ', '0,3', '2 ']
['30000 ', ' 4,5 ', '1 ', ' ']
This works for python 2 and 3. I didn't use ljust or rjust though, i just added as many spaces before and after the number as are missing to the maximum sized number in the column:
mylist = [['10', '3', '', ''],
['200', '4000,222', '3', '1,5'],
['200,21', '', '0,3', '2'],
['30000', '4,5', '1', '']]
transposed = list(zip(*mylist))
sizes = [[(x.index(",") if "," in x else len(x), len(x) - x.index(",") if "," in x else 0)
for x in l] for l in transposed]
maxima = [(max([x[0] for x in l]), max([x[1] for x in l])) for l in sizes]
withspaces = [
[' ' * (maxima[i][0] - sizes[i][j][0]) + number + ' ' * (maxima[i][1] - sizes[i][j][1])
for j, number in enumerate(l)] for i, l in enumerate(transposed)]
result = list(zip(*withspaces))
Printing the result in python3:
>>> print(*result, sep='\n')
(' 10 ', ' 3 ', ' ', ' ')
(' 200 ', '4000,222', '3 ', '1,5')
(' 200,21', ' ', '0,3', '2 ')
('30000 ', ' 4,5 ', '1 ', ' ')

tuple to string formatting

After a certain calculation i am getting an output like:
(' ','donor',' ','distance')
(' ','ARG','A','43',' ','3.55')
(' ','SOD','B',93', ' ','4.775')
(' ','URX','C',33', ' ','3.55')
while i was intending to get like:
donor distance
ARG A 43 3.55
SOD B 93 4.77
URX C 33 3.55
the thing what i am getting is a tuple, but i am very confused on how to make this tuple into a well formatted look as per my desire.
Please give some idea...
thank you.
Use str.join() on each tuple:
' '.join(your_tuple)
before printing.
If your data looks like this
data = [
(' ', 'donor', ' ', 'distance'),
(' ', 'ARG', 'A', '43', ' ', '3.55'),
(' ', 'SOD', 'B', '93', ' ', '4.775'),
(' ', 'URX', 'C', '33', ' ', '3.55')
]
Then you can just
print '\n'.join(map(' '.join, data))
You can use a for-loop and str.join:
lis = [
(' ','donor',' ','distance'),
(' ','ARG','A','43',' ','3.55'),
(' ','SOD','B','93', ' ','4.775'),
(' ','URX','C','33', ' ','3.55')
]
for item in lis:
print " ".join(item)
Output:
donor distance
ARG A 43 3.55
SOD B 93 4.775
URX C 33 3.55
It sounds like you want to use format strings. For example, assuming that you are not storing padding strings in your items:
print "{0} {1} {2} {3:>10.2f}".format(*item)
You can specify the exact format (including width and alignment) of each field of the record in the format string. In this example, the fourth string is right-aligned to fit into 10 characters, with 2 digits displayed to the right of the decimal point.
Example using your data:
>>> x = ((' ','ARG','A','43',' ','3.55'),(' ','SOD','B','93', ' ','4.775'),(' ','URX','C','33', ' ','3.55'))
>>> f = "{0:3s}{1:1s}{2:2s} {3:>10.3f}"
>>> for item in x: print f.format(item[1], item[2], item[3], float(item[5]))
...
ARGA43 3.550
SODB93 4.775
URXC33 3.550

Categories