Can anyone help me to optimize this piece of code? - python

I want to store even number and odd number in a separate list. But, here I am facing a unique problem. I am able to store it in sets but not in lists. Is there a way wherein I can store these in a List without repetition.
I have tried this in Jupyter notebook
list_loop=[1,2,3,4,5,6,7,8,9,10,11,12,13,1,4,1,51,6,17,]
for i in list_loop:
if i % 2 == 0 :
list_even = list_even + [i]
else:
list_odd = list_odd + [i]
print(set(list_even))
print(set(list_odd))
Expected output:
[2,4,6,8,10,12]
[1,3,5,7,9,11,13,17,51]

Define list_odd and list_even as lists and don't convert them to sets before printing. Note that you can use list comprehension to fill list_odd and list_even:
list_odd = []
list_even = []
list_loop=[1,2,3,4,5,6,7,8,9,10,11,12,13,1,4,1,51,6,17,]
list_odd = [elem for elem in list_loop if elem % 2 != 0]
list_even = [elem for elem in list_loop if elem % 2 == 0]
print(list_even)
print(list_odd)
Output:
[2, 4, 6, 8, 10, 12, 4, 6]
[1, 3, 5, 7, 9, 11, 13, 1, 1, 51, 17]
Edit: for uniqueness, turn list_loop into a set:
list_loop=set([1,2,3,4,5,6,7,8,9,10,11,12,13,1,4,1,51,6,17,])
Output:
[2, 4, 6, 8, 10, 12]
[1, 3, 5, 7, 9, 11, 13, 17, 51]

Use a comprehension
>>> list_loop=[1,2,3,4,5,6,7,8,9,10,11,12,13,1,4,1,51,6,17,]
>>> print(list(set(_ for _ in list_loop if _ % 2)))
[1, 3, 5, 7, 9, 11, 13, 17, 51]
Similarly for even numbers.

There are a couple of ways you could do this. You could use the OrderedDict in the collections library, or you could just sort the set and get a list,
...
print(sorted(set(list_even)))
print(sorted(set(list_odd)))
Also, I would personally create those lists using a set comprehension
list_even = sorted({x for x in list_loop if x % 2 == 0})
list_odd = sorted({x for x in list_loop if x % 2 == 1})

You can solve this using a list comprehension with a filter condition - but you then iterate your list twice.
By using a simple for loop you only need to touch any number once at it will conserve the original order - what putting your numbers through a set might not do - order in a set is not guaranteed:
Keep a set of seen numbers, only add anything if your current number was not yet seen.
list_loop = [1,2,3,4,5,6,7,8,9,10,11,12,13,1,4,1,51,6,17,]
list_even = []
list_odd = []
seen = set()
trick = [list_even, list_odd] # even list is at index 0, odd list at index 1
for i in list_loop:
if i in seen:
continue
else:
seen.add(i)
# the trick eliminates the need for an if-clause
trick[i%2].append(i) # you use i%2 to get either the even or odd index
print(list_even)
print(list_odd)
Output:
[2, 4, 6, 8, 10, 12]
[1, 3, 5, 7, 9, 11, 13, 51, 17]

You can apply the list function to your set object in order to
convert it to a list.
list_from_set = list(set(list_even))
>>> print(list_from_set)
[2, 4, 6, 8, 10, 12]

Related

Splitting list in Python if two elements are next to each other

