Modify function to accept tables as well (Python 3) - python

I've designed a simple function that looks at an inputted list of numbers, identifies the minimum and maximum values, then substitutes both of them for the midpoint value between them, the function is here:
def mainfunction(list_of_numbers):
smallest_number = list_of_numbers[0]
for a in list_of_numbers:
if a < smallest_number:
smallest_number = a
largest_number = list_of_numbers[0]
for b in list_of_numbers:
if b > largest_number:
largest_number = b
midpoint = (smallest_number + largest_number)/2
final_list = [x if (x != largest_number and x != smallest_number) else midpoint for x in list_of_numbers]
return final_list
print(mainfunction([10, 7, 14, 3, -200, 8, 1, -12, 250]))
Unfortunately, I can't get the function to work on TABLES of numbers, is there an easy way to convert a table of numbers into a list? Any info would be appreciated, cheers!

You can use itertools.chain
from itertools import chain
a = [[3,4], [15,16], [19,20]]
res = list(chain.from_iterable(a))
print(res)
Output:
[3, 4, 15, 16, 19, 20]
with list comprehension
res = [x for lst in a for x in lst]

Related

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)

Nested List Comprehension with if/else

I have a function that takes a string in a format such as '1,3-5,7,19' and will output the list [1, 3, 4, 5, 7, 19].
However, I was thinking that maybe this was simple enough to do with a nested list comprehension.
My original function is:
result = []
for x in row_range.split(','):
if '-' in x:
for y in range(int(x.split('-')[0]), int(x.split('-')[1]) + 1)):
result.append(y)
else:
result.append(int(x))
I thought the comprehension would be something like:
result = [y for x in row_range.split(',') if '-' in x else int(x) for y in range(int(x.split('-')[0]), int(x.split('-')[1] + 1)]
or even
result = [y for x in row_range.split(',') if '-' in x for y in range(int(x.split('-')[0]), int(x.split('-')[1] + 1) else int(x)]
but these are SyntaxError. Moving the if/else to the front of the comprehension as
result = [y if '-' in x else int(x) for x in row_range.split(',') for y in range(int(x.split('-')[0]), int(x.split('-')[1]) + 1)]
results in an IndexError: list index out of range.
Is this possible? I already have a function that handles it nicely and is more readable but I am simply curious if this can be accomplished in python.
You could define a small helper function:
def foo(x):
x, y = map(int, x.split('-'))
return (x, y + 1)
Now, use a list comprehension with a nested loop.
>>> [y for x in row_range.split(',')
for y in ([int(x)] if '-' not in x else range(*foo(x)))]
[1, 3, 4, 5, 7, 19]
Alternative solution with re.sub() function:
import re
row_range = '1,3-5,7,8,10-14,19' # extended example
cb = lambda r: repr(list(range(int(r.group(1)), int(r.group(2))+1)))[1:-1]
result = [int(i) for i in re.sub(r'(\d+)-(\d+)', cb, row_range).split(',')]
print(result)
The output:
[1, 3, 4, 5, 7, 8, 10, 11, 12, 13, 14, 19]

Shuffling an array without built in functions?

Trying to shuffle an array without using built in functions.
def shuffle2():
lis = [5,6,7,10,11,12]
print (lis)
x = random.randint (0,len (lis)-1)
y = random.randint (0,len (lis)-1)
z = lis[x], lis[y] = lis[y], lis[x]#flips the elements around
shuffle2()
So far it switches two elements around once, however I need it to shuffle the whole array rather than just two positions.
output:
[5, 6, 7, 10, 11, 12]
[11, 6, 7, 10, 5, 12]
Any ideas?
Here's one way:
import bisect
import random
def shuffle(lis):
newlis = []
temp = []
for i in lis:
x = random.random()
#put x into temp and put i into the same place in newlis
n = bisect.bisect(temp,x)
temp.insert(n,x)
newlis.insert(n,i)
return newlis
lis=[5,6,7,10,11,12]
>>> shuffle(lis)
[6, 12, 10, 11, 7, 5]
The method essentially generate random numbers and puts them into a sorted list (temp). The items is lis are put into a newlis in the same way, which has the effect of sorting it.
You, of course, can write your own bisect, or use a more trivial search function quite easily...
import random
def shuffle2():
i = 1
lis = [5,6,7,10,11,12]
print (lis)
while i < len(lis):
x = random.randint (0,len (lis)-1)
y = random.randint (0,len (lis)-1)
z = lis[x], lis[y] = lis[y], lis[x]
i = i + 1
#flips the elements around
print (lis)

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(','))
)
)))

