How can I get nth element from a list?

前端 未结 7 656
长发绾君心
长发绾君心 2020-12-12 18:51

How can I access a list by index in Haskell, analog to this C code?

int a[] = { 34, 45, 56 };
return a[1];
相关标签:
7条回答
  • 2020-12-12 19:05

    I know it's an old post ... but it may be useful for someone ... in a "functional" way ...

    import Data.List
    
    safeIndex :: [a] -> Int -> Maybe a
    safeIndex xs i 
            | (i> -1) && (length xs > i) = Just (xs!!i)
            | otherwise = Nothing
    
    0 讨论(0)
  • 2020-12-12 19:21

    An alternative to using (!!) is to use the lens package and its element function and associated operators. The lens provides a uniform interface for accessing a wide variety of structures and nested structures above and beyond lists. Below I will focus on providing examples and will gloss over both the type signatures and the theory behind the lens package. If you want to know more about the theory a good place to start is the readme file at the github repo.

    Accessing lists and other datatypes

    Getting access to the lens package

    At the command line:

    $ 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
    


    Accessing lists

    To access a list with the infix operator

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

    Unlike the (!!) this will not throw an exception when accessing an element out of bounds and will return Nothing instead. It is often recommend to avoid partial functions like (!!) or head since they have more corner cases and are more likely to cause a run time error. You can read a little more about why to avoid partial functions at this wiki page.

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

    You can force the lens technique to be a partial function and throw an exception when out of bounds by using the (^?!) operator instead of the (^?) operator.

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


    Working with types other than lists

    This is not just limited to lists however. For example the same technique works on trees from the standard containers package.

     > 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
    

    We can now access the elements of the tree in depth-first order:

    > 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
    

    We can also access sequences from the containers package:

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

    We can access the standard int indexed arrays from the vector package, text from the standard text package, bytestrings fro the standard bytestring package, and many other standard data structures. This standard method of access can be extended to your personal data structures by making them an instance of the typeclass Taversable, see a longer list of example Traversables in the Lens documentation..


    Nested structures

    Digging down into nested structures is simple with the lens hackage. For example accessing an element in a list of lists:

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

    This composition works even when the nested data structures are of different types. So for example if I had a list of trees:

    > :{
     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
    

    You can nest arbitrarily deeply with arbitrary types as long as they meet the Traversable requirement. So accessing a list of trees of sequences of text is no sweat.


    Changing the nth element

    A common operation in many languages is to assign to an indexed position in an array. In python you might:

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

    The lens package gives this functionality with the (.~) operator. Though unlike in python the original list is not mutated, rather a new list is returned.

    > 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 is just a function and the (&) operator, part of the lens package, is just reverse function application. Here it is with the more common function application.

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

    Assignment again works perfectly fine with arbitrary nesting of Traversables.

    > [[1,2,3],[4,5,6]] & element 0 . element 1 .~ 9
    [[1,9,3],[4,5,6]]
    
    0 讨论(0)
  • 2020-12-12 19:21

    The straight answer was already given: Use !!.

    However newbies often tend to overuse this operator, which is expensive in Haskell (because you work on single linked lists, not on arrays). There are several useful techniques to avoid this, the easiest one is using zip. If you write zip ["foo","bar","baz"] [0..], you get a new list with the indices "attached" to each element in a pair: [("foo",0),("bar",1),("baz",2)], which is often exactly what you need.

    0 讨论(0)
  • 2020-12-12 19:24

    Look here, the operator used is !!.

    I.e. [1,2,3]!!1 gives you 2, since lists are 0-indexed.

    0 讨论(0)
  • 2020-12-12 19:25

    I'm not saying that there's anything wrong with your question or the answer given, but maybe you'd like to know about the wonderful tool that is Hoogle to save yourself time in the future: With Hoogle, you can search for standard library functions that match a given signature. So, not knowing anything about !!, in your case you might search for "something that takes an Int and a list of whatevers and returns a single such whatever", namely

    Int -> [a] -> a
    

    Lo and behold, with !! as the first result (although the type signature actually has the two arguments in reverse compared to what we searched for). Neat, huh?

    Also, if your code relies on indexing (instead of consuming from the front of the list), lists may in fact not be the proper data structure. For O(1) index-based access there are more efficient alternatives, such as arrays or vectors.

    0 讨论(0)
  • 2020-12-12 19:25

    You can use !!, but if you want to do it recursively then below is one way to do it:

    dataAt :: Int -> [a] -> a
    dataAt _ [] = error "Empty List!"
    dataAt y (x:xs)  | y <= 0 = x
                     | otherwise = dataAt (y-1) xs
    
    0 讨论(0)
提交回复
热议问题