LINUX.ORG.RU

полиморфный стек

 ,


0

1

В общем есть задача, дано выражение: 5 * [1,2,3] + ( 5 * [3,2,1] - [1,2]) + [1,2,3,4,5,6]

то что в [ ] это вектор, его можно складывать или вычитать с вектором, но не с числом, вектор можно умножать на число, так же есть скобки для приоритетов. если векторы разной размерности, дополняем нулями меньший

на выходе должно получиться [20,20,23,4,5,6]

в общем для реализации изобрел полиморфный стек, который может хранить «оператор», «множитель», «вектор»

но с таким стеком крайне неудобно работать, так как после операции pop не известно, что получим и надо освобождать память, если получили vector. В общем решение неудачное, подскажите, как еще можно решить?

★★★

Паттерн матчингом и рекурсией.

nanoolinux ★★★★
()
Ответ на: комментарий от anonymous

Потому что в общем случае ни число от вектора ни вектор от числа не наследуются.

на каких скрижалях мудрости это записано?

Это один из адекватных способов обобщить понятия вектора и числа. Но даже при этом класс Matrix с привычным для линейной алгебры интерфейсом нельзя сделать для них базовым. В общем, это надо сидеть и продумывать.

диванные теоретики на марше

emulek
()
Ответ на: комментарий от Waterlaz

ну и?

Я хотел услышать о недостатках моего кода.

http://pastebin.com/swaUqe6d

парсит, считает.

вот только вводить новые операции будет тяжело, в твоей грамматики понятие приоритета неявно выводится, pars_add вызывает pars_mul, которая потом будет вызывать pars_pow и так далее. Вообще твой код жёстко приколочен к значкам «+», "-", и т.д.

У меня все вычисления сосредоточены здесь:

	switch(x->operator)
	{
		case '+':
			x->value[0] += y->value[0];
			break;
		case '*':
			x->value[0] *= y->value[0];
			break;
		default:
			assert(0);
	}
и можно добавлять сколько угодно и каких угодно операторов. При этом сама часть работающая со стеком никак не меняется. Также несложно добавить новые типы, у меня всего один тип, но не составит труда добавить сколько угодно и каких угодно типов.

emulek
()
Ответ на: комментарий от emulek

на каких сериалах мудрости это записано?

Диванные теоретеги отвечают - реально фигня получится! 😊

Но если не веришь, просто попробуй сделать иерархию, в которой вектор наследуется от числа - увидишь сам.

anonymous
()
Ответ на: комментарий от anonymous

Но если не веришь, просто попробуй сделать иерархию, в которой вектор наследуется от числа - увидишь сам.

я делал, УМВР.

Конечно, если иерархия будет сложной, то есть смысл сделать тип NIL, который будет иметь одно значение NULL, и являться базой для всех остальных типов.

реально фигня получится!

попробуй пример этой «фигни» придумать, хорошо?

А я тебе расскажу, как эта фигня реализуется.

emulek
()
Ответ на: комментарий от emulek

По тому интерфейсу, что требуется ТС:

Число умножается на число и получается число. Число умножается на вектор любой размерности и получается вектор той же размерности. Число складывается с числом и получается число. Число не складывается с вектором.

Вектор любой размерности умножается на число и получается вектор той же размерности. Вектор умножается на вектор той же размерности и получается число. Вектор не складывается с числом. Вектор складывается с вектором любой размерности и получается вектор наибольшей из них двух размерности.

В общем, эти интерфейсы не имеют ничего общего. Интересно, как ты эту проблему решил?

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

anonymous
()
Ответ на: комментарий от anonymous

В общем, эти интерфейсы не имеют ничего общего. Интересно, как ты эту проблему решил?

с такой постановкой вопроса поможет двойная диспетчеризация.

На пальцах:

1. два класса Number и Vector наследуются от общего предка Base (он может быть абстрактным, что-бы его никто не мог использовать непосредственно. Впрочем, его можно использовать и не как абстрактный, для реализации NULL/NaN/Empty).

2. оба класса имеют реализацию do_operator(const Base& y) (функция-селектор), которая нужна для вычисления this->value ¤ y.value, однако в классе Number этот this является Number, а в другом классе Vector. Таким образом, тип this в методе do_operator() известен.

3. обе эти функции используют (каждая свой) обратный метод, например do_operator_number(const Number& x), вот так:

void Number::do_operator(const Base& y)
{
  y.do_operator_number(*this);
}
При этом тип *this уже известен (Number), но не известен тип второго операнда y. Функция do_operator_number тоже виртуальная, в двух вариантах, в зависимости от типа второго операнда y.

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

Если у нас пять типов, то функций будет 25, и ещё 5 функций-селекторов. Но скорость работы от этого НЕ изменится.

ЗЫЖ в данном конкретном случае для двух типов, я не думаю, что двойная диспетчеризация оправдана. Потому и предложил всего лишь два класса, с наследованием Vector от Number. В реализации все числа будут Vector, и Number::do_operator будет выполняться тогда, и только тогда, когда оба операнда — Number. Т.е. Vector будет обёрткой класса Number.

