web-dev-qa-db-ja.com

リストからn番目の要素を取得するにはどうすればよいですか?

このCコードに類似したHaskellのインデックスでリストにアクセスするにはどうすればよいですか?

int a[] = { 34, 45, 56 };
return a[1];
85
Eonil

こちら 、演算子!!を見てください。

つまりリストには0インデックスが付けられているため、[1,2,3]!!12を提供します。

138
phimuemue

私はあなたの質問や与えられた答えに問題があると言っているわけではありませんが、将来の時間を節約するために Hoogle というすばらしいツールについて知りたいと思うかもしれません。特定の署名に一致する標準ライブラリ関数を検索できます。したがって、!!について何も知らない場合、「Intと何かのリストを取り、そのようなものを1つ返すもの」、つまり

Int -> [a] -> a

Loと behold 、最初の結果として!!を使用します(ただし、型シグネチャには実際に検索したものとは逆の2つの引数があります)。きちんとした?

また、コードが(リストの先頭から消費するのではなく)インデックス付けに依存している場合、リストは実際には適切なデータ構造ではない可能性があります。 O(1)インデックスベースのアクセスの場合、 arrays または vectors などのより効率的な代替手段があります。

83
gspr

(!!)を使用する代わりに、 lens パッケージとそのelement関数および関連する演算子を使用することもできます。 lens は、リストの上下にあるさまざまな構造およびネストされた構造にアクセスするための統一されたインターフェースを提供します。以下では、例を提供することに焦点を当て、タイプシグネチャと lens パッケージの背後にある理論の両方について説明します。理論の詳細を知りたい場合は、開始するのに適した場所は github repo のreadmeファイルです。

リストおよびその他のデータ型へのアクセス

レンズパッケージにアクセスする

コマンドラインで:

$ cabal install lens
$ ghci
GHCi, version 7.6.3: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
> import Control.Lens


リストへのアクセス

中置演算子を使用してリストにアクセスするには

> [1,2,3,4,5] ^? element 2  -- 0 based indexing
Just 3

(!!)とは異なり、これは範囲外の要素にアクセスするときに例外をスローせず、代わりにNothingを返します。多くの場合、(!!)headなどの部分関数を避けることをお勧めします。これらは、より多くのコーナーケースがあり、実行時エラーを引き起こす可能性が高いためです。 このwikiページ で、部分的な機能を避ける理由についてもう少し読むことができます。

> [1,2,3] !! 9
*** Exception: Prelude.(!!): index too large

> [1,2,3] ^? element 9
Nothing

(^?!)演算子の代わりに(^?)演算子を使用して、レンズテクニックを強制的に部分関数にし、範囲外の場合に例外をスローできます。

> [1,2,3] ^?! element 1
2
> [1,2,3] ^?! element 9
*** Exception: (^?!): empty Fold


リスト以外のタイプを操作する

ただし、これはリストに限定されません。たとえば、同じテクニックが標準の containers パッケージの trees で機能します。

 > import Data.Tree
 > :{
 let
  tree = Node 1 [
       Node 2 [Node 4[], Node 5 []]
     , Node 3 [Node 6 [], Node 7 []]
     ]
 :}
> putStrLn . drawTree . fmap show $tree
1
|
+- 2
|  |
|  +- 4
|  |
|  `- 5
|
`- 3
   |
   +- 6
   |
   `- 7

これで、ツリーの要素に深さ優先順でアクセスできます。

> tree ^? element 0
Just 1
> tree ^? element 1
Just 2
> tree ^? element 2
Just 4
> tree ^? element 3
Just 5
> tree ^? element 4
Just 3
> tree ^? element 5
Just 6
> tree ^? element 6
Just 7

containers パッケージから sequences にアクセスすることもできます:

> import qualified Data.Sequence as Seq
> Seq.fromList [1,2,3,4] ^? element 3
Just 4

vector パッケージの標準intインデックス配列、標準 text パッケージのテキスト、標準 bytestring パッケージのバイト文字列などにアクセスできます。他の標準データ構造。この標準的なアクセス方法は、typeclass Taversable のインスタンスにすることで個人データ構造に拡張できます。例のより長いリストを参照してください LensドキュメントのTraversables


入れ子構造

レンズ hackage を使用すると、ネスト構造に掘り下げるのは簡単です。たとえば、リストのリスト内の要素にアクセスする場合:

> [[1,2,3],[4,5,6]] ^? element 0 . element 1
Just 2
> [[1,2,3],[4,5,6]] ^? element 1 . element 2
Just 6

この構成は、ネストされたデータ構造のタイプが異なる場合でも機能します。たとえば、ツリーのリストがある場合:

> :{
 let
  tree = Node 1 [
       Node 2 []
     , Node 3 []
     ]
 :}
> putStrLn . drawTree . fmap show $ tree
1
|
+- 2
|
`- 3
> :{
 let 
  listOfTrees = [ tree
      , fmap (*2) tree -- All tree elements times 2
      , fmap (*3) tree -- All tree elements times 3
      ]            
 :}

