Вы описываете недетерминированный take
:
ndtake :: Int -> [a] -> [[a]]
ndtake 0 _ = [[]]
ndtake n [] = []
ndtake n (x:xs) = map (x:) (ndtake (n-1) xs) ++ ndtake n xs
Либо мы берем x
, и имеем n-1
больше, чтобы взять от xs
; или мы не берем x
и не имеем n
дополнительных элементов, которые можно взять из xs
.
Продолжительность:
> ndtake 3 [1..4]
[[1,2,3],[1,2,4],[1,3,4],[2,3,4]]
Обновление: вы хотели эффективности. Если мы уверены, что список ввода конечен, мы можем стремиться остановить как можно скорее:
ndetake n xs = go (length xs) n xs
where
go spare n _ | n > spare = []
go spare n xs | n == spare = [xs]
go spare 0 _ = [[]]
go spare n [] = []
go spare n (x:xs) = map (x:) (go (spare-1) (n-1) xs)
++ go (spare-1) n xs
Попытка:
> length $ ndetake 443 [1..444]
444
Первая версия, кажется, застряла на этом входе, но последняя возвращается немедленно.
Но он измеряет длину всего списка, и в этом нет необходимости, как указывает @ dfeuer в комментариях. Мы можем добиться такого же повышения эффективности, сохранив немного больше лень :
ndzetake :: Int -> [a] -> [[a]]
ndzetake n xs | n > 0 =
go n (length (take n xs) == n) (drop n xs) xs
where
go n b p ~(x:xs)
| n == 0 = [[]]
| not b = []
| null p = [(x:xs)]
| otherwise = map (x:) (go (n-1) b p xs)
++ go n b (tail p) xs
Теперь последний тест также работает мгновенно с этим кодом.
Здесь еще есть возможности для улучшения. Как и с библиотечной функцией subsequences
, пространство поиска можно исследовать еще более лениво. Прямо сейчас у нас есть
> take 9 $ ndzetake 3 [1..]
[[1,2,3],[1,2,4],[1,2,5],[1,2,6],[1,2,7],[1,2,8],[1,2,9],[1,2,10],[1,2,11]]
, но он может найти [2,3,4]
, прежде чем вытолкнуть 5
из списка ввода. Должны ли мы оставить это как упражнение?