Explain [:0] in Python - python

Ok, so before I get flamed for not RTFM, I understand that [:0] in my case of:
s ="itsastring"
newS= []
newS[:0] = s
ends up converting s to a list through slicing. This is my end goal, but coming from a Java background, I don't fully understand the "0" part in "[:0] and syntactically why it's placed there (I know it roughly means increase by 0). Finally, how does Python know that I want to have each char of s be an element based on this syntax? I want to understand it so I can remember it more clearly.

If S and T are sequences, S[a:b] = T will replace the subsequence from index a to b-1 of S by the elements of T.
If a == b, it will act as a simple insertion.
And S[:0] is the same thing as S[0:0] : so it's a simple insertion at the front.
s = [11,22,33,44,55,66,77]
s[3:3] = [1,2,3] # insertion at position 3
print( s )
s = [11,22,33,44,55,66,77]
s[3:4] = [1,2,3] # deletion of element at position 3, and then insertion
print( s )
s = [11,22,33,44,55,66,77]
s[3:6] = [1,2,3] # deletion of elements from position 3 to 5, and then insertion
print( s )
s = [11,22,33,44,55,66,77]
s[:] = [1,2,3] # deletion of all elements, and then insertion : whole replacement
print( s )
output:
[11, 22, 33, 1, 2, 3, 44, 55, 66, 77]
[11, 22, 33, 1, 2, 3, 55, 66, 77]
[11, 22, 33, 1, 2, 3, 77]
[1, 2, 3]

Hope it helps:
s ="itsastring"
#if you add any variable in left side then python will start slicing from there
#and slice to one less than last index assigned on right side
newS = s[:]

So the [:0] means slice the string from the beggining to the 0 (or again the first) element in the s string and the result is nothing. By default the slicing is done from the first element to the last or print(s[0:]) is the same as print(s).
I suggest you try a loop through the whole string like this:
s = [x for x in "itsastring"]
print(s)
# result
['i', 't', 's', 'a', 's', 't', 'r', 'i', 'n', 'g']

Related

How shuffle and then unshuffle a bytearray, given a key as seed

I am trying to create an encryption system, and for that I will be taking a bytearray, and I intend to use a
random.Random(seed).shuffle(bytearray)
function to encrypt the information.
I'm having trouble reversing this process for the deencryption, i tried something like (didnt work) :
random.Random(1/seed).shuffle(encryptedbytearray)
Is there anyway to do this?
Shuffle a sorted range, so that we can match the shuffled indicies to the unshuffled indicies.
x = list(range(len(s)))
random.Random(seed).shuffle(x)
For a seed of 12345, this produces [14, 15, 12, 3, 24, 16, 7, 22, 10, 2, 19, 4, 20, 17, 1, 21, 5, 25, 18, 8, 6, 11, 9, 0, 23, 13]. This indicates that the value in the 0th index in the shuffled list is actually in the 14th index of the unshuffled list, the 1st index is actually the 15th unshuffled, etc.
Then match each shuffled index to the shuffled value, and then sort (based on the index) back into their unshuffled positions.
unshuffled = bytearray(c for i, c in sorted(zip(x, s)))
print(unshuffled)
Full example:
import random
# setup
s = bytearray(b"abcdefghijklmnopqrstuvxwyz")
seed = 12345
random.Random(seed).shuffle(s)
# shuffle a sorted range, so that we can match the shuffled indicies to the unshuffled indicies
x = list(range(len(s)))
random.Random(seed).shuffle(x)
# match each shuffled index to the shuffled value, and then sort (based on the index) back into their unshuffled positions
unshuffled = bytearray(c for i, c in sorted(zip(x, s)))
print(unshuffled)
The process detailed above should work for any shuffled sequence (eg. lists), not just bytearrays.
Here is a more detailed explanation of this process on crypto.se with a lot more math involved.
You need to use the same seed to shuffle indexes so that you can backtrack the original positions. (enumerate will allow you to avoid sorting that mapping)
import random
def encrypt(decrypted,seed=4):
encrypted = decrypted.copy()
random.Random(seed).shuffle(encrypted)
return encrypted
def decrypt(encrypted,seed=4):
decrypted = encrypted.copy()
indexes = list(range(len(encrypted)))
random.Random(seed).shuffle(indexes)
for e,d in enumerate(indexes):
decrypted[d] = encrypted[e]
return decrypted
Sample run (using list of characters but it will work for bytearrays or any other type of lists):
clearText = list('ABCDE')
encryptedText = encrypt(clearText)
print(encryptedText)
['D', 'E', 'A', 'C', 'B']
decryptedText = decrypt(encryptedText)
print(decryptedText)
['A', 'B', 'C', 'D', 'E']
If you want the functions to work "in place" directly on the array (instead of returning a value), you can writ them like this:
def encrypt(decrypted,seed=4):
random.Random(seed).shuffle(encrypted)
def decrypt(encrypted,seed=4):
before = encrypted.copy()
indexes = list(range(len(encrypted)))
random.Random(seed).shuffle(indexes)
for e,d in enumerate(indexes):
encrypted[d] = before[e]

