Kodomo

Пользователь

Разбор задачи

Я не удержусь, и добавлю несколько комментариев / нравоучений по поводу этого примера...

Про etree:

Мне помнится, что из вас почти никто так и не пытался использовать etree, так что прошу вас посмотреть в мою версию, чтобы иметь какое-то представление о том, как оно применяется в жизни. Сравните это с вашими решениями на регулярных выражениях, устыдитесь и проникнетесь тем, мимо какой полезной штуки вы прошли не глядя! (Я не акцентирую ваше внимание на том, что etree сделан в согласии со стандартом XML, и поэтому он никак не зависит от того, где автор какого-нибудь XML-ника решил сделать перенос строки). Попробуйте хотя бы посчитать число строк, которое заняло решение какой-то подзадачки у вас на регулярных выражениях, и сколько строк потребовалось здесь!

Дабы проще было разбираться, попробуйте отследить путь переменных:

Про преобразование деревьев:

В ваших решениях я наблюдал несколько подходов к решению задачи: как нам перевести деревья из представления, где у каждого узла известен родитель (в котором они в syntagrus), к представлению, в котором у каждого родителя известны дети (которое мы умеем рисовать). Большинство подходов сводились, в конечном счёте, к следующему: мы создаём родителя, а потом проходим по списку всех известных вершин дерева, и если у кого-нибудь данный узел указывался в качестве родителя, то добавляли его в качестве ребёнка. Собственно, мой подход здесь ничем не отличается.

Это решение работает за квадратичное время от количества вершин, но так как предложений даже с сотней слов уже почти не бывает (а 100^2 = 10000, что не очень много), то такое время работы вполне приемлемо.

Эту задачу можно решать и за линейное время, совсем просто. Предположим, parents – словарь, в котором по ключу узлу можно получить значение родителя узла. Мы хотим сделать словарь children, в котором по ключу родителю лежит список детей:

   1 children = {}
   2 for node in parents:
   3     parent = parents[node]
   4     if parent not in children:
   5          children[parent] = []
   6     children[parent].append(node)

Доска почёта: Аня Выборнова и Егор Лакомкин изобрели такой же подход.

Про рисование интерфейса:

В лице класса GUI и того, как он запускается, я демонстрирую здесь довольно общепринятый сейчас подход к тому, как делать интерфейсы (что графические, что вебовские – впрочем, в flask оно выглядит проще). А именно: мы заводим класс (здесь GUI), в котором есть один-несколько методов для рисования (здесь это create_ui*), несколько методов, которые обрабатывают события (здесь это open, search, redraw), и метод run, запуск которого и есть запуск всей программы.

Про всякое рисование окошек я здесь показал почти все возможности, которые есть в Tkinter, надеюсь, этот код не выглядит страшным.

Про оформление кода:

Хорошим тоном считается:

Ещё хорошим тоном считается иметь гораздо больше договорённостей с собой о том, как делать мелочи (чтобы одинаковые вещи всегда делались одинаково) и соблюдать их. Думаю, моё решение вполне можно воспринимать и как пример применения этих идей к жизни. Не нужно стремиться к тому, чтобы ваш код был похож на мой, но я очень рекомендую, чтобы вы старались делать код столь же упорядоченным. Это упрощает чтение кода – и в первую очередь, вам же самим во время отладки.

