How can I loop through a list of strings? - python-3.x

I have this code:
test = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O"]
for i in test:
if i not in ["C","D"]:
test.remove(i)
print(test)
I was expecting to get ['C','D'] as a result of running the code above, however I am getting this ['B', 'C', 'D', 'F', 'H', 'J', 'L', 'N']
How can I successfully loop through a list of strings and delete the elements I don't need using Python 3?
NOTE: I don't want to use comprehension lists
thanks

When removing from lists in other languages, I used to reverse walk the list:
test = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O"]
for i in reversed(test):
if i not in ["C","D"]:
test.remove(i)
print(test)
Note that reversed will create a new list so this might not be the best solution for large lists. Now, since you already walk a copy of your list, and if you need to parse in the correct order, you can use copy:
import copy
for i in copy.copy(test):
if i not in ["C","D"]:
test.remove(i)
and to avoid the import (from here):
for i in test[:]:
if i not in ["C","D"]:
test.remove(i)
Finally, the best solution for me, is a traditional, in-place reverse iteration without copying the list ("borrowed" and modified from this answer)
for i in range(len(test) - 1, -1, -1):
if test[i] not in ["C","D"]:
del test[i]

First loop: i = 'A', i not in ['C', 'D'] -> remove i. Now the first item of test is 'B'. So in the next loop i will be equal to 'C'. That is where things gone wrong...

Related

Get all the combinations of given lists having N items in each list without duplicate?