What is the fastest way to convert a dictionary frequency to list in Python?

I have dictionary frequency as follows:
freq = {'a': 1, 'b': 2, 'c': 3}
It simply means that I have one a's, twob's, and three c's.
I would like to convert it into a complete list:
lst = ['a', 'b', 'b', 'c', 'c', 'c']
What is the fastest way (time-efficient) or most compact way (space-efficient) to do so?
Yes, but only if the items are (or can be represented as) integers, and if the number of items between the smallest and largest item is sufficiently close to the difference between the two, in which case you can use bucket sort, resulting in O(n) time complexity, where n is the difference between the smallest and the largest item. This would be more efficient than using other sorting algorithms, with an average time complexity of O(n log n).
In the case of List = [1, 4, 5, 2, 6, 7, 9, 3] as it is in your question, it is indeed more efficient to use bucket sort when it is known that 1 is the smallest item and 9 is the largest item, since only 8 is missing between the range. The following example uses collections.Counter to account for the possibility that there can be duplicates in the input list:
from collections import Counter
counts = Counter(List)
print(list(Counter({i: counts[i] for i in range(1, 10)}).elements()))
This outputs:
[1, 2, 3, 4, 5, 6, 7, 9]
Let's break this into two O(N) passes: one to catalog the numbers, and one to create the sorted list. I updated the variable names; List is an especially bad choice, given the built-in type list. I also added 10 to each value, so you can see how the low-end offset works.
coll = [11, 14, 15, 12, 16, 17, 19, 13]
last = 19
first = 11
offset = first
size = last-first+1
# Recognize all values in a dense "array"
need = [False] * size
for item in coll:
need[item - offset] = True
# Iterate again in numerical order; for each True value, add that item to the new list
sorted_list = [idx + offset for idx, needed_flag in enumerate(need) if needed_flag]
print(sorted_list)
OUTPUT:
[11, 12, 13, 14, 15, 16, 17, 19]
The most compact way I usually use is list comprehension -
lst = ['a', 'b', 'b', 'c', 'c', 'c']
freq = {i: 0 for i in lst}
for i in lst: freq[i] += 1
Space complexity - O(n)
Time complexity - O(n)

how to find a continuous string using python

