Удалить элементы по индексу в haskell

Я новичок в Haskell, и я ищу некоторые стандартные функции для работы со списками индексами.

моя конкретная проблема заключается в том, что я хочу удалить 3 элемента через каждые 5. Если его недостаточно ясно, вот иллюстрация:

OOOOOXXXOOOOOXXX...

Я знаю, как написать огромную функцию со многими параметрами, но есть ли умный способ сделать это?

8 ответов


два совершенно разных подхода

  1. можно использовать List.splitAt вместе с drop:

    import Data.List (splitAt)
    f :: [a] -> [a]
    f [] = []
    f xs = let (h, t) = splitAt 5 xs in h ++ f (drop 3 t)
    

    теперь f [1..12] доходность [1,2,3,4,5,9,10,11,12]. Обратите внимание, что эта функция может быть выражена более изящно, используя uncurry и Control.Arrow.second:

    import Data.List (splitAt)
    import Control.Arrow (second)
    f :: [a] -> [a]
    f [] = []
    f xs = uncurry (++) $ second (f . drop 3) $ splitAt 5 xs
    

    так как мы используем Control.Arrow в любом случае, мы можем выбрать, чтобы капля splitAt и вместо этого позвоните в помощь Control.Arrow.(&&&) в сочетании с take:

    import Control.Arrow ((&&&))
    f :: [a] -> [a]
    f [] = []
    f xs = uncurry (++) $ (take 5 &&& (f . drop 8)) xs
    

    но теперь ясно, что еще более коротким решением является следующее:

    f :: [a] -> [a] 
    f [] = []
    f xs = take 5 xs ++ (f . drop 8) xs
    

    As Крис Лутц Примечания, это решение может быть обобщено следующим образом:

    nofm :: Int -> Int -> [a] -> [a]
    nofm _ _ [] = []
    nofm n m xs = take n xs ++ (nofm n m . drop m) xs
    

    теперь nofm 5 8 дает требуемую функцию. обратите внимание, что решение с splitAt может быть еще более эффективным!

  2. применить некоторые математики, используя map, snd, filter, mod и zip:

    f :: [a] -> [a]
    f = map snd . filter (\(i, _) -> i `mod` 8 < (5 :: Int)) . zip [0..]
    

    идея здесь в том, что мы соединяем каждый элемент в списке с его индексом, натуральным числом я. Затем мы удаляем те элементы, для которых i % 8 > 4. Общая версия этого решения:

    nofm :: Int -> Int -> [a] -> [a]
    nofm n m = map snd . filter (\(i, _) -> i `mod` m < n) . zip [0..]
    

поскольку никто не делал версию с "unfoldr", вот мой take:

drop3after5 lst = concat $ unfoldr chunk lst
  where
    chunk [] = Nothing
    chunk lst = Just (take 5 lst, drop (5+3) lst)

Кажется, самый короткий до сих пор


Вы можете легко подсчитать свои элементы:

strip' (x:xs) n | n == 7 = strip' xs 0
                | n >= 5 = strip' xs (n+1)
                | n < 5 = x : strip' xs (n+1)
strip l = strip' l 0

хотя открытое кодирование выглядит короче:

strip (a:b:c:d:e:_:_:_:xs) = a:b:c:d:e:strip xs
strip (a:b:c:d:e:xs) = a:b:c:d:e:[]
strip xs = xs

вот мое мнение:

deleteAt idx xs = lft ++ rgt
  where (lft, (_:rgt)) = splitAt idx xs

на take и drop функции могут помочь вам здесь.

drop, take :: Int -> [a] -> [a]

из них мы можем построить функцию, чтобы сделать один шаг.

takeNdropM :: Int -> Int -> [a] -> ([a], [a])
takeNdropM n m list = (take n list, drop (n+m) list)

и затем мы можем использовать это, чтобы уменьшить наши проблемы

takeEveryNafterEveryM :: Int -> Int -> [a] -> [a]
takeEveryNafterEveryM n m [] = []
takeEveryNafterEveryM n m list = taken ++ takeEveryNafterEveryM n m rest
    where
        (taken, rest) = takeNdropM n m list

*Main> takeEveryNafterEveryM 5 3 [1..20]
[1,2,3,4,5,9,10,11,12,13,17,18,19,20]

поскольку это не примитивная форма рекурсии, сложнее выразить это как простую складку.

таким образом, новая функция складывания может быть определена в соответствии с вашими потребностями

splitReduce :: ([a] -> ([a], [a])) -> [a] -> [a]
splitReduce f []   = []
splitReduce f list = left ++ splitReduce f right
    where
        (left, right) = f list

тогда определение takeEveryNafterEveryM просто

takeEveryNafterEveryM2 n m = splitReduce (takeNdropM 5 3)

Это мое решение. Это очень похоже на @barkmadley это, используя только take и drop, но с меньшим беспорядком на мой взгляд:

takedrop :: Int -> Int -> [a] -> [a]
takedrop _ _ [] = []
takedrop n m l  = take n l ++ takedrop n m (drop (n + m) l)

не уверен, что он выиграет какие-либо награды за скорость или ум, но я думаю, что это довольно ясно и лаконично, и это, безусловно, работает:

*Main> takedrop 5 3 [1..20]
[1,2,3,4,5,9,10,11,12,13,17,18,19,20]
*Main> 

myRemove = map snd . filter fst . zip (cycle $ (replicate 5 True) ++ (replicate 3 False))

вот мое решение:

remElements step num=rem' step num
    where rem' _ _ []=[]
          rem' s n (x:xs)
              |s>0 = x:rem' (s-1) num xs
              |n==0 = x:rem' (step-1) num xs
              |otherwise= rem' 0 (n-1) xs

пример:

*Main> remElements 5 3 [1..20]
[1,2,3,4,5,9,10,11,12,13,17,18,19,20]