Skip to content

Commit 450910e

Browse files
committed
Added docs and tests.
1 parent c503d4b commit 450910e

File tree

5 files changed

+701
-30
lines changed

5 files changed

+701
-30
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
/_build
2+
/docs
23
/deps
34
erl_crash.dump
5+
mix.lock
46
*.ez

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ arr = Array.new()
1414
arr = Array.set(arr, 0, 100)
1515
1616
# Access by indices
17-
arr[0]
17+
arr[0] # -> 0
18+
arr[1000] # -> nil
1819
1920
# Convert from/to list
2021
Array.from_list([1,2,3,4,5])

lib/array.ex

Lines changed: 235 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,115 +1,325 @@
11
defmodule Array do
2+
@moduledoc """
3+
A wrapper module for Erlang's array.
4+
"""
25
defstruct content: nil
36

7+
@type t :: %__MODULE__{content: :array.array()}
8+
@type index :: non_neg_integer
9+
@type element :: any
10+
@type opts :: opt | [opt]
11+
@type opt :: {:fixed, boolean} | :fixed | {:default, any} | {:size, non_neg_integer} | non_neg_integer
12+
@type orddict :: [{index, element}]
13+
14+
@doc """
15+
Creates a new, extendible array with initial size zero.
16+
The default value is the atom nil, not undefined.
17+
"""
18+
@spec new() :: t
419
def new() do
520
%Array{content: :array.new({:default, nil})}
621
end
722

8-
def new(size) do
9-
%Array{content: :array.new(size, {:default, nil})}
10-
end
11-
12-
def new(size, options) do
23+
@doc """
24+
Creates a new fixed array according to the given options.
25+
By default, the array is extendible and has initial size zero.
26+
The default value is the atom nil, if not specified.
27+
28+
`options` is a single term or a list of terms, selected from the following:
29+
30+
* `n : non_neg_integer` or `{:size, n : non_neg_integer}`
31+
* Specifies the initial size of the array; this also implies `{:fixed, true}`.
32+
If `n` is not a nonnegative integer, the call raises `ArgumentError`.
33+
* `:fixed` or `{:fixed, true}`
34+
* Creates a fixed-size array.
35+
* `{:fixed, false}`
36+
* Creates an extendible (non fixed-size) array.
37+
* `{:default, value}`
38+
* Sets the default value for the array to `value`.
39+
"""
40+
@spec new(opts) :: t
41+
def new(options) do
1342
if is_list(options) do
14-
if List.keymember?(options, :default, 0) do
15-
%Array{content: :array.new(size, options)}
16-
else
17-
%Array{content: :array.new(size, [{:default, nil} | options])}
18-
end
43+
%Array{content: :array.new([{:default, nil} | options])}
1944
else
20-
case options do
21-
{:default, _} -> %Array{content: :array.new(size, options)}
22-
_ -> %Array{content: :array.new(size, [{:default, nil}, options])}
23-
end
45+
%Array{content: :array.new([{:default, nil}, options])}
46+
end
47+
end
48+
49+
@doc """
50+
Check if two arrays are equal using ===.
51+
"""
52+
@spec equal?(t, t) :: boolean
53+
def equal?(%Array{content: c1}, %Array{content: c2}) do
54+
s1 = :array.size(c1)
55+
s2 = :array.size(c2)
56+
cond do
57+
s1 != s2 -> false
58+
59+
s1 <= 0 -> true
60+
61+
true ->
62+
Enumerable.reduce(Range.new(0, s1-1), {:cont, true}, fn(idx, _acc) ->
63+
if :array.get(idx, c1) === :array.get(idx, c2) do
64+
{:cont, true}
65+
else
66+
{:halt, false}
67+
end
68+
end) |> elem(1)
2469
end
2570
end
2671

72+
@doc """
73+
Gets the value used for uninitialized entries.
74+
"""
75+
@spec default(t) :: any
2776
def default(%Array{content: c}),
2877
do: :array.default(c)
2978

79+
@doc """
80+
Fixes the size of the array. This prevents it from growing automatically upon insertion.
81+
"""
82+
@spec fix(t) :: t
3083
def fix(%Array{content: c} = arr),
3184
do: %Array{arr | content: :array.fix(c)}
3285

86+
@doc """
87+
Folds the elements of the array using the given function and initial accumulator value.
88+
The elements are visited in order from the lowest index to the highest.
89+
90+
If `fun` is not a function, the call raises `ArgumentError`.
91+
"""
92+
@spec foldl(t, acc, (index, element, acc -> acc)) :: acc when acc: var
3393
def foldl(%Array{content: c}, acc, fun),
3494
do: :array.foldl(fun, acc, c)
3595

96+
@doc """
97+
Folds the elements of the array right-to-left using the given function and initial accumulator value.
98+
The elements are visited in order from the highest index to the lowest.
99+
100+
If `fun` is not a function, the call raises `ArgumentError`.
101+
"""
102+
@spec foldr(t, acc, (index, element, acc -> acc)) :: acc when acc: var
36103
def foldr(%Array{content: c}, acc, fun),
37104
do: :array.foldr(fun, acc, c)
38105

