Rearrange a list of strings - python

I want to rearrange or modify he sequence of elements (strings) in a list. This is the original list
['A', 'B', 'C', 'D', 'E', 'F', 'G']
I want to move E and F behind (or after?) B.
['A', 'B', 'E', 'F', 'C', 'D', 'G']
^^^ ^^^
The decision what to move comes from the user. There is no rule behind and no way to formulate that in an algorithm. In other words the action move something behind something other is input from the user; e.g. the user mark two elements with her/his mouse and drag an drop it behind another element.
My code works and is able to do this. But I wonder if there is a more efficient and pythonic way to do this. Maybe I missed some of Python's nice in-build features.
#!/usr/bin/env python3
# input data
original = list('ABCDEFG')
# move "EF" behind "B" (this is user input)
to_move = 'EF'
behind = 'B'
# expected result
rearanged = list('ABEFCDG')
# index for insertion
idx_behind = original.index(behind)
# each element to move
for c in reversed(to_move): # "reverse!"
# remove from original position
original.remove(c)
# add to new position
original.insert(idx_behind + 1, c)
# True
print(original == rearanged)
You can assume
Elements in original are unique.
to_move always exist in original.
behind always exist in original.
The elements in to_move are always adjacent.
Other example of possible input:
Move ['B'] behind F
Move ['A', 'B'] behind C
This is not possible:
Move ['A', 'F'] behind D

Don't use .remove when the goal is to erase from a specific position; though you may know what is at that position, .remove a) will search for it again, and b) remove the first occurrence, which is not necessarily the one you had in mind.
Don't remove elements one at a time if you want to remove several consecutive elements; that's why slices exist, and why the del operator works the way that it does. Not only is it already harder to iterate when you can say what you want directly, but you have to watch out for the usual problems with modifying a list while iterating over it.
Don't add elements one at a time if you want to add several elements that will be consecutive; instead, insert them all at once by slice assignment. Same reasons apply here.
Especially don't try to interleave insertion and removal operations. That's far more complex than necessary, and could cause problems if the insertion location overlaps the source location.
Thus:
original = list('ABCDEFG')
start = original.index('E')
# grabbing two consecutive elements:
to_move = original[start:start+2]
# removing them:
del original[start:start+2]
# now figure out where to insert in that result:
insertion_point = original.index('B') + 1
# and insert:
original[insertion_point:insertion_point] = to_move

If it is just a small number of items you want to rearrange, just swap the relevant elements:
lst = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
lst[2], lst[4] = lst[4], lst[2] # switch 'C' and 'E'
lst[3], lst[5] = lst[5], lst[3] # switch 'D' and 'F'
lst
['A', 'B', 'E', 'F', 'C', 'D', 'G']

Related

Is there a simple way to randomly pick from a list with the exception of one element?

If I wanted to pick randomly from a list like this, and I got b...
list=[a,b,c,d]
Is there a simple way to pick another element from the list, without picking b again, without altering the list in anyway?
Maybe you could use random.sample:
Return a k length list of unique elements chosen from the population
sequence or set. Used for random sampling without replacement.
>>> import random
>>> l = ['a', 'b', 'c', 'd']
>>> random.sample(l, 3) # Pick 3 random elements without replacement from l
['c', 'd', 'a']
Whilst the answer by Shash Sinha is certainly what you are looking for, you can play around with recursion a bit as well (this does alter the original list, so its not ideal):
import random
choices = ['a', 'b', 'c', 'd']
def chooseRandom():
if len(choices) > 0:
choice = choices.pop(random.randint(0, len(choices)-1))
print (f'Chosen Character: {choice}')
print (f'Remaining Choices: {choices}')
chooseRandom()
chooseRandom()
Which would output something similar to the following (depending on what is randomly chosen):
Chosen Character: c
Remaining Choices: ['a', 'b', 'd']
Chosen Character: a
Remaining Choices: ['b', 'd']
Chosen Character: b
Remaining Choices: ['d']
Chosen Character: d
Remaining Choices: []
import copy
import random
list = ['a', 'b' , 'c', 'd']
list2 = copy.deepcopy(list)
random.shuffle(list2)
for i in list2:
# i is now a random item from original list
do_something(i)
You can also just pop() off list2 when you need a random item

Simulating the sample space for a probability problem in Python

