Здравствуйте, Павел Кузнецов, Вы писали:
ПК>Я знаю, что эти интерфейсы наследуются от IEnumerable. Вопрос как раз в том, что именно IEnumerable конструкторы контейнеров не принимают, что говорит о недостаточности возможностей IEnumerable/IEnumerator для этих целей.
Они достаточны, но не оптимальны.
>> И это есть good. ПК>Что именно?
Словоблудие, имелось ввиду то что в следующем предложении. >> Что касается получения объекта в энумераторе NET, то в этом случае очень удобно делать ленивое получение объекта. Это есть very good.
ПК>Для многих алгоритмов, более сложных, чем простой перебор, нужно создавать копии итераторов внутри функции, реализующей алгоритм. ПК>Вопрос не о копировании контейнера, а о копировании итераторов/энумераторов.
Да, с этим нехорошо. Если говорить не о копирования энумераторов (поскольку в действительности их можно получить сколько угодно), а о копирования состояния энумератора в данный момент. Эту полезную функцию придется делать ручками. И она сама сильно зависит от реализации энумератора и его тенденции к ленивости. Поэтому, говорить о каком-то стандартном удовольствии здесь не приходится.
ПК>Естественно. Речь шла о возможностях, которые предоставляют те или иные абстракции. Возможности энумераторов .Net и итераторов Java, на мой взгляд, очень сильно ограничены. Фактически, сводятся к перебору по контейнеру. О чем спор-то? Какие конкретно возможности, помимо последовательного перебора элементов, дают энумераторы .Net или итераторы Java? Какие алгоритмы можно/имеет смысл реализовать с их помощью? Даже для простого копирования элементов, как выяснилось, в контейнерах .Net предпочитают на них не рассчитывать
1. Оптимальность, и еще раз оптимальность. Некоторое время назад, мне приходилось дописывать некоторую левую реализацию STL, чтобы вместо пообъектного копирования с помощью был alloc и memcopy памяти. На элементарных типах это очень даже хорошо прокатывает.
2. Энумераторы живут вместе с контейнером. Это будем говорить вынужденное свойство первой Net, поскольку у них нет шаблонов. Таким образом, получение нескольких разных энумераторов работает только через реализацию интерфейсов(к которому можно уже присобачить делегирование). И как не плюйся, полностью реализации, а ля STL не получится. Хотя если привязать энумератор к IList, то на здоровье. То можно его получать с внешней стороны. Но это не есть good. Так как есть пункт 3.
3. Стандартные энумераторы защищают энумератор от изменений в коллекции. То есть, если было изменение в коллекции, то на следующей итерации, получишь ошибку. Поскольку итераторы в stl внешние, то подобное (мне сходу так кажется) получить сложно. Если IEnumerator знает о коллекции с которой общается, то такая проблема решается на ура.
Так что есть где поработать (и судя по сообщению Влада поработали), и есть плюсы недоступные другим.
Здравствуйте, GlebZ, Вы писали:
GZ>2. К Владу. Возможность получения энумератора еще совсем не обозначает что это наиболее оптимальный путь. Для быстрого копирования нужно знать длину копируемых данных, что в случае стандартного IEnumerator невозможно. IEnumerator вполне может работать с потоками, за что им и спасибо. В то же время, в ICollection такая информация предоставляется.
Несомненно. Но это безграмотный путь, так как не универсальный. В прочем, я уже все сказал в ответе Паше
.
ПК>>Вообще же, энумераторы .Net и итераторы Java, фактически, непригодны для сколько-нибудь сложных алгоритмов, т.к. их даже копировать нельзя. GZ>Вообще-то, достаточно пригодны.
Ты не понял. Тут задача не установить истину, а обосновать ущербность. Сам факт ущербности как бы даже не обсуждается.
... << RSDN@Home 1.1.4 beta 4 rev. 351>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Кодт, Вы писали:
К>А кто мешает сделать прокси-коллекцию? Которая умеет делать CopyTo из энумератора?
Этого не нужно делать. Любой ICollection является наследником IEnumerable. Так что от любого класса реализующего ICollection можно его получить. Причем очень многие классы реализующие IEnumerable реализуют и ICollection. Так что универсальнее было бы просто пытаться привести полученный в качестве параметра IEnumerable к ICollection. В ArrayList этого не сделано (по глупости). Но во втором фрэймворке это сделано по полной программе. Именно о нем я и говорил. Я не знаю, что ПК полез в старье после обсуждения PowerCollections которые по определению на втором фрэймворке.
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>Такой подход либо требует контейнера с произвольным доступом, либо приводит к дополнительному проходу по элементам там, где в случае итераторов он не нужен.
Это ежу понятно. Вот только с диапазонами обычно и работают когда контейнер обеспечивает (хотя бы внутненне) эффективный индексный доступ.
"Форвард итераторы" в СТЛ тут ничем не отличаются. А вместо "рандом эксесс" используюется IList или ICollection.
Вообще подходов море. В общем, yield и делегаты/интерфейсы решают любую проблему. Жаль, что их использование кое что стоит. Была бы оптимизациия этого дела вообще вопросов небыло бы. А пока в особо отвественных местах приходится возиться с массивами.
... << RSDN@Home 1.1.4 beta 4 rev. 351>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
>>> В дотнетной библиотеке наличие конструктора принимающего энумератор или другую структуру в порядке вещей. А так как почти все коллекции к ним приводятся, то код получается вообще минимальным.
ПК>>Например, у того же Hashtable конструктора, принимающего IEnumerator или IEnumerable, не обнаружилось. VD>И было бы странно если бы он был. Все же к списку хэш-таблица отношения не имеет.
Очень даже имеет. EntryDictionary никто не отменял. Как и нагрузку по хеширонию перехешированию всей этой гадости.
VD>Что с успехом и делается в новом фрэймворке. Почему до этого не доперли раньше я не знаю. Видимо ступили.
Ну да, а потом объяснять юзверям, что такое боксинг
ПК>>При этом идиология энумераторов .Net от идиологии энумераторов Java очень далека, т.к. в энумераторах Java получение значения совмещено с переходом на следующий/предыдущий элемент, а в .Net эти операции разделены. VD>Для практических нужд это без разницы. Суть итератора от этого не меняется. В Яве нет свойст и дотнетный подход выглядил бы не очень естественно. В дотнете же это очень удобно.
GetCurrentValue(). И вся ленивость готова.
ПК>>Вообще же, энумераторы .Net и итераторы Java, фактически, непригодны для сколько-нибудь сложных алгоритмов, т.к. их даже копировать нельзя. VD>Очень обоснованное и серьезное мнение. Жаль, правда, что ничего с действительностью не имеет. VD>Что касается копирования, то IEnumerable — это фабрика итераторов. Так что копировать ничего и не нужно. Работа идет обычно именно с этим интерфейсом.
Тут ПК прав. Он говорит о копирования состояния итераторов. С yield все становится более печально. Так что голову никто не отменял.
Здравствуйте, c-smile, Вы писали:
CS>Здравствуйте, jazzer, Вы писали:
J>>потому что в STL это делается одним движением мизинца левой ноги: J>>
J>>std::for_each(it1, it2, <всякая фигня>);
J>>
CS>Интересно а что произойдет если it1 и it2 взяты от разных последовательностей?
я думаю, ты и сам знаешь, что произойдет в общем случае. И что с того? Предлагаешь затеять войну на тему "Что безопаснее"?
В STLPort, например, в режиме отладки проверяется выход за границы массива при индексации, так что же тебе ответить на вопрос: "Что произойдет, если индекс массива больше его размера"? В случае STLPort — ничего страшного, тебе укажут на ошибку и ты ее исправишь.
Так вот никто не мешает встроить в итераторы аналогичную защиту. Например, хранить в итераторе указатель (или GUID, предвосхищая плевки в сторону указателей) на контейнер и при сравнении, если сравниваемые итераторы принадлежат разным контейнерам, бросить исключение. Ошибка обнаружится сразу же, при первом обращении к этому коду.
Так что давай на этом закончим обсуждение безопасности и вернемся к содержательной части разговора.
Представь, что у меня есть два предиката P1 и P2 и некий контейнер cont типа MyCont.
И я хочу найти в этом контейнере элемент, удовлетворяющий P1, затем следующий где-то за ним Р2, а затем с выделенной таким образом подпоследовательностью произвести некое действие f.
Все. Это полный код. Если не нашлось элементов, удовлетворяющих Р1 и Р2 — просто ничего не произойдет, так что тут даже никакой дополнительной защиты не нужно.
Согласись, код на STL практически слово в слово повторяет описание на человеческом языке, ничего лишнего писать не пришлось. Причем этот код будет работать, естественно, с разной эффективностью, с любым контейнером, от односвязного списка до массива с произвольным доступом (хотя из-за for_each наименьшая сложность в любом случае будет O(it2-it1) ), более того, этот код можно использовать рекурсивно, например, вместо for_each позвать сам себя, использовав другие предикаты и пришедшие it1 и it2 вместо cont.begin() и cont.end() соответственно.
Опять же, действие над подпоследовательностью может заключаться и не в простом переборе, сразу дающем сложность O(it2-it1), а в каком-либо другом действии, имеющим меньшую сложность и для хорошего контейнера (т.е. контейнера с быстрым поиском) понижающим общую сложность этого кода.
Если .NET или Java может предоставить подобную гибкость, лаконичность и ясность кода, я очень хотел бы это увидеть.
И я очень прошу не поднимать снова тему делегатов и замыканий в том смысле, что на них легче записать предикаты и действие, которое надо произвести, это — тема отдельного разговора. Давайте сконцентрирумеся на итераторах и алгоритмах.
VladD2,
> ПК> Такой подход либо требует контейнера с произвольным доступом, либо приводит к дополнительному проходу по элементам там, где в случае итераторов он не нужен. > > Это ежу понятно. Вот только с диапазонами обычно и работают когда контейнер обеспечивает (хотя бы внутненне) эффективный индексный доступ.
С диапазонами и в (связных) списках работают, в которых произвольного доступа нет по определению. Например, см. splice — пример функции, часто использующейся с диапазонами.
Также у меня была библиотека для работы с деревьями, интерфейс которой был построен на итераторах. Там работа с диапазонами узлов тоже использовалась очень интенсивно, например, при использовании данного дерева в качестве основы для bounding volumes hierarchy — по мере построения такой иерархии очень удобно иметь возможность эффективно перемещать диапазон узлов в новую вершину, их объединяющую.
Другой пример работы с диапазонами, заданными с помощью forward iterators — std::search.
И т.п.
> "Форвард итераторы" в СТЛ тут ничем не отличаются.
Отличаются принципиально: любой итератор задает позицию, в т.ч. forward iterator (*) позволяет задавать позицию в (связном) списке безо всяких индексов. Возможность скопировать итератор позволяет запомнить эту позицию для нужд алгоритма, и, если надо, вернуться к ней позднее за константное время. С энумераторами такой номер не пройдет: как, имея один энумератор, за константное время создать из него другой, с тем же состоянием?
> yield и делегаты/интерфейсы решают любую проблему.
Если yield основан на все тех же некопирующихся энумераторах, которые не умеют обозначать позицию — вряд ли.
Например, как выразить через yield return и энумераторы с делегатами и интерфейсами ту же std::remove_if, с условием повторного использования аналога std::remove_copy_if?
Еще одна задачка. std::map<>::lower_bound(key) возвращает итератор элемента, равного key, или же следующего, перед которым можно вставить key, так что порядок не нарушится. std::map<>::insert() может принимать в качестве подсказки итератор, с которого начинать поиск позиции для вставки нового элемента. В случаях, когда нужно вставить элемент в std::map только в случае, если такого элемента там нет, это позволяет делать только один поиск, а не два (второй, если не передавать подсказку, будет осуществлен в insert):
Map::iterator pos = m.lower_bound(key); // ищем элементif ( pos == m.end() || pos->first != key )
{
// такого элемента еще нет, добавляем
m.insert(pos, Map::value_type(key, value)); // поиска здесь уже не будет
}
else
{
// OK, такой элемент уже есть, делаем, что нужно для этого случая
// например, изменяем его как-нибудь
modify_existing_value(it->second, key, value);
}
(*) В std::list используются bidirectional iterators, но это несущественно. Если это принципиально можно остановиться и на slist, у которого как раз forward iterators.
Posted via RSDN NNTP Server 2.0 beta
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
jazzer,
> В STLPort, например, в режиме отладки проверяется выход за границы массива при индексации, так что же тебе ответить на вопрос: "Что произойдет, если индекс массива больше его размера"? В случае STLPort — ничего страшного, тебе укажут на ошибку и ты ее исправишь. > > Так вот никто не мешает встроить в итераторы аналогичную защиту.
В STLport она уже встроена, _STLP_DEBUG. Было описано by Cay S. Horstmann в 1995 году (Safe STL).
>
> <...> > Если .NET или Java может предоставить подобную гибкость, лаконичность и ясность кода, я очень хотел бы это увидеть.
+1
> И я очень прошу не поднимать снова тему делегатов и замыканий в том смысле, что на них легче записать предикаты и действие, которое надо произвести, это — тема отдельного разговора. Давайте сконцентрирумеся на итераторах и алгоритмах.
+1
Posted via RSDN NNTP Server 2.0 beta
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
jazzer,
> Так вот никто не мешает встроить в итераторы аналогичную защиту. <...> Так что давай на этом закончим обсуждение безопасности и вернемся к содержательной части разговора.
Добавка. c-smile на самом деле прав в своем замечании. Если бы алгоритмы STL умели бы работать с диапазонами, подобных ошибок было бы меньше, чем при работе с итераторами. Все же контроль подобных ошибок во время компиляции приятнее, чем во время исполнения...
Posted via RSDN NNTP Server 2.0 beta
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>С диапазонами и в (связных) списках работают, в которых произвольного доступа нет по определению. Например, см. splice — пример функции, часто использующейся с диапазонами.
В связанных списках диапазоном является конкретный узел и количество.
ПК>Другой пример работы с диапазонами, заданными с помощью forward iterators — std::search.
ПК>Отличаются принципиально: любой итератор задает позицию, в т.ч. forward iterator (*) позволяет задавать позицию в (связном) списке безо всяких индексов. Возможность скопировать итератор позволяет запомнить эту позицию для нужд алгоритма, и, если надо, вернуться к ней позднее за константное время. С энумераторами такой номер не пройдет: как, имея один энумератор, за константное время создать из него другой, с тем же состоянием?
Для юлозенья я предпочел бы индесированный доступ или методы выполняющие работу для тиапазон/возвращающие итератор диапазона. А химия с закладками приведет к сложному и запутанному коду.
>> yield и делегаты/интерфейсы решают любую проблему.
ПК>Если yield основан на все тех же некопирующихся энумераторах, которые не умеют обозначать позицию — вряд ли.
Ты просто смотришь не стой позиции. Просто логика по запоминанию и т.п. выносится из абстрактного итератора в класс имеющий на то право.
В обющем, концептуально другой подход рассчитанный на упрощение и стандартизацию абстракции. При этом всегда можно выбрать между IEnumerable, ICollection, IList в зависимости от требований предъявляемых к коллекции. Так LinkedList принципиально не будет реализовывать IList и ты не сможешь создать квадратичный алгоритм выбором не верной коллекции.
В итоге решаются точно такие же задачи, но при этом результат получается более простым в понимании и исключаются ошибки.
ПК>Например, как выразить через yield return и энумераторы с делегатами и интерфейсами ту же std::remove_if, с условием повторного использования аналога std::remove_copy_if? ПК>
Дык, элементарно . Сделать подобные операции методами конкретных контейнеров. Тогда они будут реализованы наиболее оптимально и нельзя будет, к примеру, применить операцию Remove к массиву не поддерживающему подобных операций.
Нельзя забывать о том, что в Шарпе интерфейсы вроде IList, ICollection, IEnumerable применимы так же и к таким встроенным типам как массивы. И семантика того же удаления будет совсем иным. Для массива — это будет создание его копии в котрой будут отсуствовать некоторые ячейки. А для List<T> логично будет удаление по месту.
Так что для List<T> будет добавлен метод вроде:
public int RemoveAll(Predicate<T> match)
А для обычного массива будет нечто вроде:
public static T[] FindAll<T>(T[] array, Predicate<T> match)
с инвертированным делегатом.
Да, я как бы слашал о теории, что это мовитон, но не разделяю ее. По мне так все должно быть просто, удобно и эффективно.
ПК>Еще одна задачка. std::map<>::lower_bound(key) возвращает итератор элемента, равного key, или же следующего, перед которым можно вставить key, так что порядок не нарушится.
Да уж. Само по себе понятие порядка для не древестного словаря (мапа) у меня вызывает улыбку.
ПК> std::map<>::insert() может принимать в качестве подсказки итератор, с которого начинать поиск позиции для вставки нового элемента. В случаях, когда нужно вставить элемент в std::map только в случае, если такого элемента там нет, это позволяет делать только один поиск, а не два (второй, если не передавать подсказку, будет осуществлен в insert)
Полезная возможность. Но я бы предпочел иметь методы типа TryGetValue и TryAddValue, а не мучиться с итераторами.
ПК>: ПК>
ПК>Map::iterator pos = m.lower_bound(key); // ищем элемент
ПК>if ( pos == m.end() || pos->first != key )
ПК>{
ПК> // такого элемента еще нет, добавляем
ПК> m.insert(pos, Map::value_type(key, value)); // поиска здесь уже не будет
ПК>}
ПК>else
ПК>{
ПК> // OK, такой элемент уже есть, делаем, что нужно для этого случая
ПК> // например, изменяем его как-нибудь
ПК> modify_existing_value(it->second, key, value);
ПК>}
ПК>
ПК> ПК>(*) В std::list используются bidirectional iterators, но это несущественно. Если это принципиально можно остановиться и на slist, у которого как раз forward iterators.
Вот реальный код из редактора кода который я сейчас пишу (вернее не пишу, так как мы тут тепимся ):
public static CombinedStyle Combine(Style style1, Style style2)
{
HashHelper hashHelper = new HashHelper(style1, style2);
CombinedStyle cachedStyle;
if (_styleCache.TryGetValue(hashHelper, out cachedStyle))
return cachedStyle;
CombinedStyle combinedStyle =
new CombinedStyle(hashHelper.HashCode, style1, style2);
_styleCache.Add(hashHelper, combinedStyle);
return combinedStyle;
}
Как видишь все тоже самое, только без лишних телодвижений.
... << RSDN@Home 1.1.4 beta 4 rev. 351>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>С диапазонами и в (связных) списках работают, в которых произвольного доступа нет по определению.
А в таком разе вариант, предложеный Синклером, оптимален.
ПК>Также у меня была библиотека для работы с деревьями, интерфейс которой был построен на итераторах. Там работа с диапазонами узлов тоже использовалась очень интенсивно, например, при использовании данного дерева в качестве основы для bounding volumes hierarchy — по мере построения такой иерархии очень удобно иметь возможность эффективно перемещать диапазон узлов в новую вершину, их объединяющую.
Да, для работы с деревьями действительно нужен более навороченный итератор (а точнее даже не итератор, а пара — итератор-навигатор). Что собственно в дотнете мы и видим на примере XPathNavigator-XPathNodeIterator. В базовых коллекциях дотнета этого нет, потому что там нет деревьев.
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>Добавка. c-smile на самом деле прав в своем замечании. Если бы алгоритмы STL умели бы работать с диапазонами, подобных ошибок было бы меньше, чем при работе с итераторами. Все же контроль подобных ошибок во время компиляции приятнее, чем во время исполнения...
А как же юнит-тесты?
... << RSDN@Home 1.1.4 beta 4 rev. 351>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, jazzer, Вы писали:
J>Представь, что у меня есть два предиката P1 и P2 и некий контейнер cont типа MyCont. J>И я хочу найти в этом контейнере элемент, удовлетворяющий P1, затем следующий где-то за ним Р2, а затем с выделенной таким образом подпоследовательностью произвести некое действие f.
J>На STL это записывается так: J>
J>Все. Это полный код. Если не нашлось элементов, удовлетворяющих Р1 и Р2 — просто ничего не произойдет, так что тут даже никакой дополнительной защиты не нужно.
Здравствуйте, VladD2, Вы писали:
VD>Здравствуйте, jazzer, Вы писали:
J>>Представь, что у меня есть два предиката P1 и P2 и некий контейнер cont типа MyCont. J>>И я хочу найти в этом контейнере элемент, удовлетворяющий P1, затем следующий где-то за ним Р2, а затем с выделенной таким образом подпоследовательностью произвести некое действие f.
J>>На STL это записывается так: J>>
J>>Все. Это полный код. Если не нашлось элементов, удовлетворяющих Р1 и Р2 — просто ничего не произойдет, так что тут даже никакой дополнительной защиты не нужно.
VD>А если it1 окажется больше чем it2?
VladD2 wrote:
> J>Представь, что у меня есть два предиката P1 и P2 и некий контейнер cont типа MyCont. > J>И я хочу найти в этом контейнере элемент, удовлетворяющий P1, затем следующий где-то за ним Р2, а затем с выделенной таким образом подпоследовательностью произвести некое действие f. > > J>На STL это записывается так: > J>
> J>Все. Это полный код. Если не нашлось элементов, удовлетворяющих Р1 и Р2 — просто ничего не произойдет, так что тут даже никакой дополнительной защиты не нужно. > > А если it1 окажется больше чем it2?
Не окажется
> Ну, да на шарпе это будет выглядить примерно так: >
Этот код делает совсем не то что запрашивал jazzer. Вот подумалась возможная интерпретация задания: модифицировать или вывести все элементы последовательности, находящиеся между первыми открывающим и закрывающим тегами (например < и >). Правда открывающий тег будет входить в диапазон, ну да фиг с этим.
А вообще интересно, в твоем коде будет создан промежуточный контейнер или он будет выполнен лениво?
Здравствуйте, VladD2, Вы писали:
VD>Здравствуйте, Павел Кузнецов, Вы писали:
ПК>>С диапазонами и в (связных) списках работают, в которых произвольного доступа нет по определению.
... VD>В связанных списках диапазоном является конкретный узел и количество.
Тогда тебе прийдется хранить в списке его размер и при каждой операции его обновлять. Это хуже по производительности по сравнению с оперированием только итераторами, когда размер списка тебе почти никогда (а если вспомнить, что он в этом случае может вычисляться за линейное время, то вообще никогда) не нужен.
... VD>Ты просто смотришь не стой позиции. Просто логика по запоминанию и т.п. выносится из абстрактного итератора в класс имеющий на то право.
VD>В обющем, концептуально другой подход рассчитанный на упрощение и стандартизацию абстракции. При этом всегда можно выбрать между IEnumerable, ICollection, IList в зависимости от требований предъявляемых к коллекции. Так LinkedList принципиально не будет реализовывать IList и ты не сможешь создать квадратичный алгоритм выбором не верной коллекции.
Возьмем, к примеру, всё тот-же Algorithms.Fill<T>(IList<T>,int,int,T). Что-то его не сильно применишь к LinkedList, который не реализует IList (наверное, из-за опасений создать квадратичный алгоритм ). Градация интерфейсов очень грубая. Либо у тебя есть IEnumerator, который только может ходить вперед, либо IList, который может сразу всё: произвольный доступ и даже изменение коллекции. (ICollection я в расчет не беру. Он для итерирования не подходит.)
VD>В итоге решаются точно такие же задачи, но при этом учитывая скудность стандартных интерфейсов >результат получается более простым в понимании и исключаются ошибки.
(я тут немножко добавил между строк, чтобы показать поспешность твоего заявления).
ПК>>Например, как выразить через yield return и энумераторы с делегатами и интерфейсами ту же std::remove_if, с условием повторного использования аналога std::remove_copy_if?
... VD>Дык, элементарно . Сделать подобные операции методами конкретных контейнеров. Тогда они будут реализованы наиболее оптимально и нельзя будет, к примеру, применить операцию Remove к массиву не поддерживающему подобных операций.
Ты забываешь, что многие алгоритмы могут быть определены над итераторами вполне оптимально. Это дает много приемуществ по сравнению с реализаций этих алгоритмов как часть коллекций. Но, конечно, для этого требуется достаточно мелко градуированная иерархия итераторов. Двух интерфейсов здесь явно недостаточно.
Алгоритм remove, кстати, если определить его соответствующим образом, еще как подходит для массива.
VD>Нельзя забывать о том, что в Шарпе интерфейсы вроде IList, ICollection, IEnumerable применимы так же и к таким встроенным типам как массивы. И семантика того же удаления будет совсем иным. Для массива — это будет создание его копии в котрой будут отсуствовать некоторые ячейки. А для List<T> логично будет удаление по месту.
VD>Так что для List<T> будет добавлен метод вроде: VD>
Несмотря на твои рассуждения, STL'ная комбинация методов remove/erase все равно будет наиболее оптимальной, так как, используя ее, ты не обязан изменять размер коллекции или создавать новый массив, если тебе это не нужно.
VD>Да, я как бы слашал о теории, что это мовитон, но не разделяю ее. По мне так все должно быть просто, удобно и эффективно.
Вот и я про то же. Просто, но не проще. Удобно, но до тех пор, пока удобство не мешает эффективности и не ущемляет мой выбор.
Здравствуйте, VladD2, Вы писали:
VD>Здравствуйте, McSeem2, Вы писали:
MS>>Влад, тебе хотя бы знаком смысл слова "убогий"? Посмотри в толковом словаре, если что.
VD>Ты бы сам залез и глянул, чем выпендриваться. Чтобы облегчить тебе жизнь скажу, что одно из значений "жалкий на вид, изувеченный".
MS>> Реализацию итераторов в STL можно назвать какой угодно, но только не убогой. Наоборот, она слишком наворочена.
VD>Хех. К итераторам "итераторы СТЛ" отношения не имеют. А так конечно. Потому и убогий, или если тебе будет угодно, уродливый.
Напрасно ты так категорично. Задача С++ кода времени разработки STL — это задача написания эффективных в run-time решений. Итераторы STL — пока единственное существующее решение, которому удалось имплементировать абстракцию итератора единообразно начиная от голых указателей (итераторы вектороподобных контейнеров) и заканчивая объектами с произвольной сложностью и даже неопределенной структуры (итераторы ввода-вывода). Задача тех итераторов — это адаптирующий механизм, позволяющий использовать готовые и эффективные имплементации алгоритмов на теоретически бесконечном множестве целевых классов объектов.
Мы прекрасно понимаем, что полиморфные итераторы представляют из себя красоту ООП-подхода (опять же — скорее "внутреннюю" красоту, ибо пользоваться готовыми STL-итераторами не сложнее), так же как понимаем, во что обходится динамическое создание объекта и вызов его полиморфных методов в итераторах Java/C#. Платить подобную цену имеет смысл только тогда, когда других путей нет (не ты ли неоднократно советовал использовать for вместо foreach по-возможности )
Для меня "жалким на вид" смотрится замах на рупь а удар на копейку (типа перебрать 5 элементов в контейнере и для этого динамически создать объект-итератор у которого 11 раз вызвать полиморфные методы...).
По крайней мере в STL для меня задача "to be or not to be" не стоит, никакую дополнительную цену за использование итератора мы не платим.
А паттерны — вещь хорошая, разумеется, но им стоило бы быть аккуратнее в именовании тех вещей, в которых не они были первыми. Итератор из GoF они могли бы назвать и подругому, дабы избежать обсуждаемого конфликта одноименных понятий.
Опять же — стоит ли подходить к паттернам GoF так буквально? Часто ли мы применяем паттерны в голом виде? В том же дотнет, берем иерархию и взаимоотношение классов, отвечающих за сервисы WinNT и видим целую гроздь этих паттернов в одном месте, что неудивительно.
Точно так же шаблон "итератор" в GoF (так же как и остальные шаблоны) — это выхолощенное понятие. Задача описания ООП паттерна проктирования — дать самую голую суть ООП-трюка для решения "атомарной" задачи. Для паттерна они поставили задачу "перебрать" элементы коллекции, не зная внутренней структуры самой коллекции. Все! Для описания именно патерна большего и не надо. Но это не значит, что в реальном применении у нас будет стоять именно столь узкая задача. У итераторов STL стояли задачи: доступ, ввод и вывод. И им удалось эти операции сделать весьма единообразными, а значит применимыми в произвольных комбинациях в тех же алгоритмах.
В общем с ярлыками можно было бы и поаккуратнее...