name_bank 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +50 -12
- data/lib/name_bank/errors.rb +5 -0
- data/lib/name_bank/version.rb +1 -1
- data/lib/name_bank.rb +30 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6e238aa5013b3c3a8513f0bd233bb7365f7703c6f852270d93044ee424f6df95
|
|
4
|
+
data.tar.gz: 697fe182bc1222111617ca9dd2803d208cff375aff171b41d42bf32047cef45c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 12411b5e09c5e83bcecefc555c7568843832ed4a37c42553d5fc8efef2c1f4699f828731b1ac68c7e5cd1db93fdbc2baf8be34207553f67bb901b9e2fbc05c6f
|
|
7
|
+
data.tar.gz: f959d2ec630d8d53cf461c22dfa261935e8369e866d95ce513d5e64c86b42082c0c5622ce129d4e41bce9d42a87a7182c82202baa8c842b34b478e34b61ad2ab
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,28 @@ All notable changes to this project are documented here. The format is based on
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
|
|
5
5
|
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.4.0] - 2026-08-16
|
|
8
|
+
|
|
9
|
+
Many names in one call. Nothing that worked before changes: sampling from the
|
|
10
|
+
existing methods is byte-identical to 0.3.0 over 2830 samples across every
|
|
11
|
+
country, script and variant, and the name data is untouched.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- `full_names(country:, gender:, rng:, count:)` draws that many distinct
|
|
15
|
+
given/family pairs in one call — the shape a database seed needs. No pair
|
|
16
|
+
repeats; single names do, because only the pair is unique. The guarantee
|
|
17
|
+
holds within one call, so no state is kept between calls and sampling stays
|
|
18
|
+
a function of the caller's RNG. `count:` is required, and asking for more
|
|
19
|
+
pairs than the two pools can form raises the new `NameBank::PoolExhausted`
|
|
20
|
+
rather than silently returning fewer.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- README: the first usage example showed a name pair that the seed above it
|
|
24
|
+
never produces. Every `# =>` in the README is now the value the code actually
|
|
25
|
+
returns, checked against the shipped pools. The sampling examples under
|
|
26
|
+
*Scripts* and *Variants* name their seed too, so all of them can be
|
|
27
|
+
reproduced rather than taken on trust. No code or data changed.
|
|
28
|
+
|
|
7
29
|
## [0.3.0] - 2026-08-11
|
|
8
30
|
|
|
9
31
|
Lookup, documentation and one platform bug. Sampling behaviour and the name
|
data/README.md
CHANGED
|
@@ -27,23 +27,23 @@ gem install name_bank
|
|
|
27
27
|
```ruby
|
|
28
28
|
require "name_bank"
|
|
29
29
|
|
|
30
|
-
rng = Random.new(
|
|
30
|
+
rng = Random.new(10246)
|
|
31
31
|
|
|
32
32
|
# A full name (given + family) for a country and gender:
|
|
33
33
|
NameBank.full_name(country: "DE", gender: :female, rng: rng)
|
|
34
|
-
# => { firstname: "
|
|
34
|
+
# => { firstname: "Alexandra", lastname: "Werner" }
|
|
35
35
|
|
|
36
36
|
# Just a given name or a surname:
|
|
37
|
-
NameBank.first_name(country: "IT", gender: :male, rng: rng) # => "
|
|
38
|
-
NameBank.last_name(country: "JP", rng: rng) # => "
|
|
37
|
+
NameBank.first_name(country: "IT", gender: :male, rng: rng) # => "Settimio"
|
|
38
|
+
NameBank.last_name(country: "JP", rng: rng) # => "Katsuta"
|
|
39
39
|
|
|
40
40
|
# Latin (default), or the country's native script where it has one:
|
|
41
|
-
NameBank.first_name(country: "JP", gender: :female, rng: rng) # => "
|
|
42
|
-
NameBank.first_name(country: "JP", gender: :female, rng: rng, script: :native) # => "
|
|
41
|
+
NameBank.first_name(country: "JP", gender: :female, rng: rng) # => "Misako"
|
|
42
|
+
NameBank.first_name(country: "JP", gender: :female, rng: rng, script: :native) # => "さえ"
|
|
43
43
|
|
|
44
44
|
# Default pool, or an alternate cultural pool where one exists:
|
|
45
|
-
NameBank.first_name(country: "US", gender: :male, rng: rng) # => "
|
|
46
|
-
NameBank.first_name(country: "US", gender: :male, rng: rng, variant: "african_american") # => "
|
|
45
|
+
NameBank.first_name(country: "US", gender: :male, rng: rng) # => "Jonah"
|
|
46
|
+
NameBank.first_name(country: "US", gender: :male, rng: rng, variant: "african_american") # => "Tyrone"
|
|
47
47
|
|
|
48
48
|
# List available countries (ISO alpha-2 codes):
|
|
49
49
|
NameBank.countries.size # => 106
|
|
@@ -54,10 +54,38 @@ Sampling is uniform over each pool and fully deterministic for a given
|
|
|
54
54
|
`rng` — the same seed always yields the same name. `gender:` is `:male`
|
|
55
55
|
or `:female`.
|
|
56
56
|
|
|
57
|
+
## Many at once
|
|
58
|
+
|
|
59
|
+
Seeding a table wants many names in one call, and wants them to differ.
|
|
60
|
+
`full_names` draws distinct given/family pairs:
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
rng = Random.new(24134)
|
|
64
|
+
|
|
65
|
+
NameBank.full_names(country: "DE", gender: :female, rng: rng, count: 3)
|
|
66
|
+
# => [{ firstname: "Gerlinde", lastname: "Nowak" },
|
|
67
|
+
# { firstname: "Juliane", lastname: "Stark" },
|
|
68
|
+
# { firstname: "Kornelia", lastname: "Lenz" }]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
No pair is drawn twice. Single names do recur once the draw is large enough —
|
|
72
|
+
only the pair is unique, which is what a real population looks like. The
|
|
73
|
+
guarantee holds within one call; two calls know nothing of each other.
|
|
74
|
+
|
|
75
|
+
`count:` is required, because there is no pool of pairs to hand back without
|
|
76
|
+
one. A country can form as many pairs as given names × surnames: 1500 × 1500
|
|
77
|
+
for most, 30 × 30 for the hand-curated `CN` and `UA` pools. Asking for more
|
|
78
|
+
raises rather than quietly returning fewer:
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
NameBank.full_names(country: "UA", gender: :male, rng: rng, count: 901)
|
|
82
|
+
# => NameBank::PoolExhausted: UA: 901 pairs requested, 900 available
|
|
83
|
+
```
|
|
84
|
+
|
|
57
85
|
## Whole pools
|
|
58
86
|
|
|
59
|
-
Where sampling
|
|
60
|
-
|
|
87
|
+
Where sampling is not enough — applying your own weighting, drawing single
|
|
88
|
+
names without repeats, or checking what a country actually ships —
|
|
61
89
|
take the pool itself. `first_names` and `last_names` accept the same
|
|
62
90
|
`country:`, `variant:` and `script:` options as the samplers, and return the
|
|
63
91
|
frequency-ordered pool as a frozen array:
|
|
@@ -68,6 +96,11 @@ NameBank.first_names(country: "DE", gender: :female).first(3) # => ["Nicole", "
|
|
|
68
96
|
NameBank.last_names(country: "JP", script: :native).first(3) # => ["佐藤", "鈴木", "田中"]
|
|
69
97
|
```
|
|
70
98
|
|
|
99
|
+
For single names without repeats, the pool plus Ruby is the whole answer:
|
|
100
|
+
`pool.sample(200, random: rng)` returns 200 different names, deterministically.
|
|
101
|
+
Note that `Array#sample` returns fewer than asked for rather than complaining
|
|
102
|
+
when the pool is too small — unlike `full_names`, which raises.
|
|
103
|
+
|
|
71
104
|
Every method shown so far is also available on an instance, which lets you
|
|
72
105
|
point name_bank at your own directory of pool files:
|
|
73
106
|
`NameBank.new(data_dir: "…").first_name(country: "DE", gender: :male, rng: rng)`.
|
|
@@ -153,6 +186,8 @@ Names come in Latin (default) and, for countries with a non-Latin writing
|
|
|
153
186
|
system, their native script. Pass `script:`:
|
|
154
187
|
|
|
155
188
|
```ruby
|
|
189
|
+
rng = Random.new(325089)
|
|
190
|
+
|
|
156
191
|
NameBank.first_name(country: "RU", gender: :male, rng: rng) # => "Dmitry"
|
|
157
192
|
NameBank.first_name(country: "RU", gender: :male, rng: rng, script: :native) # => "Алексей"
|
|
158
193
|
|
|
@@ -179,6 +214,8 @@ Some countries offer an alternate cultural name pool layered on the default.
|
|
|
179
214
|
Pass `variant:`:
|
|
180
215
|
|
|
181
216
|
```ruby
|
|
217
|
+
rng = Random.new(41)
|
|
218
|
+
|
|
182
219
|
NameBank.first_name(country: "US", gender: :male, rng: rng, variant: "african_american")
|
|
183
220
|
# => "DeShawn"
|
|
184
221
|
|
|
@@ -199,10 +236,11 @@ still catches the two that are genuinely caller mistakes:
|
|
|
199
236
|
| `NameBank::UnknownCountry` | `StandardError` | no pool file for that country code |
|
|
200
237
|
| `NameBank::UnknownVariant` | `StandardError` | no pool file for that variant |
|
|
201
238
|
| `NameBank::EmptyPool` | `StandardError` | the pool exists but holds no names |
|
|
239
|
+
| `NameBank::PoolExhausted` | `StandardError` | `full_names` was asked for more distinct pairs than the pools can form |
|
|
202
240
|
| `NameBank::MalformedPool` | `StandardError` | a pool file is missing a key, or holds something other than a list |
|
|
203
241
|
|
|
204
|
-
|
|
205
|
-
`NameBank.new(data_dir:)` at pool files of your own.
|
|
242
|
+
`EmptyPool` and `MalformedPool` cannot occur with the shipped data; they matter
|
|
243
|
+
when you point `NameBank.new(data_dir:)` at pool files of your own.
|
|
206
244
|
|
|
207
245
|
```ruby
|
|
208
246
|
begin
|
data/lib/name_bank/errors.rb
CHANGED
|
@@ -28,6 +28,11 @@ class NameBank
|
|
|
28
28
|
include Error
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
# More distinct pairs were asked for than the pools can form.
|
|
32
|
+
class PoolExhausted < StandardError
|
|
33
|
+
include Error
|
|
34
|
+
end
|
|
35
|
+
|
|
31
36
|
# script: was neither :latin nor :native.
|
|
32
37
|
class UnknownScript < ArgumentError
|
|
33
38
|
include Error
|
data/lib/name_bank/version.rb
CHANGED
data/lib/name_bank.rb
CHANGED
|
@@ -27,6 +27,7 @@ class NameBank
|
|
|
27
27
|
def first_name(...) = default.first_name(...)
|
|
28
28
|
def last_name(...) = default.last_name(...)
|
|
29
29
|
def full_name(...) = default.full_name(...)
|
|
30
|
+
def full_names(...) = default.full_names(...)
|
|
30
31
|
def first_names(...) = default.first_names(...)
|
|
31
32
|
def last_names(...) = default.last_names(...)
|
|
32
33
|
def countries(...) = default.countries(...)
|
|
@@ -53,6 +54,21 @@ class NameBank
|
|
|
53
54
|
}
|
|
54
55
|
end
|
|
55
56
|
|
|
57
|
+
# Distinct given/family pairs, for seeding many rows at once. No pair is drawn
|
|
58
|
+
# twice; single names do recur, which is what a population looks like. The
|
|
59
|
+
# guarantee holds within one call — two calls know nothing of each other.
|
|
60
|
+
# Asking for more pairs than the two pools can form raises PoolExhausted.
|
|
61
|
+
def full_names(country:, gender:, rng:, count:, variant: nil, script: :latin)
|
|
62
|
+
firsts = first_names(country: country, gender: gender, variant: variant, script: script)
|
|
63
|
+
lasts = last_names(country: country, variant: variant, script: script)
|
|
64
|
+
available = firsts.size * lasts.size
|
|
65
|
+
raise PoolExhausted, "#{country}: #{count} pairs requested, #{available} available" if count > available
|
|
66
|
+
|
|
67
|
+
distinct_indices(available, count, rng).map do |index|
|
|
68
|
+
{ firstname: firsts[index / lasts.size], lastname: lasts[index % lasts.size] }
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
56
72
|
# The whole frequency-ordered pool, frozen. The name strings stay mutable.
|
|
57
73
|
def first_names(country:, gender:, variant: nil, script: :latin)
|
|
58
74
|
pool(country, variant, PoolSchema.gender_key(gender), script)
|
|
@@ -79,6 +95,20 @@ class NameBank
|
|
|
79
95
|
|
|
80
96
|
private
|
|
81
97
|
|
|
98
|
+
# Partial Fisher-Yates over the space of pair indices, with the swaps kept in
|
|
99
|
+
# a Hash so that the untouched majority of a two-million-pair space is never
|
|
100
|
+
# built. Drawing every pair a country has takes count steps; drawing at random
|
|
101
|
+
# until enough distinct pairs turn up would stall on the last few.
|
|
102
|
+
def distinct_indices(available, count, rng)
|
|
103
|
+
swapped = {}
|
|
104
|
+
Array.new(count) do |drawn|
|
|
105
|
+
index = rng.rand(drawn...available)
|
|
106
|
+
picked = swapped.fetch(index, index)
|
|
107
|
+
swapped[index] = swapped.fetch(drawn, drawn)
|
|
108
|
+
picked
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
82
112
|
def pool(country, variant, key, script)
|
|
83
113
|
names = names_for_script(@store.pools(country, variant), key, script)
|
|
84
114
|
raise EmptyPool, "#{country}/#{script}" if names.empty?
|