В любом случае, вам необходимо выполнить одну или две операции для каждого элемента. Необходимым является проверка предиката. Второе добавление 1 зависит от результата предиката.
Итак, если вы не учитываете эффекты кеша и т. Д., Оба случая генерируют равное количество операций.
Картинка, которую вы получаете, состоит в том, что в первом случае есть два отдельных прохода, один из которых собирает элементы и один их подсчитывает. Учитывая, что список больше, чем может обрабатывать кеш, это замедлит обработку. И это на самом деле происходит в строгом языке.
Однако лента Haskell появляется здесь. Список, генерируемый filter
, оценивается (возникает) поэтапно, поскольку функция подсчета length
нуждается в этом. И тогда, поскольку length
использует их только для подсчета и не сохраняет ссылки на вновь созданный список, элементы немедленно освобождаются от сбора мусора. Таким образом, в любой момент времени во время вычисления используется только O(1)
.
Существует небольшая накладная часть для создания результирующего «отфильтрованного» списка в первой версии. Но это обычно незначительно по сравнению с кеш-эффектами, которые возникают, когда списки большие. (Для небольших списков это может иметь значение, это нужно проверить.) Кроме того, он может быть оптимизирован в зависимости от выбранного компилятора и выбранного уровня оптимизации.
Обновление. Вторая версия фактически потребляет память, как указано в одном из комментариев. Для справедливого сравнения вам нужно написать его с помощью аккумулирующего параметра и строгого аннотатора в нужном месте, потому что (я ожидаю) length
уже написан таким образом.
Два обхода, вероятно, будут слиты с одним, когда вы включите оптимизацию в GHC. Но, в принципе, вы правы, хотя второй обход - только над более коротким, отфильтрованным списком, так что часто это не так важно. – leftaroundabout
Вторая версия не является хвостом рекурсивным и, вероятно, приведет к большему количеству используемой памяти, чем первая. Вместо этого вы можете использовать (строгий) аккумулятор, чтобы поддерживать его в постоянной памяти. Или вы можете использовать 'foldl '(\ c a -> if p a then succ c else c) 0'. – chi
вам может понравиться [руководство по ленивой оценке] (https://hackhands.com/guide-lazy-evaluation-haskell/): D – Carsten