Related
I am relatively new to programming, and have this problem:
There are two lists: C=[i,i,k,l,i] and D =[m,n,o,p,q]
I want to select the index of the minimum element of C. If k or l is the minimum, it is quite simple, since the min function will directly return the desired index. But if i is the minimum, there are several possibilities. In that case, I want to look at list D's elements, but only at the indices where i occurs in C. I then want to chose my sought-after index based on the minimum of those particular elements in D
I thought of the following code:
min_C = min(C)
if C.count(min_C) == 1:
soughtafter_index = C.index(min_C)
else:
possible_D_value = []
for iterate in C:
if iterate==min_C:
possible_index = C.index(iterate)
possible_D_value.append(D[possible_index])
best_D_value = min(possible_D_value)
soughtafter_index = D.index(best_D_value)
(Note that in the problem C and D will always have the same length)
I havent had a chance to test the code yet, but wanted to ask whether it is reasonable? Is there a better way to handle this? (and what if there is a third list-- then this code will get even longer...)
Thank you all
Try this:
soughtafter_index = list(zip(C, D)).index(min(zip(C,D)))
UPDATE with the required explanation:
>>> C = [1, 5, 1, 3, 1, 4]
>>> D = [0, 1, 1, 3, 0, 1]
>>> list(zip(C, D))
[(1, 0), (5, 1), (1, 1), (3, 3), (1, 0), (4, 1)]
>>> min(zip(C, D))
(1, 0)
I have some permutations of a list:
>>> import itertools
>>> perms = list(itertools.permutations([0,1,2,3]))
>>> perms
[(0, 1, 2, 3), (0, 1, 3, 2), (0, 2, 1, 3), (0, 2, 3, 1), (0, 3, 1, 2), (0, 3, 2, 1), (1, 0, 2, 3), (1, 0, 3, 2), (1, 2, 0, 3), (1, 2, 3, 0), (1, 3, 0, 2), (1, 3, 2, 0), (2, 0, 1, 3), (2, 0, 3, 1), (2, 1, 0, 3), (2, 1, 3, 0), (2, 3, 0, 1), (2, 3, 1, 0), (3, 0, 1, 2), (3, 0, 2, 1), (3, 1, 0, 2), (3, 1, 2, 0), (3, 2, 0, 1), (3, 2, 1, 0)]
>>> len(perms)
24
What function can I use (without access to the list perm) to get the index of an arbitrary permutation, e.g. (0, 2, 3, 1) -> 3?
(You can assume that permuted elements are always an ascending list of integers, starting at zero.)
Hint: The factorial number system may be involved. https://en.wikipedia.org/wiki/Factorial_number_system
Off the top of my head I came up with the following, didn't test it thoroughly.
from math import factorial
elements = list(range(4))
permutation = (3, 2, 1, 0)
index = 0
nf = factorial(len(elements))
for n in permutation:
nf //= len(elements)
index += elements.index(n) * nf
elements.remove(n)
print(index)
EDIT: replaced nf /= len(elements) with nf //= len(elements)
I suppose this is a challenge, so here is my (recursive) answer:
import math
import itertools
def get_index(l):
# In a real function, there should be more tests to validate that the input is valid, e.g. len(l)>0
# Terminal case
if len(l)==1:
return 0
# Number of possible permutations starting with l[0]
span = math.factorial(len(l)-1)
# Slightly modifying l[1:] to use the function recursively
new_l = [ val if val < l[0] else val-1 for val in l[1:] ]
# Actual solution
return get_index(new_l) + span*l[0]
get_index((0,1,2,3))
# 0
get_index((0,2,3,1))
# 3
get_index((3,2,1,0))
# 23
get_index((4,2,0,1,5,3))
# 529
list(itertools.permutations((0,1,2,3,4,5))).index((4,2,0,1,5,3))
# 529
You need to write your own function. Something like this would work
import math
def perm_loc(P):
N = len(P)
assert set(P) == set(range(N))
def rec(perm):
nums = set(perm)
if not perm:
return 0
else:
sub_res = rec(perm[1:]) # Result for tail of permutation
sub_size = math.factorial(len(nums) - 1) # How many tail permutations exist
sub_index = sorted(nums).index(perm[0]) # Location of first element in permutaiotn
# in the sorted list of number
return sub_index * sub_size + sub_res
return rec(P)
The function that does all the work is rec, with perm_loc just serving as a wrapper around it. Note that this algorithm is based on the nature of the permutation algorithm that itertools.permutation happens to use.
The following code tests the above function. First on your sample, and then on all permutations of range(7):
print perm_loc([0,2,3,1]) # Print the result from the example
import itertools
def test(N):
correct = 0
perms = list(itertools.permutations(range(N)))
for (i, p) in enumerate(perms):
pl = perm_loc(p)
if i == pl:
correct += 1
else:
print ":: Incorrect", p, perms.index(p), perm_loc(N, p)
print ":: Found %d correct results" % correct
test(7) # Test on all permutations of range(7)
from math import factorial
def perm_to_permidx(perm):
# Extract info
n = len(perm)
elements = range(n)
# "Gone"s will be the elements of the given perm
gones = []
# According to each number in perm, we add the repsective offsets
offset = 0
for i, num in enumerate(perm[:-1], start=1):
idx = num - sum(num > gone for gone in gones)
offset += idx * factorial(n - i)
gones.append(num)
return offset
the_perm = (0, 2, 3, 1)
print(perm_to_permidx(the_perm))
# 3
Explanation: All permutations of a given range can be considered as a groups of permutations. So, for example, for the permutations of 0, 1, 2, 3 we first "fix" 0 and permute rest, then fix 1 and permute rest, and so on. Once we fix a number, the rest is again permutations; so we again fix a number at a time from the remaining numbers and permute the rest. This goes on till we are left with one number only. Every level of fixing has a corresponding (n-i)! permutations.
So this code finds the "offsets" for each level of permutation. The offset corresonds to where the given permutation starts when we fix numbers of perm in order. For the given example of (0, 2, 3, 1), we first look at the first number in the given perm which is 0, and figure the offset as 0. Then this goes to gones list (we will see its usage). Then, at the next level of permutation we see 2 as the fixing number. To calculate the offset for this, we need the "order" of this 2 among the remaining three numbers. This is where gones come into play; if an already-fixed and considered number (in this case 0) is less than the current fixer, we subtract 1 to find the new order. Then offset is calculated and accumulated. For the next number 3, the new order is 3 - (1 + 1) = 1 because both previous fixers 0 and 2 are at the "left" of 3.
This goes on till the last number of the given perm since there is no need to look at it; it will have been determined anyway.
The task is to sort queryset in "chess order". ie:
class Item(models.Model):
CHOICES = [
(1, 1),
(2, 2),
(3, 3),
]
branch = models.PositiveSmallIntegerField(choices=CHOICES)
item1.branch == 1
item2.branch == 1
item3.branch == 2
item4.branch == 3
item5.branch == 3
The desired output of Item.objects.all() would be:
[item1, item3, item4, item2, item5]
So the resulted queryset would be sorted in a manner where branches are (1,2,3), (1,2,3), (1,2,3) etc.
I have never heard of chess sort, but from your description it seems to be defined like this.
A list l with smallest element xmin and greatest element xmax is in chess sort order if l[0] is xmin and l[j] is recursively picked to minimize the step k which is defined as the smallest positive integer such that l[j-1] + k == l[j] mod xmax.
In other words, it's like you were only allowed to place items on the column corresponding to their value on a chessboard. The list is considered sorted if every element is positionned as early as possible on the chessboard.
The problem with such an ordering is that it is not local. This mean that every item being correctly placed with respect to their neighbors does not imply that the whole list is correctly sorted. This is important because it indicates that we will not be able to sort the list with sorted and a well-crafted key argument.
Although, we can write an algorithm similar to counting sort that sorts in chess order.
Code
from collections import defaultdict, deque
from itertools import cycle
def chess_sort(lst, key=lambda x: x):
count = defaultdict(deque)
for x in lst:
count[key(x)].append(x)
order = sorted(count)
output = []
for x in cycle(order):
if len(output) == len(lst):
break
if count[x]:
output.append(count[x].popleft())
return output
Example
import random
lst = random.choices(range(5), k=15)
print('list:', lst)
print('sorted:', chess_sort(lst))
Output
list: [0, 1, 4, 2, 1, 2, 1, 3, 4, 3, 0, 0, 0, 3, 2]
sorted: [0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 0]
Applying to your problem
Notice how I allowed to pass a key to chess_sort? You can use it as you would for sorted to sort your items by branch attribute.
chess_sort(Item.objects.all(), key=lambda x: x.branch)
Here is what I am trying to do. Given a number and a set of numbers, I want to partition that number into the numbers given in the set (with repetitions).
For example :
take the number 9, and the set of numbers = {1, 4, 9}.
It will yield the following partitions :
{ (1, 1, 1, 1, 1, 1, 1, 1, 1), (1, 1, 1, 1, 1, 4), (1, 4, 4), (9,)}
No other possible partitions using the set {1, 4, 9} cannot be formed to sum the number 9.
I wrote a function in Python which do the task :
S = [ 1, 4, 9, 16 ]
def partition_nr_into_given_set_of_nrs(nr , S):
lst = set()
# Build the base case :
M = [1]*(nr%S[0]) + [S[0]] * (nr //S[0])
if set(M).difference(S) == 0 :
lst.add(M)
else :
for x in S :
for j in range(1, len(M)+1):
for k in range(1, nr//x +1 ) :
if k*x == sum(M[:j]) :
lst.add( tuple(sorted([x]*k + M[j:])) )
return lst
It works correctly but I want to see some opinions about it. I'm not satisfied about the fact that it uses 3 loops and I guess that it can be improved in a more elegant way. Maybe recursion is more suited in this case. Any suggestions or corrections would be appreciated. Thanks in advance.
I would solve this using a recursive function, starting with the largest number and recursively finding solutions for the remaining value, using smaller and smaller numbers.
def partition_nr_into_given_set_of_nrs(nr, S):
nrs = sorted(S, reverse=True)
def inner(n, i):
if n == 0:
yield []
for k in range(i, len(nrs)):
if nrs[k] <= n:
for rest in inner(n - nrs[k], k):
yield [nrs[k]] + rest
return list(inner(nr, 0))
S = [ 1, 4, 9, 16 ]
print(partition_nr_into_given_set_of_nrs(9, S))
# [[9], [4, 4, 1], [4, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1]]
Of course you could also do without the inner function by changing the parameters of the function and assuming that the list is already sorted in reverse order.
If you want to limit the number of parts for large numbers, you can add an aditional parameter indicating the remaining allowed number of elements and only yield result if that number is still greater than zero.
def partition_nr_into_given_set_of_nrs(nr, S, m=10):
nrs = sorted(S, reverse=True)
def inner(n, i, m):
if m > 0:
if n == 0:
yield []
for k in range(i, len(nrs)):
if nrs[k] <= n:
for rest in inner(n - nrs[k], k, m - 1):
yield [nrs[k]] + rest
return list(inner(nr, 0, m))
Here is a solution using itertools and has two for loops so time complexity is about O(n*n) (roughly)
A little memoization applied to reshape list by removing any element that is greater than max sum needed.
Assuming you are taking sum to be max of your set (9 in this case).
sourceCode
import itertools
x = [ 1, 4, 9, 16 ]
s = []
n = 9
#Remove elements >9
x = [ i for i in x if i <= n]
for i in xrange(1,n + 1):
for j in itertools.product(x,repeat = i):
if sum(j) == n:
s.append(list(j))
#Sort each combo
s =[sorted(i) for i in s]
#group by unique combo
print list(k for k,_ in itertools.groupby(s))
Result
>>>
>>>
[[9], [1, 4, 4], [1, 1, 1, 1, 1, 4], [1, 1, 1, 1, 1, 1, 1, 1, 1]]
EDIT
You can further optimize speed (if needed) by stopping finding combo's after sum of product is > 9
e.g.
if sum(j) > n + 2:
break
I have a list of integers as follows:
my_list = [2,2,2,2,3,4,2,2,4,4,3]
What I want is to have this as a list os strings, indexed and 'compressed', that is, with each element indicated by its position in the list and with each successive duplicate element indicated as a range, like this:
my_new_list = ['0-3,2', '4,3', '5,4', '6-7,2', '8-9,4', '10,3']
EDIT: The expected output should indicate that list elements 0 to 3 have the number 2, element 3, the number 3, element 5, the number 4, elements 6 and 7, the number 2, elements 8 and 9, number 4, and element 10, number 3.
EDIT 2: The output list need not (indeed cannot) be a list of integers, but a list of strings instead.
I could find many examples of finding (and deleting) duplicated elements from lists, but nothing along the lines of what I need.
Could someone point out a relevant example or suggest an algorithm for solving this?
Thanks in advance!
Like most problems involving cascading consecutive duplicates, you can still use groupby() for this. Just group indices by the value at each index.
values = [2,2,2,2,3,4,2,2,4,4,3]
result = []
for key, group in itertools.groupby(range(len(values)), values.__getitem__):
indices = list(group)
if len(indices) > 1:
result.append('{}-{},{}'.format(indices[0], indices[-1], key))
else:
result.append('{},{}'.format(indices[0], key))
print(result)
Output:
['0-3,2', '4,3', '5,4', '6-7,2', '8-9,4', '10,3']
Here is a lazy version that works on any sequence, and yields slices. Thus it's generic and memory efficient.
def compress(seq):
start_index = 0
previous = None
n = 0
for i, x in enumerate(seq):
if previous and x != previous:
yield previous, slice(start_index, i)
start_index = i
previous = x
n += 1
if previous:
yield previous, slice(start_index, n)
Usage :
assert list(compress([2, 2, 2, 2, 3, 4, 2, 2, 4, 4, 3])) == [
(2, slice(0, 4)),
(3, slice(4, 5)),
(4, slice(5, 6)),
(2, slice(6, 8)),
(4, slice(8, 10)),
(3, slice(10, 11)),
]
Why slices? Because it's convenient (can be used as-is for indexing) and the semantics (upper bound not included) are more "standard". Changing that to tuples or string with upper bound is easy btw.
You could use enumerate with a generator function
def seq(l):
it = iter(l)
# get first element and set the start index to 0.
start, prev = 0, next(it)
# use enumerate to track the rest of the indexes
for ind, ele in enumerate(it, 1):
# if last seen element is not the same the sequence is over
# if start i == ind - 1 the sequence had just a single element.
if prev != ele:
yield ("{}-{}, {}".format(start, ind - 1, prev)) \
if start != ind - 1 else ("{}, {}".format(start, prev))
start = ind
prev = ele
yield ("{}-{}, {}".format(start-1, ind-1, prev)) \
if start != ind else ("{}, {}".format(start, prev))
Output:
In [3]: my_list = [2, 2, 2, 2, 3, 4, 2, 2, 4, 4, 3]
In [4]: list(seq(my_list))
Out[4]: ['0-3, 2', '4, 3', '5, 4', '6-7, 2', '8-9, 4', '10, 3']
I was going to use groupby but will be faster.
In [11]: timeit list(seq(my_list))
100000 loops, best of 3: 4.38 µs per loop
In [12]: timeit itools()
100000 loops, best of 3: 9.23 µs per loop
Construct the list with number of consecutive occurences with the item. Then iterate the list and get the list with the range of index of each item.
from itertools import groupby
new_list = []
for k, g in groupby([2,2,2,2,3,4,2,2,4,4,3]):
sum_each = 0
for i in g:
sum_each += 1
##Construct the list with number of consecutive occurences with the item like this `[(4, 2), (1, 3), (1, 4), (2, 2), (2, 4), (1, 3)]`
new_list.append((sum_each, k))
x = 0
for (n, item) in enumerate(new_list):
if item[0] > 1:
new_list[n] = str(x) + '-' + str(x+item[0]-1) + ',' + str(item[1])
else:
new_list[n] = str(x) + ',' + str(item[1])
x += item[0]
print new_list
First off, your requested results are not valid python. I'm going to assume that the following format would work for you:
my_new_list = [ ((0,3),2), ((4,4),3), ((5,5),4), ((6,7),2), ((8,9),4), ((10,10),3) ]
Given that, you can first transform my_list into a list of ((index,index),value) tuples, then use reduce to gather that into ranges:
my_new_list = reduce(
lambda new_list,item:
new_list[:-1] + [((new_list[-1][0][0],item[0][1]),item[1])]
if len(new_list) > 0 and new_list[-1][1] == item[1]
else new_list + [item]
, [((index,index),value) for (index,value) in enumerate(my_list)]
, []
)
This does the following:
transform the list into ((index,index),value) tuples:
[((index,index),value) for (index,value) in enumerate(my_list)]
use reduce to merge adjacent items with the same value: If the list being built has at least 1 item and the last item in the list has the same value as the item being processed, reduce it to the list minus the last item, plus a new item consisting of the first index from the last list item plus the second index of the current item and the value of the current item. If the list being built is empty or the last item in the list is not the same value as the item being processed, just add the current item to the list.
Edited to use new_list instead of list as my lambda parameter; using list as a parameter or variable name is bad form
Here's a generator-based solution similar to Padraic's. However it avoids enumerate()-based index tracking and thus is probably faster for huge lists. I didn't worry about your desired output formatting, either.
def compress_list(ilist):
"""Compresses a list of integers"""
left, right = 0, 0
length = len(ilist)
while right < length:
if ilist[left] == ilist[right]:
right += 1
continue
yield (ilist[left], (left, right-1))
left = right
# at the end of the list, yield the last item
yield (ilist[left], (left, right-1))
It would be used like this:
my_list = [2,2,2,2,3,4,2,2,4,4,3]
my_compressed_list = [i for i in compress_list(my_list)]
my_compressed_list
Resulting in output of:
[(2, (0, 3)),
(3, (4, 4)),
(4, (5, 5)),
(2, (6, 7)),
(4, (8, 9)),
(3, (10, 10))]
Some good answers here, and thought I would offer an alternative. We iterate through the list of numbers and keep an updating current value, associated with a list of indicies for that value current_indicies. We then look-ahead one element to see if the consecutive number differs from current, if it does we go ahead and add it as a 'compressed number'.
def compress_numbers(l):
result = []
current = None
current_indicies = None
for i, item in enumerate(l):
if current != item:
current = item
current_indicies = [i]
elif current == item:
current_indicies.append(i)
try:
if l[i+1] != current:
result.append(format_entry(current_indicies, current))
except:
result.append(format_entry(current_indicies, current))
return result
# Helper method to format entry in the list.
def format_entry(indicies, value):
i_range = None
if len(indicies) > 1:
i_range = '{}-{}'.format(indicies[0], indicies[-1])
else:
i_range = indicies[0]
return '{},{}'.format(i_range, value)
Sample Output:
>>> print compress_numbers([2, 2, 2, 2, 3, 4, 2, 2, 4, 4, 3])
['0-3,2', '4,3', '5,4', '6-7,2', '8-9,4', '10,3']