Periodically slice an list/array - python

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]

Related

Combining a list of ranges in 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]

How to expand hyphenated numbers from a file into a range of numbers?

I have a text file formatted like so
1-8
10-12
14-45
48-50
How do I go about getting the range of each line?
1, 2, 3, 4, 5, 6, 7, 8
10, 11, 12
I've tried splitting the file to get each number into a list. 1, 8, 10, 12, 14, 45, 48, 50, but I'm not sure how to translate that into the start and end numbers for range.
data = []
with open('file.txt','r') as myfile:
for line in myfile:
data.extend(map(int, line.split('-')))
print (data)
You can use a short list comprehension to extract the numbers from a given line. Since the ranges you listed seem to be inclusive on both ends, we need to add one to end interval (j) below.
for line in myfile:
i, j = [int(n) for n in line.split("-")]
for x in range(i, j + 1):
# do things
If you simply want to stick the two numbers in a range, here's an alternative:
range(*[int(n) for n in line.split("-")])
If you want to list out the numbers in the range, you need to wrap the expression in a list().
You just need to extract the start and end index from each line and use it in range to create your list
data = []
with open('file.txt','r') as myfile:
for line in myfile:
start,end = [int(item) for item in line.split('-')]
li = list(range(start ,end+1))
data.append(li)
print(data)
So if the input is:
1-8
10-12
14-45
48-50
The output will be:
[
[1, 2, 3, 4, 5, 6, 7, 8],
[10, 11, 12],
[14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45],
[48, 49, 50]
]
If you just want to print the ranges, you can use this:
with open('file.txt', 'r') as myfile:
for line in myfile:
nums = [int(i) for i in line.split('-')]
nums[1] += 1 # more on this in a second
my_range = range(*nums) # the * unpacks the two numbers into two arguments
print([i for i in my_range])
# [1, 2, 3, 4, 5, 6, 7, 8]
Walking through it, the first line in the for loop you pretty much have already. This just grabs the two numbers and interprets them as integers. After this line, nums = [1, 8] for your first line '1-8'. Next we add one to the the last element of the list, so nums = [1, 9] now. We do this because the range(a, b) builtin generates numbers from a to b-1.
Next we create a range that will generate the numbers that you want with range(*nums). The * in that statement unpacks the list into the two arguments that range() is expecting.
Finally we print all of the items in the range. Since my_range is now a generator, we need to unpack it to print, so we use a list comprehension to iterate over it and get all of the numbers.
You're very close, you just need to append the list range():
data = []
with open("file.txt") as f:
for line in f:
start, end = map(int, line.split("-"))
data.append(list(range(start, end + 1)))
print(data)
# [[1, 2, 3, 4, 5, 6, 7, 8], [10, 11, 12], [14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45], [48, 49, 50]]
And also making sure the end is incremented by one because it is not inclusive in range().
How about something like this?
Of course, it has no error checking, so you'd want to be mindful of the quality of your input data.
data = []
with open('file.txt','r') as myfile:
for line in myfile:
# Split the line and force conversion to int
start_int, end_int = map(int, line.split('-'))
# Get a Python 3 range - note this is not a list but a "range" type in Python 3, so we'll have to convert it before appending to our global list
int_range = range(start_int, end_int+1)
data.append(list(int_range))
I believe that you can match the pattern using capturing groups using Regex and add it into your list.
Regex: https://regex101.com/r/f1Ghff/1
(\d+)\-(\d+)
Explanation:
1st Capturing Group (\d+)
\d+ matches a digit (equal to [0-9])
+ Quantifier — Matches between one and unlimited times, as many times as possible, giving back as needed (greedy)
\- matches the character - literally (case sensitive)
2nd Capturing Group (\d+)
\d+ matches a digit (equal to [0-9])
+ Quantifier — Matches between one and unlimited times, as many times as possible, giving back as needed (greedy)
The code:
import re
str = """
1-8
10-12
14-45
48-50
"""
pattern = r"(\d+)\-(\d+)"
matches = re.findall(pattern, str)
ranges = []
# Convert Group1 and Group2 into integers
for tuple in matches:
low = int(tuple[0])
high = int(tuple[1])
ranges.append(list(range(low, high)))
print (ranges)
Outputting:
[[1, 2, 3, 4, 5, 6, 7], [10, 11], [14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44], [48, 49]]

Print list in specified range Python

I'm new to Python and I have this problem
I have a list of numbers like this:
n = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
I want to print from 11 to 37, that means the output = 11, 13,.... 37.
I tried to print(n[11:37]) but of course it will print [37, 41, 43, 47]
because that is range index.
Any ideas or does Python have any built-in method for this ?
This should do the job...
n = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
n.sort()
mylist = [x for x in n if x in range(11, 38)]
print(mylist)
Want to print that as comma separated string:
print(mylist.strip('[]'))
This will work. (Assuming list is sorted)
print n[n.index(11): n.index(37)+1]
Output:
[11, 13, 17, 19, 23, 29, 31, 37]
Considering your list is ordered and it has no duplicates:
n = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
print(",".join(map(str,n[n.index(11): n.index(37)+1])))
Here you have a live example
Using numpy:
import numpy as np
narr = np.array(n)
m = (narr >= 11) & (narr <= 37)
for v in narr[m]:
print(v)
# or, to get rid of the loop:
print('\n'.join(map(str, narr[m])))
it pretty simple, since your list is already sorted you can write
my_list = [x for x in n if x in range(11, 38)]
print(*my_list)
what the '*' does is that it unpacks the array into individual elements, a term known as unpacking.This will produce the actual result you wanted and not an array
If your data is sorted, you can use a generator expression with either a range object or chained comparisons:
n = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
print(*(i for i in n if i in range(11, 38)), sep=', ')
print(*(i for i in n if 11 <= i <= 37), sep=', ')
If your data is unsorted and you can use indices of the first occurrences of each value, you can slice your list:
print(*n[n.index(11): n.index(37)+1], sep=', ')
Result with the data you have provided:
11, 13, 17, 19, 23, 29, 31, 37

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.

python combining a range and a list of numbers

range(5, 15) [1, 1, 5, 6, 10, 10, 10, 11, 17, 28]
range(6, 24) [4, 10, 10, 10, 15, 16, 18, 20, 24, 30]
range(7, 41) [9, 18, 19, 23, 23, 26, 28, 40, 42, 44]
range(11, 49) [9, 23, 24, 27, 29, 31, 43, 44, 45, 45]
range(38, 50) [1, 40, 41, 42, 44, 48, 49, 49, 49, 50]
I get the above outpout from a print command from a function. What I really want is a combined list of the range, for example in the top line 5,6,7...15,1,1,5,6 etc.
The output range comes from
range_draws=range(int(lower),int(upper))
which I naively thought would give a range. The other numbers come from a sliced list.
Could someone help me to get the desired result.
The range() function returns a special range object to save on memory (no need to keep all the numbers in memory when only the start, end and step size will do). Cast it to a list to 'expand' it:
list(yourrange) + otherlist
To quote the documentation:
The advantage of the range type over a regular list or tuple is that a range object will always take the same (small) amount of memory, no matter the size of the range it represents (as it only stores the start, stop and step values, calculating individual items and subranges as needed).

Categories

Resources