Python : Split list based on negative integers - python

I have a list say l = [1,5,8,-3,6,8,-3,2,-4,6,8]. Im trying to split it into sublists of positive integers i.e. the above list would give me [[1,5,8],[6,8],[2],[6,8]]. I've tried the following:
l = [1,5,8,-3,6,8,-3,2,-4,6,8]
index = 0
def sublist(somelist):
a = []
for i in somelist:
if i > 0:
a.append(i)
else:
global index
index += somelist.index(i)
break
return a
print sublist(l)
With this I can get the 1st sublist ( [1,5,8] ) and the index number of the 1st negative integer at 3. Now if I run my function again and pass it l[index+1:], I cant get the next sublist and assume that index will be updated to show 6. However i cant, for the life of me cant figure out how to run the function in a loop or what condition to use so that I can keep running my function and giving it l[index+1:] where index is the updated, most recently encountered position of a negative integer. Any help will be greatly appreciated

You need to keep track of two levels of list here - the large list that holds the sublists, and the sublists themselves. Start a large list, start a sublist, and keep appending to the current sublist while i is non-negative (which includes positive numbers and 0, by the way). When i is negative, append the current sublist to the large list and start a new sublist. Also note that you should handle cases where the first element is negative or the last element isn't negative.
l = [1,5,8,-3,6,8,-3,2,-4,6,8]
def sublist(somelist):
result = []
a = []
for i in somelist:
if i > 0:
a.append(i)
else:
if a: # make sure a has something in it
result.append(a)
a = []
if a: # if a is still accumulating elements
result.append(a)
return result
The result:
>>> sublist(l)
[[1, 5, 8], [6, 8], [2], [6, 8]]

Since somelist never changes, rerunning index will always get index of the first instance of an element, not the one you just reached. I'd suggest looking at enumerate to get the index and element as you loop, so no calls to index are necessary.
That said, you could use the included batteries to solve this as a one-liner, using itertools.groupby:
from itertools import groupby
def sublist(somelist):
return [list(g) for k, g in groupby(somelist, key=(0).__le__) if k]
Still worth working through your code to understand it, but the above is going to be fast and fairly simple.

This code makes use of concepts found at this URL:
Python list comprehension- "pop" result from original list?
Applying an interesting concept found here to your problem, the following are some alternatives to what others have posted for this question so far. Both use list comprehensions and are commented to explain the purpose of the second option versus the first. Did this experiment for me as part of my learning curve, but hoping it may help you and others on this thread as well:
What's nice about these is that if your input list is very very large, you won't have to double your memory expenditure to get the job done. You build one up as you shrink the other down.
This code was tested on Python 2.7 and Python 3.6:
o1 = [1,5,8,-3,6,9,-4,2,-5,6,7,-7, 999, -43, -1, 888]
# modified version of poster's list
o1b = [1,5,8,-3,6,8,-3,2,-4,6,8] # poster's list
o2 = [x for x in (o1.pop() for i in range(len(o1))) \
if (lambda x: True if x < 0 else o1.insert(0, x))(x)]
o2b = [x for x in (o1b.pop() for i in range(len(o1b))) \
if (lambda x: True if x < 0 else o1b.insert(0, x))(x)]
print(o1)
print(o2)
print("")
print(o1b)
print(o2b)
It produces result sets like this (on iPython Jupyter Notebooks):
[1, 5, 8, 6, 9, 2, 6, 7, 999, 888]
[-1, -43, -7, -5, -4, -3]
[1, 5, 8, 6, 8, 2, 6, 8]
[-4, -3, -3]
Here is another version that also uses list comprehensions as the work horse, but functionalizes the code in way that is more read-able (I think) and easier to test with different numeric lists. Some will probably prefer the original code since it is shorter:
p1 = [1,5,8,-3,6,9,-4,2,-5,6,7,-7, 999, -43, -1, 888]
# modified version of poster's list
p1b = [1,5,8,-3,6,8,-3,2,-4,6,8] # poster's list
def lst_mut_byNeg_mod(x, pLst): # list mutation by neg nums module
# this function only make sense in context of usage in
# split_pos_negs_in_list()
if x < 0: return True
else:
pLst.insert(0,x)
return False
def split_pos_negs_in_list(pLst):
pLngth = len(pLst) # reduces nesting of ((()))
return [x for x in (pLst.pop() for i in range(pLngth)) \
if lst_mut_byNeg_mod(x, pLst)]
p2 = split_pos_negs_in_list(p1)
print(p1)
print(p2)
print("")
p2b = split_pos_negs_in_list(p1b)
print(p1b)
print(p2b)
Final Thoughts:
Link provided earlier had a number of ideas in the comment thread:
It recommends a Google search for the "python bloom filter library" - this sounds promising from a performance standpoint but I have not yet looked into it
There is a post on that thread with 554 up-voted, and yet it has at least 4 comments explaining what might be faulty with it. When exploring options, it may be advisable to scan the comment trail and not just review what gets the most votes. There are many options proposed for situations like this.

