Comment fonctionne une liste ? 

Romain Berthon

#JobHacker

@RomainTrm

romainberthon.blog

Au programme

  • Définir une liste
  • Coder une liste
  • Les listes C# et F#
  • Volumétrie

Définir une liste

Aparté : récursivité


    // int -> int
    let     factorial n =

    // int -> int
    let rec factorial n =
        // Exit pattern that ends the recursion
        if n <= 1 then 
            1
        // Recursion process, apply and move to next element
        else 
            n * factorial (n - 1)
    
    > factorial -1;;
    val it : int = 1
    
    > factorial 1;;
    val it : int = 1
    
    > factorial 3;;
    val it : int = 6

    // int -> int
    let rec factorial n =
        
 
            
        // Recursion process, apply and move to next element
        
            n * factorial (n - 1)

Une définition mathématique

List a = Nil | Cons a (List a)

Une liste d'éléments a est :

une liste vide

OU

un élément a (head) ET une liste d'éléments a (tail).

Alonzo Church - Lambda Calculus

Une définition mathématique

Exemple avec une liste [1; 5; 3] :

1

5

3

Nil

List a = Nil | Cons a (List a)

Cons 1 (  

Cons 5 (  

Cons 3 (  

Nil )))

Coder une liste

Coder une liste

La liste

    
        public class LinkedList<T> : IEquatable<LinkedList<T>>
        {
            public bool IsEmpty { get; }
    
            public T Head { get; }
    
            public LinkedList<T> Tail { get; }
    
            public LinkedList()
            {
                IsEmpty = true;
            }
    
            public LinkedList(T head, LinkedList<T> tail)
            {
                Head = head;
                Tail = tail;
            }
    
            // Equatable pattern [...]
        }

Coder une liste

La liste

    
    // [1; 5; 3]    
    var list = new LinkedList<int>(1, 
                    new LinkedList<int>(5, 
                        new LinkedList<int>(3, 
                            new LinkedList<int>())))

1

5

3

Nil

Cons 1 (  

Cons 5 (  

Cons 3 (  

Nil )))

Coder une liste

La méthode Map


    [Test]
    public void MapElements()
    {
        LinkedList<int> list = // [1; 2]

        var result = list.Map(i => i.ToString());

        LinkedList<string> expectedResult = // ["1"; "2"]
        Assert.AreEqual(expectedResult, result);
    }

Coder une liste

La méthode Map


        public static LinkedList<TOutput> Map<TInput, TOutput>(
            this LinkedList<TInput> list, 
            Func<TInput, TOutput> morphism)
        {
            // List is empty : we are at the end of the tail, return an empty list
            if (list.IsEmpty) return new LinkedList<TOutput>();

            

            


        }

        public static LinkedList<TOutput> Map<TInput, TOutput>(
            this LinkedList<TInput> list, 
            Func<TInput, TOutput> morphism)
        {
            


            

            


        }

        public static LinkedList<TOutput> Map<TInput, TOutput>(
            this LinkedList<TInput> list, 
            Func<TInput, TOutput> morphism)
        {
            // List is empty : we are at the end of the tail, return an empty list
            if (list.IsEmpty) return new LinkedList<TOutput>();

            // We apply morphism to head and apply Map to the tail
            // Then return a new list 
            return new LinkedList<TOutput>(
                morphism(list.Head),
                Map(list.Tail, morphism));
        }

Coder une liste

La méthode Filter


    [Test]
    public void FilterElements()
    {
        LinkedList<int> list = // [1; 2; 3]

        var result = list.Filter(i => i % 2 == 1);

        LinkedList<int> expectedResult = // [1; 3]
        Assert.AreEqual(expectedResult, result);
    }

Coder une liste

