1 (изменено: ypppu, 2019-02-23 20:20:49)

Тема: AHK: Парсинг дерева

Доброго времени суток. Как захватить последнюю/оставшуюся строку(в данном случае "last string 3")?


input:="string1`nstring2`nlast string 3"
pos:=1
while(pos:=RegExMatch(input,"m)(*ANYCRLF)(.*)(`r`n|`n|`r)",out,pos+Strlen(out)))
	msgbox % out

2 (изменено: KusochekDobra, 2019-02-23 02:08:26)

Re: AHK: Парсинг дерева

Сепарируйте по признаку новой строки:

For i, out in StrSplit("string1`nstring2`nlast string 3","`n")
	msgbox % out

Или так:

input:="string1`nstring2`nlast string 3"
pos:=1
while(pos:=RegExMatch(input,"[[:print:]\d]+",out,pos+Strlen(out)))
	msgbox % out

3 (изменено: MandarinKa02, 2019-02-23 02:22:23)

Re: AHK: Парсинг дерева

Благодарю за ответ. Рассматривал эту функцию, не совсем подходит. Я пишу функцию для парсинга файла, который имеет следующую иерархию:


Казахстан
	Акмолинская область
		Акколь
		Степногорск
		Степняк
		Щучинск
	Актюбинская область
		Актобе
		Хромтау
		Шалкар
		Эмба
	Алматинская область
		Есик
		Жаркент
	Алматы

При каждом новом отступе, функция вызывает саму себя для парсинга следующей секции, и в параметре передается отступ от начала строки. Возможно ли поправить RegExMatch или есть другой более правильный вариант?


С кириллицей не робит.

KusochekDobra пишет:

Или так:

input:="строка1`nstring2`nlast string 3"
pos:=1
while(pos:=RegExMatch(input,"[[:print:]\d]+",out,pos+Strlen(out)))
	msgbox % out

4

Re: AHK: Парсинг дерева

input:="string1`nstring2`nlast string 3"
msgbox % RegExReplace(input, "s)^.*\R(.*?)$", "$1")

5

Re: AHK: Парсинг дерева

MandarinKa02 пишет:

Я пишу функцию для парсинга файла, который имеет следующую иерархию

Такое через Loop, parse проще. На каждой итерации записываете текущий отступ и сравниваете с предыдущим, если равны — значит тот же уровень, если меньше, значит родительский, если больше, значит вложенный.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

6

Re: AHK: Парсинг дерева

OFF: KusochekDobra, а зачем в русском языке слово "сепарировать", в то время как есть "разделять"?

7

Re: AHK: Парсинг дерева

Сложный вопрос. Нужно знать, чем руководствовался тот, кто ввёл этот синоним.
Если Вы спрашиваете, почему я использовал первый, вместо второго, то тут тоже всё сложно. Раз это смысловые эквиваленты, то можно было бы задаться тем же вопросом, если бы было наоборот.

8 (изменено: stealzy, 2019-02-23 13:36:57)

Re: AHK: Парсинг дерева

Грамотно съехал с темы. Я не я и синонима не моя .
За маркет-то кто отвечать будет?

9

Re: AHK: Парсинг дерева

KusochekDobra пишет:

Раз это смысловые эквиваленты,

KusochekDobra пишет:

тот, кто ввёл этот синоним

KusochekDobra пишет:

руководствовался

сепарированием русского языка)).

10 (изменено: MandarinKa02, 2019-02-23 16:21:57)

Re: AHK: Парсинг дерева

teadrinker, спасибо за ответ. Если в моем случае взять Loop,parse вместо RegExMatch(функция getString), то будет немалый удар по производительности. Получится так что, loop каждый раз будет бегать от начала файла до того момента, где сейчас проходит парсинг. Для маленького файла(пример выше) ничего сложного, но это только 1/100(а то и 1/1000) от базы городов.

teadrinker пишет:

На каждой итерации записываете текущий отступ и сравниваете с предыдущим, если равны — значит тот же уровень, если меньше, значит родительский, если больше, значит вложенный.

У меня такой алгоритм:
- считываем текущую строку
- считываем следующую
если у следующей больше "табов", чем у текущей, то создаем объект
иначе curObj.Push(line)

