Convert List of Numbers to String Ranges - python

I'd like to know if there is a simple (or already created) way of doing the opposite of this: Generate List of Numbers from Hyphenated.... This link could be used to do:
>> list(hyphen_range('1-9,12,15-20,23'))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 15, 16, 17, 18, 19, 20, 23]:
I'm looking to do the opposite (note that 10 and 21 are included so it would be compatible with the range function, where range(1,10)=[1,2,3,4,5,6,7,8,9]):
>> list_to_ranges([1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 15, 16, 17, 18, 19, 20, 23])
'1-10,12,15-21,23'
Eventually, I would like to have the output also incorporate a step where the last number of the output indicates the step:
>> list_to_ranges([1, 3, 5, 7, 8, 9, 10, 11])
'1-13:2,8,10'
Essentially, this would end up being kind of like an "inverse" range function
>> tmp = list_to_ranges([1, 3, 5])
>> print tmp
'1-7:2'
>> range(1, 7, 2)
[1, 3, 5]
My guess is that there is no really easy/simple way to do this, but I thought I would ask on here before I go make some brute force, long method.
EDIT
Using the code from an answer to this post as an example, I came up with a simple way to do the first part. But I think that identifying the patterns to do steps would be a bit harder.
from itertools import groupby
from operator import itemgetter
data = [ 1, 4,5,6, 10, 15,16,17,18, 22, 25,26,27,28]
print data, '\n'
str_list = []
for k, g in groupby(enumerate(data), lambda (i,x):i-x):
ilist = map(itemgetter(1), g)
print ilist
if len(ilist) > 1:
str_list.append('%d-%d' % (ilist[0], ilist[-1]+1))
else:
str_list.append('%d' % ilist[0])
print '\n', ','.join(str_list)
EDIT 2
Here is my attempt at including the step size...it is pretty close, but the first numbers get repeated. I think that with a little bit of tweaking of this, it will be close to what I want - or at least good enough.
import numpy as np
from itertools import groupby
def list_to_ranges(data):
data = sorted(data)
diff_data = np.diff(data).tolist()
ranges = []
i = 0
for k, iterable in groupby(diff_data, None):
rng = list(iterable)
step = rng[0]
if len(rng) == 1:
ranges.append('%d' % data[i])
elif step == 1:
ranges.append('%d-%d' % (data[i], data[i+len(rng)]+step))
else:
ranges.append('%d-%d:%d' % (data[i], data[i+len(rng)]+step, step))
i += len(rng)
return ','.join(ranges)
data = [1, 3, 5, 6, 7, 11, 13, 15, 16, 17, 18, 19, 22, 25, 28]
print data
data_str = list_to_ranges(data)
print data_str
_list = []
for r in data_str.replace('-',':').split(','):
r = [int(a) for a in r.split(':')]
if len(r) == 1:
_list.extend(r)
elif len(r) == 2:
_list.extend(range(r[0], r[1]))
else:
_list.extend(range(r[0], r[1], r[2]))
print _list
print list(set(_list))

