Generator - parse range of number using 2 generators - python

So I have string that represent numbers separate by '-' and and I need to write 2 generator the get this string and return the range of each numbers.
For example the input string '1-2,4-4,8-10' need to return:
[1, 2, 4, 8, 9, 10]
So the first generator need to return list of numbers (could be list of string) for each iteration so this is what I have done:
def parse_ranges(ranges_string):
range_splitter = (n for n in ranges_string.split(','))
print(next(range_splitter).split('-'))
print(next(range_splitter).split('-'))
print(next(range_splitter).split('-'))
This return:
['1', '2']
['4', '4']
['8', '10']
The second generator need to use this values and return each time all the numbers that exist in the range.
So currently this is what I have try:
numbers = [int(n) for n in list]
This returns list of numbers (minimum and maximum) and now I need to convert it to numbers inside this range.

As you speak of generators, you would at least need to use yield.
Here are the two generators I think you need:
def singlerange(s):
start, stop = map(int, s.split('-'))
yield from range(start, stop + 1)
def multirange(s):
for rng in s.split(','):
yield from singlerange(rng)
Example run:
s = '1-2,4-4,8-10'
print(*multirange(s)) # 1 2 4 8 9 10

For each pair of start,end you need to get the corresponding range [start,end], in python range(start, end+1)
def parse_ranges(ranges_string):
result = []
for str_range in ranges_string.split(','):
start, end = str_range.split("-")
result.extend(range(int(start), int(end) + 1))
return result
s = '1-2,4-4,8-10'
x = parse_ranges(s)
print(x) # [1, 2, 4, 8, 9, 10]

Since you need 2 generators. If you wanted to find all the numbers that exist between 2 numbers just use list(range(start, end+1)). This will include both the start and end number. This doesn't check for duplicate ranges or 2 ranges with intersecting numbers though. But it's a starting point
def stringRangetoNumberRange(stringRange):
return [[int(j) for j in x.split('-')] for x in stringRange.split(',')]
def numberRangetoNumberList(numberRange):
result = []
for i in numberRange:
result += list(range(i[0], i[1]+1))
result.sort()
return result
numberRange = stringRangetoNumberRange("1-2,4-4,8-10")
numberList = numberRangetoNumberList(numberRange)
print(numberList)
# [1, 2, 4, 8, 9, 10]

You can try this.
def parse_ranges(ranges_string):
l = ranges_string.split(',')
range_list = []
for a in l:
range_list.append(a.split('-'))
return range_list
def to_int(range_list):
out_int = []
for a in range_list:
for a in range(int(a[0]),int(a[1])+1):
out_int.append(a)
return out_int
result = to_int(parse_ranges('1-2,4-4,8-10'))
print(result) # [1, 2, 4, 8, 9, 10]

Related

Using recursion to convert list members into an integer value

I cannot get listA as integer 2345 with this recursion function. I want to convert [2,3,4,5] to 2345.
listA = [2,3,4,5]
n = len(listA)
def fun(n):
if(n == 0):
return listA[n]
else:
return fun((listA[n]) + (10**n))
fun(listA)
Without len() and powers of 10:
def conv(lst):
return 10*conv(lst[:-1]) + lst[-1] if lst else 0
print(conv([2,3,4,5])) # 2345
listA = [2,3,4,5]
def turnLstToInt(lst):
return turnLstToIntHelper(lst,0)
def turnLstToIntHelper(lst,index):
if index>len(lst)-1: # if greater than list return 0
return 0
power = len(lst)-1-index # find 10's power of digit
placeVal = lst[index]*(10**power) # get value of digit at power level
return placeVal + turnLstToIntHelper(lst,index+1)
print(turnLstToInt(listA))
You've got the right idea, but you have some issues.
You aren't going through the list but rather running the recursion on the "n-th" element of the list (which isn't even a valid index).
You have no "quit" condition.
You aren't concatenating the list together, but concatenating the length of the list together.
You will find that the following code solves these issues.
def fun(listA):
n = len(listA)
if n == 0:
return
elif n == 1:
return listA[0]
else:
return int(str(listA[0]) + str(fun(listA[1:])))
listA = [2,3,4,5]
fun(listA)
If you aren't comfortable with type-casting, you can change the final return statement:
return (10**(n-1))*listA[0] + fun(listA[1:])
I understand that this question is requesting recursion, but an interesting (and probably much faster) solution is the following:
def fun(lst):
return sum([n*10**i for i, n in enumerate(lst[::-1])])
Test
>>> fun([1,2,3,4])
1234
>>> fun([1,0,0,0,0,1,0,0,0,1])
1000010001
>>> fun([0,0,0,5])
5
>>> fun([9,8,7,6,5,4,3,2,1,0] * 10)
9876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210
>>> len(str(fun([9,8,7,6,5,4,3,2,1,0] * 10)))
100
Explanation
Assume lst = [2, 3, 4, 5].
def fun(lst):
return sum(
[
n*10**i for i, n in
enumerate(
lst[::-1]
)
]
)
lst[::-1] takes a list of number and reverses it, e.g. [2, 3, 4, 5] → [5, 4, 3, 2]. This is needed because of the later logic.
enumerate() takes a list of items, and returns a generator (essentially just a list) of tuples, the same length as the input list, each tuple containing two items: the index of the item in the list, and the item itself, e.g. [5, 4, 3, 2] → [[(0, 5), (1, 4), (2, 3), (3, 2)]].
n*10**i for i, n this list comprehension is a cool bit of code. It takes the list of index/item pairs returned by enumerate, and multiples the number by 10 raised to the power of index, e.g. [(0, 5), (1, 4), (2, 3), (3, 2)]] → [5*1, 4*10, 3*100, 2*1000] → [5, 40, 300, 2000]
sum adds the resulting numbers together, e.g.
5 + 40 + 300 + 2000 = 2000 + 300 + 40 + 5
2000 +
300 +
40 +
5 =
2345
That's it!
You don't actually need to get the length of the list beforehand. Instead you can get the length as you go.
l1 = [2,3,4,5]
def fun(l):
if l1:
return l1.pop(0)*(10**len(l1)) + fun(l)
return 0
print(fun(l1))
Output:
2345