+ code
class parser {
	Parse(input,curpos=0,curTab=-1) {
		curObj:=[]
		;curTab:=0
		pos:=curpos?curpos:1
		length:=0

		while (pos:=this.getString(input,pos+length,line,length)) {
			if(!line || RegExMatch(line,"^(\s{0,})\/\/") || RegExMatch(line,"^(\s{0,})$")) ;"//"
				Continue
			tab:=this.getTabs(line)
			if(curTab=-1) {
				if(!tab) {
					curTab:=0
				} else return -1
			} else tab-=curTab
			if(tab=-1) { ;inloop
				pos-=length
				break
			}


			while(1) {
				if(!this.getString(input,pos+length,next,nextLength)) {
					Break
				} nextTab:=this.getTabs(next)-curTab

				this.delTabs(next)
				if(!next)
					length+=nextLength
				else Break
			} this.delTabs(line)
			if(tab<nextTab)
				this.delTabs(line)
				,curObj[line]:=[]
				,i:=this.Parse(input,pos+length,nextTab)
				,ObjectPut2Object(i[1],curObj[line])
				,pos:=i[2],length:=0
			else this.delTabs(line),curObj.Push(line)
			if(tab>nextTab) ;in loop
				Break
		} return curpos?[curObj,pos+length]:curObj
	}
	getString(input,pos,ByRef line,ByRef length="") {
		i:=RegExMatch(input,"m)(*ANYCRLF)(.*)(`r`n|`n|`r)",out,pos)
		length:=StrLen(out),line:=out1
		return i
	}
	getTabs(line) {
		if(RegExMatch(line,"^(\t{1,})",match))
			return StrLen(match)
		return 0
	}
	delTabs(ByRef line) {
		line:=StrReplace(line,"`t")
	}
}

P.S. Сейчас использую костыль - `n в конец строки(файла), но все же, хотелось бы довести до блеска.

11

Re: AHK: Парсинг дерева

А чего вы хотите добится, есть список, что из него надо получить?

По вопросам возмездной помощи пишите на E-Mail: serzh82saratov@mail.ru Telegram: https://t.me/sergiol982
Win10x64 AhkSpy, Hotkey, ClockGui

12

Re: AHK: Парсинг дерева

MandarinKa02 пишет:

loop каждый раз будет бегать от начала файла до того момента, где сейчас проходит парсинг

Просто проходите один раз сначала до конца.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

13

Re: AHK: Парсинг дерева

serzh82saratov, в объект преобразовать.

teadrinker, как в таком случае получить следующую строку?
Допустим я сохраню предыдущую строку в переменную, и при каждом последующем парсинге новой строки, буду обрабатывать и предыдущую и новую. Здесь всё понятно, но как в таком случае "играться" с объектами?

Может, меня не правильно поняли. Из такого файла:

Млечный путь
 Земля
  Азия
   Россия
    Московская область
     Москва

я должен получить следующий объект:

[Млечный путь]
 [Земля]
  [Азия]
   [Россия]
    [Московская область]
     1=Москва

14

Re: AHK: Парсинг дерева

Вот примерчик. Не уверен, что это именно то, что нужно, но принцип должен быть понятен:

data =
(
Казахстан
	Акмолинская область
		Акколь
		Степногорск
		Степняк
		Щучинск
	Актюбинская область
		Актобе
		Хромтау
		Шалкар
		Эмба
	Алматинская область
		Есик
		Жаркент
		Алматы
РФ
	Ленинградская область
		Павловск
		Гатчина
)

obj := {}

Loop, parse, data, `n, `r
{
   RegExMatch(A_LoopField, "^(?<indent>`t*)(?<text>.*)", _)
   if (_indent = "")
      child1 := obj[_text] := {}
   if (_indent = A_Tab)
      child2 := child1[_text] := []
   if (_indent = A_Tab . A_Tab)
      child2.Push(_text)
}

for k, v in obj["Казахстан", "Актюбинская область"]
   MsgBox, % v

for k, v in obj["РФ", "Ленинградская область"]
   MsgBox, % v
Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

15

Re: AHK: Парсинг дерева

teadrinker, то, но он одноразовый. Для нашей Вселенной не подойдет.
Вся сложность заключается в объектах. Похоже, придется написать отдельный класс, чтобы можно было удобно обращаться с ними.
Вызвал функцию obj.getParent() и он вернул объект-родитель. (наподобие DOM)

16

Re: AHK: Парсинг дерева

Такая форма хранения данных с вложенной структурой показалась интересной. Попробовал сделал черновик. Тут счётчик табуляций показывает степень вложенности. Используя его, можно создать объект, а по объекту можно отрисовывать TreeView.

input_tree =
(
Казахстан
	Акмолинская область
		Акколь
		Степногорск
		Степняк
		Щучинск
	Актюбинская область
		Актобе
		Хромтау
		Шалкар
		Эмба
	Алматинская область
		Есик
		Жаркент
	Алматы
)