Given a string (e.g., jaghiuuabc ), i want to find a string with subsequent letter in alphabet
here is my code
import string
alpha = list(string.ascii_lowercase)
s = 'jaghiuuabc'
a = []
for i in range(len(alpha)-1):
for j in range(len(s)-1)
if s[j] in alpha[i]:
a.append(s[j])
print(a)
There's a nice example in the Python 2.6 itertools docs that shows how to find consecutive sequences. To quote:
Find runs of consecutive numbers using groupby. The key to the
solution is differencing with a range so that consecutive numbers all
appear in same group.
For some strange reason, that example is not in the later versions of the docs. That code works for sequences of numbers, the code below shows how to adapt it to work on letters.
from itertools import groupby
s = 'jaghiuuabc'
def keyfunc(t):
''' Subtract the character's index in the string
from its Unicode codepoint number.
'''
i, c = t
return ord(c) - i
a = []
for k, g in groupby(enumerate(s), key=keyfunc):
# Extract the chars from the (index, char) tuples in the group
seq = [t[1] for t in g]
if len(seq) > 1:
a.append(''.join(seq))
print(a)
output
['ghi', 'abc']
How it works
The heart of this code is
groupby(enumerate(s), key=keyfunc)
enumerate(s) generates tuples containing the index number and character for each character in s. For example:
s = 'ABCEF'
for t in enumerate(s):
print(t)
output
(0, 'A')
(1, 'B')
(2, 'C')
(3, 'E')
(4, 'F')
groupby takes items from a sequence or iterator and gathers adjacent equal items together into groups. By default, it simply compares the values of the items to see if they're equal. But you can also give it a key function. When you do that, it passes each item to the key function and uses the result returned by that key function for its equality test.
Here's a simple example. First, we define a function div_by_10 that divides a number by 10, using integer division. This basically gets rid of the last digit in the number.
def div_by_10(n):
return n // 10
a = [2, 5, 10, 13, 17, 21, 22, 29, 33, 35]
b = [div_by_10(u) for u in a]
print(a)
print(b)
output
[2, 5, 10, 13, 17, 21, 22, 29, 33, 35]
[0, 0, 1, 1, 1, 2, 2, 2, 3, 3]
So if we use div_by_10 as the key function to groupby it will ignore the last digit in each number and thus it will group adjacent numbers together if they only differ in the last digit.
from itertools import groupby
def div_by_10(n):
return n // 10
a = [2, 5, 10, 13, 17, 21, 22, 29, 33, 35]
print(a)
for key, group in groupby(a, key=div_by_10):
print(key, list(group))
output
[2, 5, 10, 13, 17, 21, 22, 29, 33, 35]
0 [2, 5]
1 [10, 13, 17]
2 [21, 22, 29]
3 [33, 35]
My keyfunc receives a (index_number, character) tuple and subtracts that index_number from the character's code number and returns the result. Let's see what that does with my earlier example of 'ABCEF':
def keyfunc(t):
i, c = t
return ord(c) - i
for t in enumerate('ABCEF'):
print(t, keyfunc(t))
output
(0, 'A') 65
(1, 'B') 65
(2, 'C') 65
(3, 'E') 66
(4, 'F') 66
The code number for 'A' is 65, the code number for 'B' is 66, the code number for 'C' is 67, etc. So when we subtract the index from the code number for each of 'A', 'B', and 'C' we get 65. But we skipped over 'D' so when we do the subtractions for 'E' and 'F' we get 66. And that's how groupby can put 'A', 'B', & 'C' in one group and 'E' & 'F' in the next group.
This can be tricky stuff. Don't expect to understand it all completely straight away. But if you do some experiments yourself I'm sure it will gradually sink in. ;)
Just for fun, here's the unreadable multiply-nested list comprehension version of that code. ;)
print([z for _, g in groupby(enumerate(s),lambda t:ord(t[1])-t[0])for z in[''.join([*zip(*g)][1])]if len(z)>1])
Here's another version which was inspired by Amit Tripathi's answer. This one doesn't use any imports because it does the grouping manually. prev contains the codepoint number of the previous character. We initialize prev to -2 so that the first time the if i != prev + 1 test is performed it's guaranteed to be true because the smallest possible value of ord(ch) is zero, so a new empty list will be added to groups.
s = 'jaghiuuabcxyzq'
prev, groups = -2, []
for ch in s:
i = ord(ch)
if i != prev + 1:
groups.append([])
groups[-1].append(ch)
prev = i
print(groups)
a = [''.join(u) for u in groups if len(u) > 1]
print(a)
output
[['j'], ['a'], ['g', 'h', 'i'], ['u'], ['u'], ['a', 'b', 'c'], ['x', 'y', 'z'], ['q']]
['ghi', 'abc', 'xyz']
This can be done easily with pure Python
Python 3(should work with Python 2 also) implementation. A simple 8 liner
s = 'jaghiuuabc'
prev, counter, dct = None, 0, dict()
for i in s:
if prev is not None:
if not chr(ord(prev) + 1) == i:
counter += 1
prev = i
dct.setdefault(counter, []).append(prev)
[''.join(dct[d]) for d in dct if len(dct[d]) > 1]
Out[51]: ['ghi', 'abc']
ord converts char to equivalent ASCII number
chr converts a number to equivalent ASCII char
setdefault set default value as list if a key doesn't exists
What about some recursion without any external module ?
a='jaghiuuabc'
import string
alpha = list(string.ascii_lowercase)
def trech(string_1,chr_list,new_string):
final_list=[]
if not string_1:
return 0
else:
for chunk in range(0,len(string_1),chr_list):
for sub_chunk in range(2,len(string_1)+1):
if string_1[chunk:chunk + sub_chunk] in ["".join(alpha[i:i + sub_chunk]) for i in range(0, len(alpha), 1)]:
final_list.append(string_1[chunk:chunk + sub_chunk])
if final_list:
print(final_list)
return trech(string_1[1:],chr_list-1,new_string)
print(trech(a,len(a),alpha))
output:
['gh', 'ghi']
['hi']
['ab', 'abc']
['bc']
0