> listOfTrees ^? element 1 . element 0
Just 2
> listOfTrees ^? element 1 . element 1
Just 4

Traversable要件を満たしている限り、任意の型で任意に深くネストできます。したがって、テキストシーケンスのツリーのリストにアクセスするのは簡単です。


N番目の要素を変更する

多くの言語で一般的な操作は、配列内のインデックス位置に割り当てることです。 pythonでは、次のことができます。

>>> a = [1,2,3,4,5]
>>> a[3] = 9
>>> a
[1, 2, 3, 9, 5]

lens パッケージは、(.~)演算子でこの機能を提供します。 pythonとは異なり、元のリストは変更されませんが、新しいリストが返されます。

> let a = [1,2,3,4,5]
> a & element 3 .~ 9
[1,2,3,9,5]
> a
[1,2,3,4,5]

element 3 .~ 9は単なる関数であり、 lens パッケージの一部である(&)演算子は単なる逆関数アプリケーションです。ここでは、より一般的な機能アプリケーションを使用しています。

> (element 3 .~ 9) [1,2,3,4,5]
[1,2,3,9,5]

割り当ては、Traversablesの任意のネストで再び完全に機能します。

> [[1,2,3],[4,5,6]] & element 0 . element 1 .~ 9
[[1,9,3],[4,5,6]]
57
Davorak

まっすぐな答えはすでに与えられています:!!を使用します。

ただし、初心者はこの演算子を使いすぎる傾向があり、Haskellではコストがかかります(配列ではなく単一のリンクリストで作業するため)。これを回避するための便利なテクニックがいくつかありますが、最も簡単な方法はZipを使用することです。 Zip ["foo","bar","baz"] [0..]と書くと、ペアの各要素にインデックスが「アタッチ」された新しいリスト[("foo",0),("bar",1),("baz",2)]が得られます。

11
Landei

実装におけるHaskellの標準リストデータ型forall t. [t]は、標準のCリンクリストに非常に似ており、本質的にそのプロパティを共有します。リンクリストは配列とは大きく異なります。最も注目すべきは、インデックスによるアクセスは、O(n)の一定時間操作ではなく、O(1) linear-です。

頻繁なランダムアクセスが必要な場合は、 Data.Array 標準を検討してください。

!!は、部分的に定義された安全でない関数であり、範囲外のインデックスのクラッシュを引き起こします。標準ライブラリには、そのような部分関数(headlastなど)がいくつか含まれていることに注意してください。安全のために、オプションタイプMaybe、または Safe モジュールを使用してください。

合理的に効率的で堅牢な合計(インデックス≥0の場合)インデックス関数の例:

data Maybe a = Nothing | Just a

lookup :: Int -> [a] -> Maybe a
lookup _ []       = Nothing
lookup 0 (x : _)  = Just x
lookup i (_ : xs) = lookup (i - 1) xs

リンクリストを使用すると、通常は序数が便利です。

nth :: Int -> [a] -> Maybe a
nth _ []       = Nothing
nth 1 (x : _)  = Just x
nth n (_ : xs) = nth (n - 1) xs
4
user6428287

!!を使用できますが、再帰的に実行する場合は、次の方法で実行できます。

dataAt :: Int -> [a] -> a
dataAt _ [] = error "Empty List!"
dataAt y (x:xs)  | y <= 0 = x
                 | otherwise = dataAt (y-1) xs
3
Abgo80