;Разбиваем исходные данные построчно 
Loop, Parse, input_tree, `n, `r
{
My_Line := A_LoopField

	;Разбиваем текущую строку по табуляции
	Loop, Parse, My_Line, `t
	{
	;MsgBox, Текущая итерация: %A_Index% `n Текущее поле: %A_LoopField%
	nest_lvl := A_Index, value := A_LoopField
	}
MsgBox, nest_lvl: %nest_lvl% `n value: %value%
}


Tree_object := {}
Tree_object["Level_1", "Level_2", "Level_3"] := value

17

Re: AHK: Парсинг дерева

MandarinKa02 пишет:

он одноразовый

Почему, можно для любого уровня вложенности сделать, главное знать последний уровень, на котором нужно заканчивать цепочку, либо придётся 2 раза проходить.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

18 (изменено: serzh82saratov, 2019-02-23 17:42:49)

Re: AHK: Парсинг дерева

Если вложенность одинаковая, то её можно одним регексом определить, что то типа - m)^(\t+)\w$. Но везде ли она одинаковая? Где то ведь иерархия "страна - область - населённый пункт" может не подойти.

АП: Ошибаюсь про регекс, перебор понадобится.

По вопросам возмездной помощи пишите на E-Mail: serzh82saratov@mail.ru Telegram: https://t.me/sergiol982
Win10x64 AhkSpy, Hotkey, ClockGui

19 (изменено: teadrinker, 2019-02-24 20:50:02)

Re: AHK: Парсинг дерева

Чтобы не заботиться о вложенности, можно так:

data =
(
Казахстан
	Акмолинская область
		Акколь
		Степногорск
		Степняк
		Щучинск
	Актюбинская область
		Актобе
		Хромтау
		Шалкар
		Эмба
	Алматинская область
		Есик
		Жаркент
		Алматы
РФ
	Ленинградская область
		Павловск
		Гатчина
)
obj := {}
child0 := obj
Loop, parse, data, `n, `r
{
   RegExMatch(A_LoopField, "^(?<indent>`t*)(?<text>.*)", _)
   StrReplace( _indent, A_Tab, "", level)
   prevLevel := level, level += 1
   child%level% := child%prevLevel%[_text] := {}
}

for k in obj["Казахстан", "Актюбинская область"]
   MsgBox, % k

for k in obj["РФ", "Ленинградская область"]
   MsgBox, % k
Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

20

Re: AHK: Парсинг дерева

Норм.
Можно кстати в один проход сделать чтобы "города" были значениями, а не ключами, просто проверяя следующую строку на такую же вложенность, но после твоего примера уже не интересно.

По вопросам возмездной помощи пишите на E-Mail: serzh82saratov@mail.ru Telegram: https://t.me/sergiol982
Win10x64 AhkSpy, Hotkey, ClockGui

21 (изменено: MandarinKa02, 2019-02-23 19:58:14)

Re: AHK: Парсинг дерева

Да, города должны быть значениями. Вот, что получилось у меня:


class NEI {
	Parse(input) {

		obj:=[]
		curObj:=obj,objStack:=[],objStack.Push(curObj)

		curTab:=-1,prevLine:=-1,prevTab:=-1
		Loop, Parse, input, `n, `r
		{
			if(!(line:=A_LoopField) || RegExMatch(line,"^(\s{0,})\/\/") || RegExMatch(line,"^(\s{0,})$")) ;"//" or empty line
				Continue
			if(curTab=-1) {
				curTab:=0,prevLine:=line
				if(prevTab:=this.getTabs(line)) ;line starts with TAB and has't obj-parent
					return -1
				this.delTabs(prevLine)
				Continue
			} tab:=this.getTabs(line)
			if(tab-curTab<0) { ;prev level
				curObj.Push(prevLine)
				loop % Abs(tab-curTab)
					objStack.Pop() ;remove last object
				curObj:=objStack[objStack.MaxIndex()]
			} this.delTabs(line)

			if(tab=prevTab) ;curr level
				curObj.Push(prevLine)
			else if(tab>prevTab) ;new level
				curObj[prevLine]:=[]
				,curObj:=curObj[prevLine]
				,objStack.Push(curObj)
				,curTab:=tab
			else if(prevTab-tab>1)
				curObj[line]:=[],curObj:=curObj[line],objStack.Push(curObj)
			prevLine:=line,prevTab:=tab
			this.delTabs(prevLine)
			Continue
		} curObj.Push(prevLine)
		return obj
	}
	getTabs(line) {
		if(RegExMatch(line,"^(\t{1,})",match))
			return StrLen(match)
		return 0
	}
	delTabs(ByRef line) {
		line:=StrReplace(line,"`t")
	}
}

