Перейти к содержимому

Fingercomp

Гуру
  • Публикации

    1 629
  • Зарегистрирован

  • Посещение

  • Победитель дней

    283

Все публикации пользователя Fingercomp

  1. Аватары лучше оставить квадратными.
  2. Вто Второй вариант, конечно же.
  3. Fingercomp

    Парсер CSV

    Форму добавления сначала сделай.
  4. Fingercomp

    Парсер CSV

    CSV идёт от Comma-Separated Values, что, в общем, довольно точно описывает этот формат хранения таблиц. Вот типичная таблица: aaa,bbb,ccc,dddeee,fff,ggg,hhh Как видно, строки отделяются \n, а ячейки ­— запятой. Последняя строка может иметь или не иметь \n. Формат очень простой. Описывается он в RFC 4180. Там всего 7 пунктов. Ну а раз простой, давайте соорудим парсер. Вот у нас есть строка aaa,bbb,ccc,ddd\neee,fff,ggg,hhh. Задача: сделать из неё [ [ "aaa", "bbb", "ccc", "ddd" ], [ "eee", "fff", "ggg", "hhh" ]] Так как позже я немного усложню парсер, очевидный вариант со split, которая делит строку, опустим. Сделаем так: def parse_csv(s): # Сюда идёт результат result = [] # Текущая строка row = [] # Текущая ячейка cell = "" # Проходимся по строке for i in range(len(s)): # Текущий символ c = s[i] if c == ",": # Если символ — запятая, закрываем ячейку row.append(cell) cell = "" elif c == "\n": # Если это перевод строки, то закрываем ячейку и строку row.append(cell) cell = "" result.append(row) row = [] else: # Любой другой символ добавляем в ячейку cell += c # Возвращаем результат return result Запускаем: >>> parse_csv("aaa,bbb,ccc,ddd\neee,fff,ggg,hhh\n")[['aaa', 'bbb', 'ccc', 'ddd'], ['eee', 'fff', 'ggg', 'hhh']] >>> parse_csv("aaa,bbb,ccc,ddd\neee,fff,ggg,hhh")[['aaa', 'bbb', 'ccc', 'ddd']] Действительно, в конце может и не быть \n. Давайте поправим: def parse_csv(s): result = [] row = [] cell = "" for i in range(len(s)): c = s[i] if c == ",": row.append(cell) cell = "" elif c == "\n": row.append(cell) cell = "" result.append(row) row = [] else: cell += c # Если ячейка не пуста if cell: # Закрываем ячейку и строку row.append(cell) result.append(row) return result Проверяем: >>> parse_csv("aaa,bbb,ccc,ddd\neee,fff,ggg,hhh\n")[['aaa', 'bbb', 'ccc', 'ddd'], ['eee', 'fff', 'ggg', 'hhh']] >>> parse_csv("aaa,bbb,ccc,ddd\neee,fff,ggg,hhh")[['aaa', 'bbb', 'ccc', 'ddd'], ['eee', 'fff', 'ggg', 'hhh']] Замечательно. Почему я проверяю только ячейку, а не строку ещё? Просто пустая ячейка и непустая строка может быть только тогда, когда на конце строки висит запятая. aaa,bbb,. А это явно запрещено по RFC. В текущем виде в ячейке у нас не получится хранить \n и ,. Если первый символ ещё кое-как, то без запятой как-то совсем не весело, верно? На наше счастье, в спецификации есть и это. Ячейку можно поместить в двойные кавычки (", кто не понял), тогда до следующей кавычки обрабатываться \n и , не будут. Давайте улучшим наш парсер, добавив поддержку этих самых кавычек. Так как у нас посимвольный парсинг, сделать это гораздо проще. Вот так: def parse_csv(s): result = [] row = [] cell = "" # Начиналась ли текущая ячейка с кавычки quoted = False for i in range(len(s)): c = s[i] if quoted: if c == '"': # Закрывающая кавычка quoted = False else: cell += c else: if c == '"': if not cell: # Открывающая кавычка в начале ячейки quoted = True else: # Кавычка в середине строки: запрещено return False elif c == ",": row.append(cell) cell = "" elif c == "\n": row.append(cell) cell = "" result.append(row) row = [] else: cell += c if cell: if quoted: # Где-то не закрыли кавычки return False row.append(cell) result.append(row) return result Проверяем: Всё верно, кроме последнего. В середине строки в закавыченных строках эти самые кавычки должны быть экранированы вот так: "". Например: "aaa""bbb,ccc",ddd,eee. Давайте починим и это. def parse_csv(s): result = [] row = [] cell = "" quoted = False # Является ли предыдущий символ кавычкой prevQuote = False for i in range(len(s)): c = s[i] if quoted: if c == '"': # Помечаем, что у нас есть кавычка в середине строки. # Она может быть экранированной. prevQuote = True quoted = False else: cell += c else: if c == '"': if not cell: quoted = True else: if prevQuote: # Если у нас прошлый символ был кавычкой, # то получаем экранированную кавычку. cell += '"' quoted = True prevQuote = False else: return False elif c == ",": row.append(cell) cell = "" # Кавычка была закрывающей prevQuote = False elif c == "\n": row.append(cell) cell = "" result.append(row) row = [] # Кавычка была закрывающей prevQuote = False else: if prevQuote: # Мы ждали кавычку или закрытие ячейки. return False cell += c if cell: if quoted: return False row.append(cell) result.append(row) return result Опять тестируем: Вот и всё. 44 строки кода на Python — и мы можем парсить CSV. Я также переписал парсер на Lua, опубликовал его в OPPM под libcsv. Можете качать и радоваться. Вот сырцы. Ну и надеюсь, это было менее сложно, чем мои записи про пакетные менеджеры до этого, и вы смогли прочитать это .
  5. Идём сюда. Качаем нужный файл (смотрим по версии кубача и дате создания). На 1.7.10 ставим Asielib отсюда. Выше уже не надо. Качаем последнюю версию OC 1.6. Ссылка в блоке наверху в правом сайдбаре. Кидаем в mods/. Запускаемся.
  6. Лучше бы вообще не читал, чем оставлял идиотские комменты пятилетнего ребёнка.
  7. В прошлой части: Вы-то прогу скопировать/разархировать и сами можете, вот только если программа зависит от другой, а та — от двух других, и т. д., вам это надоест. Людям надоело. Создали пакетные менеджеры. Итак, давайте сделаем программу для установки пакетов. Очевидно, что просто так в рандомном порядке пакеты поставить нельзя: нам надо сначала брать пакеты без неразрешённых зависимостей и подниматься вверх. Итак, у нас есть простая функция, которая составляет список пакетов для последовательной установки без ломаний.... ...Но время шло, и появилось такое явление как версии. Вот о них мы сегодня и побеседуем. Зачем нужны версии? Нуууу, наверное, чтобы отмечать разные варианты кода. Зачем ПМ нужны версии? Хм, чтобы быть уверенным, что при установке пакета, зависимости будут именно такие, которые были при написании кода. Ну, то есть. Вчера у нас была одна функция сделатьХорошо() и попала в релиз 1.0.0, а сегодня она переименована в сделатьПлохо(), сменили код этой функции, да ещё впридачу накинули ничегоНеСделать(). Всё это в релизе 2.0.0. Но если новый код будет использовать ничегоНеСделать(), то его версия 1.0.0 не устроит — там ведь функции нет. А если нужно будет сделатьХорошо(), то в версии 2.0.0 её уже не будет. Люди — существа чертовски изобретательные, так что форматы версий у нас тоже всяко-разно изобретательны. Начиная от даты релиза (20161023) и номером билда (1243, она ещё ревизией зовётся), заканчивая полноценным семантическим версионированием. Первые потуги нас интересовать будут не сильно, а вот про SemVer можно поговорить. Спецификация SemVer прямо говорит: версия задаётся тремя числами, разделённые точкой. Первое число — мажорная версия, увеличивается, когда происходят "ломающие" изменения типа удаления старых функций, второе — минорная версия, которая на новых фичах обычно увеличивается, а третье — патчи, это всякие багфиксы. Вот сферическая версия в вакууме: 1.2.3. На самом деле, в описании semver задано несколько правил, например, место пререлиза (например, 1.2.3-dev), метаданных (например, 1.2.3+build-15+20161023+amd64), ну и т.д. Если интересно — можете почитать, ссылочка в конце. Так вот, здесь попробуем организовать разрешение зависимостей с версиями. Не будем брать пока очень мудрёную систему конфликтов. Начнём с манифеста. Дополним его указанием версий: { "name": "pkg1", "versions": [ { "number": "1.0.0" , "files": [ { "url": "http://example.com/pkg1/1.0.0/file1", "path": "/opt/pkg1/file1" } , { "url": "http://example.com/pkg1/1.0.0file2", "path": "/opt/pkg1/file2" } ] , "depends": [ { "name": "pkg11", "version": "^1" } , { "name": "pkg12", "version": "1.6.2" } ] } ]} Ну и по аналогии с другими пакетами. Вот такое чудо у нас должно получиться. Напомню, чем у нас закончилась прошлая часть: def resolveDeps(name, resolved=None, unresolved=None): resolved = resolved or [] unresolved = unresolved or [] if name in unresolved: raise ValueError("circular dependencies detected") if name in resolved: return resolved unresolved.append(name) if not isInstalled(name): manifest = getManifest(name) for dep in manifest["deps"]: resolveDeps(dep, resolved, unresolved) resolved.append(name) del unresolved[unresoved.index(name)] return resolved Давайте перепишем эту функцию так, чтобы она работала после наших изменений в манифесты: def resolveDeps(name, resolved=None, unresolved=None): resolved = resolved or [] unresolved = unresolved or [] if name in unresolved: raise ValueError("circular dependencies detected") if name in resolved: return resolved unresolved.append(name) if not isInstalled(name): manifest = getManifest(name) version, data = getLatestVersion(manifest) # получаем последнюю версию for dep in latest["deps"]: resolveDeps(dep["name"], resolved, unresolved) resolved.append({"name": name, "version": version) del unresolved[unresoved.index(name)] return resolved Всё хорошо и замечательно, вот только толку от того, что мы ввели версии, как-то нет совсем. А вот далее нам потребуется очень серьёзная либа-парсер семверов. На Python есть semantic_version, которую я ещё портировал на MoonScript — очень доволен. Но это так, будем пока плавать на более высоком уровне абстракции. Итак, версии. Тот самый граф, ещё раз: Около стрелочек висят какие-то штуки, ^1, например. Эти штуки, которые мы ещё вписываем в манифесты пакетов, ограничивают варианты версий пакетов, которые можно поставить. ^1 говорит, что можно брать любую версию не менее 1.0.0 и не более следующего мажорного релиза (2.0.0). * говорит, что пофигу абсолютно, какая версия встанет. А точное указание версии, как, например, в случае с 1.6.2, не даёт установиться какой-либо другой версии. Ну а так как они ограничивают, то и называются они ограничениями (или constriants). Пакетный менеджер — скажем так, классическая задача о соблюдении ограничений. Отнюдь не простая. Раз есть версии, есть ограничения, нужно эти ограничения, значит, включить в функцию разрешателя. Например, дополнительным аргументом. Давайте так и поступим: def resolveDeps(name, vconstraint="*" resolved=None, unresolved=None): resolved = resolved or [] unresolved = unresolved or [] if name in unresolved: raise ValueError("circular dependencies detected") if name in resolved: return resolved unresolved.append(name) # Создаём объект ограничения из строки vconstraint = createSemVerConstraint(vconstraint) if not isInstalled(name): manifest = getManifest(name) version, data = vconstraint.match(manifest) # получаем версию, соответствующую ограничению for dep in data["deps"]: resolveDeps(dep["name"], dep["version"], resolved, unresolved) # и не забываем передавать версию требуемую resolved.append({"name": name, "version": version) del unresolved[unresoved.index(name)] return resolved При запуске resolveDeps("pkg1") мы теперь получим [ { "name": "pkg1-1-1" , "version": "1.0.1" }, { "name": "pkg1-1" , "version": "1.2.4" }, { "name": "pkg1-2" , "version": "1.6.2" }, { "name": "pkg1" , "version": "1.2.3" }] Вот как это на графе будет: Давайте теперь приспособим функцию install к установке: def install(name): depList = resolveDeps(name) for pkg in depList: manifest = getManifest(pkg["name"]) data = getVersion(manifest, pkg["version"]) for file in data["files"]: download(file["url"], file["path"] В общем-то, это всё. У нас есть вполне рабочая функция установки пакетов с учётом версии по Semantic Versioning. Однако у неё есть некоторые проблемы. Ну, во-первых, мы ошибочно считаем, что если пакет установлен, то у него нужная версия. Если у нас уже будет пакет версии 1.12.53, а мы потребуем ^2, то новая версия не поставится. Рискуем попасть на глюки, баги. Кажется, надо просто обновить пакет!.. Но при таком решении невозможно организовать обновление пакетов. Вообще. Никак. А почему? У нас зависимости задаются в версиях. Каждая версия может сменить зависимости. При этом каждая новая версия может не соблюдать ограничения, которые дают зависимые от данного пакеты. А вот вам адская ситуация: Так вот, чтобы обновить выделенный пакет без нарушения всех зависимостей, потребовалось разрешать конфликты версий. Это достаточно сложный алгоритм, и о нём мы поговорим как-нибудь потом. Тем более, мне только предстоит ввести в свой ПМ резолвер конфликтов. А пока можете почитать спецификацию SemVer. .
  8. Пакеты, пакеты, всем по пакету

  9. Раз уж я тут пишу понемногу свой крутой пакетный манагёр, расскажу о пакетных менеджерах немного. Пакетный менеджер — штука сложная. Потому что, хотя задача у него, в общем-то, одна — менеджировать пакеты — сюда включается и установка, и удаление, и обновление, и, вообще, много всякого. Но а так как пока сам не напишешь, ПМ не поймёшь, здесь расскажу об установке пакетов и зависимостей с кодом. Ещё немного предисловий, о зависимостях. Это ключевая фича ПМ: вы-то прогу скопировать/разархировать и сами можете, вот только если программа зависит от другой, а та — от двух других, и т. д., вам это надоест. Людям надоело. Создали пакетные менеджеры. Теперь программы пакуются в пакеты — а рядом со скомпилированными бинарниками лежит ещё кусок информации: имя пакета, версии, зависимости, авторы, изменения и много-много всяких других полей. При установке данные считываются и далее уже делается, что сказано. Зависимости ли ставятся, ещё ли что-нибудь. А затем пакетов становится много, появляются репозитории полноценные, ну и так далее. Итак, давайте сделаем программу для установки пакетов. Ну, почти. Именно полезной нагрузки как таковой не будет, будем использовать такую структуру информации о пакете (назовём это манифестом пакета): { "name": "имя пакета", "files": [ { "url": "http://example.com/bin-1", "path": "/usr/bin/program1" } , { "url": "http://example.com/library-1.so", "path": "/usr/lib/library1.so" } ]} Пока без зависимостей, всё просто. Вот такой код получим: def install(name): # получаем манифест пакета с данным именем manifest = getManifest(name) # проходимся по файлам... for file in manifest["files"]: # ...скачиваем и ставим их в нужные места download(url=file["url"], path=file["path"]) Ничего примечательного, на самом деле. Получаем манифест, скачиваем файлы и пишем "тадаам". Давайте сделаем вот такие манифесты: { "name": "pkg1" # имя пакета, "deps": # зависимости [ { "name": "pkg1-1" } , { "name": "pkg1-2" } ], "files": [ { "url": "http://example.com/pkg1/file", "path": "/opt/pkg1/file" } ]} { "name": "pkg1-1", "deps": [ { "name": "pkg1-1-1" } ], "files": [ { "url": "http://example.com/pkg1-1/file1", "path": "/opt/pkg1-1/file1" } , { "url": "http://example.com/pkg1-1/file2", "path": "/opt/pkg1-1/file2" } ]} { "name": "pkg1-1-1", "deps": [], "files": [ { "url": "http://example.com/pkg1-1-1/file", "path": "/opt/pkg1-1-1/file" } ] { "name": "pkg1-2", "deps": [], "files": [ { "url": "http://example.com/pkg1-2/file1", "path": "/opt/pkg1-2/file1" } , { "url": "http://example.com/pkg1-2/file2", "path": "/opt/pkg1-2/file2" } ]} У нас есть 4 пакета: pkg1, pkg1-1, pkg1-1-1, pkg1-2. Вот граф зависимостей: Очевидно, что просто так теперь тут в рандомном порядке пакеты поставить нельзя. Так как при установке пакета, например, pkg1-1, он совершенно справедливо считает, что его зависимость, pkg1-1-1, уже установлена. То есть, по-хорошему, нам надо сначала брать пакеты без неразрешённых зависимостей, и подниматься вверх. Однако, есть идея покруче. Я сейчас наваяю рекурсивную функцию resolveDeps, которая будет, как ни странно, разрешать зависимости: def resolveDeps(name): result = [] # результатирующая последовательность установки пакетов manifest = getManifest(name) for dep in manifest["deps"]: # В Python справедливен код типа `[1, 2, 3] + [4, 5, 6] == [1, 2, 3, 4, 5, 6]`, т.е. склеиваение списков. result = resolveDeps(dep["name"]) + result return result Если мы дадим ей манифесты, она выдаст вот такой список: ["pkg1-1-1", "pkg1-1", "pkg1-2", "pkg1"] — от менее сложного к более сложному. То, что нужно. Затем мы ставим просто их: for pkg in resolveDeps("pkg1"): install(pkg) Давайте улучшим алгоритм. Сделаем проверку на установленность: нам ведь не надо повторно скачивать файлы, которые уже есть. def resolveDeps(name): result = [] # Проверяем, установлен ли пакет if not isInstalled(name): manifest = getManifest(name) for dep in manifest["deps"]: result = resolveDeps(dep["name"]) + result return result Если у нас уже поставлен pkg1-1, то получим всего лишь ["pkg1-2", "pkg1"]. Круто! Возьмём другой граф: Как видно, от pkg1-1-1 зависят сразу 2 пакета: pkg1-1 и pkg1-2. Проблема в том, что на выходе у нас будет ["pkg1-1-1", "pkg1-1-1", "pkg1-1", "pkg1-2", "pkg1"] — ни разу не то, что мы хотели. Давайте это исправим: def resolveDeps(name, resolved=None): resolved = resolved or [] # список уже разрешённых пакетов if name in resolved: # Пакет уже был разрешён, ничего больше не требуется return resolved if not isInstalled(name): manifest = getManifest(name) for dep in manifest["deps"]: resolveDeps(dep["name"], resolved) # Теперь список один на всю рекурсию resolved.append(name) # Без рекурсии сюда попасть можно, если пакет не имеет неустановленных зависимостей return resolved Теперь выхлоп у нас ["pkg1-1-1", "pkg1-1", "pkg1-2", "pkg1"] — как и предписывали. А вот вам ещё граф: Какая тут засада? А у нас дерево циркулярное — не руки циркуляркой отрезает, а в бесконечную рекурсию вводит. Вот как можно этого избежать: def resolveDeps(name, resolved=None, unresolved=None): resolved = resolved or [] unresolved = unresolved or [] # список ещё не разрешённых пакетов if name in unresolved: # Мы попали сюда через рекурсию. Когда-то пакет уже был добавлен в список unresolved, # после чего функция ушла в рекурсивное разрешение зависимостей этого пакета. # Какой-то из зависимостей в итоге опять имеет данный пакет как зависимость. # Это ошибка, такого быть не должно, паникуем. raise ValueError("circular dependencies detected") if name in resolved: # Пакет уже был разрешён, ничего больше не требуется return resolved unresolved.append(name) if not isInstalled(name): manifest = getManifest(name) for dep in manifest["deps"]: resolveDeps(dep["name"], resolved, unresolved) # даём unresolved resolved.append(name) # Не забываем убирать из списка del unresolved[unresoved.index(name)] return resolved Теперь у нас в данном графе будет сгенерировано исключение, а потому рекурсии бесконечной не произойдёт. Итак, у нас есть простая функция, которая составляет список пакетов для последовательной установки без ломаний. Это уже круто, но время шло, и появилось такое явление как версии. Впрочем, об этом поговорим в другой раз. Там есть свои заморочки, с которыми нужно разобраться. Вот похожая статья, но на английском. Рекомендую ознакомиться. Лицензия: CreativeCommons Attribution-NonCommercial 4.0 International License
  10. Видишь же в топике [OC 1.6]. Работает прекрасно, проверял сам. Чего ей ещё нужно — не представляю. Она же копать только должна.
  11. К чёрту notepad++, какие-то другие IDE (которые именно с OC Lua работать не могут от слова никак). Юзайте, господа, vim! И да прибудет к вам счастье.
  12. У разрабов MineOS ты ничего не воровал. Та либа написана вообще не им. Как и другие две. Но результат зато красивый
  13. А листья ржавеют...

    1. davial

      davial

      Видать : влажность большая ... =)

  14. Обожаю некропостить, особенно, когда есть причина :P Отпушил эту действительно полезную библиотеку на OpenPrograms. oppm install libvector. Странно, что тема висит незакреплённой в разделе. И сам ещё несколько раз пролистывал, когда отбирал.
  15. Fingercomp

    BuMPGold // завершён

    Очень трудно дать оценку. Одно только решение соответствует всем требованием (и это было моё, свой вывод надо было сравнивать с образцом), но оно длинное, есть короткое, но неверное. И вот как теперь выбирать? В общем, в этот раз будем считать, что просто попрактиковались в написании маленького кода, к сожалению. Объективно выбрать победителя просто невозможно. Конкурс завершён, победителей нет.
  16. Это НЕ хардкор. Это детские ясельки. Как минимум, насколько я понимаю.
  17. Fingercomp

    BuMPGold // завершён

    Ждите понедельника, сейчас ещё пятница
  18. реинкорнации — raincornation — rain corn nation — нация дождливого орешка! Какое отношение это имеет к итлиту? Зная Алекса, он, конечно, первым делом станет на каждый чих менять сборку, угу.
  19. Так и есть, многопоточность для бедных Хотя даже и до такого титула не дотягивает. Ибо вызывает все функции по очереди из таблицы на полученный сигнал, так что если один заглохнет, умрёт всё. Ты и сам-то не шибко грамотный, если честно. Главное, что не вырвиглазно писано.
  20. Fingercomp

    BuMPGold // завершён

    Приём работ закрыт! Итоги организую в понедельник.
×
×
  • Создать...