Example 1
List = [12,40,30,53,82,31,100]
I want to produce a list which includes so many numbers close to 80 and not larger than 80 . If the number originally larger than 80 just pass.
The result like [52,30, ,53,82, 31,100]
Because of [12+40,30,53,82,31 ,100]
Example 2
List =[45,102,31,25,2,99]
Result = [45,102,58,99]
Please help me figure out how to code with this question, really appreciate it.
I have tried
For i in range(len(List)):
Try this:
start = -1
sum_ = 0
List = [45,102,31,25,2,99]
result = []
i = 0
while i < len(List):
if start == -1:
start = i
new_sum = sum_ + List[i]
if new_sum > 80:
result.append(sum_)
start = -1
sum_ = 0
new_sum = 0
if List[i] > 80:
result.append(List[i])
i += 1
continue
else:
sum_ = new_sum
i += 1
print(result)
# [45, 102, 58, 99]
List =[45,102,31,25,2,99]
# List = [12,40,30,53,82,31,100]
result = []
for i in range(0,len(List)):
if List[i]>=80 or len(result)==0:
result.append(List[i])
elif result[-1] + List[i] <=80 :
result[-1] += List[i]
else :
result.append(List[i])
print(result)
You can define simple generator function which will yield sum of values if they reached limit:
def sum_up_to(source, limit):
summ = 0
for i in source:
if summ + i > limit:
if summ:
yield summ
summ = i
else:
yield i
else:
summ += i
if summ:
yield summ
Then just call it with any limit:
src1 = [12, 40, 30, 53, 82, 31, 100]
dst1 = list(sum_up_to(src1, 80)) # [52, 30, 53, 82, 31, 100]
src2 = [45, 102, 31, 25, 2, 99]
dst2 = list(sum_up_to(src2, 80)) # [45, 102, 58, 99]
Upd. I've made some tests (code) to compare solutions from all answers. Here is results (lower is better):
olvin_roght: 0.263744345
chandrapal_singh: 0.39295867199999995
captain_trojan: 0.605765677
Tests shown that solution from this answer demonstrates best performance.
The code below is not iterating through the list and sorting the list. How should I tweak it just enough to make it work while still looking a little the same? I've tried multiple ways to make it work to no avail. Oh and, I'm aware of the Selection sorting shortcut, the sort() function, but I want to learn the long way as well in how to code functions, programs and processes. Thanks!
def sortList(L,n):
minValue = L[0]
L2 = []
idx = 0
counter = 0
while (counter < n):
v = L[counter]
if v < minValue:
minValue = v
idx = counter
L2.append(minValue)
del L[idx]
n-=1
counter += 1
return L2
L = [34, -1, 0, 89, 21, -40, 7]
n = len(L)
print(sortList(L, n))
I highly recommend to use nested loops because it is easier, but I decided to give this a go
make it work while still looking a little the same
def sortList(L,n):
minValue = L[0]+1
L2 = []
idx = 0
counter = 0
while (len(L) > 0):
v = L[counter]
if v <= minValue:
minValue = v
idx = counter
n-=1
counter += 1
if counter >= len(L):
L2.append(minValue)
del L[idx]
counter = 0
if len(L):
minValue = L[0]
return L2
L = [34, -1, 0, 89, 21, -40, 7]
n = len(L)
print(sortList(L, n))
import sys
def selection_sort(unsorted_list):
"""Traverses the list to finds the min and inserts it in the beginning.
Then repeats traversing through the unsorted members,
each time popping and inserting the min after the previous mins."""
my_list = list(unsorted_list)
counter = 0
j = 0
while j < len(my_list):
min = sys.maxsize
min_index = -1
for i in range(j, len(my_list)):
counter += 1
if my_list[i] < min:
min = my_list[i]
min_index = i
a = my_list.pop(min_index)
my_list.insert(j, a)
j += 1
return my_list
print(selection_sort([34, -1, 0, 89, 21, -40, 7]))
#output: [-40, -1, 0, 7, 21, 34, 89]
You can implement a insertion sort:
def sortList(L):
L2 = []
while len(L) != 0:
minValue = L[0]
indx = 0 # index of the element that will be deleted
counter = 0 # iterating counter
for num in L:
if num<minValue:
minValue = num
indx=counter
counter+=1
L2.append(minValue)
del L[indx]
return L2
L = [34, -1, 0, 89, 21, -40, 7]
print(sortList(L))
or you can implement a selection sort:
def sortList(L):
for counter in range(0,len(L)):
minValueIndex = counter
for indx in range(counter,len(L)):
if(L[indx] < L[minValueIndex]):
minValueIndex = indx
L[counter],L[minValueIndex] = L[minValueIndex],L[counter]
L = [34, -1, 0, 89, 21, -40, 7]
sortList(L)
print(L)
Your code does not follow the selection sort logic (there are two main errors: selection sort does not use an auxiliary arrangement, such as L2; you have not changed the value of the current ( L[counter] ) and lower value ( L[minValueIndex] ) variables). Try using print on the method lines to try to understand the logic error of your algorithm.
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])