One approach could be "eating" piece by piece the input sequence and store the partial range results untill you've got them all:
def formatter(start, end, step):
return '{}-{}:{}'.format(start, end, step)
# return '{}-{}:{}'.format(start, end + step, step)
def helper(lst):
if len(lst) == 1:
return str(lst[0]), []
if len(lst) == 2:
return ','.join(map(str,lst)), []
step = lst[1] - lst[0]
for i,x,y in zip(itertools.count(1), lst[1:], lst[2:]):
if y-x != step:
if i > 1:
return formatter(lst[0], lst[i], step), lst[i+1:]
else:
return str(lst[0]), lst[1:]
return formatter(lst[0], lst[-1], step), []
def re_range(lst):
result = []
while lst:
partial,lst = helper(lst)
result.append(partial)
return ','.join(result)
I test it with a bunch of unit tests and it passed them all, it can handle negative numbers too, but they'll look kind of ugly (it's really anybody's fault).
Example:
>>> re_range([1, 4,5,6, 10, 15,16,17,18, 22, 25,26,27,28])
'1,4-6:1,10,15-18:1,22,25-28:1'
>>> re_range([1, 3, 5, 7, 8, 9, 10, 11, 13, 15, 17])
'1-7:2,8-11:1,13-17:2'
Note: I wrote the code for Python 3.
Performance
I didn't put any performance effort in the solution above. In particular, every time a list get re-builded with slicing, it might take some time if the input list has a particular shape. So, the first simple improvement would be using itertools.islice() where possible.
Anyway here's another implementation of the same algorithm, that scan through the input list with a scan index instead of slicing:
def re_range(lst):
n = len(lst)
result = []
scan = 0
while n - scan > 2:
step = lst[scan + 1] - lst[scan]
if lst[scan + 2] - lst[scan + 1] != step:
result.append(str(lst[scan]))
scan += 1
continue
for j in range(scan+2, n-1):
if lst[j+1] - lst[j] != step:
result.append(formatter(lst[scan], lst[j], step))
scan = j+1
break
else:
result.append(formatter(lst[scan], lst[-1], step))
return ','.join(result)
if n - scan == 1:
result.append(str(lst[scan]))
elif n - scan == 2:
result.append(','.join(map(str, lst[scan:])))
return ','.join(result)
I stopped working on it once it got ~65% faster than the previous top solution, it seemed enough :)
Anyway I'd say that there might still be room for improvement (expecially in the middle for-loop).