Trying to figure out how to iterate a list twice in a One function

I am trying to figure out how to iterate over fn to modify the list again with only even numbers, AFTER appending. The desire result is
[2,4,6,8,10,12,14,16,18,20]
is this possible? I do not want to add fn + sn or lst + lst2. I want to continue over this specific function.
lst = [1,2,3,4,5,6,7,8,9,10]
lst2 =[11,12,13,14,15,16,17,18,19,20]
def even(fn,sn):
for i in sn:
if i %2 ==0:
fn.append(i)
even(lst,lst2)
print(lst) # output [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 20]
I know there are more efficient and pythonic ways like this example:
example 1:
def even2(fn,sn):
fn[:] = [x for x in fn + sn if x % 2 == 0]
example 2:
def even(fn, sn):
fn.extend(sn)
fn[:] = [x for x in fn if x % 2 == 0]
def even(fn,sn):
for i in sn:
if i %2 ==0:
fn.append(i)
fn = list(filter(lambda x: x %2 == 0, fn))
the last code block is what i tried and failed with
You don't need to rebuild the entire list; just extend it with the desired extra elements.
>>> lst = [1,2,3,4,5,6,7,8,9,10]
>>> lst2 =[11,12,13,14,15,16,17,18,19,20]
>>> def even(fn, sn):
... fn.extend(i for i in sn if i % 2 == 0)
...
>>> even(lst, lst2)
>>> lst
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 20]
In the above expression i for i... is a generator expression that allows extend to iterate over all of the i elements without storing them in a separate list first.
You could do it in two steps:
remove odd numbers from fn
extend fn with the even numbers in sn
This function does it without creating any temporary copies or sublists:
lst = [1,2,3,4,5,6,7,8,9,10]
lst2 =[11,12,13,14,15,16,17,18,19,20]
def even(fn,sn):
for i,n in enumerate(reversed(fn),1-len(fn)): # delete odds backwards
if n%2: del fn[-i] # so indexes stay stable
fn.extend(n for n in sn if n%2==0) # extend with sn's evens
even(lst,lst2)
print(lst)
# [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
Note that you could extend fn first and then remove the odd numbers, but that would be inefficient:
def even(fn,sn):
fn.extend(sn) # add all sn values
for i,n in enumerate(reversed(fn),1-len(fn)): # delete odds backwards
if n%2: del fn[-i] # so indexes stay stable
A more efficient way would be to iterate through the two lists and assign the result to fn[:]. Doing this starting with fn will ensure that there is no conflict between the iterators and the assignment process:
def even(fn,sn):
fn[:] = (n for L in (fn,sn) for n in L if n%2==0)
The nested comprehension loops allow iterating through the two lists without actually concatenating them so there is no temporary copies or sublists in that solution either.
Does this meet your requirements? Pulling lst into the even() func as a global.
lst = [1,2,3,4,5,6,7,8,9,10]
lst2 =[11,12,13,14,15,16,17,18,19,20]
def even(fn, sn):
global lst
lst = [x for x in (fn + sn) if x % 2 == 0]
even(lst,lst2)
print(lst)