P.S. вроде как, работает.

22

Re: AHK: Парсинг дерева

Как-то сложно вроде. Потом попроще попробую.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

23 (изменено: MandarinKa02, 2019-02-23 20:31:16)

Re: AHK: Парсинг дерева

Сравнил по скоростям.
Первоначальный парсер(с регексом) занимает 207-218мс.
Новое решение 15-31мс.
Размер файла: 94 569 байт.
Кол-во символов: 51 607 (unicode)

24

Re: AHK: Парсинг дерева

Кстати если будет допилено, можно добавить в тему в коллекции про "Сохранение массива в строку", в текущем варианте есть проблемы, я хотел даже удалить, теперь не вспомню с чем было связано. Единственно надо решить что делать с лидирующей табуляцией в строке.

По вопросам возмездной помощи пишите на E-Mail: serzh82saratov@mail.ru Telegram: https://t.me/sergiol982
Win10x64 AhkSpy, Hotkey, ClockGui

25

Re: AHK: Парсинг дерева

serzh82saratov пишет:

Единственно надо решить что делать с лидирующей табуляцией в строке

Это вы о моем решении? Что вы подразумеваете под лидирующей табуляцией?

26

Re: AHK: Парсинг дерева

Нет, забыл указать что я обращался к teadrinker.

По вопросам возмездной помощи пишите на E-Mail: serzh82saratov@mail.ru Telegram: https://t.me/sergiol982
Win10x64 AhkSpy, Hotkey, ClockGui

27

Re: AHK: Парсинг дерева

Сейчас без компьютера, в понедельник попробую.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

28 (изменено: teadrinker, 2019-02-24 20:37:58)

Re: AHK: Парсинг дерева

MandarinKa02

data =
(
МИР
	Казахстан
		Акмолинская область
			Акколь
			Степногорск
			Степняк
			Щучинск
		Актюбинская область
			Актобе
			Хромтау
			Шалкар
			Эмба
		Алматинская область
			Есик
			Жаркент
			Алматы
	РФ
		Ленинградская область
			Павловск
			Гатчина
)
obj := {}
child0 := obj
Loop, parse, data, `n, `r
{
   RegExMatch(A_LoopField, "^(?<indent>`t*)(?<text>.*)", _)
   StrReplace( _indent, A_Tab, "", level)
   prevLevel := level, level += 1
   if (A_Index > 1) {
      if (level > prev_level)
         child%prev_level% := child%prev_prevLevel%[prev_text] := {}
      else
         child%prev_prevLevel%.Push(prev_text)
   }
   prev_level := level, prev_prevLevel := prevLevel, prev_text := _text
}
child%prev_prevLevel%.Push(_text)

MsgBox, % obj["МИР", "Казахстан", "Алматинская область", 1] . "`n"
        . obj["МИР", "РФ", "Ленинградская область", 2]

И мой, и ваш вариант имеют смысл, только если в области есть хотя бы один город; если это не так, то логика ломается. В таком случае придётся делать предварительный проход, чтобы узнать максимальную вложенность.
Я бы вообще остановился на этом варианте, как на более универсальном.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

29

Re: AHK: Парсинг дерева

teadrinker, вы под логикой подразумеваете, что область(Алтайский край) будет не объектом, а значением?

+ открыть спойлер
Россия
 Алтайский край
 Московская область
  Апрелевка
  Балашиха
  Бронницы

На выходе:

Россия
 1=Алтайский край
 [Московская область]
  1=..
  2=..
  3=..

В моем случае это не страшно. Сделаю отдельную функцию, которая "выпилит" пустые области и всё готово.
В целях было только сам парсер сделать, чтобы "играться" с данными. А то, как эти данные должны выглядеть, это уже другой вопрос.
Города и области в качестве примера привел. А так, это может быть что угодно.
Всем спасибо за помощь.

P.S. ваш скрипт быстрее в два раза.

30

Re: AHK: Парсинг дерева

Получился вот такой класс. Парсит в объект и обратно. Думаю, можно в коллекцию.