Given list is
`test1List = ['A', 'B', 'C', 'D', 'E']`
Let's say, N = 4,
then I am searching for all the combinations created from test1List with following conditions:
there won't be duplication of element in one combination
same element can be in another combination with different order
should output for N times of elements on list.
I have tried combinations, permutations and product methods and searched on all existing solutions but I haven't found any solution regarding my issue and I tried my self to do it, but I am loosing the logical parts in it. So, please help me to find out the solution.
Here is an example which will help to demonstrate my output:
Let's say N = 4
`['A', 'B', 'C', 'D']`
`['B', 'A', 'C', 'D']` (element not in order way, but can't be repeated once element is added)
`['A', 'B', 'D', 'C']`
`'D', 'A', 'E', 'C']`
Mostly, I found in order ways, so I am having issue on it.
Thank you in advance for any kind of solutions and information.

Is there a way to create combinations that preserve the order of elements in a list?

I have a function that supplies me with a list of lists. The length of the list corresponds to the length of the combination, while the length of the sublist corresponds to the different letters that can be used in that position. So, for instance the expected combinations for this list [['W'], ['I'], ['C', 'J'], ['K', 'Y']] are "WICK", "WICY", "WIJK", and "WIJY". I know how to generate those combinations using nested for loops as follows:
for a in lst[0]:
for b in lst[1]:
for c in lst[2]:
for d in lst[3]:
print(a+b+c+d)
However, since the length of each list may vary, doing it manually is not feasible for my program. Is there a way I can do this automatically?
I think what you're looking for is product (short for Cartesian product) which is in the itertools module. You can read about it here.
Here is the sample code:
import itertools as it
data = [['W'], ['I'], ['C', 'J'], ['K', 'Y']] #not a very good variable name
combos = list(it.product(*data))
This is hardcoreded considering 4 elements in Parent List # As per Qn
lst = [['W'], ['I'], ['C', 'J'], ['K', 'Y']]
for a in range(len(lst[0])):
for b in range(len(lst[1])):
for c in range(len(lst[2])):
for d in range(len(lst[3])):
print(lst[0][a]+lst[1][b]+lst[2][c]+lst[3][d])

Creating a dictionary from list of lists using only one for loop

I came across this question in a test. There are two parts to this question:
Part i:
Given a list of flavors, eg. ['A','A','A','A','B','B','B','B','B','C','C','C','C'], write a function that returns a dictionary of the number of each flavor respectively.
My solution:
flavors = ['A','A','A','A','B','B','B','B','B','C','C','C','C']
def count_flavors(l):
dict_flavors={}
for i in l:
dict_flavors[i] = l.count(i)
return dict_flavors
print(count_flavors(flavors))
Part ii:
Using not more than ONE for loop write a function that accepts a list of lists of flavors eg. [['A', 'A', 'B', 'B', 'B', 'C', 'C'], ['A', 'A', 'B', 'B', 'B', 'B', 'C'], ['A', 'B', 'C', 'C']] and returns a dictionary for the total number of each flavor. You must include the function that you defined in part one in this solution.
(To clarify, essentially there should only be two for loops; one from part one and one from part two)
So far my solution is the following:
batches = [['A','A','A','A','B','B','B','B','B','C','C','C','C'], ['A', 'A', 'B', 'B' ,'B','B','C'], ['A','B','C','C']]
def batch_count(b):
batch_dict = []
result = {}
for j in b:
batch_dict.append(count_flavors(j))
print(batch_dict)
for i in batch_dict:
for k in i.keys():
result[k] = result.get(k,0) + i[k]
return result
print('batch count 1:' + str(batch_count(batches)))
I am struggling to find a solution that only uses one for loop for this part. I am aware that there are modules that exist for this sort of thing like collections.Counter(). Is a naive solution that does not include any modules possible for this problem?
Thanks!
Here is the best naive solution which I can think of in order to achieve what you want
Benefits of using the solution
No need to create extra variable like batch_dict = [], which takes unnecessary space in your system
No need to carry out multiple computations using different methods, like you did above using count_flavors()
Straight forward and easy to understand
FINAL SOLUTION
batches = [['A','A','A','A','B','B','B','B','B','C','C','C','C'], ['A', 'A', 'B', 'B' ,'B','B','C'], ['A','B','C','C']]
def batch_count(b):
result = {} # for storing final count results
# two loops are required to get into the arrays of array, not other option is there
for items in b:
# Getting the nested array item here
for item in items:
# final computation, if the item is there in the result dict, then increment
# else simply assign 1 to the item as a key which eventually gives you the total number
# of counts of each item throughout the batches array items
if item in result:
result[item] += 1
else:
result[item] = 1
return result
print('batch count 1:' + str(batch_count(batches)))
# OUTPUT
# >>> batch count 1:{'A': 7, 'C': 7, 'B': 10}
Feel free to test this out for some other batches too, and let me know. This is by far the naive solution which is possible to give out what you want to achieve. Keep learning :)
ANOTHER SOLUTION [MAKING USE OF FIRST METHOD COUNT_FLAVORS]
Hey, if you really want to use the first method, then there is a work around, but you need to compromise with one thing now, that is Counter has to be imported, but I assure you, it will be as simple as that, and will give you straight forward answer
Your count_flavors works fine, so we take the count_falvors() as is.
We will be making changes to the batch_count method now
FINAL SOLUTION
from collections import Counter
# Taking your method as is, to get the dictionary which counts
# the items occurence from your array
def count_flavors(l):
dict_flavors={}
for i in l:
dict_flavors[i] = l.count(i)
return dict_flavors
# This method will do your stuffs
def batch_count(b):
result = {} #this will be used to return the final result
# now just one loop, since we will passing the array
# to our method for computation count_flavors()
for items in b: # this will give out single array
'''
now we will call your count_flavor method
we will use Counter() to merge the dictionary data
coming from the count_flavor and then add it to the result
Counter() keep track of same item, if present in multiple
dict, ADDS +1 to the same item, doesn't duplicate value
Hence counter required
'''
if len(result) != 0:
# if the result is not empty, then result = result + data
result += Counter(count_flavors(items)) # no more extra for loop
else:
# else first fill the data by assigning it
result = Counter(count_flavors(items))
# this will give out the output in {}
# else the output will come in Counter({}) format
return dict(result)
# our test array of arrays
batches = [['A','A','A','A','B','B','B','B','B','C','C','C','C'], ['A', 'A', 'B', 'B' ,'B','B','C'], ['A','B','C','C']]
print('batch count 1:' + str(batch_count(batches)))
# OUTPUT
# >>> batch count 1:{'A': 7, 'B': 10, 'C': 7}
In this way you achieve the output with the usage of your count_flavors() method too, that too with no multiple loops in the batch_count(). Hope that will give you more clarity :). If this works out for you, you may accept the answer, for the the people who will come looking for answer to this question :)
The first function can become much faster by modifying your approach in this way:
def count_flavors(lst):
dict_flavors = {}
for item in lst:
if item in dict_flavors:
dict_flavors[item] += 1
else:
dict_flavors[item] = 1
return dict_flavors
You could also use Counter to simplify your code:
from collections import Counter
def count_flavors(lst):
return dict(Counter(lst))
The second function can use itertools.chain:
from collections import Counter
from itertools import chain
def batch_count(b):
return dict(Counter(chain(*b)))