Can you for loop completely through a range, but starting from the nth element?

I would like to know if there exists a base solution to do something like this:
for n in range(length=8, start_position= 3, direction= forward)
The problem I'm encountering is I would like the loop to continue past the final index, and pick up again at idx =0, then idx=1, etc. and stop at idx= 3, the start_position.
To give context, I seek all possible complete solutions to the n-queen problem.
Based on your latest edit, you need a "normal" range and the modulo operator:
for i in range(START, START + LEN):
do_something_with(i % LEN)
from itertools import chain
for n in chain(range(3,8), range(3)):
...
The chain() returns an iterator with 3, 4, ..., 7, 0, 1, 2
Another option for solving this is to use modular arithmetic. You could do something like this, for example:
for i in range(8)
idx = (i + 3) % 8
# use idx
This easily can be generalized to work with different lengths and offsets.
def loop_around_range(length, start_position, direction='forward'):
looped_range = [k % length for k in range(start_position, start_position+length)]
if direction == 'forward':
return looped_range
else:
return looped_range[::-1]
You could implement this for an arbitrary iterable by using itertools.cycle.
from itertools import cycle
def circular_iterator(iterable, skip=0, length=None, reverse=False):
"""Produces a full cycle of #iterable#, skipping the first #skip# elements
then tacking them on to the end.
if #iterable# does not implement #__len__#, you must provide #length#
"""
if reverse:
iterable = reversed(iterable)
cyc_iter = cycle(iterable)
for _ in range(skip):
next(cyc_iter, None)
if length:
total_length = length
else:
total_length = len(iterable)
for _ in range(total_length):
yield next(cyc_iter, None)
>>> lst = [x for x in range(1, 9)]
# [1, 2, 3, 4, 5, 6, 7, 8]
>>> list(circular_iterator(lst, skip=3))
[4, 5, 6, 7, 8, 1, 2, 3]

Expand a range which looks like: "1-3,6,8-10" to [1,2,3, 6, 8,9,10]

I am trying to add an option to my program which allow the user to choose which steps of the program he wants to do.
I would like to be able to parse a string like "1-3,6,8-10" and get [1, 2, 3, 6, 8, 9, 10].
Do you know if something in Python which is doing that already exists ?
This function does what you asked. It assumes no negative numbers are used, otherwise it needs some changes to support that case.
def mixrange(s):
r = []
for i in s.split(','):
if '-' not in i:
r.append(int(i))
else:
l,h = map(int, i.split('-'))
r+= range(l,h+1)
return r
print mixrange('1-3,6,8-10')
One way using list comprehensions:
s = "1-3,6,8-10"
x = [ss.split('-') for ss in s.split(',')]
x = [range(int(i[0]),int(i[1])+1) if len(i) == 2 else i for i in x]
print([int(item) for sublist in x for item in sublist])
Outputs:
[1, 2, 3, 6, 8, 9, 10]
No builtin function as such, but can be done using xrange and generators:
from itertools import chain
s = "1-3,6,8-10"
spans = (el.partition('-')[::2] for el in s.split(','))
ranges = (xrange(int(s), int(e) + 1 if e else int(s) + 1) for s, e in spans)
all_nums = chain.from_iterable(ranges) # loop over, or materialse using `list`
# [1, 2, 3, 6, 8, 9, 10]
s = '1-3,6,8-10,13-16'
temp = [x.split('-') if '-' in x else x for x in s.split(',')]
# temp = [['1', '3'], '6', ['8', '10'], ['13', '16']]
res = []
for l in temp:
if isinstance(l, list):
a, b = map(int, l)
res += list(range(a, b + 1))
else:
res.append(int(l))
# res = [1, 2, 3, 6, 8, 9, 10, 13, 14, 15, 16]
A little function I just created:
def expand(st):
res = []
for item in st.split(','):
if '-' in item:
temp = map(int, item.split('-'))
res.extend(range(temp[0], temp[1]+1))
else:
res.append(int(item))
return res
s = '1-3,6,8-10'
print expand(s)
Returns:
[1, 2, 3, 6, 8, 9, 10]
def parseIntSet(nputstr=""):
selection = set()
invalid = set()
# tokens are comma seperated values
tokens = [x.strip() for x in nputstr.split(',')]
for i in tokens:
try:
# typically tokens are plain old integers
selection.add(int(i))
except:
# if not, then it might be a range
try:
token = [int(k.strip()) for k in i.split('-')]
if len(token) > 1:
token.sort()
# we have items seperated by a dash
# try to build a valid range
first = token[0]
last = token[len(token)-1]
for x in range(first, last+1):
selection.add(x)
except:
# not an int and not a range...
invalid.add(i)
# Report invalid tokens before returning valid selection
print "Invalid set: " + str(invalid)
return selection
Via: Parsing a list of numbers in Python
Aha, one line proof of concept anyone?
EDIT: Improved version
import itertools
s = "1-3,6,8-10"
print(list(itertools.chain.from_iterable(range(int(ranges[0]), int(ranges[1])+1) for ranges in ((el+[el[0]])[:2] for el in (miniRange.split('-') for miniRange in s.split(','))))))
Now split on several lines for readability:
print(list(itertools.chain.from_iterable(
range(
int(ranges[0]),
int(ranges[1])+1
)
for ranges in
(
(el+[el[0]])[:2] # Allows to get rid of the ternary condition by always adding a duplicate of the first element if it is alone
for el in
(miniRange.split('-') for miniRange in s.split(','))
)
)))

