Задача
Имеется файл (перепись 1917 года) такой структуры
248963_Нагорнов Герасим Терентьевич_Пензенский уезд_Бекетовская волость_село Михайловка_ф.158 оп.3 д.4287
248964_Нагорнов Петр Терентьевич_Пензенский уезд_Бекетовская волость_село Телегино_ф.158 оп.3 д.4287
248965_Нагорнова Александра Федоровна_Пензенский уезд_Бекетовская волость_село Михайловка_ф.158 оп.3 д.4287
248966_Новичков Лаврентий Иванович_Пензенский уезд_Дертевская волость_село Сабуровка_ф.158 оп.3 д.4287
248967_Новичков Никита Иванович_Пензенский уезд_Дертевская волость_село Сабуровка_ф.158 оп.3 д.4287
Его особенность - дублирование информации в соседних строках. В каждой из них - 6 колонок, но информация в некоторых часто повторяется. Для табличной структуры это неизбежно, если мы для каждой персоны хотим указать ее адрес: уезд-волость-село. Иерархическая структура позволит нам сохранить полноту и устранить дублирование информации. Такая структура могла бы выглядеть, например, так:
ф.158 оп.3 д.4287
Пензенский уезд
Бекетовская волость
село Михайловка
248963_Нагорнов Герасим Терентьевич
село Телегино
248964_Нагорнов Петр Терентьевич
село Михайловка
248965_Нагорнова Александра Федоровна
Дертевская волость
село Сабуровка
248966_Новичков Лаврентий Иванович
248967_Новичков Никита Иванович
В этом варианте дублирование почти ликвидировано. Дважды упоминается село Михайловка, но это уже не поправить, если не разрешается менять порядок строк. Итак, имеется табличная структура, в которой все строки имеют одинаковое число колонок, имеющих во всех строках одинаковый смысл. Порядок строк менять нельзя. Нужно преобразовать эту структуру в иерархическую, в которой повторяющиеся значения заданных колонок выносятся в заголовок и указываются один раз.
Программа на Python
dir = 'f:/'
f = open ( dir + "Таблица.txt" , "r" , encoding = 'utf-8-sig' ) # открыть таблицу
nums = f.read().splitlines() # прочитать построчно
rows = [s.split('_') for s in nums] # создать список строк с разделенными колонками
g = open ( dir + 'Структура.xml' , 'w' , encoding = 'utf-8' )
g . write ( '<?xml version="1.0"?>' )
g . write ( ' \n <root>' )
v0 = None ; v1 = None ; v2 = None ; v3 = None # значения колонок
n = [ 'n' , 'fio' , 'u' , 'v' , 's' , 'f' ] # имена колонок
i0 = 2 ; i1 = 5 ; i2 = 3 ; i3 = 4 # номера колонок
k = 0
line = f . readline ()
while line :
k += 1
line = line . rstrip ( ' \n ' )
row = line . split ( '_' )
if not k % 20000 :
print ( k , row )
cv0 = row [ i0 ]; cv1 = row [ i1 ]; cv2 = row [ i2 ]; cv3 = row [ i3 ] ;
n0 = n [ i0 ]; n1 = n [ i1 ]; n2 = n [ i2 ]; n3 = n [ i3 ] ;
if cv0 != v0 : # сменился уровень 0
if v0 != None :
g . write ( ' \n </' + n3 + '>' + ' \n </' + n2 + '>' + ' \n </' + n1 + '>' + ' \n </' + n0 + '>' )
v0 = cv0 ; v1 = cv1 ; v2 = cv2 ; v3 = cv3
g . write ( ' \n <' + n0 + '>' + cv0 + ' \n <' + n1 + '>' + cv1 + ' \n <' + n2 + '>' + cv2 + ' \n <' + n3 + '>' + cv3 )
elif cv1 != v1 : # сменился уровень 1
if v1 != None :
g . write ( ' \n </' + n3 + '>' + ' \n </' + n2 + '>' + ' \n </' + n1 + '>' )
v1 = cv1 ; v2 = cv2 ; v3 = cv3
g . write ( ' \n <' + n1 + '>' + cv1 + ' \n <' + n2 + '>' + cv2 + ' \n <' + n3 + '>' + cv3 )
elif cv2 != v2 : # сменился уровень 2
if v2 != None :
g . write ( ' \n </' + n3 + '>' + ' \n </' + n2 + '>' )
v2 = cv2 ; v3 = cv3
g . write ( ' \n <' + n2 + '>' + cv2 + ' \n <' + n3 + '>' + cv3 )
elif cv3 != v3 : # сменился уровень 3
if v3 != None :
g . write ( ' \n </' + n3 + '>' )
v3 = cv3 ;
g . write ( ' \n <' + n3 + '>' + cv3 )
g . write ( ' \n <p>' + row [ 1 ]+ '</p>' )
line = f . readline ()
f . close ()
g . write ( ' \n </' + n3 + '>' + ' \n </' + n2 + '>' + ' \n </' + n1 + '>' + ' \n </' + n0 + '>' + ' \n </root>' )
print ( 'finished' )
Программа получилась довольно большой. Разберем ее.
- Первый этап - открытие исходной таблицы на чтение и файла XML выходной структуры - на запись.
- Затем мы выбираем, какие "колонки с дублированием" следует выбрать и в каком порядке:
v0 = None ; v1 = None ; v2 = None ; v3 = None # значения колонок
n = [ 'n' , 'fio' , 'u' , 'v' , 's' , 'f' ] # имена колонок
i0 = 2 ; i1 = 5 ; i2 = 3 ; i3 = 4 # номера колонок
Здесь указано, что колонок с дублированием четыре, и они упорядочены в следующую иерархию: 'u','f','v','s', т.е. фонд-уезд-волость-село. Первые две колонки 'n','fio' в число дублированных не входят. - Теперь мы читаем таблицу построчно
line = f . readline ()
while line : - В текущей строке мы запоминаем значения дублированных колонок и их имена
cv0 = row [ i0 ]; cv1 = row [ i1 ]; cv2 = row [ i2 ]; cv3 = row [ i3 ] ;
n0 = n [ i0 ]; n1 = n [ i1 ]; n2 = n [ i2 ]; n3 = n [ i3 ] ; - Для каждой колонки, начиная с самой главной по иерархии, уровень 0, затем уровень 1 и т.д. (на примере уровня 0, на других уровнях - аналогично): Если значение изменилось по сравнению с предыдущей строкой,
if cv0 != v0 : # сменился уровень 0
то
1) закрываем текущий и все последующие уровни иерархии (если они были открыты, т.е. если строка не первая):
if v0 != None :
g . write ( ' \n </' + n3 + '>' + ' \n </' + n2 + '>' + ' \n </' + n1 + '>' + ' \n </' + n0 + '>' )
2) обновляем список предыдущих значений колонок
v0 = cv0 ; v1 = cv1 ; v2 = cv2 ; v3 = cv3
3) открываем текущий и все последующие уровни иерархии - с новыми их значениями
g . write ( ' \n <' + n0 + '>' + cv0 + ' \n <' + n1 + '>' + cv1 + ' \n <' + n2 + '>' + cv2 + ' \n <' + n3 + '>' + cv3 ) - Когда обработка дублирующихся колонок завершена, записываем в выходной файл остаток исходной строки
g . write ( ' \n <p>' + row [ 1 ]+ '</p>' )
В данном случае выводится только вторая колонка ('fio') - для уменьшения объема выходного файла и в связи с тем, что первая колонка 'n' - для данного конкретного входного файла - совпадает с номером строки в нем и, таким образом, не несет дополнительной информации. - Отметим, что операции чтения и записи происходят построчно - каждая очередная запись читается из таблицы, результат ее обработки записывается в выходной файл (в виде одной или нескольких строк), и затем это повторяется для следующей строки. При такой обработке размер файла не важен, т.к. при обработке мы не читаем его целиком.
Результат работы программы
<?xml version="1.0"?>
<root>
<u>Городищенский уезд
<f>ф.158 оп.3 д.2612
<v>Аришкинская волость
<s>село Аришка
<p>Абрамов Сергей Матвеевич</p>
<p>Агапов Лаврентий Степанович</p>
<p>Агапов Степан Васильевич</p>
<p>Агапов Трофим Савельевич</p>
...
<p>Шурыгин Павел Иванович</p>
<p>Шурыгин Степан Ефимович</p>
<p>Шурыгина Аксинья </p>
</s>
</v>
</f>
<f>ф.158 оп.3 д.2613
<v>Аришкинская волость
<s>село Исаевка
<p>Андреев Андрей Иванович</p>
<p>Андреев Василий Петрович</p>
<p>Андреев Зиновий Петрович</p>
...
<v>Свищевская волость
<s>село Голощапово
<p>Яшина Аграфена Николаевна</p>
</s>
</v>
</f>
</u>
</root>
Анализ
Применение программы позволило первоначальную таблицу размером 56 Мбайт превратить в XML файл размером 21 Мбайт - без потери информации, за счет устранения дублирования.
Отметим, что дублирование полностью истребить не удается. Рассмотрим, на примерах почему это происходит.
Пример 1 Неполная информация
002113_Хоханова Агафья Ивановна_Городищенский уезд_Базарно-Кеньшинская волость_село Ивановские выселки_ф.158 оп.3 д.2623
002114_Хоханова Ефросинья Степановна_Городищенский уезд_Базарно-Кеньшинская волость_село Ивановские выселки_ф.158 оп.3 д.2623
002115__уезд_волость_село_ф.158 оп.3 д.2624
002116_Алябин Аким Яковлевич_Городищенский уезд_Базарно-Кеньшинская волость_село Карамалы_ф.158 оп.3 д.2624
002117_Алябин Кузьма Акимович_Городищенский уезд_Базарно-Кеньшинская волость_село Карамалы_ф.158 оп.3 д.2624
Здесь мы видим пять строк, в третьей из которых не указаны ни фамилия, ни составляющие адреса. Есть только сведения о фонде-описи-деле. Если бы этой третьей строки не было (или если бы в исходном файле значения были заполнены, и тогда, скорее всего, село в этой третьей записи было бы либо 'Ивановские выселки', либо 'Карамалы'), то не было бы смены уезда и волости в диапазоне этих пяти строк. Но в данной ситуации результат получается таким:
<p>Хоханова Агафья Ивановна</p>
<p>Хоханова Ефросинья Степановна</p>
</s>
</v>
</f>
</u>
<u>уезд
<f>ф.158 оп.3 д.2624
<v>волость
<s>село
<p></p>
</s>
</v>
</f>
</u>
<u>Городищенский уезд
<f>ф.158 оп.3 д.2624
<v>Базарно-Кеньшинская волость
<s>село Карамалы
<p>Алябин Аким Яковлевич</p>
<p>Алябин Кузьма Акимович</p>
Программа вынуждена закрыть уровни 1-4, затем открыть их снова (чтобы отобразить третью строку, которая на самом деле пуста, как мы видим), а потом опять открыть уровни 1-4. При последнем открытии программа дублирует уезд, фонд и волость, хотя по сравнению со строками 1-2, они в строках 4-5 не изменились (и только село по факту изменилось). Это проблема не программы, а вопрос полноты данных исходного файла.
Пример 2 Порядок строк
001100_Казаков Алексей Иванович_Городищенский уезд_Базарно-Кеньшинская волость_село Серман_ф.158 оп.3 д.2621
001101_Казаков Иван Михайлович_Городищенский уезд_Базарно-Кеньшинская волость_село Серман_ф.158 оп.3 д.2621
001102_Казакова Екатерина Николаевна_Городищенский уезд_Базарно-Кеньшинская волость_село Базарная Кеньша_ф.158 оп.3 д.2621
001103_Карпунин Андрей Степанович_Городищенский уезд_Базарно-Кеньшинская волость_село Серман_ф.158 оп.3 д.2621
001104_Карпунин Трофим Георгиевич_Городищенский уезд_Базарно-Кеньшинская волость_село Серман_ф.158 оп.3 д.2621
Здесь снова пять строк. Полнота данных обеспечена, но здесь другая беда. Обычно в списке сведения упорядочены по уезду, волости и селу, а внутри села - по фамилии. Но в данном случае для Екатерины Николаевны указано село "Базарная Кеньша", которое в этом диапазоне окружено селом "Серман". Результат программы для этого диапазона строк имеет вид:
<p>Казаков Алексей Иванович</p>
<p>Казаков Иван Михайлович</p>
</s>
<s>село Базарная Кеньша
<p>Казакова Екатерина Николаевна</p>
</s>
<s>село Серман
<p>Карпунин Андрей Степанович</p>
<p>Карпунин Трофим Георгиевич</p>
Это также не вина программы. Поскольку мы договорились, что порядок строк (без разбора ситуации с привлечением оригинала исходного документа) не меняем, то устранить повторение села Сарман (которое было сначала указано для Казакова, а потом повторено перед Карпуниным) нельзя.
Пример 3 Ошибка (неоднозначность) в оригинале
097418_Воронков Павел Иванович_Керенский уезд_Шеинская волость_село Алексеевка_ф.158 оп.3 д.3223
097419_Воронков Петр Андреевич_Керенский уезд_Шеинская волость_село Алекс._ф.158 оп.3 д.3223
097420_Воронков Сергей Степанович_Керенский уезд_Шеинская волость_село Алексеевка_ф.158 оп.3 д.3223
В этом примере намного меньше строк, чем в предыдущих (только три, а в тех было аж целых пять). Но это мало помогает разрешить дилемму. Это ж как, извиняюсь, понимать, "село Алекс." - это и есть "село Алексеевка", али как? Без первоисточника (или на худой конец, поллитры) не разберешь. Поэтому то,что программа выдает в таких случаях, возможно, вас разочарует:
<p>Воронков Павел Иванович</p>
</s>
<s>село Алекс.
<p>Воронков Петр Андреевич</p>
</s>
<s>село Алексеевка
<p>Воронков Сергей Степанович</p>
Не стреляйте в программу, она исполняет ваши желания (кроме тайных), как может..
Подписаться на канал Математика и программирование
Вокруг ЕГЭ: разложить x^5+1 на множители с вещественными коэффициентами
Web Scraping: преобразовать иерархическую структуру в табличную
Web Scraping: преобразовать табличную структуру в иерархическую
Web Scraping: всероссийская перепись 1917
Как я поженил Лагранжа и сигмоиду
Интерполяция функций и правило Лопиталя
Подписаться на канал Новости из царской России
Вокруг ЕГЭ: разложить x^5+1 на множители с вещественными коэффициентами
Web Scraping: преобразовать иерархическую структуру в табличную
Web Scraping: преобразовать табличную структуру в иерархическую
Web Scraping: всероссийская перепись 1917
Как я поженил Лагранжа и сигмоиду
Оглавление статей канала "Новости из царской России"
YouTube "Новости из царской России"