emulek
()
Последнее исправление: emulek (всего исправлений: 1)
Ответ на: комментарий от anonymous

Базовые классы типа NIL со значением NULL огорчают анонимуса

на практике NULL — полезная и нужная штука. Например она позволяет реализовать division by zero без прерываний.

emulek
()
Ответ на: комментарий от i-rinat

IMHO проще было написать printf(«%d\n», 2+3*4*5);.

конечно проще. Но ТС просил реализацию стека с выделением и освобождением памяти. Сишка для этого конечно плохо подходит, т.ч. получилось конечно коряво. Ну просили утюгом гвоздь забить — забил жеж! ☺

emulek
()
Ответ на: комментарий от Waterlaz

В C не надо кастить возвращаемое malloc'ом значение. Тип void* прозрачно преобразуется в указатель на любой другой тип, кроме указателя на функцию. Это обязательно только в C++, но там не стоит пользоваться malloc'ом. И ещё print_vector() некорректно обрабатывает вектора нулевой длины.

i-rinat ★★★★★
()
Ответ на: комментарий от emulek

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

i-rinat ★★★★★
()
Ответ на: комментарий от i-rinat

Дело в том, что ты просто захардкодил одно выражение.

ну извини, с парсингом строки у ТСа проблемы не было, мне лениво было писать то, что и так понятно. Что, у тебя, у меня или у ТСа с этим есть какие-то проблемы, да?

Да и зачем место загромождать ненужными деталями?

Даже если ошибки есть, их нет смысла искать.

другое выражение вбей, сложно что-ли? Ну если так сложно, я для тебя сейчас парсинг argv[1] напишу, надо?

emulek
()
Ответ на: комментарий от i-rinat

вот именно. Мне выше приведённый код было не сложно написать, и не долго. А то, что там захардкорено — это пусть студент думает, у меня есть задачи поважнее, нежели числа парсить.

Это вообще PoC, на основе которого можно что-то создавать. Калькулятор (bc, dc) у меня уже есть, зачем мне ещё один?

PS: совсем забыл, у меня же есть уже калькулятор, на, лови и играйся: https://github.com/emulek/zz/blob/master/src/getopt.cpp

умеет +-*/^(возведение в степень), над полем Галуа GF(1399).

сам калькулятор называется RSD clk(const char**sp, char cend)

emulek
()
Ответ на: комментарий от emulek

Значит наследование вектора от числа не выражает их общее поведение! Оно является редукцией их наследования от общего предка, которое нужно для реализации двойной диспетчеризации, вообще говоря лишней в данном случае, нужной только лишь для того, чтоб оправдать их наследование одного от другого.

Иногда кажется что Линус был прав😞

anonymous
()
Ответ на: комментарий от anonymous

Значит наследование вектора от числа не выражает их общее поведение!

успокойся, горячий финский парень. На секундочку вспомни о том, что даже числа int НЕ выражают поведение целых чисел. Числа int представляют собой кольцо, но полем НЕ являются. С алгебраической т.з. числа int — никому ненужная хрень.

Пока у тебя исключительно сложение/вычитание, всё нормально (если ты через замыкание кольца не проходишь), но для умножения всё печально, т.к. сложение определено лишь по некоему модулю M, и значит множители не могут быть больше корня из M, на сегодня это всего-лишь жалкое 46340.

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

реализация получается простой, быстрой, и понятной человеку. А твои рассуждения не нужны. Именно по этой причине борщееды в глубокой жопе.

emulek
()
Ответ на: комментарий от anonymous

Конечно же, обычные числа и векторы расширяют поведение NaN.

почему нет? NaN это абстрактное «число», в которое входит «не число», и опционально «числа» разных типов.

Без NaN ты даже школьную арифметику не построишь.

emulek
()
Ответ на: комментарий от Waterlaz

Читер! У тебя же один тип. Можно складывать вектор и число

Хотя, круто, конечно. 214 строк, пример из ОП считает правильно

У меня с зачаточными проверками типов и с более, э-э, честной токенизацией получилось почти в два раза больше. Ну, и еще можно в векторах использовать выражения, типа [1+2, 3*(4+5), 6]

http://pastebin.com/0M0NdCdi

Deleted
()
Ответ на: комментарий от emulek

Наследование только ради наследования? Ну ок😊 Наследование потому что реализация этого требует? Тоже ок😊 Ничего, что интерфейс дерьмо, главное реализация «простая, быстрая, понятная человеку»😊 Ты бы хоть на готовые пакеты для ЛА в С++ глянул.

anonymous
()
Ответ на: комментарий от anonymous

Наследование только ради наследования?

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

Ты бы хоть на готовые пакеты для ЛА в С++ глянул.

я смотрел. Там своя заточка под свои задачи.

emulek
()
Ответ на: комментарий от emulek

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

anonymous
()
Ответ на: комментарий от anonymous

Ну так это по-человечески надо было делать, чтоб при добавлении специфических операций в интерфейс скаляра они не появлялись в интерфейсе вектора.

