How to change this for loop into while loop? - python

I have this code that when u pass in aList = [5,8,2,13,1,8,3,1,8]
it prints countList= [0, 2, 1, 1, 0, 1, 0, 0, 3, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]. Basically, say index 1 in countList is 2 because there are two number 1's in aList.
This is the code in for loop:
countList = 20*[0]
aList = [5,8,2,13,1,8,3,1,8]
for num in aList:
countList[num] += 1
And I tried changing it to a while loop but prints countList= [0, 2, 2, 3, 0, 5, 0, 0, 24, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0] instead.
This is what I tried:
countList = 20*[0]
aList = [5,8,2,13,1,8,3,1,8]
index=0#initialize index
while index<len(aList):
number=aList[index]
count=0
while count<number:
countList[number] += 1
count+=1
index+=1

You have too many whiles. One for loop can be replaced with one while loop.
The inner while is not needed. Replace it with just an increment to the counter in the list.
You only need to track 3 things for a successful loop, the index which goes over the elements, it's initialization and the end condition.
They are in different places in the different looping commands but they are logically the same.
countList = 20*[0]
aList = [5,8,2,13,1,8,3,1,8]
index=0#initialize index
while index<len(aList):
countList[aList[index]] += 1
index+=1

Related

Counting and summing list elements, in sections separated by three or more zeros