class NEI {
	Parse(input) {

		obj:=[]
		curObj:=obj,objStack:=[],objStack.Push(curObj)

		curTab:=prevLine:=prevTab:=-1
		Loop, Parse, input, `n, `r
		{
			if(!RegExMatch(A_LoopField, "^(?<tab>`t*)(?<line>.*)", _) || !_line || RegExMatch(_line,"^(\s{0,})\/\/")) ;empty line or "//" 
				Continue
			StrReplace(_tab, A_Tab, "", tab)
			if(curTab=-1) {
				curTab:=0,prevLine:=_line
				if(prevTab:=tab) ;line starts with TAB and has't obj-parent
					return -1
				Continue
			} if(tab-curTab<0) { ;prev level
				curObj.Push(prevLine)
				loop % Abs(tab-curTab)
					objStack.Pop() ;remove last object
				curObj:=objStack[objStack.MaxIndex()]
			}

			if(tab=prevTab) ;curr level
				curObj.Push(prevLine)
			else if(tab>prevTab) ;new level
				curObj[prevLine]:=[]
				,curObj:=curObj[prevLine]
				,objStack.Push(curObj)
				,curTab:=tab
			else if(prevTab-tab>1)
				curObj[_line]:=[],curObj:=curObj[_line],objStack.Push(curObj)
			prevLine:=_line,prevTab:=tab
		} curObj.Push(prevLine)
		return obj
	}
	Dump(obj,tab=0) {
		if(!isObject(obj))
			return -1
		out:=""
		for i,v in obj {
			if(isObject(v))
				out.=this._mult_tab(tab) i "`n" this.Dump(v,tab+1)
			out.=this._mult_tab(tab) v (A_Index=obj.MaxIndex()?"":"`n")
		} return out
	}
	_mult_tab(i) {
		out:=""
		loop % i
			out.=A_Tab
		return out
	}
}

Замерил время парсинга:
Размер файла: 94 569 байт.
Кол-во символов: 51 607 (unicode)

Ваш скрипт: 15мс.
Скрипт из данного поста: 16мс.

+1мс за счет проверки на возможные комментарии в файле.

31 (изменено: teadrinker, 2019-02-24 23:30:26)

Re: AHK: Парсинг дерева

Не работает:

data =
(
МИР
   Казахстан
      Акмолинская область
         Акколь
         Степногорск
         Степняк
         Щучинск
      Актюбинская область
      Алматинская область
         Есик
         Жаркент
         Алматы
   РФ
      Ленинградская область
         Павловск
         Гатчина
)

obj := NEI.Parse(data)
MsgBox, % obj["МИР", "Казахстан", "Алматинская область", 1]

class NEI {
   Parse(input) {

      obj:=[]
      curObj:=obj,objStack:=[],objStack.Push(curObj)

      curTab:=prevLine:=prevTab:=-1
      Loop, Parse, input, `n, `r
      {
         if(!RegExMatch(A_LoopField, "^(?<tab>`t*)(?<line>.*)", _) || !_line || RegExMatch(_line,"^(\s{0,})\/\/")) ;empty line or "//" 
            Continue
         StrReplace(_tab, A_Tab, "", tab)
         if(curTab=-1) {
            curTab:=0,prevLine:=_line
            if(prevTab:=tab) ;line starts with TAB and has't obj-parent
               return -1
            Continue
         } if(tab-curTab<0) { ;prev level
            curObj.Push(prevLine)
            loop % Abs(tab-curTab)
               objStack.Pop() ;remove last object
            curObj:=objStack[objStack.MaxIndex()]
         }

         if(tab=prevTab) ;curr level
            curObj.Push(prevLine)
         else if(tab>prevTab) ;new level
            curObj[prevLine]:=[]
            ,curObj:=curObj[prevLine]
            ,objStack.Push(curObj)
            ,curTab:=tab
         else if(prevTab-tab>1)
            curObj[_line]:=[],curObj:=curObj[_line],objStack.Push(curObj)
         prevLine:=_line,prevTab:=tab
      } curObj.Push(prevLine)
      return obj
   }
   Dump(obj,tab=0) {
      if(!isObject(obj))
         return -1
      out:=""
      for i,v in obj {
         if(isObject(v))
            out.=this._mult_tab(tab) i "`n" this.Dump(v,tab+1)
         out.=this._mult_tab(tab) v (A_Index=obj.MaxIndex()?"":"`n")
      } return out
   }
   _mult_tab(i) {
      out:=""
      loop % i
         out.=A_Tab
      return out
   }
}
Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

32 (изменено: serzh82saratov, 2019-02-24 23:23:10)

Re: AHK: Парсинг дерева

А чем должно быть "Актюбинская область"?
Одним из значений "Казахстан"?
Или одним из объектов "Казахстан", тогда с каким значением?

По вопросам возмездной помощи пишите на E-Mail: serzh82saratov@mail.ru Telegram: https://t.me/sergiol982
Win10x64 AhkSpy, Hotkey, ClockGui