I am interested in simulating the sample space for the following question on a probability assignment:
A man will carve pumpkins for his two daughters and three sons. His wife will bring each kid’s pumpkins in a completely random order. The man has decided that as soon as he has carved pumpkins for two of his sons, he would ask his wife to carve the remaining pumpkins. Let W denote the number of pumpkins he will carve.
So the resulting sample space of W would look something like this:
sample_space=[['S','S'],
['S','D','S'],
['S','D','D','S'],
['D','S','S'],
['D','S','D','S'],
['D','D','S','S']]
I was thinking about having two lists, one of sons, one of daughters:
son_list1=['S','S','S']
daughter_list1=['D','D']
And then combining them with in every possible order:
result_list1=[['S','S','S','D','D'],
['S','S','D','S','D'],
['S','S','D','D','S'],
['S','D','S','S','D'],
['S','D','S','D','S'],
['S','D','D','S','S'],
['D','S','S','S','D'],
['D','S','S','D','S'],
['D','S','D','S','S'],
['D','D','S','S','S']]
I don't know if numbering each son and each daughter and then combining them would be easier where we have:
son_list2=['S1','S2','S3']
daughter_list2=['D1','D2']
where this resulting list would be something like:
result_list2=[['S1','S2','S3','D1','D2'],
['S1','S3','S2','D1','D2'],
['S2','S1','S3','D1','D2'],
['S2','S3','S1','D1','D2'],
['S3','S1','S2','D1','D2'],
['S3','S2','S1','D1','D2'],
...
['D2','D1','S3','S2','S1']]
But if this method would be easier, I could just get rid of the numbers after result_list2 was generaged and then delete the repeats.
Anyway, after I get the resulting list in the form of result_list1, I could create a "son counter" and then go through each list and then stop when the "son counter" reaches 2 and then from there delete the repeats to get the sample_space list.
Is there any better logic?
To solve this problem, I think the best solution would be to get all of the permutations of the order in which he carves each pumpkin.
I just used the following code, for getting all permutations of a set, from GeeksforGeeks. I just changed some of the variable names to make it more clear.
def permutation(passed_list):
# If passed_list is empty then there are no permutations
if len(passed_list) == 0:
return []
# If there is only one element in lst then, only
# one permutation is possible
if len(passed_list) == 1:
return [passed_list]
# Find the permutations for passed_list if there are
# more than 1 characters
perm_list = [] # empty list that will store current permutation
# Iterate the input(passed_list) and calculate the permutation
for i in range(len(passed_list)):
item = passed_list[i]
# Extract passed_list[i] or item from the list. remaining_list is
# remaining list
remaining_list = passed_list[:i] + passed_list[i + 1:]
# Generating all permutations where item is first
# element
for p in permutation(remaining_list):
perm_list.append([item] + p)
return perm_list
Then you can just iterate through all of the permutations, keeping track of the order as you go. Once you get to two sons, you stop going through that iteration, add that order to your sample space, and then go to the next permutation.
if __name__ == '__main__':
# Set of all children. It doesn't matter what order this list is in
children = ['S', 'S', 'S', 'D', 'D']
# perms is the list of all permutations of children list
perms = permutation(children)
# This set will hold the resulting sample space you are looking for
total_set = []
# For each permutation
for perm in perms:
order = [] # Contains the order of whose pumpkin he carves
son_counter = 0
for child in perm:
if child is 'S':
son_counter += 1
# Update the order
order.append(child)
if son_counter is 2:
# To keep from adding duplicate orders
if order not in total_set:
total_set.append(order)
# Reset the following two variables for the next iteration
order = []
son_counter = 0
break
print(total_set)
This gave me the following output:
[['S', 'S'], ['S', 'D', 'S'], ['S', 'D', 'D', 'S'], ['D', 'S', 'S'], ['D', 'S', 'D', 'S'], ['D', 'D', 'S', 'S']]
I believe this is the answer you are looking for.
Let me know if you have any questions!
You could use dynamic programming to build up the sample space from the bottom up. For example,
def create_samples(n_sons, n_daughters):
if n_sons == 0:
# stop carving
yield []
elif n_daughters == 0:
# must carve n_sons more pumpkins
yield ['S'] * n_sons
else:
# choose to carve for a sun
for sample in create_samples(n_sons - 1, n_daughters):
yield ['S'] + sample
# choose to carve for a daughter
for sample in create_samples(n_sons, n_daughters - 1):
yield ['D'] + sample
samples = list(create_samples(2, 2))
# [['S', 'S'],
# ['S', 'D', 'S'],
# ['S', 'D', 'D', 'S'],
# ['D', 'S', 'S'],
# ['D', 'S', 'D', 'S'],
# ['D', 'D', 'S', 'S']]
The function create_samples(n_sons, n_daughters) returns all samples that meet your condition, under the assumption that n_sons and n_daughters remain to be processed.

Understanding Recursion "local" variable in Python