how can i split a list based on neighboring elements, so if i have a list such as
test = [3,5,7,1,10,17]
and i want to split the list if element 10 and 17 are next to each other so that the split happens between [3,5,7,1] and [10,17].
I know there is groupby but i could only figure out how to use that to check if one element is present and then split, but not two after each other.
pseudocode:
for i in list:
if element[i] == 10 and element[i+1] == 17:
splitlist() # split before elements 10
You can zip() the list with an offset of itself to get pairs. Then find the index of the pair you are looking for (assuming this happens once or you only care about the first). Then splice the list:
test = [3,5,7,1,10,17]
def partition_on_pair(test, pair):
index = next((i for i, n in enumerate(zip(test, test[1:])) if n == pair), len(test))
return test[:index], test[index:]
partition_on_pair(test, (10, 17))
# ([3, 5, 7, 1], [10, 17])
partition_on_pair(test, (10, 19)) # doesn't exist, so you get an empty
#([3, 5, 7, 1, 10, 17], [])
partition_on_pair(test, (5, 7))
#([3], [5, 7, 1, 10, 17])
partition_on_pair(test, (3,5))
#([], [3, 5, 7, 1, 10, 17])
Here is an example based on your output:
def split_list(test, match):
idx = [test.index(i) for i in match]
if sum([i - min(idx) for i in idx]) == sum(range(len(match))
return [
test[0:idx[0]],
test[idx[0]:idx[-1]+1]
]
split_list(test=[3, 5, 7, 1, 10, 17], match=[10, 17])
Here is a simple working code:
test = [3,5,7,1,10,17]
def neighbor_splitting():
for x in test:
if x == 10:
index = test.index(x)
list1 = test[:index]
list2 = test[index:]
return list1, list2
# [3, 5, 7, 1]
# [10, 17]

List comprehension, keep highest value in a list

I want to create a list comprehension that keeps the previous value unless the next value is higher.
Example:
list_input = [3, 4, 2, 8, 9, 3, 3, 4, 20, 1]
list_output = [3, 4, 4, 8, 9, 9, 9, 9, 20, 20]
Is there a way to do this in a single list comprehension expression?
One way to really just do it in a list comprehension:
list_output = [m
for m in list_input[:1]
for x in list_input
for m in [max(m, x)]]
Better way:
from itertools import accumulate
list_output = list(accumulate(list_input, max))
Requested explanation for the list comprehension: It does pretty much the same as this:
list_output = []
if list_input:
m = list_input[0]
for x in list_input:
m = max(m, x)
list_output.append(m)

How to print out certain elements of a list in python?

So I have a list li = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13] and I only want to print out elements that are a part of an arithmetic sequence 6n - 5 (1st, 7th and 13th).
How can I do that if I have a list with n elements?
You could simply say
print([x for x in li if x % 6 == 1])
or, alternatively, if you just want the sequence and don't want to bother about creating li in the first place,
print([6*n-5 for n in range(1, (13+5)//6+1)])
Use the code:
li = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
new=[]
for i in li:
if int((i+5)/6)==((i+5)/6):
#You can also use
#if ((i+5)/6).is_integer():
new.append(i)
I tried to make it as easy as possible.
Hope it helps :)
li = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
for n in range(1,len(li)+1): #choose n such that it is length of the list since it cant have more values than the number of values in the list.
for i in li:
if (6*n - 5) == i:
print(i)
Hope this helps
Thanks
Michael
From what I understand, you want the elements whose positions are generated by the sequence. Hence, you want elements from an array of length n whose index is from the sequence function 6x-5.
NOTE: I am assuming you are using 1-based indexing, which means, when you say 1st element in your list, you intend to get 1 and not 2.
n = 13
li = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
# generate the sequence till n
seq = []
x = 1
while 6*x-5 <= n:
seq.append(6*x-5)
x += 1
# Print the elements from the sequence:
for idx in seq:
print(li[idx-1])
# If you want to store it in another list:
li2 = [li[idx-1] for idx in seq]
Below is a more generic and efficient way for above code:
n = 13
li = list(range(1, n+1)) # More easy to write
# More efficient way is to create a generator function
def get_seq(n):
x = 1
while 6*x-5 <= n:
yield 6*x-5
x += 1
# Get the generator object
seq = get_seq(n)
# Print the elements from the sequence:
for idx in seq:
print(li[idx-1])
# Want to store it in another list:
seq = get_seq(n) # Don't forget to get a new generator object.
li2 = [li[idx-1] for idx in seq]
Output for both snippets:
1
7
13
Hope the answer helps, and remove confusion for others as well ;)
You can simply generate the sequence for any n.
for example:
n = 10
print([ 6*x - 5 for x in range(1,n)])
output:
[1, 7, 13, 19, 25, 31, 37, 43, 49]
>>> [Finished in 0.2s]
But if you just want to filter your existing list li:
li = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
print([ x for x in li if x % 6 == 1 ])
output:
[1, 7, 13]
>>>
[Finished in 0.3s]
li[1] is 2, li[7] is 8 and the element with the index 13 is out of range.
li = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
for num, i in enumerate(li):
if ((num + 5)/6).is_integer():
print(i)
# 2
# 8
If you want to start with the index 1 add start=1 to the enumerate() function.
for num, i in enumerate(li, start=1):
if ((num + 5)/6).is_integer():
print(i)
# 1
# 7
# 13

Python - Remove between indexes of two values if it occurs twice in a list

Title is definitely confusing, so here's an example: Say I have a list of values [1,2,3,2,1,4,5,6,7,8]. I want to remove between the two 1s in the list, and by pythonic ways it will also end up removing the first 1 and output [1,4,5,6,7,8]. Unfortunately, due to my lack of pythonic ability, I have only been able to produce something that removes the first set:
a = [1,2,3,2,1,4,5,6,7]
uniques = []
junks = []
for value in a:
junks.append(value)
if value not in uniques:
uniques.append(value)
for value in uniques:
junks.remove(value)
for value in junks:
a.remove(value)
a.remove(value)
a[0] = 1
print(a)
[1,4,5,6,7]
Works with the first double occurrence and will not work with the next occurrence in a larger list. I have an idea which is to remove between the index of the first occurrence and the second occurrence which will preserve the second and not have me do some dumb thing like a[0] = 1 but I'm really not sure how to implement it.
Would this do what you asked:
a = [1, 2, 3, 2, 1, 4, 5, 6, 7, 8]
def f(l):
x = l.copy()
for i in l:
if x.count(i) > 1:
first_index = x.index(i)
second_index = x.index(i, first_index + 1)
x = x[:first_index] + x[second_index:]
return x
So the output of f(a) would be [1, 4, 5, 6, 7, 8] and the output of f([1, 2, 3, 2, 1, 4, 5, 6, 7, 8, 7, 6, 5, 15, 16]) would be [1, 4, 5, 15, 16].
if you want to find unique elements you can use set and list
mylist = list(set(mylist))
a = [1, 2, 3, 2, 1, 4, 5, 6, 7, 8, 7, 6, 5, 15, 16]
dup = [x for x in a if a.count(x) > 1] # list of duplicates
while dup:
pos1 = a.index(dup[0])
pos2 = a.index(dup[0], pos1+1)
a = a[:pos1]+a[pos2:]
dup = [x for x in a if a.count(x) > 1]
print a #[1, 4, 5, 15, 16]
A more efficient solution would be
a = [1, 2, 3, 2, 1, 4, 5, 6, 7, 8, 7, 6, 5, 15, 16]
pos1 = 0
while pos1 < len(a):
if a[pos1] in a[pos1+1:]:
pos2 = a.index(a[pos1], pos1+1)
a = a[:pos1]+a[pos2:]
pos1 += 1
print a #[1, 4, 5, 15, 16]
(This probably isn't the most efficient way, but hopefully it helps)
Couldn't you just check if something appears twice, if it does you have firstIndex, secondIndex, then:
a=[1,2,3,4,5,1,7,8,9]
b=[]
#do a method to get the first and second index of the repeated number then
for index in range(0, len(a)):
print index
if index>firstIndex and index<secondIndex:
print "We removed: "+ str(a[index])
else:
b.append(a[index])
print b
The output is [1,1,7,8,9] which seems to be what you want.
To do the job you need:
the first and the last position of duplicated values
all indexes between, to remove them
Funny thing is, you can simply tell python to do this:
# we can use a 'smart' dictionary, that can construct default value:
from collections import defaultdict
# and 'chain' to flatten lists (ranges)
from itertools import chain
a = [1, 2, 3, 2, 1, 4, 5, 6, 7]
# build dictionary where each number is key, and value is list of positions:
index = defaultdict(list)
for i, item in enumerate(a):
index[item].append(i)
# let's take first only and last index for non-single values
edges = ((pos[0], pos[-1]) for pos in index.values() if len(pos) > 1)
# we can use range() to get us all index positions in-between
# ...use chain.from_iterable to flatten our list
# ...and make set of it for faster lookup:
to_remove = set(chain.from_iterable(range(start, end)
for start, end in edges))
result = [item for i, item in enumerate(a) if i not in to_remove]
# expected: [1, 4, 5, 6, 7]
print result
Of course you can make it shorter:
index = defaultdict(list)
for i, item in enumerate([1, 2, 3, 2, 1, 4, 5, 6, 7]):
index[item].append(i)
to_remove = set(chain.from_iterable(range(pos[0], pos[-1])
for pos in index.values() if len(pos) > 1))
print [item for i, item in enumerate(a) if i not in to_remove]
This solution has linear complexity and should be pretty fast. The cost is
additional memory for dictionary and set, so you should be careful for huge data sets. But if you have a lot of data, other solutions that use lst.index will choke anyway, because they are O(n^2) with a lot of dereferencing and function calls.

Detecting consecutive integers in a list [duplicate]

This question already has answers here:
Identify groups of consecutive numbers in a list
(19 answers)
Closed 4 years ago.
I have a list containing data as such:
[1, 2, 3, 4, 7, 8, 10, 11, 12, 13, 14]
I'd like to print out the ranges of consecutive integers:
1-4, 7-8, 10-14
Is there a built-in/fast/efficient way of doing this?
From the docs:
>>> from itertools import groupby
>>> from operator import itemgetter
>>> data = [ 1, 4,5,6, 10, 15,16,17,18, 22, 25,26,27,28]
>>> for k, g in groupby(enumerate(data), lambda (i, x): i-x):
... print map(itemgetter(1), g)
...
[1]
[4, 5, 6]
[10]
[15, 16, 17, 18]
[22]
[25, 26, 27, 28]
You can adapt this fairly easily to get a printed set of ranges.
A short solution that works without additional imports. It accepts any iterable, sorts unsorted inputs, and removes duplicate items:
def ranges(nums):
nums = sorted(set(nums))
gaps = [[s, e] for s, e in zip(nums, nums[1:]) if s+1 < e]
edges = iter(nums[:1] + sum(gaps, []) + nums[-1:])
return list(zip(edges, edges))
Example:
>>> ranges([2, 3, 4, 7, 8, 9, 15])
[(2, 4), (7, 9), (15, 15)]
>>> ranges([-1, 0, 1, 2, 3, 12, 13, 15, 100])
[(-1, 3), (12, 13), (15, 15), (100, 100)]
>>> ranges(range(100))
[(0, 99)]
>>> ranges([0])
[(0, 0)]
>>> ranges([])
[]
This is the same as #dansalmo's solution which I found amazing, albeit a bit hard to read and apply (as it's not given as a function).
Note that it could easily be modified to spit out "traditional" open ranges [start, end), by e.g. altering the return statement:
return [(s, e+1) for s, e in zip(edges, edges)]
This will print exactly as you specified:
>>> nums = [1, 2, 3, 4, 7, 8, 10, 11, 12, 13, 14]
>>> ranges = sum((list(t) for t in zip(nums, nums[1:]) if t[0]+1 != t[1]), [])
>>> iranges = iter(nums[0:1] + ranges + nums[-1:])
>>> print ', '.join([str(n) + '-' + str(next(iranges)) for n in iranges])
1-4, 7-8, 10-14
If the list has any single number ranges, they would be shown as n-n:
>>> nums = [1, 2, 3, 4, 5, 7, 8, 9, 12, 15, 16, 17, 18]
>>> ranges = sum((list(t) for t in zip(nums, nums[1:]) if t[0]+1 != t[1]), [])
>>> iranges = iter(nums[0:1] + ranges + nums[-1:])
>>> print ', '.join([str(n) + '-' + str(next(iranges)) for n in iranges])
1-5, 7-9, 12-12, 15-18
Built-In: No, as far as I'm aware.
You have to run through the array. Start off with putting the first value in a variable and print it, then as long as you keep hitting the next number do nothing but remember the last number in another variable. If the next number is not in line, check the last number remembered versus the first number. If it's the same, do nothing. If it's different, print "-" and the last number. Then put the current value in the first variable and start over.
At the end of the array you run the same routine as if you had hit a number out of line.
I could have written the code, of course, but I don't want to spoil your homework :-)
I had a similar problem and am using the following for a sorted list. It outputs a dictionary with ranges of values listed in a dictionary. The keys separate each run of consecutive numbers and are also the running total of non-sequential items between numbers in sequence.
Your list gives me an output of {0: [1, 4], 1: [7, 8], 2: [10, 14]}
def series_dictf(index_list):
from collections import defaultdict
series_dict = defaultdict(list)
sequence_dict = dict()
list_len = len(index_list)
series_interrupts = 0
for i in range(list_len):
if i == (list_len - 1):
break
position_a = index_list[i]
position_b = index_list[i + 1]
if position_b == (position_a + 1):
sequence_dict[position_a] = (series_interrupts)
sequence_dict[position_b] = (series_interrupts)
if position_b != (position_a + 1):
series_interrupts += 1
for position, series in sequence_dict.items():
series_dict[series].append(position)
for series, position in series_dict.items():
series_dict[series] = [position[0], position[-1]]
return series_dict
Using set operation, the following algorithm can be executed
def get_consecutive_integer_series(integer_list):
integer_list = sorted(integer_list)
start_item = integer_list[0]
end_item = integer_list[-1]
a = set(integer_list) # Set a
b = range(start_item, end_item+1)
# Pick items that are not in range.
c = set(b) - a # Set operation b-a
li = []
start = 0
for i in sorted(c):
end = b.index(i) # Get end point of the list slicing
li.append(b[start:end]) # Slice list using values
start = end + 1 # Increment the start point for next slicing
li.append(b[start:]) # Add the last series
for sliced_list in li:
if not sliced_list:
# list is empty
continue
if len(sliced_list) == 1:
# If only one item found in list
yield sliced_list[0]
else:
yield "{0}-{1}".format(sliced_list[0], sliced_list[-1])
a = [1, 2, 3, 6, 7, 8, 4, 14, 15, 21]
for series in get_consecutive_integer_series(a):
print series
Output for the above list "a"
1-4
6-8
14-15
21
Here is another basic solution without using any module, which is good for interview, generally in the interview they asked without using any modules:
#!/usr/bin/python
def split_list(n):
"""will return the list index"""
return [(x+1) for x,y in zip(n, n[1:]) if y-x != 1]
def get_sub_list(my_list):
"""will split the list base on the index"""
my_index = split_list(my_list)
output = list()
prev = 0
for index in my_index:
new_list = [ x for x in my_list[prev:] if x < index]
output.append(new_list)
prev += len(new_list)
output.append([ x for x in my_list[prev:]])
return output
my_list = [1, 3, 4, 7, 8, 10, 11, 13, 14]
print get_sub_list(my_list)
Output:
[[1], [3, 4], [7, 8], [10, 11], [13, 14]]
You can use collections library which has a class called Counter. Counter can come in handy if trying to poll the no of distinct elements in any iterable
from collections import Counter
data = [ 1, 4,5,6, 10, 15,16,17,18, 22, 25,26,27,28]
cnt=Counter(data)
print(cnt)
the output for this looks like
Counter({1: 1, 4: 1, 5: 1, 6: 1, 10: 1, 15: 1, 16: 1, 17: 1, 18: 1, 22: 1, 25: 1, 26: 1, 27: 1, 28: 1})
which just like any other dictionary can be polled for key values

Categories

Resources