33 (изменено: teadrinker, 2019-02-24 23:29:05)

Re: AHK: Парсинг дерева

Так я о том и говорю. Если руководствоваться логикой, что всё, у чего есть вложенные строки, должно быть ключами, а всё, у чего нет, значениями — получается нелогично, что строки одного уровня вложенности в объекте могут быть представлены по-разному.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

34

Re: AHK: Парсинг дерева

Ок, если все строки в итоге будут объектами.
ТС писал про getParent, как думаешь, можно как то у каждого объекта сохранять некую ссылку на его родительский объект?

По вопросам возмездной помощи пишите на E-Mail: serzh82saratov@mail.ru Telegram: https://t.me/sergiol982
Win10x64 AhkSpy, Hotkey, ClockGui

35

Re: AHK: Парсинг дерева

Можно, но может путаница получиться. Придётся как-то отличать ключ со ссылкой от остальных ключей.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

36 (изменено: MandarinKa02, 2019-02-24 23:57:40)

Re: AHK: Парсинг дерева

teadrinker, поправил.

+ открыть спойлер

class NEI {
	Parse(input) {

		obj:=[]
		curObj:=obj,objStack:=[],objStack.Push(curObj)

		curTab:=prevLine:=prevTab:=-1
		Loop, Parse, input, `n, `r
		{
			if(!RegExMatch(A_LoopField, "^(?<tab>`t*)(?<line>.*)", _) || !_line || RegExMatch(_line,"^(\s{0,})\/\/")) ;empty line or "//" 
				Continue
			StrReplace(_tab, A_Tab, "", tab)
			if(curTab=-1) {
				curTab:=0,prevLine:=_line
				if(prevTab:=tab) ;line starts with TAB and has't obj-parent
					return -1
				Continue
			} if(tab-curTab<0) { ;prev level
				curObj.Push(prevLine)
				loop % Abs(tab-prevTab)
					objStack.Pop() ;remove last object
				curObj:=objStack[objStack.MaxIndex()]
			}

			if(tab=prevTab && tab-curTab>=0) ;curr level && already added in line 20
				curObj.Push(prevLine)
			else if(tab>prevTab) ;new level
				curObj[prevLine]:=[]
				,curObj:=curObj[prevLine]
				,objStack.Push(curObj)
				,curTab:=prevTab+1
			else if(prevTab-tab>1)
				curObj[_line]:=[],curObj:=curObj[_line],objStack.Push(curObj)
			prevLine:=_line,prevTab:=tab
		} curObj.Push(prevLine)
		return obj
	}
	Dump(obj,tab=0) {
		if(!isObject(obj))
			return -1
		out:=""
		for i,v in obj {
			if(isObject(v))
				out.=this._mult_tab(tab) i "`n" this.Dump(v,tab+1)
			out.=this._mult_tab(tab) v (A_Index=obj.MaxIndex()?"":"`n")
		} return out
	}
	_mult_tab(i) {
		out:=""
		loop % i
			out.=A_Tab
		return out
	}
}
teadrinker пишет:

всё, у чего есть вложенные строки, должно быть ключами, а всё, у чего нет, значениями

Именно.

37

Re: AHK: Парсинг дерева

teadrinker пишет:

Можно, но может путаница получиться. Придётся как-то отличать ключ со ссылкой от остальных ключей.

Был вариант каждому объекту(т.е. экземпляру класса) задавать переменную parent(экземпляр класса родителя) и data(тот самый объект в котором содержаться спарсенные данные) и уже с этим как-то пробовать. Но не в этот раз. Да и возможно, это бы ухудшило производительность.

teadrinker пишет:

alekserus258, идите в задницу.

А что, так можно было?

38

Re: AHK: Парсинг дерева

MandarinKa02 пишет:

teadrinker, поправил.

Не работает:

data =
(
МИР
	Казахстан
		Акмолинская область
			Акколь
			Степногорск
			Степняк
			Щучинск
		Актюбинская область
		Алматинская область
			Есик
			Жаркент
			Алматы
	РФ
		Ленинградская область
			Павловск
			Гатчина
)

obj := NEI.Parse(data)
MsgBox, % obj["МИР", "Казахстан", "Алматинская область", 3]

