Regarding the behavior of string padding using str.ljust in python - python

I have a use case in which I am setting the labels for
matplotlib.colorbar
and I want to distribute the description evenly using the code below
temp = ""
for section in range(total_section_len):
temp.ljust(60//(total_section_len * 5))
temp += "stream 0"
temp.ljust(60//(total_section_len * 5))
temp += "stream 1"
temp.ljust(60//(total_section_len * 5))
print temp
I am expecting something like
" stream0 stream1 "
but instead what I get is
"stream0stream1"
Why does
str.ljust
behavior in such fashion?
Thanks

The parameter to ljust is the "minimum" length of the string. It will only pad with spaces if this value is longer than the current length of the string.
Unless total_section_len is only 1, 60//(total_section_len * 5) will be less than the length of your strings, so no padding is taking place.
Also, it doesn't modify the string in place as strings are immutable. You need to use the return value, i.e. temp = temp.ljust(...)

ljust(width, fillchr) returns a left justified string by width specified and fills it with fillchr, whose default is empty space.
So
a = ''
b = a.ljust(10)
print(a)
>>> ''
print(b)
>>> ' '
It returns a left justified string to variable b and variable a is not modified.
and So this piece of code should work:
a = ''
a += ''.ljust(10) + 'stream0'
a += ''.ljust(10) + 'stream1'
print(a)
>>> ' stream0 stream1'

Related

I need to make a map (literally not the function) in which i should print a 20*20 map with pluses(+) but mark some of the coordinates

I need to make a map (literally not the function) in which I should print a 20*20 map with pluses(+) but mark some of the coordinates which are inputs with #
first, I ask how many coordinates the user wants to give me
second, I get the coordinates and print the map with said coordinates marked on it
e.g. :
input:
2
(10,11)
(10,12)
output :
# + + + + + + +
c= ( 10 , 11)
a=c\[0\]
b=c\[1\]
for y in range (20):
if y == a :
for x in range (20):
if x==b:
print('#' , end= ' ')
else :
print('+' , end = ' ')
else:
for x in range (20):
print('+' , end= ' ')
print()
This should do the trick:
from typing import List
ROWS = COLS = 20
# Define a function to create rows
def create_row(num_cols: int, default_char: str = "+")->List[str]:
return [default_char for i in range(num_cols)]
# Define a function to create the map using function for row creation
# Make the arguments keyword only, it does matter which int is rows, which is cols (as long as your matrix is not necessarily n x n, but n x m (in general)
def create_map(*,rows:int, cols:int, default_char:str = "+")->List[List[str]]:
char_map = []
for i in range(rows):
char_map.append(create_row(num_cols=cols, default_char=default_char))
return char_map
# Create the map
plus_map = create_map(rows=ROWS, cols=COLS)
# This is just an example of user input coordinates
hashtag_coordinates = [(1,2), (3,4)] # I'll leave this part for you, populate it as you wish (using input function or whatever, file reading etc.), just keep it in a form of list of tuples
# Substitute the default "+" with "#" for user input coordinates
for x,y in hashtag_coordinates:
plus_map[x][y] = "#"
# Print your map row by row
for r in plus_map:
print(r)
add docstrings, docstrings are cool...
think of good variable names, those above are ok'ish, probably can be better / more descriptive
[OPTIONAL] wrap the substitution of "+" into "#" in another function
[OPTIONAL] write your own print function if the output is not what you expect it to be
do not name variables "a", "b", cause that means nothing
if you repeat the same magic number in a few places like in range(20) and so on name it, all caps if it's a constant

specific characters printing with Python

given a string as shown below,
"[xyx],[abc].[cfd],[abc].[dgr],[abc]"
how to print it like shown below ?
1.[xyz]
2.[cfd]
3.[dgr]
The original string will always maintain the above-mentioned format.
I did not realize you had periods and commas... that adds a bit of trickery. You have to split on the periods too
I would use something like this...
list_to_parse = "[xyx],[abc].[cfd],[abc].[dgr],[abc]"
count = 0
for i in list_to_parse.split('.'):
for j in i.split(','):
string = str(count + 1) + "." + j
if string:
count += 1
print(string)
string = None
Another option is split on the left bracket, and then just re-add it with enumerate - then strip commas and periods - this method is also probably a tiny bit faster, as it's not a loop inside a loop
list_to_parse = "[xyx],[abc].[cfd],[abc].[dgr],[abc]"
for index, i in enumerate(list.split('[')):
if i:
print(str(index) + ".[" + i.rstrip(',.'))
also strip is really "what characters to remove" not a specific pattern. so you can add any characters you want removed from the right, and it will work through the list until it hits a character it can't remove. there is also lstrip() and strip()
string manipulation can always get tricky, so pay attention. as this will output a blank first object, so index zero isn't printed etc... always practice and learn your needs :D
You can use split() function:
a = "[xyx],[abc].[cfd],[abc].[dgr],[abc]"
desired_strings = [i.split(',')[0] for i in a.split('.')]
for i,string in enumerate(desired_strings):
print(f"{i+1}.{string}")
This is just a fun way to solve it:
lst = "[xyx],[abc].[cfd],[abc].[dgr],[abc]"
count = 1
var = 1
for char in range(0, len(lst), 6):
if var % 2:
print(f"{count}.{lst[char:char + 5]}")
count += 1
var += 1
output:
1.[xyx]
2.[cfd]
3.[dgr]
explanation : "[" appears in these indexes: 0, 6, 12, etc. var is for skipping the next pair. count is the counting variable.
Here we can squeeze the above code using list comprehension and slicing instead of those flag variables. It's now more Pythonic:
lst = "[xyx],[abc].[cfd],[abc].[dgr],[abc]"
lst = [lst[i:i+5] for i in range(0, len(lst), 6)][::2]
res = (f"{i}.{item}" for i, item in enumerate(lst, 1))
print("\n".join(res))
You can use RegEx:
import regex as re
pattern=r"(\[[a-zA-Z]*\])\,\[[a-zA-Z]*\]\.?"
results=re.findall(pattern, '[xyx],[abc].[cfd],[abc].[dgr],[abc]')
print(results)
Using re.findall:
import re
s = "[xyx],[abc].[cfd],[abc].[dgr],[abc]"
print('\n'.join(f'{i+1}.{x}' for i,x in
enumerate(re.findall(r'(\[[^]]+\])(?=,)', s))))
Output:
1.[xyx]
2.[cfd]
3.[dgr]

Add border(*) for rectangular matrix of characters

I am trying to add * as borders for rectangular matrix of characters. For example,
["abc", "ded"]
should return
["*****","*abc*","*ded*","*****"]
What I did was I create a new matrix with 2 more rows and columns than the original one, and I filled it with *. So the problem is when I am replacing the * inside with original letters, I have an error of out of index. I couldn't quite figure out why?
def addBorder(picture):
m=len(picture) #number of rows
n=len(picture[0]) #num of columns
newpic=[['*'*(n+2)]for y in range(m+2)]
for x in range(1,m+1):
for y in range(1,n+1):
newpic[x][y]=picture[x-1][y-1]
return newpic
Strings are immutable, so you can not "edit" single characters inside them bey indexing into them - thats what you are trying in your for x ..: for y: ... loop.
To keep most of your code, you can change it to:
def addBorder(picture):
m=len(picture) #number of rows
n=len(picture[0]) #num of columns
newpic=[['*'*(n+2)]for y in range(m+2)]
for idx,text in enumerate(picture): # get text and index here
newpic[idx+1] = '*' + text+ '*' # change text in +1 row in target list
return newpic
print(addBorder( ["abc", "ded"]))
Output:
[['*****'], '*abc*', '*ded*', ['*****']]
Changing more code:
def addBorder(picture):
# slightly more compley length computation, will work for ragged text as well
maxL = max(len(x) for x in picture)
patt = "*{:<"+str(maxL)+"}*" # left justified by maxL , see link below
rv = []
rv.append('*'*(maxL+2)) # top border
for t in picture:
rv.append(patt.format(t)) # text + adornment
rv.append('*'*(maxL+2)) # bottom border
return rv
print(addBorder( ["abc", "defgh","i"]))
Output:
['*******',
'*abc *',
'*defgh*',
'*i *',
'*******']
Link: string format mini language
Your out of index error message is somewhat misleading - you are inside the bounds of your lists, but you are trying to manipulate the string - I would have thought a 'str' object does not support item assignment would be more appropriate here...
Edit: see Azats answer for why your error occurs - I left text in so his post does not loose its reference.
If I understood correctly, you are trying to
Create filled with * newpic.
Modify newpic[1:-1] and replace * (excluding borders) with picture elements contents.
This approach has a problem with str objects immutability, but even if they were mutable this seems to be inefficient to create **...** string and then mutate them character-by-character.
About your error: it isn't misleading as stated by #PatrickArtner, it originates from a typo (I guess), because you are creating lists of list of str:
>>> m = 3
>>> n = 4
>>> [['*'*(n+2)]for y in range(m+2)]
[['******'], ['******'], ['******'], ['******'], ['******']]
so when y equals to 1 you are getting this error (because each of newpic sublists has single str element inside of them).
Instead of trying to modify list of str we can create list and append str to it like
def addBorder(picture,
border_size=1):
max_substring_length = max(map(len, picture))
# top border
result = ['*' * (max_substring_length + border_size * 2)]
for substring in picture:
diff = max_substring_length - len(substring)
additional_length, extra = divmod(diff, 2)
# handling non-equivalent case
prepend = '*' * (border_size + additional_length + extra)
append = '*' * (border_size + additional_length)
result.append(prepend + substring + append)
# bottom border
result.append('*' * (max_substring_length + border_size * 2))
return result
Test
for string in addBorder(["abc", "ded"]):
print(string)
gives us
*****
*abc*
*ded*
*****
non-equivalent by size case
for string in addBorder(["abc", "deed"]):
print(string)
gives us
******
**abc*
*deed*
******
In C++
std::vector<std::string> addBorder(std::vector<std::string> picture)
{
for(auto &p: picture)
p = "*"+p+"*";
picture.insert(picture.begin(), string(picture[0].size(),'*'));
picture.insert(picture.end(), string(picture[0].size(),'*'));
return picture;
}

ArcGIS:Python - Adding Commas to a String

In ArcGIS I have intersected a large number of zonal polygons with another set and recorded the original zone IDs and the data they are connected with. However the strings that are created are one long list of numbers ranging from 11 to 77 (each ID is 11 characters long). I am looking to add a "," between each one making, it easier to read and export later as a .csv file. To do this I wrote this code:
def StringSplit(StrO,X):
StrN = StrO #Recording original string
StrLen = len(StrN)
BStr = StrLen/X #How many segments are inside of one string
StrC = BStr - 1 #How many times it should loop
if StrC > 0:
while StrC > 1:
StrN = StrN[ :((X * StrC) + 1)] + "," + StrN[(X * StrC): ]
StrC = StrC - 1
while StrC == 1:
StrN = StrN[:X+1] + "," + StrN[(X*StrC):]
StrC = 0
while StrC == 0:
return StrN
else:
return StrN
The main issue is how it has to step through multiple rows (76) with various lengths (11 -> 77). I got the last parts to work, just not the internal loop as it returns an error or incorrect outputs for strings longer than 22 characters.
Thus right now:
1. 01234567890 returns 01234567890
2. 0123456789001234567890 returns 01234567890,01234567890
3. 012345678900123456789001234567890 returns either: Error or ,, or even ,,01234567890
I know it is probably something pretty simple I am missing, but I can't seem remember what it is...
It can be easily done by regex.
those ........... are 11 dots for give split for every 11th char.
you can use pandas to create csv from the array output
Code:
import re
x = re.findall('...........', '01234567890012345678900123456789001234567890')
print(x)
myString = ",".join(x)
print(myString)
output:
['01234567890', '01234567890', '01234567890', '01234567890']
01234567890,01234567890,01234567890,01234567890
for the sake of simplicity you can do this
code:
x = ",".join(re.findall('...........', '01234567890012345678900123456789001234567890'))
print(x)
Don't make the loops by yourself, use python libraries or builtins, it will be easier. For example :
def StringSplit(StrO,X):
substring_starts = range(0, len(StrO), X)
substrings = (StrO[start:start + X] for start in substring_starts)
return ','.join(substrings)
string = '1234567890ABCDE'
print(StringSplit(string, 5))
# '12345,67890,ABCDE'

I want my parser to return a list of strings, but it returns a blank list

I have a parser that reads in a long octet string, and I want it to print out smaller strings based on the parsing details. It reads in a hexstring which is as follows
The string will be in a format like so:
01046574683001000004677265300000000266010000
The format of the interface contained in the hex is like so:
version:length_of_name:name:op_status:priority:reserved_byte
==
01:04:65746830:01:00:00
== (when converted from hex)
01:04:eth0:01:00:00
^ this is 1 segment of the string , represents eth0 (I inserted the : to make it easier to read). At the minute, however, my code returns a blank list, and I don't know why. Can somebody help me please!
def octetChop(long_hexstring, from_ssh_):
startpoint_of_interface_def=0
# As of 14/8/13 , the network operator has not been implemented
network_operator_implemented=False
version_has_been_read = False
position_of_interface=0
chopped_octet_list = []
#This while loop moves through the string of the interface, based on the full length of the container
try:
while startpoint_of_interface_def < len(long_hexstring):
if version_has_been_read == True:
pass
else:
if startpoint_of_interface_def == 0:
startpoint_of_interface_def = startpoint_of_interface_def + 2
version_has_been_read = True
endpoint_of_interface_def = startpoint_of_interface_def+2
length_of_interface_name = long_hexstring[startpoint_of_interface_def:endpoint_of_interface_def]
length_of_interface_name_in_bytes = int(length_of_interface_name) * 2 #multiply by 2 because its calculating bytes
end_of_interface_name_point = endpoint_of_interface_def + length_of_interface_name_in_bytes
hex_name = long_hexstring[endpoint_of_interface_def:end_of_interface_name_point]
text_name = hex_name.decode("hex")
print "the text_name is " + text_name
operational_status_hex = long_hexstring[end_of_interface_name_point:end_of_interface_name_point+2]
startpoint_of_priority = end_of_interface_name_point+2
priority_hex = long_hexstring[startpoint_of_priority:startpoint_of_priority+2]
#Skip the reserved byte
network_operator_length_startpoint = startpoint_of_priority+4
single_interface_string = long_hexstring[startpoint_of_interface_def:startpoint_of_priority+4]
print single_interface_string + " is chopped from the octet string"# - keep for possible debugging
startpoint_of_interface_def = startpoint_of_priority+4
if network_operator_implemented == True:
network_operator_length = long_hexstring[network_operator_length_startpoint:network_operator_length_startpoint+2]
network_operator_length = int(network_operator_length) * 2
network_operator_start_point = network_operator_length_startpoint+2
network_operator_end_point = network_operator_start_point + network_operator_length
network_operator = long_hexstring[network_operator_start_point:network_operator_end_point]
#
single_interface_string = long_hexstring[startpoint_of_interface_def:network_operator_end_point]
#set the next startpoint if there is one
startpoint_of_interface_def = network_operator_end_point+1
else:
self.network_operator = None
print single_interface_string + " is chopped from the octet string"# - keep for possible debugging
#This is where each individual interface is stored, in a list for comparison.
chopped_octet_list.append(single_interface_string)
finally:
return chopped_octet_list
The reason your code is returning a blank list is the following: In this line:
else:
self.network_operator = None
self is not defined so you get a NameError exception. This means that the try jumps directly to the the finally clause without ever executing the part where you:
chopped_octet_list.append(single_interface_string)
As a consequence the list remains empty. In any case the code is overly complicated for such a task, I would follow one of the other answers.
I hope I got you right. You got a hex-string which contains various interface definition. Inside each interface definition the second octet describes the length of the name of the interface.
Lets say the string contains the interfaces eth0 and eth01 and looks like this (length 4 for eth0 and length 5 for eth01):
01046574683001000001056574683031010000
Then you can split it like this:
def splitIt (s):
tokens = []
while s:
length = int (s [2:4], 16) * 2 + 10 #name length * 2 + 10 digits for rest
tokens.append (s [:length] )
s = s [length:]
return tokens
This yields:
['010465746830010000', '01056574683031010000']
To add onto Hyperboreus's answer, here's a simple way to parse the interface strings once you split them:
def parse(s):
version = int(s[:2], 16)
name_len = int(s[2:4], 16)
name_end = 4 + name_len * 2
name = s[4:name_end].decode('hex')
op_status = int(s[name_end:name_end+2], 16)
priority = int(s[name_end+2:name_end+4], 16)
reserved = s[name_end+4:name_end+6]
return version, name_len, name, op_status, priority, reserved
Here's the output:
>>> parse('010465746830010000')
(1, 4, 'eth0', 1, 0, '00')
Check if the following helps. Call parse method below and pass a string stream into it, then iterate to get card infos (hope I got you right :)) parse will return you tuple(s) of the desired info.
>>> def getbytes(hs):
"""Returns a generator of bytes from a hex string"""
return (int(hs[i:i+2],16) for i in range(0,len(hs)-1,2))
>>> def get_single_card_info(g):
"""Fetches a single card info from a byte generator"""
v = g.next()
l = g.next()
name = "".join(chr(x) for x in map(lambda y: y.next(),[g]*l))
return (str(v),name,g.next(),g.next(),g.next())
>>> def parse(hs):
"""Parses a hex string stream and returns a generator of card infos"""
bs = getbytes(hs)
while True:
yield get_single_card_info(bs)
>>> c = 1
>>> for card in parse("01046574683001000001056574683031010000"):
print "Card:{0} -> Version:{1}, Id:{2}, Op_stat:{3}, priority:{4}, reserved:{5} bytes".format(c,*card)
c = c + 1
Card:1 -> Version:1, Id:eth0, Op_stat:1, priority:0, reserved:0 bytes
Card:2 -> Version:1, Id:eth01, Op_stat:1, priority:0, reserved:0 bytes
Pyparsing includes a built-in expression for parsing a counted array of elements, so this would take care of your 'name' field nicely. Here's the whole parser:
from pyparsing import Word,hexnums,countedArray
# read in 2 hex digits, convert to integer at parse time
octet = Word(hexnums,exact=2).setParseAction(lambda t:int(t[0],16))
# read in a counted array of octets, convert to string
nameExpr = countedArray(octet, intExpr=octet)
nameExpr.setParseAction(lambda t: ''.join(map(chr,t[0])))
# define record expression, with named results
recordExpr = (octet('version') + nameExpr('name') + octet('op_status') +
octet('priority') #+ octet('reserved'))
Parsing your sample:
sample = "01046574683001000004677265300000000266010000"
for rec in recordExpr.searchString(sample):
print rec.dump()
Gives:
[1, 'eth0', 1, 0]
- name: eth0
- op_status: 1
- priority: 0
- version: 1
[0, 'gre0', 0, 0]
- name: gre0
- op_status: 0
- priority: 0
- version: 0
[0, 'f\x01', 0, 0]
- name: f
- op_status: 0
- priority: 0
- version: 0
The dump() method shows results names that you can use to access the individually parsed bits, like rec.name or rec.version.
(I commented out the reserved byte, else the second entry wouldn't parse correctly. Also, the third entry contains a name with a \x01 byte.)

Categories

Resources