I'm just learning about recursion and am trying to apply it in some for-fun, come-to-understand-it ways. (Yes, this whole thing is better done by three nested for loops)
def generate_string(current_string, still_to_place):
if still_to_place:
potential_items = still_to_place.pop(0)
for item in potential_items:
generate_string(current_string + item, still_to_place)
#print("Want to call generate_string({}, {})".format(current_string + item, still_to_place))
else:
print(current_string)
generate_string("", [['a','b','c'],['d','e','f'],['g','h','i']])
If I comment out the recursive call and uncomment the print, it prints exactly what I'd hope it would be calling. However, just uncommenting the print shows that it calls an empty still_to_place array even when it should still have the [d,e,f], [g,h,i] from the "higher up" recursion I think.
What am I missing in my understanding? Thanks!
Right, this is the expected behavior. The reason is that still_to_place is being shared between each function call. Mutable objects in Python are 'passed by assignment', meaning that, if you pass a list to a function, that function shares a reference to the SAME list. This thread has more detail.
So, each time you call still_to_place.pop(0), you are popping the list in every recursive call. They all share the exact same list.
This behavior is not always desirable, often you want your list to be immutable. In this case, you need to pass your recursive call a modified copy of the data structure. Here's what your code would look like using the immutable approach:
def generate_string(current_string, still_to_place):
if still_to_place:
potential_items = still_to_place[0]
for item in potential_items:
generate_string(current_string + item, still_to_place[1:])
print("Want to call generate_string({}, {})".format(current_string + item, still_to_place))
else:
print(current_string)
generate_string("", [['a','b','c'],['d','e','f'],['g','h','i']])
As a rule of thumb, methods on the object (e.g. .pop) will modify it in-place. Also, different languages approach mutability differently, in some language, data structures are ALWAYS immutable.
I outputted what I got during each iteration of generate_string and this is what I got. It's probably all confusing because nothing is behaving how you expected, but let me explain what Python is thinking.
#1st time
current_string = ""
still_to_place = [['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i']]
We start out by passing in the above data, however, as we walk through what happens, we first pop the first array ['a', 'b', 'c'], and we begin to iterate through this popped array. However because we called .pop(0), we now only have the latter part of the array, the still_to_place.pop(0) on the first recursive call that is made to generate_string()
#2nd time
current_string = "a"
still_to_place = [['d', 'e', 'f'], ['g', 'h', 'i']]
This is exactly what was in current_string and still_to_place the first time the recursive call was made. Now we are going to begin executing the function again from the beginning. We call the pop function again, removing the second array ['d', 'e', 'f']. Now we are only left with the third and final array.
#3rd time
current_string = "ad"
still_to_place = [['g', 'h', 'i']]
As we iterate through ['g', 'h', 'i'], because still_to_place is now empty. (We have just popped the last array.) Any calls to generate_string will go directly to the else clause, and we are going to be printing out the "ad" string plus the values in the array we just popped.
#4th, 5th and 6th time
still_to_place = []
current_string = "adg"
still_to_place = []
current_string = "adh"
still_to_place = []
current_string = "adi"
We now continue where the last recursive call left off, when we where going through the second time. This is where things get confusing. When we left off current_string = "a" and still_to_place was originally [['d', 'e', 'f'], ['g', 'h', 'i']], but we have since popped everything off of the array. You see, arrays behave differently than numbers or strings. All versions of an array share the same data. You change the data once, it changes it everywhere that array is used. (objects and dictionaries also behave this way.)
So with all that said still_to_place = [], and still_to_place will stay empty for the remainder of the recursive calls. potential_items still has the data that it popped of ['d', 'e', 'f']. We've already executed the 'd' string in steps #4, #5, and #6, so we can finish where we left of
#7th and 8th times
still_to_place = []
current_string = "ae"
still_to_place = []
current_string = "af"
Once again potential_items has ['a', 'b', 'c'] and we've already executed 'a'. Unlike still_to_place, potential_items is a local variable with a smaller scope. If you know how scopes work, then it will make since why we can have multiple potential_items, but it is the same still_to_place that was being used. Each time we popped an item off of still_to_place we where adding the popped result to a new potential items variable with a limited scope. still_to_place was global to the entire program, and so one change to still_to_place would cause changes that where not being anticipated.
Hopefully I made things more confusing, and not less confusing. Leave a comment on what you need more clarification on.

python order of elements in set

I do not understand the ordering what Python applies from holding sets. For example:
visited = set()
visited.add('C')
visited.add('A')
visited.add('B')
print(set)
The ordering is 'A', 'C', 'B'. Why 'A' is before 'C' (maybe alphabetical order)?
What I have to do in order to preserve the adding ordering, i.e. 'C', 'A', 'B'?
You cannot have order in sets. and there is no way to tell how Python orders it. Check this answer for alternatives.
Sets are different than lists. If you want to preserve an order, use a list.
For example :
a = []
a.append('C')
a.append('A')
a.append('B')
print a # ['C', 'A', 'B']

Python insert operation on list

I am newbie to Python and I have a doubt regarding insert operation on the list.
Example 1:
mylist = ['a','b','c','d','e']
mylist.insert(len(mylist),'f')
print(mylist)
Output:
['a', 'b', 'c', 'd', 'e', 'f']
Example 2:
mylist = ['a','b','c','d','e']
mylist.insert(10,'f')
print(mylist)
Output:
['a', 'b', 'c', 'd', 'e', 'f']
In second example why it still inserts 'f' element in the list even if I am giving index 10 to insert method?
The list.insert function will insert before the specified index. Since the list is not that long anyways in your example, it goes on the end. Why not just use list.append if you want to put stuff on the end anyways.
x = ['a']
x.append('b')
print x
Output is
['a', 'b']
The concept here is "insert before the element with this index". To be able to insert at the end, you have to allow the invalid off-the-end index. Since there are no well-defined "off-the-end iterators" or anything in Python, it makes more sense to just allow all indices than to allow one invalid one, but no others.

Categories

Resources