Здравствуйте, <Аноним>, Вы писали:
А>Не мог вечером зайти в форум, какая-то ошибка, связанная с Dictionary Key, поэтому вечером писал по-памяти, что прочитал с утра, так что требования не соблюдены точно, но достаточно изоморфны.
Ладно, не важно. Главное, что ты теперь сможешь оценить альтернативное решение.
А>150 строк, пока в c# не разочаровался.
Ну, что же... неплохой результат. А сколько тебе потребовалось времени на изменения?
Ну, а теперь решение на вариантах и паттерн-матчинге:
using System;
using System.Console;
using Nemerle.Utility;
using Expr;
public variant Expr
{
| Literal { value : double; }
| Call { name : string; parms : list[Expr]; }
| Plus { expr1 : Expr; expr2 : Expr; }
| Minus { expr1 : Expr; expr2 : Expr; }
| Mul { expr1 : Expr; expr2 : Expr; }
| Div { expr1 : Expr; expr2 : Expr; }
public override ToString() : string
{
match (this)
{
| Literal(value) => value.ToString()
| Call(name, parms) => $"$name(..$parms)"
| Plus(e1, e2) with op = "+" | Minus(e1, e2) with op = "-"
| Mul (e1, e2) with op = "*" | Div (e1, e2) with op = "/"
=> $"$e1 $op $e2"
}
}
public Eval() : double
{
match (this)
{
| Literal(value) => value
| Plus (e1, e2) => e1.Eval() + e2.Eval()
| Minus(e1, e2) => e1.Eval() - e2.Eval()
| Mul (e1, e2) => e1.Eval() * e2.Eval()
| Div (e1, e2) => e1.Eval() / e2.Eval()
| Call("max", [arg1, arg2]) => Math.Max(arg1.Eval(), arg2.Eval())
| Call("min", [arg1, arg2]) => Math.Min(arg1.Eval(), arg2.Eval())
| Call("foo", [arg1]) => arg1.Eval() // добавили поддержку фунции foo() с одним параметром
| Call(_, _) => throw InvalidOperationException(this.ToString());
}
}
public Convert(transform : Expr -> Expr) : Expr // добавили поддержку трансформации выражений
{
def res = transform(this);
match (res)
{
| Literal => res
| Plus (e1, e2) => Plus (e1.Convert(transform), e2.Convert(transform))
| Minus(e1, e2) => Minus(e1.Convert(transform), e2.Convert(transform))
| Mul (e1, e2) => Mul (e1.Convert(transform), e2.Convert(transform))
| Div (e1, e2) => Div (e1.Convert(transform), e2.Convert(transform))
| Call(name, parms) => Call(name, parms.Map(_.Convert(transform)))
}
}
}
module Program
{
Main() : void
{
def expr = Mul(Plus(Literal(1.23), Call("max", [Literal(1), Literal(2)])), Literal(1));
WriteLine("До трансформации");
WriteLine($"Expression '$expr' = $(expr.Eval())");
def expr = expr.Convert(fun(e : Expr) { | Mul(x, Literal(1.0)) => x | _ => e });
WriteLine("После трансформации");
WriteLine($"Expression '$expr' = $(expr.Eval())");
WriteLine("..."); _ = ReadLine();
}
}
Итого 70 строк. И вряд ли кто-то будет спорить, что они намного лучше читаются.
Причем, заметь, я не менял весь код а добавил всего два фрагмента. Фнукцию Convert:
public Convert(transform : Expr -> Expr) : Expr // добавили поддержку трансформации выражений
{
def res = transform(this);
match (res)
{
| Literal => res
| Plus (e1, e2) => Plus (e1.Convert(transform), e2.Convert(transform))
| Minus(e1, e2) => Minus(e1.Convert(transform), e2.Convert(transform))
| Mul (e1, e2) => Mul (e1.Convert(transform), e2.Convert(transform))
| Div (e1, e2) => Div (e1.Convert(transform), e2.Convert(transform))
| Call(name, parms) => Call(name, parms.Map(_.Convert(transform)))
}
}
и одну строчку в функцию Eval() чтобы добавить обработку функции foo().
Теперь давай проанализируем что получилось.
К каждому классу иерархии был добавлен соврешенно метод RemoveOneParameterMin(). Он увеличил код и сделал его плохо читаемым. К тому же RemoveOneParameterMin не универсален, а в исходном задании было сказано о том, что требуется универсальное решение, а не для конкретного случая.
Не выполнено по сути и задие с добавлением фунций. Ведь у тебя количество параметров не контролируется.
Теперь можно продолжить наши упраженния. Сколько тебе потребуется времени и сил чтобы добавить еще две оптимизирующие трансформации — заменять выражение max(константа, константа) на значение большей из констант. И соответственно min(константа, константа), меньшей? Мне — пол минуты. Я просто добавлю пару образцов и все. Вот измененный код метода Main() делающий эти оптимизации:
module Program
{
Main() : void
{
def expr = Mul(Plus(Literal(1.23), Call("max", [Literal(1), Literal(2)])), Literal(1));
WriteLine("До трансформации");
WriteLine($"Expression '$expr' = $(expr.Eval())");
def expr = expr.Convert(fun(e : Expr) {
| Mul(x, Literal(1.0)) => x
| Call("max", [Literal(x), Literal(y)]) => Literal(Math.Max(x, y))
| Call("mix", [Literal(x), Literal(y)]) => Literal(Math.Min(x, y))
| _ => e });
WriteLine("После трансформации");
WriteLine($"Expression '$expr' = $(expr.Eval())");
WriteLine("..."); _ = ReadLine();
}
}
Консольный вывод:
До трансформации
Expression '1.23 + max(1, 2) * 1' = 3.23
После трансформации
Expression '1.23 + 2' = 3.23
...
Заметь, по сути мне нужно было добавить две строки. Тебе же прийдется еще раз менять дизайн всего приложения.
С Посетителем код у тебя может и был бы несколько больше, но все же не пришлось бы каждый раз курочить все классы ради подобных вещей.
Ну, и естественно, что без паттерн-матчинга как бы ты не извивался, но твой код все равно будет больше и непонятнее.
И знаешь почему? Потому ты говоришь компилятору "как надо делать", а я "что надо делать".
... << RSDN@Home 1.2.0 alpha rev. 637>>