Python: form a list based on the same values - python

consider x = [10,10,20,20,20,30]
How do i form another list_x1 which contains only same values example: list_x1 = [10,10]
and list_x2 =[20,20] and list_x3 =[30] ?

You can use counter.
from collections import Counter
x = [10, 10, 20, 20, 20, 30]
my_counter = Counter(x)
d = {'list_x{0}'.format(key): [key] * my_counter[key] for key in my_counter}
>>> d
{'list_x10': [10, 10], 'list_x20': [20, 20, 20], 'list_x30': [30]}
One of the issues with your request is that you would need to pre-assign variables, which aren't initially know. I've used a dictionary as a container to hold them.
For a list, [10] * 3 results in [10, 10, 10]. So, [k] * my_counter multiplies the unique key value by the number of occurrences.

With itertools.groupby
>>> from itertools import groupby
>>> x = [10,10,20,20,20,30]
>>> [list(g) for k, g in groupby(x)]
[[10, 10], [20, 20, 20], [30]]

Perhaps the best way is #Alexander's idea with collections, but I always find it helpful to look at more 'native' python code to see what's going on. So here's a way to do it:
x = [10,10,20,20,20,30]
def foo(iterable):
for val in iterable:
cnt = iterable.count(val)
iterable = list(filter(lambda x: x != val, iterable))
if cnt:
yield [val]*cnt
for _ in foo(x):
print(_)
Note that the complexity factor is going to be fairly high. Certainly not O(n) because you have to:
Iterate through each of the values in our main for val in iterable
Iterate through each of the values every time we call iterable.count
Iterate through each of the values when we filter() them to prevent duplicates.

Using collections.Counter:
>>> def list_gen(in_list, elem):
... count = collections.Counter(in_list)
... return [elem] * count[elem]
...
>>> a
[1, 2, 3, 2, 3]
>>> list_gen(a, 2)
[2, 2]

This isn't exactly what you're looking for, but this code will generate a list of lists separating the values.
x = [10, 10, 20, 20, 20, 30]
uniques = set(x)
output = []
for unique in uniques:
unique_count = x.count(unique)
temp = []
for i in range(0, unique_count):
temp.append(unique)
output.append(temp)
You can then use list comprehensions on output

Related

How to consolidate the function of local_sum (list of mixed numbers and sublists) in one loop?

