How to iterate through a list, element by element

Viewed 319

I have just started learning Haskell and I am trying to write some basic functions in order to get a better understanding of this language.

I want to write a function which takes a list and an Int (N) as argument and returns the element at index N in the list, without using the !! operator or any built-in function.

Here is what I tried :

myHead :: [a] -> a
myHead (x:_) = x
myHead [] = error "head: empty list"

myNth :: [a] -> Int -> a
myNth x i = if i < 0
  then error "nth: index can't be negative"
  else myNthIterator x i 0

myNthIterator :: [a] -> Int -> Int -> a
myNthIterator [] i n = error "nth: bad index"
myNthIterator (_:x) i n = if i == n
  then myHead x
  else myNthIterator x i ( n + 1 )

It works but it's shifted to the right. For example myNth [1, 2, 3, 4] 2 would give 4 and not 3.

From what I understand, (_:x) removes the first element of the list and I don't see how to iterate through the list element by element.

Could someone put me on the trail? I find it difficult to find resources for beginners in this language.

3 Answers

We can use Maybe to model whether the index was valid.

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

We can pattern match on the index to get our base case, and the list to get the first element and tail.

What you're doing there with (_:x) is called "pattern matching" in case you didn't know. The general pattern for iterating through a list would be (x : xs) where x is head element of the list being matched and xs is the rest of the list. If you use _ you don't remove anything it is still matched to _ which is the convention for saying "I won't use this".

With that you can make a function like this:

myNth :: [a] -> Int -> a
myNth  []      _ = error "out of range"
myNth (x : xs) 0 = x
myNth (_ : xs) n = myNth xs (n - 1)

Whenever myNth is called it will go top to bottom over those definitions trying to match the patterns to the input. So when you call myNth [10,11] 1 it won't match the first clause because [10,11] doesn't match an empty list, it won't match the second either because 1 is not 0 and so it will match the third case where it will match the [10,11] on (10 : [11]), therefore _ is 10 and xs is [11] and 1 will be matched as n. Then it calls itself recursively, as myNth [11] 0. Now that will match the second case and it will return x from the match of [11] on (11 : [])

Like 414owen said you can use the Maybe a type to avoid using error.

P.S.: I don't know how beginner you are but I assume you know of the : operator, it prepends an element to a list... If you go more in depth (afaik) every list is actually stored as a sequence of a:(b:(c:(d:(e:[])))) which is equivalent to [a,b,c,d,e] which is equivalent to a:[b,c,d,e] etc.

It works but it's shifted to the right. For example myNth [1, 2, 3, 4] 2 would give 4 and not 3.

myNthIterator (_:x) i n = if i == n
  then myHead x
  else myNthIterator x i ( n + 1 )

Let us look at myNthIterator [1..4] 1 1

myNthIterator [1..4] 1 1            -- replace [a, b] with (a: (b : []))
== myNthIterator (1 : [2, 3, 4]) 1 1
-- matching with `myNthIterator (_:x) i n` will result in
-- 1 ~ _
-- x ~ [2, 3, 4]
-- i ~ 1
-- n ~ 1
== if 1 == 1 then myHead [2, 3, 4] else myNthIterator [2, 3, 4] 1 (1 + 1)
== myHead [2, 3, 4]
== 2

So (_:x) matching against (1 : [2, 3, 4]) is suspicious. A first step in fixing it is to replace (_:x) by (x:xs).

myNthIterator (x:xs) i n = ...

In our example this would mean x == 1 and xs == [2, 3, 4].

Related