Why is sorted() not sorting my list of strings?

Problem:
So I was trying to alphabetically sort my list of strings maybe I overlooked something very minor. I have tried both .sort and sorted() but maybe I didn't do it correctly?
Here is my Code:
words = input("Words: ")
list1 = []
list1.append(words.split())
print(sorted(list1))
Expected output-
Input: "a b d c"
Output: ['a', 'b', 'c', 'd']
Current output-
Input: "a b d c"
Output: [['a', 'b', 'd', 'c']]
Your code is not working because you are trying to sort a list inside a list.
When you call words.split() it returns a list. So when you do list1.append(words.split()) it is appending a list into list1.
You should do this:
words = input("Words: ")
list1 = words.split()
print(sorted(list1))
You can try a simple method as follows:
list1 = [i for i in input('Words: ').split(' ')]
print(sorted(list1))
I've tested it. And it is working
Without deviating from your current effort, the only modification you need to do to fix your code is :
words = input("Words: ")
list1 = []
list1.append(words.split())
print(sorted(list1[0]))
Explanation of what you were doing wrong:
The root cause of your confusion is append() .According to python docs,append() takes exactly one argument.
So when you do this,
words.split()
You are trying to append more than 1 element into the list1 and when you append() something more than 1 in a list, it appends as a nested list (i.e a list inside another list.)
To support my explanation you can see that your code fixed by a simple [0]
print(sorted(list1[0]))
That is because your input is stored as a list of list, AND it is stored in the first index (Point to note - 1st index in a python list is 0, hence the usage of list1[0])
Please let me know if I could have explained it in a more simpler way or if you have any other confusions that aid from the above explanation.

Python iterator unexpected behavior while modifying its list inside a for-in loop

The main rule is: do not modify a list while iterating over it, but...
Given the following code to remove a specific element in a python list (but intentionally written as a loop):
mylist = ['a', 'b', 'c', 'd', 'e', 'f']
for i in mylist:
if i == 'c':
mylist.remove(i)
print(mylist)
>> ['a', 'b', 'd', 'e', 'f']
How is that the iterator does not get lost while iterating if the list is being modified inside the loop? I expected this to yield an error or an inconsistent behavior.
List iterators (other iterators may behave differently) work by remembering what index they are currently at, and when you call next it checks to see if the index is in range, and if so, gets an item and then updates the index. Your code reaches index 2, removes 'c', then goes to index 3, which is now the 'e' entry and continues on, skipping the 'd' entry completely. If you add a print statement in the for loop, you'll see the behaviour.
You can have a look at the C source code for list objects at:
https://github.com/python/cpython/blob/master/Objects/listobject.c
Look for "listiter_next" to see the implementation.
It checks if the current index is less than the length of the list:
if (it->it_index < PyList_GET_SIZE(seq))
and if it is, gets the item and increments the index:
item = PyList_GET_ITEM(seq, it->it_index);
++it->it_index;

Resources