class NEI {
	Parse(input) {

		obj:=[]
		curObj:=obj,objStack:=[],objStack.Push(curObj)

		curTab:=prevLine:=prevTab:=-1
		Loop, Parse, input, `n, `r
		{
			if(!RegExMatch(A_LoopField, "^(?<tab>`t*)(?<line>.*)", _) || !_line || RegExMatch(_line,"^(\s{0,})\/\/")) ;empty line or "//" 
				Continue
			StrReplace(_tab, A_Tab, "", tab)
			if(curTab=-1) {
				curTab:=0,prevLine:=_line
				if(prevTab:=tab) ;line starts with TAB and has't obj-parent
					return -1
				Continue
			} if(tab-curTab<0) { ;prev level
				;msgbox % prevLine "`n" _line "`n" prevTab "`n" tab "`n" curTab
				;curObj.Push(prevLine)
				loop % Abs(tab-prevTab)
					objStack.Pop() ;remove last object
				;curTab:=prevTab
				curObj:=objStack[objStack.MaxIndex()]
			}

			if(tab=prevTab) ;curr level
				curObj.Push(prevLine)
			else if(tab>prevTab) ;new level
				curObj[prevLine]:=[]
				,curObj:=curObj[prevLine]
				,objStack.Push(curObj)
				,curTab:=prevTab+1
			else if(prevTab-tab>1)
				curObj[_line]:=[],curObj:=curObj[_line],objStack.Push(curObj)
			prevLine:=_line,prevTab:=tab
		} curObj.Push(prevLine)
		return obj
	}
	Dump(obj,tab=0) {
		if(!isObject(obj))
			return -1
		out:=""
		for i,v in obj {
			if(isObject(v))
				out.=this._mult_tab(tab) i "`n" this.Dump(v,tab+1)
			out.=this._mult_tab(tab) v (A_Index=obj.MaxIndex()?"":"`n")
		} return out
	}
	_mult_tab(i) {
		out:=""
		loop % i
			out.=A_Tab
		return out
	}
}
Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

39

Re: AHK: Парсинг дерева

MandarinKa02 пишет:

Да и возможно, это бы ухудшило производительность.

А чем плох мой предыдущий вариант, где все строки — объекты? Он и в плане возможного расширения исходного текста хорош.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

40

Re: AHK: Парсинг дерева

teadrinker, отредактировал код в посте 36.
Вам вариант ничем не плох, не подходит под мои нужды.

teadrinker пишет:

возможного расширения исходного текста хорош

Не понял, что вы имеете ввиду?

41 (изменено: teadrinker, 2019-02-25 00:22:25)

Re: AHK: Парсинг дерева

MandarinKa02 пишет:

отредактировал код в посте 36.

Теперь работает. В чём я тут вижу проблему:

https://i.imgur.com/d9xlgoe.png

"Акмолинская область" и "Актюбинская область" по идее родственные объекты, а представлены по-разному, да и порядок нарушен.

MandarinKa02 пишет:

Не понял, что вы имеете ввиду?

Ну, например было

МИР
	Казахстан
		Акмолинская область
			Акколь
			Степногорск
			Степняк
			Щучинск
		Актюбинская область
		Алматинская область
			Есик
			Жаркент
			Алматы
	РФ
		Ленинградская область
			Павловск
			Гатчина

а потом в этот файл будут добавлены новые строки:

МИР
	Казахстан
		Акмолинская область
			Акколь
			Степногорск
			Степняк
			Щучинск
		Актюбинская область
			Актобе
			Хромтау
			Шалкар
			Эмба
		Алматинская область
			Есик
			Жаркент
			Алматы
	РФ
		Ленинградская область
			Павловск
			Гатчина

Так вот в моём предыдущем варианте путь к "Актюбинская область" в старом и новом тексте будет одинаков, а в вашем нет.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

42 (изменено: MandarinKa02, 2019-02-25 00:58:26)

Re: AHK: Парсинг дерева

Опять же, была задача написать парсер, который бы выполнял свою работу без нареканий. А то, что в нашем случае "Актюбинская область" представляется в виде значения - это уже другой вопрос. Давайте приведу еще один пример.
Допустим, список фруктов на складах.

склад 1
	Яблоки,Груши,Ананас
	Настройки
		Размер
		Кол-во контейнеров
	Яблоки
		И тут разновидности
		в 100+ строк
	Груши
		и пошло, поехало
	Ананас
		к сожалению один, привоза не было

	Какой-нибудь объект, содержащий другую необходимую информацию о складе
		что-то внутри
	Размер
		500кв км.
	Кол-во контейнеров
		...
склад2 
	и.т.д

Что у нас здесь? А здесь у нас в объекте сами склады с порядковым номером. И в каждом из них в ячейке 1 будет находится список фруктов.
С вашим бы скриптом, вместо ячейки 1 был бы пустой объект с названием "Яблоки,Груши,Ананас". И как бы я узнал, что здесь фрукт, а что не фрукт?