Just for fun you can use re too for a one liner.
l = [1,5,8,-3,6,8,-3,2,-4,6,8]
print map(lambda x: map(int,x.split(",")), re.findall(r"(?<=[,\[])\s*\d+(?:,\s*\d+)*(?=,\s*-\d+|\])", str(l)))
Output:[[1, 5, 8], [6, 8], [2], [6, 8]]

Related

Unable to generate combination of numbers in python list using recursion

I am trying to generate a combination of numbers in python list using recursion
and my code is as follows
nums = [2,3,4,6]
def backtrck(temp, starting_index, nums):
if len(temp) == 2:
print(temp)
return
temp.append(nums[starting_index])
for i in range(starting_index + 1, len(nums)):
backtrck(temp, i, nums)
backtrck([], 0, nums)
for some reason, the above code is unable to generate the proper combinations.
Aim of the code: I want to generate all the combination of numbers starting with index 0 whose length should be equal to 2
expected output
[2, 3]
[2, 4]
[2, 6]
actual output
[2, 3]
[2, 3]
[2, 3]
[2, 3]
I don't understand what is going wrong with this recursion, I am hoping that someone could help me figure this out
Recursion is unnecessary when you can simply use for loop:
nums = [2,3,4,6]
def backtrck(starting_index, nums):
start = nums[starting_index]
for num in nums[starting_index + 1:]:
print([start, num])
backtrck(0, nums)
Output:
[2, 3]
[2, 4]
[2, 6]
where the slice nums[start_index + 1:] returns a list of all the elements of the nums list starting from one indice after the starting_index.
UPDATE
Since you've pointed out that the recursion was necessary in your code, simply replace the backtrck(temp, i, nums) recursive call with backtrck(i, nums, [temp[0], nums[i]]) to keep the starting index of the list:
nums = [2, 3, 4, 6]
def backtrck(starting_index, nums, temp=[]):
if len(temp) == 2:
print(temp)
return
temp.append(nums[starting_index])
for i in range(starting_index + 1, len(nums)):
backtrck(i, nums, [temp[0], nums[i]])
backtrck(0, nums)
Output:
[2, 3]
[2, 4]
[2, 6]
Note that I've changed the positional argument temp into a keyword argument. It will still work with temp as a positional argument, but it will be less practical if the temp list always starts out as empty.
What is wrong with your function:
After couple of recursive calls temp becomes [2,3] then on the next recursion your base case is met (len(temp) == 2:) and that instance of the function returns without adding anything. The next for loop iteration recurses and the same thing happens. Once temp is [2,3] it can never change.
How to fix it:
There are a number of problems with the structure of your function and it is not a simple one-line-fix. You need to figure out how to
when the base case is met
capture (or print) temp
return something meaningful to the previous function that it can use to continue making combinations
the function needs to act upon the return value from the recursive call
adding a for loop to a recursive procedure/process complicates things, maybe figure out how to do without it.
I would start over with the function. I don't know if you are asking someone to give you a completely new function so I'm going to search for questions regarding recursive solutions to find/generate list item combinations.
Here are some related SO Questions/Answers. If any of them solve your problem let us know so we can mark yours as a duplicate. Most don't have the taken two-at-a-time constraint but maybe you can adapt. there are many more.
Recursive function that returns combinations of size n chosen from list
Recursively find all combinations of list
python recursion list combinations without using generator
Using recursion to create a list combination
Loosely related:
Nth Combination
Finding all possible combinations of numbers to reach a given sum
Creating all possible k combinations of n items in C++ - not Python but algorithms might be useful.
When it comes to recursion, my advice is keep it simple and let the recursion do the work for you:
def backtrck(numbers):
if len(numbers) < 2:
return []
first, second, *rest = numbers
return [[first, second]] + backtrck([first] + rest)
nums = [2, 3, 4, 6]
print(*backtrck(nums), sep='\n')
OUTPUT
> python3 test.py
[2, 3]
[2, 4]
[2, 6]
>