Код программы

   1 # coding: utf-8
   2 import Tkinter
   3 import tkFileDialog
   4 from xml.etree import ElementTree
   5 
   6 class Tree(object):
   7         dx = 25
   8         dy = 25
   9         font = 'Arial'
  10 
  11         def __init__(self, canvas, sentence, id=None):
  12                 if id is None:
  13                         self.word = sentence.find('.//*[@DOM="_root"]')
  14                         id = self.word.attrib['ID']
  15                 else:
  16                         self.word = sentence.find('.//*[@ID="{0}"]'.format(id))
  17                 self.canvas = canvas
  18                 self.sentence = sentence
  19                 self.children = [Tree(canvas, sentence, word.attrib['ID'])
  20                         for word in sentence.findall('.//*[@DOM="{0}"]'.format(id))]
  21 
  22         def draw(self):
  23                 self._draw_node(self.dx, self.dy)
  24                 self._row = [self]
  25                 while self._row:
  26                         self._draw_row()
  27                         self._next_row()
  28 
  29         def _draw_row(self):
  30                 max_x = self.dx
  31                 for parent in self._row:
  32                         for child in parent.children:
  33                                 child._draw_node(max_x, parent.y1 + self.dy)
  34                                 max_x = child.x1 + self.dx
  35                                 parent._draw_line(child)
  36 
  37         def _draw_node(self, x, y):
  38                 self.canvas_id = self.canvas.create_text(
  39                         (x, y),
  40                         text=self.word.text,
  41                         anchor='nw',
  42                         justify='center',
  43                         font=self.font,
  44                 )
  45                 self.x0, self.y0, self.x1, self.y1 = self.canvas.bbox(self.canvas_id)
  46 
  47         def _draw_line(self, child):
  48                 x0, y0 = (self.x0 + self.x1) / 2, self.y1
  49                 x1, y1 = (child.x0 + child.x1) / 2, child.y0
  50                 self.canvas.create_line((x0,y0), (x1,y1), arrow='last')
  51 
  52         def _next_row(self):
  53                 result = []
  54                 for parent in self._row:
  55                         for child in parent.children:
  56                                 result.append(child)
  57                 self._row = result
  58 
  59 class GUI(object):
  60         W, H = 500, 500
  61 
  62         def __init__(self):
  63                 self.xmls = {}
  64                 self.xml = ElementTree.fromstring("<xml/>")
  65                 self.create_ui()
  66 
  67         def create_ui(self):
  68                 self.root = Tkinter.Tk()
  69                 self.create_ui_top()
  70                 self.create_ui_middle()
  71                 self.create_ui_bottom()
  72 
  73         def create_ui_top(self):
  74                 top = Tkinter.Frame(self.root)
  75                 self.query = query = Tkinter.Entry(top)
  76                 search = Tkinter.Button(top, text="Search", command=self.search)
  77                 openfile = Tkinter.Button(top, text="Open file", command=self.open)
  78                 query.pack(side="left", fill="both", expand="yes")
  79                 search.pack(side="left")
  80                 openfile.pack(side="left")
  81                 top.pack(side="top", fill="x")
  82                 query.bind("<Return>", self.search)
  83 
  84         def create_ui_middle(self):
  85                 middle = Tkinter.Frame(self.root)
  86                 self.sentences = sentences = Tkinter.Listbox(middle)
  87                 scroll = Tkinter.Scrollbar(middle, command=sentences.yview)
  88                 sentences['yscrollcommand'] = scroll.set
  89                 sentences.pack(side="left", fill="both", expand="yes")
  90                 scroll.pack(side="left", fill="y")
  91                 middle.pack(side="top", fill="both", expand="yes")
  92                 sentences.bind("<<ListboxSelect>>", self.redraw)
  93 
  94         def create_ui_bottom(self):
  95                 self.canvas = canvas = Tkinter.Canvas(self.root,
  96                         width=self.W, height=self.H, background="white")
  97                 canvas.pack(side="top", fill="both", expand="yes")
  98 
  99         def open(self):
 100                 file = tkFileDialog.askopenfile()
 101                 self.xml = ElementTree.parse(file).getroot()
 102 
 103         def search(self, ev=None):
 104                 query = self.query.get()
 105                 self.xmls = {}
 106                 self.sentences.delete(0, "end")
 107                 xml_sentences = self.xml.findall(u'.//*[@LEMMA="{0}"]/..'.format(query.upper()))
 108                 for xml_sentence in xml_sentences:
 109                         sentence = "".join(word.text + word.tail for word in xml_sentence)
 110                         sentence = "".join(xml_sentence.itertext())
 111                         sentence = sentence.replace("\n", "")
 112                         self.xmls[sentence] = xml_sentence
 113                         self.sentences.insert("end", sentence)
 114                 if not xml_sentences:
 115                         self.sentences.insert("end", "nothing found")
 116 
 117         def redraw(self, event):
 118                 self.canvas.delete("all")
 119                 sentence = self.sentences.get("active")
 120                 xml_sentence = self.xmls[sentence]
 121                 Tree(self.canvas, xml_sentence).draw()
 122 
 123         def run(self):
 124                 return self.root.mainloop()
 125 
 126 if __name__ == "__main__":
 127         GUI().run()