I have a list of integers which I want to separate according to a certain condition. I want to get the sum and the count of the list elements, stopping when three or more consecutive elements are equal to 0; then the sum and count orders restart again from where they stopped.
For example, part of the list is:
[8, 2, 1, 1, 2, 0, 0, 0, 0, 0, 6, 0, 2, 0, 0, 0, 8, 0, 0, 2, 0, 0, 0, 6, 0, 0]
The process would be:
8, 2, 1, 1, 2 -> sum: 14, length: 5
0, 0, 0, 0, 0
6, 0, 2 -> sum: 8, length: 3
0, 0, 0
8, 0, 0, 2 -> sum: 10, length: 4
0, 0, 0
6, 0, 0 -> sum: 6, length: 3
So the output I want is:
[[14, 5], [8, 3], [10, 4], [6, 3]]
What I've written so far computes the sum okay, but my problem is that zeros within sections aren't counted in the lengths.
Current (incorrect) output:
[[14, 5], [8, 2], [10, 2], [6, 2]]
Code:
arr = [8, 2, 1, 1, 2, 0, 0, 0, 0, 0, 6, 0, 2, 0, 0, 0, 8, 0, 0, 2, 0, 0, 0, 6, 0, 0]
result = []
summed, count = 0, 0
for i in range(0, len(arr) - 2):
el, el1, el2 = arr[i], arr[i + 1], arr[i + 2]
if el != 0:
summed = summed + el
count = count + 1
if el == 0 and el1 == 0 and el2 == 0:
if summed != 0:
result.append([summed, count])
summed = 0
count = 0
elif i == len(arr) - 3:
summed = el + el1 + el2
count = count + 1
result.append([summed, count])
break
print(result)
It is quite hard to understand what your code does. Working with Strings seems more straightforward and readable, your output can be achieved in just two lines (thanks to #CrazyChucky for the improvement):
import re
arr = [8, 2, 1, 1, 2, 0, 0, 0, 0, 0, 6, 0, 2, 0, 0, 0, 8, 0, 0, 2, 0, 0, 0, 6, 0, 0]
# Convert to String by joining integers, and split into substrings, the separator being three zeros or more
strings = re.split(r'0{3,}', ''.join(str(i) for i in arr))
# Sums and counts using list comprehensions
output = [[sum(int(x) for x in substring), len(substring)] for substring in strings]
Output:
>>>output
>>>[[14, 5], [8, 3], [10, 4], [6, 3]]
Remember that readability is always the most important factor in any code. One should read your code for the first time and understand how it works.
If the full list contains numbers with more than one digit, you can do the following:
# Convert to String by joining integers, seperating them by a commade, and split into substrings, the separator being three zeros or more
strings = re.split(r',?(?:0,){3,}', ','.join(str(i) for i in arr))
# Make a list of numbers from those strings
num_lists = [string.split(',') for string in strings]
# # Sums and counts using list comprehensions
output = [[sum(int(x) for x in num_list), len(num_list)] for num_list in num_lists]
This answer is not so much to suggest a way I'd recommend doing it, as to highlight how clever Paul Lemarchand's idea of using a regular expression is. Without Python's re module doing the heavy lifting for you, you have to either look ahead to see how many zeros are coming (as in Prakash Dahal's answer), or keep track of how many zeros you've seen as you go. I think this implementation of the latter is about the simplest and shortest way you could solve this problem "from scratch":
input_list = [8, 2, 1, 1, 2, 0, 0, 0, 0, 0, 6, 0, 2, 0, 0, 0, 8, 0, 0, 2, 0, 0,
0, 6, 0, 0]
output_list = []
current_run = []
pending_zeros = 0
for num in input_list:
# If the number is 0, increment the number of "pending" zeros. (We
# don't know yet if they're part of a separating chunk or not.)
if num == 0:
pending_zeros += 1
# If this is the first nonzero after three or more zeros, process
# the existing run and start over from the current number.
elif pending_zeros >= 3:
output_list.append((sum(current_run), len(current_run)))
current_run = [num]
pending_zeros = 0
# Otherwise, the pending zeros (if any) should be included in the
# current run. Add them, and then the current number.
else:
current_run += [0] * pending_zeros
current_run.append(num)
pending_zeros = 0
# Once we're done looping, there will still be a run of numbers in the
# buffer (assuming the list had any nonzeros at all). It may have
# pending zeros at the end, too. Include the zeros if there are 2 or
# fewer, then process.
if current_run:
if pending_zeros <= 2:
current_run += [0] * pending_zeros
output_list.append((sum(current_run), len(current_run)))
print(output_list)
[(14, 5), (8, 3), (10, 4), (6, 3)]
One note: I made each entry in the list a tuple rather than a list. Tuples and lists have a lot of overlap, and in this case either would probably work perfectly well... but a tuple is a more idiomatic choice for an immutable data structure that will always be the same length, in which each position refers to something different. (In other words, it's not a list of equivalent items, but rather a well-defined combination of (sum, length).)
Use this:
a = [8, 2, 1, 1, 2, 0, 0, 0, 0, 0, 6, 0, 2, 0, 0, 0, 8, 0, 0, 2, 0, 0, 0, 6, 0,0]
total_list = []
i = 0
sum_int = 0
count_int = 0
for ind, ele in enumerate(a):
if ind < (len(a) - 2):
sum_int += ele
if sum_int != 0:
count_int += 1
if (a[ind] == 0) and (a[ind+1] == 0) and (a[ind+2] == 0):
if sum_int != 0:
count_int -= 1
total_list.append([sum_int, count_int])
sum_int = 0
count_int = 0
else:
sum_int += ele
count_int += 1
if sum_int != 0:
total_list.append([sum_int, count_int+1])
sum_int = 0
count_int = 0
print(total_list)

Python find similar combinations of elements in a list

So I have a list that looks a bit like this:
my_list = [0,1,1,1,0,0,1,0,1,0,1,1,0,0,0,1,0,1,1,0,1 ... 0,1,0]
it contains thousands of 0's and 1's basically. Im looking for a way to find similar (repeating) combinations of elements in it (10 next elements to be specific). So (for example) if there is a :
... 0,1,1,1,0,0,1,1,0,1 ...
combination and it appears more than once I would like to know where it is in my list (index) and how many times it repeats.
I need to check all possible combinations here, that is 1024 possibilities...
Here is a solution using regex:
import random
from itertools import product
import re
testlist = [str(random.randint(0,1)) for i in range(1000)]
testlist_str = "".join(testlist)
for i in ["".join(seq) for seq in product("01", repeat=10)]:
print(f'pattern {i} has {len(re.findall(i, testlist_str))} matches')
outputs:
pattern 0000000000 has 0 matches
pattern 0000000001 has 0 matches
pattern 0000000010 has 1 matches
pattern 0000000011 has 2 matches
pattern 0000000100 has 2 matches
pattern 0000000101 has 2 matches
....
It looks like a homework problem, so I don't want to give the solution at once, just hints.
Don't look at it literally. It's 0s and 1s, so you can look at them like at binary numbers.
Some hints:
1024 "patterns" become just numbers from 0 to 1023.
Checking for a pattern is making a number from those 10 digits.
Think how you would do that then.
More hints, more technical:
If you have one number pattern, e.g. from 0th to 9th element, you can get 1st to 10th pattern by taking 9-digit (from 1st index to 9th index) value (aka %512), "move" them left (*2) and add the 10th digit.
Make a dictionary or list of lists where key/index is the pattern number (0 to 1023) and list contains indexes of the start pattern.
I'll edit this answer later to provide an example solution but I gotta take a short break first.
Edit:
Customisable base and length with defaults for your case.
def find_patterns(my_list, base=2, pattern_size=10):
modulo_value = base ** (pattern_size-1)
results = [[] for _ in range(base ** pattern_size)]
current_value = 0
for index, elem in enumerate(a):
if index < pattern_size:
current_value = base*current_value + elem
elif index == pattern_size:
results[current_value].append(0)
if index >= pattern_size:
current_value = base*(current_value % modulo_value) + elem
results[current_value].append(index+1-pattern_size) #index of the first element in the pattern
return results
IIUC, you could do:
my_list = [0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0]
w = 10
occurrences = {}
for i in range(len(my_list) - w + 1):
key = tuple(my_list[i:i+w])
occurrences.setdefault(key, []).append(i)
for pattern, indices in occurrences.items():
print(pattern, indices)
Output
(0, 1, 1, 1, 0, 0, 1, 0, 1, 0) [0]
(1, 1, 1, 0, 0, 1, 0, 1, 0, 1) [1]
(1, 1, 0, 0, 1, 0, 1, 0, 1, 1) [2]
(1, 0, 0, 1, 0, 1, 0, 1, 1, 0) [3]
(0, 0, 1, 0, 1, 0, 1, 1, 0, 0) [4]
(0, 1, 0, 1, 0, 1, 1, 0, 0, 0) [5]
(1, 0, 1, 0, 1, 1, 0, 0, 0, 1) [6]
(0, 1, 0, 1, 1, 0, 0, 0, 1, 0) [7]
(1, 0, 1, 1, 0, 0, 0, 1, 0, 1) [8]
(0, 1, 1, 0, 0, 0, 1, 0, 1, 1) [9]
(1, 1, 0, 0, 0, 1, 0, 1, 1, 0) [10]
(1, 0, 0, 0, 1, 0, 1, 1, 0, 1) [11]
(0, 0, 0, 1, 0, 1, 1, 0, 1, 0) [12]
(0, 0, 1, 0, 1, 1, 0, 1, 0, 1) [13]
(0, 1, 0, 1, 1, 0, 1, 0, 1, 0) [14]
Treat the elements as bits that can be converted to integers. The solution below converts the input list to integers, find number of occurrence of each integer and what index they can be found on.
import collections
x = [0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1]
as_int = []
# given the input above there is no pattern longer than 6 that occure more than once...
pattern_length = 6
# convert input to a list of integers
# can this be done in a nicer way, like skipping the string-conversion?
for s in range(len(x) - pattern_length+1) :
bitstring = ''.join([str(b) for b in x[s:s+pattern_length]])
as_int.append(int(bitstring,2))
# create a dict with integer as key and occurence as value
count_dict = collections.Counter(as_int)
# empty dict to store index for each integer
index_dict = {}
# find index for each integer that occur more than once
for key in dict(count_dict):
if count_dict[key] > 1:
indexes = [i for i, x in enumerate(as_int) if x == key]
index_dict[key] = indexes
#print as binary together with its index
for key, value in index_dict.items():
print('{0:06b}'.format(key), 'appears', count_dict[key], 'times, on index:', value)
Output:
101011 appears 2 times, on index: [6, 18]
010110 appears 2 times, on index: [7, 14]

Count since last occurence in NumPy

Seemingly straightforward problem: I want to create an array that gives the count since the last occurence of a given condition. In this condition, let the condition be that a > 0:
in: [0, 0, 5, 0, 0, 2, 1, 0, 0]
out: [0, 0, 0, 1, 2, 0, 0, 1, 2]
I assume step one would be something like np.cumsum(a > 0), but not sure where to go from there.
Edit: Should clarify that I want to do this without iteration.
Numpy one-liner:
x = numpy.array([0, 0, 5, 0, 0, 2, 1, 0, 0])
result = numpy.arange(len(x)) - numpy.maximum.accumulate(numpy.arange(len(x)) * (x > 0))
Gives
[0, 1, 0, 1, 2, 0, 0, 1, 2]
If you want to have zeros in the beginning, turn it to zero explicitly:
result[:numpy.nonzero(x)[0][0]] = 0
Split the array based on the condition and use the lengths of the remaining pieces and the condition state of the first and last element in the array.
A pure python solution:
result = []
delta = 0
for val in [0, 0, 5, 0, 0, 2, 1, 0, 0]:
delta += 1
if val > 0:
delta = 0
result.append(delta)

How to check for adjacency in list, then fix adjacency in python

I have this list:
row = [1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
I need to then shuffle or randomize the list:
shuffle(row)
And then I need to go through and find any adjacent 1's and move them so that they are separated by at least one 0. For example I need the result to look like this:
row = [0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0]
I am not sure of what the most efficient way to go about searching for adjacent 1's and then moving them so that they aren't adjacent is... I will also being doing this repeatedly to come up with multiple combinations of this row.
Originally when the list was shorter I did it this way:
row = [1, 1, 1, 0, 0, 0, 0, 0, 0, 0]
rowlist = set(list(permutations(row)))
rowschemes = [(0, 0) + x for x in rowlist if '1, 1' not in str(x)]
But now that my row is 20 elements long this takes forever to come up with all the possible permutations.
Is there an efficient way to go about this?
I had a moderately clever partition-based approach in mind, but since you said there are always 20 numbers and 6 1s, and 6 is a pretty small number, you can construct all the possible locations (38760) and toss the ones which are invalid. Then you can uniformly draw from those, and build the resulting row:
import random
from itertools import combinations
def is_valid(locs):
return all(y-x >= 2 for x,y in zip(locs, locs[1:]))
def fill_from(size, locs):
locs = set(locs)
return [int(i in locs) for i in range(size)]
and then
>>> size = 20
>>> num_on = 6
>>> on_locs = list(filter(is_valid, combinations(range(size), num_on)))
>>> len(on_locs)
5005
>>> fill_from(size, random.choice(on_locs))
[0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1]
>>> fill_from(size, random.choice(on_locs))
[0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1]
>>> fill_from(size, random.choice(on_locs))
[1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1]
Why not go directly for what you want? Something like:
row = ["0","0","0","0","0","0","0","0","0","01","01","01","01","01","01"]
random.shuffle(row)
print (map(int, list("".join(row)[1:])))
Since the number of 1's is fixed in a row and you don't want any 1's to be adjacent, let m be the number of 1's and let k be the number of 0's of the row. Then you want to place the m 1's in (k+1) locations randomly so that there is at most one 1 in each location. This amounts to choosing a random subset of size ((k+1) choose m) from the set (1,2,...,k+1). This is easy to do. Given the random choice of subset, you can construct your random arrangement of 0's and 1's so that no two 1's are adjacent. The random choice algorithm takes O(m) time.
Place the 6 1's and 5 of the 0's in a list giving
row = [1,0,1,0,1,0,1,0,1,0,1]
Then insert the remaining 0's one by one at random positions in the (growing) list.
for i in range(11,19):
row.insert(random.randint(0,i), 0)

Tricky: How to compare Dictionary values (containing lists) with a list?

I am having a dict(containing lists) and a list, which I want to compare:
The first thing, I want to find out is whether each value-list (e.g. for issue1, the value-list is [1, 1, 0, 0, 0, 1, 1, 1]) in ref has the same length as the list abf.
Then comes the tricky part: If they have the same length, I want to compare each item in the list abf with each item of the value-lists in ref.
But... Under one condition, the program shall move on to the next value-list in ref (without checking the remaining items of the current value-list), and this is if the item of the value-list is 1 and the corresponding item in the list abf is 0.
To make it clear, here is one example:
the value-list of the key 'issue1' in the dict ref is [1, 1, 0, 0, 0, 1, 1, 1]. The list abf is [1, 1, 0, 1, 0, 1, 0, 0].
Now, I want to check each item of those two lists (the first item of the value-list of issue1 with the first item of the list abf, then the second item of issue1 with the second item of abf and so on...): As the first two items are 1 and 1 and the condition (see above) is not fulfilled, it shall go on to the next two items (which are again 1 and 1) and so on, UNTIL it gets to (in this case) the seventh items (which are 1 and 0). At this point it shall stop comparing value-list of issue1 with list abf and continue with comparing the next value-list (of issue2) with the list abf. I hope you get the idea!
Here is my code so far:
## ref is a dict with lists as values, abf is a list
ref = {'issue1': [1, 1, 0, 0, 0, 1, 1, 1],
'issue2': [1, 0, 0, 1, 0, 0, 0, 0],
'issue3': [0, 1, 0, 0, 1, 0, 0, 1]}
abf = [1, 1, 0, 1, 0, 1, 0, 0]
## getting the length of the lists in ref and abf ans save them in ref_total & abf_total
for key in ref:
[int(item) for item in ref[key]]
ref_total = len(ref[key])
abf_total = len(abf)
## check whether ref_total and abf_total has same value
if ref_total == abf_total:
for key, value in ref.items():
for j in value:
if (ref[key][j] == 1) and (abf[j] == 0): ## if item in ref is 1 and in abf is 0, go on to the next value-list
break
if j == abf_total-1: ## if he compared the whole value-list of the current key of ref with abf and the condition above did not occur, save the key of this value-list in resp!
resp = ref[key]
else:
resp = 'Length of strings varies!' ##if the lists don't have the same length
print resp ##let me know, which key "went through"
I am really looking forward to your responeses. The code does not work and I have no idea why!
I have added some notes as comment, but this should work:
## ref is a dict with lists as values, abf is a list
ref = {'issue1': [1, 1, 0, 0, 0, 1, 0, 1],
'issue2': [1, 0, 0, 1, 0, 0, 0, 0],
'issue3': [0, 1, 0, 0, 1, 0, 0, 1]}
abf = [1, 1, 0, 1, 0, 1, 1, 0]
# abf_total does not change during cycle. Calculate it outside.
abf_total = len(abf)
## getting the length of the lists in ref and abf ans save them in ref_total & abf_total
for key, items in ref.iteritems():
ref_total = len(items)
## check whether ref_total and abf_total has same value
if ref_total == abf_total:
# Here 'ref' screened the outside ref. Use 'i'
for i, value in enumerate(items):
if (items[i] == 1) and (abf[i] == 0): ## if item in ref is 1 and in abf is 0, go on to the next value-list
break
if i == abf_total-1:
# if he compared the whole value-list of the current key of ref with abf and the condition above did not occur, save the key of
this value-list in resp!
resp = "%s = %s" % (key, items)
else:
resp = 'Length of strings varies!' ##if the lists don't have the same length
print resp ##let me know, which key "went through"
The output is:
issue2 = [1, 0, 0, 1, 0, 0, 0, 0]
If I understand your requirement right, Python has that functionality built in. You can do this.
ref = {'issue1': [1, 1, 0, 0, 0, 1, 0, 1],
'issue2': [1, 0, 0, 1, 0, 0, 0, 0],
'issue3': [0, 1, 0, 0, 1, 0, 0, 1]}
abf = [1, 1, 0, 1, 0, 1, 1, 0]
abf == abf
# OUT: True
def findinref(d, abf):
for key, value in ref.items():
if value == abf:
return value
return None
findinref(ref, abf)
findinref(ref, [1,1,0,0,0,1,0,1])
# OUT: [1, 1, 0, 0, 0, 1, 0, 1]
There are few problems with your code, that I would like to point: -
for key in ref:
[int(item) for item in ref[key]]
To start with, your above loop is ambiguous. Its not doing anything, but just creating a list which is then ignored
Secondly,
ref_total = len(ref[key])
abf_total = len(abf)
The above assignment does not work, because you have placed it outside the for loop. Indentation problem.
if ref_total == abf_total:
for key, value in ref.items():
In the above code segment, rather than having your if condition first, and then the for loop, you should move your if condition in the for loop. So, for each key, value pair, check whether the len(value) == len(abf). If true, then continue with the inner for loop.
if (ref[key][j] == 1) and (abf[j] == 0)
This condition does not account for abf[j] = 1 and ref[key][j] = 0. Instead of checking the inequality, you can check the equality, and just take a negation of that. That would be easier. (EDIT: - Just noticed that, you just want to check it for that condition only. So you can ignore this change).
Also, your inner loop should not be for j in value. You cannot index on j then. Use for j in range(ref_list), to compare values at each index.
You can try out this code after the above changes: -
ref = {'issue1': [1, 1, 0, 0, 0, 1, 1, 1],
'issue2': [1, 0, 0, 1, 0, 0, 0, 0],
'issue3': [0, 1, 0, 0, 1, 0, 0, 1],
'issue4': [1, 1, 0, 1, 0, 1, 0, 0]}
abf = [1, 1, 0, 1, 0, 1, 0, 0]
abf_total = len(abf) # Since it will never change. Move it outside
for key, value in ref.items():
resp = ""
ref_total = len(value)
if ref_total == abf_total:
for j in range(ref_total):
if not (value[j] == abf[j]):
break
if j == abf_total-1:
resp = value
print resp
else:
resp = 'Length of strings varies!' ##if the lists don't have the same length
OUTPUT : -
[1, 1, 0, 1, 0, 1, 0, 0]
thank you all very very much, you are really awesome! I didn't expect that much feedback!! You really helped me a lot. I went for Rohit Jain's solution - thank you very much Rohit!! Here is the complete code (if there is space for improvements, don't hesitate to critizise me! any feedback is really appreciated!!)
ref = {'issue1': [1, 1, 0, 0, 0, 1, 1, 1],
'issue2': [1, 0, 0, 1, 0, 0, 0, 0],
'issue3': [0, 1, 0, 0, 1, 0, 0, 1]}
abf = [1, 1, 0, 1, 0, 1, 0, 0]
abf_total = len(abf)
for key, value in ref.items():
ref_total = len(ref[key])
if ref_total == abf_total:
i = -1
for j in value:
i += 1
if (j == 1) and (abf[i] == 0):
break
if i == len(abf)-1:
resp = key
else:
resp = 'Length of strings varies!'
print resp

Categories