Find index of minimum value in a Python sublist - min() returns index of minimum value in list

I've been working on implementing common sorting algorithms into Python, and whilst working on selection sort I ran into a problem finding the minimum value of a sublist and swapping it with the first value of the sublist, which from my testing appears to be due to a problem with how I am using min() in my program.
Here is my code:
def selection_sort(li):
for i in range(0, len(li)):
a, b = i, li.index(min(li[i:]))
li[a], li[b] = li[b], li[a]
This works fine for lists that have zero duplicate elements within them:
>>> selection_sort([9,8,7,6,5,4,3,2,1])
[1, 2, 3, 4, 5, 6, 7, 8, 9]
However, it completely fails when there are duplicate elements within the list.
>>> selection_sort([9,8,8,7,6,6,5,5,5,4,2,1,1])
[8, 8, 7, 6, 6, 5, 5, 5, 4, 2, 9, 1, 1]
I tried to solve this problem by examining what min() is doing on line 3 of my code, and found that min() returns the index value of the smallest element inside the sublist as intended, but the index is of the element within the larger list rather than of the sublist, which I hope this experimentation helps to illustrate more clearly:
>>> a = [1,2,1,1,2]
>>> min(a)
1 # expected
>>> a.index(min(a))
0 # also expected
>>> a.index(min(a[1:]))
0 # should be 1?
I'm not sure what is causing this behaviour; it could be possible to copy li[i:] into a temporary variable b and then do b.index(min(b)), but copying li[i:] into b for each loop might require a lot of memory, and selection sort is an in-place algorithm so I am uncertain as to whether this approach is ideal.
You're not quite getting the concept correctly!
li.index(item) will return the first appearance of that item in the list li.
What you should do instead is if you're finding the minimum element in the sublist, search for that element in the sublist as well instead of searching it in the whole list. Also when searching in the sliced list, you will get the index in respect to the sublist. Though you can easily fix that by adding the starting step to the index returned.
A small fix for your problem would be:
def selection_sort(li):
for i in range(0, len(li)):
a, b = i, i + li[i:].index(min(li[i:]))
li[a], li[b] = li[b], li[a]
When you write a.index(min(a[1:])) you are searching for the first occurence of the min of a[1:], but you are searching in the original list. That's why you get 0 as a result.
By the way, the function you are looking for is generally called argmin. It is not contained in pure python, but numpy module has it.
One way you can do it is using list comprehension:
idxs = [i for i, val in enumerate(a) if val == min(a)]
Or even better, write your own code, which is faster asymptotically:
idxs = []
minval = None
for i, val in enumerate(a):
if minval is None or minval > val:
idxs = [i]
minval = val
elif minval == val:
idxs.append(i)

Basic question about replacing entries in list of lists according to if condition

