Say I have a sorted list of strings as in:
[\'A\', \'B\' , \'B1\', \'B11\', \'B2\', \'B21\', \'B22\', \'C\', \'C1\', \'C11\', \'C2\']
Now I wan
To answer precisely what you describe you can do this :
l = ['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2', 'D']
def custom_sort(data, c):
s = next(i for i, x in enumerate(data) if x.startswith(c))
e = next((i for i, x in enumerate(data) if not x.startswith(c) and i > s), -1)
return data[:s] + sorted(data[s:e], key=lambda d: int(d[1:] or -1)) + data[e:]
print(custom_sort(l, "B"))
if you what an complete sort you can simply do this (as @Mike JS Choi answered but simplier) :
output = sorted(l, key=lambda elem: (elem[0], int(elem[1:] or -1)))
You can use cmp
in the sorted()
-function instead of key
:
s1=['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']
def compare(a,b):
if (a[0],b[0])==('B','B'): #change to whichever condition you'd like
inta=int(a[1:] or 0)
intb=int(b[1:] or 0)
return cmp(inta,intb) #change to whichever mode of comparison you'd like
else:
return 0 #if one of a, b doesn't fulfill the condition, do nothing
sorted(s1,cmp=compare)
This assumes transitivity for the comparator, which is not true for a more general case. This is also much slower than using key
, but the advantage is that it can take context into account (to a small extent).
You could generalise the comparison-type sorting algorithms by checking every other element in the list, and not just neighbours:
s1=['11', '2', 'A', 'B', 'B11', 'B21', 'B1', 'B2', 'C', 'C11', 'C2', 'B09','C8','B19']
def cond1(a): #change this to whichever condition you'd like
return a[0]=='B'
def comparison(a,b): #change this to whichever type of comparison you'd like to make
inta=int(a[1:] or 0)
intb=int(b[1:] or 0)
return cmp(inta,intb)
def n2CompareSort(alist,condition,comparison):
for i in xrange(len(alist)):
for j in xrange(i):
if condition(alist[i]) and condition(alist[j]):
if comparison(alist[i],alist[j])==-1:
alist[i], alist[j] = alist[j], alist[i] #in-place swap
n2CompareSort(s1,cond1,comparison)
I don't think that any of this is less of a hassle than making a separate list/tuple, but it is "in-place" and leaves elements that don't fulfill our condition untouched.
You can use the following key function. It will return a tuple of the form (letter, number)
if there is a number, or of the form (letter,)
if there is no number. This works since ('A',) < ('A', 1)
.
import re
a = ['A', 'B' ,'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']
regex = re.compile(r'(\d+)')
def order(e):
num = regex.findall(e)
if num:
num = int(num[0])
return e[0], num
return e,
print(sorted(a, key=order))
>> ['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C2', 'C11']
import re
from collections import OrderedDict
a = ['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']
dict = OrderedDict()
def get_str(item):
_str = list(map(str, re.findall(r"[A-Za-z]", item)))
return _str
def get_digit(item):
_digit = list(map(int, re.findall(r"\d+", item)))
return _digit
for item in a:
_str = get_str(item)
dict[_str[0]] = sorted([get_digit(dig) for dig in a if _str[0] in dig])
nested_result = [[("{0}{1}".format(k,v[0]) if v else k) for v in dict[k]] for k in dict.keys()]
print (nested_result)
# >>> [['A'], ['B', 'B1', 'B2', 'B11', 'B21', 'B22'], ['C', 'C1', 'C2', 'C11']]
result = []
for k in dict.keys():
for v in dict[k]:
result.append("{0}{1}".format(k,v[0]) if v else k)
print (result)
# >>> ['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C2', 'C11']
def compound_sort(input_list, natural_sort_prefixes=()):
padding = '{:0>%s}' % len(max(input_list, key=len))
return sorted(
input_list,
key = lambda li: \
''.join(
[li for c in '_' if not li.startswith(natural_sort_prefixes)] or
[c for c in li if not c.isdigit()] + \
[c for c in padding.format(li) if c.isdigit()]
)
)
This sort method receives:
input_list
: The list
to be sorted, natural_sort_prefixes
: A string
or a tuple
of string
s.List items targeted by the natural_sort_prefixes
will be sorted naturally. Items not matching those prefixes will be sorted lexicographically.
This method assumes that the list items are structured as one or more non-numerical characters followed by one or more digits.
It should be more performant than solutions using regex, and doesn't depend on external libraries.
You can use it like:
print compound_sort(['A', 'B' , 'B11', 'B1', 'B2', 'C11', 'C2'], natural_sort_prefixes=("A","B"))
# ['A', 'B', 'B1', 'B2', 'B11', 'C11', 'C2']
You can use ord() to transform for exemple 'B11' in numerical value:
cells = ['B11', 'C1', 'A', 'B1', 'B2', 'B21', 'B22', 'C11', 'C2', 'B']
conv_cells = []
## Transform expression in numerical value.
for x, cell in enumerate(cells):
val = ord(cell[0]) * (ord(cell[0]) - 65) ## Add weight to ensure respect order.
if len(cell) > 1:
val += int(cell[1:])
conv_cells.append((val, x)) ## List of tuple (num_val, index).
## Display result.
for x in sorted(conv_cells):
print(str(cells[x[1]]) + ' - ' + str(x[0]))