How to apply a dict in python to a string as opposed to a single letter

I am trying to output the alphabetical values of a user entered string, I have created a dict and this process works, but only with one letter.
If I try entering more than one letter, it returns a KeyError: (string I entered)
If I try creating a list of the string so it becomes ['e', 'x', 'a', 'm', 'p', 'l', 'e'] and I get a TypeError: unhashable type: 'list'
I cannot use the chr and ord functions (I know how to but they aren't applicable in this situation) and I have tried using the map function once I've turned it to a list but only got strange results.
I've also tried turning the list into a tuple but that produces the same error.
Here is my code:
import string
step = 1
values = dict()
for index, letter in enumerate(string.ascii_lowercase):
values[letter] = index + 1
keyw=input("Enter your keyword for encryption")
keylist=list(keyw)
print(values[keylist])
Alt version without the list:
import string
step=1
values=dict()
for index, letter in enumerate(string.ascii_lowercase):
values[letter] = index + 1
keyw=input("Enter your keyword for encryption")
print(values[keyw])
You need to loop through all the letters and map each one individually:
mapped = [values[letter] for letter in keyw]
print(mapped)
This uses a list comprehension to build the list of integers:
>>> [values[letter] for letter in 'example']
[5, 24, 1, 13, 16, 12, 5]
The map() function would do the same thing, essentially, but returns an iterator; you need to loop over that object to see the results:
>>> for result in map(values.get, 'example'):
... print(result)
5
24
1
13
16
12
5
Note that you can build your values dictionary in one line; enumerate() takes a second argument, the start value (which defaults to 0); using a dict comprehension to reverse the value-key tuple would give you:
values = {letter: index for index, letter in enumerate(string.ascii_lowercase, 1)}
You most certanly can use ord()
inp = input('enter stuff:')
# a list of the ord() value of alphabetic character
# made uppercase and subtracted 64 --> position in the alphabet
alpha_value = [ord(n.upper())-64 for n in inp if n.isalpha()]
print(alpha_value)
Test:
import string
print([ord(n.upper())-64 for n in string.ascii_lowercase if n.isalpha()])
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]
You can write simple for loop to map alphabet to integer.
try to this.
print[(item, (values[item]))for item in keylist]