43

Re: AHK: Парсинг дерева

Ну, я бы сказал, что здесь семантически неправильно составлен список, в одну кучу свалены перечисления и заголовки групп объектов. Правильнее было бы так:

склад 1
	Список фруктов
		Яблоки,Груши,Ананас
	Настройки
		Размер
		Кол-во контейнеров
	Яблоки
		И тут разновидности
		в 100+ строк
	Груши
		и пошло, поехало
	Ананас
		к сожалению один, привоза не было

	Какой-нибудь объект, содержащий другую необходимую информацию о складе
		что-то внутри
	Размер
		500кв км.
	Кол-во контейнеров
		...
склад2 
	и.т.д
Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

44

Re: AHK: Парсинг дерева

Только пример, хоть и не правильный.

45 (изменено: teadrinker, 2019-02-25 01:45:04)

Re: AHK: Парсинг дерева

Ок, тогда так можно, чтобы сохранить структуру:

data =
(
склад 1
	Яблоки,Груши,Ананас
	Настройки
		Размер
		Кол-во контейнеров
	Яблоки
		И тут разновидности
		в 100+ строк
	Груши
		и пошло, поехало
	Ананас
		к сожалению один, привоза не было
склад2 
	Сливы,Киви,Апельсины
	Настройки
		Размер
		Кол-во контейнеров
)

obj := {}
child0 := obj
Loop, parse, data, `n, `r
{
   RegExMatch(A_LoopField, "^(?<indent>`t*)(?<text>.*)", _)
   StrReplace( _indent, A_Tab, "", level)
   prevLevel := level, level += 1
   if (A_Index > 1) {
      if !(level > prev_level)
         child%prev_prevLevel%.Push(prev_text)
      else {
         o := {}, o[prev_text] := child%prev_level% := []
         child%prev_prevLevel%.Push(o)
      }
   }
   prev_level := level, prev_prevLevel := prevLevel, prev_text := _text
}
child%prev_prevLevel%.Push(_text)

https://i.imgur.com/7Xq3SVZ.png
На картинке массивы от 0, в реальности от 1.
Все родственные объекты на одном уровне, вне зависимости от того, строки это, или массивы.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

46

Re: AHK: Парсинг дерева

В общем, наверно идеального варианта нет, к каждому случаю какой-то свой лучше.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

47

Re: AHK: Парсинг дерева

Если заранее известно, какую глубину считать конечной для получения значений залегающих на её уровне, то можно так:


data =
(
База 1
	склад 1
		Яблоки,Груши,Ананас
		Настройки
			Размер
			Кол-во контейнеров
		Яблоки
			И тут разновидности
			в 100+ строк
		Груши
			и пошло, поехало
		Ананас
			к сожалению один, привоза не было
База 2
	склад2 
		Сливы,Киви,Апельсины
		Настройки
			Размер
			Кол-во контейнеров
)

obj := Separator_Razdelyator( StrSplit(data, "`n"), 3 )
MsgBox % obj["База 1", "склад 1", "Яблоки", 2]

ExitApp
Separator_Razdelyator(data, depth := 2) {
	obj := {}
	For i, item in data {
		item := StrReplace(item, "`t", "", level)
		if (!level) {
			obj[item] := {}
			itemLevel_1 := obj[item]
		} else if (level == depth) {
			itemLevel_%depth%.Push( item )
		} else {
			itemLevel_%level%[item] := level + 1 == depth ? [] : {}
			levelPlus := level + 1
			itemLevel_%levelPlus% := itemLevel_%level%[item]
		}
	} Return obj
}

Но если значения ожидать на разных уровнях, то тогда нужно рассчитывать маску.

48

Re: AHK: Парсинг дерева

KusochekDobra пишет:

то можно так

А как тут получить строку "Яблоки,Груши,Ананас"?

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

49

Re: AHK: Парсинг дерева

Можно отдаться полемике и порассуждать на тему того, как это условие подталкивает к тому, что в такую структуру собирал криворукий программист, привнеся в свою БД некоторую произвольность. Что тут в пору переписать логику получения данных, иначе это грозит периодическими нестыковками, или излишними проверками для понимания того, верно ли код обращается за данными, или начальным перебором, чтобы получить "карту" БД и прочее...

Для "Яблоки,Груши,Ананас" семантически подходит быть значением в такой БД, находясь в доступе по какому-нибудь "Наименования", самому быть такой ёмкостью, или не существовать вообще. И хотя, "Хозяин - барин" и мне с таким "Хозяином" "детей не растить", перфекционист во мне протестует и это скорее "крик души".