П>Это просто другой "мирок", никто Вам не навязывает подобные техники и подходы. Ну не нравится — ну не пользуйтесь, проблем то
Это другой мирок. Это другой, новый, прекрасный мирок. В нем живут более разумные, более совершенные, "другие" люди.
Не пытайтесь их понять, возможно, вам этого просто не дано. Они "другие". Они просто "другие".
Я уже послал Андерсу Хейлсбергу письмо c предложением включить в C# два кейворда для разделения императивного и декларативного кода:
// Кейворд aliens помечает декларативный код для "других" людей.
aliens
{
var isAscending = xs.Replay(ys => ys.Zip(ys.Skip(1), (x, y) => x <= y), 1).All(x => x);
}
// Кейворд bydlokod помечает императивный код.
bydlokod
{
bool isAscending = true;
for (int i = 1; i < numbers.Length; i++)
{
if (numbers[i - 1] > numbers[i])
{
isAscending = false;
break;
}
}
}
Так сразу будет видно, чей код где.
Правда, какой режим будет по умолчанию, я еще не придумал, наверно, все-таки bydlokod. Варваров всегда большинство, и они темной массой окружают цивилизованное государство, угрожая его истребить.
Здравствуйте, gandjustas, Вы писали:
JR>>Рекурсия — гадость сама по себе, тем более, что без неё практически всегда можно обойтись. G>Расскажи это программистам на haskell\ocaml\lisp\f#
Вот потому что рекурсия гадость сама по себе ни один из этих языков не является мэйнстримовским. Выигрыш такие языки могут дать только в тех областях где по условию большинства решаемых задач без рекурсии обойтись невозможно, но таких областей очень мало.
Здравствуйте, Undying, Вы писали:
U>Здравствуйте, Klapaucius, Вы писали:
U>>>А здесь нам просто повезло, что в языке есть стандартная функция разбиения на пары, которую достаточно для решения данной задачи. Если бы задача была бы немного другая, там бы стандартной функции не было.
K>>В большинстве языков можно объявить функцию (метод, процедуру) самому. Представляете?
U>И? Это типа очень хорошо, когда для решения тривиальных задач нужно писать специальные функции?
Это лучше чем писать циклы.
Комбинатор типа pairwise обладает в разы большим потенциалом повторного использования, чем написанный цикл.
0K>Прошу прощения. А не проще ли и не понятее ли написать простенкий цикл с временной переменной? Или, профессиональный программист обязательно должен втулить это уродство куда ни попади, дабы все знали что он слышал про Linq и умеет им пользоваться?
Я далеко не "профессиональный программист", но попытаюсь Вам ответить. Дело в том, что если прочитать первый пост данной темы, то можно понять, что топикстартер хотел получить решение на базе Linq и я думаю Вас не должны сильно волновать причины потребности именно Linq-решения. Если Вас раздражает наличие в языке и BCL подобных штук, то думаю это с удовольствием обсудят на холиварных форумах.
Насчёт "уродства" — императивные циклы с мутабельными переменными и break-переходом куда красивше, дааа
П>>На Rx мона чуть короче: П>>
0K>Что такое Rx? Это какая-то библиотека, или подход к написанию этих ваших Linq?
Ну а почему бы не погуглить?
Это библиотека для работы с реактивными последовательностями (a.k.a. observer pattern), а так же некоторые дополнительные комбинаторы для работы с обычными интерактивными IEnumerable-последовательностями, которых иногда очень не хватает. Очень советую посмотреть, много интересного там
0K>Вам это правда понятее простого цикла с одной единственной временной переменной?
Это просто другой подход для решения поставленной задачи, декларативный. Не нужны никакие циклы и переменные, надо лишь собрать нужный Вам алгоритм из комбинируемых кусочков, представленных в Rx. Да, это не очень понятно выглядит на первый взгляд, но это можно даже прочитать:
Помня одно предыдущее значение, соедини последовательность с самой собой, смещённой с на один элемент вперёд, и проверь, что во всех парах левое значение меньше или равно правому.
Это просто другой "мирок", никто Вам не навязывает подобные техники и подходы. Ну не нравится — ну не пользуйтесь, проблем то
Прошу прощения. А не проще ли и не понятее ли написать простенкий цикл с временной переменной? Или, профессиональный программист обязательно должен втулить это уродство куда ни попади, дабы все знали что он слышал про Linq и умеет им пользоваться?
П>На Rx мона чуть короче: П>
Здравствуйте, Undying, Вы писали:
U>А здесь нам просто повезло, что в языке есть стандартная функция разбиения на пары, которую достаточно для решения данной задачи. Если бы задача была бы немного другая, там бы стандартной функции не было.
В большинстве языков можно объявить функцию (метод, процедуру) самому. Представляете?
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, Dog, Вы писали:
Dog>Есть последовательность чисел {1, 3, 5, 7, 9} Dog>Надо проверить что числа следуют по возрастанию Dog>Можно сделать при помощи linq?
c Rx можно?
public static IEnumerable<int> Enumerate()
{
...
}
public static void Main(string[] args)
{
var ascending = Enumerate()
.Memoize(1)
.Let(e => e.Zip(e.Skip(1), (a, b) => a < b))
.All(x => x);
}
Здравствуйте, Dog, Вы писали:
Dog>Есть последовательность чисел {1, 3, 5, 7, 9} Dog>Надо проверить что числа следуют по возрастанию Dog>Можно сделать при помощи linq?
Это исключительно и только вопрос полноты библиотеки для работы с коллекциями.
При использовании этой библиотеки, например, решение будет выглядеть так:
var isAscending = new[]{1, 3, 5, 7, 9}.Reduce((a, b) => a <= b).And();
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
в вашем случае исходная последовательность будет перебираться дважды
var sequence = new[] { 1, 2, 3, 4, 5, 6 }.Do(Console.Write);
var isAscending = sequence
.Zip(sequence.Skip(1), (x, y) => x <= y)
.All(x => x); // 112233445566
для того, чтобы этого избежать нужно запоминать последний элемент (Memoize(1))
var sequence = new[] { 1, 2, 3, 4, 5, 6 }.Do(Console.Write).Memoize(1);
var isAscending = sequence
.Zip(sequence.Skip(1), (x, y) => x <= y)
.All(x => x); //123456
IB>А вот с Rx можно попробовать сделать так, чтобы перебор останавливался как только случится первое неудовлетворение предикату...
а это обеспечивает стандартный All
var sequence = new[] { 1, 2, 3, 2, 4, 5, 6 }.Do(Console.Write).Memoize(1);
var isAscending = sequence
.Zip(sequence.Skip(1), (x, y) => x <= y)
.All(x => x); //1232
Здравствуйте, Dog, Вы писали:
Dog>Есть последовательность чисел {1, 3, 5, 7, 9} Dog>Надо проверить что числа следуют по возрастанию Dog>Можно сделать при помощи linq?
Неэффективно, но декларативненько:
var isAscending = xs.OrderBy(x => x).SequenceEqual(xs);
На Rx мона чуть короче:
var isAscending = xs.Replay(ys => ys.Zip(ys.Skip(1), (x, y) => x <= y), 1).All(x => x);
Здравствуйте, SpaceConscience, Вы писали:
SC>Позвольте скромное замечание, коллега. Удивительно от апологетов мирка слышать такие некомпетентные заявление об устройстве этого же мирка. Никакого "промежуточного состояния" (фюю, гадость императивная) свертка не требует, она прекрасно выражается рекурсивной функцией.
Рекурсия — гадость сама по себе, тем более, что без неё практически всегда можно обойтись.
Здравствуйте, gandjustas, Вы писали:
JR>>Рекурсия — гадость сама по себе, тем более, что без неё практически всегда можно обойтись.
G>Расскажи это программистам на haskell\ocaml\lisp\f#
Явная рекурсия — гадость. Она — как 'goto' функционального программирования. Практичеси всегда ее можно заменить на комбинацию более высокоуровневых конструкций (map/fold/unfold и т.д.). За более детальным объяснением с доказательствами и примерами можно обратиться к классическому труду почитаемого тут Эрика Мейера Functional Programming with Bananas, Lenses, Envelopes, and Barbed Wire
Здравствуйте, Undying, Вы писали:
U>Здравствуйте, FR, Вы писали:
FR>>Конкретно строку a::b::_ when a > b можно прочитать примерно так "список должен содержать два идущих друг за другом элемента a и b FR>>(и неважный нам хвост _) таких что a > b" если это условие выполняется то элементам a и b сопоставятся реальные значения из списка FR>>и выполнится то что идет после "->" то есть вернется false.
U>Тогда этот шаг более-менее понятен, а вот в этом шаге: U>
U>| h::t -> f t
U>
U>как компилятор догадывается, что у списка надо отсечь первый элемент?
:: — один из типов шаблонов, поддерживаемых OCaml, просто встроенная в компилятор поддержка.
Тут компилятор ничего не "отсекает", он просто проверяет совпадение списка с образцом h::t и в случае успеха (если список не пуст) предоставляет Вам имена h и t для доступа к совпавшим частям (голове и хвосту) списка.
U>Если бы надо было отсечь два первых элемента, то как бы эта строчка выглядела?
Здравствуйте, Пельмешко, Вы писали:
П>Это просто другой подход для решения поставленной задачи, декларативный. Не нужны никакие циклы и переменные, надо лишь собрать нужный Вам алгоритм из комбинируемых кусочков, представленных в Rx. Да, это не очень понятно выглядит на первый взгляд, но это можно даже прочитать:
А зачем себя ограничивать? Не проще ли императивные задачи записывать императивно, а функциональные функционально? Приведенная задача требует хранения промежуточного значения, т.е. является императивной, соответственно ее функциональное решение весьма уродливо. Так в чем смысл тащить это уродство в код?
Здравствуйте, Jolly Roger, Вы писали:
SC>>Позвольте скромное замечание, коллега. Удивительно от апологетов мирка слышать такие некомпетентные заявление об устройстве этого же мирка. Никакого "промежуточного состояния" (фюю, гадость императивная) свертка не требует, она прекрасно выражается рекурсивной функцией.
JR>Рекурсия — гадость сама по себе, тем более, что без неё практически всегда можно обойтись.
С чего бы это? Рекурсия — это большущая красота, которая позволяет делать очень изящные штучки.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, Undying, Вы писали:
FR>>С этим вариантом вариант с рекурсией одинаков по понятности. Но это для языков без паттерн матчинга, на том же
U>В принципе понятность алгоритма понятие вполне объективное, более понятным является тот алгоритм, который лучше удовлетворяет бритве Оккама, т.е. в котором вводится меньше дополнительных сущностей и производится меньше их изменений.
U>В императивном алгоритме дополнительных сущностей вводится две: это предыдущий и текущий элементы (или их индексы). В рекурсивном алгоритме дополнительных сущностей три: во-первых, усеченная коллекция, во-вторых, индексы предыдущего и текущего элемента. Т.е. избавиться от предыдущего и текущего элемента все равно не получилось, но зато пришлось ввести еще лишнюю сущность — усеченную коллекцию, соответственно алгоритм стал сложнее.
В императивном алгоритме в конкретный момент работы алгоритма дополнительные сущности существуют в одном и только в одном экземпляре, в рекурсивном алгоритме в этот же момент имеется множество экземпляров (равное глубине рекурсии) дополнительных сущностей. Удерживать в голове один экземпляр намного проще, чем множество, поэтому нерекурсивные алгоритмы понимаются намного проще рекурсивных.
Здравствуйте, Undying, Вы писали:
U>Здравствуйте, _FRED_, Вы писали:
_FR>>С чего бы это? Рекурсия — это большущая красота, которая позволяет делать очень изящные штучки.
U>Рекурсия это именно что гадость сама по себе. Т.к. в нерекурсивном алгоритме в конкретный момент работы алгоритма имеется только одно состояние, а в рекурсивном алгоритме в этот же момент висит гирлянда состояний, которую удержать в голове намного сложнее.
А зачем держать в голове весь "стек" вызовов рекурсивного алгоритма? Чисто-функциональный алгоритм зависит только от входных параметров, а был-ли он вызван рекурсивно или нет, совершенно неважно.
Здравствуйте, Undying, Вы писали:
U>Здравствуйте, ankf, Вы писали:
A>>В Linq для целей задачи топиккастера есть функция Any и проблема решается достаточно просто
A>>
A>> int p = arr[0];
A>> if (arr.Any(x => p > (p = x) ))
A>> Console.WriteLine("Не упорядочена");
A>>
U>Ты чего? Тут же используется богопротивное изменяемое состояние. Тебя щас анафеме предадут, за такое надругательство над линком.
лучше богопротивное изменямое состояние, чем сортировка или агрегация.
да и ничего богопротивного тут нет, бог создал Linq таким , что он позволяет использовать внешнюю переменную, потому что бог не фанат и идеолог, а практик.
Я программист, я Иван Помидоров, хватить трепатся — наш козырь error.
Здравствуйте, Undying, Вы писали:
U>Это типа очень хорошо, когда для решения тривиальных задач нужно писать специальные функции?
Во-первых, функции и существуют для решения тривиальных задач. И, собственно, являются инструментом для декомпозиции нетривиальных задач на эти самые тривиальные.
Во-вторых, в этом случае, как и в большинстве остальных случаев, нужно написать не специальную функцию, а универсальную, если только она уже не была написана раньше. Количество этих универсальных функций будет сначала расти, если стандартная библиотека некудышная, ну а потом рост замедлится и ими можно будет просто пользоваться, а не изобретать велосипед в каждом цикле. То, что стандартная библиотека бедна — это плохо. То, что язык предоставляет средства для исправления этой проблемы — разумеется, хорошо.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
П>Например, свёртка — фундаментальная операция над списками и ещё много чем — требует как раз промежуточного значения под аккумулятор
Позвольте скромное замечание, коллега. Удивительно от апологетов мирка слышать такие некомпетентные заявление об устройстве этого же мирка. Никакого "промежуточного состояния" (фюю, гадость императивная) свертка не требует, она прекрасно выражается рекурсивной функцией.
U>Если честно, то глядел-глядел, но логику уловить вообще не сумел, т.е. отдельные элементы вроде понятны, но почему они работают как единое целое непонятно совершенно. Как, например, компилятор догадался, что такое a и b?
Боюсь я не сумею в двух словах объяснить что такое паттерн матчинг, это надо самому грокать
Компилятор не догадывается он сверху вниз пытается сопоставить образцы которые ему дали и если получилось присвоить переменным
образца (тем самым a и b то есть это и одновременно объявление переменных) реальные значения.
Конкретно строку a::b::_ when a > b можно прочитать примерно так "список должен содержать два идущих друг за другом элемента a и b
(и неважный нам хвост _) таких что a > b" если это условие выполняется то элементам a и b сопоставятся реальные значения из списка
и выполнится то что идет после "->" то есть вернется false.
Здравствуйте, Undying, Вы писали:
U>Так match позволяет одновременно работать с несколькими списками или нет? Т.е. можно ли написать так: match xs1, xs2 with... ?
Да без проблем:
let rec iter2 f l1 l2 =
match (l1, l2) with
([], []) -> ()
| (a1::l1, a2::l2) -> f a1 a2; iter2 f l1 l2
| (_, _) -> invalid_arg "List.iter2"
Здравствуйте, Undying, Вы писали:
U>Здравствуйте, k.o., Вы писали:
KO>>Ты сам себе противоречишь. Без рекурсии в обычных императивных языках можно обойтись всегда, в том числе и при обходе деревьев.
U>Каким образом можно обойти дерево произвольной глубины без рекурсии?
Я даже не знаю как отвечать на такие вопросы, взять на себя работу компилятора/интерпритатора и самому управлять стеком состояний?
class Tree
{
public Tree Left;
public Tree Right;
}
int RecursiveNodeCount(Tree root)
{
if (root == null)
{
return 0;
}
return RecursiveNodeCount(root.Left) + RecursiveNodeCount(root.Right);
}
int NodeCount(Tree tree)
{
var unvisitedNodes = new Stack<Tree>();
unvisitedNodes.Push(root);
var count = 0;
while (!unvisitedNodes.IsEmpty)
{
var node = unvisitedNodes.Pop();
while (node != null)
{
++count;
node = node.Left;
unvisitedNodes.Push(node.Right);
}
}
return count;
}
Как-то так, компилировать не пробовал и с нерекурсивным алгоритмом мог немного налажать, он, как ни странно, оказался сложнее рекурсивного.
Здравствуйте, Undying, Вы писали:
U>Рекурсия точно также требует промежуточных состояний, только делает это менее явным и более сложным образом (к примеру, в данной задаче вместо одного индекса приходится модифицировать и хранить аж коллекцию). Поэтому использовать рекурсию нужно только там где без этого никак не обойтись (например, при обходе деревьев), во всех остальных случаях рекурсия это резкое усложнение кода.
Тут не модифицируется и не копируется колекция, как уже писал используется срез который в машинном коде сводится к манипуляции указателями, и так как рекурсия хвостовая то она в том же машинном коде приводится к циклу:
pure auto f(int Arr[])
{
if(Arr.length < 2)
return true;
if(Arr[0] > Arr[1])
return false;
return f(Arr[1 .. $]);
}
pure auto f(int Arr[])
{
00402010 sub esp,20h
00402013 mov ecx,dword ptr [esp+28h]
if(Arr.length < 2)
00402017 push ebx
00402018 push esi
00402019 mov esi,dword ptr [esp+2Ch]
0040201D cmp esi,2
00402020 push edi
00402021 jae main@f+21h (402031h)
return true;
00402023 pop edi
00402024 mov eax,1
00402029 pop esi
0040202A pop ebx
0040202B add esp,20h
0040202E ret 8
if(Arr[0] > Arr[1])
00402031 mov edx,ecx
00402033 mov ebx,dword ptr [edx]
00402035 mov eax,esi
00402037 cmp ebx,dword ptr [edx+4]
0040203A jle main@f+37h (402047h)
return false;
0040203C pop edi
0040203D xor eax,eax
0040203F pop esi
00402040 pop ebx
00402041 add esp,20h
00402044 ret 8
return f(Arr[1 .. $]);
00402047 lea edi,[esi-1]
0040204A add edx,4
0040204D mov ecx,edx
0040204F mov dword ptr [esp+0Ch],edi
00402053 mov esi,edi
00402055 mov dword ptr [esp+10h],edx
00402059 cmp dword ptr [esp+0Ch],2
0040205E jb main@f+13h (402023h)
00402060 jmp main@f+21h (402031h)
Здравствуйте, Dog, Вы писали:
Dog>Есть последовательность чисел {1, 3, 5, 7, 9} Dog>Надо проверить что числа следуют по возрастанию Dog>Можно сделать при помощи linq?
static void Main(string[] args)
{
var a = new[] { 1, 2, 3, 4, 5 };
int last = 0;
var asc = a.Select(x => { if (x >= last) { last = x; return true; } else return false; }).All(x=>x);
Console.WriteLine(asc);
}
Здравствуйте, Undying, Вы писали:
U>Здравствуйте, Пельмешко, Вы писали:
П>>Это просто другой подход для решения поставленной задачи, декларативный. Не нужны никакие циклы и переменные, надо лишь собрать нужный Вам алгоритм из комбинируемых кусочков, представленных в Rx. Да, это не очень понятно выглядит на первый взгляд, но это можно даже прочитать:
U>А зачем себя ограничивать? Не проще ли императивные задачи записывать императивно, а функциональные функционально?
Божемой, да прочитайте уже первый пост аккуратно по символам, попросили решение на LINQ!
U>Приведенная задача требует хранения промежуточного значения, т.е. является императивной, соответственно ее функциональное решение весьма уродливо.
OMG, а в функциональном программировании "промежуточных" значений не должно быть что ли?
Например, свёртка — фундаментальная операция над списками и ещё много чем — требует как раз промежуточного значения под аккумулятор
Мне кажется, или Вы полный бред сказали? Свёртка что ли уродлива???
U>Так в чем смысл тащить это уродство в код?
Да никакого смысла, просто дали топикстартеру что он хотел
Здравствуйте, Undying, Вы писали:
U>Здравствуйте, FR, Вы писали:
FR>>С этим вариантом вариант с рекурсией одинаков по понятности. Но это для языков без паттерн матчинга, на том же
U>В принципе понятность алгоритма понятие вполне объективное, более понятным является тот алгоритм, который лучше удовлетворяет бритве Оккама, т.е. в котором вводится меньше дополнительных сущностей и производится меньше их изменений.
U>В императивном алгоритме дополнительных сущностей вводится две: это предыдущий и текущий элементы (или их индексы). В рекурсивном алгоритме дополнительных сущностей три: во-первых, усеченная коллекция, во-вторых, индексы предыдущего и текущего элемента. Т.е. избавиться от предыдущего и текущего элемента все равно не получилось, но зато пришлось ввести еще лишнюю сущность — усеченную коллекцию, соответственно алгоритм стал сложнее.
Естественно, массив — не рекурсивный тип данных, на нем рекурсивные алгоритмы плохо выглядят. На рекурсивных типах рекурсивные алгоритмы оказываются на порядок проще.
Здравствуйте, ankf, Вы писали:
A>лучше богопротивное изменямое состояние, чем сортировка или агрегация. A>да и ничего богопротивного тут нет, бог создал Linq таким , что он позволяет использовать внешнюю переменную, потому что бог не фанат и идеолог, а практик.
Согласен.
Касательно же приведенного алгоритма, если добавить обработку ситуации с пустой коллекцией, то чтобы сохранить лаконичность придется шаманить:
int p = int.MinValue;
if (arr.Any(x => p > (p = x) ))
Console.WriteLine("Не упорядочена");
Здесь это вроде прокатывает, но на более сложных задач это чревато ошибками, соответственно превосходство над старым-добрым foreach'ем теряется.
Здравствуйте, FR, Вы писали:
FR>Этот не работает FR>Так как удаляет последний элемент.
Я так и понял. Вот этим и чревата работа с индексами, что легко ошибиться по невнимательности.
FR>Новый массив не создается, в D массивы ссылочные, создается срез который дает доступ только к нужным элементам.
С точки зрения понятности алгоритма это не важно, это важно только с точки зрения скорости и потребляемой памяти.
Здравствуйте, FR, Вы писали:
FR>На OCaml рекурсивный вариант будет такой:
FR>
FR>let rec f = function
FR> | a::b::_ when a > b -> false
FR> | h::t -> f t
FR> | _ -> true
FR>
Если честно, то глядел-глядел, но логику уловить вообще не сумел, т.е. отдельные элементы вроде понятны, но почему они работают как единое целое непонятно совершенно. Как, например, компилятор догадался, что такое a и b?
FR>
FR>let f l = pairwise l |> for_all (fun (a, b) -> a <= b)
FR>
FR>Вообще деларативное описание чего хотим.
А здесь нам просто повезло, что в языке есть стандартная функция разбиения на пары, которую достаточно для решения данной задачи. Если бы задача была бы немного другая, там бы стандартной функции не было.
Здравствуйте, gandjustas, Вы писали:
А>>Явная рекурсия — гадость. Она — как 'goto' функционального программирования. G>Громкие слова, а толку ноль.
В общем-то по сути правильно, если возможно лучше использовать комбинаторы.
Но даже при том что рекурсия это goto функциональщины, она все-равно проще и лучше чем императивщина
Здравствуйте, Undying, Вы писали:
U>Здравствуйте, k.o., Вы писали:
KO>>С такой логикой, тебе придётся держать в голове всю историю изменений состояния нерекурсивного алгоритма.
U>Зачем? Чтобы убедиться, что на текущем шаге нерекурсивный алгоритм находится в правильном состоянии мне достаточно держать в голове один текущий экземпляр состояния. Чтобы убедиться, что на текущем шаге рекурсивный алгоритм находится в правильном состоянии мне нужно держать в голове весь стек экземпляров состояний.
"Текущий шаг" чисто-функционального рекурсивного алгоритма — это применение функции к конкретному набору параметров, т.е. всё тот же один текущий экземпляр состояния, зачем тебе весь стек экземпляров состояний? И, кстати, если он тебе вдруг всё таки понадобится, тебе его покажет отладчик, в отличие от предыдущих состояний императивного кода.
Здравствуйте, Undying, Вы писали:
U>Здравствуйте, Пельмешко, Вы писали:
U>>>Если бы надо было отсечь два первых элемента, то как бы эта строчка выглядела? П>>
П>>| h1::h2::tl -> f tl
П>>
U>А если нужно одновременно работать с двумя списками? Как компилятор определяет о каком списке идет речь?
Приведённый выше код:
let rec f = function
| a::b::_ when a > b -> false
| h::t -> f t
| _ -> true
Является укороченной записью следующего:
let rec f xs =
matchxswith
| a::b::_ when a > b -> false
| h::t -> f t
| _ -> true
Как видите, список здесь — это лишь параметр, передающийся в выражение сравнения с образцом.
Здравствуйте, gandjustas, Вы писали:
G>Здравствуйте, k.o., Вы писали:
KO>>Здравствуйте, gandjustas, Вы писали:
G>>>Здравствуйте, k.o., Вы писали:
KO>>Как здесь поможет оптимизация хвостовой рекурсии и может-ли GC собрать root, до завершения работы RecursiveNodeCount(root.Left) и вызова RecursiveNodeCount(root.Right)?с
G>Ну покажи эквивалентный код с циклами, где собираться будет.
С какой это радости? Код с циклами я приводил выше, о том что он эквивалентен рекурсивному я никогда не говорил, как раз наоборот. Прочитай внимательней дискуссию с Undying начиная отсюда
, вот краткая выжимка: U> Использовать рекурсию нужно только там где без этого никак не обойтись. Я> Без рекурсии можно обойтись всегда, вот пример. U> Хорошо, использовать рекурсию нужно только там где нет возможности избавиться от хранения стека состояний. Я> В моём примере, у явного управления стеком есть свои преимущества. Ты> покажи эквивалентный код с циклами. Я>
Хотя, надо признать, тут я не подумал, сборку узлов, после незначительного изменения можно получить и для рекурсивного кода.
int RecursiveNodeCount(Tree root)
{
if (root == null)
{
return 0;
}
var right = root.Right;
return 1 + RecursiveNodeCount(root.Left) + RecursiveNodeCount(right);
}
Но разница всё же есть: если в рекурсивном варианте об этом нужно специально позаботиться, то в коде с циклами это результат прямолинейного подхода.
KO>>>>2) в случае слишком глубокой рекурсии мы получим OutOfMemoryException, а не StackOverflowException, при котором даже блоки finally не выполняются. G>>>Покажи код где такое происходит.
KO>>Что происходит? OutOfMemoryException? StackOverflowException? Код настолько тривиален, что, подозреваю, я неправильно тебя понял.
G>Реальный рекурсивный код где происходит OutOfMemoryException, но не происходит StackOverflowException. G>И как помогут циклы в этом случае?
Прошу прощения, не думал, что мои слова будут настолько неправильно поняты. Ещё раз: в тех случаях, в которых использование рекурсии приведёт к StackOverflowException, эмуляция рекурсии через циклы и ручное управление стеком, приведёт, в худшем случае, к OutOfMemoryException.
Есть последовательность чисел {1, 3, 5, 7, 9}
Надо проверить что числа следуют по возрастанию
Можно сделать при помощи linq?
Re: [Linq] проверить последовательность
От:
Аноним
Дата:
30.07.10 16:55
Оценка:
Здравствуйте, Dog, Вы писали:
Dog>Есть последовательность чисел {1, 3, 5, 7, 9} Dog>Надо проверить что числа следуют по возрастанию Dog>Можно сделать при помощи linq?
var s = new[] {1, 3, 3, 7, 9};
var r = s.Aggregate(new {V=int.MinValue, C=0}, (a, val) => a.V < val ? new {V=val, C=a.C+1} : a).C == s.Length;
0K>Прошу прощения. А не проще ли и не понятее ли написать простенкий цикл с временной переменной? Или, профессиональный программист обязательно должен втулить это уродство куда ни попади, дабы все знали что он слышал про Linq и умеет им пользоваться?
П>>На Rx мона чуть короче: П>>
0K>Что такое Rx? Это какая-то библиотека, или подход к написанию этих ваших Linq?
0K>Вам это правда понятее простого цикла с одной единственной временной переменной?
походу вопрос с собеседования или с экзамена, Rx действительно нах нужно
Здравствуйте, desco, Вы писали:
D>в вашем случае исходная последовательность будет перебираться дважды
Не совсем так, не перебираться дважды, а дважды обращаться к одному и тому же элементу. =)
D>для того, чтобы этого избежать нужно запоминать последний элемент (Memoize(1))
Да, эта функция уже в Rx...
D>а это обеспечивает стандартный All
Верно =)
Здравствуйте, IB, Вы писали:
IB>Здравствуйте, desco, Вы писали:
D>>в вашем случае исходная последовательность будет перебираться дважды IB>Не совсем так, не перебираться дважды, а дважды обращаться к одному и тому же элементу. =)
перебирается-перебирается
class Seq : IEnumerable<int>
{
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public IEnumerator<int> GetEnumerator()
{
Console.WriteLine("GetEnumerator");
Console.WriteLine(1);
yield return 1;
Console.WriteLine(2);
yield return 2;
Console.WriteLine(3);
yield return 3;
}
}
class Program
{
static void Main(string[] args)
{
var sequence = new Seq();
// var sequence = new Seq().Memoize(1);var isAscending = sequence
.Zip(sequence.Skip(1), (x, y) => x <= y)
.All(x => x);
}
}
Здравствуйте, desco, Вы писали:
D>перебирается-перебирается
Мммм.. Сформулируем еще точнее — создаются два итератора, которые поочереди обращаются к одному и тому же элементу. =)
Подозреваю, что накладные расходы на Memoize() и дополнительный итератор в среднем одинаковы, при стандартных сценариях. Вот если бы нам пришла блажь за каждым элементом лезть, например, в базу...
Здравствуйте, Dog, Вы писали:
Dog>Есть последовательность чисел {1, 3, 5, 7, 9} Dog>Надо проверить что числа следуют по возрастанию Dog>Можно сделать при помощи linq?
Скажи пожалуйста, с какой целью ты задал вопрос ?
Вроде ж до сих пор ты не был студентом, а вопрос задал студенческий
У меня есть одно предположение, какое то решение у тебя есть и тебе нужны были варианты. Но для чего это надо — не ясно. Собеседование проводишь или сам ходишь по оным ?
Re[5]: [Linq] проверить последовательность
От:
Аноним
Дата:
31.07.10 23:24
Оценка:
Здравствуйте, SpaceConscience, Вы писали:
SC>Я уже послал Андерсу Хейлсбергу письмо c предложением включить в C# два кейворда для разделения императивного и декларативного кода:
SC>
Здравствуйте, SpaceConscience, Вы писали:
SC>Я уже послал Андерсу Хейлсбергу письмо c предложением включить в C# два кейворда для разделения императивного и декларативного кода:
Я, как автор топика, могу тебе ответить лишь твоими же словами.
Мальчик. Объясняю пока жив: главное внимательно читать что написано и не выдумывать лишнего.
I>Скажи пожалуйста, с какой целью ты задал вопрос ? I>Вроде ж до сих пор ты не был студентом, а вопрос задал студенческий I>У меня есть одно предположение, какое то решение у тебя есть и тебе нужны были варианты. Но для чего это надо — не ясно. Собеседование проводишь или сам ходишь по оным ?
Да нет, стало интересно, может я чего не знаю
Вот, оказывается на Rx можно написать. Да и про Zip я не знал.
Здравствуйте, Dog, Вы писали:
Dog>Есть последовательность чисел {1, 3, 5, 7, 9} Dog>Надо проверить что числа следуют по возрастанию Dog>Можно сделать при помощи linq?
Помня одно предыдущее значение, соедини последовательность с самой собой, смещённой с на один элемент вперёд, и проверь, что во всех парах левое значение меньше или равно правому.
И с каких это пор эта заумь стала декларативным описанием задачи "проверить, что числа следуют по возрастанию"?
Тут указаний как делать намного больше, чем указаний что делать. А в декларативном описании все должно быть наоборот.
Здравствуйте, Undying, Вы писали:
U>А зачем себя ограничивать? Не проще ли императивные задачи записывать императивно, а функциональные функционально? Приведенная задача требует хранения промежуточного значения, т.е. является императивной, соответственно ее функциональное решение весьма уродливо. Так в чем смысл тащить это уродство в код?
Эта задача отлично ложится на функциональщину, но не на linq.
И она совсем не требует сохранения промежуточных значений, достаточно доступа к двум первым элементам и рекурсии:
pure auto f(int Arr[])
{
if(Arr.length < 2)
return true;
if(Arr[0] > Arr[1])
return false;
return f(Arr[1 .. $]);
}
K>Это исключительно и только вопрос полноты библиотеки для работы с коллекциями. K>При использовании этой библиотеки, например, решение будет выглядеть так: K>
K>var isAscending = new[]{1, 3, 5, 7, 9}.Reduce((a, b) => a <= b).And();
K>
Здравствуйте, FR, Вы писали:
FR>Эта задача отлично ложится на функциональщину, но не на linq. FR>И она совсем не требует сохранения промежуточных значений, достаточно доступа к двум первым элементам и рекурсии:
FR>
Здравствуйте, Undying, Вы писали:
U>Т.е. ты хочешь сказать, что в функциональном стиле обращение к элементам массива по индексу считается хорошим тоном?
В нормальных ФЯ есть паттерн матчинг, который позволяет без проблем выбрать первые два элемента.
U>Допустим, но тогда не понятно, чем это лучше в сравнении:
U>
U>for (int i = 1; i < arr.Length; ++i)
U>{
U> if (arr[i] <= arr[i-1])
U> return false;
U>}
U>return true;
U>
U>Неужели вариант с рекурсией можно считать более понятным?
С этим вариантом вариант с рекурсией одинаков по понятности. Но это для языков без паттерн матчинга, на том же
Ocaml'е например вариант с рекурсией намного более декларативен чем вариант с циклом.
Здравствуйте, SpaceConscience, Вы писали:
П>>Например, свёртка — фундаментальная операция над списками и ещё много чем — требует как раз промежуточного значения под аккумулятор
SC>Позвольте скромное замечание, коллега. Удивительно от апологетов мирка слышать такие некомпетентные заявление об устройстве этого же мирка. Никакого "промежуточного состояния" (фюю, гадость императивная) свертка не требует, она прекрасно выражается рекурсивной функцией.
Господи, да что же Вы до бедной императивной студоты докопались-то так
Неужели я настолько бред написал, что Вы никак успокоиться не можете уже который день?
Да, свёртка выражается рекурсивной функцией, состояния у неё никакого нету.
Я отвечал на сообщение:
Приведенная задача требует хранения промежуточного значения, т.е. является императивной
так как не согласен с этим утверждением. Я имел ввиду, что за счёт постоянного вычисления нового значения аккамулятора из предыдущего, свёрткой достигается такое же поведение, как если бы было мутабельное "промежуточное" значение. То есть вполне можно выражать задачи, требующие "хранения промежуточного значения" и в функциональном стиле.
Выразился неудачно и некорректно, да. Прошу прощения.
Здравствуйте, Jolly Roger, Вы писали:
JR>Здравствуйте, SpaceConscience, Вы писали:
SC>>Позвольте скромное замечание, коллега. Удивительно от апологетов мирка слышать такие некомпетентные заявление об устройстве этого же мирка. Никакого "промежуточного состояния" (фюю, гадость императивная) свертка не требует, она прекрасно выражается рекурсивной функцией.
JR>Рекурсия — гадость сама по себе, тем более, что без неё практически всегда можно обойтись.
Расскажи это программистам на haskell\ocaml\lisp\f#
Это типа здесь Arr не промежуточное состояние? И чем же оно не промежуточное и не состояние?
Рекурсия точно также требует промежуточных состояний, только делает это менее явным и более сложным образом (к примеру, в данной задаче вместо одного индекса приходится модифицировать и хранить аж коллекцию). Поэтому использовать рекурсию нужно только там где без этого никак не обойтись (например, при обходе деревьев), во всех остальных случаях рекурсия это резкое усложнение кода.
А, кстати, этот код вообще работает? Операция Arr[0 .. $ — 1] что делает? Создает новый массив, в котором нет последнего элемента? А нам разве не первый элемент удалить надо?
Здравствуйте, FR, Вы писали:
FR>С этим вариантом вариант с рекурсией одинаков по понятности. Но это для языков без паттерн матчинга, на том же
В принципе понятность алгоритма понятие вполне объективное, более понятным является тот алгоритм, который лучше удовлетворяет бритве Оккама, т.е. в котором вводится меньше дополнительных сущностей и производится меньше их изменений.
В императивном алгоритме дополнительных сущностей вводится две: это предыдущий и текущий элементы (или их индексы). В рекурсивном алгоритме дополнительных сущностей три: во-первых, усеченная коллекция, во-вторых, индексы предыдущего и текущего элемента. Т.е. избавиться от предыдущего и текущего элемента все равно не получилось, но зато пришлось ввести еще лишнюю сущность — усеченную коллекцию, соответственно алгоритм стал сложнее.
Здравствуйте, Dog, Вы писали:
Dog>Есть последовательность чисел {1, 3, 5, 7, 9} Dog>Надо проверить что числа следуют по возрастанию Dog>Можно сделать при помощи linq?
Странно зачем народ использует OrderBy, Sort, Aggregate, все эти функции перебирают все множество элементов. Даже если условие не сработало на самом первом.
В Linq для целей задачи топиккастера есть функция Any и проблема решается достаточно просто
var arr = new int[] { 1, 6, 7, 8, 9, 7 };
int p = arr[0];
if (arr.Any(x => p > (p = x) ))
Console.WriteLine("Не упорядочена");
else
Console.WriteLine("Упорядочена");
при этом сложность алгоритма наименьшая — ищется первое несовпадение с предикатом и сразу выход из цикла.
Я программист, я Иван Помидоров, хватить трепатся — наш козырь error.
Здравствуйте, _FRED_, Вы писали:
_FR>С чего бы это? Рекурсия — это большущая красота, которая позволяет делать очень изящные штучки.
Рекурсия это именно что гадость сама по себе. Т.к. в нерекурсивном алгоритме в конкретный момент работы алгоритма имеется только одно состояние, а в рекурсивном алгоритме в этот же момент висит гирлянда состояний, которую удержать в голове намного сложнее.
Здравствуйте, Undying, Вы писали:
U>Здравствуйте, SpaceConscience, Вы писали:
SC>>Никакого "промежуточного состояния" (фюю, гадость императивная) свертка не требует, она прекрасно выражается рекурсивной функцией.
U>Это типа здесь Arr не промежуточное состояние? И чем же оно не промежуточное и не состояние?
U>Рекурсия точно также требует промежуточных состояний, только делает это менее явным и более сложным образом (к примеру, в данной задаче вместо одного индекса приходится модифицировать и хранить аж коллекцию). Поэтому использовать рекурсию нужно только там где без этого никак не обойтись (например, при обходе деревьев), во всех остальных случаях рекурсия это резкое усложнение кода.
Ты сам себе противоречишь. Без рекурсии в обычных императивных языках можно обойтись всегда, в том числе и при обходе деревьев. Тем не менее, я вижу, ты не собираешься спорить с тем, что, например, обход деревьев при помощи рекурсии выглядит понятнее и не является усложнением кода (иными словами в ряде случаев "импользовать рекурсию там где без этого можно обойтись" не только можно, но и нужно)? Более того, не потому-ли обход деревьев с помощью рекурсии проще, что такое состояние, как положение в дереве, неявным образом представлено стеком вызовов?
Здравствуйте, k.o., Вы писали:
KO>Ты сам себе противоречишь. Без рекурсии в обычных императивных языках можно обойтись всегда, в том числе и при обходе деревьев.
Каким образом можно обойти дерево произвольной глубины без рекурсии?
Здравствуйте, Undying, Вы писали:
_FR>>С чего бы это? Рекурсия — это большущая красота, которая позволяет делать очень изящные штучки.
U>Рекурсия это именно что гадость сама по себе.
Такого не бывает. Даже "гады морские" не сами по себе гады, а по дремучести и суеверию мужиков.
U>Т.к. в нерекурсивном алгоритме в конкретный момент работы алгоритма имеется только одно состояние,
Одно _изменяемое_ состояние.
U>а в рекурсивном алгоритме в этот же момент висит гирлянда состояний, которую удержать в голове намного сложнее.
Зачем её [всю] в голове держать? Тем более, если это так "сложно"
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, k.o., Вы писали:
KO>А зачем держать в голове весь "стек" вызовов рекурсивного алгоритма? Чисто-функциональный алгоритм зависит только от входных параметров, а был-ли он вызван рекурсивно или нет, совершенно неважно.
Е-мое. Естественно если алгоритм работает правильно, то совершенно по барабану как он там вызывается. А ошибок у вас в коде не бывает, стадии отладки тоже?
Здравствуйте, Undying, Вы писали:
U>Здравствуйте, k.o., Вы писали:
KO>>А зачем держать в голове весь "стек" вызовов рекурсивного алгоритма? Чисто-функциональный алгоритм зависит только от входных параметров, а был-ли он вызван рекурсивно или нет, совершенно неважно.
U>Е-мое. Естественно если алгоритм работает правильно, то совершенно по барабану как он там вызывается. А ошибок у вас в коде не бывает, стадии отладки тоже?
С такой логикой, тебе придётся держать в голове всю историю изменений состояния нерекурсивного алгоритма. И, кстати, для чисто-функционального алгоритма всегда по барабану как он там вызывается, если ошибка есть, она воспроизведётся и без рекурсии.
Здравствуйте, k.o., Вы писали:
KO>Как-то так, компилировать не пробовал и с нерекурсивным алгоритмом мог немного налажать, он, как ни странно, оказался сложнее рекурсивного.
Если ты так любишь строгость определений, то нужно заменить:
U>Поэтому использовать рекурсию нужно только там где без этого никак не обойтись
на
U>Поэтому использовать рекурсию нужно только там где нет возможности избавиться от хранения стека состояний
А вот вводить хранение стека состояний в задачах, которые можно решить без этого, это усложнение кода и гадость сама по себе.
Здравствуйте, k.o., Вы писали:
KO>С такой логикой, тебе придётся держать в голове всю историю изменений состояния нерекурсивного алгоритма.
Зачем? Чтобы убедиться, что на текущем шаге нерекурсивный алгоритм находится в правильном состоянии мне достаточно держать в голове один текущий экземпляр состояния. Чтобы убедиться, что на текущем шаге рекурсивный алгоритм находится в правильном состоянии мне нужно держать в голове весь стек экземпляров состояний.
Здравствуйте, Undying, Вы писали:
U>Здравствуйте, Undying, Вы писали:
FR>>>С этим вариантом вариант с рекурсией одинаков по понятности. Но это для языков без паттерн матчинга, на том же
U>>В принципе понятность алгоритма понятие вполне объективное, более понятным является тот алгоритм, который лучше удовлетворяет бритве Оккама, т.е. в котором вводится меньше дополнительных сущностей и производится меньше их изменений.
U>>В императивном алгоритме дополнительных сущностей вводится две: это предыдущий и текущий элементы (или их индексы). В рекурсивном алгоритме дополнительных сущностей три: во-первых, усеченная коллекция, во-вторых, индексы предыдущего и текущего элемента. Т.е. избавиться от предыдущего и текущего элемента все равно не получилось, но зато пришлось ввести еще лишнюю сущность — усеченную коллекцию, соответственно алгоритм стал сложнее.
U>В императивном алгоритме в конкретный момент работы алгоритма дополнительные сущности существуют в одном и только в одном экземпляре, в рекурсивном алгоритме в этот же момент имеется множество экземпляров (равное глубине рекурсии) дополнительных сущностей.
Бредятина.
1) Есть оптимизация хвостовой рекурсии, есть ленивость.
2) Если объекты неизменяемые, то в большинстве случаев не надо создавать новые экземпляры, только ссылки на них
U>Удерживать в голове один экземпляр намного проще, чем множество, поэтому нерекурсивные алгоритмы понимаются намного проще рекурсивных.
Почти все рекурсивные алгоритмы проще при применении правильных структур данных.
U>А, кстати, этот код вообще работает? Операция Arr[0 .. $ — 1] что делает? Создает новый массив, в котором нет последнего элемента? А нам разве не первый элемент удалить надо?
Этот не работает
Так как удаляет последний элемент. Вот этот
pure auto f(int Arr[])
{
if(Arr.length < 2)
return true;
if(Arr[0] > Arr[1])
return false;
return f(Arr[1 .. $]);
}
работает, я его даже запускал
Новый массив не создается, в D массивы ссылочные, создается срез который дает доступ только к нужным элементам.
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, gandjustas, Вы писали:
JR>>>Рекурсия — гадость сама по себе, тем более, что без неё практически всегда можно обойтись.
G>>Расскажи это программистам на haskell\ocaml\lisp\f#
А>Явная рекурсия — гадость. Она — как 'goto' функционального программирования.
Громкие слова, а толку ноль.
А>Практичеси всегда ее можно заменить на комбинацию более высокоуровневых конструкций (map/fold/unfold и т.д.).
Сделай-ка dropWhile без рекурсии. Или хотя бы эту самую проверку на упорядоченность.
А>За более детальным объяснением с доказательствами и примерами можно обратиться к классическому труду почитаемого тут Эрика Мейера Functional Programming with Bananas, Lenses, Envelopes, and Barbed Wire
Я много умного прочитал, в том числе и эту статью. Но у меня хватает мозга не кидаться громкими заявлениями.
Здравствуйте, gandjustas, Вы писали:
G>2) Если объекты неизменяемые, то в большинстве случаев не надо создавать новые экземпляры, только ссылки на них
И какая разница? Еще одна сущность-то все равно возникает, и с точки зрения сложности алгоритма неважно представлена ли она физически в виде нового экземпляра или обертки над старым.
Здравствуйте, Undying, Вы писали:
U>В принципе понятность алгоритма понятие вполне объективное, более понятным является тот алгоритм, который лучше удовлетворяет бритве Оккама, т.е. в котором вводится меньше дополнительных сущностей и производится меньше их изменений.
При этом все эти оценки субъективны.
U>В императивном алгоритме дополнительных сущностей вводится две: это предыдущий и текущий элементы (или их индексы). В рекурсивном алгоритме дополнительных сущностей три: во-первых, усеченная коллекция, во-вторых, индексы предыдущего и текущего элемента. Т.е. избавиться от предыдущего и текущего элемента все равно не получилось, но зато пришлось ввести еще лишнюю сущность — усеченную коллекцию, соответственно алгоритм стал сложнее.
В рекурсивном алгоритме нет никаких индексов, и никаких усеченных коллекций все это чисто из-за бедности использованного языка, нет в D паттерн матчинга.
На OCaml рекурсивный вариант будет такой:
let rec f = function
| a::b::_ when a > b -> false
| h::t -> f t
| _ -> true
Тут все сущности уровня for или if в сиобразных.
А если на том же OCaml взять комбинаторный вариант:
let f l = pairwise l |> for_all (fun (a, b) -> a <= b)
Здравствуйте, FR, Вы писали:
FR>Тут не модифицируется и не копируется колекция, как уже писал используется срез который в машинном коде сводится к манипуляции указателями, и так как рекурсия хвостовая то она в том же машинном коде приводится к циклу:
Еще раз повторяю, что с точки зрения понятности алгоритма совершенно не важно создается ли физически новый экземпляр или только обертка над старым. Для программиста это все равно выглядит как появление в алгоритме еще одной сущности.
Re[11]: [Linq] проверить последовательность
От:
Аноним
Дата:
07.08.10 11:32
Оценка:
Здравствуйте, gandjustas, Вы писали:
А>>Явная рекурсия — гадость. Она — как 'goto' функционального программирования. G>Громкие слова, а толку ноль.
Слова-то не мои. Все притенции — к Эрику.
G>Сделай-ка dropWhile без рекурсии. Или хотя бы эту самую проверку на упорядоченность.
Ну многие вещи через goto тоже проще и короче сделать Стоило ли городить весь этот огород из Foldable да Traversable если те же деревья можно рекурсивно обойти?
Ну впрочем
myDropWhile p = foldr (++) [] . unfoldr st
where
st [] = Nothing
st (x:xs) = if p x then Just ([],xs) else Just ((x:xs),[])
А упорядочность тут вроде почти все без рекурсий решили.
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, gandjustas, Вы писали:
А>>>Явная рекурсия — гадость. Она — как 'goto' функционального программирования. G>>Громкие слова, а толку ноль. А>Слова-то не мои. Все притенции — к Эрику.
Так не надо их повторять, головой думай.
G>>Сделай-ка dropWhile без рекурсии. Или хотя бы эту самую проверку на упорядоченность. А>Ну многие вещи через goto тоже проще и короче сделать
Какие например? Я знаю только одну — выход их вложенных циклов, она собственно так и делается при необходимости.
А>Стоило ли городить весь этот огород из Foldable да Traversable если те же деревья можно рекурсивно обойти?
Не выкручивайся теперь.
А>Ну впрочем А>
А>myDropWhile p = foldr (++) [] . unfoldr st
А> where
А> st [] = Nothing
А> st (x:xs) = if p x then Just ([],xs) else Just ((x:xs),[])
А>
Жесть
А>А упорядочность тут вроде почти все без рекурсий решили.
Только убого получилось, почти как у тебя.
dropWhile p [] = []
dropWhile p x:xs = if p x then dropWhile p xs else (x:xs)
isOrdered [] = true
isOrdered [_] = true
isOrdered a:b:xs = (a<b) && isOrdered (b:xs)
Здравствуйте, FR, Вы писали:
FR>Конкретно строку a::b::_ when a > b можно прочитать примерно так "список должен содержать два идущих друг за другом элемента a и b FR>(и неважный нам хвост _) таких что a > b" если это условие выполняется то элементам a и b сопоставятся реальные значения из списка FR>и выполнится то что идет после "->" то есть вернется false.
Тогда этот шаг более-менее понятен, а вот в этом шаге:
| h::t -> f t
как компилятор догадывается, что у списка надо отсечь первый элемент? Если бы надо было отсечь два первых элемента, то как бы эта строчка выглядела?
Re[13]: [Linq] проверить последовательность
От:
Аноним
Дата:
07.08.10 12:10
Оценка:
Здравствуйте, gandjustas, Вы писали:
G>Только убого получилось, почти как у тебя. G>
Здравствуйте, Undying, Вы писали:
U>Здравствуйте, k.o., Вы писали:
KO>>Как-то так, компилировать не пробовал и с нерекурсивным алгоритмом мог немного налажать, он, как ни странно, оказался сложнее рекурсивного.
U>Если ты так любишь строгость определений, то нужно заменить:
U>>Поэтому использовать рекурсию нужно только там где без этого никак не обойтись
U>на
U>>Поэтому использовать рекурсию нужно только там где нет возможности избавиться от хранения стека состояний
U>А вот вводить хранение стека состояний в задачах, которые можно решить без этого, это усложнение кода и гадость сама по себе.
На самом деле у нерекурсивного варианта, несмотря на всю его неприглядность, есть пара важных особенностей: 1) узлы могут быть собраны сборщиком мусора сразу после их обработки и 2) в случае слишком глубокой рекурсии мы получим OutOfMemoryException, а не StackOverflowException, при котором даже блоки finally не выполняются. Эквивалентный (при наличии оптимизации хвостовых вызовов) рекурсивный код, использующий CPS, будет выгледть уже не так хорошо:
int RecursiveNodeCount(Tree root, Func<int, int> continuation)
{
if (root == null)
{
return continuation(0);
}
return RecursiveNodeCount(
root.Left,
leftCount =>
RecursiveNodeCount(
root.Right,
rightCount => continuation(1 + leftCount + rightCount)));
}
int RecursiveNodeCount(Tree root)
{
return RecursiveNodeCount(root, x => x);
}
В итоге, придём к тому, что строгого определения когда можно, а когда нельзя использовать рекурсию нет, а нужно, как и всегда, думать головой и использовать тот инструмент, который лучше всего решает задачу.
Здравствуйте, Undying, Вы писали:
U>Тогда этот шаг более-менее понятен, а вот в этом шаге:
U>
U>| h::t -> f t
U>
U>как компилятор догадывается, что у списка надо отсечь первый элемент? Если бы надо было отсечь два первых элемента, то как бы эта строчка выглядела?
Так тут и не надо догадываться, в ФЯ список это конструкция вида <элемент>::<хвост>, например в OCaml список можно задать таким псевдокодом
type 'a list = [] | :: of 'a * 'a list
То есть список или пустой или состоит из элемента и хвоста который сам является списком.
Двоеточие это оператор конструирующий список он принимает слева элемент справа список.
Для двух элементов будет так:
a::b::tail -> ...
Ну и учти что сопоставление идет сверху вниз, и срабатывает первое удачное.
Здравствуйте, SpaceConscience, Вы писали:
SC>Ничесе, какие у вас в мирке разночтения. Я думал, рекурсия — это вообще основа функционального программирования.
Рекурсия это база и является самой низкоуровневой конструкцией, без нее конечно обойтись нельзя, но по возможности
нужно уменьшать ее использование так как комбинаторы выразительней и надежней.
FR>Рекурсия это база и является самой низкоуровневой конструкцией, без нее конечно обойтись нельзя, но по возможности FR>нужно уменьшать ее использование так как комбинаторы выразительней и надежней.
Ну это понятно, что нет смысла лепить низкоуровневую конструкцию, если у тебя есть высокоуровневая. Это вообще не ФП-специфичная вещь, а универсальная.
U>Это типа здесь Arr не промежуточное состояние? И чем же оно не промежуточное и не состояние?
Типа нет, это (неизменяемые) аргументы функции. Промежуточное состояние — это когда ты создаешь переменную и ее изменяешь.
U>Рекурсия точно также требует промежуточных состояний, только делает это менее явным и более сложным образом (к примеру, в данной задаче вместо одного индекса приходится модифицировать и хранить аж коллекцию). Поэтому использовать рекурсию нужно только там где без этого никак не обойтись (например, при обходе деревьев), во всех остальных случаях рекурсия это резкое усложнение кода.
Потрясающий текст. "Делает это менее явным и более сложным образом" по сравнению с чем? По сравнению с комбинаторами? Но ведь комбинаторы, по крайней мере логически, могут быть реализованы только через рекурсию.
Говоря о том, что "рекурсия требует промежуточных состояний", ты, видимо, имеешь в виду физическую реализацию на компьютере. Логически в функциональном программировании нет никаких промежуточных состояний.
Рекурсия — это основная конструкция функционального программирования, обеспечивающая в нем полноту Тьюринга. Как можно говорить, что без нее можно обойтись? О чем речь вообще?
Чета, ребята, я смотрю у вас у всех разное понимание функционального программирования.
SC>Рекурсия — это основная конструкция функционального программирования, обеспечивающая в нем полноту Тьюринга. Как можно говорить, что без нее можно обойтись? О чем речь вообще?
Если брать математический базис ФП — лямбда исчисление, то рекурсия не является основной конструкцией, и определяется через Y — комбинатор.
SC>Чета, ребята, я смотрю у вас у всех разное понимание функционального программирования.
U> int p = int.MinValue;
U> if (arr.Any(x => p > (p = x) ))
U> Console.WriteLine("Не упорядочена");
U>
U>Здесь это вроде прокатывает, но на более сложных задач это чревато ошибками, соответственно превосходство над старым-добрым foreach'ем теряется.
Что вы имеете ввиду?
Здравствуйте, Klapaucius, Вы писали:
U>>А здесь нам просто повезло, что в языке есть стандартная функция разбиения на пары, которую достаточно для решения данной задачи. Если бы задача была бы немного другая, там бы стандартной функции не было.
K>В большинстве языков можно объявить функцию (метод, процедуру) самому. Представляете?
И? Это типа очень хорошо, когда для решения тривиальных задач нужно писать специальные функции?
U>> int p = int.MinValue;
U>> if (arr.Any(x => p > (p = x) ))
U>> Console.WriteLine("Не упорядочена");
U>>
U>>Здесь это вроде прокатывает, но на более сложных задач это чревато ошибками, соответственно превосходство над старым-добрым foreach'ем теряется. Dog>Что вы имеете ввиду?
То что неинициализированное с точки зрения алгоритма p уже получило значение корректное с точки зрения компилятора и рантайма. В некоторых случаях (например, при ошибках в реализации алгоритма) это чревато неочевидными ошибками (т.е. такими на которые ни компилятор не ругается, ни рантайм не падает).
U>> int p = int.MinValue;
U>> if (arr.Any(x => p > (p = x) ))
U>> Console.WriteLine("Не упорядочена");
U>>
U>>Здесь это вроде прокатывает, но на более сложных задач это чревато ошибками, соответственно превосходство над старым-добрым foreach'ем теряется. Dog>Что вы имеете ввиду?
Да и вообще это просто хак, который в другом случае бы не прокатил, например, если бы условие было не строгое больше, а больше-равно, то int.MinValue уже использовать было бы нельзя.
Здравствуйте, k.o., Вы писали:
KO>На самом деле у нерекурсивного варианта, несмотря на всю его неприглядность, есть пара важных особенностей: KO>1) узлы могут быть собраны сборщиком мусора сразу после их обработки
Это в случае использования генератора, а не материализованной коллекции.
А если есть оптимизация хвостовой рекурсии, то совершенно идентично работает.
KO>2) в случае слишком глубокой рекурсии мы получим OutOfMemoryException, а не StackOverflowException, при котором даже блоки finally не выполняются.
Покажи код где такое происходит.
Здравствуйте, Klapaucius, Вы писали:
K>Во-первых, функции и существуют для решения тривиальных задач. И, собственно, являются инструментом для декомпозиции нетривиальных задач на эти самые тривиальные.
Хорошим инструментов является тот, в котором нужно производить декомпозицию тогда, когда это действительно нужно, а инструмент, в котором декомпозицию нужно производить на каждый чих, это плохой инструмент.
Здравствуйте, gandjustas, Вы писали:
G>Здравствуйте, k.o., Вы писали:
KO>>На самом деле у нерекурсивного варианта, несмотря на всю его неприглядность, есть пара важных особенностей: KO>>1) узлы могут быть собраны сборщиком мусора сразу после их обработки G>Это в случае использования генератора, а не материализованной коллекции.
Разумеется, GC не будет собирать объекты если они ещё где-то используются.
G>А если есть оптимизация хвостовой рекурсии, то совершенно идентично работает.
int RecursiveNodeCount(Tree root)
{
if (root == null)
{
return 0;
}
return 1 + RecursiveNodeCount(root.Left) + RecursiveNodeCount(root.Right);
}
Как здесь поможет оптимизация хвостовой рекурсии и может-ли GC собрать root, до завершения работы RecursiveNodeCount(root.Left) и вызова RecursiveNodeCount(root.Right)?
KO>>2) в случае слишком глубокой рекурсии мы получим OutOfMemoryException, а не StackOverflowException, при котором даже блоки finally не выполняются. G>Покажи код где такое происходит.
Что происходит? OutOfMemoryException? StackOverflowException? Код настолько тривиален, что, подозреваю, я неправильно тебя понял.
Здравствуйте, Undying, Вы писали:
U>Хорошим инструментов является тот, в котором нужно производить декомпозицию тогда, когда это действительно нужно
Ну так декомпозиция и производится потому, что она действительно нужна. Чтобы исключить дублирование кода и улучшить его читабельность, например. Так что все в порядке.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, k.o., Вы писали:
KO>Здравствуйте, gandjustas, Вы писали:
G>>Здравствуйте, k.o., Вы писали:
KO>>>На самом деле у нерекурсивного варианта, несмотря на всю его неприглядность, есть пара важных особенностей: KO>>>1) узлы могут быть собраны сборщиком мусора сразу после их обработки G>>Это в случае использования генератора, а не материализованной коллекции.
KO>Разумеется, GC не будет собирать объекты если они ещё где-то используются.
G>>А если есть оптимизация хвостовой рекурсии, то совершенно идентично работает.
KO>
KO>Как здесь поможет оптимизация хвостовой рекурсии и может-ли GC собрать root, до завершения работы RecursiveNodeCount(root.Left) и вызова RecursiveNodeCount(root.Right)?с
Ну покажи эквивалентный код с циклами, где собираться будет.
KO>>>2) в случае слишком глубокой рекурсии мы получим OutOfMemoryException, а не StackOverflowException, при котором даже блоки finally не выполняются. G>>Покажи код где такое происходит.
KO>Что происходит? OutOfMemoryException? StackOverflowException? Код настолько тривиален, что, подозреваю, я неправильно тебя понял.
Реальный рекурсивный код где происходит OutOfMemoryException, но не происходит StackOverflowException.
И как помогут циклы в этом случае?
Здравствуйте, Undying, Вы писали:
U>Здравствуйте, Dog, Вы писали:
U>>>
U>>> int p = int.MinValue;
U>>> if (arr.Any(x => p > (p = x) ))
U>>> Console.WriteLine("Не упорядочена");
U>>>
U>>>Здесь это вроде прокатывает, но на более сложных задач это чревато ошибками, соответственно превосходство над старым-добрым foreach'ем теряется. Dog>>Что вы имеете ввиду? U>Да и вообще это просто хак, который в другом случае бы не прокатил, например, если бы условие было не строгое больше, а больше-равно, то int.MinValue уже использовать было бы нельзя.