This is a comparison of the 3 methods. Change the amount of data and the density via the values below...no matter what values I use, the first solution seems to be the quickest for me. For very large sets of data, the third solution becomes very slow.
EDITED
Edited to include comments below and add in a new solution. The last solution seems to be the quickest now.
import numpy as np
import itertools
import random
import timeit
# --- My Solution --------------------------------------------------------------
def list_to_ranges1(data):
data = sorted(data)
diff_data = np.diff(data)
ranges = []
i = 0
skip_next = False
for k, iterable in itertools.groupby(diff_data, None):
rng = list(iterable)
step = rng[0]
if skip_next:
skip_next = False
rng.pop()
if len(rng) == 0:
continue
elif len(rng) == 1:
ranges.append('%d' % data[i])
elif step == 1:
ranges.append('%d-%d' % (data[i], data[i+len(rng)]+step))
i += 1
skip_next = True
else:
ranges.append('%d-%d:%d' % (data[i], data[i+len(rng)]+step, step))
i += 1
skip_next = True
i += len(rng)
if len(rng) == 0 or len(rng) == 1:
ranges.append('%d' % data[i])
return ','.join(ranges)
# --- Kaidence Solution --------------------------------------------------------
# With a minor edit for use in range function
def list_to_ranges2(data):
onediff = np.diff(data)
twodiff = np.diff(onediff)
increments, breakingindices = [], []
for i in range(len(twodiff)):
if twodiff[i] != 0:
breakingindices.append(i+2) # Correct index because of the two diffs
increments.append(onediff[i]) # Record the increment for this section
# Increments and breakingindices should be the same size
str_list = []
start = data[0]
for i in range(len(breakingindices)):
str_list.append("%d-%d:%d" % (start,
data[breakingindices[i]-1] + increments[i],
increments[i]))
start = data[breakingindices[i]]
str_list.append("%d-%d:%d" % (start,
data[len(data)-1] + onediff[len(onediff)-1],
onediff[len(onediff)-1]))
return ','.join(str_list)
# --- Rik Poggi Solution -------------------------------------------------------
# With a minor edit for use in range function
def helper(lst):
if len(lst) == 1:
return str(lst[0]), []
if len(lst) == 2:
return ','.join(map(str,lst)), []
step = lst[1] - lst[0]
#for i,x,y in itertools.izip(itertools.count(1), lst[1:], lst[2:]):
for i,x,y in itertools.izip(itertools.count(1),
itertools.islice(lst, 1, None, 1),
itertools.islice(lst, 2, None, 1)):
if y-x != step:
if i > 1:
return '{}-{}:{}'.format(lst[0], lst[i]+step, step), lst[i+1:]
else:
return str(lst[0]), lst[1:]
return '{}-{}:{}'.format(lst[0], lst[-1]+step, step), []
def list_to_ranges3(lst):
result = []
while lst:
partial,lst = helper(lst)
result.append(partial)
return ','.join(result)
# --- Rik Poggi Solution 2 -----------------------------------------------------
def formatter(start, end, step):
#return '{}-{}:{}'.format(start, end, step)
return '{}-{}:{}'.format(start, end + step, step)
def list_to_ranges4(lst):
n = len(lst)
result = []
scan = 0
while n - scan > 2:
step = lst[scan + 1] - lst[scan]
if lst[scan + 2] - lst[scan + 1] != step:
result.append(str(lst[scan]))
scan += 1
continue
for j in xrange(scan+2, n-1):
if lst[j+1] - lst[j] != step:
result.append(formatter(lst[scan], lst[j], step))
scan = j+1
break
else:
result.append(formatter(lst[scan], lst[-1], step))
return ','.join(result)
if n - scan == 1:
result.append(str(lst[scan]))
elif n - scan == 2:
result.append(','.join(itertools.imap(str, lst[scan:])))
return ','.join(result)
# --- Test Function ------------------------------------------------------------
def test_data(data, f_to_test):
data_str = f_to_test(data)
_list = []
for r in data_str.replace('-',':').split(','):
r = [int(a) for a in r.split(':')]
if len(r) == 1:
_list.extend(r)
elif len(r) == 2:
_list.extend(range(r[0], r[1]))
else:
_list.extend(range(r[0], r[1], r[2]))
return _list
# --- Timing Tests -------------------------------------------------------------
# Generate some sample data...
data_list = []
for i in range(5):
# Note: using the "4000" and "5000" values below, the relative density of
# the data can be changed. This has a huge effect on the results
# (particularly on the results for list_to_ranges3 which uses recursion).
data_list.append(sorted(list(set([random.randint(1,4000) for a in \
range(random.randint(5,5000))]))))
testfuncs = list_to_ranges1, list_to_ranges2, list_to_ranges3, list_to_ranges4
for f in testfuncs:
print '\n', f.__name__
for i, data in enumerate(data_list):
t = timeit.Timer('f(data)', 'from __main__ import data, f')
#print f(data)
print i, data==test_data(data, f), round(t.timeit(200), 3)

This is most likely what you are looking for.
Edit: I see you already found the post. My apologies.
To help with the second part, I've tinkered a bit myself. This is what I came up with:
from numpy import diff
data = [ 1, 3, 5, 7, 8, 9, 10, 11, 13, 15, 17 ]
onediff, twodiff = diff(data), diff(diff(data))
increments, breakingindices = [], []
for i in range(len(twodiff)):
if twodiff[i] != 0:
breakingindices.append(i+2) # Correct index because of the two diffs
increments.append(onediff[i]) # Record the increment for this section
# Increments and breakingindices should be the same size
str_list = []
start = data[0]
for i in range(len(breakingindices)):
str_list.append("%d-%d:%d" % (start, data[breakingindices[i]-1], increments[i]))
start = data[breakingindices[i]]
str_list.append("%d-%d:%d" % (start, data[len(data)-1], onediff[len(onediff)-1]))
print str_list
For the given input list, this gives: ['1-7:2', '8-11:1', '13-17:2']. The code could do with a bit of cleanup, but this sorts with your problem assuming the grouping can be done sequentially.
{caution: for [1,2,3,5,6,7] this gives ['1-3:1', '5-5:2', '6-7:1'] instead of ['1-3:1', '5-7:1']}

