namespace Playground.Lists

open System

/// Lists are ordered, immutable collections of elements of the same type. 
/// They are singly-linked lists, which means they are meant for enumeration, but a poor choice for random access and concatenation if they're large. 
/// This in contrast to Lists in other popular languages, which typically do not use a singly-linked list to represent Lists.
module Lists =
    
    /// Lists are defined using [ ... ]. This is an empty list.
    let list1 = [ ]

    /// This is a list with 3 elements. ';' is used to separate elements on the same line.
    let list2 = [ 1; 2; 3 ]

    /// You can also separate elements by placing them on their own lines.
    let list3 = [
        1
        2
        3
    ]

    /// This is a list of integers from 1 to 1000
    let numberList = [ 1 .. 1000 ]

    /// Lists can also be generated by computations. This is a list containing all the days of the year.
    let daysList = 
        [ for month in 1 .. 12 do
              for day in 1 .. System.DateTime.DaysInMonth(2017, month) do 
                  yield System.DateTime(2017, month, day) ]

    /// Format a DateTime as a Norwegian date
    let toNbDateFormat (date: DateTime) = date.ToString("dd.MM.yyyy")

    // Print the first 5 elements of 'daysList' using 'List.take'.
    let ex1() = printfn $"The first 5 days of 2017 are: %A{daysList |> List.take 5 |> List.map toNbDateFormat}"

    /// Computations can include conditionals. This is a list containing the tuples
    /// which are the coordinates of the black squares on a chess board.
    let blackSquares = 
        [ for i in 0 .. 7 do
              for j in 0 .. 7 do 
                  if (i+j) % 2 = 1 then 
                      yield (i, j) ]

    let ex2() = printfn $"black squares: %A{blackSquares}"

    // Draw a chess board using lists and pipes
    let isBlack (i,j) = List.contains (i,j) blackSquares

    let asSquare isBlack = 
        if isBlack then 
            sprintf "XX"
        else
            sprintf "[]"

    // let asSquare isBlack = 
    //     match isBlack with
    //     | true -> "XX"
    //     | _ -> "[]"

    let chessboard() = 
        for i in 0 .. 7 do
            printfn ""
            for j in 0 .. 7 do
                (i,j) |> isBlack |> asSquare |> printf "%s"
        printfn ""

    /// Lists can be transformed using 'List.map' and other functional programming combinators.
    /// This definition produces a new list by squaring the numbers in numberList, using the pipeline 
    /// operator to pass an argument to List.map.
    let squares = 
        numberList 
        |> List.map (fun x -> x*x) 

    /// There are many other list combinations. The following computes the sum of the squares of the 
    /// numbers divisible by 3.
    let sumOfSquares = 
        numberList
        |> List.filter (fun x -> x % 3 = 0)
        |> List.sumBy (fun x -> x * x)

    let ex3() = printfn $"The sum of the squares of numbers up to 1000 that are divisible by 3 is: %d{sumOfSquares}"

/// Arrays are fixed-size, mutable collections of elements of the same type. 
/// They support fast random access of elements, and are faster than F# lists because they are just contiguous blocks of memory.
module Arrays =

    /// This is The empty array.  Note that the syntax is similar to that of Lists, but uses `[| ... |]` instead.
    let array1 = [| |]

    /// Arrays are specified using the same range of constructs as lists.
    let array2 = [| "hello"; "world"; "and"; "hello"; "world"; "again" |]

    /// This is an array of numbers from 1 to 1000.
    let array3 = [| 1 .. 1000 |]

    /// This is an array containing only the words "hello" and "world".
    let array4 = 
        [| for word in array2 do
               if word.Contains("l") then 
                   yield word |]

    /// This is an array initialized by index and containing the even numbers from 0 to 2000.
    let evenNumbers = Array.init 1001 (fun n -> n * 2) 

    /// Sub-arrays are extracted using slicing notation.
    let evenNumbersSlice = evenNumbers.[0..500]

    /// You can loop over arrays and lists using 'for' loops.
    for word in array4 do 
        printfn $"word: {word}"

    // You can modify the contents of an array element by using the left arrow assignment operator.
    //
    // To learn more about this operator, see: https://docs.microsoft.com/dotnet/fsharp/language-reference/values/index#mutable-variables
    array2.[1] <- "WORLD!"

    /// You can transform arrays using 'Array.map' and other functional programming operations.
    /// The following calculates the sum of the lengths of the words that start with 'h'.
    let sumOfLengthsOfWords = 
        array2
        |> Array.filter (fun x -> x.StartsWith "h")
        |> Array.sumBy (fun x -> x.Length)

    let ex1() = printfn $"The sum of the lengths of the words in Array 2 is: %d{sumOfLengthsOfWords}"

/// Sequences are a logical series of elements, all of the same type. 
/// These are a more general type than Lists and Arrays, capable of being your "view" into any logical series of elements. 
/// They also stand out because they can be lazy, which means that elements can be computed only when they are needed.
module Sequences = 

    /// This is the empty sequence.
    let seq1 = Seq.empty

    /// This a sequence of values.
    let seq2 = seq { yield "hello"; yield "world"; yield "and"; yield "hello"; yield "world"; yield "again" }

    /// This is an on-demand sequence from 1 to 1000.
    let numbersSeq = seq { 1 .. 1000 }

    /// This is a sequence producing the words "hello" and "world"
    let seq3 = 
        seq { for word in seq2 do
                  if word.Contains("l") then 
                      yield word }

    let ex1() = printfn $"Sequence of words containing L: %A{seq3 |> Seq.toList}"

    /// This sequence producing the even numbers up to 2000.
    let evenNumbers = Seq.init 1001 (fun n -> n * 2) 

    let rnd = System.Random()

    /// This is an infinite sequence which is a random walk.
    /// This example uses yield! to return each element of a subsequence.
    ///
    /// yield! (pronounced "yield bang") keyword returns a subsequence which is merged into the final sequence.
    /// yield! is similar to IEnumerable.SelectMany in its ability to flatten a collection of collections.
    let rec randomWalk x =
        seq { yield x
              yield! randomWalk (x + rnd.NextDouble() - 0.5) }

    /// This example shows the first 100 elements of the random walk.
    let first100ValuesOfRandomWalk = 
        randomWalk 5.0 
        |> Seq.truncate 100
        |> Seq.toList

    let ex2() = printfn $"First 100 elements of a random walk: %A{first100ValuesOfRandomWalk}"
