Combining a list of ranges in python - python

What is the easiest way to construct a list of consecutive integers in given ranges, like this?
[1,2,3,4,5,6,7, 15,16,17,18,19, 56,57,58,59]
I know the start and end values of each group.
I tried this:
ranges = ( range(1,8),
range(15,20),
range(56,60) )
Y = sum( [ list(x) for x in ranges ] )
which works, but seems like a mouthful. And in terms of code legibility, the sum() is just confusing. In MATLAB it's just
Y = [ 1:7, 15:19, 56:59 ]
Is there an better way? Can use numpy if easier.
Bonus question
Can anybody explain why it doesn't work if I use a generator for the sum?
Y = sum( (list(x) for x in ranges) )
TypeError: unsupported operand types for +: 'int' and 'list'
Seems like it doesn't know the starting value should be [] rather than 0!

The matlab syntax has it's equivalent in numpy with numpy.r_:
import numpy as np
np.r_[1:7, 15:19, 56:59]
output: array([ 1, 2, 3, 4, 5, 6, 15, 16, 17, 18, 56, 57, 58])
For a list:
np.r_[1:7, 15:19, 56:59].tolist()
output: [1, 2, 3, 4, 5, 6, 15, 16, 17, 18, 56, 57, 58]

One option, and maybe the closest to the Matlab syntax, is to use the star operator:
>>> [*range(1,8), *range(15,20), *range(56, 60)]
[1, 2, 3, 4, 5, 6, 7, 15, 16, 17, 18, 19, 56, 57, 58, 59]

To fix your code with sum, you can specify an empty list as the start keyword argument as the starting point of aggregation:
sum((list(x) for x in ranges), start=[])

You could write your own generator helper function:
def ranges(*argses):
for args in argses:
yield from range(*args)
for x in ranges((1, 7), (15, 19), (56, 59)):
print(x)
If you need an actual list out of those multi-ranges, then list(ranges(...)).

You could you use itertool's chain.from_iterable, which lazily evaluates the args from a single interable:
>>> list(itertools.chain.from_iterable((range(1,8), range(15,20), range(56,60))))
[1, 2, 3, 4, 5, 6, 7, 15, 16, 17, 18, 19, 56, 57, 58, 59]

Related

Python: Getting all pairs and frequency of pairs in multi-dimensional list

my multi-dimensional list looks as follows:
mylist = [[13, 41, 3, 23, 12, 16], [12, 32, 30, 49, 3, 18],
[34, 12, 14, 24, 35, 20], [29, 28, 12, 44, 13, 4],
[31, 44, 6, 49, 5, 39]]
There are pairs (numbers with difference 1) in some of the lists. not in every list: (12,13) in the first list, (34,35) in the third list, (28,29) and (12,13) in the fourth.
What I want to have is to get all the found pairs which should be saved in a (sorted) list based on the frequency (ascending). In my case above, it would look like follows:
fr_list = [[12,13],[12,13],[28,29],[34,35]]
I wrote the following code to find the pairs
def find_pairs(lst, key):
return [(a,b) for a,b in permutations(lst, 2) if a-b==key]
Then, I tried this:
fr_list = [find_pairs(mylist,1) for x in mylist]
However, I get the following error message:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <listcomp>
File "<stdin>", line 2, in find_pairs
File "<stdin>", line 2, in <listcomp>
TypeError: unsupported operand type(s) for -: 'list' and 'list'
Can anybody help me out? Thanks.
Your code is functionally correct, but your final list comprehension is incorrect. It should be fr_list = [find_pairs(x,1) for x in mylist]. In your case, you attempt to put a list of lists into your find_pairs function. However, changing mylist to x means you are iterating through each nested list, not the entire list of lists for every nested list.
You made a mistake here:
[find_pairs(mylist, 1) for x in mylist]
You passed the same original list everytime to the function and not the sublists. Hence, inside function when you do if a-b==key, a and b are lists.
Correcting this, you can use following code to obtain your desired output:
from itertools import permutations, chain
def find_pairs(lst, key):
return [(a, b) for a, b in permutations(lst, 2) if a - b == key]
mylist = [[13, 41, 3, 23, 12, 16], [12, 32, 30, 49, 3, 18],
[34, 12, 14, 24, 35, 20], [29, 28, 12, 44, 13, 4],
[31, 44, 6, 49, 5, 39]]
temp_list = list(chain.from_iterable([find_pairs(x, 1) for x in mylist]))
fr_list = sorted(temp_list, key=lambda x: temp_list.count(x), reverse=True)
print(fr_list)
You probably should use sorting to avoid comparing each pair with all other pairs, and collections Counter to aggregate the results:
Maybe something like this:
from collections import Counter
def find_pairs(seq):
s = sorted(seq)
all_pairs = []
for first, second in zip(s[:-1], s[1:]):
if second - first == 1:
all_pairs.append((first, second))
return all_pairs
mylist = [[13, 41, 3, 23, 12, 16], [12, 32, 30, 49, 3, 18],
[34, 12, 14, 24, 35, 20], [29, 28, 12, 44, 13, 4],
[31, 44, 6, 49, 5, 39]]
all_pairs = []
for seq in mylist:
all_pairs += find_pairs(seq)
res = []
for pair, qtty in sorted([(k, v) for k, v in Counter(all_pairs).items()], key=lambda x: x[1])[::-1]:
for _ in range(qtty):
res.append(pair)
res
output:
[(12, 13), (12, 13), (5, 6), (28, 29), (34, 35)]
this is how i would go about this:
from collections import Counter
from itertools import repeat, chain
def find_diff(lst):
srt = sorted(lst)
return Counter((a, b) for a, b in zip(srt, srt[1:]) if b - a == 1)
pairs = sum((find_diff(item) for item in mylist), Counter())
# Counter({(12, 13): 2, (34, 35): 1, (28, 29): 1, (5, 6): 1})
res = tuple(
chain.from_iterable(repeat(item, count) for item, count in pairs.most_common())
)
# ((12, 13), (12, 13), (34, 35), (28, 29), (5, 6))
it is more efficient to iterate over the sorted list if you are looking for a difference of 1. then i store everything in Counter objects in order to efficiently find the most common.
the last part is a bit of itertools magic that extracts the items and their multiplicity by frequency.

