Skip to content

Commit 38a0c8b

Browse files
FrassleKevinRansom
andauthored
Add Map.Change (#6876)
* Add Map.Update More efficent way of updating a value in Map. * Update SurfaceArea * Fix expected surfacearea * Empty and one element map update tests * update -> change * Support adding or removal in change * Add xml docs * Update baselines * AreFalse -> IsFalse * Fix test * Test old elements are still present after Change removed one * Add Experimental attribute * Add more experimental attributes * Revert whitespace changes * Check existing items still present Co-authored-by: Kevin Ransom (msft) <codecutter@hotmail.com>
1 parent 37d0ccc commit 38a0c8b

6 files changed

Lines changed: 131 additions & 1 deletion

File tree

src/fsharp/FSharp.Core/map.fs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,43 @@ module MapTree =
218218
mk l sk sv r'
219219
else rebalance l k2 v2 (remove comparer k r)
220220

221+
let rec change (comparer: IComparer<'Key>) k (u: 'Value option -> 'Value option) (m: MapTree<'Key, 'Value>) =
222+
match m with
223+
| MapEmpty ->
224+
match u None with
225+
| None -> m
226+
| Some v -> MapOne (k, v)
227+
| MapOne (k2, v2) ->
228+
let c = comparer.Compare(k, k2)
229+
if c < 0 then
230+
match u None with
231+
| None -> m
232+
| Some v -> MapNode (k, v, MapEmpty, m, 2)
233+
elif c = 0 then
234+
match u (Some v2) with
235+
| None -> MapEmpty
236+
| Some v -> MapOne (k, v)
237+
else
238+
match u None with
239+
| None -> m
240+
| Some v -> MapNode (k, v, m, MapEmpty, 2)
241+
| MapNode (k2, v2, l, r, h) ->
242+
let c = comparer.Compare(k, k2)
243+
if c < 0 then
244+
rebalance (change comparer k u l) k2 v2 r
245+
elif c = 0 then
246+
match u (Some v2) with
247+
| None ->
248+
match l, r with
249+
| MapEmpty, _ -> r
250+
| _, MapEmpty -> l
251+
| _ ->
252+
let sk, sv, r' = spliceOutSuccessor r
253+
mk l sk sv r'
254+
| Some v -> MapNode (k, v, l, r, h)
255+
else
256+
rebalance l k2 v2 (change comparer k u r)
257+
221258
let rec mem (comparer: IComparer<'Key>) k (m: MapTree<'Key, 'Value>) =
222259
match m with
223260
| MapEmpty -> false
@@ -512,6 +549,10 @@ type Map<[<EqualityConditionalOn>]'Key, [<EqualityConditionalOn; ComparisonCondi
512549
#endif
513550
new Map<'Key, 'Value>(comparer, MapTree.add comparer key value tree)
514551

552+
[<Experimental("Experimental library feature, requires '--langversion:preview'")>]
553+
member m.Change(key, f) : Map<'Key, 'Value> =
554+
new Map<'Key, 'Value>(comparer, MapTree.change comparer key f tree)
555+
515556
[<DebuggerBrowsable(DebuggerBrowsableState.Never)>]
516557
member m.IsEmpty = MapTree.isEmpty tree
517558

@@ -729,6 +770,11 @@ module Map =
729770
let add key value (table: Map<_, _>) =
730771
table.Add (key, value)
731772

773+
[<Experimental("Experimental library feature, requires '--langversion:preview'")>]
774+
[<CompiledName("Change")>]
775+
let change key f (table: Map<_, _>) =
776+
table.Change (key, f)
777+
732778
[<CompiledName("Find")>]
733779
let find key (table: Map<_, _>) =
734780
table.[key]

src/fsharp/FSharp.Core/map.fsi

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ namespace Microsoft.FSharp.Collections
2222
/// <returns>The resulting map.</returns>
2323
member Add: key:'Key * value:'Value -> Map<'Key,'Value>
2424

25+
/// <summary>Returns a new map with the value stored under key changed according to f.</summary>
26+
/// <param name="key">The input key.</param>
27+
/// <param name="f">The change function.</param>
28+
/// <returns>The resulting map.</returns>
29+
[<Experimental("Experimental library feature, requires '--langversion:preview'")>]
30+
member Change: key:'Key * f:('Value option -> 'Value option) -> Map<'Key,'Value>
31+
2532
/// <summary>Returns true if there are no bindings in the map.</summary>
2633
member IsEmpty: bool
2734

@@ -86,6 +93,15 @@ namespace Microsoft.FSharp.Collections
8693
[<CompiledName("Add")>]
8794
val add: key:'Key -> value:'T -> table:Map<'Key,'T> -> Map<'Key,'T>
8895

96+
/// <summary>Returns a new map with the value stored under key changed according to f.</summary>
97+
/// <param name="key">The input key.</param>
98+
/// <param name="f">The change function.</param>
99+
/// <param name="table">The input map.</param>
100+
/// <returns>The resulting map.</returns>
101+
[<Experimental("Experimental library feature, requires '--langversion:preview'")>]
102+
[<CompiledName("Change")>]
103+
val change: key:'Key -> f:('T option -> 'T option) -> table:Map<'Key,'T> -> Map<'Key,'T>
104+
89105
/// <summary>Returns a new map made from the given bindings.</summary>
90106
/// <param name="elements">The input list of key/value pairs.</param>
91107
/// <returns>The resulting map.</returns>
@@ -263,4 +279,4 @@ namespace Microsoft.FSharp.Collections
263279

264280
/// <summary>The number of bindings in the map.</summary>
265281
[<CompiledName("Count")>]
266-
val count: table:Map<'Key,'T> -> int
282+
val count: table:Map<'Key,'T> -> int

tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/MapModule.fs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,40 @@ type MapModule() =
6161
Assert.AreEqual(resultExt.[2], "dup")
6262

6363

64+
()
65+
66+
[<Test>]
67+
member this.Change() =
68+
69+
let a = (Map.ofArray [|(1,1);(2,4);(3,9)|])
70+
let b = Map.change 4 (fun current -> Assert.AreEqual(current, None); Some 16) a
71+
Assert.AreEqual(b.[1], 1)
72+
Assert.AreEqual(b.[2], 4)
73+
Assert.AreEqual(b.[3], 9)
74+
Assert.AreEqual(b.[4], 16)
75+
let c = Map.change 4 (fun current -> Assert.AreEqual(current, Some 16); Some 25) b
76+
Assert.AreEqual(b.[1], 1)
77+
Assert.AreEqual(b.[2], 4)
78+
Assert.AreEqual(b.[3], 9)
79+
Assert.AreEqual(c.[4], 25)
80+
81+
// empty Map
82+
let eptMap = Map.empty
83+
let resultEpt = Map.change 1 (fun current -> Assert.AreEqual(current, None); Some "a") eptMap
84+
Assert.AreEqual(resultEpt.[1], "a")
85+
86+
// One-element Map
87+
let oeleMap = Map.ofSeq [(1, "one")]
88+
let resultOele = Map.change 7 (fun current -> Assert.AreEqual(current, None); Some "seven") oeleMap
89+
Assert.AreEqual(resultOele.[7], "seven")
90+
91+
// Remove element
92+
let resultRm = Map.change 1 (fun current -> Assert.AreEqual(current, Some 1); None) c
93+
Assert.IsFalse(resultRm.ContainsKey 1)
94+
Assert.AreEqual(resultRm.[2], 4)
95+
Assert.AreEqual(resultRm.[3], 9)
96+
Assert.AreEqual(resultRm.[4], 25)
97+
6498
()
6599

66100
[<Test>]

tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/MapType.fs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ type MapType() =
246246
let ae = e.Add(1,"Monday")
247247
Assert.AreEqual(ae.[1], "Monday")
248248

249+
[<Test>]
249250
member this.Add() =
250251

251252
let a = (Map.ofArray [|(1,1);(2,4);(3,9)|])
@@ -256,6 +257,34 @@ type MapType() =
256257
let e = Map.empty<int,string>
257258
let ae = e.Add(1,"Monday")
258259
Assert.AreEqual(ae.[1], "Monday")
260+
261+
[<Test>]
262+
member this.Change() =
263+
264+
let a = (Map.ofArray [|(1,1);(2,4);(3,9)|])
265+
let b = a.Change(4, fun current -> Assert.AreEqual(current, None); Some 16)
266+
Assert.AreEqual(b.[1], 1)
267+
Assert.AreEqual(b.[2], 4)
268+
Assert.AreEqual(b.[3], 9)
269+
Assert.AreEqual(b.[4], 16)
270+
let c = b.Change(4, fun current -> Assert.AreEqual(current, Some 16); Some 25)
271+
Assert.AreEqual(b.[1], 1)
272+
Assert.AreEqual(b.[2], 4)
273+
Assert.AreEqual(b.[3], 9)
274+
Assert.AreEqual(c.[4], 25)
275+
276+
let e = Map.empty<int,string>
277+
let ue = e.Change(1, fun current -> Assert.AreEqual(current, None); Some "Monday")
278+
Assert.AreEqual(ue.[1], "Monday")
279+
280+
let uo = ue.Change(1, fun current -> Assert.AreEqual(current, Some "Monday"); Some "Tuesday")
281+
Assert.AreEqual(uo.[1], "Tuesday")
282+
283+
let resultRm = c.Change(1, fun current -> Assert.AreEqual(current, Some 1); None)
284+
Assert.IsFalse(resultRm.ContainsKey 1)
285+
Assert.AreEqual(resultRm.[2], 4)
286+
Assert.AreEqual(resultRm.[3], 9)
287+
Assert.AreEqual(resultRm.[4], 25)
259288

260289
[<Test>]
261290
member this.ContainsKey() =

tests/FSharp.Core.UnitTests/SurfaceArea.coreclr.fs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ Microsoft.FSharp.Collections.FSharpMap`2[TKey,TValue]: Int32 get_Count()
218218
Microsoft.FSharp.Collections.FSharpMap`2[TKey,TValue]: Microsoft.FSharp.Collections.FSharpMap`2[TKey,TValue] Add(TKey, TValue)
219219
Microsoft.FSharp.Collections.FSharpMap`2[TKey,TValue]: Microsoft.FSharp.Collections.FSharpMap`2[TKey,TValue] Remove(TKey)
220220
Microsoft.FSharp.Collections.FSharpMap`2[TKey,TValue]: Microsoft.FSharp.Core.FSharpOption`1[TValue] TryFind(TKey)
221+
Microsoft.FSharp.Collections.FSharpMap`2[TKey,TValue]: Microsoft.FSharp.Collections.FSharpMap`2[TKey,TValue] Change(TKey, Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.FSharpOption`1[TValue],Microsoft.FSharp.Core.FSharpOption`1[TValue]])
221222
Microsoft.FSharp.Collections.FSharpMap`2[TKey,TValue]: System.String ToString()
222223
Microsoft.FSharp.Collections.FSharpMap`2[TKey,TValue]: TValue Item [TKey]
223224
Microsoft.FSharp.Collections.FSharpMap`2[TKey,TValue]: TValue get_Item(TKey)
@@ -374,6 +375,7 @@ Microsoft.FSharp.Collections.MapModule: TResult Pick[TKey,T,TResult](Microsoft.F
374375
Microsoft.FSharp.Collections.MapModule: TState FoldBack[TKey,T,TState](Microsoft.FSharp.Core.FSharpFunc`2[TKey,Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[TState,TState]]], Microsoft.FSharp.Collections.FSharpMap`2[TKey,T], TState)
375376
Microsoft.FSharp.Collections.MapModule: TState Fold[TKey,T,TState](Microsoft.FSharp.Core.FSharpFunc`2[TState,Microsoft.FSharp.Core.FSharpFunc`2[TKey,Microsoft.FSharp.Core.FSharpFunc`2[T,TState]]], TState, Microsoft.FSharp.Collections.FSharpMap`2[TKey,T])
376377
Microsoft.FSharp.Collections.MapModule: Void Iterate[TKey,T](Microsoft.FSharp.Core.FSharpFunc`2[TKey,Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.Unit]], Microsoft.FSharp.Collections.FSharpMap`2[TKey,T])
378+
Microsoft.FSharp.Collections.MapModule: Microsoft.FSharp.Collections.FSharpMap`2[TKey,T] Change[TKey,T](TKey, Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.FSharpOption`1[T],Microsoft.FSharp.Core.FSharpOption`1[T]], Microsoft.FSharp.Collections.FSharpMap`2[TKey,T])
377379
Microsoft.FSharp.Collections.SeqModule: Boolean Contains[T](T, System.Collections.Generic.IEnumerable`1[T])
378380
Microsoft.FSharp.Collections.SeqModule: Boolean Exists2[T1,T2](Microsoft.FSharp.Core.FSharpFunc`2[T1,Microsoft.FSharp.Core.FSharpFunc`2[T2,System.Boolean]], System.Collections.Generic.IEnumerable`1[T1], System.Collections.Generic.IEnumerable`1[T2])
379381
Microsoft.FSharp.Collections.SeqModule: Boolean Exists[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], System.Collections.Generic.IEnumerable`1[T])

tests/FSharp.Core.UnitTests/SurfaceArea.net40.fs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,8 @@ Microsoft.FSharp.Collections.FSharpMap`2[TKey,TValue]: Int32 get_Count()
218218
Microsoft.FSharp.Collections.FSharpMap`2[TKey,TValue]: Microsoft.FSharp.Collections.FSharpMap`2[TKey,TValue] Add(TKey, TValue)
219219
Microsoft.FSharp.Collections.FSharpMap`2[TKey,TValue]: Microsoft.FSharp.Collections.FSharpMap`2[TKey,TValue] Remove(TKey)
220220
Microsoft.FSharp.Collections.FSharpMap`2[TKey,TValue]: Microsoft.FSharp.Core.FSharpOption`1[TValue] TryFind(TKey)
221+
Microsoft.FSharp.Collections.FSharpMap`2[TKey,TValue]: Boolean TryGetValue(TKey, TValue ByRef)
222+
Microsoft.FSharp.Collections.FSharpMap`2[TKey,TValue]: Microsoft.FSharp.Collections.FSharpMap`2[TKey,TValue] Change(TKey, Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.FSharpOption`1[TValue],Microsoft.FSharp.Core.FSharpOption`1[TValue]])
221223
Microsoft.FSharp.Collections.FSharpMap`2[TKey,TValue]: System.String ToString()
222224
Microsoft.FSharp.Collections.FSharpMap`2[TKey,TValue]: TValue Item [TKey]
223225
Microsoft.FSharp.Collections.FSharpMap`2[TKey,TValue]: TValue get_Item(TKey)
@@ -369,6 +371,7 @@ Microsoft.FSharp.Collections.MapModule: System.Collections.Generic.IEnumerable`1
369371
Microsoft.FSharp.Collections.MapModule: System.Tuple`2[Microsoft.FSharp.Collections.FSharpMap`2[TKey,T],Microsoft.FSharp.Collections.FSharpMap`2[TKey,T]] Partition[TKey,T](Microsoft.FSharp.Core.FSharpFunc`2[TKey,Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean]], Microsoft.FSharp.Collections.FSharpMap`2[TKey,T])
370372
Microsoft.FSharp.Collections.MapModule: System.Tuple`2[TKey,T][] ToArray[TKey,T](Microsoft.FSharp.Collections.FSharpMap`2[TKey,T])
371373
Microsoft.FSharp.Collections.MapModule: T Find[TKey,T](TKey, Microsoft.FSharp.Collections.FSharpMap`2[TKey,T])
374+
Microsoft.FSharp.Collections.MapModule: Microsoft.FSharp.Collections.FSharpMap`2[TKey,T] Change[TKey,T](TKey, Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.FSharpOption`1[T],Microsoft.FSharp.Core.FSharpOption`1[T]], Microsoft.FSharp.Collections.FSharpMap`2[TKey,T])
372375
Microsoft.FSharp.Collections.MapModule: TKey FindKey[TKey,T](Microsoft.FSharp.Core.FSharpFunc`2[TKey,Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean]], Microsoft.FSharp.Collections.FSharpMap`2[TKey,T])
373376
Microsoft.FSharp.Collections.MapModule: TResult Pick[TKey,T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[TKey,Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpOption`1[TResult]]], Microsoft.FSharp.Collections.FSharpMap`2[TKey,T])
374377
Microsoft.FSharp.Collections.MapModule: TState FoldBack[TKey,T,TState](Microsoft.FSharp.Core.FSharpFunc`2[TKey,Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[TState,TState]]], Microsoft.FSharp.Collections.FSharpMap`2[TKey,T], TState)

0 commit comments

Comments
 (0)