почему?

А множественная диспетчеризация начнет хорошо доставлять при увеличении кроличества типов и операций.

заметь, у меня ОДНА функция выполняет ВСЕ операторы, коих может быть Over9000.

Ну а для типов так и так необходимо делать матрицу n×n функций, что-бы учесть все варианты. И это совсем не много, т.к. у тебя получается n классов по n+1 методов в каждом классе. И да, расскажи, в каких задачах нужно много(>10) типов?

Математическая строгость тут ни при чем.

тогда давай пример, когда ни один, ни другой мой вариант не подходит. Зачем воду в ступе толочь?

emulek
()
Ответ на: комментарий от IvanR

Конечно же, есть.

При разборе/интерпретации выражения просто использовать доп. стек/список/массив для представления текущего разбираемого вектора, например.

Ну это, конечно, вариант, если ты парсер не через задницу написал (а то ведь «полиморфный стек» намекает на вид остального кода)...

d_Artagnan ★★
()
Ответ на: комментарий от emulek

не встречал. Видимо это твоя фантазия.

Это лишь говорит о скудости образования.

На курсах по какой-нить дискретке или там численному анализу или вообще на квантмехе спецом подобные случаи (разреженные матрицы) разбирают.

d_Artagnan ★★
()
Ответ на: комментарий от pon4ik

Конечно со стеком неудобно работать, ведь тут нужно дерево. Которое AST же.

Стесняюсь спросить: ЗАЧЕМ? Ну, просто AST самим своим названием говорит любому вменяемому человеку, что его как-либо овеществлять нет нужды.

d_Artagnan ★★
()
Ответ на: комментарий от emulek

Ну, мне , например не нравится, что указанная иерархия не только неполна, но и вредна. Аргументирую свой пойнт тем, что «по ООП» в контексте обсуждения стоило бы вспомнить про паттерн «Интерпретатор», но этого до сих пор никем сделано не было. Что лишь говорит об отсутствии практики использования оного у господ-обсуждателей темы.

d_Artagnan ★★
()
Ответ на: комментарий от emulek

Это, конечно, все хорошо, но ответьте пожалуйста для ясности на простой вопрос:

Не нарушает ли LSP иерархия Int <- Vector?

d_Artagnan ★★
()
Ответ на: комментарий от d_Artagnan

Это лишь говорит о скудости образования.

ок, расскажи, где такое встречается.

На курсах по какой-нить дискретке или там численному анализу или вообще на квантмехе спецом подобные случаи (разреженные матрицы) разбирают.

именно такие: «10 мерная матрица с размерностью в миллион и одним числом, надо это оптимизировать», да?

emulek
()
Ответ на: комментарий от d_Artagnan

Конечно со стеком неудобно работать, ведь тут нужно дерево. Которое AST же.

Стесняюсь спросить: ЗАЧЕМ?

без AST ничего не получится. Хотя в явном виде оно нужно не всегда.

emulek
()
Ответ на: комментарий от d_Artagnan

Ну, мне , например не нравится, что указанная иерархия не только неполна, но и вредна. Аргументирую свой пойнт тем, что «по ООП» в контексте обсуждения стоило бы вспомнить про паттерн «Интерпретатор», но этого до сих пор никем сделано не было. Что лишь говорит об отсутствии практики использования оного у господ-обсуждателей темы.

1. не понял, почему моя иерархия(точнее одна из мною предложенных) «вредна»?

2. дык мы с первого поста интерпретатор и обсуждаем. Точнее, ТСу непонятно, как сделать стек в его интерпретаторе. Я ему рассказал, как это делается IRL в контексте C и C++. Смысл об этом упоминать? Если ТСу нужно вычислить 2*2, то что, «необходимо упомянуть паттерн „умножение“», да?

emulek
()
Ответ на: комментарий от d_Artagnan

Не нарушает ли LSP иерархия Int <- Vector?

нет. У ТСа определенны операции сложения/вычитания/умножения/деления для целых и для векторов, значит можно оперировать объектами типа Integer. Но я уже говорил, что тут нужен ещё один, более общий класс, либо класс Integer должен уметь NaN, ибо по условию

Vector - Integer → NaN
Если Integer умеет NaN, то матрица операций получается полной, и интерпретатор может смело использовать Integer, даже если этот Integer на самом деле Vector. Критерий Лискова НЕ нарушен.

И да, NaN так и так придётся сделать, что-бы как-то реализовать division by zero.

emulek
()
Ответ на: комментарий от emulek

Критерий Лискова

Барбара Лисков (англ. Barbara Liskov, урождённая Барбара Джейн Губерман — Barbara Jane Huberman; род. 7 ноября 1939) — американский учёный в области информатики, исследователь проблемы абстракции данных, руководитель группы разработки языка программирования Клу, лауреат премии Тьюринга 2008 года.

Раз уж начал склонять, надо было писать «Принцип Лисковой».

i-rinat ★★★★★
()
Ответ на: комментарий от i-rinat

Раз уж начал склонять, надо было писать «Принцип Лисковой».

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

emulek
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.