106+
@doc """
107+
Equivalent to `from_list(list, nil)`.
108+
"""
109+
@spec from_list(list) :: t
39110
def from_list(list),
40111
do: %Array{content: :array.from_list(list, nil)}
41112

113+
@doc """
114+
Converts a list to an extendible array.
115+
`default` is used as the value for uninitialized entries of the array.
116+
117+
If `list` is not a proper list, the call raises `ArgumentError`.
118+
"""
119+
@spec from_list(list, any) :: t
42120
def from_list(list, default),
43121
do: %Array{content: :array.from_list(list, default)}
44122

123+
@doc """
124+
Equivalent to `from_orddict(orddict, nil)`.
125+
"""
126+
@spec from_orddict(orddict) :: t
45127
def from_orddict(orddict),
46128
do: %Array{content: :array.from_orddict(orddict, nil)}
47129

130+
@doc """
131+
Converts an ordered list of pairs `{index, value}` to a corresponding extendible array.
132+
`default` is used as the value for uninitialized entries of the array.
133+
134+
If `orddict` is not a proper, ordered list of pairs whose first elements are nonnegative integers,
135+
the call raises `ArgumentError`.
136+
"""
137+
@spec from_orddict(orddict, any) :: t
48138
def from_orddict(orddict, default),
49139
do: %Array{content: :array.from_orddict(orddict, default)}
50140

141+
@doc """
142+
Converts an Erlang's array to an array.
143+
All properties (size, elements, default value, fixedness) of the original array are preserved.
144+
145+
If `erl_arr` is not an Erlang's array, the call raises `ArgumentError`.
146+
"""
147+
@spec from_erlang_array(:array.array()) :: t
148+
def from_erlang_array(erl_arr) do
149+
if :array.is_array(erl_arr) do
150+
%Array{content: erl_arr}
151+
else
152+
raise ArgumentError
153+
end
154+
end
155+
156+
@doc """
157+
Gets the value of entry `idx`. If `idx` is not a nonnegative integer, or if the array has
158+
fixed size and `idx` is larger than the maximum index, the call raises `ArgumentError`.
159+
"""
160+
@spec get(t, index) :: element
161+
def get(%Array{content: c}, idx),
162+
do: :array.get(idx, c)
163+
164+
@doc """
165+
Returns `true` if `arr` appears to be an array, otherwise `false`.
166+
Note that the check is only shallow; there is no guarantee that `arr` is a well-formed array
167+
representation even if this function returns `true`.
168+
"""
169+
@spec is_array(t) :: boolean
51170
def is_array(arr) do
52171
case arr do
53172
%Array{content: c} -> :array.is_array(c)
54173
_ -> false
55174
end
56175
end
57176

177+
@doc """
178+
Checks if the array has fixed size. Returns `true` if the array is fixed, otherwise `false`.
179+
"""
180+
@spec is_fix(t) :: boolean
58181
def is_fix(%Array{content: c}),
59182
do: :array.is_fix(c)
60183

184+
@doc """
185+
Maps the given function onto each element of the array.
186+
The elements are visited in order from the lowest index to the highest.
187+
188+
If `fun` is not a function, the call raises `ArgumentError`.
189+
"""
190+
@spec map(t, (index, element -> any)) :: t
61191
def map(%Array{content: c} = arr, fun),
62192
do: %Array{arr | content: :array.map(fun, c)}
63193

194+
@doc """
195+
Makes the array resizable.
196+
"""
197+
@spec relax(t) :: t
64198
def relax(%Array{content: c} = arr),
65199
do: %Array{arr | content: :array.relax(c)}
66200

201+
@doc """
202+
Resets entry `idx` to the default value for the array.
203+
If the value of entry `idx` is the default value the array will be returned unchanged.
204+
Reset will never change size of the array. Shrinking can be done explicitly by calling `resize/2`.
205+
206+
If `idx` is not a nonnegative integer, or if the array has fixed size and `idx` is
207+
larger than the maximum index, the call raises `ArgumentError`.
208+
"""
209+
@spec reset(t, index) :: t
67210
def reset(%Array{content: c} = arr, idx),
68211
do: %Array{arr | content: :array.reset(idx, c)}
69212

213+
@doc """
214+
Changes the size of the array to that reported by `sparse_size/1`.
215+
If the given array has fixed size, the resulting array will also have fixed size.
216+
"""
217+
@spec resize(t) :: t
70218
def resize(%Array{content: c} = arr),
71219
do: %Array{arr | content: :array.resize(c)}
72220

221+
@doc """
222+
Changes the size of the array.
223+
If `size` is not a nonnegative integer, the call raises `ArgumentError`.
224+
If the given array has fixed size, the resulting array will also have fixed size.
225+
"""
226+
@spec resize(t, non_neg_integer) :: t
73227
def resize(%Array{content: c} = arr, size),
74228
do: %Array{arr | content: :array.resize(size, c)}
75229

