Имеет ли Haskell список фрагментов (например, Python)?
Есть ли у Haskell аналогичный синтаксический сахар для Python List Slices?
Например, в Python:
x = ['a','b','c','d']
x[1:3]
дает символы от индекса 1 к индексу 2, включенному (или к индексу 3 исключен):
['b','c']
Я знаю, что у Haskell есть функция (!!)
для конкретных индексов, но есть ли эквивалентная функция "нарезки" или списка?
Ответы
Ответ 1
Нет встроенной функции для нарезки списка, но вы можете легко написать его самостоятельно, используя drop
и take
:
slice from to xs = take (to - from + 1) (drop from xs)
Следует отметить, что поскольку списки Haskell являются односвязными списками (в то время как списки python являются массивами), создание подобных подписок будет O(to)
, а не O(1)
, как в python (предполагая, конечно, что весь список фактически оценивается - в противном случае ловкость Haskell вступает в силу).
Ответ 2
Если вы пытаетесь сопоставить "списки" Python (который не является списком, как отмечают другие), вы можете использовать Haskell vector, который имеет встроенный slice. Кроме того, Vector
может быть оцениваться параллельно, что, я думаю, действительно круто.
Ответ 3
Нет синтаксического сахара. В тех случаях, когда это необходимо, вы можете просто take
и drop
.
take 2 $ drop 1 $ "abcd" -- gives "bc"
Ответ 4
Я не думаю, что он включен, но вы можете написать достаточно просто:
slice start end = take (end - start + 1) . drop start
Конечно, с предварительным условием, что start
и end
являются границами и end >= start
.
Ответ 5
Сеты Python также поддерживают шаг:
>>> range(10)[::2]
[0, 2, 4, 6, 8]
>>> range(10)[2:8:2]
[2, 4, 6]
Итак, вдохновленный Дэном Бертоном отбрасывая каждый N-й элемент, я реализовал срез с шагом. Он работает с бесконечными списками!
takeStep :: Int -> [a] -> [a]
takeStep _ [] = []
takeStep n (x:xs) = x : takeStep n (drop (n-1) xs)
slice :: Int -> Int -> Int -> [a] -> [a]
slice start stop step = takeStep step . take (stop - start) . drop start
Однако Python также поддерживает отрицательный запуск и остановку (он отсчитывается от конца списка) и отрицательный шаг (он меняет список, останавливается, становится стартом и наоборот, и выполняет шаги по списку).
from pprint import pprint # enter all of this into Python interpreter
pprint([range(10)[ 2: 6], # [2, 3, 4, 5]
range(10)[ 6: 2:-1], # [6, 5, 4, 3]
range(10)[ 6: 2:-2], # [6, 4]
range(10)[-8: 6], # [2, 3, 4, 5]
range(10)[ 2:-4], # [2, 3, 4, 5]
range(10)[-8:-4], # [2, 3, 4, 5]
range(10)[ 6:-8:-1], # [6, 5, 4, 3]
range(10)[-4: 2:-1], # [6, 5, 4, 3]
range(10)[-4:-8:-1]]) # [6, 5, 4, 3]]
Как реализовать это в Haskell? Мне нужно отменить список, если шаг отрицательный, начните считать начало и конец с конца списка, если они отрицательные, и имейте в виду, что результирующий список должен содержать элементы с индексами start <= k < stop (с положительным шагом) или start >= k > stop (с отрицательным шагом).
takeStep :: Int -> [a] -> [a]
takeStep _ [] = []
takeStep n (x:xs)
| n >= 0 = x : takeStep n (drop (n-1) xs)
| otherwise = takeStep (-n) (reverse xs)
slice :: Int -> Int -> Int -> [a] -> [a]
slice a e d xs = z . y . x $ xs -- a:start, e:stop, d:step
where a' = if a >= 0 then a else (length xs + a)
e' = if e >= 0 then e else (length xs + e)
x = if d >= 0 then drop a' else drop e'
y = if d >= 0 then take (e'-a') else take (a'-e'+1)
z = takeStep d
test :: IO () -- slice works exactly in both languages
test = forM_ t (putStrLn . show)
where xs = [0..9]
t = [slice 2 6 1 xs, -- [2, 3, 4, 5]
slice 6 2 (-1) xs, -- [6, 5, 4, 3]
slice 6 2 (-2) xs, -- [6, 4]
slice (-8) 6 1 xs, -- [2, 3, 4, 5]
slice 2 (-4) 1 xs, -- [2, 3, 4, 5]
slice (-8)(-4) 1 xs, -- [2, 3, 4, 5]
slice 6 (-8)(-1) xs, -- [6, 5, 4, 3]
slice (-4) 2 (-1) xs, -- [6, 5, 4, 3]
slice (-4)(-8)(-1) xs] -- [6, 5, 4, 3]
Алгоритм по-прежнему работает с бесконечными списками с положительными аргументами, но с отрицательным шагом он возвращает пустой список (теоретически он все еще может возвращать реверсивный подсписок) и с отрицательным запуском или прекращением его входа в бесконечный цикл. Поэтому будьте осторожны с отрицательными аргументами.
Ответ 6
Другой способ сделать это с помощью функции splitAt
из Data.List
- я нахожу, что ее немного легче читать и понимать, чем использовать take
и drop
- но это только личное предпочтение:
import Data.List
slice :: Int -> Int -> [a] -> [a]
slice start stop xs = fst $ splitAt (stop - start) (snd $ splitAt start xs)
Например:
Prelude Data.List> slice 0 2 [1, 2, 3, 4, 5, 6]
[1,2]
Prelude Data.List> slice 0 0 [1, 2, 3, 4, 5, 6]
[]
Prelude Data.List> slice 5 2 [1, 2, 3, 4, 5, 6]
[]
Prelude Data.List> slice 1 4 [1, 2, 3, 4, 5, 6]
[2,3,4]
Prelude Data.List> slice 5 7 [1, 2, 3, 4, 5, 6]
[6]
Prelude Data.List> slice 6 10 [1, 2, 3, 4, 5, 6]
[]
Это должно быть эквивалентно
let slice' start stop xs = take (stop - start) $ drop start xs
который, безусловно, будет более эффективным, но который я считаю немного более запутанным, чем думать об индексах, где список разбивается на переднюю и заднюю половинки.
Ответ 7
У меня была аналогичная проблема и я использовал понимание списка:
-- Where lst is an arbitrary list and indc is a list of indices
[lst!!x|x<-[1..]] -- all of lst
[lst!!x|x<-[1,3..]] -- odd-indexed elements of lst
[lst!!x|x<-indc]
Возможно, не такой аккуратный, как кусочки питона, но он выполняет эту работу. Обратите внимание, что indc может быть в любом порядке, и не обязательно быть смежным.
Как отмечалось, использование списков LINKED в Haskell делает эту функцию O (n), где n - это максимальный доступ к индексу, а не python slicing, который зависит от количества доступных значений.
Отказ от ответственности: я все еще новичок в Haskell, и я приветствую любые исправления.
Ответ 8
Очевидно, что моя версия foldl проиграет против подхода с захватом, но, возможно, кто-то видит способ улучшить его?
slice from to = reverse.snd.foldl build ((from, to + 1), []) where
build [email protected]((_, 0), _) _ = res
build ((0, to), xs) x = ((0, to - 1), x:xs)
build ((from, to), xs) _ = ((from - 1, to - 1), xs)
Ответ 9
sublist start length = take length . snd . splitAt start
slice start end = snd .splitAt start . take end