This is similar to versions that handle the step-size-of-one case enumerated here but also handles the singleton (elements with no more than 2 elements in a sequence or repeated elements) and non-unitary step sizes (including negative step sizes). It also does not drop duplicates for lists like [1, 2, 3, 3, 4, 5].
As for runtime: it's done before you blink.
def ranges(L):
"""return a list of singletons or ranges of integers, (first, last, step)
as they occur sequentially in the list of integers, L.
Examples
========
>>> list(ranges([1, 2, 4, 6, 7, 8, 10, 12, 13]))
[1, (2, 6, 2), 7, (8, 12, 2), 13]
>>> list(ranges([1,2,3,4,3,2,1,3,5,7,11,1,2,3]))
[(1, 4, 1), (3, 1, -1), (3, 7, 2), 11, (1, 3, 1)]
"""
if not L:
return []
r = []
for i in L:
if len(r) < 2:
r.append(i)
if len(r) == 2:
d = r[1] - r[0]
else:
if i - r[1] == d:
r[1] = i
else:
if r[1] - r[0] == d:
yield(r.pop(0))
r.append(i)
d = r[1] - r[0]
else:
yield(tuple(r+[d]))
r[:] = [i]
if len(r) == 1:
yield(r.pop())
elif r[1] - r[0] == d:
for i in r:
yield i
else:
yield(tuple(r+[d]))
The raw output can be modified as desired, e.g. actual range instances can be created.
def sranges(i):
"""return pretty string for output of ranges.
Examples
========
>>> sranges([1,2,4,6,7,8,10,12,13,15,16,17])
'1, range(2, 8, 2), 7, range(8, 14, 2), 13, range(15, 18)'
"""
out = []
for i in ranges(i):
if type(i) is int:
out.append(str(i))
elif i[-1] == 1:
if i[0] == 0:
out.append('range(%s)'%(i[1] + 1))
else:
out.append('range(%s, %s)'%(i[0], i[1] + 1))
else:
out.append('range(%s, %s, %s)'%(i[0], i[1] + i[2], i[2]))
return ', '.join(out)

This function should do what you need without requiring any imports.
def listToRanges(self, intList):
ret = []
for val in sorted(intList):
if not ret or ret[-1][-1]+1 != val:
ret.append([val])
else:
ret[-1].append(val)
return ",".join([str(x[0]) if len(x)==1 else str(x[0])+"-"+str(x[-1]) for x in ret])

Related

Can I return the base case as answer?

def encryption(n):
if len(n) == 2:
return n
result = []
for i in range(len(n) - 1):
total = n[i] + n[i + 1]
right_most_digit = total % 10
result.append(right_most_digit)
if len(result) > 2:
result = encryption(result)
return "".join([str(x) for x in result])
if __name__ == '__main__':
numbers = [1, 5, 7, 9]
print(encryption(numbers))
I need help with this code.
The problem is to add the two adjacent numbers and keep the right_most_digit, repeat the process to a point where only two numbers left, and return a string.
For example, 1+5, 5+7, 7+9 will be 6,2,6, then 6+2, 2+6 will be 8,8, then return 88 as a string.
With the least changes,
def encryption(n):
if len(n) == 2:
return n
result = []
for i in range(len(n) - 1):
total = n[i] + n[i + 1]
right_most_digit = total % 10
result.append(right_most_digit)
if len(result) > 2:
result = encryption(result)
return result
if __name__ == '__main__':
numbers = [1, 5, 7, 9]
print("".join([str(x) for x in encryption(numbers)]))
print("".join([str(x) for x in encryption(numbers)]).lstrip("0")) if you also want to strip any leading 0
Since you asked about a different approach, you might consider not doing this recursively. You can simply keep processing the list in a while loop until the list is of length 2 or less. Then convert to a string:
def encryption(n):
while len(n) > 2:
n = [(a + b) % 10 for a, b in zip(n, n[1:])]
return "".join([str(x) for x in n])
numbers = [1, 5, 7, 9]
print(encryption(numbers))
# 88
This is a little easier to reason about and it also makes it easy to debug by sticking a print(n) in the loops to see the progression.

