-
-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathSorter.py
More file actions
186 lines (154 loc) · 13.2 KB
/
Sorter.py
File metadata and controls
186 lines (154 loc) · 13.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
print('''
Python3-скрипт, сортирующий таблицу по указанным пользователем столбцам.
Автор: Платон Быкадоров (platon.work@gmail.com), 2018-2019.
Версия: V3.4.
Лицензия: GNU General Public License version 3.
Поддержать проект: https://money.yandex.ru/to/41001832285976
Простое руководство по установке среды разработки и запуску скриптов:
github.com/PlatonB/bioinformatic-python-scripts#Установка-среды-разработки
В скрипте реализован алгоритм получения наиболее универсальных правил сортировки.
Этот алгоритм создаёт правила, согласно которым сортировка ведётся по символам,
способным и, наоборот, неспособным принимать числовой тип данных.
Для конкретно вашей задачи могут потребоваться специфические правила сортировки.
В таком случае пишите в Issues, и я постараюсь создать
версию программы-сортировщика с персонализированными хаками.
''')
def cell_split(cell, sort_rule, cur_start_index = 0):
'''
Чтобы отделить друг от друга символы, способные
быть представленными как вещественное число, от
символов, могущих пренадлежать лишь строковому типу
данных, функция обрабатывает ячейку отрезками.
Отрезок должен включать в себя, как минимум, одно число
или одну строку, а, как максимум, и строку, и число.
Отделённые друг от друга на основании типа
данных части ячейки поступят в список-"правило".
Правилами такие списки назовём, т.к. из них
будет составляться ключ сортировки таблицы,
и, соответственно, они будут определять,
каким образом таблица будет отсортирована.
'''
#Регулярное выражение, находящее любой
#конвертируемый во float набор символов.
num_search_pat = r'-?\d+(?:\.\d+)?(?:[Ee][-+]?\d+)?'
#Поиск числа в очередном отрезке строки.
#Результат - либо None, либо представляет собой
#объект, содержащий найденное число, а также
#индексы его начала и конца в этом отрезке.
#Далее эти индексы пригодятся для определения границ
#как между строковой и численной составляющих текущего
#отрезка, так и между текущим и следующим отрезками.
num_object = re.search(num_search_pat, cell[cur_start_index:])
#Число нашлось.
if num_object != None:
#Число могло найтись как целое, так и дробное.
#Чтобы найденные числа правильно сравнивались при
#дальнейшей сортировке, конвертируем их все в вещественные.
num_value = float(num_object.group())
#Чтобы отмерить текущие индексы начала и конца найденного числа
#относительно начала всей ячейки, соответствующие индексы, сосчитанные
#внутри отрезка ячейки, прибавляются к текущему индексу начала отрезка.
num_min_index = cur_start_index + num_object.span()[0]
num_max_index = cur_start_index + num_object.span()[1]
#В текущем отрезке перед числом может находиться
#строковая часть, т.е. набор любых нечисловых символов.
#Её левая граница - индекс начала отрезка,
#а правая - индекс начала числа во всей ячейке.
str_value = cell[cur_start_index:num_min_index]
#Добавляем непустую или пустую строковую часть в конечный список.
#Добавление пустой строки нужно для того, чтобы элементы одинаковых
#позиций всех списков, полученных дроблением каждой ячейки определённого
#сортируемого столбца, принадлежали одному и тому же типу данных.
#Если этого не делать, то в списках, служащих правилом сортировки
#данного столбца, на место пустой строки будет смещаться число из текущего
#отрезка, а далее при сортировке пойдут попытки сравнить такие сползшие числа
#с непустым строковым компонентом соответствующего предыдущего отрезка.
sort_rule.append(str_value)
#Добавляем в конечный список найденное число.
#Оно будет расположено строго после
#непустой или пустой строковой части.
sort_rule.append(num_value)
#Левая граница следующего отрезка будет
#равна индексу конца числа, отмерянному
#относительно начала всей ячейки.
next_start_index = num_max_index
#Левая граница гипотетического следующего считывания
#ячейки совпадает с количеством символов этой ячейки.
#Это означает, что следующего отрезка не будет, а
#текущий вызов функции является самым глубоким.
#Накопленный список возвращается предпоследнему вызову.
if next_start_index == len(cell):
return sort_rule
#Рекурсивно запускаем функцию дробления ячейки уже с новыми значениями
#накопленного списка и левой границы отрезка в качестве аргументов.
#Поэтапно протаскиваем список с окончательным вариантом правила
#сортировки столбца из самого глубокого вызова функции в самый первый.
else:
return cell_split(cell, sort_rule, cur_start_index=next_start_index)
#Этот блок кода сработает либо, если в ячейке в принципе нет чисел,
#либо если обрабатывается последний отрезок, и в нём не оказалось чисел.
#Добавляем найденный последний (или единственный) строковый участок в список-правило.
#Если засчёт этого строкового участка список будет выпирать по отношению к
#другим спискам данного столбца, то ошибки сравнения разных типов данных не будет.
#Поэтому искусственно выравнивать длины списков не нужно.
else:
str_value = cell[cur_start_index:]
sort_rule.append(str_value)
#В зависимости от того, вызывалась ли функция рекурсивно,
#или нет, накопленный список будет возвращён либо
#в предпоследний вызов этой функции, либо вовне.
return sort_rule
####################################################################################################
import sys, os, re
src_dir_path = input('Путь к папке с исходными файлами: ')
trg_dir_path = input('\nПуть к папке для конечных файлов: ')
num_of_headers = input('''\nКоличество не обрабатываемых строк
в начале каждой исходной таблицы
(игнорирование ввода ==> хэдеров/шапок в таблицах нет)
[0(|<enter>)|1|2|...]: ''')
if num_of_headers == '':
num_of_headers = 0
else:
num_of_headers = int(num_of_headers)
col_numbers = input('''\nНомер одного или номера нескольких
определяющих сортировку столбцов
(через пробел)
(игнорирование ввода ==> сортировка по всем столбцам): ''').split()
reverse_val = input('''\nСортировать в обратном порядке (по убыванию)?
(игнорирование ввода ==> сортировать по возрастанию)
[yes(|y)|no(|n|<enter>)]: ''')
if reverse_val in ['yes', 'y']:
reverse_val = True
elif reverse_val in ['no', 'n', '']:
reverse_val = False
else:
print('\nОшибка. Вы не задали порядок сортировки')
sys.exit()
print('\n')
src_file_names = os.listdir(src_dir_path)
for src_file_name in src_file_names:
if src_file_name.startswith('.~lock.'):
continue
with open(os.path.join(src_dir_path, src_file_name)) as src_file_opened:
print(f'Производится сортировка {src_file_name}')
#Формирование списка хэдеров.
#Курсор смещается к началу основной части таблицы.
headers = [src_file_opened.readline() for header_index in range(num_of_headers)]
#Основная часть таблицы преобразуется в двумерный массив.
src_table = [line.split('\n')[0].split('\t') for line in src_file_opened]
#Пользователь не указал количество задающих сортировку столбцов.
#Тогда сделаем таковыми все столбцы таблицы.
if col_numbers == []:
col_numbers = range(len(src_table[0]) + 1)[1:]
#Сортировка.
src_table.sort(key=lambda row: [cell_split(row[int(col_number) - 1], []) for col_number in col_numbers],
reverse=reverse_val)
#Создание конечного файла и прописывание в него результатов.
src_file_base = '.'.join(src_file_name.split('.')[:-1])
src_file_ext = '.' + src_file_name.split('.')[-1]
trg_file_name = src_file_base + '_srtd' + src_file_ext
with open(os.path.join(trg_dir_path, trg_file_name), 'w') as trg_file_opened:
for header in headers:
trg_file_opened.write(header)
for row in src_table:
trg_file_opened.write('\t'.join(row) + '\n')