Skip to content

Commit

Permalink
Add (try)FindLastSliceIndex (#581)
Browse files Browse the repository at this point in the history
  • Loading branch information
rzikm authored Jan 6, 2024
1 parent 7552eae commit df90803
Show file tree
Hide file tree
Showing 12 changed files with 340 additions and 4 deletions.
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 =
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 -> invalidOp "Empty queue!"
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

0 comments on commit df90803

Please sign in to comment.