Skip to content

Commit cbc296a

Browse files
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>
1 parent 5d82af6 commit cbc296a

4 files changed

Lines changed: 497 additions & 0 deletions

File tree

src/Fable.Python.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<Compile Include="stdlib/Math.fs" />
2424
<Compile Include="stdlib/Random.fs" />
2525
<Compile Include="stdlib/Os.fs" />
26+
<Compile Include="stdlib/Collections.fs" />
2627
<Compile Include="stdlib/Heapq.fs" />
2728
<Compile Include="stdlib/Itertools.fs" />
2829
<Compile Include="stdlib/Datetime.fs" />

src/stdlib/Collections.fs

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
/// Type bindings for Python collections module: https://docs.python.org/3/library/collections.html
2+
module Fable.Python.Collections
3+
4+
open Fable.Core
5+
6+
// fsharplint:disable MemberNames
7+
8+
// ============================================================================
9+
// Counter
10+
// ============================================================================
11+
12+
/// A dict subclass for counting hashable objects.
13+
/// Elements are stored as dictionary keys and their counts are stored as values.
14+
/// Counts are allowed to be any integer value including zero or negative counts.
15+
/// See https://docs.python.org/3/library/collections.html#collections.Counter
16+
[<Import("Counter", "collections")>]
17+
type Counter<'T>() =
18+
/// Get the count for key; missing keys return 0 (unlike a regular dict)
19+
[<Emit("$0[$1]")>]
20+
member _.Item(key: 'T) : int = nativeOnly
21+
22+
/// Return elements and their counts as key-value pairs
23+
member _.items() : seq<'T * int> = nativeOnly
24+
25+
/// Return an iterator over elements, repeating each as many times as its count.
26+
/// Elements with counts <= 0 are not included.
27+
/// See https://docs.python.org/3/library/collections.html#collections.Counter.elements
28+
member _.elements() : seq<'T> = nativeOnly
29+
30+
/// Return the n most common elements and their counts (most common first).
31+
/// If n is omitted, return all elements in counter order.
32+
/// See https://docs.python.org/3/library/collections.html#collections.Counter.most_common
33+
member _.most_common() : seq<'T * int> = nativeOnly
34+
35+
/// Return the n most common elements and their counts (most common first).
36+
/// See https://docs.python.org/3/library/collections.html#collections.Counter.most_common
37+
[<Emit("$0.most_common(int($1))")>]
38+
member _.most_common(n: int) : seq<'T * int> = nativeOnly
39+
40+
/// Return the total of all counts (requires Python 3.10+).
41+
/// See https://docs.python.org/3/library/collections.html#collections.Counter.total
42+
member _.total() : int = nativeOnly
43+
44+
/// Add counts from the iterable or mapping; count becomes sum of old and new counts.
45+
/// See https://docs.python.org/3/library/collections.html#collections.Counter.update
46+
member _.update(iterable: 'T seq) : unit = nativeOnly
47+
48+
/// Subtract counts from the iterable or mapping; count becomes difference.
49+
/// Counts can become negative.
50+
/// See https://docs.python.org/3/library/collections.html#collections.Counter.subtract
51+
member _.subtract(iterable: 'T seq) : unit = nativeOnly
52+
53+
/// Return a Counter from a sequence of elements.
54+
/// See https://docs.python.org/3/library/collections.html#collections.Counter
55+
[<Emit("Counter($0)")>]
56+
static member ofSeq(iterable: 'T seq) : Counter<'T> = nativeOnly
57+
58+
// ============================================================================
59+
// defaultdict
60+
// ============================================================================
61+
62+
/// A dict subclass that calls a factory to supply missing values.
63+
/// When a key is not found, the factory function (called with no arguments)
64+
/// is called to produce a new value, which is then stored and returned.
65+
/// If the factory is not set (None), missing keys raise KeyError as normal.
66+
/// See https://docs.python.org/3/library/collections.html#collections.defaultdict
67+
[<Import("defaultdict", "collections")>]
68+
type defaultdict<'TKey, 'TValue>(defaultFactory: unit -> 'TValue) =
69+
/// Get or set the value for key; missing keys invoke the factory
70+
[<Emit("$0[$1]")>]
71+
member _.Item(key: 'TKey) : 'TValue = nativeOnly
72+
73+
/// Set value for key
74+
[<Emit("$0.__setitem__($1, $2)")>]
75+
member _.set(key: 'TKey, value: 'TValue) : unit = nativeOnly
76+
77+
/// Return key-value pairs
78+
member _.items() : seq<'TKey * 'TValue> = nativeOnly
79+
80+
/// Return keys
81+
member _.keys() : seq<'TKey> = nativeOnly
82+
83+
/// Return values
84+
member _.values() : seq<'TValue> = nativeOnly
85+
86+
/// Return value for key if present, otherwise None.
87+
/// Does NOT invoke the factory.
88+
member _.get(key: 'TKey) : 'TValue option = nativeOnly
89+
90+
/// Return value for key if present, otherwise defaultValue.
91+
/// Does NOT invoke the factory.
92+
[<Emit("$0.get($1, $2)")>]
93+
member _.get(key: 'TKey, defaultValue: 'TValue) : 'TValue = nativeOnly
94+
95+
/// If key is in the dict, return its value.
96+
/// If not, insert key with the factory's value and return that value.
97+
member _.setdefault(key: 'TKey) : 'TValue = nativeOnly
98+
99+
/// Remove and return the value for key, or raise KeyError.
100+
member _.pop(key: 'TKey) : 'TValue = nativeOnly
101+
102+
/// Remove and return the value for key, or return defaultValue.
103+
[<Emit("$0.pop($1, $2)")>]
104+
member _.pop(key: 'TKey, defaultValue: 'TValue) : 'TValue = nativeOnly
105+
106+
/// Merge another dict into this one
107+
member _.update(other: System.Collections.Generic.IDictionary<'TKey, 'TValue>) : unit = nativeOnly
108+
109+
/// Remove all items
110+
member _.clear() : unit = nativeOnly
111+
112+
/// Return a shallow copy
113+
member _.copy() : defaultdict<'TKey, 'TValue> = nativeOnly
114+
115+
/// Check if a key is present (does NOT invoke factory)
116+
[<Emit("$1 in $0")>]
117+
member _.contains(key: 'TKey) : bool = nativeOnly
118+
119+
// ============================================================================
120+
// deque
121+
// ============================================================================
122+
123+
/// A double-ended queue with O(1) appends and pops from either end.
124+
/// If maxlen is set, the deque is bounded to that maximum length; items are
125+
/// discarded from the opposite end when the bound is reached.
126+
/// See https://docs.python.org/3/library/collections.html#collections.deque
127+
[<Import("deque", "collections")>]
128+
type deque<'T>() =
129+
/// Number of elements in the deque
130+
[<Emit("len($0)")>]
131+
member _.length() : int = nativeOnly
132+
133+
/// Get element at index
134+
[<Emit("$0[$1]")>]
135+
member _.Item(index: int) : 'T = nativeOnly
136+
137+
/// Maximum length of the deque, or None if unbounded
138+
member _.maxlen : int option = nativeOnly
139+
140+
/// Add item to the right end
141+
member _.append(item: 'T) : unit = nativeOnly
142+
143+
/// Add item to the left end
144+
member _.appendleft(item: 'T) : unit = nativeOnly
145+
146+
/// Remove and return item from the right end
147+
member _.pop() : 'T = nativeOnly
148+
149+
/// Remove and return item from the left end
150+
member _.popleft() : 'T = nativeOnly
151+
152+
/// Extend the right side of the deque by appending elements from iterable
153+
member _.extend(iterable: 'T seq) : unit = nativeOnly
154+
155+
/// Extend the left side of the deque by appending elements from iterable.
156+
/// Note: each element is appended to the left, reversing the iterable order.
157+
member _.extendleft(iterable: 'T seq) : unit = nativeOnly
158+
159+
/// Rotate the deque n steps to the right. If n is negative, rotate left.
160+
member _.rotate(n: int) : unit = nativeOnly
161+
162+
/// Count the number of occurrences of value
163+
[<Emit("$0.count($1)")>]
164+
member _.count(value: 'T) : int = nativeOnly
165+
166+
/// Return the position of value (raise ValueError if not found)
167+
member _.index(value: 'T) : int = nativeOnly
168+
169+
/// Insert value before position i
170+
member _.insert(i: int, value: 'T) : unit = nativeOnly
171+
172+
/// Remove the first occurrence of value (raise ValueError if not found)
173+
member _.remove(value: 'T) : unit = nativeOnly
174+
175+
/// Reverse the deque in-place
176+
member _.reverse() : unit = nativeOnly
177+
178+
/// Remove all elements
179+
member _.clear() : unit = nativeOnly
180+
181+
/// Return a shallow copy
182+
member _.copy() : deque<'T> = nativeOnly
183+
184+
/// Create a deque from a sequence
185+
[<Emit("deque($0)")>]
186+
static member ofSeq(iterable: 'T seq) : deque<'T> = nativeOnly
187+
188+
/// Create a bounded deque from a sequence with maximum length
189+
[<Emit("deque($0, maxlen=int($1))")>]
190+
static member ofSeq(iterable: 'T seq, maxlen: int) : deque<'T> = nativeOnly
191+
192+
/// Create an empty bounded deque with maximum length
193+
[<Emit("deque(maxlen=int($0))")>]
194+
static member withMaxlen(maxlen: int) : deque<'T> = nativeOnly
195+
196+
// ============================================================================
197+
// OrderedDict
198+
// ============================================================================
199+
200+
/// A dict subclass that remembers insertion order. Since Python 3.7, all dicts
201+
/// maintain insertion order, but OrderedDict has a few extra features:
202+
/// `move_to_end` and order-sensitive equality.
203+
/// See https://docs.python.org/3/library/collections.html#collections.OrderedDict
204+
[<Import("OrderedDict", "collections")>]
205+
type OrderedDict<'TKey, 'TValue>() =
206+
/// Get or set value for key
207+
[<Emit("$0[$1]")>]
208+
member _.Item(key: 'TKey) : 'TValue = nativeOnly
209+
210+
/// Set value for key
211+
[<Emit("$0.__setitem__($1, $2)")>]
212+
member _.set(key: 'TKey, value: 'TValue) : unit = nativeOnly
213+
214+
/// Return key-value pairs in insertion order
215+
member _.items() : seq<'TKey * 'TValue> = nativeOnly
216+
217+
/// Return keys in insertion order
218+
member _.keys() : seq<'TKey> = nativeOnly
219+
220+
/// Return values in insertion order
221+
member _.values() : seq<'TValue> = nativeOnly
222+
223+
/// Get value for key, or None if missing
224+
member _.get(key: 'TKey) : 'TValue option = nativeOnly
225+
226+
/// Get value for key, or defaultValue if missing
227+
[<Emit("$0.get($1, $2)")>]
228+
member _.get(key: 'TKey, defaultValue: 'TValue) : 'TValue = nativeOnly
229+
230+
/// Remove and return the value for key (or raise KeyError)
231+
member _.pop(key: 'TKey) : 'TValue = nativeOnly
232+
233+
/// Remove and return the value for key, or return defaultValue
234+
[<Emit("$0.pop($1, $2)")>]
235+
member _.pop(key: 'TKey, defaultValue: 'TValue) : 'TValue = nativeOnly
236+
237+
/// Move key to the end. If last is False, move to the beginning.
238+
/// See https://docs.python.org/3/library/collections.html#collections.OrderedDict.move_to_end
239+
member _.move_to_end(key: 'TKey) : unit = nativeOnly
240+
241+
/// Move key to the end (last=True) or beginning (last=False).
242+
[<Emit("$0.move_to_end($1, last=$2)")>]
243+
member _.move_to_end(key: 'TKey, last: bool) : unit = nativeOnly
244+
245+
/// Remove and return a (key, value) pair. last=True removes from the end.
246+
/// See https://docs.python.org/3/library/collections.html#collections.OrderedDict.popitem
247+
member _.popitem() : 'TKey * 'TValue = nativeOnly
248+
249+
/// Remove and return from end (last=True) or beginning (last=False).
250+
[<Emit("$0.popitem(last=$1)")>]
251+
member _.popitem(last: bool) : 'TKey * 'TValue = nativeOnly
252+
253+
/// Merge another dict into this one
254+
member _.update(other: System.Collections.Generic.IDictionary<'TKey, 'TValue>) : unit = nativeOnly
255+
256+
/// Remove all items
257+
member _.clear() : unit = nativeOnly
258+
259+
/// Return a shallow copy
260+
member _.copy() : OrderedDict<'TKey, 'TValue> = nativeOnly
261+
262+
/// Check if key is present
263+
[<Emit("$1 in $0")>]
264+
member _.contains(key: 'TKey) : bool = nativeOnly

test/Fable.Python.Test.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<Compile Include="TestBuiltins.fs" />
1818
<Compile Include="TestBuiltinsAttr.fs" />
1919
<Compile Include="TestHeapq.fs" />
20+
<Compile Include="TestCollections.fs" />
2021
<Compile Include="TestItertools.fs" />
2122
<Compile Include="TestFunctools.fs" />
2223
<Compile Include="TestQueue.fs" />

0 commit comments

Comments
 (0)