Given a list containing numbers, and sublists. The goal of this function - local_sum() is to get all local sum (or range sum) with these conditions: 1) if it's a sublist, get its sum and remain a list, if consecutive numbers just add up them.
[Notes] a local variable just what's needed - enables keeping the running sum of sublists. Thanks.
Examples of in/outputs shown as following:
from itertools import groupby
A = [3, 4, 2, [10, 22, 32, 14], 9, 8, 6, [22, 11]]
expected = [9, 23, [111]
ans = [9, [78], 23, [33]] <---- currently getting
# is there a way to get this expected result in one shot, instead of doing another processing?
# my current working code:
def local_sum(L):
'''add up the numbers which are not in sublist,
and sum the sublist as a one unit, if there're more than one -
try to consolidate it into ONE.
'''
ans = []
for k, g in groupby(L, key=lambda x: isinstance(x, list)):
if k: # the group is a list
# the whole list :: sum(list(g))X
ans.append([sum(*g)]) # g is _grouper instance
else:
ans.append(sum(list(g))) # each single number
return ans
With only one example, it seems you want to keep a running total of everything in a sublist:
from itertools import groupby
A = [3, 4, 2, [10, 22, 32, 14], 9, 8, 6, [22, 11]]
def local_sum(L):
ans = []
sub = 0
for k, g in groupby(A, key=lambda x: isinstance(x, list)):
if k:
sub += sum(*g)
else:
ans.append(sum(list(g)))
ans.append([sub])
return ans
print(local_sum(A))
Output:
[9, 23, [111]]
Alternatively:
int_=0
int_list = []
for x in A:
if isinstance(x, int):
int_+=x
else:
int_list.append(int_)
int_=0
oneliner = [sum(map(lambda x: sum(x), filter(lambda x: isinstance(x, list), A)))]
int_list.append(oneliner)
Gives:
[9, 23, [111]]

How to make this for cycle into a list comprehension

#!/usr/bin/python2
s=5 # input any int
l1=[5,8,2,1,17] # input some int numbers
l2=[] # will be out
for i in l1:
s+=i
l2.append(s)
print l1,l2
[5, 8, 2, 1, 17]
[10, 18, 20, 21, 38]
I would like to replace the above for cycle with listcomprehension.
I generally use list comprehensions but I don't know how could I do with the above for cycle.
For a list comprehension without using libraries or the overhead of sum(), you could use an internal list to store the accumulator.
l1 = [5,8,2,1,17]
s = 5
R = [a.append(a.pop(0)+n) or a[0] for a in [[s]] for n in l1]
print(R)
[10, 18, 20, 21, 38]
If you're using Python 3.8+ you can use the walrus operator to manage the accumulation:
R = [a := n + (a if i else s) for i,n in enumerate(l1)]
Here you can see use of sum(). However I would not prefer it if the list is too long for the repetitive calculations by sum()
>>> s=5
>>>
>>> l1=[5,8,2,1,17]
>>> [s+sum(l1[:i+1]) for i,x in enumerate(l1)]
[10, 18, 20, 21, 38]
>>>
List comprehensions are much better suited to situations where you generate an output item independently for each input item. Here, having to sum the items makes it rather inconvenient.
You could use itertools.accumulate, though, which is designed to do that by default:
from itertools import accumulate
l1 = [5, 8, 2, 1, 17]
s = 5
l2 = list(accumulate(l1, initial=s))[1:]
print(l2)
# [10, 18, 20, 21, 38]
(Note that we have to exclude the first value, which would be the initial s, as you don't want it in your output.)
You have the list-comprehension option
res = [s + sum(l1[:i+1]) for i,x in enumerate(l1)]
Or using some libs along with itertools.accumulate
from operator import add
from itertools import accumulate
import numpy as np
res = np.array(list(accumulate(l1, add))) + s

Modify function to accept tables as well (Python 3)

I've designed a simple function that looks at an inputted list of numbers, identifies the minimum and maximum values, then substitutes both of them for the midpoint value between them, the function is here:
def mainfunction(list_of_numbers):
smallest_number = list_of_numbers[0]
for a in list_of_numbers:
if a < smallest_number:
smallest_number = a
largest_number = list_of_numbers[0]
for b in list_of_numbers:
if b > largest_number:
largest_number = b
midpoint = (smallest_number + largest_number)/2
final_list = [x if (x != largest_number and x != smallest_number) else midpoint for x in list_of_numbers]
return final_list
print(mainfunction([10, 7, 14, 3, -200, 8, 1, -12, 250]))
Unfortunately, I can't get the function to work on TABLES of numbers, is there an easy way to convert a table of numbers into a list? Any info would be appreciated, cheers!
You can use itertools.chain
from itertools import chain
a = [[3,4], [15,16], [19,20]]
res = list(chain.from_iterable(a))
print(res)
Output:
[3, 4, 15, 16, 19, 20]
with list comprehension
res = [x for lst in a for x in lst]

Distribute value over list

Given the amount S=25 and list L = [10,20,30] I want to distribute S over L in the following way:
output -> [10, 15, 0]
I wrote the following code, which does the job:
S = 25
l = [10,20,30]
res= []
b = True
for value in l:
if b == True:
if S - value >0:
res.append(value)
else:
res.append(S)
b= False
S -= value
else:
res.append(0)
Is it possible to rewrite it, maybe as a one-liner? (numpy is allowed)
Slightly shorter and more readable:
def distribute(S, L):
res = []
for e in L:
res.append(min(e, S))
S = max(0, S-e)
return res
While you can make this (or anything really) a one-liner, I wouldn't force it. It's better to keep things readable.
You can also use an equivalent generator function:
def distribute(S, L):
for e in L:
yield min(e, S)
S = max(0, S-e)
list(distribute(S, l))
This is one way, but please do not get attached to one-liners for the sake of them being one-liners. Often they are not the best method, either in terms of readability or performance.
from itertools import accumulate
S = 25
l = [10, 20, 30]
res = [i if j <= S else max(0, S-k) \
for i, j, k in zip(l, accumulate(l), accumulate([0]+l))]
# [10, 15, 0]
numpy
Since OP specifically asked for numpy, let's assume this is about large arrays. I think distribute in the OP subject was a key word, because this is very similar to conversion between PDF and CDF (https://en.wikipedia.org/wiki/Cumulative_distribution_function) and the inverse:
a = numpy.array([10, 20, 30])
c = a.cumsum() # [10, 30, 60]
b = c.clip(0, 25) # [20, 25, 25]
numpy.ediff1d(b, to_begin=b[0]) # [10, 15, 0]

Using list comprehension to store the maximum seen value

Is it possible to do the below with list comprehension? Trying to store the maximum value that has been seen at any given point through the loop.
def test(input):
a = input[0]
b = []
for i in input:
a = max(i,a)
b.append(a)
return b
print test([-5,6,19,4,5,20,1,30])
# returns [-5, 6, 19, 19, 19, 20, 20, 30]
You can use itertools.accumulate with the max builtin in Python 3:
from itertools import accumulate
lst = [-5,6,19,4,5,20,1,30]
r = list(accumulate(lst, max)) #[i for i in accumulate(lst, max)]
print(r)
# [-5, 6, 19, 19, 19, 20, 20, 30]
What you present here is a typical form of what is known in functional programming as scan.
A way to do this with list comprehension that is inefficient is:
[max(input[:i]) for i in range(1,n+1)]
But this will run in O(n2).
You can do this with list comprehension given you use a function with side effects: like the following:
def update_and_store(f,initial=None):
cache = [initial]
def g(x):
cache[0] = f(cache[0],x)
return cache[0]
return g
You can then use:
h = update_and_store(max,a[0])
[h(x) for x in a]
Or you can use a dictonaries setdefault() like:
def update_and_store(f):
c = {}
def g(x):
return c.setdefault(0,f(c.pop(0,x),x))
return g
and call it with:
h = update_and_store(max)
[h(x) for x in a]
like #AChampion says.
But functions with side-effects are rather unpythonic and not declarative.
But you better use a scanl or accumulate approach like the one offered by itertools:
from itertools import accumulate
accumulate(input,max)
If using NumPy is permitted, then you can use NumPy:
import numpy as np
np.maximum.accumulate([-5,6,19,4,5,20,1,30])
# array([-5, 6, 19, 19, 19, 20, 20, 30])

Categories