How does one find the largest consecutive set of numbers in a list that are not necessarily adjacent?

For instance, if I have a list
[1,4,2,3,5,4,5,6,7,8,1,3,4,5,9,10,11]
This algorithm should return [1,2,3,4,5,6,7,8,9,10,11].
To clarify, the longest list should run forwards. I was wondering what is an algorithmically efficient way to do this (preferably not O(n^2))?
Also, I'm open to a solution not in python since the algorithm is what matters.
Thank you.
Here is a simple one-pass O(n) solution:
s = [1,4,2,3,5,4,5,6,7,8,1,3,4,5,9,10,11,42]
maxrun = -1
rl = {}
for x in s:
run = rl[x] = rl.get(x-1, 0) + 1
print x-run+1, 'to', x
if run > maxrun:
maxend, maxrun = x, run
print range(maxend-maxrun+1, maxend+1)
The logic may be a little more self-evident if you think in terms of ranges instead of individual variables for the endpoint and run length:
rl = {}
best_range = xrange(0)
for x in s:
run = rl[x] = rl.get(x-1, 0) + 1
r = xrange(x-run+1, x+1)
if len(r) > len(best_range):
best_range = r
print list(best_range)
Not that clever, not O(n), could use a bit of optimization. But it works.
def longest(seq):
result = []
for v in seq:
for l in result:
if v == l[-1] + 1:
l.append(v)
else:
result.append([v])
return max(result, key=len)
You can use The Patience Sort implementation of the Largest Ascending Sub-sequence Algorithm
def LargAscSub(seq):
deck = []
for x in seq:
newDeck = [x]
i = bisect.bisect_left(deck, newDeck)
deck[i].insert(0, x) if i != len(deck) else deck.append(newDeck)
return [p[0] for p in deck]
And here is the Test results
>>> LargAscSub([1,4,2,3,5,4,5,6,7,8,1,3,4,5,9,10,11])
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
>>> LargAscSub([1, 2, 3, 11, 12, 13, 14])
[1, 2, 3, 11, 12, 13, 14]
>>> LargAscSub([11,12,13,14])
[11, 12, 13, 14]
The Order of Complexity is O(nlogn)
There was one note in the wiki link where they claimed that you can achieve O(n.loglogn) by relying on Van Emde Boas tree
How about using a modified Radix Sort? As JanneKarila pointed out the solution is not O(n). It uses Radix sort, which wikipedia says Radix sort's efficiency is O(k·n) for n keys which have k or fewer digits.
This will only work if you know the range of numbers that we're dealing with so that will be the first step.
Look at each element in starting list to find lowest, l and highest, h number. In this case l is 1 and h is 11. Note, if you already know the range for some reason, you can skip this step.
Create a result list the size of our range and set each element to null.
Look at each element in list and add them to the result list at the appropriate place if needed. ie, the element is a 4, add a 4 to the result list at position 4. result[element] = starting_list[element]. You can throw out duplicates if you want, they'll just be overwritten.
Go through the result list to find the longest sequence without any null values. Keep a element_counter to know what element in the result list we're looking at. Keep a curr_start_element set to the beginning element of the current sequence and keep a curr_len of how long the current sequence is. Also keep a longest_start_element and a `longest_len' which will start out as zero and be updated as we move through the list.
Return the result list starting at longest_start_element and taking longest_len
EDIT: Code added. Tested and working
#note this doesn't work with negative numbers
#it's certainly possible to write this to work with negatives
# but the code is a bit hairier
import sys
def findLongestSequence(lst):
#step 1
high = -sys.maxint - 1
for num in lst:
if num > high:
high = num
#step 2
result = [None]*(high+1)
#step 3
for num in lst:
result[num] = num
#step 4
curr_start_element = 0
curr_len = 0
longest_start_element = -1
longest_len = -1
for element_counter in range(len(result)):
if result[element_counter] == None:
if curr_len > longest_len:
longest_start_element = curr_start_element
longest_len = curr_len
curr_len = 0
curr_start_element = -1
elif curr_start_element == -1:
curr_start_element = element_counter
curr_len += 1
#just in case the last element makes the longest
if curr_len > longest_len:
longest_start_element = curr_start_element
longest_len = curr_len
#step 5
return result[longest_start_element:longest_start_element + longest_len-1]
If the result really does have to be a sub-sequence of consecutive ascending integers, rather than merely ascending integers, then there's no need to remember each entire consecutive sub-sequence until you determine which is the longest, you need only remember the starting and ending values of each sub-sequence. So you could do something like this:
def longestConsecutiveSequence(sequence):
# map starting values to largest ending value so far
map = collections.OrderedDict()
for i in sequence:
found = False
for k, v in map.iteritems():
if i == v:
map[k] += 1
found = True
if not found and i not in map:
map[i] = i + 1
return xrange(*max(map.iteritems(), key=lambda i: i[1] - i[0]))
If I run this on the original sample date (i.e. [1,4,2,3,5,4,5,6,7,8,1,3,4,5,9,10,11]) I get:
>>> print list(longestConsecutiveSequence([1,4,2,3,5,4,5,6,7,8,1,3,4,5,9,10,11]))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
If I run it on one of Abhijit's samples [1,2,3,11,12,13,14], I get:
>>> print list(longestConsecutiveSequence([1,2,3,11,12,13,14]))
[11, 12, 13, 14]
Regrettably, this algorithm is O(n*n) in the worst case.
Warning: This is the cheaty way to do it (aka I use python...)
import operator as op
import itertools as it
def longestSequence(data):
longest = []
for k, g in it.groupby(enumerate(set(data)), lambda(i, y):i-y):
thisGroup = map(op.itemgetter(1), g)
if len(thisGroup) > len(longest):
longest = thisGroup
return longest
longestSequence([1,4,2,3,5,4,5,6,7,8,1,3,4,5,9,10,11, 15,15,16,17,25])
You need the Maximum contiguous sum(Optimal Substructure):
def msum2(a):
bounds, s, t, j = (0,0), -float('infinity'), 0, 0
for i in range(len(a)):
t = t + a[i]
if t > s: bounds, s = (j, i+1), t
if t < 0: t, j = 0, i+1
return (s, bounds)
This is an example of dynamic programming and is O(N)
O(n) solution works even if the sequence does not start from the first element.
Warning does not work if len(A) = 0.
A = [1,4,2,3,5,4,5,6,7,8,1,3,4,5,9,10,11]
def pre_process(A):
Last = {}
Arrow = []
Length = []
ArgMax = 0
Max = 0
for i in xrange(len(A)):
Arrow.append(i)
Length.append(0)
if A[i] - 1 in Last:
Aux = Last[A[i] - 1]
Arrow[i] = Aux
Length[i] = Length[Aux] + 1
Last[A[i]] = i
if Length[i] > Max:
ArgMax = i
Max = Length[i]
return (Arrow,ArgMax)
(Arr,Start) = pre_process(A)
Old = Arr[Start]
ToRev = []
while 1:
ToRev.append(A[Start])
if Old == Start:
break
Start = Old
New = Arr[Start]
Old = New
ToRev.reverse()
print ToRev
Pythonizations are welcome!!
Ok, here's yet another attempt in python:
def popper(l):
listHolders = []
pos = 0
while l:
appended = False
item = l.pop()
for holder in listHolders:
if item == holder[-1][0]-1:
appended = True
holder.append((item, pos))
if not appended:
pos += 1
listHolders.append([(item, pos)])
longest = []
for holder in listHolders:
try:
if (holder[0][0] < longest[-1][0]) and (holder[0][1] > longest[-1][1]):
longest.extend(holder)
except:
pass
if len(holder) > len(longest):
longest = holder
longest.reverse()
return [x[0] for x in longest]
Sample inputs and outputs:
>>> demo = list(range(50))
>>> shuffle(demo)
>>> demo
[40, 19, 24, 5, 48, 36, 23, 43, 14, 35, 18, 21, 11, 7, 34, 16, 38, 25, 46, 27, 26, 29, 41, 8, 31, 1, 33, 2, 13, 6, 44, 22, 17,
12, 39, 9, 49, 3, 42, 37, 30, 10, 47, 20, 4, 0, 28, 32, 45, 15]
>>> popper(demo)
[1, 2, 3, 4]
>>> demo = [1,4,2,3,5,4,5,6,7,8,1,3,4,5,9,10,11]
>>> popper(demo)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
>>>
This should do the trick (and is O(n)):
target = 1
result = []
for x in list:
for y in result:
if y[0] == target:
y[0] += 1
result.append(x)
For any starting number, this works:
result = []
for x in mylist:
matched = False
for y in result:
if y[0] == x:
matched = True
y[0] += 1
y.append(x)
if not matched:
result.append([x+1, x])
return max(result, key=len)[1:]

Categories