Python remove N consecutive duplicates from the list

Here's the problem. The input is a list of integers. If more than three adjacent numbers appear next to each other they should be dropped and the operation goes again. Kind of similar to the Iphone game, where player needs to pop lines of three or more balls of the same colors. The output should be the count of the balls that will be removed.
The algorithm is as follows. Starting with a sample list of say [3,3,4,4,4,4,3,2].
First iteration should remove the 4,4,4,4 - so the list would become [3,3,3,2], and the intermediary output of removed numbers will be 4.
Second iteration should remove 3,3,3 - so the final list would be [2] and final count of removed numbers - 7.
The first implementation for three consecutive items came from another stackoverflow thread - Remove triplets of adjacent numbers from the list
Here's the working function implementation for exactly 3 consecutive numbers:
def balls(l):
values = l
while len(values) >= 3:
print(values) #for demonstrative purposes of this question
for x in range(0,len(values)-2):
if values[x] == values[x+1] and values[x] == values[x+2]:
values = values[:x] + values[x+3:]
break
else:
break
print(values) #for demonstrative purposes of this question
return len(l) - len(values)
balls([3, 3, 4, 4, 4, 3, 4])
Output:
[3, 3, 4, 4, 4, 3, 4]
[3, 3, 3, 4]
[4]
6
How could I update the implementation to include the more general solution of removing 3+ consecutive numbers. I am thinking about tracking the start and end index of the consecutive duplicates, then subsetting the list. However, not sure how to implement that. Here are the tests that should work.
if __name__ == "__main__":
test1 = [3, 3, 4, 4, 4, 3, 4]
print(balls(test1))
#Output should be 6
test2 = [5, 5, 5, 5, 5, 5, 5, 5]
print(balls(test2))
#Output should be 8
test3 = [5, 7, 8, 3]
print(balls(test3))
#Output should be 0
def remove_consecutive(l, length):
amount = len(l)
count = 1
start = 0
current = l[0]
i = 1
while i < len(l):
if l[i] == current:
count += 1
else:
if count >= length:
for i in range(count):
l.pop(start)
start = 0
i = 0
current = l[0]
else:
start = i
current = l[i]
count = 1
i+=1
if count >= length:
for i in range(count):
l.pop(start)
return amount - len(l)
Wuff, i got it. My brain is kinda stinky lately so it took so long.
Here is my code, it works well. But I think there may be better ways to achieve higher efficiency.
def remove_consecutive(lst):
len_init = len(lst)
contain_tuplets = True
while contain_tuplets:
for i in range(len(lst)-2):
indices_to_pop = []
if lst[i]==lst[i+1]==lst[i+2]:
indices_to_pop.extend([i, i+1, i+2])
for j in range(i+3,len(lst)):
if lst[j] == lst[i]:
indices_to_pop.append(j)
else:
break
[lst.pop(i) for _ in indices_to_pop]
contain_tuplets = True
break
else:
contain_tuplets = False
count_removed_numbers = len_init - len(lst)
return count_removed_numbers, lst
test case1:
lst = [3,3,4,4,4,4,3,2]
remove_consecutive(lst)
output
(7, [2])
test case 2:
lst = [2, 2, 1, 1, 1, 2, 1]
remove_consecutive(lst)
output:
(6, [1])
def remove_consecutive(l, length):
amount = 0
count = 1
current = l[0]
for i in range(1, len(l)):
if l[i] == current:
count += 1
if count > length:
amount += 1
elif count == length:
amount += length
else:
current = l[i]
count = 1
return amount

How do I get the index of the largest element adjacent to the greatest element in a list