230+
@doc """
231+
Sets entry `idx` of the array to `val`.
232+
If `idx` is not a nonnegative integer, or if the array has fixed size and `idx` is
233+
larger than the maximum index, the call raises `ArgumentError`.
234+
"""
235+
@spec set(t, index, element) :: t
76236
def set(%Array{content: c} = arr, idx, val),
77237
do: %Array{arr | content: :array.set(idx, val, c)}
78238

239+
@doc """
240+
Gets the number of entries in the array.
241+
Entries are numbered from 0 to `size(array)-1`; hence, this is also the index of
242+
the first entry that is guaranteed to not have been previously set.
243+
"""
244+
@spec size(t) :: non_neg_integer
79245
def size(%Array{content: c}),
80246
do: :array.size(c)
81247

248+
@doc """
249+
Folds the elements of the array using the given function and initial accumulator value,
250+
skipping default-valued entries.
251+
The elements are visited in order from the lowest index to the highest.
252+
253+
If `fun` is not a function, the call raises `ArgumentError`.
254+
"""
255+
@spec sparse_foldl(t, acc, (index, element, acc -> acc)) :: acc when acc: var
82256
def sparse_foldl(%Array{content: c}, acc, fun),
83257
do: :array.sparse_foldl(fun, acc, c)
84258

259+
@doc """
260+
Folds the elements of the array right-to-left using the given function and initial accumulator value,
261+
skipping default-valued entries.
262+
The elements are visited in order from the highest index to the lowest.
263+
264+
If `fun` is not a function, the call raises `ArgumentError`.
265+
"""
266+
@spec sparse_foldr(t, acc, (index, element, acc -> acc)) :: acc when acc: var
85267
def sparse_foldr(%Array{content: c}, acc, fun),
86268
do: :array.sparse_foldr(fun, acc, c)
87269

270+
@doc """
271+
Maps the given function onto each element of the array, skipping default-valued entries.
272+
The elements are visited in order from the lowest index to the highest.
273+
274+
If `fun` is not a function, the call raises `ArgumentError`.
275+
"""
276+
@spec sparse_map(t, (element -> any)) :: t
88277
def sparse_map(%Array{content: c} = arr, fun),
89278
do: %Array{arr | content: :array.sparse_map(fun, c)}
90279

280+
@doc """
281+
Gets the number of entries in the array up until the last non-default valued entry.
282+
In other words, returns `idx+1` if `idx` is the last non-default valued entry in the array,
283+
or zero if no such entry exists.
284+
"""
285+
@spec sparse_size(t) :: non_neg_integer
91286
def sparse_size(%Array{content: c}),
92287
do: :array.sparse_size(c)
93288

289+
@doc """
290+
Converts the array to a list, skipping default-valued entries.
291+
"""
292+
@spec sparse_to_list(t) :: list
94293
def sparse_to_list(%Array{content: c}),
95294
do: :array.sparse_to_list(c)
96295

296+
@doc """
297+
Converts the array to an ordered list of pairs `{index, value}`, skipping default-valued entries.
298+
"""
299+
@spec sparse_to_orddict(t) :: [{index, element}]
97300
def sparse_to_orddict(%Array{content: c}),
98301
do: :array.sparse_to_orddict(c)
99302

303+
@doc """
304+
Converts the array to its underlying Erlang's array.
305+
"""
306+
@spec to_erlang_array(t) :: :array.array()
307+
def to_erlang_array(%Array{content: c}),
308+
do: c
309+
310+
@doc """
311+
Converts the array to a list.
312+
"""
313+
@spec to_list(t) :: list
100314
def to_list(%Array{content: c}),
101315
do: :array.to_list(c)
102316

317+
@doc """
318+
Converts the array to an ordered list of pairs `{index, value}`.
319+
"""
320+
@spec to_orddict(t) :: [{index, element}]
103321
def to_orddict(%Array{content: c}),
104322
do: :array.to_orddict(c)
105-
106-
def get(%Array{content: c}, idx),
107-
do: :array.get(idx, c)
108-
109-
def get_and_update(%Array{content: c} = arr, idx, fun) do
110-
{get, update} = fun.(:array.get(idx, c))
111-
{get, %Array{arr | content: :array.set(idx, update, c)}}
112-
end
113323
end
114324

115325
defimpl Access, for: Array do
@@ -118,12 +328,13 @@ defimpl Access, for: Array do
118328
end
119329

120330
def get_and_update(arr, idx, fun) do
121-
Array.get_and_update(arr, idx, fun)
331+
{get, update} = fun.(Array.get(arr, idx))
332+
{get, Array.set(arr, idx, update)}
122333
end
123334
end
124335

125336
defimpl Enumerable, for: Array do
126-
def count(arr), do: Array.size(arr)
337+
def count(arr), do: {:ok, Array.size(arr)}
127338

128339
def member?(_arr, _value), do: {:error, __MODULE__}
129340

0 commit comments

Comments
 (0)