Periodically slice an list/array

Suppose i have a a = range(1,51). How can i slice a to create a new list that look like this:
[1,2,3,11,12,13,21,22,23,31,32,33,41,42,43]
Is there a pythonic way that can help me do this without writing function?
I know that [start:stop:step] for periodically slicing one element but i'm not sure if i'm missing something obvious.
EDIT: The suggested duplicate question/answer is not the same as mine question. I simply asked to slice/extract periodically elements from a larger list/array. The suggested duplicate modifies elements of existing array.
Another option you can go with logical vector subsetting, something like:
a[(a - 1) % 10 < 3]
# array([ 1, 2, 3, 11, 12, 13, 21, 22, 23, 31, 32, 33, 41, 42, 43])
(a - 1) % 10 finds the remainder of array by 10 (period); and (a - 1) % 10 < 3 gives a logical vector which gives true for the first three elements of every ten elements.
What you want is more complicated than a simple slice, so you're going to need some kind of (likely fairly simple) function to do it. I'd look at using zip to combine multiple slices, something like:
reduce(lambda a,b:a+b, map(list, zip(a[1::10], a[2::10], a[3::10])))
Given:
>>> li=range(1,52)
You can do:
>>> [l for sl in [li[i:i+3] for i in range(0,len(li),10)] for l in sl]
[1, 2, 3, 11, 12, 13, 21, 22, 23, 31, 32, 33, 41, 42, 43, 51]
Or, if you want only full sublists:
>>> [l for sl in [li[i:i+3] for i in range(0,len(li),10)] for l in sl if len(sl)==3]
[1, 2, 3, 11, 12, 13, 21, 22, 23, 31, 32, 33, 41, 42, 43]
Or, given:
>>> li=range(1,51)
Then you do not need to test sublists:
>>> [l for sl in [li[i:i+3] for i in range(0,len(li),10)] for l in sl]
[1, 2, 3, 11, 12, 13, 21, 22, 23, 31, 32, 33, 41, 42, 43]
Psidom's answer's index math can be adapted to a list comprehension too
a = range(1,51)
[n for n in a if (n - 1) % 10 < 3]
Out[23]: [1, 2, 3, 11, 12, 13, 21, 22, 23, 31, 32, 33, 41, 42, 43]

How do i reference values from various ranges within a list?

What I want to do is reference several different ranges from within a list, i.e. I want the 4-6th elements, the 12 - 18th elements, etc. This was my initial attempt:
test = theList[4:7, 12:18]
Which I would expect to give do the same thing as:
test = theList[4,5,6,12,13,14,15,16,17]
But I got a syntax error. What is the best/easiest way to do this?
You can add the two lists.
>>> theList = list(range(20))
>>> theList[4:7] + theList[12:18]
[4, 5, 6, 12, 13, 14, 15, 16, 17]
You can also use itertools module :
>>> from itertools import islice,chain
>>> theList=range(20)
>>> list(chain.from_iterable(islice(theList,*t) for t in [(4,7),(12,18)]))
[4, 5, 6, 12, 13, 14, 15, 16, 17]
Note that since islice returns a generator in each iteration it performs better than list slicing in terms of memory use.
Also you can use a function for more indices and a general way .
>>> def slicer(iterable,*args):
... return chain.from_iterable(islice(iterable,*i) for i in args)
...
>>> list(slicer(range(40),(2,8),(10,16),(30,38)))
[2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 30, 31, 32, 33, 34, 35, 36, 37]
Note : if you want to loop over the result you don't need convert the result to list!
You can add the two lists as #Bhargav_Rao stated. More generically, you can also use a list generator syntax:
test = [theList[i] for i in range(len(theList)) if 4 <= i <= 7 or 12 <= i <= 18]