So I have stock = [5,6,8,4,8,3,6,4]. I want to get the index of the greatest element adjacent to the 1st occurrence of the greatest element, 8. So what I want to get will be 6 with index 1. I have tried using this code.
closest = min(range(len(stock)), key=lambda i: abs(stock[i]-max(stock)))
but it just returns the max element.
If I understood your problem correctly, the most interesting input would look like [1,5,8,4,8,7,6,4]. I.e. we need to return the index of the 5 since it's a second maximum closest to the first occurrence of the maximum. If so, then the algorithm would look as follows:
find two leftmost and absolute maximums m1 and m2
if m1 == m2 then the target is in either of two subarrays:
[0, pos(m1))
[pos(m1) + 1, pos(m2))
otherwise, the target is in either of the following subarrays:
[0, pos(m1))
[pos(m1) + 1, len(arr))
We can find k max elements in an array in a close to linear time using the binary heap. So, I think I got a linear solution for you:
import heapq
def nlargest_with_pos(n, arr):
assert len(arr) >= n
largest = heapq.nlargest(n, ((it[1], -it[0]) for it in enumerate(arr)))
return [(it[0], -it[1]) for it in largest]
def find_x(arr):
assert len(arr) > 1
first_max, second_max = nlargest_with_pos(2, arr)
if len(arr) == 2:
return second_max[1]
left_range = (0, first_max[1])
if second_max[0] == first_max[0]:
right_range = (first_max[1] + 1, second_max[1])
else:
right_range = (first_max[1] + 1, len(arr))
left_hand = arr[left_range[0]:left_range[1]]
right_hand = arr[right_range[0]:right_range[1]]
if not left_hand:
return nlargest_with_pos(1, right_hand)[0][1]
if not right_hand:
return nlargest_with_pos(1, left_hand)[0][1]
left_second_max = nlargest_with_pos(1, left_hand)[0]
right_second_max = nlargest_with_pos(1, right_hand)[0]
if left_second_max[0] >= right_second_max[0]:
return left_second_max[1]
else:
return right_second_max[1]
print(find_x([1,5,8,4,8,7,6,4]))
Like this:
stock = [5,6,8,7,8,3,6,4]
if stock.index(max(stock)) == len(stock)-1:
print(len(stock)-2)
elif not stock.index(max(stock)):
print(1)
elif stock[stock.index(max(stock))-1] > stock[stock.index(max(stock))+1]:
print(stock.index(max(stock))-1)
else:
print(stock.index(max(stock))+1)
Output:
1
Not very elegant but this should work nonetheless:
stock.index(max(stock[stock.index(max(stock)) - 1], stock[(stock.index(max(stock)) + 1) % len(stock)]))
You'll have to add handling if there's a chance you see a list with less than three values
This prints index of highest element that's next to the first occurrence of maximum value in list stock:
stock = [1,5,8,4,8,7,6,4]
idx_max = stock.index(max(stock))
print(max([i for i in [idx_max-1, idx_max+1] if -1 < i < len(stock)], key=lambda k: stock[k]))
Prints:
1
Test cases:
stock = [8,3,8,4,8,3,6,4] # 1
stock = [1,3,1,3,8,5,6,4] # 5
stock = [1,3,1,4,1,3,6,8] # 6
stock = [1,5,8,4,8,7,6,4] # 1
Here's one way:
def get_max_neighbour(l):
_max = max(l)
excl = (-1, len(l))
neighbour = max(
(
(l[j], j)
for i in range(excl[-1])
for j in (i-1, i+1)
if l[i] == _max and j not in excl
),
key=lambda x: x[0]
)
return neighbour[1]
Result:
1
The nice thing about this is you can return both the value and index if you want.
Here's my solution to this puzzle. I'd say it is most similar to the solution of #DavidBuck, in that [8] -> -1 and [] -> None, but has four fewer exit points:
from math import inf
def index_of_max_neighbor_of_max(array):
if not array:
return None
index = array.index(max(array))
a = array[index - 1] if index - 1 >= 0 else -inf
b = array[index + 1] if index + 1 < len(array) else -inf
return index + (b > a) * 2 - 1
And my test code:
if __name__ == "__main__":
iomnom = index_of_max_neighbor_of_max
print(iomnom([5, 6, 8, 4, 8, 3, 6, 4])) # 1
print(iomnom([5, 6, 8, 4, 8, 3, 6, 9])) # 6
print(iomnom([5, 3])) # 1
print(iomnom([3, 5])) # 0
print(iomnom([8])) # -1
print(iomnom([])) # None
print(iomnom([5, 6, 8, 7, 8, 3, 6, 4])) # 3
print(iomnom([5, 6, 8, 4, 8, 3, 6, 9])) # 6
print(iomnom([5, 4, 8, 6, 8, 3, 6, 4])) # 3
def max_neighbor_index(l: list):
max_number = max(l)
max_number_indexes = [x for x in range(0, len(l)) if l[x] == max_number]
result = []
for number_index in max_number_indexes:
if number_index == 0:
result.append(1)
elif number_index == len(l) - 1:
result.append(len(l) - 2)
else:
result.append(l.index(max([l[number_index - 1], l[number_index + 1]])))
max_neighbor = max([l[x] for x in result])
return [x for x in result if l[x] == max_neighbor][0]
stock = [5, 6, 8, 4, 8, 3, 6, 4]
print(max_neighbor_index(stock))
1
stock = [5,6,8,4,8,3,6,4]
idx = 1
tmp,nxt = stock[0:2]
for i in range(1, len(stock)):
buf = stock[i-1] if i == len(stock)-1 else max(stock[i-1], stock[i+1])
if tmp < stock[i] or (tmp == stock[i] and nxt < buf):
idx = stock.index(buf, i-1)
nxt = stock[idx]
tmp = stock[i]
print('greatest next to greatest', nxt, 'at', idx)
Not very pretty, but it will cater for all possible scenarios, i.e. your list, max value a the start or the end, two or one value list.
Code is:
def highest_neighbour(stock):
if stock:
x = stock.index(max(stock))
if x - 1 >= 0:
if x + 1 < len(stock):
if stock[x + 1] > stock[x - 1]:
return x + 1
return x - 1
elif x + 1 < len(stock):
return x + 1
else:
return -1
I've set it to return -1 if the list only has one entry.
Output is:
highest_neighbour([5,6,8,4,8,3,6,4]) # -> 1
highest_neighbour([5,6,8,4,8,3,6,9]) # -> 6
highest_neighbour([9,6,8,4,8,3,6,4]) # -> 1
highest_neighbour([3,5]) # -> 0
highest_neighbour([8]) # -> -1
highest_neighbour([]) # -> None

