From cbc296a419fda831478e26ce99f4072886f1228c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 04:03:02 +0000 Subject: [PATCH 1/2] feat(stdlib): add collections module bindings (Counter, defaultdict, deque, OrderedDict) Adds F# bindings for the most commonly used classes in Python's collections module: - Counter<'T>: count hashable objects; supports most_common, elements, total, update, subtract; static Counter.ofSeq factory - defaultdict<'TKey, 'TValue>: dict with callable factory for missing keys; includes contains helper to check key presence without invoking factory - deque<'T>: O(1) double-ended queue with append/appendleft, pop/popleft, rotate, and optional bounded maxlen; static ofSeq and withMaxlen factories - OrderedDict<'TKey, 'TValue>: dict subclass with move_to_end and order-sensitive popitem Also adds 35 tests covering construction, mutation, and edge cases for all four types. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Fable.Python.fsproj | 1 + src/stdlib/Collections.fs | 264 ++++++++++++++++++++++++++++++++++ test/Fable.Python.Test.fsproj | 1 + test/TestCollections.fs | 231 +++++++++++++++++++++++++++++ 4 files changed, 497 insertions(+) create mode 100644 src/stdlib/Collections.fs create mode 100644 test/TestCollections.fs diff --git a/src/Fable.Python.fsproj b/src/Fable.Python.fsproj index 3dff498..fc91a6f 100644 --- a/src/Fable.Python.fsproj +++ b/src/Fable.Python.fsproj @@ -23,6 +23,7 @@ + diff --git a/src/stdlib/Collections.fs b/src/stdlib/Collections.fs new file mode 100644 index 0000000..d5d61e6 --- /dev/null +++ b/src/stdlib/Collections.fs @@ -0,0 +1,264 @@ +/// Type bindings for Python collections module: https://docs.python.org/3/library/collections.html +module Fable.Python.Collections + +open Fable.Core + +// fsharplint:disable MemberNames + +// ============================================================================ +// Counter +// ============================================================================ + +/// A dict subclass for counting hashable objects. +/// Elements are stored as dictionary keys and their counts are stored as values. +/// Counts are allowed to be any integer value including zero or negative counts. +/// See https://docs.python.org/3/library/collections.html#collections.Counter +[] +type Counter<'T>() = + /// Get the count for key; missing keys return 0 (unlike a regular dict) + [] + member _.Item(key: 'T) : int = nativeOnly + + /// Return elements and their counts as key-value pairs + member _.items() : seq<'T * int> = nativeOnly + + /// Return an iterator over elements, repeating each as many times as its count. + /// Elements with counts <= 0 are not included. + /// See https://docs.python.org/3/library/collections.html#collections.Counter.elements + member _.elements() : seq<'T> = nativeOnly + + /// Return the n most common elements and their counts (most common first). + /// If n is omitted, return all elements in counter order. + /// See https://docs.python.org/3/library/collections.html#collections.Counter.most_common + member _.most_common() : seq<'T * int> = nativeOnly + + /// Return the n most common elements and their counts (most common first). + /// See https://docs.python.org/3/library/collections.html#collections.Counter.most_common + [] + member _.most_common(n: int) : seq<'T * int> = nativeOnly + + /// Return the total of all counts (requires Python 3.10+). + /// See https://docs.python.org/3/library/collections.html#collections.Counter.total + member _.total() : int = nativeOnly + + /// Add counts from the iterable or mapping; count becomes sum of old and new counts. + /// See https://docs.python.org/3/library/collections.html#collections.Counter.update + member _.update(iterable: 'T seq) : unit = nativeOnly + + /// Subtract counts from the iterable or mapping; count becomes difference. + /// Counts can become negative. + /// See https://docs.python.org/3/library/collections.html#collections.Counter.subtract + member _.subtract(iterable: 'T seq) : unit = nativeOnly + + /// Return a Counter from a sequence of elements. + /// See https://docs.python.org/3/library/collections.html#collections.Counter + [] + static member ofSeq(iterable: 'T seq) : Counter<'T> = nativeOnly + +// ============================================================================ +// defaultdict +// ============================================================================ + +/// A dict subclass that calls a factory to supply missing values. +/// When a key is not found, the factory function (called with no arguments) +/// is called to produce a new value, which is then stored and returned. +/// If the factory is not set (None), missing keys raise KeyError as normal. +/// See https://docs.python.org/3/library/collections.html#collections.defaultdict +[] +type defaultdict<'TKey, 'TValue>(defaultFactory: unit -> 'TValue) = + /// Get or set the value for key; missing keys invoke the factory + [] + member _.Item(key: 'TKey) : 'TValue = nativeOnly + + /// Set value for key + [] + member _.set(key: 'TKey, value: 'TValue) : unit = nativeOnly + + /// Return key-value pairs + member _.items() : seq<'TKey * 'TValue> = nativeOnly + + /// Return keys + member _.keys() : seq<'TKey> = nativeOnly + + /// Return values + member _.values() : seq<'TValue> = nativeOnly + + /// Return value for key if present, otherwise None. + /// Does NOT invoke the factory. + member _.get(key: 'TKey) : 'TValue option = nativeOnly + + /// Return value for key if present, otherwise defaultValue. + /// Does NOT invoke the factory. + [] + member _.get(key: 'TKey, defaultValue: 'TValue) : 'TValue = nativeOnly + + /// If key is in the dict, return its value. + /// If not, insert key with the factory's value and return that value. + member _.setdefault(key: 'TKey) : 'TValue = nativeOnly + + /// Remove and return the value for key, or raise KeyError. + member _.pop(key: 'TKey) : 'TValue = nativeOnly + + /// Remove and return the value for key, or return defaultValue. + [] + member _.pop(key: 'TKey, defaultValue: 'TValue) : 'TValue = nativeOnly + + /// Merge another dict into this one + member _.update(other: System.Collections.Generic.IDictionary<'TKey, 'TValue>) : unit = nativeOnly + + /// Remove all items + member _.clear() : unit = nativeOnly + + /// Return a shallow copy + member _.copy() : defaultdict<'TKey, 'TValue> = nativeOnly + + /// Check if a key is present (does NOT invoke factory) + [] + member _.contains(key: 'TKey) : bool = nativeOnly + +// ============================================================================ +// deque +// ============================================================================ + +/// A double-ended queue with O(1) appends and pops from either end. +/// If maxlen is set, the deque is bounded to that maximum length; items are +/// discarded from the opposite end when the bound is reached. +/// See https://docs.python.org/3/library/collections.html#collections.deque +[] +type deque<'T>() = + /// Number of elements in the deque + [] + member _.length() : int = nativeOnly + + /// Get element at index + [] + member _.Item(index: int) : 'T = nativeOnly + + /// Maximum length of the deque, or None if unbounded + member _.maxlen : int option = nativeOnly + + /// Add item to the right end + member _.append(item: 'T) : unit = nativeOnly + + /// Add item to the left end + member _.appendleft(item: 'T) : unit = nativeOnly + + /// Remove and return item from the right end + member _.pop() : 'T = nativeOnly + + /// Remove and return item from the left end + member _.popleft() : 'T = nativeOnly + + /// Extend the right side of the deque by appending elements from iterable + member _.extend(iterable: 'T seq) : unit = nativeOnly + + /// Extend the left side of the deque by appending elements from iterable. + /// Note: each element is appended to the left, reversing the iterable order. + member _.extendleft(iterable: 'T seq) : unit = nativeOnly + + /// Rotate the deque n steps to the right. If n is negative, rotate left. + member _.rotate(n: int) : unit = nativeOnly + + /// Count the number of occurrences of value + [] + member _.count(value: 'T) : int = nativeOnly + + /// Return the position of value (raise ValueError if not found) + member _.index(value: 'T) : int = nativeOnly + + /// Insert value before position i + member _.insert(i: int, value: 'T) : unit = nativeOnly + + /// Remove the first occurrence of value (raise ValueError if not found) + member _.remove(value: 'T) : unit = nativeOnly + + /// Reverse the deque in-place + member _.reverse() : unit = nativeOnly + + /// Remove all elements + member _.clear() : unit = nativeOnly + + /// Return a shallow copy + member _.copy() : deque<'T> = nativeOnly + + /// Create a deque from a sequence + [] + static member ofSeq(iterable: 'T seq) : deque<'T> = nativeOnly + + /// Create a bounded deque from a sequence with maximum length + [] + static member ofSeq(iterable: 'T seq, maxlen: int) : deque<'T> = nativeOnly + + /// Create an empty bounded deque with maximum length + [] + static member withMaxlen(maxlen: int) : deque<'T> = nativeOnly + +// ============================================================================ +// OrderedDict +// ============================================================================ + +/// A dict subclass that remembers insertion order. Since Python 3.7, all dicts +/// maintain insertion order, but OrderedDict has a few extra features: +/// `move_to_end` and order-sensitive equality. +/// See https://docs.python.org/3/library/collections.html#collections.OrderedDict +[] +type OrderedDict<'TKey, 'TValue>() = + /// Get or set value for key + [] + member _.Item(key: 'TKey) : 'TValue = nativeOnly + + /// Set value for key + [] + member _.set(key: 'TKey, value: 'TValue) : unit = nativeOnly + + /// Return key-value pairs in insertion order + member _.items() : seq<'TKey * 'TValue> = nativeOnly + + /// Return keys in insertion order + member _.keys() : seq<'TKey> = nativeOnly + + /// Return values in insertion order + member _.values() : seq<'TValue> = nativeOnly + + /// Get value for key, or None if missing + member _.get(key: 'TKey) : 'TValue option = nativeOnly + + /// Get value for key, or defaultValue if missing + [] + member _.get(key: 'TKey, defaultValue: 'TValue) : 'TValue = nativeOnly + + /// Remove and return the value for key (or raise KeyError) + member _.pop(key: 'TKey) : 'TValue = nativeOnly + + /// Remove and return the value for key, or return defaultValue + [] + member _.pop(key: 'TKey, defaultValue: 'TValue) : 'TValue = nativeOnly + + /// Move key to the end. If last is False, move to the beginning. + /// See https://docs.python.org/3/library/collections.html#collections.OrderedDict.move_to_end + member _.move_to_end(key: 'TKey) : unit = nativeOnly + + /// Move key to the end (last=True) or beginning (last=False). + [] + member _.move_to_end(key: 'TKey, last: bool) : unit = nativeOnly + + /// Remove and return a (key, value) pair. last=True removes from the end. + /// See https://docs.python.org/3/library/collections.html#collections.OrderedDict.popitem + member _.popitem() : 'TKey * 'TValue = nativeOnly + + /// Remove and return from end (last=True) or beginning (last=False). + [] + member _.popitem(last: bool) : 'TKey * 'TValue = nativeOnly + + /// Merge another dict into this one + member _.update(other: System.Collections.Generic.IDictionary<'TKey, 'TValue>) : unit = nativeOnly + + /// Remove all items + member _.clear() : unit = nativeOnly + + /// Return a shallow copy + member _.copy() : OrderedDict<'TKey, 'TValue> = nativeOnly + + /// Check if key is present + [] + member _.contains(key: 'TKey) : bool = nativeOnly diff --git a/test/Fable.Python.Test.fsproj b/test/Fable.Python.Test.fsproj index 441f8a7..69c1f74 100644 --- a/test/Fable.Python.Test.fsproj +++ b/test/Fable.Python.Test.fsproj @@ -17,6 +17,7 @@ + diff --git a/test/TestCollections.fs b/test/TestCollections.fs new file mode 100644 index 0000000..931e48c --- /dev/null +++ b/test/TestCollections.fs @@ -0,0 +1,231 @@ +module Fable.Python.Tests.Collections + +open Fable.Python.Testing +open Fable.Python.Collections + +// ============================================================================ +// Counter tests +// ============================================================================ + +[] +let ``Counter: empty counter has zero count for missing key`` () = + let c = Counter() + c.Item("x") |> equal 0 + +[] +let ``Counter: ofSeq counts elements`` () = + let c = Counter.ofSeq [ "a"; "b"; "a"; "c"; "a"; "b" ] + c.Item("a") |> equal 3 + c.Item("b") |> equal 2 + c.Item("c") |> equal 1 + +[] +let ``Counter: missing key returns 0`` () = + let c = Counter.ofSeq [ "a"; "b" ] + c.Item("z") |> equal 0 + +[] +let ``Counter: most_common returns all elements sorted by count`` () = + let c = Counter.ofSeq [ "a"; "b"; "a"; "c"; "a"; "b" ] + let top = c.most_common() |> Seq.head + top |> equal ("a", 3) + +[] +let ``Counter: most_common n returns top n elements`` () = + let c = Counter.ofSeq [ "a"; "b"; "a"; "c"; "a"; "b" ] + let topTwo = c.most_common(2) |> Seq.toList + topTwo |> List.length |> equal 2 + topTwo |> List.head |> equal ("a", 3) + +[] +let ``Counter: elements returns repeated sequence`` () = + let c = Counter.ofSeq [ "a"; "a"; "b" ] + let elems = c.elements() |> Seq.toList |> List.sort + elems |> equal [ "a"; "a"; "b" ] + +[] +let ``Counter: total sums all counts`` () = + let c = Counter.ofSeq [ "a"; "b"; "a"; "c" ] + c.total() |> equal 4 + +[] +let ``Counter: update adds counts`` () = + let c = Counter.ofSeq [ "a"; "b" ] + c.update([ "a"; "c" ]) + c.Item("a") |> equal 2 + c.Item("c") |> equal 1 + +[] +let ``Counter: subtract reduces counts`` () = + let c = Counter.ofSeq [ "a"; "a"; "b" ] + c.subtract([ "a" ]) + c.Item("a") |> equal 1 + +// ============================================================================ +// defaultdict tests +// ============================================================================ + +[] +let ``defaultdict: missing key invokes factory`` () = + let d = defaultdict>(fun () -> ResizeArray()) + let list = d.Item("key") + list.Count |> equal 0 + +[] +let ``defaultdict: factory creates separate instances`` () = + let d = defaultdict>(fun () -> ResizeArray()) + let list1 = d.Item("a") + list1.Add(1) + let list2 = d.Item("b") + list2.Count |> equal 0 + +[] +let ``defaultdict: int factory starts at zero`` () = + let d = defaultdict(fun () -> 0) + d.Item("key") |> equal 0 + +[] +let ``defaultdict: get returns None for missing key without invoking factory`` () = + let mutable factoryCalled = false + let d = defaultdict(fun () -> factoryCalled <- true; 0) + let result = d.get("missing") + result |> equal None + factoryCalled |> equal false + +[] +let ``defaultdict: get with default returns default for missing key`` () = + let d = defaultdict(fun () -> 0) + d.get("missing", 42) |> equal 42 + +[] +let ``defaultdict: contains returns false for missing key`` () = + let d = defaultdict(fun () -> 0) + d.contains("key") |> equal false + +[] +let ``defaultdict: contains returns true after access`` () = + let d = defaultdict(fun () -> 99) + let _ = d.Item("key") + d.contains("key") |> equal true + +// ============================================================================ +// deque tests +// ============================================================================ + +[] +let ``deque: empty deque has length 0`` () = + let d = deque() + d.length() |> equal 0 + +[] +let ``deque: ofSeq creates deque from sequence`` () = + let d = deque.ofSeq [ 1; 2; 3 ] + d.length() |> equal 3 + +[] +let ``deque: append adds to right`` () = + let d = deque.ofSeq [ 1; 2 ] + d.append(3) + d.Item(2) |> equal 3 + +[] +let ``deque: appendleft adds to left`` () = + let d = deque.ofSeq [ 1; 2 ] + d.appendleft(0) + d.Item(0) |> equal 0 + d.length() |> equal 3 + +[] +let ``deque: pop removes from right`` () = + let d = deque.ofSeq [ 1; 2; 3 ] + let v = d.pop() + v |> equal 3 + d.length() |> equal 2 + +[] +let ``deque: popleft removes from left`` () = + let d = deque.ofSeq [ 1; 2; 3 ] + let v = d.popleft() + v |> equal 1 + d.length() |> equal 2 + +[] +let ``deque: rotate shifts elements right`` () = + let d = deque.ofSeq [ 1; 2; 3; 4; 5 ] + d.rotate(2) + d.Item(0) |> equal 4 + d.Item(1) |> equal 5 + +[] +let ``deque: maxlen is None for unbounded deque`` () = + let d = deque.ofSeq [ 1; 2; 3 ] + d.maxlen |> equal None + +[] +let ``deque: withMaxlen creates bounded deque`` () = + let d = deque.withMaxlen(3) + d.append(1) + d.append(2) + d.append(3) + d.append(4) // should push out 1 + d.length() |> equal 3 + d.Item(0) |> equal 2 + +[] +let ``deque: ofSeq with maxlen creates bounded deque`` () = + let d = deque.ofSeq ([ 1; 2; 3; 4; 5 ], 3) + d.length() |> equal 3 + d.maxlen |> equal (Some 3) + +[] +let ``deque: count occurrences`` () = + let d = deque.ofSeq [ 1; 2; 1; 3; 1 ] + d.count(1) |> equal 3 + +// ============================================================================ +// OrderedDict tests +// ============================================================================ + +[] +let ``OrderedDict: preserves insertion order`` () = + let od = OrderedDict() + od.set("a", 1) + od.set("b", 2) + od.set("c", 3) + od.keys() |> Seq.toList |> equal [ "a"; "b"; "c" ] + +[] +let ``OrderedDict: get existing key`` () = + let od = OrderedDict() + od.set("x", 42) + od.Item("x") |> equal 42 + +[] +let ``OrderedDict: get returns None for missing key`` () = + let od = OrderedDict() + od.get("missing") |> equal None + +[] +let ``OrderedDict: move_to_end moves last element`` () = + let od = OrderedDict() + od.set("a", 1) + od.set("b", 2) + od.set("c", 3) + od.move_to_end("a") + od.keys() |> Seq.toList |> equal [ "b"; "c"; "a" ] + +[] +let ``OrderedDict: move_to_end with last false moves to front`` () = + let od = OrderedDict() + od.set("a", 1) + od.set("b", 2) + od.set("c", 3) + od.move_to_end("c", false) + od.keys() |> Seq.toList |> equal [ "c"; "a"; "b" ] + +[] +let ``OrderedDict: contains returns correct result`` () = + let od = OrderedDict() + od.set("a", 1) + od.contains("a") |> equal true + od.contains("b") |> equal false From 8cbb159baa2f31db4d9ab1e0e672fad7710490a3 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 26 Apr 2026 10:03:04 +0200 Subject: [PATCH 2/2] fix(stdlib): address review feedback on collections bindings - defaultdict: switch to `defaultdict.withFactory(...)` static factory. The primary-ctor approach compiled to `defaultdict(default_factory=...)`, which Python rejects: defaultdict only accepts the factory positionally. Tests now actually exercise the factory path. - Counter: add `keys`, `values`, `pop`, `clear`, `contains` so the dict-side of the API is reachable; tighten `most_common()` docstring. - Drop unnecessary `[]` attributes that duplicated Fable defaults (`Counter.most_common(n)`, `deque.count`). - Replace `__setitem__` Emit on `defaultdict.set` / `OrderedDict.set` with `$0[$1] = $2` for cleaner generated Python. - Add iterable-of-pairs `update` overload on defaultdict and OrderedDict. - Rename test cases to start with `test ` so pytest discovers them (previously skipped because the compiled function names didn't match pytest's default discovery pattern). Add `extendleft` reversal test. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/stdlib/Collections.fs | 51 ++++++++++++++---- test/TestCollections.fs | 107 ++++++++++++++++++++++++-------------- 2 files changed, 107 insertions(+), 51 deletions(-) diff --git a/src/stdlib/Collections.fs b/src/stdlib/Collections.fs index d5d61e6..8727336 100644 --- a/src/stdlib/Collections.fs +++ b/src/stdlib/Collections.fs @@ -22,34 +22,51 @@ type Counter<'T>() = /// Return elements and their counts as key-value pairs member _.items() : seq<'T * int> = nativeOnly + /// Return the elements (keys) of the counter + member _.keys() : seq<'T> = nativeOnly + + /// Return the counts (values) of the counter + member _.values() : seq = nativeOnly + /// Return an iterator over elements, repeating each as many times as its count. /// Elements with counts <= 0 are not included. /// See https://docs.python.org/3/library/collections.html#collections.Counter.elements member _.elements() : seq<'T> = nativeOnly - /// Return the n most common elements and their counts (most common first). - /// If n is omitted, return all elements in counter order. + /// Return all elements and their counts, ordered from most common to least common. /// See https://docs.python.org/3/library/collections.html#collections.Counter.most_common member _.most_common() : seq<'T * int> = nativeOnly /// Return the n most common elements and their counts (most common first). /// See https://docs.python.org/3/library/collections.html#collections.Counter.most_common - [] member _.most_common(n: int) : seq<'T * int> = nativeOnly /// Return the total of all counts (requires Python 3.10+). /// See https://docs.python.org/3/library/collections.html#collections.Counter.total member _.total() : int = nativeOnly - /// Add counts from the iterable or mapping; count becomes sum of old and new counts. + /// Add counts from the iterable; count becomes sum of old and new counts. /// See https://docs.python.org/3/library/collections.html#collections.Counter.update member _.update(iterable: 'T seq) : unit = nativeOnly - /// Subtract counts from the iterable or mapping; count becomes difference. - /// Counts can become negative. + /// Subtract counts from the iterable; count becomes difference. Counts can become negative. /// See https://docs.python.org/3/library/collections.html#collections.Counter.subtract member _.subtract(iterable: 'T seq) : unit = nativeOnly + /// Remove and return the count for key, or raise KeyError if missing. + member _.pop(key: 'T) : int = nativeOnly + + /// Remove and return the count for key, or return defaultValue if missing. + [] + member _.pop(key: 'T, defaultValue: int) : int = nativeOnly + + /// Remove all items + member _.clear() : unit = nativeOnly + + /// Check if a key is present in the counter + [] + member _.contains(key: 'T) : bool = nativeOnly + /// Return a Counter from a sequence of elements. /// See https://docs.python.org/3/library/collections.html#collections.Counter [] @@ -62,16 +79,23 @@ type Counter<'T>() = /// A dict subclass that calls a factory to supply missing values. /// When a key is not found, the factory function (called with no arguments) /// is called to produce a new value, which is then stored and returned. -/// If the factory is not set (None), missing keys raise KeyError as normal. +/// +/// Use the `withFactory` static method to attach a factory; the empty +/// constructor produces a defaultdict with no factory (missing keys raise KeyError). /// See https://docs.python.org/3/library/collections.html#collections.defaultdict [] -type defaultdict<'TKey, 'TValue>(defaultFactory: unit -> 'TValue) = +type defaultdict<'TKey, 'TValue>() = + /// Create a defaultdict with the given factory for missing keys. + /// The factory is invoked with no arguments and must return a new value of type 'TValue. + [] + static member withFactory(defaultFactory: unit -> 'TValue) : defaultdict<'TKey, 'TValue> = nativeOnly + /// Get or set the value for key; missing keys invoke the factory [] member _.Item(key: 'TKey) : 'TValue = nativeOnly /// Set value for key - [] + [] member _.set(key: 'TKey, value: 'TValue) : unit = nativeOnly /// Return key-value pairs @@ -106,6 +130,9 @@ type defaultdict<'TKey, 'TValue>(defaultFactory: unit -> 'TValue) = /// Merge another dict into this one member _.update(other: System.Collections.Generic.IDictionary<'TKey, 'TValue>) : unit = nativeOnly + /// Merge an iterable of key-value pairs into this dict + member _.update(items: seq<'TKey * 'TValue>) : unit = nativeOnly + /// Remove all items member _.clear() : unit = nativeOnly @@ -160,7 +187,6 @@ type deque<'T>() = member _.rotate(n: int) : unit = nativeOnly /// Count the number of occurrences of value - [] member _.count(value: 'T) : int = nativeOnly /// Return the position of value (raise ValueError if not found) @@ -208,7 +234,7 @@ type OrderedDict<'TKey, 'TValue>() = member _.Item(key: 'TKey) : 'TValue = nativeOnly /// Set value for key - [] + [] member _.set(key: 'TKey, value: 'TValue) : unit = nativeOnly /// Return key-value pairs in insertion order @@ -253,6 +279,9 @@ type OrderedDict<'TKey, 'TValue>() = /// Merge another dict into this one member _.update(other: System.Collections.Generic.IDictionary<'TKey, 'TValue>) : unit = nativeOnly + /// Merge an iterable of key-value pairs into this dict + member _.update(items: seq<'TKey * 'TValue>) : unit = nativeOnly + /// Remove all items member _.clear() : unit = nativeOnly diff --git a/test/TestCollections.fs b/test/TestCollections.fs index 931e48c..a47f6c3 100644 --- a/test/TestCollections.fs +++ b/test/TestCollections.fs @@ -8,103 +8,121 @@ open Fable.Python.Collections // ============================================================================ [] -let ``Counter: empty counter has zero count for missing key`` () = +let ``test Counter empty counter has zero count for missing key`` () = let c = Counter() c.Item("x") |> equal 0 [] -let ``Counter: ofSeq counts elements`` () = +let ``test Counter ofSeq counts elements`` () = let c = Counter.ofSeq [ "a"; "b"; "a"; "c"; "a"; "b" ] c.Item("a") |> equal 3 c.Item("b") |> equal 2 c.Item("c") |> equal 1 [] -let ``Counter: missing key returns 0`` () = +let ``test Counter missing key returns 0`` () = let c = Counter.ofSeq [ "a"; "b" ] c.Item("z") |> equal 0 [] -let ``Counter: most_common returns all elements sorted by count`` () = +let ``test Counter most_common returns all elements sorted by count`` () = let c = Counter.ofSeq [ "a"; "b"; "a"; "c"; "a"; "b" ] let top = c.most_common() |> Seq.head top |> equal ("a", 3) [] -let ``Counter: most_common n returns top n elements`` () = +let ``test Counter most_common n returns top n elements`` () = let c = Counter.ofSeq [ "a"; "b"; "a"; "c"; "a"; "b" ] let topTwo = c.most_common(2) |> Seq.toList topTwo |> List.length |> equal 2 topTwo |> List.head |> equal ("a", 3) [] -let ``Counter: elements returns repeated sequence`` () = +let ``test Counter elements returns repeated sequence`` () = let c = Counter.ofSeq [ "a"; "a"; "b" ] let elems = c.elements() |> Seq.toList |> List.sort elems |> equal [ "a"; "a"; "b" ] [] -let ``Counter: total sums all counts`` () = +let ``test Counter total sums all counts`` () = let c = Counter.ofSeq [ "a"; "b"; "a"; "c" ] c.total() |> equal 4 [] -let ``Counter: update adds counts`` () = +let ``test Counter update adds counts`` () = let c = Counter.ofSeq [ "a"; "b" ] c.update([ "a"; "c" ]) c.Item("a") |> equal 2 c.Item("c") |> equal 1 [] -let ``Counter: subtract reduces counts`` () = +let ``test Counter subtract reduces counts`` () = let c = Counter.ofSeq [ "a"; "a"; "b" ] c.subtract([ "a" ]) c.Item("a") |> equal 1 +[] +let ``test Counter contains reflects key presence`` () = + let c = Counter.ofSeq [ "a"; "b" ] + c.contains("a") |> equal true + c.contains("z") |> equal false + +[] +let ``test Counter keys and values enumerate the counter`` () = + let c = Counter.ofSeq [ "a"; "b"; "a" ] + c.keys() |> Seq.toList |> List.sort |> equal [ "a"; "b" ] + c.values() |> Seq.sum |> equal 3 + +[] +let ``test Counter pop removes and returns count`` () = + let c = Counter.ofSeq [ "a"; "a"; "b" ] + c.pop("a") |> equal 2 + c.contains("a") |> equal false + // ============================================================================ // defaultdict tests // ============================================================================ [] -let ``defaultdict: missing key invokes factory`` () = - let d = defaultdict>(fun () -> ResizeArray()) +let ``test defaultdict missing key invokes factory`` () = + let d = defaultdict>.withFactory(fun () -> ResizeArray()) let list = d.Item("key") list.Count |> equal 0 [] -let ``defaultdict: factory creates separate instances`` () = - let d = defaultdict>(fun () -> ResizeArray()) +let ``test defaultdict factory creates separate instances`` () = + let d = defaultdict>.withFactory(fun () -> ResizeArray()) let list1 = d.Item("a") list1.Add(1) let list2 = d.Item("b") list2.Count |> equal 0 [] -let ``defaultdict: int factory starts at zero`` () = - let d = defaultdict(fun () -> 0) +let ``test defaultdict int factory starts at zero`` () = + let d = defaultdict.withFactory(fun () -> 0) d.Item("key") |> equal 0 [] -let ``defaultdict: get returns None for missing key without invoking factory`` () = +let ``test defaultdict get returns None for missing key without invoking factory`` () = let mutable factoryCalled = false - let d = defaultdict(fun () -> factoryCalled <- true; 0) + let d = defaultdict.withFactory(fun () -> factoryCalled <- true; 0) let result = d.get("missing") result |> equal None factoryCalled |> equal false [] -let ``defaultdict: get with default returns default for missing key`` () = - let d = defaultdict(fun () -> 0) +let ``test defaultdict get with default returns default for missing key`` () = + let d = defaultdict.withFactory(fun () -> 0) d.get("missing", 42) |> equal 42 [] -let ``defaultdict: contains returns false for missing key`` () = - let d = defaultdict(fun () -> 0) +let ``test defaultdict contains returns false for missing key`` () = + let d = defaultdict.withFactory(fun () -> 0) d.contains("key") |> equal false [] -let ``defaultdict: contains returns true after access`` () = - let d = defaultdict(fun () -> 99) +let ``test defaultdict contains returns true after access`` () = + let d = defaultdict.withFactory(fun () -> 99) let _ = d.Item("key") d.contains("key") |> equal true @@ -113,56 +131,56 @@ let ``defaultdict: contains returns true after access`` () = // ============================================================================ [] -let ``deque: empty deque has length 0`` () = +let ``test deque empty deque has length 0`` () = let d = deque() d.length() |> equal 0 [] -let ``deque: ofSeq creates deque from sequence`` () = +let ``test deque ofSeq creates deque from sequence`` () = let d = deque.ofSeq [ 1; 2; 3 ] d.length() |> equal 3 [] -let ``deque: append adds to right`` () = +let ``test deque append adds to right`` () = let d = deque.ofSeq [ 1; 2 ] d.append(3) d.Item(2) |> equal 3 [] -let ``deque: appendleft adds to left`` () = +let ``test deque appendleft adds to left`` () = let d = deque.ofSeq [ 1; 2 ] d.appendleft(0) d.Item(0) |> equal 0 d.length() |> equal 3 [] -let ``deque: pop removes from right`` () = +let ``test deque pop removes from right`` () = let d = deque.ofSeq [ 1; 2; 3 ] let v = d.pop() v |> equal 3 d.length() |> equal 2 [] -let ``deque: popleft removes from left`` () = +let ``test deque popleft removes from left`` () = let d = deque.ofSeq [ 1; 2; 3 ] let v = d.popleft() v |> equal 1 d.length() |> equal 2 [] -let ``deque: rotate shifts elements right`` () = +let ``test deque rotate shifts elements right`` () = let d = deque.ofSeq [ 1; 2; 3; 4; 5 ] d.rotate(2) d.Item(0) |> equal 4 d.Item(1) |> equal 5 [] -let ``deque: maxlen is None for unbounded deque`` () = +let ``test deque maxlen is None for unbounded deque`` () = let d = deque.ofSeq [ 1; 2; 3 ] d.maxlen |> equal None [] -let ``deque: withMaxlen creates bounded deque`` () = +let ``test deque withMaxlen creates bounded deque`` () = let d = deque.withMaxlen(3) d.append(1) d.append(2) @@ -172,22 +190,31 @@ let ``deque: withMaxlen creates bounded deque`` () = d.Item(0) |> equal 2 [] -let ``deque: ofSeq with maxlen creates bounded deque`` () = +let ``test deque ofSeq with maxlen creates bounded deque`` () = let d = deque.ofSeq ([ 1; 2; 3; 4; 5 ], 3) d.length() |> equal 3 d.maxlen |> equal (Some 3) [] -let ``deque: count occurrences`` () = +let ``test deque count occurrences`` () = let d = deque.ofSeq [ 1; 2; 1; 3; 1 ] d.count(1) |> equal 3 +[] +let ``test deque extendleft reverses iterable order`` () = + let d = deque.ofSeq [ 3 ] + d.extendleft([ 1; 2 ]) + // Each element pushed onto the left in turn => final order [2; 1; 3] + d.Item(0) |> equal 2 + d.Item(1) |> equal 1 + d.Item(2) |> equal 3 + // ============================================================================ // OrderedDict tests // ============================================================================ [] -let ``OrderedDict: preserves insertion order`` () = +let ``test OrderedDict preserves insertion order`` () = let od = OrderedDict() od.set("a", 1) od.set("b", 2) @@ -195,18 +222,18 @@ let ``OrderedDict: preserves insertion order`` () = od.keys() |> Seq.toList |> equal [ "a"; "b"; "c" ] [] -let ``OrderedDict: get existing key`` () = +let ``test OrderedDict get existing key`` () = let od = OrderedDict() od.set("x", 42) od.Item("x") |> equal 42 [] -let ``OrderedDict: get returns None for missing key`` () = +let ``test OrderedDict get returns None for missing key`` () = let od = OrderedDict() od.get("missing") |> equal None [] -let ``OrderedDict: move_to_end moves last element`` () = +let ``test OrderedDict move_to_end moves last element`` () = let od = OrderedDict() od.set("a", 1) od.set("b", 2) @@ -215,7 +242,7 @@ let ``OrderedDict: move_to_end moves last element`` () = od.keys() |> Seq.toList |> equal [ "b"; "c"; "a" ] [] -let ``OrderedDict: move_to_end with last false moves to front`` () = +let ``test OrderedDict move_to_end with last false moves to front`` () = let od = OrderedDict() od.set("a", 1) od.set("b", 2) @@ -224,7 +251,7 @@ let ``OrderedDict: move_to_end with last false moves to front`` () = od.keys() |> Seq.toList |> equal [ "c"; "a"; "b" ] [] -let ``OrderedDict: contains returns correct result`` () = +let ``test OrderedDict contains returns correct result`` () = let od = OrderedDict() od.set("a", 1) od.contains("a") |> equal true