Intersection between all elements of same list where elements are set [duplicate]

This question already has answers here:
Best way to find the intersection of multiple sets?
(7 answers)
Closed 8 years ago.
I have a list-
list_of_sets = [{0, 1, 2}, {0}]
I want to calculate the intersection between the elements of the list. I have thought about this solution:
a = list_of_sets[0]
b = list_of_sets[1]
c = set.intersection(a,b)
This solution works as i know the number of the elements of the list. (So i can declare as many as variable i need like a,b etc.)
My problem is that i can't figure out a solution for the other case, where the number of the elements of the list is unknown.
N.B: the thought of counting the number of elements of the list using loop and than creating variables according to the result has already been checked. As i have to keep my code in a function (where the argument is list_of_sets), so i need a more generalized solution that can be used for any numbered list.
Edit 1:
I need a solution for all the elements of the list. (not pairwise or for 3/4 elements)
If you wanted the intersection between all elements of all_sets:
intersection = set.intersection(*all_sets)
all_sets is a list of sets. the set is the set type.
For pairwise calculations,
This calculates intersections of all unordered pairs of 2 sets from a list all_sets. Should you need for 3, then use 3 as the argument.
from itertools import combinations, starmap
all_intersections = starmap(set.intersection, combinations(all_sets, 2))
If you did need the sets a, b for calculations, then:
for a, b in combinations(all_sets, 2):
# do whatever with a, b
You want the intersection of all the set. Then:
list_of_sets[0].intersection(*list_of_sets[1:])
Should work.
Take the first set from the list and then intersect it with the rest (unpack the list with the *).
You can use reduce for this. If you're using Python 3 you will have to import it from functools. Here's a short demo:
#!/usr/bin/env python
n = 30
m = 5
#Find sets of numbers i: 1 <= i <= n that are coprime to each number j: 2 <= j <= m
list_of_sets = [set(i for i in range(1, n+1) if i % j) for j in range(2, m+1)]
print 'Sets in list_of_sets:'
for s in list_of_sets:
print s
print
#Get intersection of all the sets
print 'Numbers less than or equal to %d that are coprime to it:' % n
print reduce(set.intersection, list_of_sets)
output
Sets in list_of_sets:
set([1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29])
set([1, 2, 4, 5, 7, 8, 10, 11, 13, 14, 16, 17, 19, 20, 22, 23, 25, 26, 28, 29])
set([1, 2, 3, 5, 6, 7, 9, 10, 11, 13, 14, 15, 17, 18, 19, 21, 22, 23, 25, 26, 27, 29, 30])
set([1, 2, 3, 4, 6, 7, 8, 9, 11, 12, 13, 14, 16, 17, 18, 19, 21, 22, 23, 24, 26, 27, 28, 29])
Numbers less than or equal to 30 that are coprime to it:
set([1, 7, 11, 13, 17, 19, 23, 29])
Actually, we don't even need reduce() for this, we can simply do
set.intersection(*list_of_sets)

Generate inverse sequence

I have a sequence
range(0,50,3)
[0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48]
This is the sequence I want to generate (excluding the 3rd element each time), but being able to start and end at nth number:
[1, 2, 4, 5, 7, 8, ...]
How about this:
def inv_range(start, stop, step):
for val in range(start, stop):
if (val - start) % step != 0:
yield val
print list(inv_range(0,50,3))
This prints
[1, 2, 4, 5, 7, 8, 10, 11, ...
P.S. If you're using Python 2, replace range() with xrange() to get a constant-memory solution.
Build a set of the numbers you want to exclude, and test for that in a list comprehension for the full range:
>>> checkset = set(range(0, 50, 3))
>>> [x for x in range(50) if x not in checkset]
[1, 2, 4, 5, 7, 8, 10, 11, 13, 14, 16, 17, 19, 20, 22, 23, 25, 26, 28, 29, 31, 32, 34, 35, 37, 38, 40, 41, 43, 44, 46, 47, 49]
Converting the list to a set is not critical, but makes for faster lookup when all you're doing is comparing
For this particular case :
[x for x in range(50) if x % 3]
Not the most efficient solution, but quick and easy to understand
excludes = range(0,50,3)
others = [x for x in range(50) if x not in excludes]
How about
r = range(0,50)
del r[::3]
print r
# [1, 2, 4, 5, 7, 8, ...]
For questions like these, where someone is asking "How can I slightly modify the behaviour of a Python built-in function to do X?" I would suggest looking at the PyPy implementation of said function and then just modify it.
In this case, here are PyPy's range() and xrange() implementations.
I realize it's not the easy way out, but you might learn something new in the process.
Basically it looks like what you want is a variant of range() that returns the values which are usually skipped (i.e., the complement of range()). Using range()'s implementation as a starting point, it doesn't look like it would take much work.

Categories

Resources