How can I change this code so the I replace the elements for a different if condition?
In the code below I change replace the 3 with 5.
But what if the wanted to replace the entry to 5 if any sub-list entry is larger than 30?
Example: If L[i][j] > 30 I get [[3,3],[5,5],[5,1], [5,8]]
L = [[3,3],[444,1111],[45,1], [90,8]]
for i, x in enumerate(L):
for j, a in enumerate(x):
if 3 in a:
L[i][j] = a.replace(3, 5)
****** Disclaimer *******:
The example was taken from this enclosed question for the sake of convenience. My aim in this question is to understand how to use the indexes so it fits a different type of if condition.
Replace elements in a list of lists python
Quick and dirty answer:
L = [[3,3],[444,1111],[45,1], [90,8]]
L = [[5 if value < 30 else 5 for value in list ] for list in L]
print(L)
out:
[[3, 3], [5, 5], [5, 1], [5, 8]]
Better way using a function
If you want to replace the values then it's better to create a simple function as shown below. You seem to have copied the code from somewhere else, so I will try my best to explain to you what is going on below.
Our original list is shown below:
L = [[3,3],[444,1111],[45,1], [90,8]]
The function below is defined to accept 3 values, the first value is the list to be manipulated, the second is the condition, as in replace if number is > condition. The third value is the new value to replace the values that satisfy the condition.
def replace_greater_than(List,condition = 30,new_value = 5):
L = [[value if value < condition else new_value for value in list ] for list in List]
Here we are using a concept know as nested list comprehension. Start by reading it from right to left. We are looping through every list in List (notice the case), please note that "list" in the code can be replaced with any variable name that you want, but i just called it "list" to improve readability.
So now we are looping through our initial list and we are retrieving the inner lists with each iteration.Then we perform list comprehension again on the inner lists that we just retrieved, so now we are looping on the values in the list themselves.
Finally in the inner list comprehension we set a condition, which is passed on by the user, if the value is less than the set condition or threshold then return the same value else return the specified new value.
How to use the function:
Example: here we place the condition as 30 and the value as 5
replace_greater_than(List = L, condition = 30, value = 5)
out:
[[3, 3], [5, 5], [5, 1], [5, 8]]
Example: We can also call the function without passing any values to the parameters because we already set the default values of our function earlier; condition = 30, new_value = 5.
replace_greater_than(List = L)
out:
[[3, 3], [5, 5], [5, 1], [5, 8]]
Example: Finally, we can also pass custom values to our function. The function below will replace all the values greater than 100 with the new_value of 23
replace_greater_than(List = L, condition = 100, new_value = 23)
out:
[[3, 3], [23, 23], [45, 1], [90, 8]]
Here are some sources to help you get started with learning python:
Great free Course covering all the basics by Microsoft
List Comprehension
Functions in python
Some channels on Youtube that I recommend:
Real Python
Corey Schafer
DataCamp
Basically you have a loop through all elements, then you have condition and action when condition is met.
for i, x in enumerate(L): #loop
for j, a in enumerate(x): #loop
if 3 in a: #condition
L[i][j] = a.replace(3, 5) #action
You want to change condition to if L[i][j] > 30 (already in your question), and you want to change action to replace or reassign entry, which is straightforward L[i][j] = 5.
In above code a is L[i][j], so you can write condition a bit differently.
I will let you modify code yourself and test. You don't need to use list comprehension at this point.
Thea easy way to do it is to iterate over each item in each list and change items via an 'if' statement.
Create a statement which will return either the item it is given, or 3 if the number is equal to 5.
replace = lambda i: 3 if i==5 else i
Next, iterate through the nested lists. This can be written out in two ways, via list comprehension or as nested for statements.
l = [[replace(l2)for l2 in l1] for l1 in l]
for l1 in l:
for l2 in l1:
l2 = replace(l2)
When we run the code we get:
print (l)
>>> [[3, 3], [444, 1111], [45, 1], [90, 8]]
To make the code change any items <30 we can change the if statement we made at the start.
replace = lambda i: 3 if i<30 else i
Printing the new code gives:
print (l)
>>> [[3, 3], [444, 1111], [45, 3], [90, 3]]
Note, using x.replace() works for strings not integers, you can play with int() and str() if you need to use .replace().

Python. Greedy algorithm, 2 for loops iterating over lists

