Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# List.mapi function with filtering

Tags:

list

filter

f#

I have a line of code like this:

list |> List.mapi (fun i x -> y, i)

(Assume that y is already defined type)

But I want to return elements with some condition (e.g filter it)

I am not able to write like this:

 list |> List.mapi (fun i x -> if 'condition' then y, i)

because it needs else condition as well and I don't have 'else' case. I also didn't manage to use filter at the same time, because I need to return correct indexes as well, and if I filter the list, indexes will be changed. Any ideas?

EDIT By now, I implemented like this:

list |> List.mapi (fun i a -> if (a = None) then O, i else X, i) |> List.filter (fun (a,i) -> a = O)

I'm giving useless X,i for else case, just to be able to write condition after that and remove X ones. It's working, that's the result I want. But I'm sure there is a better solution.

like image 846
Kote Avatar asked Dec 17 '15 15:12

Kote


3 Answers

If you want to filter, but have indexes applied in strictly monotonically increasing order after filtering, then filter first, and then add the index values:

list |> List.filter condition |> List.mapi (fun i x -> x, i)

Here's an example where b is filtered away from an alphabetical list of characters:

[('a', 0); ('c', 1); ('d', 2); ('e', 3); ('f', 4); ('g', 5); ('h', 6);
 ('i', 7); ('j', 8); ('k', 9); ('l', 10); ('m', 11); ('n', 12); ('o', 13)]
like image 29
Mark Seemann Avatar answered Nov 13 '22 06:11

Mark Seemann


Let me add yet another answer: From your question and comments I understand that you want to filter a list by a condition depending on the values while retaining the original indexes. I'm not sure whether the result should then consist of a list of fixed values and original index or you want to map. The following allows both:

let indexedFilterMap p f =
    List.indexed
    >> List.filter (snd >> p)
    >> List.map (fun (i, x) -> f x, i)
  1. add index
  2. filter by p using second values of indexed list, i.e. original values
  3. map the remaining values and reverse the order in the tuple (s.t. it is value, index)

If you need the index for the mapping (as the question title includes mapi):

let indexedFilterMapi p f =
    List.indexed
    >> List.filter (snd >> p)
    >> List.map f

Or if you need the index for the filter:

let indexedFilteriMap p f =
    List.indexed
    >> List.filter p
    >> List.map (fun (i, x) -> f x, i)

The combination should be straightforward.

These can then be used:

let list = ['a'; 'b'; 'c']
let condition = (<>) 'b'
let y = "fixed value"

indexedFilterMap condition (fun _ -> y) list // [("fixed value", 0); ("fixed value", 2)]

let m (i, _) = sprintf "fixed value %i" i
indexedFilterMapi condition m list // ["fixed value 0"; "fixed value 2"]

let c (i, _) = i <> 1
indexedFilteriMap c (fun _ -> y) list // [("fixed value", 0); ("fixed value", 2)]
like image 192
CaringDev Avatar answered Nov 13 '22 07:11

CaringDev


First of all, be aware that looking up by i from a list is an O(n) operation so if that's what you're doing, there could be a more efficient alternative available by expressing the problem differently.

For the problem as described, you could do something like this:

list 
|> List.mapi (fun i x -> x, i)
|> List.choose (fun (x,i) -> if 'condition on x' then Some (y,i) else None)

Returns a list of tuples of y and an element index that satisfied the condition.

Example:

Consider I start with ['a','b','c','d','e'], the first mapi maps the list to [('a',0),('b',1),('c',2),('d',3),('e',4)] then I apply choose with (for example) a condition that select vowels and returns some value y. I end up with [(y,4)].

Edit: In response to your update, here is an example of using this in precisely the way you want.

list 
|> List.mapi (fun i x -> x, i)
|> List.choose (fun (x,i) -> 
    match x with
    |O -> Some (O, i)
    |X -> None)
like image 2
TheInnerLight Avatar answered Nov 13 '22 08:11

TheInnerLight