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

Implement (try)FindLastSliceIndex #581

Merged
merged 8 commits into from
Jan 6, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docsrc/content/extensions.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ The String type:
* truncate, drop
* findIndex, tryFindIndex
* findSliceIndex, tryFindSliceIndex
* findLastSliceIndex, tryFindLastSliceIndex
* toArray, ofArray, toList, ofList, toSeq, ofSeq, toCodePoints, ofCodePoints
* getBytes

Expand All @@ -195,6 +196,7 @@ Collections / Traversable types:
* intercalate, intersperse,
* split, replace,
* findSliceIndex, trySliceIndex,
* findLastSliceIndex, tryLastSliceIndex,
* partitionMap
* [IList](reference/fsharpplus-ilist.html)
* toIReadOnlyList
Expand All @@ -207,6 +209,7 @@ Collections / Traversable types:
* split, replace,
* toIReadOnlyList,
* findSliceIndex, tryFindSliceIndex,
* findLastSliceIndex, tryLastSliceIndex,
* partitionMap
* setAt, removeAt
* [Enumerator](reference/fsharpplus-enumerator.html)
Expand All @@ -232,6 +235,7 @@ Collections / Traversable types:
* replicate
* toIReadOnlyList
* findSliceIndex, tryFindSliceIndex
* findLastSliceIndex, tryLastSliceIndex,
* [ IReadOnlyCollection ](reference/fsharpplus-ireadonlycollection.html)
* ofArray, ofList, ofSeq
* map
Expand Down
40 changes: 40 additions & 0 deletions src/FSharpPlus/Control/Indexable.fs
Original file line number Diff line number Diff line change
Expand Up @@ -257,4 +257,44 @@ type TryFindSliceIndex =
let inline call (a: 'a, b: 'b, n) = call_2 (a, b, n)
call (Unchecked.defaultof<TryFindSliceIndex>, source, slice)