La méthode Filter

       
        public static LinkedList<T> Filter<T>(
            this LinkedList<T> list, 
            Func<T, bool> predicate)
        {
            // List is empty : we are at the end of the tail, return an empty list
            if (list.IsEmpty) return list;

            var headMatchPredicate = predicate(list.Head);
            var filteredTail = Filter(list.Tail, predicate);

            if (headMatchPredicate)

                // Head matches predicate
                // We return a new list with head and the filtered tail
                return new LinkedList<T>(list.Head, filteredTail);

            // Head doesn't match predicate, we only return the filtered tail
            return filteredTail;
        }
       
        public static LinkedList<T> Filter<T>(
            this LinkedList<T> list, 
            Func<T, bool> predicate)
        {
            // List is empty : we are at the end of the tail, return an empty list
            if (list.IsEmpty) return list;

            var headMatchPredicate = predicate(list.Head);
            var filteredTail = Filter(list.Tail, predicate);

            if (headMatchPredicate)

                // Head matches predicate
                // We return a new list with head and the filtered tail
                return new LinkedList<T>(list.Head, filteredTail);

            

        }
       
        public static LinkedList<T> Filter<T>(
            this LinkedList<T> list, 
            Func<T, bool> predicate)
        {
            // List is empty : we are at the end of the tail, return an empty list
            if (list.IsEmpty) return list;

           


            

                



           

        }
       
        public static LinkedList<T> Filter<T>(
            this LinkedList<T> list, 
            Func<T, bool> predicate)
        {



           


            

                



           

        }
       
        public static LinkedList<T> Filter<T>(
            this LinkedList<T> list, 
            Func<T, bool> predicate)
        {
            // List is empty : we are at the end of the tail, return an empty list
            if (list.IsEmpty) return list;

            var headMatchPredicate = predicate(list.Head);
            var filteredTail = Filter(list.Tail, predicate);

          

               



            

        }

Les listes C# et F#

Linked list

  • Immutable
    • Taille fixe
    • On ne peut pas remplacer un élément

C# List

  • Mutable
    • Taille variable
    • On peut remplacer un élément
  • Objet .Net qui encapsule le type C# Array

Avec GetEnumerator()

    
    using System.Collections.Generic;

    public static List<TOutput> Map<TInput, TOutput>(
        this List<TInput> list,
        Func<TInput, TOutput> morphism)
    {
        IEnumerable<TOutput> Map(List<TInput>.Enumerator enumerator)
        {
            if (!enumerator.MoveNext()) 
                return Enumerable.Empty<TOutput>();

            var head = morphism(enumerator.Current);
            var tail = Map(enumerator);
            return new[] { head }.Concat(tail);
        }

        return Map(list.GetEnumerator()).ToList();
    }

Avec un langage fonctionnel

L'opérateur "cons" ::

    
    > [1; 5; 3] = 1::[5; 3];;
    val it : bool = true
    
    
    > [1; 5; 3] = 1::[5; 3];;
    val it : bool = true
    
    > [1; 5; 3] = 1::5::[3];;
    val it : bool = true
    
    > [1; 5; 3] = 1::5::3::[];;
    val it : bool = true

    >


    
    :: of Head : 'T * Tail : 'T list
    // Head::Tail

:: en F# ou LISP, : en Haskell

Avec un langage fonctionnel

La fonction map


    // ('a -> 'b) -> 'a list -> 'b list
    let rec map morphism list =
        match list with
        | [] -> []
        | head::tail -> (morphism head)::(map morphism tail)

    > map string [1; 5; 3];;
    val it : string list = ["1"; "5"; "3"]

    >

Volumétrie

Volumétrie

simpleSum

list = [10000]

10000 + ?

simpleSum

list = [ ]

0

simpleSum

list = [2.. ]

2 + ?

simpleSum

list = [1.. ]

1 + ?

...


    // int list -> int
    let rec simpleSum list =
        match list with
        | [] -> 0
        | head::tail -> head + simpleSum tail

    > simpleSum [1..10000];;

    > simpleSum [1..10000];;
    val it : int = 50005000

simpleSum

list = [10000]

10000 + 0

simpleSum

list = [2.. ]

2 + 50004997

simpleSum

list = [1.. ]

1 + 50004999

...

Volumétrie


    > simpleSum [1..100000];;

simpleSum

list = [64201.. ]

64201 + ?

stack limit

simpleSum

list = [2.. ]

2 + ?

simpleSum

list = [1.. ]

1 + ?

...


    // int list -> int
    let rec simpleSum list =
        match list with
        | [] -> 0
        | head::tail -> head + simpleSum tail

    > simpleSum [1..100000];;
    // -> StackOverflowException

Volumétrie : Tail recursion


    > sum [1..100000];;
    val it : int = 705082704

sum

list = [100000]

total = 704982704

sum

list = [ ]

total = 705082704

sum

list = [2.. ]

total = 1

sum

list = [1.. ]

total = 0

...


    // int list -> int 
    let sum list =
        let rec sumUtil total list =
            match list with
            | [] -> total
            | head::tail -> sumUtil (total + head) tail
        sumUtil 0 list

    > sum [1..100000];;

Merci !

Romain Berthon

#JobHacker

@RomainTrm

romainberthon.blog

Comment fonctionne une liste ?

By Romain Berthon

Comment fonctionne une liste ?

  • 174