Python finding repeating sequence in list of integers?

I have a list of lists and each list has a repeating sequence. I'm trying to count the length of repeated sequence of integers in the list:
list_a = [111,0,3,1,111,0,3,1,111,0,3,1]
list_b = [67,4,67,4,67,4,67,4,2,9,0]
list_c = [1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,23,18,10]
Which would return:
list_a count = 4 (for [111,0,3,1])
list_b count = 2 (for [67,4])
list_c count = 10 (for [1,2,3,4,5,6,7,8,9,0])
Any advice or tips would be welcome. I'm trying to work it out with re.compile right now but, its not quite right.
Guess the sequence length by iterating through guesses between 2 and half the sequence length. If no pattern is discovered, return 1 by default.
def guess_seq_len(seq):
guess = 1
max_len = len(seq) / 2
for x in range(2, max_len):
if seq[0:x] == seq[x:2*x] :
return x
return guess
list_a = [111,0,3,1,111,0,3,1,111,0,3,1]
list_b = [67,4,67,4,67,4,67,4,2,9,0]
list_c = [1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,23,18,10]
print guess_seq_len(list_a)
print guess_seq_len(list_b)
print guess_seq_len(list_c)
print guess_seq_len(range(500)) # test of no repetition
This gives (as expected):
4
2
10
1
As requested, this alternative gives longest repeated sequence. Hence it will return 4 for list_b. The only change is guess = x instead of return x
def guess_seq_len(seq):
guess = 1
max_len = len(seq) / 2
for x in range(2, max_len):
if seq[0:x] == seq[x:2*x] :
guess = x
return guess
I took Maria's faster and more stackoverflow-compliant answer and made it find the largest sequence first:
def guess_seq_len(seq, verbose=False):
seq_len = 1
initial_item = seq[0]
butfirst_items = seq[1:]
if initial_item in butfirst_items:
first_match_idx = butfirst_items.index(initial_item)
if verbose:
print(f'"{initial_item}" was found at index 0 and index {first_match_idx}')
max_seq_len = min(len(seq) - first_match_idx, first_match_idx)
for seq_len in range(max_seq_len, 0, -1):
if seq[:seq_len] == seq[first_match_idx:first_match_idx+seq_len]:
if verbose:
print(f'A sequence length of {seq_len} was found at index {first_match_idx}')
break
return seq_len
This worked for me.
def repeated(L):
'''Reduce the input list to a list of all repeated integers in the list.'''
return [item for item in list(set(L)) if L.count(item) > 1]
def print_result(L, name):
'''Print the output for one list.'''
output = repeated(L)
print '%s count = %i (for %s)' % (name, len(output), output)
list_a = [111, 0, 3, 1, 111, 0, 3, 1, 111, 0, 3, 1]
list_b = [67, 4, 67, 4, 67, 4, 67, 4, 2, 9, 0]
list_c = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2,
3, 4, 5, 6, 7, 8, 9, 0, 23, 18, 10
]
print_result(list_a, 'list_a')
print_result(list_b, 'list_b')
print_result(list_c, 'list_c')
Python's set() function will transform a list to a set, a datatype that can only contain one of any given value, much like a set in algebra. I converted the input list to a set, and then back to a list, reducing the list to only its unique values. I then tested the original list for each of these values to see if it contained that value more than once. I returned a list of all of the duplicates. The rest of the code is just for demonstration purposes, to show that it works.
Edit: Syntax highlighting didn't like the apostrophe in my docstring.

Categories