Scott Nimrod
Scott Nimrod

Reputation: 11595

How do I include multiple arguments for List.filter?

How do I include multiple arguments for List.filter?

I need to add some parameters to a function that serves as a predicate for filtering a list.

In F#, the List.filter accepts just one argument. However, I need to add multiple arguments for my predicate to work.

In my case, I need to add sourceX and sourceY as parameters:

let jumpOptions space =
    match space with
    | Allocated p -> match p with
                     | Red   (ch,xy) -> xy = (sourceX + 1, sourceY - 1) ||
                                        xy = (sourceX - 1, sourceY - 1)

                     | Black (ch,xy) -> xy = (sourceX + 1, sourceY + 1) ||
                                        xy = (sourceX - 1, sourceY + 1)
    | _ -> false

let jumpsForSoldier piece positions =
    match piece with
    | Black (ch,pos) -> positions |> List.filter jumpOptions
    | Red   (ch,pos) -> positions |> List.filter jumpOptions

In conclusion, I want to keep the elements within my list pure. Hence, I do not want to bundle each element within my list with other values just to satisfy a filter function.

Any guidance?

Appendix:

open NUnit.Framework
open FsUnit

(* Types *)
type Black = BlackKing | BlackSoldier
type Red =   RedKing   | RedSoldier

type Coordinate = int * int

type Piece =
    | Black of Black * Coordinate
    | Red   of Red   * Coordinate

type Space =
    | Allocated of Piece
    | Available of Coordinate

type Status =
    | BlacksTurn | RedsTurn
    | BlackWins  | RedWins

(* Private *)
let private black coordinate = Allocated (Black (BlackSoldier , coordinate))
let private red   coordinate = Allocated (Red   (RedSoldier   , coordinate))

let private yDirection = function
    | Black _ -> -1
    | Red   _ ->  1

let private toAvailable = function
    | Available pos -> true
    | _             -> false

let available positions = positions |> List.filter toAvailable

let private availableSelection = function
    | Available pos -> Some pos
    | Allocated _   -> None

let private availablePositions positions = 
    positions |> List.filter toAvailable
              |> List.choose availableSelection

let private allocatedSelection = function
    | Allocated p -> match p with
                     | Red   (ch,xy) -> Some xy
                     | Black (ch,xy) -> Some xy
    | _ -> None

let private allocatedPositions positions = 
    positions |> List.filter toAvailable
              |> List.choose allocatedSelection

let private getCoordinate = function
    | Available xy -> Some xy
    | _            -> None

let coordinateOf = function
    | Black (checker , pos) -> pos
    | Red   (checker , pos) -> pos

let jumpOptions space =
    match space with
    | Allocated p -> match p with
                     | Red   (ch,xy) -> let sourceX, sourceY = coordinateOf source
                                        xy = (sourceX + 1, sourceY - 1) ||
                                        xy = (sourceX - 1, sourceY - 1)

                     | Black (ch,xy) -> let sourceX, sourceY = coordinateOf p
                                        xy = (sourceX + 1, sourceY + 1) ||
                                        xy = (sourceX - 1, sourceY + 1)
    | _ -> false

let jumpsForSoldier piece positions =
    match piece with
    | Black (ch,pos) -> positions |> List.filter jumpOptions
    | Red   (ch,pos) -> positions |> List.filter jumpOptions

let private isKing piece = 
    match piece with
    | Black (checker , _) -> match checker with
                             | BlackSoldier -> false
                             | BlackKing    -> true

    | Red   (checker , _) -> match checker with
                             | RedSoldier   -> false
                             | RedKing      -> true
(* Public *)
let startGame () =
    [ red (0,0);  red (2,0);  red (4,0);  red (6,0)
      red (1,1);  red (3,1);  red (5,1);  red (7,1)
      red (0,2);  red (2,2);  red (4,2);  red (6,2)

      Available (1,3); Available (3,3); Available (5,3); Available (7,3)
      Available (0,4); Available (2,4); Available (4,4); Available (6,4)

      black (1,5);  black (3,5);  black (5,5);  black (7,5)
      black (0,6);  black (2,6);  black (4,6);  black (6,6)
      black (1,7);  black (3,7);  black (5,7);  black (7,7) ] , BlacksTurn

let optionsFor piece positions =

    let sourceX , sourceY = coordinateOf piece


    let optionsForSoldier = 
        (fun pos -> pos = ((sourceX - 1) , (sourceY + (piece |> yDirection) )) ||
                    pos = ((sourceX + 1) , (sourceY + (piece |> yDirection) )))

    let optionsForKing = 
        (fun pos -> pos = ((sourceX - 1) , (sourceY + 1 )) ||
                    pos = ((sourceX + 1) , (sourceY + 1 )) ||
                    pos = ((sourceX - 1) , (sourceY - 1 )) ||
                    pos = ((sourceX + 1) , (sourceY - 1 )))

    match piece |> isKing with
    | false -> positions |> availablePositions 
                         |> List.filter optionsForSoldier


    | true ->  positions |> availablePositions 
                         |> List.filter optionsForKing

let move piece destination positions =

    let rec movePiece positions destinationXY =
        let foundPiece = positions |> List.filter (fun space -> space = Allocated piece)
                                   |> List.head
        match foundPiece with
        | Allocated (Black (ch, xy)) -> (positions |> List.filter (fun space -> space <> Allocated (Black (ch, xy)))
                                                   |> List.filter (fun space -> space <> destination))
                                                   @  [Available (xy) ; (Allocated (Black (ch, destinationXY)))]

        | Allocated (Red   (ch, xy)) -> (positions |> List.filter (fun space -> space <> Allocated (Red (ch, xy)))
                                                   |> List.filter (fun space -> space <> destination))
                                                   @  [Available (xy) ; (Allocated (Red   (ch, destinationXY)))]
        | _ -> positions

    let options   = optionsFor piece positions
    let canMoveTo = (fun target -> options |> List.exists (fun xy -> xy = target))

    match getCoordinate destination with
    | Some target -> match canMoveTo target with
                     | true  -> movePiece positions target
                     | false -> positions
    | None -> positions

(* Tests *)
[<Test>]
let ``get jump options for red soldier`` () =
    // Setup
    let redPiece =   Red   ( RedSoldier , (0,2) )
    let blackPiece = Black ( BlackSoldier , (1,3) )
    let positions = [Allocated redPiece;    Available (2,2);  Available (4,2);  Available (6,2)                  
                     Allocated blackPiece;  Available (3,3);  Available (5,3);  Available (7,3)
                     Available (0,4);       Available (2,4);      Available (4,4);  Available (6,4)]
    // Test
    positions |> jumpsForSoldier redPiece
              |> should equal [Allocated blackPiece]

Upvotes: 0

Views: 164

Answers (2)

Sehnsucht
Sehnsucht

Reputation: 5049

You could extract the required info using pattern matching and use partial application (code edited after TQBF comments)

let jumpOptions (sourceX, sourceY) = function
  Allocated (Red (_, (x, y)) as p)
| Allocated (Black (_, (x, y)) as p) when abs (sourceX - x) = 1
    -> y = sourceY - yDirection p
| _ -> false

let jumpsForSoldier = function
  Red (_, pos)
| Black (_, pos) -> List.filter (jumpOptions pos)

Upvotes: 1

Fyodor Soikin
Fyodor Soikin

Reputation: 80744

You can have as many parameters as you want, then partially apply the function to all of them but one, and pass the result to List.filter.

let jumpOptions sourceX sourceY space = ...

...
   positions |> List.filter (jumpOptions 5 42)

Read more on partial application here.

Upvotes: 6

Related Questions