I am trying to write a greedy algorithm where I have a constraint on how many items I can fit into a box and I have to fit as many items in a box as possible before moving on to the next one (ie maximizing the weight of each box).
I've been trying to solve this by creating two identical lists, let's say a_list and b_list.
a_list = [9, 8, 6, 4, 3, 2, 2, 2]
b_list = [9, 8, 6, 4, 3, 2, 2, 2]
The constraint on each box is 10 here, so for example I can only fit the first item (9) into one before moving onto the next box. The following box should contain 8 + 2.
Each box is a list within the main list ie
list_ = [[9], [8,2],[6,4].....]
I can only move on to next box once the current one cannot have further items fitted into it.
When I am trying iterate through the two lists I don't know how to delete items to avoid them appearing multiple times in list_.
I'm close but I have a couple of items coming up twice while one doesn't come up at all.
It is also the case that despite my sorting the lists in descending order, not all my boxes are optimal, one of them only has one item with value '2' in it. I know it's to do with the loop but I don't understand why it's not going through the items in descending order.
limit = 10
list_ = [[]]
for i in a_list:
for j in b_list:
if sum(l[-1]) + i + j <= limit:
l[-1].append(i)
l[-1].append(j)
b_list.remove(j)
elif sum(l[-1]) + j <= limit:
l[-1].append(j)
b_list.remove(j)
else:
l.append([])
The only reason I think that you're using an a_list and a b_list is that you assume you need to pick two items per box, which need not be the case.
I think you should use a single list, and use a list index based approach to track which items are added.
You also will have issues with deletes, so try setting items that are added to -1 and filter them out after each pass, to avoid confusions with deletes while looping.
I'm resisting sharing the solution code here, but ping me if you need it.
Changing a list as you iterate over it is always a challenge. Here is one solution that uses a while loop, which I generally don't endorse, but it is a simple enough algorithms that it should work here with no issues.
The while loop checks if there are any elements left in the initial list. It then pops (removes and saves) the first element of the list and iterates over the rest of the list looking for additional elements that satisfy the condition of the summing to less than the constraint. If an element is found it is appended to sub-list and its index is recorded. At the end of the for loop the sub list is append to the output list and then recorded indices are removed in the reverse order.
a_list = [9, 8, 6, 4, 3, 2, 2, 2]
constraint = 10
out = []
while a_list:
# grab first element of a_list and reset the list of
# to pop from a_list to pop from a_list
sub_out = [a_list.pop(0)]
pop_list = []
for i,a in enumerate(a_list):
if a+sum(sub_out) <= constraint:
sub_out.append(a)
pop_list.append(i)
# append the sub_list to the output list
out.append(sub_out)
# remove each item in the pop_list in the reverse order
for i in reversed(pop_list):
a_list.pop(i)
#output:
>>> out
[[9], [8, 2], [6, 4], [3, 2, 2]]

Inserting and removing into/from sorted list in Python

I have a sorted list of integers, L, and I have a value X that I wish to insert into the list such that L's order is maintained. Similarly, I wish to quickly find and remove the first instance of X.
Questions:
How do I use the bisect module to do the first part, if possible?
Is L.remove(X) going to be the most efficient way to do the second part? Does Python detect that the list has been sorted and automatically use a logarithmic removal process?
Example code attempts:
i = bisect_left(L, y)
L.pop(i) #works
del L[bisect_left(L, i)] #doesn't work if I use this instead of pop
You use the bisect.insort() function:
bisect.insort(L, X)
L.remove(X) will scan the whole list until it finds X. Use del L[bisect.bisect_left(L, X)] instead (provided that X is indeed in L).
Note that removing from the middle of a list is still going to incur a cost as the elements from that position onwards all have to be shifted left one step. A binary tree might be a better solution if that is going to be a performance bottleneck.
You could use Raymond Hettinger's IndexableSkiplist. It performs 3 operations in O(ln n) time:
insert value
remove value
lookup value by rank
import skiplist
import random
random.seed(2013)
N = 10
skip = skiplist.IndexableSkiplist(N)
data = range(N)
random.shuffle(data)
for num in data:
skip.insert(num)
print(list(skip))
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
for num in data[:N//2]:
skip.remove(num)
print(list(skip))
# [0, 3, 4, 6, 9]

Categories