Convert recursive peak finding to iterative

A peak in an array is any value that is no smaller than its two adjacent neighbors. If its the first or last element of the array we only need to compare with one neighbor. I wrote some recursive code to find peaks in python which is fast in principle as it runs in O(log n) time:
def peak_recursive(A):
n = len(A)
if n == 1:
print("n == 1")
return 0
if n == 2:
return 0 if A[0] >= A[1] else 1
if A[n//2] >= A[n//2+1] and A[n//2] >= A[n//2 - 1]:
return n//2
elif A[n//2 - 1] >= A[n//2]:
return peak_recursive(A[0:n//2])
else:
return n//2 + 1 + peak_recursive(A[n//2+1:])
However, python isn't very good at recursion so I think it would be better iteratively. How can I convert this to iterative code?
Update
It turns out this code is very slow as A[n//2+1:] and A[0:n//2] make copies of the lists.
One simple solution is to iterate over the list and compare the previous and next values. You also need to consider the first element and last element situation:
# Import numpy to create random vector
import numpy as np
l = np.random.randint(0, 10, 20).tolist()
print(l)
# [6, 7, 2, 7, 1, 4, 2, 8, 9, 1, 3, 7, 0, 5, 4, 6, 9, 0, 5, 7]
def peak_iter(A):
out = [] # Output
n = len(A) # Number element A
for i, curr in enumerate(A): # Iterate over A
condi = True # Peak condition
if i > 0: condi = A[i-1] < curr # Update peak condition from previous value
if i < n - 1: condi = curr > A[i + 1] # Update peak condition from next value
if condi: out.append(curr) # If condition satisfied: add value to output
return out
print(peak_iter(l))
# [7, 7, 4, 9, 7, 5, 9, 7]
As well, you can easily get the index instead of the value (or the both) by replacing out.append(curr) with out.append(i) or out.append([curr, i]).
Update:
If you just want to get the one peak, you can exit the function after finding one element meeting condition. The following returns the first values:
def peak_iter_first(A):
out = None # Output
n = len(A) # Number element A
for i, curr in enumerate(A): # Iterate over A
condi = True # Peak condition
if i > 0: condi = A[i-1] < curr # Update peak condition from previous value
if i < n - 1: condi = curr > A[i + 1] # Update peak condition from next value
if condi: return curr # If condition satisfied: return value
return out
print(peak_iter_first(l))
# 7
Update 2:
The translation of the recursive function to an iterative one might looks something like this:
def peak_iterative(A):
n = len(A)
out = 0
while True:
if n == 1:
out += 0
break
if n == 2:
out += 0 if A[0] >= A[1] else 1
break
if A[n//2] >= A[n//2+1] and A[n//2] >= A[n//2 - 1]:
out += n//2
break
elif A[n//2 - 1] >= A[n//2]:
A = A[0:n//2]
else:
out += n//2 + 1
A = A[n//2+1:]
n = len(A)
return out
Who's the faster ?
The recursive one is a bit faster than the iterative method:
import timeit
import functools
# Bigger array (2000 elements)
l = np.random.randint(0, 10, 2000).tolist()
t = timeit.Timer(functools.partial(peak_recursive, l))
print (t.timeit(50))
# 3.950000000019216e-05
t = timeit.Timer(functools.partial(peak_iterative, l))
print (t.timeit(50))
# 7.049999999986234e-05
Hope that helps !

Get the absolute difference between elements of a circular array

Let's say I have a array like l = [1, 3, 4, 5, 6, 8]
where the nth element represents the distance between the nth and n+1th object.
I want to find the distance between any two objects, and I used this code for this:
def dis(l_list, index1, index2, mylist):
m = mylist.index(index1)
n = mylist.index(index2)
i=0
j=0
if n > m:
while n >= m:
i = i + mylist[m]
m = m + 1
elif n < m:
while n <= m:
i = i + mylist[n]
n = n + 1
else:
return(0)
j = mylist[n] % l_mylist
print(abs(i - j))
l_mylist = input()
l_mylist = int(l_mylist)
mylist = []
mylist = list(map(int, input().split()))
i,j = input().split()
i, j=int(i), int(j)
dis(l_mylist, i, j, mylist)
but I am still getting the wrong output. Can anyone please point out where I am wrong?
If you want to sum around a potentially circular list. You can use a collections.deque() to rotate the list, e.g.:
from collections import deque
def dist(l, i1, i2):
d = deque(l)
d.rotate(-i1)
return sum(list(d)[:i2-i1]))
In []:
l = [1,2,3,4,5,6,7,8]
dist(l, 3-1, 6-1) # 3, 4, 5
Out[]:
12
In []:
dist(l, 6-1, 3-1) # 6, 7, 8, 1, 2
Out[]:
24
def distance(first_index, second_index, my_list):
temp_list = my_list + my_list
if (first_index > second_index):
first_index += len(my_list)
requested_sum = sum(my_list[second_index-1:first_index-1])
else:
requested_sum = sum(my_list[first_index-1:second_index-1])
return requested_sum
If I understood you correctly, then this should do the trick.
There are much more compact and efficient ways to do this, but this is the simplest and easiest to understand in my opinion.

Categories

Resources