type FindLastSliceIndex =
inherit Default1
static member FindLastSliceIndex (x: string , e , [<Optional>]_impl: FindLastSliceIndex) = String.findLastSliceIndex e x
#if !FABLE_COMPILER || FABLE_COMPILER_3
static member FindLastSliceIndex (x: 'a ResizeArray , e: 'a ResizeArray , [<Optional>]_impl: FindLastSliceIndex) = Seq.findLastSliceIndex e x
static member FindLastSliceIndex (x: 'a [] , e , [<Optional>]_impl: FindLastSliceIndex) = Array.findLastSliceIndex e x
static member FindLastSliceIndex (x: list<'a> , e , [<Optional>]_impl: FindLastSliceIndex) = List.findLastSliceIndex e x
static member FindLastSliceIndex (x: 'a Id , e: 'a Id , [<Optional>]_impl: FindLastSliceIndex) = List.findLastSliceIndex [e.getValue] [x.getValue]
#endif

static member inline InvokeOnInstance (slice: '``Collection<'T>``) (source: '``Collection<'T>``) : 'Index =
(^``Collection<'T>``: (static member FindLastSliceIndex: _*_->_) source, slice)
static member FindLastSliceIndex (x: seq<'a> , e , [<Optional>]_impl: Default2) = Seq.findLastSliceIndex e x
static member inline FindLastSliceIndex (x: '``C<'T>``, e: '``C<'T>``, _impl: Default1) : 'Index = FindLastSliceIndex.InvokeOnInstance e x
static member inline FindLastSliceIndex (_: ^t when ^t: null and ^t: struct, _, _impl: Default1) = ()

static member inline Invoke (slice: '``Collection<'T>``) (source: '``Collection<'T>``) : 'Index =
let inline call_2 (a: ^a, b: ^b, n) = ((^a or ^b) : (static member FindLastSliceIndex : _*_*_ -> _) b, n, a)
let inline call (a: 'a, b: 'b, n) = call_2 (a, b, n)
call (Unchecked.defaultof<FindLastSliceIndex>, source, slice)

type TryFindLastSliceIndex =
inherit Default1
static member TryFindLastSliceIndex (x: 'a ResizeArray , e: 'a ResizeArray , [<Optional>]_impl: TryFindLastSliceIndex) = Seq.tryFindLastSliceIndex e x
static member TryFindLastSliceIndex (x: string , e , [<Optional>]_impl: TryFindLastSliceIndex) = String.tryFindLastSliceIndex e x
static member TryFindLastSliceIndex (x: 'a [] , e , [<Optional>]_impl: TryFindLastSliceIndex) = Array.tryFindLastSliceIndex e x
static member TryFindLastSliceIndex (x: list<'a> , e , [<Optional>]_impl: TryFindLastSliceIndex) = List.tryFindLastSliceIndex e x
static member TryFindLastSliceIndex (x: 'a Id , e: 'a Id , [<Optional>]_impl: TryFindLastSliceIndex) = List.tryFindLastSliceIndex [e.getValue] [x.getValue]

static member inline InvokeOnInstance (slice: '``Collection<'T>``) (source: '``Collection<'T>``) : 'Index option =
(^``Collection<'T>``: (static member TryFindLastSliceIndex: _*_->_) source, slice)
static member TryFindLastSliceIndex (x: seq<'a> , e , [<Optional>]_impl: Default2) = Seq.tryFindLastSliceIndex e x
static member inline TryFindLastSliceIndex (x: '``C<'T>``, e: '``C<'T>``, _impl: Default1) : 'Index option = TryFindLastSliceIndex.InvokeOnInstance e x
static member inline TryFindLastSliceIndex (_: ^t when ^t: null and ^t: struct, _, _impl: Default1) = ()

static member inline Invoke (slice: '``Collection<'T>``) (source: '``Collection<'T>``) : 'Index option =
let inline call_2 (a: ^a, b: ^b, n) = ((^a or ^b) : (static member TryFindLastSliceIndex : _*_*_ -> _) b, n, a)
let inline call (a: 'a, b: 'b, n) = call_2 (a, b, n)
call (Unchecked.defaultof<TryFindLastSliceIndex>, source, slice)

#endif
31 changes: 31 additions & 0 deletions src/FSharpPlus/Extensions/Array.fs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,37 @@ module Array =

let index = Internals.FindSliceIndex.arrayImpl slice source
if index = -1 then None else Some index

/// <summary>
/// Returns the index of the last occurrence of the specified slice in the source.
/// Note: this is unsafe and will throw ArgumentException when the specified slice is not found.
/// </summary>
/// <returns>
/// The index of the slice or <c>None</c>.
/// </returns>
let findLastSliceIndex (slice: _ []) (source: _ []) =
raiseIfNull (nameof(slice)) slice
raiseIfNull (nameof(source)) source

let index = Internals.FindLastSliceIndex.arrayImpl slice source
if index = -1 then
ArgumentException("The specified slice was not found in the sequence.") |> raise
else
index

/// <summary>
/// Returns the index of the last occurrence of the specified slice in the source.
/// Returns <c>None</c> if not found.
/// </summary>
/// <returns>
/// The index of the slice or <c>None</c>.
/// </returns>
let tryFindLastSliceIndex (slice: _ []) (source: _ []) =
raiseIfNull (nameof(slice)) slice
raiseIfNull (nameof(source)) source

let index = Internals.FindLastSliceIndex.arrayImpl slice source
if index = -1 then None else Some index
#endif

/// <summary>
Expand Down
27 changes: 27 additions & 0 deletions src/FSharpPlus/Extensions/List.fs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,33 @@ module List =
let tryFindSliceIndex (slice: _ list) (source: _ list) =
let index = Internals.FindSliceIndex.listImpl slice source
if index = -1 then None else Some index

/// <summary>
/// Gets the index of the last occurrence of the specified slice in the source.
/// </summary>
/// <exception cref="System.ArgumentException">
/// Thrown when the slice was not found in the sequence.
/// </exception>
/// <returns>
/// The index of the slice.
/// </returns>
let findLastSliceIndex (slice: _ list) (source: _ list) =
let index = Internals.FindLastSliceIndex.listImpl slice source
if index = -1 then
ArgumentException("The specified slice was not found in the sequence.") |> raise
else
index

/// <summary>
/// Gets the index of the last occurrence of the specified slice in the source.
/// Returns <c>None</c> if not found.
/// </summary>
/// <returns>
/// The index of the slice or <c>None</c>.
/// </returns>
let tryFindLastSliceIndex (slice: _ list) (source: _ list) =
let index = Internals.FindLastSliceIndex.listImpl slice source
if index = -1 then None else Some index
#endif

/// <summary>
Expand Down
27 changes: 27 additions & 0 deletions src/FSharpPlus/Extensions/ResizeArray.fs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,33 @@ module ResizeArray =
let tryFindSliceIndex (slice: _ []) (source: _ []) =
let index = Internals.FindSliceIndex.arrayImpl slice source
if index = -1 then None else Some index

/// <summary>
/// Returns the index of the last occurrence of the specified slice in the source.
/// </summary>
/// <exception cref="System.ArgumentException">
/// Thrown when the slice was not found in the sequence.
/// </exception>
/// <returns>
/// The index of the slice.
/// </returns>
let findLastSliceIndex (slice: _ []) (source: _ []) =
let index = Internals.FindLastSliceIndex.arrayImpl slice source
if index = -1 then
ArgumentException("The specified slice was not found in the sequence.") |> raise
else
index

/// <summary>
/// Returns the index of the last occurrence of the specified slice in the source.
/// Returns <c>None</c> if not found.
/// </summary>
/// <returns>
/// The index of the slice or <c>None</c>.
/// </returns>
let tryFindLastSliceIndex (slice: _ []) (source: _ []) =
let index = Internals.FindLastSliceIndex.arrayImpl slice source
if index = -1 then None else Some index
#endif

/// <summary>
Expand Down
43 changes: 43 additions & 0 deletions src/FSharpPlus/Extensions/Seq.fs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,49 @@ module Seq =
let index = Internals.FindSliceIndex.arrayImpl (Seq.toArray slice) (Seq.toArray source)
#endif
if index = -1 then None else Some index

/// <summary>
/// Gets the index of the last occurrence of the specified slice in the source.
/// </summary>
/// <remarks>
/// It is assumed that both the slice and the source are finite, otherwise it will not return forever.
/// Both the slice and the source will always be iterated to the end.
/// </remarks>
/// <exception cref="System.ArgumentException">
/// Thrown when the slice was not found in the sequence.
/// </exception>
/// <returns>
/// The index of the slice.
/// </returns>
let findLastSliceIndex (slice: seq<_>) (source: seq<_>) =
#if !FABLE_COMPILER
let index = Internals.FindLastSliceIndex.seqImpl slice source
#else
let index = Internals.FindLastSliceIndex.arrayImpl (Seq.toArray slice) (Seq.toArray source)
#endif
if index = -1 then
ArgumentException("The specified slice was not found in the sequence.") |> raise
else
index

/// <summary>
/// Gets the index of the last occurrence of the specified slice in the source.
/// Returns <c>None</c> if not found.
/// </summary>
/// <remarks>
/// It is assumed that both the slice and the source are finite, otherwise it will not return forever.
/// Both the slice and the source will always be iterated to the end.
/// </remarks>
/// <returns>
/// The index of the slice or <c>None</c>.
/// </returns>
let tryFindLastSliceIndex (slice: seq<_>) (source: seq<_>) =
#if !FABLE_COMPILER
let index = Internals.FindLastSliceIndex.seqImpl slice source
#else
let index = Internals.FindLastSliceIndex.arrayImpl (Seq.toArray slice) (Seq.toArray source)
#endif
if index = -1 then None else Some index
#endif

/// <summary>Choose with access to the index</summary>
Expand Down
27 changes: 27 additions & 0 deletions src/FSharpPlus/Extensions/String.fs
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,22 @@ module String =
ArgumentException("The specified substring was not found in the string.") |> raise
else
index

/// <summary>
/// Returns the index of the last occurrence of the specified slice in the source.
/// </summary>
/// <exception cref="System.ArgumentException">
/// Thrown when the slice was not found in the sequence.
/// </exception>
/// <returns>
/// The index of the slice.
/// </returns>
let findLastSliceIndex (slice: string) (source: string) =
let index = source.LastIndexOf slice
if index = -1 then
ArgumentException("The specified substring was not found in the string.") |> raise
else
index

/// <summary>
/// Returns the index of the first occurrence of the specified slice in the source.
Expand All @@ -243,6 +259,17 @@ module String =
let index = source.IndexOf slice
if index = -1 then None else Some index

/// <summary>
/// Returns the index of the last occurrence of the specified slice in the source.
/// Returns <c>None</c> if not found.
/// </summary>
/// <returns>
/// The index of the slice or <c>None</c>.
/// </returns>
let tryFindLastSliceIndex (slice: string) (source: string) =
let index = source.LastIndexOf slice
if index = -1 then None else Some index

#if !FABLE_COMPILER

/// Converts the given string to an array of Int32 code-points (the actual Unicode Code Point number).
Expand Down
91 changes: 91 additions & 0 deletions src/FSharpPlus/Internals.fs
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,97 @@ module FindSliceIndex =
else go (index + 1)
else -1
go 0

module FindLastSliceIndex =
open System.Collections.Generic
#if !FABLE_COMPILER
open System.Linq
let seqImpl (slice: seq<_>) (source: seq<_>) =
let cache = Queue<_>()
// we assume the slice is finite (otherwise it cannot be searched)
let slice = slice |> Seq.toArray
use sourceEnumerator = source.GetEnumerator()
// we also assume the source is finite
let rec go last index =
if sourceEnumerator.MoveNext() then
cache.Enqueue sourceEnumerator.Current
if cache.Count = slice.Length then
let last = if cache.SequenceEqual slice then index - slice.Length + 1 else last
cache.Dequeue() |> ignore
go last (index + 1)
else go last (index + 1)
else last
go -1 0
let sequenceEqual (a: _ seq) (b: _ seq) = a.SequenceEqual b
#else
let internal sequenceEqual (a: _ seq) (b: _ seq) :bool = Seq.compareWith Operators.compare a b = 0
module internal Q=
rzikm marked this conversation as resolved.
Show resolved Hide resolved
type queue<'a> = | Queue of 'a list * 'a list

let empty = Queue([], [])

let enqueue q e = match q with | Queue(fs, bs) -> Queue(e :: fs, bs)

let dequeue =
function
| Queue([], []) as q -> None, q
| Queue(fs, b :: bs) -> Some b, Queue(fs, bs)
| Queue(fs, []) ->
let bs = List.rev fs
Some bs.Head, Queue([], bs.Tail)
let toSeq =
function
| Queue([], []) -> Seq.empty
| Queue(fs, bs) -> bs @ List.rev fs |> List.toSeq
let length =
function
| Queue([], []) -> 0
| Queue(fs, bs) -> List.length bs + List.length fs
open System.Collections
type Queue<'T> () =
let mutable q : Q.queue<'T> = Q.empty
interface IEnumerable<'T> with
member _.GetEnumerator () = let s = Q.toSeq q in s.GetEnumerator()
interface IEnumerable with
member _.GetEnumerator () = let s = Q.toSeq q in s.GetEnumerator() :> IEnumerator
member _.Enqueue (v) = q <- Q.enqueue q v
member _.Dequeue () =
let (dequeued, next) = Q.dequeue q in q <- next
match dequeued with | Some v -> v | None -> failwith "Empty queue!"
rzikm marked this conversation as resolved.
Show resolved Hide resolved
member _.Count = Q.length q
#endif

let listImpl (slice: _ list) (source: _ list) =
let cache = Queue<_>()
// List.length is O(n)
let sliceLength = slice.Length
let rec go last index source =
match source with
| h :: t ->
cache.Enqueue h
if cache.Count = sliceLength then
let last = if sequenceEqual cache slice then index - sliceLength + 1 else last
cache.Dequeue() |> ignore
go last (index + 1) t
else go last (index + 1) t
| [] -> last
go -1 0 source

let arrayImpl (slice: _ []) (source: _ []) =
let revSlice = slice |> Array.rev
let cache = Queue<_>()
let rec go index =
if index >= 0 then
let h = source.[index]
cache.Enqueue h
if cache.Count = slice.Length then
if sequenceEqual cache revSlice then index
else
cache.Dequeue() |> ignore
go (index - 1)
else go (index - 1)
else -1
go (source.Length - 1)
#endif

#if FABLE_COMPILER
Expand Down
Loading
Loading