Python3 Make a list that increments for a certain amount, decrements for a certain amount

if I have the following:
[0, 1, 2, 3, 4, 5, 6...] how can I reorder the list (actually make a new copy of the list) and then fill it as such:
[0, 1, 2, 3, 4, 5, 10, 9, 8, 7, 6, 11, 12, 13...]
I.e. Every five iterations, the list starts to decrement, or increment. The reason I want to do this is that I have a list of objects, and I want to fill a new list with the objects in a different order.
One technique I tried is:
copied_icons = [{key:'Object1'}, {key:'Object2'}, {key:'Object3'}...]
reversed_copied_icons = copied_icons[::-1]
left_to_right = []
for h in range(17):
left_to_right.append(copied_icons[h])
for j in range(18, 35):
left_to_right.append(reversed_copied_icons[j])
for k in range(36, 53):
left_to_right.append(copied_icons[k])
for l in range(54, 71):
left_to_right.append(reversed_copied_icons[l])
But for some reason this returns the list out of order and duplicates some of the objects. I am wondering if there is a simpler way to alternating incrementing and decrementing while filling my list.
There are two problems with your approach:
You are reversing the entire list, not just that slice. Let's say the list is [1,2,3,4], and we want to reverse the second half, i.e. get [1,2,4,3]; with your approach, you would take the third and fourth element from the reversed list, [4,3,2,1], and end up with [1,2,2,1]
The to-index in a range is exclusive, thus by using range(17) and then range(18,35) and so forth, you are missing out on the elements at index 17, 35, and 53
You can use a loop for the different parts to be reversed, and then replace that slice of the list with the same slice in reverse order.
lst = list(range(20))
for start in range(5, len(lst), 10):
lst[start:start+5] = lst[start+4:start-1:-1]
Or this way, as pointed out in comments, which also gets rid of those nasty off-by-one indices:
for start in range(5, len(lst), 10):
lst[start:start+5] = reversed(lst[start:start+5])
Afterwards, lst is [0, 1, 2, 3, 4, 9, 8, 7, 6, 5, 10, 11, 12, 13, 14, 19, 18, 17, 16, 15].
Or, in case the intervals to be reversed are irregular (as it seems to be in your question):
reverse = [(3, 7), (12,17)]
for start, end in reverse:
lst[start:end] = reversed(lst[start:end])
This appears to accomplish your objective:
def foo(lst, n=5):
""" lst=list to be re-ordered, n=item count before reversal """
new_list = list()
direction = 1
start = 0
end = start
while start < len(lst):
# process through the list in steps of 'n',
# except use min for possible stub at end.
end = start + min(n, len(lst) - start) # i.e. start+5
# If direction is 1, append list to new list. Otherwise, append reversed list.
new_list[start:end] = lst[start:end][::direction]
direction *= -1 # Switch directions
start = end # Jump to new starting position.
return new_list
lst = np.arange(20).tolist()
foo(lst,5)
[0, 1, 2, 3, 4, 9, 8, 7, 6, 5, 10, 11, 12, 13, 14, 19, 18, 17, 16, 15]
If the direction *= -1 line where to be removed, the code would simply copy the existing list (lst) in chunks of size 'n', the number of items you'd like before reversing the list.
Just above where the direction is to be changed, the [::direction] will be either [::1] in which case the list will be sorted in regular order or else [::-1] in which case the list will be reversed for the chunk of size n which is being processed. The third argument when slicing a list is the 'step size' argument, so a step of -1 returns a copy of the list in reverse order.
In case there is a stub, i.e. your stub is 2 if your list has 22 elements but your 'n' is in steps of 5, then you need to adjust your step size 'n' so you don't go past the end of your list. The min(n, len(lst) - start) will ensure you don't go past the end of the list. Alternatively, and probably clearer, you could use end = min(start + n, len(lst)).

Categories