Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AList.partition #89

Open
fradav opened this issue May 11, 2021 · 6 comments
Open

AList.partition #89

fradav opened this issue May 11, 2021 · 6 comments
Labels
feature a feature to be implemented

Comments

@fradav
Copy link

fradav commented May 11, 2021

Similar to List.partition, adaptively.

A quick and dirt would be :

module AList =
    let partition<'a> (p: 'a -> bool) (l : clist<'a>) =
        let (left,right) = (clist<'a>[],clist<'a>[])
        let addRow (row: 'a) =
            match p row with
            | true  -> transact (fun () -> left.Add row)  
            | false -> transact (fun () -> right.Add row)
            |> ignore
        let removeRow (o: IndexList<'a>) (i : Index) =
            match p o.[i] with
            | true  -> transact (fun () -> left.Remove i)  
            | false -> transact (fun () -> right.Remove i)
            |> ignore
        let processDelta o (i : Index, e : ElementOperation<'a>) =
            match e with
            | Remove -> removeRow o i
            | Set(s) -> addRow s
            |> ignore
        l.AddCallback (fun o c -> c.ToList() 
                                      |> List.iter (processDelta o)) 
        |> ignore
        (left,right)
@fradav
Copy link
Author

fradav commented May 13, 2021

A proper way (but clearly an not optimized one) :

    let partition (predicate : 'T -> bool) (list: alist<'T>) =
        // TODO; Avoid reapplying the filter a second time
        if list.IsConstant then
            (constant (fun () -> list |> force |> IndexList.filter predicate),
             constant (fun () -> list |> force |> IndexList.filter (predicate >> not)))
        else
            match list.History with
            | Some history ->
                (ofReader (fun () -> history.NewReader(IndexList.trace, FilterReader.DeltaMapping (fun _ v -> predicate v))),
                 ofReader (fun () -> history.NewReader(IndexList.trace, FilterReader.DeltaMapping (fun _ v -> predicate v |> not))))
            | None -> 
                (ofReader (fun () -> FilterReader(list, fun _ v -> predicate v)),
                 ofReader (fun () -> FilterReader(list, fun _ v -> predicate v |> not)))

I don't see how to make a simple reader function which makes only one pass from the list to be read.

@krauthaufen
Copy link
Collaborator

An easier (just as inefficient) way would be

let partition (predicate : 'T -> bool) (list : alist<'a>) =
    AList.filter predicate list, AList.filter (predicate >> not) list

I'm currently thinking about that but I suspect there might not be a significantly more efficient way.
Nonetheless it would be possible to cache predicate appropriately s.t. it doesn't get invoked twice on the same element.

I'll let you know once I concluded my experiments

@krauthaufen
Copy link
Collaborator

So here's my current best attempt which basically uses a shared IndexCache for two (slightly customized) FilterReaders.
Since they share a mutable cache locking became necessary but that shouldn't really be a problem here.

I'm off for now and maybe I'll come up with something better. Or maybe someone else has a better idea...

[<Sealed>]
type PartitionReader<'a>(input : alist<'a>, predicate : IndexCache<'a, bool>, side : bool) =
    inherit AbstractReader<IndexListDelta<'a>>(IndexListDelta.empty)

    let reader = input.GetReader()

    static member DeltaMapping (side : bool, predicate : IndexCache<'a, bool>) =
        IndexListDelta.choose (fun i op ->
            match op with
            | Remove -> 
                match lock predicate (fun () -> predicate.Revoke(i)) with
                | Some v when v = side -> Some Remove
                | _ -> None
            | Set v -> 
                let o, n = lock predicate (fun () -> predicate.InvokeAndGetOld(i, v))
                match n = side with
                | true -> 
                    Some (Set v)
                | false -> 
                    match o with
                    | Some v when v = side -> Some Remove
                    | _ -> None
        )

    override x.Compute(token) =
        reader.GetChanges token |> IndexListDelta.choose (fun i op ->
            
            match op with
            | Remove -> 
                match lock predicate (fun () -> predicate.Revoke(i)) with
                | Some v when v = side -> Some Remove
                | _ -> None
            | Set v -> 
                let o, n = lock predicate (fun () -> predicate.InvokeAndGetOld(i, v))
                match n = side with
                | true -> 
                    Some (Set v)
                | false -> 
                    match o with
                    | Some v when v = side -> Some Remove
                    | _ -> None
        )
        
let partition (predicate : Index -> 'a -> bool) (l : alist<'a>) =
    if l.IsConstant then
        let t, f = AList.force(l).Content |> MapExt.partition predicate
        AList.ofIndexList (IndexList.ofMap t), AList.ofIndexList (IndexList.ofMap f)
    else
        let predicate = IndexCache predicate
        match l.History with
        | Some history ->
            AList.ofReader (fun () -> history.NewReader(IndexList.trace, PartitionReader.DeltaMapping(true, predicate))),
            AList.ofReader (fun () -> history.NewReader(IndexList.trace, PartitionReader.DeltaMapping(false, predicate)))
        | None ->
            AList.ofReader (fun () -> PartitionReader(l, predicate, true)),
            AList.ofReader (fun () -> PartitionReader(l, predicate, false))

@fradav
Copy link
Author

fradav commented May 15, 2021

Pardon my ignorance on the subject, but isn't an internal compiler memoization for the predicate already there, this making this cache somewhat redundant?

@krauthaufen
Copy link
Collaborator

Hey, I don't think there's any optmization done for these predicates because any kind of memoization introduced by the compiler would need information about when to evict caches (which it generally can't have)

So afaik. there's no such thing (unless of course I am wrong)

@fradav
Copy link
Author

fradav commented May 15, 2021

You're surely right, I just thought of trivial predicates for which memoization should be too much a waste of memory and cpu cycles to be enforced on a compiler level basis.

@krauthaufen krauthaufen added the feature a feature to be implemented label Apr 22, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature a feature to be implemented
Projects
None yet
Development

No branches or pull requests

2 participants