Haskellには Python List Slices と同様の構文糖がありますか?
たとえばPythonでは:
x = ['a','b','c','d']
x[1:3]
インデックス1からインデックス2までの文字を含めます(またはインデックス3を除外します)。
['b','c']
Haskellが(!!)
特定のインデックスの関数ですが、同等の「スライス」またはリスト範囲関数はありますか?
リストをスライスする組み込み関数はありませんが、drop
とtake
を使用して自分で簡単に作成できます。
slice :: Int -> Int -> [a] -> [a]
slice from to xs = take (to - from + 1) (drop from xs)
Haskellのリストは単一リンクリストであるため(pythonリストは配列です)、そのようなサブリストを作成すると、O(to)
ではなくO(to - from)
になることに注意してください。 pythonのように(もちろん、リスト全体が実際に評価されると仮定すると、そうでなければHaskellの遅延が有効になります)。
構文糖なし。必要な場合は、take
とdrop
を使用できます。
take 2 $ drop 1 $ "abcd" -- gives "bc"
含まれているとは思いませんが、かなり簡単に記述できます。
slice start end = take (end - start + 1) . drop start
もちろん、start
とend
がインバウンドであり、end >= start
。
Pythonスライスもステップをサポートしています:
>>> range(10)[::2]
[0, 2, 4, 6, 8]
>>> range(10)[2:8:2]
[2, 4, 6]
Dan Burtonの 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]
このアルゴリズムは正の引数が与えられた無限リストでも機能しますが、負のステップでは空のリストを返し(理論的には逆のサブリストを返す可能性があります)、負の開始または停止では無限ループに入ります。したがって、負の引数には注意してください。
同様の問題があり、リスト内包表記を使用しました:
-- 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]
おそらく、Pythonのスライスほど整然とはしていませんが、それでうまくいきます。 indcは任意の順序でかまいませんが、連続している必要はありません。
前述のように、Haskellがリンクリストを使用すると、この関数は次のようになりますO(n)ここで、nは最大インデックスに依存するpythonのスライスではなく、アクセス値の数アクセスされます。
免責事項:私はまだHaskellに不慣れであり、訂正があれば歓迎します。
HaskellでPython(mからnまで)の範囲)をエミュレートする場合、ドロップとテイクの組み合わせを使用します。
Pythonの場合:
print("Hello, World"[2:9]) # prints: "llo, Wo"
Haskellの場合:
print (drop 2 $ take 9 "Hello, World!") -- prints: "llo, Wo"
-- This is the same:
print (drop 2 (take 9 "Hello, World!")) -- prints: "llo, Wo"
もちろん、これを関数にラップして、Pythonのように動作させることもできます。たとえば、!!!演算子を次のように定義したとします。
(!!!) array (m, n) = drop m $ take n array
その後、次のようにスライスすることができます:
"Hello, World!" !!! (2, 9) -- evaluates to "llo, Wo"
次のような別の関数で使用します。
print $ "Hello, World!" !!! (2, 9) -- prints: "llo, Wo"
ジョンW.
これを行う別の方法は、Data.List
の関数splitAt
を使用することです-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
これは確かにより効率的ですが、リストが前半分と後ろ半分に分割されているインデックスについて考えるよりも少し混乱します。
既存のData.Vector.slice
とData.Vector.fromList
およびData.Vector.toList
( https://stackoverflow.com/a/8530351/9443841 を参照)
import Data.Vector ( fromList, slice, toList )
import Data.Function ( (&) )
vSlice :: Int -> Int -> [a] -> [a]
vSlice start len xs =
xs
& fromList
& slice start len
& toList
私は、Pythonのリストスライスのように、負の数でも機能するこのコードを記述しましたが、リストのスライスとは無関係であるリストの逆転を除きます。
slice :: Int -> Int -> [a] -> [a]
slice 0 x arr
| x < 0 = slice 0 ((length arr)+(x)) arr
| x == (length arr) = arr
| otherwise = slice 0 (x) (init arr)
slice x y arr
| x < 0 = slice ((length arr)+x) y arr
| y < 0 = slice x ((length arr)+y) arr
| otherwise = slice (x-1) (y-1) (tail arr)
main = do
print(slice (-3) (-1) [3, 4, 29, 4, 6]) -- [29,4]
print(slice (2) (-1) [35, 345, 23, 24, 69, 2, 34, 523]) -- [23,24,69,32,34]
print(slice 2 5 [34, 5, 5, 3, 43, 4, 23] ) -- [5,3,43]
明らかに、私のfoldlバージョンはテイクドロップアプローチに負けますが、誰かがそれを改善する方法を考えているのでしょうか?
slice from to = reverse.snd.foldl build ((from, to + 1), []) where
build res@((_, 0), _) _ = res
build ((0, to), xs) x = ((0, to - 1), x:xs)
build ((from, to), xs) _ = ((from - 1, to - 1), xs)
sublist start length = take length . snd . splitAt start
slice start end = snd .splitAt start . take end