5

In APL one can use a bit vector to select out elements of another vector; this is called compression. For example 1 0 1/3 5 7 would yield 3 7.

Is there a accepted term for this in functional programming in general and F# in particular?

Here is my F# program:

let list1 = [|"Bob"; "Mary"; "Sue"|]
let list2 = [|1; 0; 1|]

[<EntryPoint>]
let main argv = 

    0 // return an integer exit code

What I would like to do is compute a new string[] which would be [|"Bob"; Sue"|]

How would one do this in F#?

JonnyBoats
  • 5,177
  • 1
  • 36
  • 60

4 Answers4

6
Array.zip list1 list2                 // [|("Bob",1); ("Mary",0); ("Sue",1)|]
|> Array.filter (fun (_,x) -> x = 1)  // [|("Bob", 1); ("Sue", 1)|]
|> Array.map fst                      // [|"Bob"; "Sue"|]

The pipe operator |> does function application syntactically reversed, i.e., x |> f is equivalent to f x. As mentioned in another answer, replace Array with Seq to avoid the construction of intermediate arrays.

I expect you'll find many APL primitives missing from F#. For lists and sequences, many can be constructed by stringing together primitives from the Seq, Array, or List modules, like the above. For reference, here is an overview of the Seq module.

Søren Debois
  • 5,598
  • 26
  • 48
1

I think the easiest is to use an array sequence expression, something like this:

let compress bits values =
    [|
        for i = 0 to bits.Length - 1 do
            if bits.[i] = 1 then
                yield values.[i]
    |]

If you only want to use combinators, this is what I would do:

Seq.zip bits values
|> Seq.choose (fun (bit, value) ->
    if bit = 1 then Some value else None)
|> Array.ofSeq

I use Seq functions instead of Array in order to avoid building intermediary arrays, but it would be correct too.

Tarmil
  • 11,177
  • 30
  • 35
1

One might say this is more idiomatic:

Seq.map2 (fun l1 l2 -> if l2 = 1 then Some(l1) else None) list1 list2
|> Seq.choose id
|> Seq.toArray

EDIT (for the pipe lovers)

(list1, list2)
||> Seq.map2 (fun l1 l2 -> if l2 = 1 then Some(l1) else None)
|> Seq.choose id
|> Seq.toArray
Eugene Fotin
  • 1,070
  • 13
  • 24
0

Søren Debois' solution is good but, as he pointed out, but we can do better. Let's define a function, based on Søren's code:

let compressArray vals idx =
    Array.zip vals idx
        |> Array.filter (fun (_, x) -> x = 1)
        |> Array.map fst

compressArray ends up creating a new array in each of the 3 lines. This can take some time, if the input arrays are long (1.4 seconds for 10M values in my quick test).
We can save some time by working on sequences and creating an array at the end only:

let compressSeq vals idx =
    Seq.zip vals idx
        |> Seq.filter (fun (_, x) -> x = 1)
        |> Seq.map fst

This function is generic and will work on arrays, lists, etc. To generate an array as output:

compressSeq sq idx |> Seq.toArray

The latter saves about 40% of computation time (0.8s in my test).

As ildjarn commented, the function argument to filter can be rewritten to snd >> (=) 1, although that causes a slight performance drop (< 10%), probably because of the extra function call that is generated.

Mau
  • 14,234
  • 2
  • 31
  • 52
  • I did point out the use of `Seq` to avoid temporary arrays, you know. Anyway, did you try also @Tarmil's first solution? I'd be interested to know if avoiding the overhead of multiple `Seq`s makes any difference. – Søren Debois Mar 31 '14 at 09:06
  • Tarmil's solution with the array expression is slightly slower than solution 2 above, at 0.9s. – Mau Mar 31 '14 at 09:12