├── .eslintignore ├── assets └── nozbe_demo.gif ├── demo ├── README.md ├── src │ ├── testdata │ │ ├── README.md │ │ ├── index.js │ │ ├── companies.js │ │ └── usernames.js │ ├── index.js │ ├── index.css │ ├── App.css │ └── App.js ├── .gitignore ├── public │ └── index.html └── package.json ├── src ├── react │ ├── Highlight │ │ ├── platform.js │ │ ├── platform.native.js │ │ ├── index.d.ts │ │ └── index.js │ ├── index.d.ts │ ├── index.js │ ├── useFuzzySearchList.d.ts │ └── useFuzzySearchList.js ├── normalizeText.d.ts ├── normalizeText.js ├── __tests__ │ ├── normalizeText.js │ └── test.js ├── index.d.ts ├── index.js └── impl.js ├── react.d.ts ├── CHANGELOG.md ├── .gitignore ├── .prettierrc ├── tsconfig.json ├── jest.config.js ├── PUBLISHING.md ├── .flowconfig ├── .github └── workflows │ └── ci.yml ├── react.js ├── babel.config.js ├── LICENSE ├── package.json ├── .eslintrc.js └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | flow-typed/ 4 | dev/ 5 | -------------------------------------------------------------------------------- /assets/nozbe_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nozbe/microfuzz/HEAD/assets/nozbe_demo.gif -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | # Running the demo locally 2 | 3 | ``` 4 | yarn install 5 | yarn start 6 | ``` 7 | -------------------------------------------------------------------------------- /demo/src/testdata/README.md: -------------------------------------------------------------------------------- 1 | `testdata.js` borrowed from https://github.com/farzher/fuzzysort by Stephen Kamenar 2 | -------------------------------------------------------------------------------- /src/react/Highlight/platform.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export const TextElement: string | React$ComponentType<*> = 'span' 4 | -------------------------------------------------------------------------------- /react.d.ts: -------------------------------------------------------------------------------- 1 | export { Highlight, createHighlightComponent, useFuzzySearchList } from './dist/react' 2 | export type { UseFuzzySearchListOptions } from './dist/react' 3 | -------------------------------------------------------------------------------- /src/react/Highlight/platform.native.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | // $FlowFixMe[cannot-resolve-module] 3 | import { Text } from 'react-native' 4 | 5 | export const TextElement: string | React$ComponentType<*> = Text 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## Unreleased 6 | 7 | **New features** 8 | 9 | **Performance** 10 | 11 | **Bug fixes** 12 | 13 | **Other** 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | .cache/ 4 | node_modules/ 5 | 6 | npm-debug.log 7 | yarn-error.log 8 | lerna-debug.log 9 | 10 | dist/ 11 | dev/ 12 | package/ 13 | docs/**/*.map 14 | 15 | *.tgz 16 | coverage/ 17 | 18 | .vscode 19 | -------------------------------------------------------------------------------- /src/react/index.d.ts: -------------------------------------------------------------------------------- 1 | export { default as Highlight, createHighlightComponent } from './Highlight' 2 | export { default as useFuzzySearchList } from './useFuzzySearchList' 3 | 4 | export type { UseFuzzySearchListOptions } from './useFuzzySearchList' 5 | -------------------------------------------------------------------------------- /src/react/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export { default as Highlight, createHighlightComponent } from './Highlight' 4 | export { default as useFuzzySearchList } from './useFuzzySearchList' 5 | 6 | export type { UseFuzzySearchListOptions } from './useFuzzySearchList' 7 | -------------------------------------------------------------------------------- /src/normalizeText.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Normalizes text so that it's suitable to comparisons, sorting, search, etc. by: 3 | * - turning into lowercase 4 | * - removing diacritics 5 | * - removing extra whitespace 6 | */ 7 | export default function normalizeText(string: string): string 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel", 3 | "printWidth": 100, 4 | "trailingComma": "all", 5 | "singleQuote": true, 6 | "semi": false, 7 | "overrides": [ 8 | { 9 | "files": "*.ts", 10 | "options": { 11 | "parser": "typescript" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "declaration": true, 7 | "emitDeclarationOnly": true, 8 | "experimentalDecorators": true 9 | }, 10 | "include": [ 11 | "src/**/*" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /demo/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import './index.css' 4 | import App from './App' 5 | 6 | const root = ReactDOM.createRoot(document.getElementById('root')) 7 | root.render( 8 | 9 | 10 | , 11 | ) 12 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: true, 3 | bail: true, 4 | rootDir: __dirname, 5 | modulePaths: ['/src'], 6 | moduleDirectories: ['/node_modules'], 7 | restoreMocks: true, 8 | moduleFileExtensions: ['js'], 9 | testEnvironment: 'node', 10 | modulePathIgnorePatterns: ['/dist', '/dev'], 11 | cacheDirectory: '.cache/jest', 12 | } 13 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /demo/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /PUBLISHING.md: -------------------------------------------------------------------------------- 1 | # Publishing 2 | 3 | ```bash 4 | yarn ci:check 5 | yarn build 6 | # update changelog&readme 7 | npm version 1.1.1-1 8 | 9 | # publish as prerelease 10 | npm publish --tag next 11 | git push && git push --tags 12 | 13 | # publish normally 14 | npm publish 15 | git push && git push --tags 16 | 17 | # update docs 18 | git checkout gh-pages 19 | git merge main 20 | cd demo && yarn gh-pages 21 | # commit result and push 22 | ``` 23 | -------------------------------------------------------------------------------- /demo/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | Microfuzz demo 12 | 13 | 14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | /.cache 3 | /src/__tests__ 4 | /dist 5 | /dev 6 | /package 7 | /examples 8 | .*/node_modules/react\-native/.* 9 | .*/node_modules/resolve/test/resolver/** 10 | 11 | [include] 12 | 13 | [libs] 14 | ; flow-typed/ 15 | 16 | [options] 17 | server.max_workers=8 18 | emoji=true 19 | merge_timeout=120 20 | react.runtime=automatic 21 | exact_by_default=false 22 | 23 | # fixes Flow on ARM 24 | sharedmemory.heap_size=4000000000 25 | 26 | [version] 27 | >=0.199.0 28 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: main 7 | 8 | jobs: 9 | ci-check: 10 | runs-on: ubuntu-latest 11 | name: Check CI 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: Set Node.js version 15 | uses: actions/setup-node@v3 16 | with: 17 | node-version: 16.x 18 | - uses: actions/cache@v2 19 | with: 20 | path: 'node_modules' 21 | key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} 22 | - run: yarn up 23 | - run: yarn ci:check 24 | -------------------------------------------------------------------------------- /react.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | // TODO: What's the current best practice for allowing `@nozbe/microfuzz/react` imports without overcomplicating 4 | // build setup for such a tiny library that just works with everything? I tried declaring `exports` on package.json 5 | // but it seems broken on my setup? 6 | 7 | // $FlowFixMe[cannot-resolve-module] 8 | const { Highlight, createHighlightComponent, useFuzzySearchList } = require('./dist/react') 9 | 10 | module.exports = { Highlight, createHighlightComponent, useFuzzySearchList } 11 | 12 | // $FlowFixMe[cannot-resolve-module] 13 | /*:: export type { UseFuzzySearchListOptions } from './dist/react' */ 14 | -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^18.2.0", 7 | "react-dom": "^18.2.0", 8 | "react-scripts": "5.0.1", 9 | "@nozbe/microfuzz": "0.1.5" 10 | }, 11 | "homepage": "https://nozbe.github.io/microfuzz/", 12 | "scripts": { 13 | "start": "react-scripts start", 14 | "build": "react-scripts build", 15 | "gh-pages": "yarn build && cd .. && rm -fr docs && cp -r demo/build docs && touch docs/.nojekyll", 16 | "eject": "react-scripts eject" 17 | }, 18 | "eslintConfig": { 19 | "extends": [ 20 | "react-app" 21 | ] 22 | }, 23 | "browserslist": { 24 | "production": [ 25 | ">0.2%", 26 | "not dead", 27 | "not op_mini all" 28 | ], 29 | "development": [ 30 | "last 1 chrome version", 31 | "last 1 firefox version", 32 | "last 1 safari version" 33 | ] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@babel/preset-env'], 3 | plugins: ['@babel/plugin-transform-flow-comments', '@babel/plugin-transform-react-jsx'], 4 | assumptions: { 5 | // arrayLikeIsIterable: true, 6 | constantReexports: true, 7 | constantSuper: true, 8 | enumerableModuleMeta: true, 9 | ignoreFunctionLength: true, 10 | ignoreToPrimitiveHint: true, 11 | iterableIsArray: true, 12 | mutableTemplateObject: true, 13 | noClassCalls: true, 14 | noDocumentAll: true, 15 | noIncompleteNsImportDetection: true, 16 | noNewArrows: true, 17 | objectRestNoSymbols: true, 18 | // privateFieldsAsProperties: true, 19 | // privateFieldsAsSymbols: true, 20 | pureGetters: true, 21 | setClassMethods: true, 22 | setComputedProperties: true, 23 | setPublicClassFields: true, 24 | setSpreadProperties: true, 25 | skipForOfIteratorClosing: true, 26 | superIsCallableConstructor: true, 27 | }, 28 | } 29 | -------------------------------------------------------------------------------- /src/normalizeText.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | const diacriticsRegex = /[\u0300-\u036f]/g 4 | const regexŁ = /ł/g 5 | const regexÑ = /ñ/g 6 | 7 | /** 8 | * Normalizes text so that it's suitable to comparisons, sorting, search, etc. by: 9 | * - turning into lowercase 10 | * - removing diacritics 11 | * - removing extra whitespace 12 | */ 13 | export default function normalizeText(string: string): string { 14 | return ( 15 | string 16 | .toLowerCase() 17 | // get rid of diacritics 18 | // https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript 19 | // yeah, it's not perfect, but 97% is good enough and it doesn't seem worth it to add in a whole 20 | // library for this 21 | .normalize('NFD') 22 | .replace(diacriticsRegex, '') 23 | // fix letters that unicode considers separate, not letters with diacritics 24 | .replace(regexŁ, 'l') 25 | .replace(regexÑ, 'n') 26 | .trim() 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /src/react/useFuzzySearchList.d.ts: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { type FuzzyResult, type FuzzySearchStrategy } from '../index' 3 | 4 | export type UseFuzzySearchListOptions = { 5 | list: T[] 6 | key?: string 7 | getText?: (item: T) => Array 8 | queryText: string 9 | mapResultItem: (result: FuzzyResult) => U 10 | strategy?: FuzzySearchStrategy 11 | } 12 | 13 | /** 14 | * Hook for fuzzy searching `list` against `queryText` and mapping the results with `mapResultItem`. 15 | * 16 | * If `queryText` is blank, `list` is returned in whole. 17 | * 18 | * See `createFuzzySearch` for more details. This hook simply wraps it (with memoization) in a React hook. 19 | * 20 | * For best performance, `getText` and `mapResultItem` functions should be memoized by the user. 21 | */ 22 | export default function useFuzzySearchList({ 23 | list, 24 | key, 25 | getText, 26 | queryText, 27 | mapResultItem, 28 | strategy, 29 | }: UseFuzzySearchListOptions): U[] 30 | -------------------------------------------------------------------------------- /demo/src/testdata/index.js: -------------------------------------------------------------------------------- 1 | import testdata from './testdata' 2 | import companies from './companies' 3 | import { firstNames, lastNames } from './usernames' 4 | 5 | // Returns a random integer between `min` and `max` (inclusive) 6 | function randomNumber(min, max) { 7 | return Math.round(Math.random() * (max - min)) + min 8 | } 9 | 10 | function randomElement(array) { 11 | return array[randomNumber(0, array.length - 1)] 12 | } 13 | 14 | function capitalize(string) { 15 | return string.charAt(0).toUpperCase() + string.slice(1) 16 | } 17 | 18 | function generatePeople(n) { 19 | return Array(n) 20 | .fill(0) 21 | .map(() => { 22 | const firstName = capitalize(randomElement(firstNames)) 23 | const lastName = capitalize(randomElement(lastNames)) 24 | 25 | return `${firstName} ${lastName}` 26 | }) 27 | } 28 | 29 | const people = generatePeople(2137) 30 | 31 | // eslint-disable-next-line import/no-anonymous-default-export 32 | export default { 33 | companies, 34 | people, 35 | ...testdata, 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Nozbe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/react/Highlight/index.d.ts: -------------------------------------------------------------------------------- 1 | import { FunctionComponent } from 'react' 2 | import type { HighlightRanges } from '../../index' 3 | 4 | type Style = { [key: string]: any } | null | undefined 5 | type ClassName = string | null | undefined 6 | 7 | type Props = { 8 | text: string 9 | ranges: HighlightRanges | null 10 | style?: Style 11 | className?: ClassName 12 | } 13 | 14 | declare const FullSelection: HighlightRanges 15 | 16 | /** 17 | * Highlights `text` at `ranges`. 18 | * 19 | * To override default styling, pass `style` and `className` or use `createHighlightComponent` to 20 | * create a custom component with default styles overriden. 21 | * 22 | * To higlight all of text, pass `ranges={Highlight.FullSelection}`. 23 | */ 24 | declare const Highlight: FunctionComponent & { 25 | FullSelection: typeof FullSelection 26 | } 27 | export default Highlight 28 | 29 | /** 30 | * Creates a variant of `` component with default styles set to `customStyle` and 31 | * `customClassName`. 32 | */ 33 | export function createHighlightComponent( 34 | customStyle: Style, 35 | customClassName: ClassName, 36 | ): FunctionComponent & { 37 | FullSelection: typeof FullSelection 38 | } 39 | -------------------------------------------------------------------------------- /demo/src/App.css: -------------------------------------------------------------------------------- 1 | .app { 2 | margin: 0 auto; 3 | /* width: 100vw; */ 4 | padding: 15px; 5 | max-width: 850px; 6 | } 7 | 8 | .tabs-container { 9 | display: flex; 10 | align-items: center; 11 | gap: 10px; 12 | margin: 20px 0; 13 | } 14 | 15 | .tabs { 16 | padding-left: 0; 17 | margin-bottom: 15px; 18 | border: 1px solid #ADBBC7; 19 | border-top-color: #7F8B99; 20 | border-left-color: #7F8B99; 21 | border-radius: 5px; 22 | overflow: hidden; 23 | margin: 0; 24 | } 25 | 26 | .tabs ul { 27 | display: flex; 28 | justify-content: left; 29 | flex-wrap: wrap; 30 | margin: 0; 31 | padding: 0; 32 | } 33 | 34 | .tabs li { 35 | list-style: none; 36 | margin: 0; 37 | } 38 | 39 | .tabs button { 40 | appearance: none; 41 | border: none; 42 | background: none; 43 | font-size: 15px; 44 | border: 1px solid #7F8B99; 45 | border-left: 0; 46 | padding: 4px 10px; 47 | margin-top: -1px; 48 | } 49 | 50 | .tabs li:last-child button { 51 | border-bottom-right-radius: 3px; 52 | } 53 | 54 | .tabs button:hover { 55 | background: #E6ECF0; 56 | cursor: pointer; 57 | } 58 | 59 | .tabs button[data-selected="true"] { 60 | background: #E6ECF0; 61 | } 62 | 63 | .search input { 64 | width: 100%; 65 | border: 1px solid #7F8B99; 66 | border-radius: 8px; 67 | font-size: 24px; 68 | padding: 10px 18px; 69 | box-shadow: 1px 1px 0 #ADBBC7; 70 | } 71 | 72 | .warning { 73 | background-color: rgba(255, 150, 0, .15); 74 | border: 1px solid rgb(255, 150, 0); 75 | border-radius: 8px; 76 | padding: 10px 18px; 77 | } 78 | -------------------------------------------------------------------------------- /src/__tests__/normalizeText.js: -------------------------------------------------------------------------------- 1 | import normalize from '../normalizeText' 2 | 3 | describe('normalizeText()', () => { 4 | it(`normalizes english strings`, () => { 5 | expect(normalize('abcdef')).toBe('abcdef') 6 | expect(normalize(' qwerty foo ')).toBe('qwerty foo') 7 | expect(normalize('\nfoo\n\t')).toBe('foo') 8 | expect(normalize('Fo0 BAR')).toBe('fo0 bar') 9 | expect(normalize(' x ')).toBe('x') // hard space 10 | }) 11 | it(`removes polish diacritics`, () => { 12 | expect(normalize('ąśćźżółłńę')).toBe('asczzollne') 13 | expect(normalize('ĄŚĆŹŻÓŁŃĘ')).toBe('asczzolne') 14 | }) 15 | it(`removes other latin diacritics`, () => { 16 | expect(normalize('áéíóúýčďěňřšťžů')).toBe('aeiouycdenrstzu') // Czech 17 | expect(normalize('ẞäöü')).toBe('ßaou') // German 18 | expect(normalize('áéíóúüññ')).toBe('aeiouunn') // Spanish 19 | expect(normalize('Radziu̙̙̙̙̙̙̙̙̙̙̙̙̙̙̙̙͛͛͛͛͛͛͛͛͛͛͛͛ͅ')).toBe('radziu') // zalgo/dbag 20 | }) 21 | it(`normalizes Russian script`, () => { 22 | // quirks: Ё->Е, Й->И 23 | expect(normalize('БВГДЖЗКЛМНПРСТФХЦЧШЩАЕЁИОУЫЭЮЯЙЬЪ')).toBe('бвгджзклмнпрстфхцчшщаееиоуыэюяиьъ') 24 | }) 25 | it(`does kinda nothing to Chinese, Japanese`, () => { 26 | expect(normalize(' ラドクリフ、マラソン五輪代表に 1万メートル出場にも含ふくみ ')).toBe( 27 | 'ラドクリフ、マラソン五輪代表に 1万メートル出場にも含ふくみ', 28 | ) 29 | expect(normalize(' 日本語 ')).toBe('日本語') 30 | }) 31 | it(`decomposes Hangul`, () => { 32 | // looks the same, but the Unicode encoding is different! 33 | expect(normalize(' 한국어 ')).toBe('한국어') 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /src/react/useFuzzySearchList.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react' 4 | import createFuzzySearch, { type FuzzyResult, type FuzzySearchStrategy } from '../index' 5 | 6 | export type UseFuzzySearchListOptions = $Exact<{ 7 | list: T[], 8 | key?: string, 9 | getText?: (T) => Array, 10 | queryText: string, 11 | mapResultItem: (FuzzyResult) => U, 12 | strategy?: FuzzySearchStrategy, 13 | }> 14 | 15 | /** 16 | * Hook for fuzzy searching `list` against `queryText` and mapping the results with `mapResultItem`. 17 | * 18 | * If `queryText` is blank, `list` is returned in whole. 19 | * 20 | * See `createFuzzySearch` for more details. This hook simply wraps it (with memoization) in a React hook. 21 | * 22 | * For best performance, `getText` and `mapResultItem` functions should be memoized by the user. 23 | */ 24 | export default function useFuzzySearchList({ 25 | list, 26 | key, 27 | getText, 28 | queryText, 29 | mapResultItem, 30 | strategy, 31 | }: UseFuzzySearchListOptions): U[] { 32 | const performSearch = React.useMemo( 33 | () => createFuzzySearch(list, { key, getText, strategy }), 34 | [list, key, getText, strategy], 35 | ) 36 | 37 | const searchResults = React.useMemo(() => { 38 | return queryText 39 | ? performSearch(queryText).map(mapResultItem) 40 | : list.map((item) => 41 | mapResultItem({ 42 | item, 43 | score: Number.POSITIVE_INFINITY, 44 | matches: [], 45 | }), 46 | ) 47 | }, [list, mapResultItem, performSearch, queryText]) 48 | 49 | return searchResults 50 | } 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nozbe/microfuzz", 3 | "description": "A tiny, simple, fast fuzzy search library", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "up": "yarn", 7 | "ci:check": "concurrently -n 'test,eslint,flow,ts' -c 'auto' 'npm run test' 'npm run eslint' 'npm run flow' 'npm run ts' --kill-others-on-fail", 8 | "ci": "npm run ci:check", 9 | "flow": "flow check --max-warnings=0 --color=always", 10 | "ts": "tsc", 11 | "eslint": "eslint ./src -c ./.eslintrc.js --cache --cache-location ./.cache/.eslintcache", 12 | "test": "jest", 13 | "build": "rm -fr dist && babel src --out-dir dist --ignore 'src/__tests__' --copy-files --no-copy-ignored" 14 | }, 15 | "keywords": [ 16 | "fuzzy search", 17 | "command palette", 18 | "jump to", 19 | "autocomplete", 20 | "react" 21 | ], 22 | "author": { 23 | "name": "Radek Pietruszewski", 24 | "email": "this.is@radex.io", 25 | "url": "https://radex.io" 26 | }, 27 | "license": "MIT", 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/Nozbe/microfuzz.git" 31 | }, 32 | "bugs": { 33 | "url": "https://github.com/Nozbe/microfuzz/issues" 34 | }, 35 | "homepage": "https://github.com/Nozbe/microfuzz#readme", 36 | "publishConfig": { 37 | "registry": "https://registry.npmjs.org/", 38 | "access": "public" 39 | }, 40 | "files": [ 41 | "dist", 42 | "src", 43 | "react.js", 44 | "react.d.ts", 45 | "LICENSE", 46 | "README.md", 47 | "CHANGELOG.md" 48 | ], 49 | "main": "dist/index.js", 50 | "types": "dist/index.d.ts", 51 | "devDependencies": { 52 | "@babel/cli": "^7.21.0", 53 | "@babel/core": "^7.21.0", 54 | "@babel/eslint-parser": "^7.19.1", 55 | "@babel/plugin-transform-flow-comments": "^7.22.5", 56 | "@babel/plugin-transform-react-jsx": "^7.21.0", 57 | "@babel/preset-env": "^7.22.9", 58 | "@typescript-eslint/eslint-plugin": "^5.53.0", 59 | "@typescript-eslint/parser": "^5.53.0", 60 | "chalk": "^4.1.0", 61 | "chokidar-cli": "^3.0.0", 62 | "concurrently": "^7.6.0", 63 | "eslint": "^8.34.0", 64 | "eslint-config-airbnb": "^19.0.4", 65 | "eslint-config-prettier": "^8.6.0", 66 | "eslint-plugin-flowtype": "^8.0.3", 67 | "eslint-plugin-import": "^2.22.1", 68 | "eslint-plugin-jest": "^27.2.1", 69 | "eslint-plugin-jsx-a11y": "^6.7.1", 70 | "eslint-plugin-react": "^7.32.2", 71 | "eslint-plugin-react-hooks": "^4.6.0", 72 | "flow-bin": "0.201.0", 73 | "jest": "^29", 74 | "patch-package": "^6.5.1", 75 | "prettier": "^2.8.8", 76 | "react": "^18.2.0", 77 | "react-native": "^0.72.3", 78 | "typescript": "^4.5.0" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/react/Highlight/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { memo, Fragment, type Node } from 'react' 3 | import { type HighlightRanges } from '../../index' 4 | import { TextElement } from './platform' 5 | 6 | type Style = ?{ [string]: mixed } 7 | type ClassName = ?string 8 | 9 | type Props = $Exact<{ 10 | text: string, 11 | ranges: ?HighlightRanges, 12 | style?: Style, 13 | className?: ClassName, 14 | }> 15 | 16 | const FullSelection: HighlightRanges = [[0, Number.MAX_VALUE]] 17 | 18 | const defaultStyle: Style = { backgroundColor: 'rgba(245,220,0,.25)' } 19 | 20 | /** 21 | * Highlights `text` at `ranges`. 22 | * 23 | * To override default styling, pass `style` and `className` or use `createHighlightComponent` to 24 | * create a custom component with default styles overriden. 25 | * 26 | * To higlight all of text, pass `ranges={Highlight.FullSelection}`. 27 | */ 28 | const Highlight: React$StatelessFunctionalComponent = (props) => { 29 | const { text, ranges, style, className } = props 30 | 31 | if (!ranges) { 32 | return text 33 | } 34 | 35 | let lastHighlightedIndex = 0 36 | const nodes: Array = [] 37 | 38 | ranges.forEach(([start, end]) => { 39 | // Broken range, ignore 40 | if (start < lastHighlightedIndex || end < start) { 41 | // eslint-disable-next-line no-console 42 | console.warn(`Broken range in : ${start}-${end}, last: ${lastHighlightedIndex}`) 43 | return 44 | } 45 | 46 | if (start > lastHighlightedIndex) { 47 | nodes.push( 48 | 49 | {text.slice(lastHighlightedIndex, start)} 50 | , 51 | ) 52 | } 53 | nodes.push( 54 | 55 | {text.slice(start, end + 1)} 56 | , 57 | ) 58 | lastHighlightedIndex = end + 1 59 | }) 60 | 61 | if (text.length > lastHighlightedIndex) { 62 | nodes.push({text.slice(lastHighlightedIndex, text.length)}) 63 | } 64 | 65 | return nodes 66 | } 67 | 68 | type HighlightExport = React$ComponentType & 69 | $Exact<{ 70 | FullSelection: typeof FullSelection, 71 | }> 72 | 73 | const ExportedHighlight: HighlightExport = Object.assign((memo(Highlight): any), { 74 | FullSelection, 75 | }) 76 | 77 | export default ExportedHighlight 78 | 79 | /** 80 | * Creates a variant of `` component with default styles set to `customStyle` and 81 | * `customClassName`. 82 | */ 83 | export function createHighlightComponent( 84 | customStyle: Style, 85 | customClassName: ClassName, 86 | ): HighlightExport { 87 | const HighlightComponent = ({ style, className, ...props }: Props) => 88 | Highlight({ ...props, style: style ?? customStyle, className: className ?? customClassName }) 89 | HighlightComponent.FullSelection = FullSelection 90 | // $FlowFixMe[incompatible-exact] 91 | return HighlightComponent 92 | } 93 | -------------------------------------------------------------------------------- /demo/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './App.css' 3 | import { useFuzzySearchList, Highlight } from '@nozbe/microfuzz/react' 4 | import testdata from './testdata' 5 | 6 | const testdataKeys = Object.keys(testdata) 7 | const strategies = ['off', 'smart', 'aggressive'] 8 | 9 | const mapResultItem = ({ item, matches: [highlightRanges] }) => [item, highlightRanges] 10 | 11 | function App() { 12 | const [datasetName, setDatasetName] = React.useState('companies') 13 | const dataset = testdata[datasetName] 14 | 15 | const [queryText, setQueryText] = React.useState('') 16 | const [strategy, setStrategy] = React.useState('smart') 17 | 18 | const b4 = performance.now() 19 | const filtered = useFuzzySearchList({ list: dataset, queryText, mapResultItem, strategy }) 20 | const filterTime = performance.now() - b4 21 | 22 | return ( 23 |
24 |

microfuzz demo

25 |

26 | Find out about microfuzz on GitHub. 27 |

28 |
29 | Dataset: 30 |
31 |
    32 | {testdataKeys.map((aDataset) => ( 33 |
  • 34 | 40 |
  • 41 | ))} 42 |
43 |
44 |
45 |
46 | Fuzzy search strategy: 47 |
48 |
    49 | {strategies.map((aStrategy) => ( 50 |
  • 51 | 57 |
  • 58 | ))} 59 |
60 |
61 |
62 |
63 | setQueryText(e.target.value)} 67 | placeholder={`Start typing to search ${datasetName}…`} 68 | autoFocus 69 | /> 70 |
71 | {dataset.length >= 10_000 ? ( 72 |

73 | Note: microfuzz works best with datasets below 10,000 items (this one has {dataset.length} 74 | ) 75 |

76 | ) : null} 77 |
    78 | {filtered.slice(0, 40).map(([item, highlightRanges]) => ( 79 |
  • 80 | 81 |
  • 82 | ))} 83 |
84 |

85 | Matched {filtered.length} items (out of {dataset.length}) in {filterTime.toFixed(1)} ms. 86 |

87 |
88 | ) 89 | } 90 | 91 | export default App 92 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | env: { 3 | es6: true, 4 | jest: true, 5 | }, 6 | plugins: ['flowtype', '@typescript-eslint'], 7 | extends: [ 8 | // https://github.com/airbnb/javascript 9 | 'airbnb', 10 | 'plugin:flowtype/recommended', 11 | 'prettier', 12 | 'plugin:jest/recommended', 13 | ], 14 | parser: '@babel/eslint-parser', 15 | ignorePatterns: 'examples/typescript/**/*.ts', 16 | settings: { 17 | flowtype: { 18 | onlyFilesWithFlowAnnotation: true, 19 | }, 20 | }, 21 | rules: { 22 | curly: ['error', 'all'], 23 | 'class-methods-use-this': 'off', 24 | 'comma-dangle': ['error', 'always-multiline'], 25 | 'no-console': ['error'], 26 | 'no-unused-expressions': 'off', 27 | 'no-param-reassign': [ 28 | 'error', 29 | { 30 | props: false, 31 | }, 32 | ], 33 | 'no-useless-escape': 'off', 34 | 'func-names': 'off', 35 | 'no-underscore-dangle': 'off', 36 | 'no-use-before-define': 'off', 37 | 'no-unused-vars': [ 38 | 'error', 39 | { 40 | argsIgnorePattern: '^_', 41 | }, 42 | ], 43 | 'no-else-return': [ 44 | 'error', 45 | { 46 | allowElseIf: true, 47 | }, 48 | ], 49 | // formatting (off - formatting is Prettier's job) 50 | semi: ['error', 'never'], 51 | 'arrow-parens': 'off', 52 | 'react/jsx-closing-bracket-location': 'off', 53 | 'react/jsx-first-prop-new-line': 'off', 54 | 'operator-linebreak': 'off', 55 | 'object-curly-newline': 'off', 56 | 'function-paren-newline': 'off', 57 | 'max-classes-per-file': 'off', 58 | camelcase: 'off', 59 | 'react/jsx-indent': 'off', 60 | quotes: 'off', 61 | 'react/jsx-curly-newline': 'off', 62 | 'lines-between-class-members': 'off', 63 | 'one-var': 'off', 64 | 'arrow-body-style': 'off', 65 | // react 66 | 'react/prop-types': 'off', 67 | 'react/jsx-filename-extension': 'off', 68 | 'react/jsx-indent-props': ['error'], 69 | 'react/prefer-stateless-function': [ 70 | 1, 71 | { 72 | ignorePureComponents: true, 73 | }, 74 | ], 75 | 'react/jsx-boolean-value': ['error', 'always'], 76 | 'react/no-unused-prop-types': 'off', 77 | 'react/destructuring-assignment': 'off', 78 | 'react/jsx-one-expression-per-line': 'off', 79 | 'import/prefer-default-export': 'off', 80 | 'import/named': 'off', // doesn't seem to work with Flow 81 | 'import/no-extraneous-dependencies': 'off', 82 | 'import/no-cycle': 'error', 83 | 'jest/no-large-snapshots': 'warn', 84 | 'jest/no-disabled-tests': 'off', 85 | 'jest/expect-expect': 'off', 86 | 'global-require': 'off', 87 | 'no-plusplus': 'off', 88 | 'prefer-object-spread': 'off', 89 | 'react/jsx-props-no-spreading': 'off', 90 | 'react/jsx-no-bind': 'off', 91 | 'flowtype/space-after-type-colon': 'off', 92 | 'flowtype/generic-spacing': 'off', 93 | 'flowtype/delimiter-dangle': ['error', 'always-multiline'], 94 | 'flowtype/require-return-type': [ 95 | 'error', 96 | 'always', 97 | { 98 | excludeArrowFunctions: true, 99 | annotateUndefined: 'always', 100 | }, 101 | ], 102 | }, 103 | overrides: [ 104 | { 105 | files: ['src/**/*.js'], 106 | excludedFiles: ['*integrationTest.js', '*test.js', '**/__tests__/**', '*test.*.js'], 107 | rules: { 108 | 'flowtype/require-valid-file-annotation': ['error', 'always'], 109 | }, 110 | }, 111 | { 112 | files: ['src/**/*.ts', 'examples/typescript/*.ts'], 113 | parser: '@typescript-eslint/parser', 114 | rules: { 115 | 'flowtype/no-types-missing-file-annotation': 'off', 116 | 'no-unused-vars': 'off', 117 | }, 118 | }, 119 | ], 120 | globals: { 121 | document: true, 122 | window: true, 123 | self: true, 124 | globalThis: true, 125 | }, 126 | } 127 | 128 | module.exports = config 129 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | General idea: 4 | - case-insensitive 5 | - diacritics-insensitive 6 | - works with Latin script, Cyrillic, rudimentary CJK support 7 | - limited fuzzing: matches query letters in order, but they don't have to be consecutive 8 | (but no or very limited transposition or removals are allowed - they cause more confusion than help) 9 | - no FTS-style stemming, soundex, levenstein, autocorrect - query can be lazy, but not sloppy 10 | - sort by how well text matches the query 11 | 12 | Think VS Code/Dash-like search, not Google-like search 13 | 14 | ## Sorting 15 | 16 | Results are sorted in roughly this order: 17 | 18 | 1. [0] Exact match (found === query) 19 | 2. [0.1] Full match (like exact, but case&diacritics-insensitive) 20 | 3. [0.5] "Starts with" match 21 | 4. [0.9] Contains query (starting at word boundary) exactly 22 | 5. [1] Contains query (starting at word boundary) 23 | 6. [1.5+] Contains query words (space separated) in any order 24 | 7. [2] Contains query 25 | 8. [2+] Contains letters -- the fewer chunks, the better 26 | 27 | Note: 28 | - Lower score is better (think "error level") 29 | - Exact scoring values are not an API contract, can change between releases 30 | - Secondary sort criteria (for equal fuzzy sort) is input order 31 | 32 | */ 33 | 34 | /** 35 | * Range of indices in a string, [index of first character, index of last character] 36 | */ 37 | export type Range = [number, number] 38 | 39 | /** 40 | * List of character ranges in a string that should be highlighted 41 | */ 42 | export type HighlightRanges = Range[] 43 | 44 | /** 45 | * List of fuzzy search matches (ranges of matching characters) for an item. This usually has one item, but can have more if `getText` 46 | * was used to return multiple strings for an item. 47 | */ 48 | export type FuzzyMatches = Array 49 | 50 | /** 51 | * Result of fuzzy matching `queryText` against an item. 52 | * 53 | * `score` - lower = better match (think "error level") 54 | */ 55 | export type FuzzyResult = { item: T; score: number; matches: FuzzyMatches } 56 | 57 | /** 58 | * Strategy for fuzzy search 59 | * 60 | * 'off' - no fuzzy search, only matches if item contains/starts with query/contains query words 61 | * 'smart' - (default) matches letters in order, but poor quality matches are ignored 62 | * 'aggressive' - matches letters in order with no restrictions (classic fuzzy search) 63 | */ 64 | export type FuzzySearchStrategy = 'off' | 'smart' | 'aggressive' 65 | 66 | export type FuzzySearchOptions = { 67 | key?: string 68 | getText?: (unknown) => Array 69 | strategy?: FuzzySearchStrategy 70 | } 71 | 72 | export type FuzzySearcher = (string) => Array> 73 | 74 | /** 75 | * Creates a fuzzy search function that can be used to search `list` by passing `queryText` to it: 76 | * 77 | * ```js 78 | * const fuzzySearch = createFuzzySearch(list) 79 | * const results = fuzzySearch(queryText) 80 | * ``` 81 | * 82 | * Only matching items will be returned, and they will be sorted by how well they match `queryText`. 83 | * 84 | * If `list` is an array of strings, it can be searched as-is. Otherwise pass to `options`: 85 | * 86 | * ```js 87 | * // search by `text` property 88 | * { key: 'text' } 89 | * // OR: 90 | * { getText: (item) => [item.text] } 91 | * // search by multiple properties: 92 | * { getText: (item) => [item.text, item.otherText] } 93 | * ``` 94 | * 95 | * If you use React, use `useFuzzySearchList` hook for convenience. 96 | */ 97 | export function createFuzzySearch( 98 | list: Element[], 99 | options?: FuzzySearchOptions, 100 | ): FuzzySearcher 101 | 102 | export default createFuzzySearch 103 | 104 | /** 105 | * Runs a one-off fuzzy search matching on `text` against `queryText`. 106 | * 107 | * Use `createFuzzySearch` whenever you have a list of items to search. 108 | */ 109 | export function fuzzyMatch(text: string, queryText: string): FuzzyResult | null 110 | 111 | export { default as normalizeText } from './normalizeText' 112 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | /* 4 | 5 | General idea: 6 | - case-insensitive 7 | - diacritics-insensitive 8 | - works with Latin script, Cyrillic, rudimentary CJK support 9 | - limited fuzzing: matches query letters in order, but they don't have to be consecutive 10 | (but no or very limited transposition or removals are allowed - they cause more confusion than help) 11 | - no FTS-style stemming, soundex, levenstein, autocorrect - query can be lazy, but not sloppy 12 | - sort by how well text matches the query 13 | 14 | Think VS Code/Dash-like search, not Google-like search 15 | 16 | ## Sorting 17 | 18 | Results are sorted in roughly this order: 19 | 20 | 1. [0] Exact match (found === query) 21 | 2. [0.1] Full match (like exact, but case&diacritics-insensitive) 22 | 3. [0.5] "Starts with" match 23 | 4. [0.9] Contains query (starting at word boundary) exactly 24 | 5. [1] Contains query (starting at word boundary) 25 | 6. [1.5+] Contains query words (space separated) in any order 26 | 7. [2] Contains query 27 | 8. [2+] Contains letters -- the fewer chunks, the better 28 | 29 | Note: 30 | - Lower score is better (think "error level") 31 | - Exact scoring values are not an API contract, can change between releases 32 | - Secondary sort criteria (for equal fuzzy sort) is input order 33 | 34 | */ 35 | 36 | /** 37 | * Range of indices in a string, [index of first character, index of last character] 38 | */ 39 | export type Range = [number, number] 40 | 41 | /** 42 | * List of character ranges in a string that should be highlighted 43 | */ 44 | export type HighlightRanges = Range[] 45 | 46 | /** 47 | * List of fuzzy search matches (ranges of matching characters) for an item. This usually has one item, but can have more if `getText` 48 | * was used to return multiple strings for an item. 49 | */ 50 | export type FuzzyMatches = Array 51 | 52 | /** 53 | * Result of fuzzy matching `queryText` against an item. 54 | * 55 | * `score` - lower = better match (think "error level") 56 | */ 57 | export type FuzzyResult = $Exact<{ item: T, score: number, matches: FuzzyMatches }> 58 | 59 | /** 60 | * Strategy for fuzzy search 61 | * 62 | * 'off' - no fuzzy search, only matches if item contains/starts with query/contains query words 63 | * 'smart' - (default) matches letters in order, but poor quality matches are ignored 64 | * 'aggressive' - matches letters in order with no restrictions (classic fuzzy search) 65 | */ 66 | export type FuzzySearchStrategy = 'off' | 'smart' | 'aggressive' 67 | 68 | export type FuzzySearchOptions = $Exact<{ 69 | key?: string, 70 | getText?: (any) => Array, 71 | strategy?: FuzzySearchStrategy, 72 | }> 73 | 74 | export type FuzzySearcher = (string) => Array> 75 | 76 | /** 77 | * Creates a fuzzy search function that can be used to search `list` by passing `queryText` to it: 78 | * 79 | * ```js 80 | * const fuzzySearch = createFuzzySearch(list) 81 | * const results = fuzzySearch(queryText) 82 | * ``` 83 | * 84 | * Only matching items will be returned, and they will be sorted by how well they match `queryText`. 85 | * 86 | * If `list` is an array of strings, it can be searched as-is. Otherwise pass to `options`: 87 | * 88 | * ```js 89 | * // search by `text` property 90 | * { key: 'text' } 91 | * // OR: 92 | * { getText: (item) => [item.text] } 93 | * // search by multiple properties: 94 | * { getText: (item) => [item.text, item.otherText] } 95 | * ``` 96 | * 97 | * If you use React, use `useFuzzySearchList` hook for convenience. 98 | */ 99 | export function createFuzzySearch( 100 | list: Element[], 101 | options?: FuzzySearchOptions = ({}: any), 102 | ): FuzzySearcher { 103 | return require('./impl').createFuzzySearchImpl(list, options) 104 | } 105 | 106 | export default createFuzzySearch 107 | 108 | /** 109 | * Runs a one-off fuzzy search matching on `text` against `queryText`. 110 | * 111 | * Use `createFuzzySearch` whenever you have a list of items to search. 112 | */ 113 | export function fuzzyMatch(text: string, queryText: string): ?FuzzyResult { 114 | return require('./impl').fuzzyMatchImpl(text, queryText) 115 | } 116 | 117 | export { default as normalizeText } from './normalizeText' 118 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | microfuzz in action in Nozbe 3 |

4 | 5 |

6 | microfuzz 7 |

8 | 9 |

10 | A tiny, simple, fast JS fuzzy search library 11 |

12 | 13 |

14 | ✨ Easily add power user-friendly search, autocomplete, jump to, command palette to your app. 15 |

16 | 17 |

18 | 19 | MIT License 20 | 21 | 22 | 23 | npm 24 | 25 |

26 | 27 | | | microfuzz | 28 | | - | ------------ | 29 | | 🤓 | **Fuzzy search**. Power users love it | 30 | | 🗜️ | **Tiny**. 2KB gzipped | 31 | | ✅ | **Simple**. Only a few options, reasonable defaults | 32 | | ⚡️ | **Fast**. Filter thousands of items in milliseconds | 33 | | 🧰 | **Framework-agnostic**. Plain JS, no dependencies | 34 | | ⚛️ | **React/React Native** helpers (optional) included | 35 | | ⚠️ | **Static typing** with [Flow](https://flow.org) or [TypeScript](https://typescriptlang.org) | 36 | 37 | ## microfuzz pitch 38 | 39 | General idea of how `microfuzz` works: 40 | 41 | - Case-insensitive and diacritics-insensitive search 42 | - Works with Latin script, Cyrillic, rudimentary CJK support 43 | - Limited fuzzing: matches query letters in order, but they don't have to be consecutive 44 | (but transposition and missing characters are not allowed) 45 | - Some very poor fuzzy matches are rejected by default (see [_Fuzzy search strategies_](#fuzzy-search-strategies)) 46 | - Additionally, matches query _words_ in any order 47 | - NOT full-text search. Stemming, soundex, levenstein, autocorrect are _not_ included 48 | - Sorts by how well text matches the query with simple heuristics (for equal fuzzy score, input 49 | order is preserved, so you can pre-sort array if you want). 50 | - Returns ranges of matching characters for pretty highlighting 51 | - In-memory search, no indexing 52 | 53 | `microfuzz` is not a one-size-fits-all solution (see [_Alternatives to consider_](#alternatives-to-consider)). 54 | 55 | ## Demo 56 | 57 | [**➡️ See demo**](https://nozbe.github.io/microfuzz/) 58 | 59 | ## Using microfuzz (plain JS) 60 | 61 | ```js 62 | import createFuzzySearch from '@nozbe/microfuzz' 63 | 64 | const list = [/* an array of strings to fuzzy search */] 65 | const fuzzySearch = createFuzzySearch(list) 66 | 67 | // Run this whenever search term changes 68 | // Only matching items will be returned, sorted by how well they match `queryText` 69 | const results = fuzzySearch(queryText) 70 | ``` 71 | 72 | This is split into two steps for performance (`createFuzzySearch` pre-processes `list`, and you can cache/memoize function returned by it). 73 | 74 | If `list` is an array of objects: 75 | 76 | ```js 77 | const fuzzySearch = createFuzzySearch(list, { 78 | // search by `name` property 79 | key: 'name', 80 | // search by `description.text` property 81 | getText: (item) => [item.description.text], 82 | // search by multiple properties: 83 | getText: (item) => [item.name, item.description.text] 84 | }) 85 | ``` 86 | 87 | ### Using microfuzz in React 88 | 89 | If you use React or React Native, you can use these optional helpers for convenience: 90 | 91 | ```js 92 | import { useFuzzySearchList, Highlight } from '@nozbe/microfuzz/react' 93 | 94 | // `useFuzzySearchList` simply wraps `createFuzzySearch` with memoization built in 95 | // NOTE: For best performance, `getText` and `mapResultItem` should be memoized by user 96 | const filteredList = useFuzzySearchList({ 97 | list, 98 | // If `queryText` is blank, `list` is returned in whole 99 | queryText, 100 | // optional `getText` or `key`, same as with `createFuzzySearch` 101 | getText: (item) => [item.name], 102 | // arbitrary mapping function, takes `FuzzyResult` as input 103 | mapResultItem: ({ item, score, matches: [highlightRanges] }) => ({ item, highlightRanges }) 104 | }) 105 | 106 | // Render `filteredList`'s labels with matching characters highlighted 107 | filteredList.map(({ item, highlightRanges }) => ( 108 | 109 | 110 | 111 | )) 112 | ``` 113 | 114 | ### Fuzzy search strategies 115 | 116 | You can optionally pass `{ strategy: }` parameter to `createFuzzySearch` / `useFuzzySearchList`: 117 | 118 | - `'off'` - no fuzzy search, only matches if item contains query (or contains query words in any order) 119 | - `'smart'` - (default) matches letters in order, but poor quality matches are ignored 120 | - `'aggressive'` - matches letters in order with no restrictions (classic fuzzy search) 121 | 122 | ## Alternatives to consider 123 | 124 | I wrote `microfuzz` simply because I didn't quite like how other fuzzy search libraries I found worked, **for my use case**. Your mileage may vary. 125 | 126 | It's not the tiniest, the simplest, or the fastest implementation you can find. But it's tiny, simple, and fast enough, while providing fuzzy search heuristics and sorting that I found to work reasonably well in [Nozbe](https://nozbe.com), a project management app, where it's used to filter down or autocomplete lists of short labels — names of projects, sections, tasks, user names, etc. 127 | 128 | By "fast" I mean that on my computer, with a list of ~4500 labels, the first search (one-letter search query) takes ~7ms, while subsequent searches take less than 1.5ms — all in-memory, without indexing. More than fast enough to search on every keystroke without any lag. 129 | 130 | If you have much larger lists to fuzzy-search, you may find the performance unsatisfactory — consider implementations with simpler heuristics or indexing. For very long strings (notes, comments), fuzzy-searching may not be the right strategy — consider Full-Text Search (with indexing) instead. 131 | 132 | Feel free to contribute improvements to sorting heuristics or alternative search strategies (provided that the "fast, simple, tiny" criteria don't suffer too much). 133 | 134 | Alternatives: 135 | 136 | - [Fuse.js](https://github.com/krisk/Fuse) - popular implementation with **many more options**, including extended search and indexing. However, while its scoring (sorting) is much more sophisticated in theory, I found it unsatisfactory in practice. 137 | - [fuzzysort](https://github.com/farzher/fuzzysort) - faster and really good for fuzzy searching lists of file names/file paths, but I don't like its scoring for natural language labels. I borrowed the test data from fuzzysort so you can compare both demos side by side. 138 | - [MiniSearch](https://www.npmjs.com/package/minisearch) 139 | - [fuzzy](https://github.com/mattyork/fuzzy) 140 | - [fuzzy-search](https://github.com/wouterrutgers/fuzzy-search) - an even simpler implementation than microfuzz 141 | - [fuzzysearch](https://github.com/bevacqua/fuzzysearch) - tiniest implementation of the list 142 | 143 | ## Author and license 144 | 145 | **microfuzz** was created by [@Nozbe](https://github.com/Nozbe). 146 | 147 | **microfuzz's** main author and maintainer is [Radek Pietruszewski](https://github.com/radex) ([website](https://radex.io) ⋅ [twitter](https://twitter.com/radexp) ⋅ [engineering posters](https://beamvalley.com)) 148 | 149 | [See all contributors](https://github.com/Nozbe/microfuzz/graphs/contributors). 150 | 151 | microfuzz is available under the MIT license. See the [LICENSE file](https://github.com/Nozbe/microfuzz/LICENSE) for more info. 152 | -------------------------------------------------------------------------------- /src/__tests__/test.js: -------------------------------------------------------------------------------- 1 | import createFuzzySearch, { normalizeText } from '../index' 2 | import { experimentalSmartFuzzyMatch } from '../impl' 3 | 4 | describe('createFuzzySearch', () => { 5 | const matches = (text, query, expectedScore, expectedIndices = null) => { 6 | // console.log(`${text} ${query}`) 7 | const results = createFuzzySearch([text])(query) 8 | expect(results.length).toBe(1) 9 | const [result] = results 10 | expect(result.item).toBe(text) 11 | if (expectedScore != null) { 12 | expect(result.score).toBeCloseTo(expectedScore) 13 | } 14 | if (expectedIndices) { 15 | expect(result.matches.length).toBe(1) 16 | expect(result.matches[0]).toEqual(expectedIndices) 17 | } 18 | } 19 | it(`can match by: exact match`, () => { 20 | matches('foo', 'foo', 0, [[0, 2]]) 21 | matches('ABC', 'ABC', 0) 22 | matches('ąść', 'ąść', 0) 23 | matches('📚', '📚', 0) 24 | matches('123¡™£§', '123¡™£§', 0) 25 | matches('żabki', 'żabki', 0) 26 | matches('ząbki', 'ząbki', 0) 27 | matches('русский язык', 'русский язык', 0) 28 | matches('汉语', '汉语', 0) 29 | matches('日本語', '日本語', 0) 30 | matches('한국어', '한국어', 0) 31 | matches('ภาษาไทย', 'ภาษาไทย', 0) 32 | matches(' he ', ' he ', 0) 33 | matches(' he\n\t', ' he\n\t', 0) // hard space 34 | }) 35 | it(`can match by: full match`, () => { 36 | matches('Foo', 'foo', 0.1) 37 | matches('FOO', 'foo', 0.1) 38 | matches('foo', 'Foo', 0.1) 39 | matches('foo', 'FOO', 0.1) 40 | matches('foo', 'foo ', 0.1) 41 | matches(' foo bar', 'foo bar', 0.1) 42 | // eslint-disable-next-line 43 | // matches('foo bar', 'foo bar', 0.1) // hard space 44 | matches('Żabki', 'żabki', 0.1, [[0, 4]]) 45 | matches('Żabki', 'zabki', 0.1) 46 | matches('Ząbki', 'zabki', 0.1) 47 | matches('ZABKI', 'żąbki', 0.1) 48 | matches('Szczegół', 'szczegol', 0.1) 49 | matches('Язык', 'язык', 0.1, [[0, 3]]) 50 | // Check for regression - previously highlight would be off by whitespace 51 | matches('foo ', 'foo', 0.1, [[0, 2]]) 52 | }) 53 | it(`can match by: "Starts with" match`, () => { 54 | // TODO: startsWith with exact diacritics should match more strongly (e.g. ża -> Żabka before Zabawa), but case-insensitively 55 | // (we want `to` to match Tom more than Couto) 56 | matches('Tomasz Kapelak', 'to', 0.5, [[0, 1]]) 57 | matches('Żabka - oferta', 'Żab', 0.5, [[0, 2]]) 58 | matches('Żabka - oferta', 'Zab', 0.5) 59 | matches('Żabka - oferta', 'zab', 0.5) 60 | matches('Szczegółowe', 'szcz', 0.5, [[0, 3]]) 61 | matches('Русский язык', 'рус', 0.5, [[0, 2]]) 62 | matches('汉语', '汉', 0.5, [[0, 0]]) 63 | matches('日本語', '日', 0.5, [[0, 0]]) 64 | // TODO: Fix Hangul highlighting 65 | // matches('한국어', '한', 0.5, [[0, 0]]) 66 | matches('ภาษาไทย', 'ภ', 0.5, [[0, 0]]) 67 | // Check for regression - previously highlight would be 1 longer 68 | matches('There is no icon', 'the ', 0.5, [[0, 2]]) 69 | }) 70 | it(`can match by: contains query (at word boundary) exactly`, () => { 71 | matches('[N4] Marketing', 'Market', 0.9, [[5, 10]]) 72 | matches('Wypad do Żabki', 'Żabk', 0.9) 73 | matches('русский язык', 'язык', 0.9, [[8, 11]]) 74 | }) 75 | it(`can match by: contains query (at word boundary)`, () => { 76 | matches('[N4] Marketing', 'market', 1, [[5, 10]]) 77 | matches('Wypad do Żabki', 'zabki', 1) 78 | matches('Myjcie ząbki!!', 'zabki', 1, [[7, 11]]) 79 | matches('Русский Язык', 'язык', 1, [[8, 11]]) 80 | }) 81 | it(`can match by: contains query (at any position)`, () => { 82 | matches('Marco Couto', 'To', 2, [[9, 10]]) 83 | matches('汉语', '语', 2, [[1, 1]]) 84 | matches('日本語', '本', 2, [[1, 1]]) 85 | // TODO: Fix Hangul highlighting 86 | // matches('한국어', '국', 2, [[1, 1]]) 87 | matches('ภาษาไทย', 'า', 2, [[1, 1]]) 88 | }) 89 | it(`can match by words (in some order)`, () => { 90 | matches('Setting to disable fuzzy search', 'fuzzy setting', 1.9, [ 91 | [0, 6], 92 | [19, 23], 93 | ]) 94 | matches('Setting to disable fuzzy search', 'disable setting fuzzy search', 2.3, [ 95 | [0, 6], 96 | [11, 17], 97 | [19, 23], 98 | [25, 30], 99 | ]) 100 | // matches by words before matching by letters 101 | matches('Cloak Duck, test clock', 'clock test', 1.9, [ 102 | [12, 15], 103 | [17, 21], 104 | ]) 105 | }) 106 | it(`can match by: contains letters awww yisss fuzzzzz`, () => { 107 | // score(of) 108 | const s = (score) => 2 + score 109 | // full word 110 | const w = 0.2 111 | // beginning of word 112 | const b = 0.4 113 | // middle of word 114 | const m = 0.8 115 | // middle of word (1 or 2 chars) 116 | const ms = 1.6 117 | 118 | // TODO: Matching diacritics should be scored better 119 | matches('Wypad do Żabki', 'wdż', s(b + ms + b), [ 120 | [0, 0], 121 | [4, 4], 122 | [9, 9], 123 | ]) 124 | matches('Wypad do Żabki', 'wdz', s(b + ms + b), [ 125 | [0, 0], 126 | [4, 4], 127 | [9, 9], 128 | ]) 129 | matches('Wypad do Żabki', 'wypażab', s(b + b), [ 130 | [0, 3], 131 | [9, 11], 132 | ]) 133 | matches('Wypad do Żabki', 'wypa żab', s(b + w + b), [ 134 | [0, 3], 135 | [5, 5], 136 | [9, 11], 137 | ]) 138 | matches('Marco Couto', 'mc', s(b + ms)) 139 | matches('Marco Couto', 'm c', s(b + b)) 140 | matches('Tomasz Kapelak', 'tokp', s(b + b + ms)) 141 | // trying to match `Referral` (and not r, e in Marketing) is probably too complex/magic 142 | matches('[Marketing ] Referral Program', 'mrefp', s(ms + ms + ms + ms + b)) 143 | matches('[Marketing ] Referral Program', 'm refp', s(ms + w + b + b)) 144 | matches('Nozbe.com web site', 'website') 145 | matches('Książka 10 steps EN', '10en', s(w + ms + ms)) 146 | matches('Won’t fix', 'wontfix') 147 | matches('[HR] JavaScript Developer', 'jsdev', s(b + ms + b), [ 148 | [5, 5], 149 | [9, 9], 150 | [16, 18], 151 | ]) 152 | matches('MacKay', 'mckay', s(b + m)) 153 | matches('Русский Язык', 'уски', s(ms + ms)) 154 | matches('Русский Язык', 'руя', s(b + b), [ 155 | [0, 1], 156 | [8, 8], 157 | ]) 158 | matches('汉语的,又称汉文、華文、唐文', '汉语唐', s(b + ms), [ 159 | [0, 1], 160 | [12, 12], 161 | ]) 162 | matches('日本語', '日語', s(b + ms), [ 163 | [0, 0], 164 | [2, 2], 165 | ]) 166 | matches('한국어', '한어', s(b + ms)) 167 | // matches('한국어', '한어', 2, [[0, 0], [2, 2]]) // FIXME: Fix highglighting for Hangul 168 | }) 169 | const noMatch = (text, query) => { 170 | const results = createFuzzySearch([text])(query) 171 | expect(results.length).toBe(0) 172 | } 173 | it(`can not match everything, okay :(`, () => { 174 | // no stemming 175 | noMatch('recognition', 'recognize') 176 | noMatch('production', 'produce') 177 | noMatch('żołądź', 'żołędzie') 178 | noMatch('take', 'took') 179 | noMatch('produce', 'reproduce') 180 | 181 | // no synonyms/alt spellings 182 | noMatch('McKay', 'MacKay') 183 | noMatch('mac', 'macintosh') 184 | noMatch('grey', 'gray') 185 | 186 | // no soundex 187 | noMatch('kay', 'kai') 188 | 189 | // no substitutions/typo autofix 190 | noMatch('leters', 'letters') 191 | noMatch('letters', 'lettesr') 192 | noMatch('referral', 'referarl') 193 | }) 194 | it(`can search by key`, () => { 195 | expect( 196 | createFuzzySearch([{ t: 'foo' }, { t: 'foo2' }, { t: 'bar' }], { key: 't' })('foo'), 197 | ).toMatchObject([{ item: { t: 'foo' } }, { item: { t: 'foo2' } }]) 198 | }) 199 | it(`can search by many keys`, () => { 200 | const u1 = { name: 'foo1', alias: 'fooa1' } 201 | const u2 = { name: 'foo2', alias: 'bar' } 202 | const u3 = { name: 'bar', alias: '3foo' } 203 | const u4 = { name: 'bar', alias: 'bar' } 204 | expect( 205 | createFuzzySearch([u1, u2, u3, u4], { 206 | getText: (item) => [item.name, item.alias], 207 | })('foo'), 208 | ).toMatchObject([ 209 | { 210 | item: u1, 211 | matches: [[[0, 2]], [[0, 2]]], 212 | }, 213 | { item: u2, matches: [[[0, 2]], null] }, 214 | { item: u3, matches: [null, [[1, 3]]] }, 215 | ]) 216 | }) 217 | it(`sorts searches by score`, () => { 218 | expect( 219 | createFuzzySearch([ 220 | '[Marketing] Żabka etc.', 221 | 'Zabawny Katar', 222 | 'Żal Betoniarka', 223 | 'Ząbka', 224 | 'Żabka', 225 | 'Żabowe Karabiny', 226 | 'Żabka - oferta współpracy', 227 | 'żabka', 228 | '[Marketing] żabka', 229 | ])('żabka'), 230 | ).toMatchObject([ 231 | { item: 'żabka' /* score: 0 */ }, 232 | { item: 'Ząbka' /* score: 0.1 */ }, 233 | { item: 'Żabka' /* score: 0.1 */ }, 234 | { item: 'Żabka - oferta współpracy' /* score: 0.5 */ }, 235 | { item: '[Marketing] żabka' /* score: 0.9 */ }, 236 | { item: '[Marketing] Żabka etc.' /* score: 1 */ }, 237 | { item: 'Zabawny Katar' /* score: 2 */ }, 238 | { item: 'Żabowe Karabiny' /* score: 2 */ }, 239 | { item: 'Żal Betoniarka' /* score: 3 */ }, 240 | ]) 241 | }) 242 | it(`sorts searches by score for many keys`, () => { 243 | expect( 244 | createFuzzySearch( 245 | [ 246 | { name: 'Matt', alias: 'Matthias Obst-Mölinger' }, 247 | { name: 'Marco Couto', alias: null }, 248 | { name: 'Tomasz Kapelak', alias: 'Tom' }, 249 | { name: 'tommy' }, 250 | { name: 'Jacob Tom Belinger', alias: 'Jake' }, 251 | ], 252 | { 253 | getText: (item) => [item.name, item.alias], 254 | }, 255 | )('tom'), 256 | ).toMatchObject([ 257 | { item: { name: 'Tomasz Kapelak', alias: 'Tom' } /* score: 0.1 */ }, 258 | { item: { name: 'tommy' } /* score: 0.5 */ }, 259 | { item: { name: 'Jacob Tom Belinger', alias: 'Jake' } /* score: 1 */ }, 260 | { item: { name: 'Matt', alias: 'Matthias Obst-Mölinger' } /* score: 3 */ }, 261 | ]) 262 | }) 263 | it(`returns empty array for empty query`, () => { 264 | expect(createFuzzySearch(['a', 'b', 'c', 'd'])('')).toEqual([]) 265 | }) 266 | const matchesNew = (text, query, ...expectedIndices) => { 267 | const result = experimentalSmartFuzzyMatch(normalizeText(text), normalizeText(query)) 268 | expect(result).not.toBe(null) 269 | if (expectedIndices.length) { 270 | const [, indices] = result 271 | expect(indices).toEqual(expectedIndices) 272 | } 273 | } 274 | it(`experimentalSmartFuzzyMatch`, () => { 275 | matchesNew('Wypad do Żabki', 'wdż', [0, 0], [6, 6], [9, 9]) 276 | matchesNew('Wypad do Żabki', 'wdz', [0, 0], [6, 6], [9, 9]) 277 | matchesNew('Wypad do Żabki', 'wypażab', [0, 3], [9, 11]) 278 | matchesNew('Wypad do Żabki', 'wypa żab', [0, 3], [8, 11]) 279 | matchesNew( 280 | 'Marco Couto', 281 | 'mc', 282 | [0, 0], 283 | [3, 3], // NOTE: Ideally would be (6,6), but remaining query is len 1, so [3] matches 284 | ) 285 | matchesNew('Marco Couto', 'm c', [0, 0], [5, 6]) 286 | matchesNew('Tomasz Kapelak', 'tokp', [0, 1], [7, 7], [9, 9]) 287 | matchesNew('[Marketing ] Referral Program', 'mrefp', [1, 1], [13, 15], [22, 22]) 288 | matchesNew('[Marketing ] Referral Program', 'm refp', [1, 1], [12, 15], [22, 22]) 289 | matchesNew('Nozbe.com web site', 'website', [10, 12], [14, 17]) 290 | matchesNew('Książka 10 steps EN', '10en') 291 | // FIXME: 292 | // matchesNew('Won’t fix', 'wontfix') 293 | // matchesNew('[HR] JavaScript Developer', 'jsdev') 294 | matchesNew('MacKay', 'mckay') 295 | matchesNew('Русский Язык', 'усс') 296 | matchesNew('Русский Язык', 'руя') 297 | matchesNew('汉语的,又称汉文、華文、唐文', '汉语唐') 298 | matchesNew('日本語', '日語') 299 | matchesNew('한국어', '한어') 300 | 301 | // new cases 302 | matchesNew('GH - Growth Hacking', 'growha', [0, 0], [6, 8], [12, 13]) 303 | }) 304 | }) 305 | -------------------------------------------------------------------------------- /src/impl.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /* eslint-disable no-continue */ 3 | import normalizeText from './normalizeText' 4 | import type { 5 | Range, 6 | FuzzySearcher, 7 | FuzzySearchOptions, 8 | FuzzySearchStrategy, 9 | FuzzyResult, 10 | HighlightRanges, 11 | FuzzyMatches, 12 | } from './index' 13 | 14 | const { MAX_SAFE_INTEGER } = Number 15 | 16 | const sortByScore = (a: FuzzyResult, b: FuzzyResult): number => a.score - b.score 17 | const sortRangeTuple = (a: Range, b: Range): number => a[0] - b[0] 18 | 19 | const validWordBoundaries = new Set('  []()-–—\'"“”'.split('')) 20 | 21 | function isValidWordBoundary(character: string): boolean { 22 | return validWordBoundaries.has(character) 23 | } 24 | 25 | function matchesFuzzily( 26 | item: string, 27 | normalizedItem: string, 28 | itemWords: Set, 29 | query: string, 30 | normalizedQuery: string, 31 | queryWords: string[], 32 | strategy: FuzzySearchStrategy, 33 | ): ?[number, HighlightRanges] { 34 | // quick matches 35 | if (item === query) { 36 | return [0, [[0, item.length - 1]]] 37 | } 38 | 39 | const queryLen = query.length 40 | const normalizedItemLen = normalizedItem.length 41 | const normalizedQueryLen = normalizedQuery.length 42 | 43 | if (normalizedItem === normalizedQuery) { 44 | return [0.1, [[0, normalizedItemLen - 1]]] 45 | } else if (normalizedItem.startsWith(normalizedQuery)) { 46 | return [0.5, [[0, normalizedQueryLen - 1]]] 47 | } 48 | 49 | // contains query (starting at word boundary) 50 | // NOTE: It would be more correct to do a regex search, than to check previous character, since 51 | // it could be that the item found does _not_ start at a word boundary, but there is another match 52 | // that does. However, this is faster and should rarely be a problem, while fuzzy search will still 53 | // find other matches (just ranked lower) 54 | const exactContainsIdx = item.indexOf(query) 55 | if (exactContainsIdx > -1 && isValidWordBoundary(item[exactContainsIdx - 1])) { 56 | return [0.9, [[exactContainsIdx, exactContainsIdx + queryLen - 1]]] 57 | } 58 | 59 | const containsIdx = normalizedItem.indexOf(normalizedQuery) 60 | if (containsIdx > -1 && isValidWordBoundary(normalizedItem[containsIdx - 1])) { 61 | return [1, [[containsIdx, containsIdx + queryLen - 1]]] 62 | } 63 | 64 | // Match by words included 65 | // Score: 1.5 + 0.2*words (so that it's better than two non-word chunks) 66 | const queryWordCount = queryWords.length 67 | if (queryWordCount > 1) { 68 | if (queryWords.every((word) => itemWords.has(word))) { 69 | const score = 1.5 + queryWordCount * 0.2 70 | return [ 71 | score, 72 | queryWords 73 | .map((word) => { 74 | const wordIndex = normalizedItem.indexOf(word) 75 | return ([wordIndex, wordIndex + word.length - 1]: Range) 76 | }) 77 | .sort(sortRangeTuple), 78 | ] 79 | } 80 | } 81 | 82 | // Contains query (at any position) 83 | if (containsIdx > -1) { 84 | return [2, [[containsIdx, containsIdx + queryLen - 1]]] 85 | } 86 | 87 | // Match by consecutive letters (fuzzy) 88 | if (strategy === 'aggressive') { 89 | return aggressiveFuzzyMatch(normalizedItem, normalizedQuery) 90 | } else if (strategy === 'smart') { 91 | return experimentalSmartFuzzyMatch(normalizedItem, normalizedQuery) 92 | } 93 | 94 | return null 95 | } 96 | 97 | export function aggressiveFuzzyMatch( 98 | normalizedItem: string, 99 | normalizedQuery: string, 100 | ): ?[number, HighlightRanges] { 101 | const normalizedItemLen = normalizedItem.length 102 | const normalizedQueryLen = normalizedQuery.length 103 | 104 | let queryIdx = 0 105 | let queryChar = normalizedQuery[queryIdx] 106 | const indices: HighlightRanges = [] 107 | let chunkFirstIdx = -1 108 | let chunkLastIdx = -2 109 | // TODO: May improve performance by early exits (less to go than remaining query) 110 | // and by using .indexOf(x, fromIndex) 111 | for (let itemIdx = 0; itemIdx < normalizedItemLen; itemIdx += 1) { 112 | // DEBUG: 113 | // console.log(`${itemIdx} (${normalizedItem[itemIdx]}), ${queryIdx} (${queryChar}), ${chunkLastIdx}, score: ${consecutiveChunks}`) 114 | if (normalizedItem[itemIdx] === queryChar) { 115 | if (itemIdx !== chunkLastIdx + 1) { 116 | if (chunkFirstIdx >= 0) { 117 | indices.push([chunkFirstIdx, chunkLastIdx]) 118 | } 119 | chunkFirstIdx = itemIdx 120 | } 121 | chunkLastIdx = itemIdx 122 | queryIdx += 1 123 | if (queryIdx === normalizedQueryLen) { 124 | indices.push([chunkFirstIdx, chunkLastIdx]) 125 | return scoreConsecutiveLetters(indices, normalizedItem) 126 | } 127 | queryChar = normalizedQuery[queryIdx] 128 | } 129 | } 130 | 131 | return null 132 | } 133 | 134 | export function experimentalSmartFuzzyMatch( 135 | normalizedItem: string, 136 | normalizedQuery: string, 137 | ): ?[number, HighlightRanges] { 138 | const normalizedItemLen = normalizedItem.length 139 | 140 | // Match by consecutive letters, but only match beginnings of words or chunks of 3+ letters 141 | // Note that there may be multiple valid ways in which such matching can be done, and we'll only 142 | // match each chunk to the first one found that matches these criteria. It's not perfect as it's 143 | // possible that later chunks will fail to match while there's a better match, for example: 144 | // - query: ABC 145 | // - item: A xABC 146 | // ^___xx (no match) 147 | // ___^^^ (better match) 148 | // But we want to limit the algorithmic complexity and this should generally work. 149 | 150 | const indices: HighlightRanges = [] 151 | let queryIdx = 0 152 | let queryChar = normalizedQuery[queryIdx] 153 | let chunkFirstIdx = -1 154 | let chunkLastIdx = -2 155 | 156 | // eslint-disable-next-line no-constant-condition 157 | while (true) { 158 | // Find match for first letter of chunk 159 | const idx = normalizedItem.indexOf(queryChar, chunkLastIdx + 1) 160 | if (idx === -1) { 161 | break 162 | } 163 | 164 | // Check if chunk starts at word boundary 165 | if (idx === 0 || isValidWordBoundary(normalizedItem[idx - 1])) { 166 | chunkFirstIdx = idx 167 | } else { 168 | // Else, check if chunk is at least 3+ letters 169 | const queryCharsLeft = normalizedQuery.length - queryIdx 170 | const itemCharsLeft = normalizedItem.length - idx 171 | const minimumChunkLen = Math.min(3, queryCharsLeft, itemCharsLeft) 172 | const minimumQueryChunk = normalizedQuery.slice(queryIdx, queryIdx + minimumChunkLen) 173 | 174 | if (normalizedItem.slice(idx, idx + minimumChunkLen) === minimumQueryChunk) { 175 | chunkFirstIdx = idx 176 | } else { 177 | // Move index to continue search for valid chunk 178 | chunkLastIdx += 1 179 | continue 180 | } 181 | } 182 | 183 | // We have first index of a valid chunk, find its last index 184 | // TODO: We could micro-optimize by setting chunkLastIdx earlier if we already know it's len 3 or more 185 | for (chunkLastIdx = chunkFirstIdx; chunkLastIdx < normalizedItemLen; chunkLastIdx += 1) { 186 | if (normalizedItem[chunkLastIdx] !== queryChar) { 187 | break 188 | } 189 | 190 | queryIdx += 1 191 | queryChar = normalizedQuery[queryIdx] 192 | } 193 | 194 | // Add chunk to indices 195 | chunkLastIdx -= 1 // decrement as we've broken out of loop on non-matching char 196 | indices.push([chunkFirstIdx, chunkLastIdx]) 197 | 198 | // Check if we're done 199 | if (queryIdx === normalizedQuery.length) { 200 | return scoreConsecutiveLetters(indices, normalizedItem) 201 | } 202 | } 203 | 204 | // eslint-disable-next-line no-unreachable 205 | return null 206 | } 207 | 208 | function scoreConsecutiveLetters( 209 | indices: HighlightRanges, 210 | normalizedItem: string, 211 | ): ?[number, HighlightRanges] { 212 | // Score: 2 + sum of chunk scores 213 | // Chunk scores: 214 | // - 0.2 for a full word 215 | // - 0.4 for chunk starting at beginning of word 216 | // - 0.8 for chunk in the middle of the word (if >=3 characters) 217 | // - 1.6 for chunk in the middle of the word (if 1 or 2 characters) 218 | let score = 2 219 | 220 | indices.forEach(([firstIdx, lastIdx]) => { 221 | const chunkLength = lastIdx - firstIdx + 1 222 | const isStartOfWord = 223 | firstIdx === 0 || normalizedItem[firstIdx] === ' ' || normalizedItem[firstIdx - 1] === ' ' 224 | const isEndOfWord = 225 | lastIdx === normalizedItem.length - 1 || 226 | normalizedItem[lastIdx] === ' ' || 227 | normalizedItem[lastIdx + 1] === ' ' 228 | const isFullWord = isStartOfWord && isEndOfWord 229 | // DEBUG: 230 | // console.log({ 231 | // firstIdx, 232 | // lastIdx, 233 | // chunkLength, 234 | // isStartOfWord, 235 | // isEndOfWord, 236 | // isFullWord, 237 | // before: normalizedItem[firstIdx - 1], 238 | // after: normalizedItem[lastIdx + 1], 239 | // }) 240 | if (isFullWord) { 241 | score += 0.2 242 | } else if (isStartOfWord) { 243 | score += 0.4 244 | } else if (chunkLength >= 3) { 245 | score += 0.8 246 | } else { 247 | score += 1.6 248 | } 249 | }) 250 | 251 | return [score, indices] 252 | } 253 | 254 | export function fuzzyMatchImpl(text: string, query: string): ?FuzzyResult { 255 | const normalizedQuery = normalizeText(query) 256 | const queryWords = normalizedQuery.split(' ') 257 | 258 | const normalizedText = normalizeText(text) 259 | const itemWords = new Set(normalizedText.split(' ')) 260 | 261 | const result = matchesFuzzily( 262 | text, 263 | normalizedText, 264 | itemWords, 265 | query, 266 | normalizedQuery, 267 | queryWords, 268 | 'smart', 269 | ) 270 | if (result) { 271 | return { item: text, score: result[0], matches: [result[1]] } 272 | } 273 | 274 | return null 275 | } 276 | 277 | export function createFuzzySearchImpl( 278 | collection: Element[], 279 | options: FuzzySearchOptions, 280 | ): FuzzySearcher { 281 | // TODO: Change default strategy to smart 282 | const { strategy = 'aggressive', getText } = options 283 | 284 | const preprocessedCollection: [Element, [string, string, Set][]][] = collection.map( 285 | (element: Element) => { 286 | let texts: (?string)[] 287 | if (getText) { 288 | texts = getText(element) 289 | } else { 290 | // $FlowFixMe[incompatible-use] 291 | const text: string = options.key ? element[options.key] : (element: any) 292 | texts = [text] 293 | } 294 | 295 | const preprocessedTexts: [string, string, Set][] = texts.map((text) => { 296 | const item = text || '' 297 | const normalizedItem = normalizeText(item) 298 | const itemWords = new Set(normalizedItem.split(' ')) 299 | 300 | return [item, normalizedItem, itemWords] 301 | }) 302 | 303 | return [element, preprocessedTexts] 304 | }, 305 | ) 306 | 307 | return (query: string) => { 308 | // DEBUG 309 | // const b4 = Date.now() 310 | const results: Array> = [] 311 | const normalizedQuery = normalizeText(query) 312 | const queryWords = normalizedQuery.split(' ') 313 | 314 | if (!normalizedQuery.length) { 315 | return [] 316 | } 317 | 318 | preprocessedCollection.forEach(([element, texts]) => { 319 | let bestScore = MAX_SAFE_INTEGER 320 | const matches: FuzzyMatches = [] 321 | for (let i = 0, len = texts.length; i < len; i += 1) { 322 | const [item, normalizedItem, itemWords] = texts[i] 323 | const result = matchesFuzzily( 324 | item, 325 | normalizedItem, 326 | itemWords, 327 | query, 328 | normalizedQuery, 329 | queryWords, 330 | strategy, 331 | ) 332 | if (result) { 333 | bestScore = Math.min(bestScore, result[0]) // take the lowest score of any match 334 | matches.push(result[1]) 335 | } else { 336 | matches.push(null) 337 | } 338 | } 339 | if (bestScore < MAX_SAFE_INTEGER) { 340 | results.push({ item: element, score: bestScore, matches }) 341 | } 342 | }) 343 | 344 | results.sort(sortByScore) 345 | 346 | // DEBUG 347 | // console.log(`fuzzy search complete in ${Date.now() - b4} ms`) 348 | 349 | return results 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /demo/src/testdata/companies.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-anonymous-default-export 2 | export default [ 3 | '3Com Corp', 4 | '3M Company', 5 | 'A.G. Edwards Inc.', 6 | 'Abbott Laboratories', 7 | 'Abercrombie & Fitch Co.', 8 | 'ABM Industries Incorporated', 9 | 'Ace Hardware Corporation', 10 | 'ACT Manufacturing Inc.', 11 | 'Acterna Corp.', 12 | 'Adams Resources & Energy, Inc.', 13 | 'ADC Telecommunications, Inc.', 14 | 'Adelphia Communications Corporation', 15 | 'Administaff, Inc.', 16 | 'Adobe Systems Incorporated', 17 | 'Adolph Coors Company', 18 | 'Advance Auto Parts, Inc.', 19 | 'Advanced Micro Devices, Inc.', 20 | 'AdvancePCS, Inc.', 21 | 'Advantica Restaurant Group, Inc.', 22 | 'The AES Corporation', 23 | 'Aetna Inc.', 24 | 'Affiliated Computer Services, Inc.', 25 | 'AFLAC Incorporated', 26 | 'AGCO Corporation', 27 | 'Agilent Technologies, Inc.', 28 | 'Agway Inc.', 29 | 'Apartment Investment and Management Company', 30 | 'Air Products and Chemicals, Inc.', 31 | 'Airborne, Inc.', 32 | 'Airgas, Inc.', 33 | 'AK Steel Holding Corporation', 34 | 'Alaska Air Group, Inc.', 35 | 'Alberto-Culver Company', 36 | "Albertson's, Inc.", 37 | 'Alcoa Inc.', 38 | 'Alleghany Corporation', 39 | 'Allegheny Energy, Inc.', 40 | 'Allegheny Technologies Incorporated', 41 | 'Allergan, Inc.', 42 | 'ALLETE, Inc.', 43 | 'Alliant Energy Corporation', 44 | 'Allied Waste Industries, Inc.', 45 | 'Allmerica Financial Corporation', 46 | 'The Allstate Corporation', 47 | 'ALLTEL Corporation', 48 | 'The Alpine Group, Inc.', 49 | 'Amazon.com, Inc.', 50 | 'AMC Entertainment Inc.', 51 | 'American Power Conversion Corporation', 52 | 'Amerada Hess Corporation', 53 | 'AMERCO', 54 | 'Ameren Corporation', 55 | 'America West Holdings Corporation', 56 | 'American Axle & Manufacturing Holdings, Inc.', 57 | 'American Eagle Outfitters, Inc.', 58 | 'American Electric Power Company, Inc.', 59 | 'American Express Company', 60 | 'American Financial Group, Inc.', 61 | 'American Greetings Corporation', 62 | 'American International Group, Inc.', 63 | 'American Standard Companies Inc.', 64 | 'American Water Works Company, Inc.', 65 | 'AmerisourceBergen Corporation', 66 | 'Ames Department Stores, Inc.', 67 | 'Amgen Inc.', 68 | 'Amkor Technology, Inc.', 69 | 'AMR Corporation', 70 | 'AmSouth Bancorp.', 71 | 'Amtran, Inc.', 72 | 'Anadarko Petroleum Corporation', 73 | 'Analog Devices, Inc.', 74 | 'Anheuser-Busch Companies, Inc.', 75 | 'Anixter International Inc.', 76 | 'AnnTaylor Inc.', 77 | 'Anthem, Inc.', 78 | 'AOL Time Warner Inc.', 79 | 'Aon Corporation', 80 | 'Apache Corporation', 81 | 'Apple Computer, Inc.', 82 | 'Applera Corporation', 83 | 'Applied Industrial Technologies, Inc.', 84 | 'Applied Materials, Inc.', 85 | 'Aquila, Inc.', 86 | 'ARAMARK Corporation', 87 | 'Arch Coal, Inc.', 88 | 'Archer Daniels Midland Company', 89 | 'Arkansas Best Corporation', 90 | 'Armstrong Holdings, Inc.', 91 | 'Arrow Electronics, Inc.', 92 | 'ArvinMeritor, Inc.', 93 | 'Ashland Inc.', 94 | 'Astoria Financial Corporation', 95 | 'AT&T Corp.', 96 | 'Atmel Corporation', 97 | 'Atmos Energy Corporation', 98 | 'Audiovox Corporation', 99 | 'Autoliv, Inc.', 100 | 'Automatic Data Processing, Inc.', 101 | 'AutoNation, Inc.', 102 | 'AutoZone, Inc.', 103 | 'Avaya Inc.', 104 | 'Avery Dennison Corporation', 105 | 'Avista Corporation', 106 | 'Avnet, Inc.', 107 | 'Avon Products, Inc.', 108 | 'Baker Hughes Incorporated', 109 | 'Ball Corporation', 110 | 'Bank of America Corporation', 111 | 'The Bank of New York Company, Inc.', 112 | 'Bank One Corporation', 113 | 'Banknorth Group, Inc.', 114 | 'Banta Corporation', 115 | 'Barnes & Noble, Inc.', 116 | 'Bausch & Lomb Incorporated', 117 | 'Baxter International Inc.', 118 | 'BB&T Corporation', 119 | 'The Bear Stearns Companies Inc.', 120 | 'Beazer Homes USA, Inc.', 121 | 'Beckman Coulter, Inc.', 122 | 'Becton, Dickinson and Company', 123 | 'Bed Bath & Beyond Inc.', 124 | 'Belk, Inc.', 125 | 'Bell Microproducts Inc.', 126 | 'BellSouth Corporation', 127 | 'Belo Corp.', 128 | 'Bemis Company, Inc.', 129 | 'Benchmark Electronics, Inc.', 130 | 'Berkshire Hathaway Inc.', 131 | 'Best Buy Co., Inc.', 132 | 'Bethlehem Steel Corporation', 133 | 'Beverly Enterprises, Inc.', 134 | 'Big Lots, Inc.', 135 | 'BJ Services Company', 136 | "BJ's Wholesale Club, Inc.", 137 | 'The Black & Decker Corporation', 138 | 'Black Hills Corporation', 139 | 'BMC Software, Inc.', 140 | 'The Boeing Company', 141 | 'Boise Cascade Corporation', 142 | 'Borders Group, Inc.', 143 | 'BorgWarner Inc.', 144 | 'Boston Scientific Corporation', 145 | 'Bowater Incorporated', 146 | 'Briggs & Stratton Corporation', 147 | 'Brightpoint, Inc.', 148 | 'Brinker International, Inc.', 149 | 'Bristol-Myers Squibb Company', 150 | 'Broadwing, Inc.', 151 | 'Brown Shoe Company, Inc.', 152 | 'Brown-Forman Corporation', 153 | 'Brunswick Corporation', 154 | 'Budget Group, Inc.', 155 | 'Burlington Coat Factory Warehouse Corporation', 156 | 'Burlington Industries, Inc.', 157 | 'Burlington Northern Santa Fe Corporation', 158 | 'Burlington Resources Inc.', 159 | 'C. H. Robinson Worldwide Inc.', 160 | 'Cablevision Systems Corp', 161 | 'Cabot Corp', 162 | 'Cadence Design Systems, Inc.', 163 | 'Calpine Corp.', 164 | 'Campbell Soup Co.', 165 | 'Capital One Financial Corp.', 166 | 'Cardinal Health Inc.', 167 | 'Caremark Rx Inc.', 168 | 'Carlisle Cos. Inc.', 169 | 'Carpenter Technology Corp.', 170 | "Casey's General Stores Inc.", 171 | 'Caterpillar Inc.', 172 | 'CBRL Group Inc.', 173 | 'CDI Corp.', 174 | 'CDW Computer Centers Inc.', 175 | 'CellStar Corp.', 176 | 'Cendant Corp', 177 | 'Cenex Harvest States Cooperatives', 178 | 'Centex Corp.', 179 | 'CenturyTel Inc.', 180 | 'Ceridian Corp.', 181 | 'CH2M Hill Cos. Ltd.', 182 | 'Champion Enterprises Inc.', 183 | 'Charles Schwab Corp.', 184 | 'Charming Shoppes Inc.', 185 | 'Charter Communications Inc.', 186 | 'Charter One Financial Inc.', 187 | 'ChevronTexaco Corp.', 188 | 'Chiquita Brands International Inc.', 189 | 'Chubb Corp', 190 | 'Ciena Corp.', 191 | 'Cigna Corp', 192 | 'Cincinnati Financial Corp.', 193 | 'Cinergy Corp.', 194 | 'Cintas Corp.', 195 | 'Circuit City Stores Inc.', 196 | 'Cisco Systems Inc.', 197 | 'Citigroup, Inc', 198 | 'Citizens Communications Co.', 199 | 'CKE Restaurants Inc.', 200 | 'Clear Channel Communications Inc.', 201 | 'The Clorox Co.', 202 | 'CMGI Inc.', 203 | 'CMS Energy Corp.', 204 | 'CNF Inc.', 205 | 'Coca-Cola Co.', 206 | 'Coca-Cola Enterprises Inc.', 207 | 'Colgate-Palmolive Co.', 208 | 'Collins & Aikman Corp.', 209 | 'Comcast Corp.', 210 | 'Comdisco Inc.', 211 | 'Comerica Inc.', 212 | 'Comfort Systems USA Inc.', 213 | 'Commercial Metals Co.', 214 | 'Community Health Systems Inc.', 215 | 'Compass Bancshares Inc', 216 | 'Computer Associates International Inc.', 217 | 'Computer Sciences Corp.', 218 | 'Compuware Corp.', 219 | 'Comverse Technology Inc.', 220 | 'ConAgra Foods Inc.', 221 | 'Concord EFS Inc.', 222 | 'Conectiv, Inc', 223 | 'Conoco Inc', 224 | 'Conseco Inc.', 225 | 'Consolidated Freightways Corp.', 226 | 'Consolidated Edison Inc.', 227 | 'Constellation Brands Inc.', 228 | 'Constellation Emergy Group Inc.', 229 | 'Continental Airlines Inc.', 230 | 'Convergys Corp.', 231 | 'Cooper Cameron Corp.', 232 | 'Cooper Industries Ltd.', 233 | 'Cooper Tire & Rubber Co.', 234 | 'Corn Products International Inc.', 235 | 'Corning Inc.', 236 | 'Costco Wholesale Corp.', 237 | 'Countrywide Credit Industries Inc.', 238 | 'Coventry Health Care Inc.', 239 | 'Cox Communications Inc.', 240 | 'Crane Co.', 241 | 'Crompton Corp.', 242 | 'Crown Cork & Seal Co. Inc.', 243 | 'CSK Auto Corp.', 244 | 'CSX Corp.', 245 | 'Cummins Inc.', 246 | 'CVS Corp.', 247 | 'Cytec Industries Inc.', 248 | 'D&K Healthcare Resources, Inc.', 249 | 'D.R. Horton Inc.', 250 | 'Dana Corporation', 251 | 'Danaher Corporation', 252 | 'Darden Restaurants Inc.', 253 | 'DaVita Inc.', 254 | 'Dean Foods Company', 255 | 'Deere & Company', 256 | 'Del Monte Foods Co', 257 | 'Dell Computer Corporation', 258 | 'Delphi Corp.', 259 | 'Delta Air Lines Inc.', 260 | 'Deluxe Corporation', 261 | 'Devon Energy Corporation', 262 | 'Di Giorgio Corporation', 263 | 'Dial Corporation', 264 | 'Diebold Incorporated', 265 | "Dillard's Inc.", 266 | 'DIMON Incorporated', 267 | 'Dole Food Company, Inc.', 268 | 'Dollar General Corporation', 269 | 'Dollar Tree Stores, Inc.', 270 | 'Dominion Resources, Inc.', 271 | "Domino's Pizza LLC", 272 | 'Dover Corporation, Inc.', 273 | 'Dow Chemical Company', 274 | 'Dow Jones & Company, Inc.', 275 | 'DPL Inc.', 276 | 'DQE Inc.', 277 | "Dreyer's Grand Ice Cream, Inc.", 278 | 'DST Systems, Inc.', 279 | 'DTE Energy Co.', 280 | 'E.I. Du Pont de Nemours and Company', 281 | 'Duke Energy Corp', 282 | 'Dun & Bradstreet Inc.', 283 | 'DURA Automotive Systems Inc.', 284 | 'DynCorp', 285 | 'Dynegy Inc.', 286 | 'E*Trade Group, Inc.', 287 | 'E.W. Scripps Company', 288 | 'Earthlink, Inc.', 289 | 'Eastman Chemical Company', 290 | 'Eastman Kodak Company', 291 | 'Eaton Corporation', 292 | 'Echostar Communications Corporation', 293 | 'Ecolab Inc.', 294 | 'Edison International', 295 | 'EGL Inc.', 296 | 'El Paso Corporation', 297 | 'Electronic Arts Inc.', 298 | 'Electronic Data Systems Corp.', 299 | 'Eli Lilly and Company', 300 | 'EMC Corporation', 301 | 'Emcor Group Inc.', 302 | 'Emerson Electric Co.', 303 | 'Encompass Services Corporation', 304 | 'Energizer Holdings Inc.', 305 | 'Energy East Corporation', 306 | 'Engelhard Corporation', 307 | 'Enron Corp.', 308 | 'Entergy Corporation', 309 | 'Enterprise Products Partners L.P.', 310 | 'EOG Resources, Inc.', 311 | 'Equifax Inc.', 312 | 'Equitable Resources Inc.', 313 | 'Equity Office Properties Trust', 314 | 'Equity Residential Properties Trust', 315 | 'Estee Lauder Companies Inc.', 316 | 'Exelon Corporation', 317 | 'Exide Technologies', 318 | 'Expeditors International of Washington Inc.', 319 | 'Express Scripts Inc.', 320 | 'ExxonMobil Corporation', 321 | 'Fairchild Semiconductor International Inc.', 322 | 'Family Dollar Stores Inc.', 323 | 'Farmland Industries Inc.', 324 | 'Federal Mogul Corp.', 325 | 'Federated Department Stores Inc.', 326 | 'Federal Express Corp.', 327 | 'Felcor Lodging Trust Inc.', 328 | 'Ferro Corp.', 329 | 'Fidelity National Financial Inc.', 330 | 'Fifth Third Bancorp', 331 | 'First American Financial Corp.', 332 | 'First Data Corp.', 333 | 'First National of Nebraska Inc.', 334 | 'First Tennessee National Corp.', 335 | 'FirstEnergy Corp.', 336 | 'Fiserv Inc.', 337 | 'Fisher Scientific International Inc.', 338 | 'FleetBoston Financial Co.', 339 | 'Fleetwood Enterprises Inc.', 340 | 'Fleming Companies Inc.', 341 | 'Flowers Foods Inc.', 342 | 'Flowserv Corp', 343 | 'Fluor Corp', 344 | 'FMC Corp', 345 | 'Foamex International Inc', 346 | 'Foot Locker Inc', 347 | 'Footstar Inc.', 348 | 'Ford Motor Co', 349 | 'Forest Laboratories Inc.', 350 | 'Fortune Brands Inc.', 351 | 'Foster Wheeler Ltd.', 352 | 'FPL Group Inc.', 353 | 'Franklin Resources Inc.', 354 | 'Freeport McMoran Copper & Gold Inc.', 355 | 'Frontier Oil Corp', 356 | 'Furniture Brands International Inc.', 357 | 'Gannett Co., Inc.', 358 | 'Gap Inc.', 359 | 'Gateway Inc.', 360 | 'GATX Corporation', 361 | 'Gemstar-TV Guide International Inc.', 362 | 'GenCorp Inc.', 363 | 'General Cable Corporation', 364 | 'General Dynamics Corporation', 365 | 'General Electric Company', 366 | 'General Mills Inc', 367 | 'General Motors Corporation', 368 | 'Genesis Health Ventures Inc.', 369 | 'Gentek Inc.', 370 | 'Gentiva Health Services Inc.', 371 | 'Genuine Parts Company', 372 | 'Genuity Inc.', 373 | 'Genzyme Corporation', 374 | 'Georgia Gulf Corporation', 375 | 'Georgia-Pacific Corporation', 376 | 'Gillette Company', 377 | 'Gold Kist Inc.', 378 | 'Golden State Bancorp Inc.', 379 | 'Golden West Financial Corporation', 380 | 'Goldman Sachs Group Inc.', 381 | 'Goodrich Corporation', 382 | 'The Goodyear Tire & Rubber Company', 383 | 'Granite Construction Incorporated', 384 | 'Graybar Electric Company Inc.', 385 | 'Great Lakes Chemical Corporation', 386 | 'Great Plains Energy Inc.', 387 | 'GreenPoint Financial Corp.', 388 | 'Greif Bros. Corporation', 389 | 'Grey Global Group Inc.', 390 | 'Group 1 Automotive Inc.', 391 | 'Guidant Corporation', 392 | 'H&R Block Inc.', 393 | 'H.B. Fuller Company', 394 | 'H.J. Heinz Company', 395 | 'Halliburton Co.', 396 | 'Harley-Davidson Inc.', 397 | 'Harman International Industries Inc.', 398 | "Harrah's Entertainment Inc.", 399 | 'Harris Corp.', 400 | 'Harsco Corp.', 401 | 'Hartford Financial Services Group Inc.', 402 | 'Hasbro Inc.', 403 | 'Hawaiian Electric Industries Inc.', 404 | 'HCA Inc.', 405 | 'Health Management Associates Inc.', 406 | 'Health Net Inc.', 407 | 'Healthsouth Corp', 408 | 'Henry Schein Inc.', 409 | 'Hercules Inc.', 410 | 'Herman Miller Inc.', 411 | 'Hershey Foods Corp.', 412 | 'Hewlett-Packard Company', 413 | 'Hibernia Corp.', 414 | 'Hillenbrand Industries Inc.', 415 | 'Hilton Hotels Corp.', 416 | 'Hollywood Entertainment Corp.', 417 | 'Home Depot Inc.', 418 | 'Hon Industries Inc.', 419 | 'Honeywell International Inc.', 420 | 'Hormel Foods Corp.', 421 | 'Host Marriott Corp.', 422 | 'Household International Corp.', 423 | 'Hovnanian Enterprises Inc.', 424 | 'Hub Group Inc.', 425 | 'Hubbell Inc.', 426 | 'Hughes Supply Inc.', 427 | 'Humana Inc.', 428 | 'Huntington Bancshares Inc.', 429 | 'Idacorp Inc.', 430 | 'IDT Corporation', 431 | 'IKON Office Solutions Inc.', 432 | 'Illinois Tool Works Inc.', 433 | 'IMC Global Inc.', 434 | 'Imperial Sugar Company', 435 | 'IMS Health Inc.', 436 | 'Ingles Market Inc', 437 | 'Ingram Micro Inc.', 438 | 'Insight Enterprises Inc.', 439 | 'Integrated Electrical Services Inc.', 440 | 'Intel Corporation', 441 | 'International Paper Co.', 442 | 'Interpublic Group of Companies Inc.', 443 | 'Interstate Bakeries Corporation', 444 | 'International Business Machines Corp.', 445 | 'International Flavors & Fragrances Inc.', 446 | 'International Multifoods Corporation', 447 | 'Intuit Inc.', 448 | 'IT Group Inc.', 449 | 'ITT Industries Inc.', 450 | 'Ivax Corp.', 451 | 'J.B. Hunt Transport Services Inc.', 452 | 'J.C. Penny Co.', 453 | 'J.P. Morgan Chase & Co.', 454 | 'Jabil Circuit Inc.', 455 | 'Jack In The Box Inc.', 456 | 'Jacobs Engineering Group Inc.', 457 | 'JDS Uniphase Corp.', 458 | 'Jefferson-Pilot Co.', 459 | 'John Hancock Financial Services Inc.', 460 | 'Johnson & Johnson', 461 | 'Johnson Controls Inc.', 462 | 'Jones Apparel Group Inc.', 463 | 'KB Home', 464 | 'Kellogg Company', 465 | 'Kellwood Company', 466 | 'Kelly Services Inc.', 467 | 'Kemet Corp.', 468 | 'Kennametal Inc.', 469 | 'Kerr-McGee Corporation', 470 | 'KeyCorp', 471 | 'KeySpan Corp.', 472 | 'Kimball International Inc.', 473 | 'Kimberly-Clark Corporation', 474 | 'Kindred Healthcare Inc.', 475 | 'KLA-Tencor Corporation', 476 | 'K-Mart Corp.', 477 | 'Knight-Ridder Inc.', 478 | "Kohl's Corp.", 479 | 'KPMG Consulting Inc.', 480 | 'Kroger Co.', 481 | 'L-3 Communications Holdings Inc.', 482 | 'Laboratory Corporation of America Holdings', 483 | 'Lam Research Corporation', 484 | 'LandAmerica Financial Group Inc.', 485 | "Lands' End Inc.", 486 | 'Landstar System Inc.', 487 | 'La-Z-Boy Inc.', 488 | 'Lear Corporation', 489 | 'Legg Mason Inc.', 490 | 'Leggett & Platt Inc.', 491 | 'Lehman Brothers Holdings Inc.', 492 | 'Lennar Corporation', 493 | 'Lennox International Inc.', 494 | 'Level 3 Communications Inc.', 495 | 'Levi Strauss & Co.', 496 | 'Lexmark International Inc.', 497 | 'Limited Inc.', 498 | 'Lincoln National Corporation', 499 | "Linens 'n Things Inc.", 500 | 'Lithia Motors Inc.', 501 | 'Liz Claiborne Inc.', 502 | 'Lockheed Martin Corporation', 503 | 'Loews Corporation', 504 | 'Longs Drug Stores Corporation', 505 | 'Louisiana-Pacific Corporation', 506 | "Lowe's Companies Inc.", 507 | 'LSI Logic Corporation', 508 | 'The LTV Corporation', 509 | 'The Lubrizol Corporation', 510 | 'Lucent Technologies Inc.', 511 | 'Lyondell Chemical Company', 512 | 'M & T Bank Corporation', 513 | 'Magellan Health Services Inc.', 514 | 'Mail-Well Inc.', 515 | 'Mandalay Resort Group', 516 | 'Manor Care Inc.', 517 | 'Manpower Inc.', 518 | 'Marathon Oil Corporation', 519 | 'Mariner Health Care Inc.', 520 | 'Markel Corporation', 521 | 'Marriott International Inc.', 522 | 'Marsh & McLennan Companies Inc.', 523 | 'Marsh Supermarkets Inc.', 524 | 'Marshall & Ilsley Corporation', 525 | 'Martin Marietta Materials Inc.', 526 | 'Masco Corporation', 527 | 'Massey Energy Company', 528 | 'MasTec Inc.', 529 | 'Mattel Inc.', 530 | 'Maxim Integrated Products Inc.', 531 | 'Maxtor Corporation', 532 | 'Maxxam Inc.', 533 | 'The May Department Stores Company', 534 | 'Maytag Corporation', 535 | 'MBNA Corporation', 536 | 'McCormick & Company Incorporated', 537 | "McDonald's Corporation", 538 | 'The McGraw-Hill Companies Inc.', 539 | 'McKesson Corporation', 540 | 'McLeodUSA Incorporated', 541 | 'M.D.C. Holdings Inc.', 542 | 'MDU Resources Group Inc.', 543 | 'MeadWestvaco Corporation', 544 | 'Medtronic Inc.', 545 | 'Mellon Financial Corporation', 546 | "The Men's Wearhouse Inc.", 547 | 'Merck & Co., Inc.', 548 | 'Mercury General Corporation', 549 | 'Merrill Lynch & Co. Inc.', 550 | 'Metaldyne Corporation', 551 | 'Metals USA Inc.', 552 | 'MetLife Inc.', 553 | 'Metris Companies Inc', 554 | 'MGIC Investment Corporation', 555 | 'MGM Mirage', 556 | 'Michaels Stores Inc.', 557 | 'Micron Technology Inc.', 558 | 'Microsoft Corporation', 559 | 'Milacron Inc.', 560 | 'Millennium Chemicals Inc.', 561 | 'Mirant Corporation', 562 | 'Mohawk Industries Inc.', 563 | 'Molex Incorporated', 564 | 'The MONY Group Inc.', 565 | 'Morgan Stanley Dean Witter & Co.', 566 | 'Motorola Inc.', 567 | 'MPS Group Inc.', 568 | 'Murphy Oil Corporation', 569 | 'Nabors Industries Inc', 570 | 'Nacco Industries Inc', 571 | 'Nash Finch Company', 572 | 'National City Corp.', 573 | 'National Commerce Financial Corporation', 574 | 'National Fuel Gas Company', 575 | 'National Oilwell Inc', 576 | 'National Rural Utilities Cooperative Finance Corporation', 577 | 'National Semiconductor Corporation', 578 | 'National Service Industries Inc', 579 | 'Navistar International Corporation', 580 | 'NCR Corporation', 581 | 'The Neiman Marcus Group Inc.', 582 | 'New Jersey Resources Corporation', 583 | 'New York Times Company', 584 | 'Newell Rubbermaid Inc', 585 | 'Newmont Mining Corporation', 586 | 'Nextel Communications Inc', 587 | 'Nicor Inc', 588 | 'Nike Inc', 589 | 'NiSource Inc', 590 | 'Noble Energy Inc', 591 | 'Nordstrom Inc', 592 | 'Norfolk Southern Corporation', 593 | 'Nortek Inc', 594 | 'North Fork Bancorporation Inc', 595 | 'Northeast Utilities System', 596 | 'Northern Trust Corporation', 597 | 'Northrop Grumman Corporation', 598 | 'NorthWestern Corporation', 599 | 'Novellus Systems Inc', 600 | 'NSTAR', 601 | 'NTL Incorporated', 602 | 'Nucor Corp', 603 | 'Nvidia Corp', 604 | 'NVR Inc', 605 | 'Northwest Airlines Corp', 606 | 'Occidental Petroleum Corp', 607 | 'Ocean Energy Inc', 608 | 'Office Depot Inc.', 609 | 'OfficeMax Inc', 610 | 'OGE Energy Corp', 611 | 'Oglethorpe Power Corp.', 612 | 'Ohio Casualty Corp.', 613 | 'Old Republic International Corp.', 614 | 'Olin Corp.', 615 | 'OM Group Inc', 616 | 'Omnicare Inc', 617 | 'Omnicom Group', 618 | 'On Semiconductor Corp', 619 | 'ONEOK Inc', 620 | 'Oracle Corp', 621 | 'Oshkosh Truck Corp', 622 | 'Outback Steakhouse Inc.', 623 | 'Owens & Minor Inc.', 624 | 'Owens Corning', 625 | 'Owens-Illinois Inc', 626 | 'Oxford Health Plans Inc', 627 | 'Paccar Inc', 628 | 'PacifiCare Health Systems Inc', 629 | 'Packaging Corp. of America', 630 | 'Pactiv Corp', 631 | 'Pall Corp', 632 | 'Pantry Inc', 633 | 'Park Place Entertainment Corp', 634 | 'Parker Hannifin Corp.', 635 | 'Pathmark Stores Inc.', 636 | 'Paychex Inc', 637 | 'Payless Shoesource Inc', 638 | 'Penn Traffic Co.', 639 | 'Pennzoil-Quaker State Company', 640 | 'Pentair Inc', 641 | 'Peoples Energy Corp.', 642 | 'PeopleSoft Inc', 643 | 'Pep Boys Manny, Moe & Jack', 644 | 'Potomac Electric Power Co.', 645 | 'Pepsi Bottling Group Inc.', 646 | 'PepsiAmericas Inc.', 647 | 'PepsiCo Inc.', 648 | 'Performance Food Group Co.', 649 | 'Perini Corp', 650 | 'PerkinElmer Inc', 651 | 'Perot Systems Corp', 652 | 'Petco Animal Supplies Inc.', 653 | "Peter Kiewit Sons', Inc.", 654 | 'PETsMART Inc', 655 | 'Pfizer Inc', 656 | 'Pacific Gas & Electric Corp.', 657 | 'Pharmacia Corp', 658 | 'Phar Mor Inc.', 659 | 'Phelps Dodge Corp.', 660 | 'Philip Morris Companies Inc.', 661 | 'Phillips Petroleum Co', 662 | 'Phillips Van Heusen Corp.', 663 | 'Phoenix Companies Inc', 664 | 'Pier 1 Imports Inc.', 665 | "Pilgrim's Pride Corporation", 666 | 'Pinnacle West Capital Corp', 667 | 'Pioneer-Standard Electronics Inc.', 668 | 'Pitney Bowes Inc.', 669 | 'Pittston Brinks Group', 670 | 'Plains All American Pipeline LP', 671 | 'PNC Financial Services Group Inc.', 672 | 'PNM Resources Inc', 673 | 'Polaris Industries Inc.', 674 | 'Polo Ralph Lauren Corp', 675 | 'PolyOne Corp', 676 | 'Popular Inc', 677 | 'Potlatch Corp', 678 | 'PPG Industries Inc', 679 | 'PPL Corp', 680 | 'Praxair Inc', 681 | 'Precision Castparts Corp', 682 | 'Premcor Inc.', 683 | 'Pride International Inc', 684 | 'Primedia Inc', 685 | 'Principal Financial Group Inc.', 686 | 'Procter & Gamble Co.', 687 | 'Pro-Fac Cooperative Inc.', 688 | 'Progress Energy Inc', 689 | 'Progressive Corporation', 690 | 'Protective Life Corp', 691 | 'Provident Financial Group', 692 | 'Providian Financial Corp.', 693 | 'Prudential Financial Inc.', 694 | 'PSS World Medical Inc', 695 | 'Public Service Enterprise Group Inc.', 696 | 'Publix Super Markets Inc.', 697 | 'Puget Energy Inc.', 698 | 'Pulte Homes Inc', 699 | 'Qualcomm Inc', 700 | 'Quanta Services Inc.', 701 | 'Quantum Corp', 702 | 'Quest Diagnostics Inc.', 703 | 'Questar Corp', 704 | 'Quintiles Transnational', 705 | 'Qwest Communications Intl Inc', 706 | 'R.J. Reynolds Tobacco Company', 707 | 'R.R. Donnelley & Sons Company', 708 | 'Radio Shack Corporation', 709 | 'Raymond James Financial Inc.', 710 | 'Raytheon Company', 711 | "Reader's Digest Association Inc.", 712 | 'Reebok International Ltd.', 713 | 'Regions Financial Corp.', 714 | 'Regis Corporation', 715 | 'Reliance Steel & Aluminum Co.', 716 | 'Reliant Energy Inc.', 717 | 'Rent A Center Inc', 718 | 'Republic Services Inc', 719 | 'Revlon Inc', 720 | 'RGS Energy Group Inc', 721 | 'Rite Aid Corp', 722 | 'Riverwood Holding Inc.', 723 | 'RoadwayCorp', 724 | 'Robert Half International Inc.', 725 | 'Rock-Tenn Co', 726 | 'Rockwell Automation Inc', 727 | 'Rockwell Collins Inc', 728 | 'Rohm & Haas Co.', 729 | 'Ross Stores Inc', 730 | 'RPM Inc.', 731 | 'Ruddick Corp', 732 | 'Ryder System Inc', 733 | 'Ryerson Tull Inc', 734 | 'Ryland Group Inc.', 735 | 'Sabre Holdings Corp', 736 | 'Safeco Corp', 737 | 'Safeguard Scientifics Inc.', 738 | 'Safeway Inc', 739 | 'Saks Inc', 740 | 'Sanmina-SCI Inc', 741 | 'Sara Lee Corp', 742 | 'SBC Communications Inc', 743 | 'Scana Corp.', 744 | 'Schering-Plough Corp', 745 | 'Scholastic Corp', 746 | 'SCI Systems Onc.', 747 | 'Science Applications Intl. Inc.', 748 | 'Scientific-Atlanta Inc', 749 | 'Scotts Company', 750 | 'Seaboard Corp', 751 | 'Sealed Air Corp', 752 | 'Sears Roebuck & Co', 753 | 'Sempra Energy', 754 | 'Sequa Corp', 755 | 'Service Corp. International', 756 | 'ServiceMaster Co', 757 | 'Shaw Group Inc', 758 | 'Sherwin-Williams Company', 759 | 'Shopko Stores Inc', 760 | 'Siebel Systems Inc', 761 | 'Sierra Health Services Inc', 762 | 'Sierra Pacific Resources', 763 | 'Silgan Holdings Inc.', 764 | 'Silicon Graphics Inc', 765 | 'Simon Property Group Inc', 766 | 'SLM Corporation', 767 | 'Smith International Inc', 768 | 'Smithfield Foods Inc', 769 | 'Smurfit-Stone Container Corp', 770 | 'Snap-On Inc', 771 | 'Solectron Corp', 772 | 'Solutia Inc', 773 | 'Sonic Automotive Inc.', 774 | 'Sonoco Products Co.', 775 | 'Southern Company', 776 | 'Southern Union Company', 777 | 'SouthTrust Corp.', 778 | 'Southwest Airlines Co', 779 | 'Southwest Gas Corp', 780 | 'Sovereign Bancorp Inc.', 781 | 'Spartan Stores Inc', 782 | 'Spherion Corp', 783 | 'Sports Authority Inc', 784 | 'Sprint Corp.', 785 | 'SPX Corp', 786 | 'St. Jude Medical Inc', 787 | 'St. Paul Cos.', 788 | 'Staff Leasing Inc.', 789 | 'StanCorp Financial Group Inc', 790 | 'Standard Pacific Corp.', 791 | 'Stanley Works', 792 | 'Staples Inc', 793 | 'Starbucks Corp', 794 | 'Starwood Hotels & Resorts Worldwide Inc', 795 | 'State Street Corp.', 796 | 'Stater Bros. Holdings Inc.', 797 | 'Steelcase Inc', 798 | 'Stein Mart Inc', 799 | 'Stewart & Stevenson Services Inc', 800 | 'Stewart Information Services Corp', 801 | 'Stilwell Financial Inc', 802 | 'Storage Technology Corporation', 803 | 'Stryker Corp', 804 | 'Sun Healthcare Group Inc.', 805 | 'Sun Microsystems Inc.', 806 | 'SunGard Data Systems Inc.', 807 | 'Sunoco Inc.', 808 | 'SunTrust Banks Inc', 809 | 'Supervalu Inc', 810 | 'Swift Transportation, Co., Inc', 811 | 'Symbol Technologies Inc', 812 | 'Synovus Financial Corp.', 813 | 'Sysco Corp', 814 | 'Systemax Inc.', 815 | 'Target Corp.', 816 | 'Tech Data Corporation', 817 | 'TECO Energy Inc', 818 | 'Tecumseh Products Company', 819 | 'Tektronix Inc', 820 | 'Teleflex Incorporated', 821 | 'Telephone & Data Systems Inc', 822 | 'Tellabs Inc.', 823 | 'Temple-Inland Inc', 824 | 'Tenet Healthcare Corporation', 825 | 'Tenneco Automotive Inc.', 826 | 'Teradyne Inc', 827 | 'Terex Corp', 828 | 'Tesoro Petroleum Corp.', 829 | 'Texas Industries Inc.', 830 | 'Texas Instruments Incorporated', 831 | 'Textron Inc', 832 | 'Thermo Electron Corporation', 833 | 'Thomas & Betts Corporation', 834 | 'Tiffany & Co', 835 | 'Timken Company', 836 | 'TJX Companies Inc', 837 | 'TMP Worldwide Inc', 838 | 'Toll Brothers Inc', 839 | 'Torchmark Corporation', 840 | 'Toro Company', 841 | 'Tower Automotive Inc.', 842 | "Toys 'R' Us Inc", 843 | 'Trans World Entertainment Corp.', 844 | 'TransMontaigne Inc', 845 | 'Transocean Inc', 846 | 'TravelCenters of America Inc.', 847 | 'Triad Hospitals Inc', 848 | 'Tribune Company', 849 | 'Trigon Healthcare Inc.', 850 | 'Trinity Industries Inc', 851 | 'Trump Hotels & Casino Resorts Inc.', 852 | 'TruServ Corporation', 853 | 'TRW Inc', 854 | 'TXU Corp', 855 | 'Tyson Foods Inc', 856 | 'U.S. Bancorp', 857 | 'U.S. Industries Inc.', 858 | 'UAL Corporation', 859 | 'UGI Corporation', 860 | 'Unified Western Grocers Inc', 861 | 'Union Pacific Corporation', 862 | 'Union Planters Corp', 863 | 'Unisource Energy Corp', 864 | 'Unisys Corporation', 865 | 'United Auto Group Inc', 866 | 'United Defense Industries Inc.', 867 | 'United Parcel Service Inc', 868 | 'United Rentals Inc', 869 | 'United Stationers Inc', 870 | 'United Technologies Corporation', 871 | 'UnitedHealth Group Incorporated', 872 | 'Unitrin Inc', 873 | 'Universal Corporation', 874 | 'Universal Forest Products Inc', 875 | 'Universal Health Services Inc', 876 | 'Unocal Corporation', 877 | 'Unova Inc', 878 | 'UnumProvident Corporation', 879 | 'URS Corporation', 880 | 'US Airways Group Inc', 881 | 'US Oncology Inc', 882 | 'USA Interactive', 883 | 'USFreighways Corporation', 884 | 'USG Corporation', 885 | 'UST Inc', 886 | 'Valero Energy Corporation', 887 | 'Valspar Corporation', 888 | 'Value City Department Stores Inc', 889 | 'Varco International Inc', 890 | 'Vectren Corporation', 891 | 'Veritas Software Corporation', 892 | 'Verizon Communications Inc', 893 | 'VF Corporation', 894 | 'Viacom Inc', 895 | 'Viad Corp', 896 | 'Viasystems Group Inc', 897 | 'Vishay Intertechnology Inc', 898 | 'Visteon Corporation', 899 | 'Volt Information Sciences Inc', 900 | 'Vulcan Materials Company', 901 | 'W.R. Berkley Corporation', 902 | 'W.R. Grace & Co', 903 | 'W.W. Grainger Inc', 904 | 'Wachovia Corporation', 905 | 'Wakenhut Corporation', 906 | 'Walgreen Co', 907 | 'Wallace Computer Services Inc', 908 | 'Wal-Mart Stores Inc', 909 | 'Walt Disney Co', 910 | 'Walter Industries Inc', 911 | 'Washington Mutual Inc', 912 | 'Washington Post Co.', 913 | 'Waste Management Inc', 914 | 'Watsco Inc', 915 | 'Weatherford International Inc', 916 | 'Weis Markets Inc.', 917 | 'Wellpoint Health Networks Inc', 918 | 'Wells Fargo & Company', 919 | "Wendy's International Inc", 920 | 'Werner Enterprises Inc', 921 | 'WESCO International Inc', 922 | 'Western Digital Inc', 923 | 'Western Gas Resources Inc', 924 | 'WestPoint Stevens Inc', 925 | 'Weyerhauser Company', 926 | 'WGL Holdings Inc', 927 | 'Whirlpool Corporation', 928 | 'Whole Foods Market Inc', 929 | 'Willamette Industries Inc.', 930 | 'Williams Companies Inc', 931 | 'Williams Sonoma Inc', 932 | 'Winn Dixie Stores Inc', 933 | 'Wisconsin Energy Corporation', 934 | 'Wm Wrigley Jr Company', 935 | 'World Fuel Services Corporation', 936 | 'WorldCom Inc', 937 | 'Worthington Industries Inc', 938 | 'WPS Resources Corporation', 939 | 'Wyeth', 940 | 'Wyndham International Inc', 941 | 'Xcel Energy Inc', 942 | 'Xerox Corp', 943 | 'Xilinx Inc', 944 | 'XO Communications Inc', 945 | 'Yellow Corporation', 946 | 'York International Corp', 947 | 'Yum Brands Inc.', 948 | 'Zale Corporation', 949 | 'Zions Bancorporation', 950 | ] 951 | -------------------------------------------------------------------------------- /demo/src/testdata/usernames.js: -------------------------------------------------------------------------------- 1 | // borrowed from Nozbe FakeDataGenerator 2 | export const firstNames = [ 3 | 'aaron', 4 | 'abdul', 5 | 'abe', 6 | 'abel', 7 | 'abraham', 8 | 'abram', 9 | 'adalberto', 10 | 'adam', 11 | 'adan', 12 | 'adolfo', 13 | 'adolph', 14 | 'adrian', 15 | 'agustin', 16 | 'ahmad', 17 | 'ahmed', 18 | 'al', 19 | 'alan', 20 | 'albert', 21 | 'alberto', 22 | 'alden', 23 | 'aldo', 24 | 'alec', 25 | 'alejandro', 26 | 'alex', 27 | 'alexander', 28 | 'alexis', 29 | 'alfonso', 30 | 'alfonzo', 31 | 'alfred', 32 | 'alfredo', 33 | 'ali', 34 | 'allan', 35 | 'allen', 36 | 'alonso', 37 | 'alonzo', 38 | 'alphonse', 39 | 'alphonso', 40 | 'alton', 41 | 'alva', 42 | 'alvaro', 43 | 'alvin', 44 | 'amado', 45 | 'ambrose', 46 | 'amos', 47 | 'anderson', 48 | 'andre', 49 | 'andrea', 50 | 'andreas', 51 | 'andres', 52 | 'andrew', 53 | 'andy', 54 | 'angel', 55 | 'angelo', 56 | 'anibal', 57 | 'anthony', 58 | 'antione', 59 | 'antoine', 60 | 'anton', 61 | 'antone', 62 | 'antonia', 63 | 'antonio', 64 | 'antony', 65 | 'antwan', 66 | 'archie', 67 | 'arden', 68 | 'ariel', 69 | 'arlen', 70 | 'arlie', 71 | 'armand', 72 | 'armando', 73 | 'arnold', 74 | 'arnoldo', 75 | 'arnulfo', 76 | 'aron', 77 | 'arron', 78 | 'art', 79 | 'arthur', 80 | 'arturo', 81 | 'asa', 82 | 'ashley', 83 | 'aubrey', 84 | 'august', 85 | 'augustine', 86 | 'augustus', 87 | 'aurelio', 88 | 'austin', 89 | 'avery', 90 | 'barney', 91 | 'barrett', 92 | 'barry', 93 | 'bart', 94 | 'barton', 95 | 'basil', 96 | 'beau', 97 | 'ben', 98 | 'benedict', 99 | 'benito', 100 | 'benjamin', 101 | 'bennett', 102 | 'bennie', 103 | 'benny', 104 | 'benton', 105 | 'bernard', 106 | 'bernardo', 107 | 'bernie', 108 | 'berry', 109 | 'bert', 110 | 'bertram', 111 | 'bill', 112 | 'billie', 113 | 'billy', 114 | 'blaine', 115 | 'blair', 116 | 'blake', 117 | 'bo', 118 | 'bob', 119 | 'bobbie', 120 | 'bobby', 121 | 'booker', 122 | 'boris', 123 | 'boyce', 124 | 'boyd', 125 | 'brad', 126 | 'bradford', 127 | 'bradley', 128 | 'bradly', 129 | 'brady', 130 | 'brain', 131 | 'branden', 132 | 'brandon', 133 | 'brant', 134 | 'brendan', 135 | 'brendon', 136 | 'brent', 137 | 'brenton', 138 | 'bret', 139 | 'brett', 140 | 'brian', 141 | 'brice', 142 | 'britt', 143 | 'brock', 144 | 'broderick', 145 | 'brooks', 146 | 'bruce', 147 | 'bruno', 148 | 'bryan', 149 | 'bryant', 150 | 'bryce', 151 | 'bryon', 152 | 'buck', 153 | 'bud', 154 | 'buddy', 155 | 'buford', 156 | 'burl', 157 | 'burt', 158 | 'burton', 159 | 'buster', 160 | 'byron', 161 | 'caleb', 162 | 'calvin', 163 | 'cameron', 164 | 'carey', 165 | 'carl', 166 | 'carlo', 167 | 'carlos', 168 | 'carlton', 169 | 'carmelo', 170 | 'carmen', 171 | 'carmine', 172 | 'carol', 173 | 'carrol', 174 | 'carroll', 175 | 'carson', 176 | 'carter', 177 | 'cary', 178 | 'casey', 179 | 'cecil', 180 | 'cedric', 181 | 'cedrick', 182 | 'cesar', 183 | 'chad', 184 | 'chadwick', 185 | 'chance', 186 | 'chang', 187 | 'charles', 188 | 'charley', 189 | 'charlie', 190 | 'chas', 191 | 'chase', 192 | 'chauncey', 193 | 'chester', 194 | 'chet', 195 | 'chi', 196 | 'chong', 197 | 'chris', 198 | 'christian', 199 | 'christoper', 200 | 'christopher', 201 | 'chuck', 202 | 'chung', 203 | 'clair', 204 | 'clarence', 205 | 'clark', 206 | 'claud', 207 | 'claude', 208 | 'claudio', 209 | 'clay', 210 | 'clayton', 211 | 'clement', 212 | 'clemente', 213 | 'cleo', 214 | 'cletus', 215 | 'cleveland', 216 | 'cliff', 217 | 'clifford', 218 | 'clifton', 219 | 'clint', 220 | 'clinton', 221 | 'clyde', 222 | 'cody', 223 | 'colby', 224 | 'cole', 225 | 'coleman', 226 | 'colin', 227 | 'collin', 228 | 'colton', 229 | 'columbus', 230 | 'connie', 231 | 'conrad', 232 | 'cordell', 233 | 'corey', 234 | 'cornelius', 235 | 'cornell', 236 | 'cortez', 237 | 'cory', 238 | 'courtney', 239 | 'coy', 240 | 'craig', 241 | 'cristobal', 242 | 'cristopher', 243 | 'cruz', 244 | 'curt', 245 | 'curtis', 246 | 'cyril', 247 | 'cyrus', 248 | 'dale', 249 | 'dallas', 250 | 'dalton', 251 | 'damian', 252 | 'damien', 253 | 'damion', 254 | 'damon', 255 | 'dan', 256 | 'dana', 257 | 'dane', 258 | 'danial', 259 | 'daniel', 260 | 'danilo', 261 | 'dannie', 262 | 'danny', 263 | 'dante', 264 | 'darell', 265 | 'daren', 266 | 'darin', 267 | 'dario', 268 | 'darius', 269 | 'darnell', 270 | 'daron', 271 | 'darrel', 272 | 'darrell', 273 | 'darren', 274 | 'darrick', 275 | 'darrin', 276 | 'darron', 277 | 'darryl', 278 | 'darwin', 279 | 'daryl', 280 | 'dave', 281 | 'david', 282 | 'davis', 283 | 'dean', 284 | 'deandre', 285 | 'deangelo', 286 | 'dee', 287 | 'del', 288 | 'delbert', 289 | 'delmar', 290 | 'delmer', 291 | 'demarcus', 292 | 'demetrius', 293 | 'denis', 294 | 'dennis', 295 | 'denny', 296 | 'denver', 297 | 'deon', 298 | 'derek', 299 | 'derick', 300 | 'derrick', 301 | 'deshawn', 302 | 'desmond', 303 | 'devin', 304 | 'devon', 305 | 'dewayne', 306 | 'dewey', 307 | 'dewitt', 308 | 'dexter', 309 | 'dick', 310 | 'diego', 311 | 'dillon', 312 | 'dino', 313 | 'dion', 314 | 'dirk', 315 | 'domenic', 316 | 'domingo', 317 | 'dominic', 318 | 'dominick', 319 | 'dominique', 320 | 'don', 321 | 'donald', 322 | 'dong', 323 | 'donn', 324 | 'donnell', 325 | 'donnie', 326 | 'donny', 327 | 'donovan', 328 | 'donte', 329 | 'dorian', 330 | 'dorsey', 331 | 'doug', 332 | 'douglas', 333 | 'douglass', 334 | 'doyle', 335 | 'drew', 336 | 'duane', 337 | 'dudley', 338 | 'duncan', 339 | 'dustin', 340 | 'dusty', 341 | 'dwain', 342 | 'dwayne', 343 | 'dwight', 344 | 'dylan', 345 | 'earl', 346 | 'earle', 347 | 'earnest', 348 | 'ed', 349 | 'eddie', 350 | 'eddy', 351 | 'edgar', 352 | 'edgardo', 353 | 'edison', 354 | 'edmond', 355 | 'edmund', 356 | 'edmundo', 357 | 'eduardo', 358 | 'edward', 359 | 'edwardo', 360 | 'edwin', 361 | 'efrain', 362 | 'efren', 363 | 'elbert', 364 | 'elden', 365 | 'eldon', 366 | 'eldridge', 367 | 'eli', 368 | 'elias', 369 | 'elijah', 370 | 'eliseo', 371 | 'elisha', 372 | 'elliot', 373 | 'elliott', 374 | 'ellis', 375 | 'ellsworth', 376 | 'elmer', 377 | 'elmo', 378 | 'eloy', 379 | 'elroy', 380 | 'elton', 381 | 'elvin', 382 | 'elvis', 383 | 'elwood', 384 | 'emanuel', 385 | 'emerson', 386 | 'emery', 387 | 'emil', 388 | 'emile', 389 | 'emilio', 390 | 'emmanuel', 391 | 'emmett', 392 | 'emmitt', 393 | 'emory', 394 | 'enoch', 395 | 'enrique', 396 | 'erasmo', 397 | 'eric', 398 | 'erich', 399 | 'erick', 400 | 'erik', 401 | 'erin', 402 | 'ernest', 403 | 'ernesto', 404 | 'ernie', 405 | 'errol', 406 | 'ervin', 407 | 'erwin', 408 | 'esteban', 409 | 'ethan', 410 | 'eugene', 411 | 'eugenio', 412 | 'eusebio', 413 | 'evan', 414 | 'everett', 415 | 'everette', 416 | 'ezekiel', 417 | 'ezequiel', 418 | 'ezra', 419 | 'fabian', 420 | 'faustino', 421 | 'fausto', 422 | 'federico', 423 | 'felipe', 424 | 'felix', 425 | 'felton', 426 | 'ferdinand', 427 | 'fermin', 428 | 'fernando', 429 | 'fidel', 430 | 'filiberto', 431 | 'fletcher', 432 | 'florencio', 433 | 'florentino', 434 | 'floyd', 435 | 'forest', 436 | 'forrest', 437 | 'foster', 438 | 'frances', 439 | 'francesco', 440 | 'francis', 441 | 'francisco', 442 | 'frank', 443 | 'frankie', 444 | 'franklin', 445 | 'franklyn', 446 | 'fred', 447 | 'freddie', 448 | 'freddy', 449 | 'frederic', 450 | 'frederick', 451 | 'fredric', 452 | 'fredrick', 453 | 'freeman', 454 | 'fritz', 455 | 'gabriel', 456 | 'gail', 457 | 'gale', 458 | 'galen', 459 | 'garfield', 460 | 'garland', 461 | 'garret', 462 | 'garrett', 463 | 'garry', 464 | 'garth', 465 | 'gary', 466 | 'gaston', 467 | 'gavin', 468 | 'gayle', 469 | 'gaylord', 470 | 'genaro', 471 | 'gene', 472 | 'geoffrey', 473 | 'george', 474 | 'gerald', 475 | 'geraldo', 476 | 'gerard', 477 | 'gerardo', 478 | 'german', 479 | 'gerry', 480 | 'gil', 481 | 'gilbert', 482 | 'gilberto', 483 | 'gino', 484 | 'giovanni', 485 | 'giuseppe', 486 | 'glen', 487 | 'glenn', 488 | 'gonzalo', 489 | 'gordon', 490 | 'grady', 491 | 'graham', 492 | 'graig', 493 | 'grant', 494 | 'granville', 495 | 'greg', 496 | 'gregg', 497 | 'gregorio', 498 | 'gregory', 499 | 'grover', 500 | 'guadalupe', 501 | 'guillermo', 502 | 'gus', 503 | 'gustavo', 504 | 'guy', 505 | 'hai', 506 | 'hal', 507 | 'hank', 508 | 'hans', 509 | 'harlan', 510 | 'harland', 511 | 'harley', 512 | 'harold', 513 | 'harris', 514 | 'harrison', 515 | 'harry', 516 | 'harvey', 517 | 'hassan', 518 | 'hayden', 519 | 'haywood', 520 | 'heath', 521 | 'hector', 522 | 'henry', 523 | 'herb', 524 | 'herbert', 525 | 'heriberto', 526 | 'herman', 527 | 'herschel', 528 | 'hershel', 529 | 'hilario', 530 | 'hilton', 531 | 'hipolito', 532 | 'hiram', 533 | 'hobert', 534 | 'hollis', 535 | 'homer', 536 | 'hong', 537 | 'horace', 538 | 'horacio', 539 | 'hosea', 540 | 'houston', 541 | 'howard', 542 | 'hoyt', 543 | 'hubert', 544 | 'huey', 545 | 'hugh', 546 | 'hugo', 547 | 'humberto', 548 | 'hung', 549 | 'hunter', 550 | 'hyman', 551 | 'ian', 552 | 'ignacio', 553 | 'ike', 554 | 'ira', 555 | 'irvin', 556 | 'irving', 557 | 'irwin', 558 | 'isaac', 559 | 'isaiah', 560 | 'isaias', 561 | 'isiah', 562 | 'isidro', 563 | 'ismael', 564 | 'israel', 565 | 'isreal', 566 | 'issac', 567 | 'ivan', 568 | 'ivory', 569 | 'jacinto', 570 | 'jack', 571 | 'jackie', 572 | 'jackson', 573 | 'jacob', 574 | 'jacques', 575 | 'jae', 576 | 'jaime', 577 | 'jake', 578 | 'jamaal', 579 | 'jamal', 580 | 'jamar', 581 | 'jame', 582 | 'jamel', 583 | 'james', 584 | 'jamey', 585 | 'jamie', 586 | 'jamison', 587 | 'jan', 588 | 'jared', 589 | 'jarod', 590 | 'jarred', 591 | 'jarrett', 592 | 'jarrod', 593 | 'jarvis', 594 | 'jason', 595 | 'jasper', 596 | 'javier', 597 | 'jay', 598 | 'jayson', 599 | 'jc', 600 | 'jean', 601 | 'jed', 602 | 'jeff', 603 | 'jefferey', 604 | 'jefferson', 605 | 'jeffery', 606 | 'jeffrey', 607 | 'jeffry', 608 | 'jerald', 609 | 'jeramy', 610 | 'jere', 611 | 'jeremiah', 612 | 'jeremy', 613 | 'jermaine', 614 | 'jerold', 615 | 'jerome', 616 | 'jeromy', 617 | 'jerrell', 618 | 'jerrod', 619 | 'jerrold', 620 | 'jerry', 621 | 'jess', 622 | 'jesse', 623 | 'jessie', 624 | 'jesus', 625 | 'jewel', 626 | 'jewell', 627 | 'jim', 628 | 'jimmie', 629 | 'jimmy', 630 | 'joan', 631 | 'joaquin', 632 | 'jody', 633 | 'joe', 634 | 'joel', 635 | 'joesph', 636 | 'joey', 637 | 'john', 638 | 'johnathan', 639 | 'johnathon', 640 | 'johnie', 641 | 'johnnie', 642 | 'johnny', 643 | 'johnson', 644 | 'jon', 645 | 'jonah', 646 | 'jonas', 647 | 'jonathan', 648 | 'jonathon', 649 | 'jordan', 650 | 'jordon', 651 | 'jorge', 652 | 'jose', 653 | 'josef', 654 | 'joseph', 655 | 'josh', 656 | 'joshua', 657 | 'josiah', 658 | 'jospeh', 659 | 'josue', 660 | 'juan', 661 | 'jude', 662 | 'judson', 663 | 'jules', 664 | 'julian', 665 | 'julio', 666 | 'julius', 667 | 'junior', 668 | 'justin', 669 | 'kareem', 670 | 'karl', 671 | 'kasey', 672 | 'keenan', 673 | 'keith', 674 | 'kelley', 675 | 'kelly', 676 | 'kelvin', 677 | 'ken', 678 | 'kendall', 679 | 'kendrick', 680 | 'keneth', 681 | 'kenneth', 682 | 'kennith', 683 | 'kenny', 684 | 'kent', 685 | 'kenton', 686 | 'kermit', 687 | 'kerry', 688 | 'keven', 689 | 'kevin', 690 | 'kieth', 691 | 'kim', 692 | 'king', 693 | 'kip', 694 | 'kirby', 695 | 'kirk', 696 | 'korey', 697 | 'kory', 698 | 'kraig', 699 | 'kris', 700 | 'kristofer', 701 | 'kristopher', 702 | 'kurt', 703 | 'kurtis', 704 | 'kyle', 705 | 'lacy', 706 | 'lamar', 707 | 'lamont', 708 | 'lance', 709 | 'landon', 710 | 'lane', 711 | 'lanny', 712 | 'larry', 713 | 'lauren', 714 | 'laurence', 715 | 'lavern', 716 | 'laverne', 717 | 'lawerence', 718 | 'lawrence', 719 | 'lazaro', 720 | 'leandro', 721 | 'lee', 722 | 'leif', 723 | 'leigh', 724 | 'leland', 725 | 'lemuel', 726 | 'len', 727 | 'lenard', 728 | 'lenny', 729 | 'leo', 730 | 'leon', 731 | 'leonard', 732 | 'leonardo', 733 | 'leonel', 734 | 'leopoldo', 735 | 'leroy', 736 | 'les', 737 | 'lesley', 738 | 'leslie', 739 | 'lester', 740 | 'levi', 741 | 'lewis', 742 | 'lincoln', 743 | 'lindsay', 744 | 'lindsey', 745 | 'lino', 746 | 'linwood', 747 | 'lionel', 748 | 'lloyd', 749 | 'logan', 750 | 'lon', 751 | 'long', 752 | 'lonnie', 753 | 'lonny', 754 | 'loren', 755 | 'lorenzo', 756 | 'lou', 757 | 'louie', 758 | 'louis', 759 | 'lowell', 760 | 'loyd', 761 | 'lucas', 762 | 'luciano', 763 | 'lucien', 764 | 'lucio', 765 | 'lucius', 766 | 'luigi', 767 | 'luis', 768 | 'luke', 769 | 'lupe', 770 | 'luther', 771 | 'lyle', 772 | 'lyman', 773 | 'lyndon', 774 | 'lynn', 775 | 'lynwood', 776 | 'mac', 777 | 'mack', 778 | 'major', 779 | 'malcolm', 780 | 'malcom', 781 | 'malik', 782 | 'man', 783 | 'manual', 784 | 'manuel', 785 | 'marc', 786 | 'marcel', 787 | 'marcelino', 788 | 'marcellus', 789 | 'marcelo', 790 | 'marco', 791 | 'marcos', 792 | 'marcus', 793 | 'margarito', 794 | 'maria', 795 | 'mariano', 796 | 'mario', 797 | 'marion', 798 | 'mark', 799 | 'markus', 800 | 'marlin', 801 | 'marlon', 802 | 'marquis', 803 | 'marshall', 804 | 'martin', 805 | 'marty', 806 | 'marvin', 807 | 'mary', 808 | 'mason', 809 | 'mathew', 810 | 'matt', 811 | 'matthew', 812 | 'maurice', 813 | 'mauricio', 814 | 'mauro', 815 | 'max', 816 | 'maximo', 817 | 'maxwell', 818 | 'maynard', 819 | 'mckinley', 820 | 'mel', 821 | 'melvin', 822 | 'merle', 823 | 'merlin', 824 | 'merrill', 825 | 'mervin', 826 | 'micah', 827 | 'michael', 828 | 'michal', 829 | 'michale', 830 | 'micheal', 831 | 'michel', 832 | 'mickey', 833 | 'miguel', 834 | 'mike', 835 | 'mikel', 836 | 'milan', 837 | 'miles', 838 | 'milford', 839 | 'millard', 840 | 'milo', 841 | 'milton', 842 | 'minh', 843 | 'miquel', 844 | 'mitch', 845 | 'mitchel', 846 | 'mitchell', 847 | 'modesto', 848 | 'mohamed', 849 | 'mohammad', 850 | 'mohammed', 851 | 'moises', 852 | 'monroe', 853 | 'monte', 854 | 'monty', 855 | 'morgan', 856 | 'morris', 857 | 'morton', 858 | 'mose', 859 | 'moses', 860 | 'moshe', 861 | 'murray', 862 | 'myles', 863 | 'myron', 864 | 'napoleon', 865 | 'nathan', 866 | 'nathanael', 867 | 'nathanial', 868 | 'nathaniel', 869 | 'neal', 870 | 'ned', 871 | 'neil', 872 | 'nelson', 873 | 'nestor', 874 | 'neville', 875 | 'newton', 876 | 'nicholas', 877 | 'nick', 878 | 'nickolas', 879 | 'nicky', 880 | 'nicolas', 881 | 'nigel', 882 | 'noah', 883 | 'noble', 884 | 'noe', 885 | 'noel', 886 | 'nolan', 887 | 'norbert', 888 | 'norberto', 889 | 'norman', 890 | 'normand', 891 | 'norris', 892 | 'numbers', 893 | 'octavio', 894 | 'odell', 895 | 'odis', 896 | 'olen', 897 | 'olin', 898 | 'oliver', 899 | 'ollie', 900 | 'omar', 901 | 'omer', 902 | 'oren', 903 | 'orlando', 904 | 'orval', 905 | 'orville', 906 | 'oscar', 907 | 'osvaldo', 908 | 'oswaldo', 909 | 'otha', 910 | 'otis', 911 | 'otto', 912 | 'owen', 913 | 'pablo', 914 | 'palmer', 915 | 'paris', 916 | 'parker', 917 | 'pasquale', 918 | 'pat', 919 | 'patricia', 920 | 'patrick', 921 | 'paul', 922 | 'pedro', 923 | 'percy', 924 | 'perry', 925 | 'pete', 926 | 'peter', 927 | 'phil', 928 | 'philip', 929 | 'phillip', 930 | 'pierre', 931 | 'porfirio', 932 | 'porter', 933 | 'preston', 934 | 'prince', 935 | 'quentin', 936 | 'quincy', 937 | 'quinn', 938 | 'quintin', 939 | 'quinton', 940 | 'rafael', 941 | 'raleigh', 942 | 'ralph', 943 | 'ramiro', 944 | 'ramon', 945 | 'randal', 946 | 'randall', 947 | 'randell', 948 | 'randolph', 949 | 'randy', 950 | 'raphael', 951 | 'rashad', 952 | 'raul', 953 | 'ray', 954 | 'rayford', 955 | 'raymon', 956 | 'raymond', 957 | 'raymundo', 958 | 'reed', 959 | 'refugio', 960 | 'reggie', 961 | 'reginald', 962 | 'reid', 963 | 'reinaldo', 964 | 'renaldo', 965 | 'renato', 966 | 'rene', 967 | 'reuben', 968 | 'rex', 969 | 'rey', 970 | 'reyes', 971 | 'reynaldo', 972 | 'rhett', 973 | 'ricardo', 974 | 'rich', 975 | 'richard', 976 | 'richie', 977 | 'rick', 978 | 'rickey', 979 | 'rickie', 980 | 'ricky', 981 | 'rico', 982 | 'rigoberto', 983 | 'riley', 984 | 'rob', 985 | 'robbie', 986 | 'robby', 987 | 'robert', 988 | 'roberto', 989 | 'robin', 990 | 'robt', 991 | 'rocco', 992 | 'rocky', 993 | 'rod', 994 | 'roderick', 995 | 'rodger', 996 | 'rodney', 997 | 'rodolfo', 998 | 'rodrick', 999 | 'rodrigo', 1000 | 'rogelio', 1001 | 'roger', 1002 | 'roland', 1003 | 'rolando', 1004 | 'rolf', 1005 | 'rolland', 1006 | 'roman', 1007 | 'romeo', 1008 | 'ron', 1009 | 'ronald', 1010 | 'ronnie', 1011 | 'ronny', 1012 | 'roosevelt', 1013 | 'rory', 1014 | 'rosario', 1015 | 'roscoe', 1016 | 'rosendo', 1017 | 'ross', 1018 | 'roy', 1019 | 'royal', 1020 | 'royce', 1021 | 'ruben', 1022 | 'rubin', 1023 | 'rudolf', 1024 | 'rudolph', 1025 | 'rudy', 1026 | 'rueben', 1027 | 'rufus', 1028 | 'rupert', 1029 | 'russ', 1030 | 'russel', 1031 | 'russell', 1032 | 'rusty', 1033 | 'ryan', 1034 | 'sal', 1035 | 'salvador', 1036 | 'salvatore', 1037 | 'sam', 1038 | 'sammie', 1039 | 'sammy', 1040 | 'samual', 1041 | 'samuel', 1042 | 'sandy', 1043 | 'sanford', 1044 | 'sang', 1045 | 'santiago', 1046 | 'santo', 1047 | 'santos', 1048 | 'saul', 1049 | 'scot', 1050 | 'scott', 1051 | 'scottie', 1052 | 'scotty', 1053 | 'sean', 1054 | 'sebastian', 1055 | 'sergio', 1056 | 'seth', 1057 | 'seymour', 1058 | 'shad', 1059 | 'shane', 1060 | 'shannon', 1061 | 'shaun', 1062 | 'shawn', 1063 | 'shayne', 1064 | 'shelby', 1065 | 'sheldon', 1066 | 'shelton', 1067 | 'sherman', 1068 | 'sherwood', 1069 | 'shirley', 1070 | 'shon', 1071 | 'sid', 1072 | 'sidney', 1073 | 'silas', 1074 | 'simon', 1075 | 'sol', 1076 | 'solomon', 1077 | 'son', 1078 | 'sonny', 1079 | 'spencer', 1080 | 'stacey', 1081 | 'stacy', 1082 | 'stan', 1083 | 'stanford', 1084 | 'stanley', 1085 | 'stanton', 1086 | 'stefan', 1087 | 'stephan', 1088 | 'stephen', 1089 | 'sterling', 1090 | 'steve', 1091 | 'steven', 1092 | 'stevie', 1093 | 'stewart', 1094 | 'stuart', 1095 | 'sung', 1096 | 'sydney', 1097 | 'sylvester', 1098 | 'tad', 1099 | 'tanner', 1100 | 'taylor', 1101 | 'ted', 1102 | 'teddy', 1103 | 'teodoro', 1104 | 'terence', 1105 | 'terrance', 1106 | 'terrell', 1107 | 'terrence', 1108 | 'terry', 1109 | 'thad', 1110 | 'thaddeus', 1111 | 'thanh', 1112 | 'theo', 1113 | 'theodore', 1114 | 'theron', 1115 | 'thomas', 1116 | 'thurman', 1117 | 'tim', 1118 | 'timmy', 1119 | 'timothy', 1120 | 'titus', 1121 | 'tobias', 1122 | 'toby', 1123 | 'tod', 1124 | 'todd', 1125 | 'tom', 1126 | 'tomas', 1127 | 'tommie', 1128 | 'tommy', 1129 | 'toney', 1130 | 'tony', 1131 | 'tory', 1132 | 'tracey', 1133 | 'tracy', 1134 | 'travis', 1135 | 'trent', 1136 | 'trenton', 1137 | 'trevor', 1138 | 'trey', 1139 | 'trinidad', 1140 | 'tristan', 1141 | 'troy', 1142 | 'truman', 1143 | 'tuan', 1144 | 'ty', 1145 | 'tyler', 1146 | 'tyree', 1147 | 'tyrell', 1148 | 'tyron', 1149 | 'tyrone', 1150 | 'tyson', 1151 | 'ulysses', 1152 | 'val', 1153 | 'valentin', 1154 | 'valentine', 1155 | 'van', 1156 | 'vance', 1157 | 'vaughn', 1158 | 'vern', 1159 | 'vernon', 1160 | 'vicente', 1161 | 'victor', 1162 | 'vince', 1163 | 'vincent', 1164 | 'vincenzo', 1165 | 'virgil', 1166 | 'virgilio', 1167 | 'vito', 1168 | 'von', 1169 | 'wade', 1170 | 'waldo', 1171 | 'walker', 1172 | 'wallace', 1173 | 'wally', 1174 | 'walter', 1175 | 'walton', 1176 | 'ward', 1177 | 'warner', 1178 | 'warren', 1179 | 'waylon', 1180 | 'wayne', 1181 | 'weldon', 1182 | 'wendell', 1183 | 'werner', 1184 | 'wes', 1185 | 'wesley', 1186 | 'weston', 1187 | 'whitney', 1188 | 'wilber', 1189 | 'wilbert', 1190 | 'wilbur', 1191 | 'wilburn', 1192 | 'wiley', 1193 | 'wilford', 1194 | 'wilfred', 1195 | 'wilfredo', 1196 | 'will', 1197 | 'willard', 1198 | 'william', 1199 | 'williams', 1200 | 'willian', 1201 | 'willie', 1202 | 'willis', 1203 | 'willy', 1204 | 'wilmer', 1205 | 'wilson', 1206 | 'wilton', 1207 | 'winford', 1208 | 'winfred', 1209 | 'winston', 1210 | 'wm', 1211 | 'woodrow', 1212 | 'wyatt', 1213 | 'xavier', 1214 | 'yong', 1215 | 'young', 1216 | 'zachariah', 1217 | 'zachary', 1218 | 'zachery', 1219 | 'zack', 1220 | 'zackary', 1221 | 'zane', 1222 | // Nozbiki <3 1223 | 'Michał', 1224 | 'Tomek', 1225 | 'Delfina', 1226 | 'Iwona', 1227 | 'Waldemar', 1228 | 'Radek', 1229 | 'Staszek', 1230 | 'Radziu', 1231 | 'Magda', 1232 | 'Rafał', 1233 | 'Martyna', 1234 | 'Marcin', 1235 | 'Hubert', 1236 | 'Łukasz', 1237 | 'Radosław', 1238 | 'Emilia', 1239 | 'Natalia', 1240 | 'Marcin', 1241 | 'Kuba', 1242 | 'Ola', 1243 | 'Celina', 1244 | 'Dominik', 1245 | 'Kamil', 1246 | 'Konrad', 1247 | 'Brianne', 1248 | 'Kasia', 1249 | 'Martina', 1250 | 'Maria', 1251 | 'Keisuke', 1252 | 'Kang', 1253 | 'Wang', 1254 | 'Callista', 1255 | 'Marina', 1256 | 'Pierre', 1257 | 'Diana', 1258 | 'Magda', 1259 | 'Jakub', 1260 | 'Michalina', 1261 | 'Wojtys', 1262 | 'Paweł', 1263 | 'Kiran', 1264 | 'Patryk', 1265 | 'Bartek', 1266 | 'Anka', 1267 | 'Zosia', 1268 | 'Patryk', 1269 | 'Krzysztof', 1270 | // Dominik OMG 1271 | 'Damazy', 1272 | 'Damian', 1273 | 'Daniel', 1274 | 'Dariusz', 1275 | 'Dawid', 1276 | 'Demetriusz', 1277 | 'Dezyderiusz', 1278 | 'Dionizy', 1279 | 'Dobiegniew', 1280 | 'Dobiesław', 1281 | 'Dobromił', 1282 | 'Dobromir', 1283 | 'Dobrosław', 1284 | 'Domicjan', 1285 | 'Dominik', 1286 | 'Dorian', 1287 | 'Drogomir', 1288 | 'Dymitriusz', 1289 | 'Dyzma', 1290 | 'Dzierżysław', 1291 | 'PolishBoy', 1292 | ] 1293 | 1294 | // LAST NAMES 1295 | export const lastNames = [ 1296 | 'smith', 1297 | 'johnson', 1298 | 'williams', 1299 | 'jones', 1300 | 'brown', 1301 | 'davis', 1302 | 'miller', 1303 | 'wilson', 1304 | 'moore', 1305 | 'taylor', 1306 | 'anderson', 1307 | 'thomas', 1308 | 'jackson', 1309 | 'white', 1310 | 'harris', 1311 | 'martin', 1312 | 'thompson', 1313 | 'garcia', 1314 | 'martinez', 1315 | 'robinson', 1316 | 'clark', 1317 | 'rodriguez', 1318 | 'lewis', 1319 | 'lee', 1320 | 'walker', 1321 | 'hall', 1322 | 'allen', 1323 | 'young', 1324 | 'hernandez', 1325 | 'king', 1326 | 'wright', 1327 | 'lopez', 1328 | 'hill', 1329 | 'scott', 1330 | 'green', 1331 | 'adams', 1332 | 'baker', 1333 | 'gonzalez', 1334 | 'nelson', 1335 | 'carter', 1336 | 'mitchell', 1337 | 'perez', 1338 | 'roberts', 1339 | 'turner', 1340 | 'phillips', 1341 | 'campbell', 1342 | 'parker', 1343 | 'evans', 1344 | 'edwards', 1345 | 'collins', 1346 | 'stewart', 1347 | 'sanchez', 1348 | 'morris', 1349 | 'rogers', 1350 | 'reed', 1351 | 'cook', 1352 | 'morgan', 1353 | 'bell', 1354 | 'murphy', 1355 | 'bailey', 1356 | 'rivera', 1357 | 'cooper', 1358 | 'richardson', 1359 | 'cox', 1360 | 'howard', 1361 | 'ward', 1362 | 'torres', 1363 | 'peterson', 1364 | 'gray', 1365 | 'ramirez', 1366 | 'james', 1367 | 'watson', 1368 | 'brooks', 1369 | 'kelly', 1370 | 'sanders', 1371 | 'price', 1372 | 'bennett', 1373 | 'wood', 1374 | 'barnes', 1375 | 'ross', 1376 | 'henderson', 1377 | 'coleman', 1378 | 'jenkins', 1379 | 'perry', 1380 | 'powell', 1381 | 'long', 1382 | 'patterson', 1383 | 'hughes', 1384 | 'flores', 1385 | 'washington', 1386 | 'butler', 1387 | 'simmons', 1388 | 'foster', 1389 | 'gonzales', 1390 | 'bryant', 1391 | 'alexander', 1392 | 'russell', 1393 | 'griffin', 1394 | 'diaz', 1395 | 'hayes', 1396 | 'myers', 1397 | 'ford', 1398 | 'hamilton', 1399 | 'graham', 1400 | 'sullivan', 1401 | 'wallace', 1402 | 'woods', 1403 | 'cole', 1404 | 'west', 1405 | 'jordan', 1406 | 'owens', 1407 | 'reynolds', 1408 | 'fisher', 1409 | 'ellis', 1410 | 'harrison', 1411 | 'gibson', 1412 | 'mcdonald', 1413 | 'cruz', 1414 | 'marshall', 1415 | 'ortiz', 1416 | 'gomez', 1417 | 'murray', 1418 | 'freeman', 1419 | 'wells', 1420 | 'webb', 1421 | 'simpson', 1422 | 'stevens', 1423 | 'tucker', 1424 | 'porter', 1425 | 'hunter', 1426 | 'hicks', 1427 | 'crawford', 1428 | 'henry', 1429 | 'boyd', 1430 | 'mason', 1431 | 'morales', 1432 | 'kennedy', 1433 | 'warren', 1434 | 'dixon', 1435 | 'ramos', 1436 | 'reyes', 1437 | 'burns', 1438 | 'gordon', 1439 | 'shaw', 1440 | 'holmes', 1441 | 'rice', 1442 | 'robertson', 1443 | 'hunt', 1444 | 'black', 1445 | 'daniels', 1446 | 'palmer', 1447 | 'mills', 1448 | 'nichols', 1449 | 'grant', 1450 | 'knight', 1451 | 'ferguson', 1452 | 'rose', 1453 | 'stone', 1454 | 'hawkins', 1455 | 'dunn', 1456 | 'perkins', 1457 | 'hudson', 1458 | 'spencer', 1459 | 'gardner', 1460 | 'stephens', 1461 | 'payne', 1462 | 'pierce', 1463 | 'berry', 1464 | 'matthews', 1465 | 'arnold', 1466 | 'wagner', 1467 | 'willis', 1468 | 'ray', 1469 | 'watkins', 1470 | 'olson', 1471 | 'carroll', 1472 | 'duncan', 1473 | 'snyder', 1474 | 'hart', 1475 | 'cunningham', 1476 | 'bradley', 1477 | 'lane', 1478 | 'andrews', 1479 | 'ruiz', 1480 | 'harper', 1481 | 'fox', 1482 | 'riley', 1483 | 'armstrong', 1484 | 'carpenter', 1485 | 'weaver', 1486 | 'greene', 1487 | 'lawrence', 1488 | 'elliott', 1489 | 'chavez', 1490 | 'sims', 1491 | 'austin', 1492 | 'peters', 1493 | 'kelley', 1494 | 'franklin', 1495 | 'lawson', 1496 | 'fields', 1497 | 'gutierrez', 1498 | 'ryan', 1499 | 'schmidt', 1500 | 'carr', 1501 | 'vasquez', 1502 | 'castillo', 1503 | 'wheeler', 1504 | 'chapman', 1505 | 'oliver', 1506 | 'montgomery', 1507 | 'richards', 1508 | 'williamson', 1509 | 'johnston', 1510 | 'banks', 1511 | 'meyer', 1512 | 'bishop', 1513 | 'mccoy', 1514 | 'howell', 1515 | 'alvarez', 1516 | 'morrison', 1517 | 'hansen', 1518 | 'fernandez', 1519 | 'garza', 1520 | 'harvey', 1521 | 'little', 1522 | 'burton', 1523 | 'stanley', 1524 | 'nguyen', 1525 | 'george', 1526 | 'jacobs', 1527 | 'reid', 1528 | 'kim', 1529 | 'fuller', 1530 | 'lynch', 1531 | 'dean', 1532 | 'gilbert', 1533 | 'garrett', 1534 | 'romero', 1535 | 'welch', 1536 | 'larson', 1537 | 'frazier', 1538 | 'burke', 1539 | 'hanson', 1540 | 'day', 1541 | 'mendoza', 1542 | 'moreno', 1543 | 'bowman', 1544 | 'medina', 1545 | 'fowler', 1546 | 'brewer', 1547 | 'hoffman', 1548 | 'carlson', 1549 | 'silva', 1550 | 'pearson', 1551 | 'holland', 1552 | 'douglas', 1553 | 'fleming', 1554 | 'jensen', 1555 | 'vargas', 1556 | 'byrd', 1557 | 'davidson', 1558 | 'hopkins', 1559 | 'may', 1560 | 'terry', 1561 | 'herrera', 1562 | 'wade', 1563 | 'soto', 1564 | 'walters', 1565 | 'curtis', 1566 | 'neal', 1567 | 'caldwell', 1568 | 'lowe', 1569 | 'jennings', 1570 | 'barnett', 1571 | 'graves', 1572 | 'jimenez', 1573 | 'horton', 1574 | 'shelton', 1575 | 'barrett', 1576 | 'obrien', 1577 | 'castro', 1578 | 'sutton', 1579 | 'gregory', 1580 | 'mckinney', 1581 | 'lucas', 1582 | 'miles', 1583 | 'craig', 1584 | 'rodriquez', 1585 | 'chambers', 1586 | 'holt', 1587 | 'lambert', 1588 | 'fletcher', 1589 | 'watts', 1590 | 'bates', 1591 | 'hale', 1592 | 'rhodes', 1593 | 'pena', 1594 | 'beck', 1595 | 'newman', 1596 | 'haynes', 1597 | 'mcdaniel', 1598 | 'mendez', 1599 | 'bush', 1600 | 'vaughn', 1601 | 'parks', 1602 | 'dawson', 1603 | 'santiago', 1604 | 'norris', 1605 | 'hardy', 1606 | 'love', 1607 | 'steele', 1608 | 'curry', 1609 | 'powers', 1610 | 'schultz', 1611 | 'barker', 1612 | 'guzman', 1613 | 'page', 1614 | 'munoz', 1615 | 'ball', 1616 | 'keller', 1617 | 'chandler', 1618 | 'weber', 1619 | 'leonard', 1620 | 'walsh', 1621 | 'lyons', 1622 | 'ramsey', 1623 | 'wolfe', 1624 | 'schneider', 1625 | 'mullins', 1626 | 'benson', 1627 | 'sharp', 1628 | 'bowen', 1629 | 'daniel', 1630 | 'barber', 1631 | 'cummings', 1632 | 'hines', 1633 | 'baldwin', 1634 | 'griffith', 1635 | 'valdez', 1636 | 'hubbard', 1637 | 'salazar', 1638 | 'reeves', 1639 | 'warner', 1640 | 'stevenson', 1641 | 'burgess', 1642 | 'santos', 1643 | 'tate', 1644 | 'cross', 1645 | 'garner', 1646 | 'mann', 1647 | 'mack', 1648 | 'moss', 1649 | 'thornton', 1650 | 'dennis', 1651 | 'mcgee', 1652 | 'farmer', 1653 | 'delgado', 1654 | 'aguilar', 1655 | 'vega', 1656 | 'glover', 1657 | 'manning', 1658 | 'cohen', 1659 | 'harmon', 1660 | 'rodgers', 1661 | 'robbins', 1662 | 'newton', 1663 | 'todd', 1664 | 'blair', 1665 | 'higgins', 1666 | 'ingram', 1667 | 'reese', 1668 | 'cannon', 1669 | 'strickland', 1670 | 'townsend', 1671 | 'potter', 1672 | 'goodwin', 1673 | 'walton', 1674 | 'rowe', 1675 | 'hampton', 1676 | 'ortega', 1677 | 'patton', 1678 | 'swanson', 1679 | 'joseph', 1680 | 'francis', 1681 | 'goodman', 1682 | 'maldonado', 1683 | 'yates', 1684 | 'becker', 1685 | 'erickson', 1686 | 'hodges', 1687 | 'rios', 1688 | 'conner', 1689 | 'adkins', 1690 | 'webster', 1691 | 'norman', 1692 | 'malone', 1693 | 'hammond', 1694 | 'flowers', 1695 | 'cobb', 1696 | 'moody', 1697 | 'quinn', 1698 | 'blake', 1699 | 'maxwell', 1700 | 'pope', 1701 | 'floyd', 1702 | 'osborne', 1703 | 'paul', 1704 | 'mccarthy', 1705 | 'guerrero', 1706 | 'lindsey', 1707 | 'estrada', 1708 | 'sandoval', 1709 | 'gibbs', 1710 | 'tyler', 1711 | 'gross', 1712 | 'fitzgerald', 1713 | 'stokes', 1714 | 'doyle', 1715 | 'sherman', 1716 | 'saunders', 1717 | 'wise', 1718 | 'colon', 1719 | 'gill', 1720 | 'alvarado', 1721 | 'greer', 1722 | 'padilla', 1723 | 'simon', 1724 | 'waters', 1725 | 'nunez', 1726 | 'ballard', 1727 | 'schwartz', 1728 | 'mcbride', 1729 | 'houston', 1730 | 'christensen', 1731 | 'klein', 1732 | 'pratt', 1733 | 'briggs', 1734 | 'parsons', 1735 | 'mclaughlin', 1736 | 'zimmerman', 1737 | 'french', 1738 | 'buchanan', 1739 | 'moran', 1740 | 'copeland', 1741 | 'roy', 1742 | 'pittman', 1743 | 'brady', 1744 | 'mccormick', 1745 | 'holloway', 1746 | 'brock', 1747 | 'poole', 1748 | 'frank', 1749 | 'logan', 1750 | 'owen', 1751 | 'bass', 1752 | 'marsh', 1753 | 'drake', 1754 | 'wong', 1755 | 'jefferson', 1756 | 'park', 1757 | 'morton', 1758 | 'abbott', 1759 | 'sparks', 1760 | 'patrick', 1761 | 'norton', 1762 | 'huff', 1763 | 'clayton', 1764 | 'massey', 1765 | 'lloyd', 1766 | 'figueroa', 1767 | 'carson', 1768 | 'bowers', 1769 | 'roberson', 1770 | 'barton', 1771 | 'tran', 1772 | 'lamb', 1773 | 'harrington', 1774 | 'casey', 1775 | 'boone', 1776 | 'cortez', 1777 | 'clarke', 1778 | 'mathis', 1779 | 'singleton', 1780 | 'wilkins', 1781 | 'cain', 1782 | 'bryan', 1783 | 'underwood', 1784 | 'hogan', 1785 | 'mckenzie', 1786 | 'collier', 1787 | 'luna', 1788 | 'phelps', 1789 | 'mcguire', 1790 | 'allison', 1791 | 'bridges', 1792 | 'wilkerson', 1793 | 'nash', 1794 | 'summers', 1795 | 'atkins', 1796 | 'wilcox', 1797 | 'pitts', 1798 | 'conley', 1799 | 'marquez', 1800 | 'burnett', 1801 | 'richard', 1802 | 'cochran', 1803 | 'chase', 1804 | 'davenport', 1805 | 'hood', 1806 | 'gates', 1807 | 'clay', 1808 | 'ayala', 1809 | 'sawyer', 1810 | 'roman', 1811 | 'vazquez', 1812 | 'dickerson', 1813 | 'hodge', 1814 | 'acosta', 1815 | 'flynn', 1816 | 'espinoza', 1817 | 'nicholson', 1818 | 'monroe', 1819 | 'wolf', 1820 | 'morrow', 1821 | 'kirk', 1822 | 'randall', 1823 | 'anthony', 1824 | 'whitaker', 1825 | 'oconnor', 1826 | 'skinner', 1827 | 'ware', 1828 | 'molina', 1829 | 'kirby', 1830 | 'huffman', 1831 | 'bradford', 1832 | 'charles', 1833 | 'gilmore', 1834 | 'dominguez', 1835 | 'oneal', 1836 | 'bruce', 1837 | 'lang', 1838 | 'combs', 1839 | 'kramer', 1840 | 'heath', 1841 | 'hancock', 1842 | 'gallagher', 1843 | 'gaines', 1844 | 'shaffer', 1845 | 'short', 1846 | 'wiggins', 1847 | 'mathews', 1848 | 'mcclain', 1849 | 'fischer', 1850 | 'wall', 1851 | 'small', 1852 | 'melton', 1853 | 'hensley', 1854 | 'bond', 1855 | 'dyer', 1856 | 'cameron', 1857 | 'grimes', 1858 | 'contreras', 1859 | 'christian', 1860 | 'wyatt', 1861 | 'baxter', 1862 | 'snow', 1863 | 'mosley', 1864 | 'shepherd', 1865 | 'larsen', 1866 | 'hoover', 1867 | 'beasley', 1868 | 'glenn', 1869 | 'petersen', 1870 | 'whitehead', 1871 | 'meyers', 1872 | 'keith', 1873 | 'garrison', 1874 | 'vincent', 1875 | 'shields', 1876 | 'horn', 1877 | 'savage', 1878 | 'olsen', 1879 | 'schroeder', 1880 | 'hartman', 1881 | 'woodard', 1882 | 'mueller', 1883 | 'kemp', 1884 | 'deleon', 1885 | 'booth', 1886 | 'patel', 1887 | 'calhoun', 1888 | 'wiley', 1889 | 'eaton', 1890 | 'cline', 1891 | 'navarro', 1892 | 'harrell', 1893 | 'lester', 1894 | 'humphrey', 1895 | 'parrish', 1896 | 'duran', 1897 | 'hutchinson', 1898 | 'hess', 1899 | 'dorsey', 1900 | 'bullock', 1901 | 'robles', 1902 | 'beard', 1903 | 'dalton', 1904 | 'avila', 1905 | 'vance', 1906 | 'rich', 1907 | 'blackwell', 1908 | 'york', 1909 | 'johns', 1910 | 'blankenship', 1911 | 'trevino', 1912 | 'salinas', 1913 | 'campos', 1914 | 'pruitt', 1915 | 'moses', 1916 | 'callahan', 1917 | 'golden', 1918 | 'montoya', 1919 | 'hardin', 1920 | 'guerra', 1921 | 'mcdowell', 1922 | 'carey', 1923 | 'stafford', 1924 | 'gallegos', 1925 | 'henson', 1926 | 'wilkinson', 1927 | 'booker', 1928 | 'merritt', 1929 | 'miranda', 1930 | 'atkinson', 1931 | 'orr', 1932 | 'decker', 1933 | 'hobbs', 1934 | 'preston', 1935 | 'tanner', 1936 | 'knox', 1937 | 'pacheco', 1938 | 'stephenson', 1939 | 'glass', 1940 | 'rojas', 1941 | 'serrano', 1942 | 'marks', 1943 | 'hickman', 1944 | 'english', 1945 | 'sweeney', 1946 | 'strong', 1947 | 'prince', 1948 | 'mcclure', 1949 | 'conway', 1950 | 'walter', 1951 | 'roth', 1952 | 'maynard', 1953 | 'farrell', 1954 | 'lowery', 1955 | 'hurst', 1956 | 'nixon', 1957 | 'weiss', 1958 | 'trujillo', 1959 | 'ellison', 1960 | 'sloan', 1961 | 'juarez', 1962 | 'winters', 1963 | 'mclean', 1964 | 'randolph', 1965 | 'leon', 1966 | 'boyer', 1967 | 'villarreal', 1968 | 'mccall', 1969 | 'gentry', 1970 | 'carrillo', 1971 | 'kent', 1972 | 'ayers', 1973 | 'lara', 1974 | 'shannon', 1975 | 'sexton', 1976 | 'pace', 1977 | 'hull', 1978 | 'leblanc', 1979 | 'browning', 1980 | 'velasquez', 1981 | 'leach', 1982 | 'chang', 1983 | 'house', 1984 | 'sellers', 1985 | 'herring', 1986 | 'noble', 1987 | 'foley', 1988 | 'bartlett', 1989 | 'mercado', 1990 | 'landry', 1991 | 'durham', 1992 | 'walls', 1993 | 'barr', 1994 | 'mckee', 1995 | 'bauer', 1996 | 'rivers', 1997 | 'everett', 1998 | 'bradshaw', 1999 | 'pugh', 2000 | 'velez', 2001 | 'rush', 2002 | 'estes', 2003 | 'dodson', 2004 | 'morse', 2005 | 'sheppard', 2006 | 'weeks', 2007 | 'camacho', 2008 | 'bean', 2009 | 'barron', 2010 | 'livingston', 2011 | 'middleton', 2012 | 'spears', 2013 | 'branch', 2014 | 'blevins', 2015 | 'chen', 2016 | 'kerr', 2017 | 'mcconnell', 2018 | 'hatfield', 2019 | 'harding', 2020 | 'ashley', 2021 | 'solis', 2022 | 'herman', 2023 | 'frost', 2024 | 'giles', 2025 | 'blackburn', 2026 | 'william', 2027 | 'pennington', 2028 | 'woodward', 2029 | 'finley', 2030 | 'mcintosh', 2031 | 'koch', 2032 | 'best', 2033 | 'solomon', 2034 | 'mccullough', 2035 | 'dudley', 2036 | 'nolan', 2037 | 'blanchard', 2038 | 'rivas', 2039 | 'brennan', 2040 | 'mejia', 2041 | 'kane', 2042 | 'benton', 2043 | 'joyce', 2044 | 'buckley', 2045 | 'haley', 2046 | 'valentine', 2047 | 'maddox', 2048 | 'russo', 2049 | 'mcknight', 2050 | 'buck', 2051 | 'moon', 2052 | 'mcmillan', 2053 | 'crosby', 2054 | 'berg', 2055 | 'dotson', 2056 | 'mays', 2057 | 'roach', 2058 | 'church', 2059 | 'chan', 2060 | 'richmond', 2061 | 'meadows', 2062 | 'faulkner', 2063 | 'oneill', 2064 | 'knapp', 2065 | 'kline', 2066 | 'barry', 2067 | 'ochoa', 2068 | 'jacobson', 2069 | 'gay', 2070 | 'avery', 2071 | 'hendricks', 2072 | 'horne', 2073 | 'shepard', 2074 | 'hebert', 2075 | 'cherry', 2076 | 'cardenas', 2077 | 'mcintyre', 2078 | 'whitney', 2079 | 'waller', 2080 | 'holman', 2081 | 'donaldson', 2082 | 'cantu', 2083 | 'terrell', 2084 | 'morin', 2085 | 'gillespie', 2086 | 'fuentes', 2087 | 'tillman', 2088 | 'sanford', 2089 | 'bentley', 2090 | 'peck', 2091 | 'key', 2092 | 'salas', 2093 | 'rollins', 2094 | 'gamble', 2095 | 'dickson', 2096 | 'battle', 2097 | 'santana', 2098 | 'cabrera', 2099 | 'cervantes', 2100 | 'howe', 2101 | 'hinton', 2102 | 'hurley', 2103 | 'spence', 2104 | 'zamora', 2105 | 'yang', 2106 | 'mcneil', 2107 | 'suarez', 2108 | 'case', 2109 | 'petty', 2110 | 'gould', 2111 | 'mcfarland', 2112 | 'sampson', 2113 | 'carver', 2114 | 'bray', 2115 | 'rosario', 2116 | 'macdonald', 2117 | 'stout', 2118 | 'hester', 2119 | 'melendez', 2120 | 'dillon', 2121 | 'farley', 2122 | 'hopper', 2123 | 'galloway', 2124 | 'potts', 2125 | 'bernard', 2126 | 'joyner', 2127 | 'stein', 2128 | 'aguirre', 2129 | 'osborn', 2130 | 'mercer', 2131 | 'bender', 2132 | 'franco', 2133 | 'rowland', 2134 | 'sykes', 2135 | 'benjamin', 2136 | 'travis', 2137 | 'pickett', 2138 | 'crane', 2139 | 'sears', 2140 | 'mayo', 2141 | 'dunlap', 2142 | 'hayden', 2143 | 'wilder', 2144 | 'mckay', 2145 | 'coffey', 2146 | 'mccarty', 2147 | 'ewing', 2148 | 'cooley', 2149 | 'vaughan', 2150 | 'bonner', 2151 | 'cotton', 2152 | 'holder', 2153 | 'stark', 2154 | 'ferrell', 2155 | 'cantrell', 2156 | 'fulton', 2157 | 'lynn', 2158 | 'lott', 2159 | 'calderon', 2160 | 'rosa', 2161 | 'pollard', 2162 | 'hooper', 2163 | 'burch', 2164 | 'mullen', 2165 | 'fry', 2166 | 'riddle', 2167 | 'levy', 2168 | 'david', 2169 | 'duke', 2170 | 'odonnell', 2171 | 'guy', 2172 | 'michael', 2173 | 'britt', 2174 | 'frederick', 2175 | 'daugherty', 2176 | 'berger', 2177 | 'dillard', 2178 | 'alston', 2179 | 'jarvis', 2180 | 'frye', 2181 | 'riggs', 2182 | 'chaney', 2183 | 'odom', 2184 | 'duffy', 2185 | 'fitzpatrick', 2186 | 'valenzuela', 2187 | 'merrill', 2188 | 'mayer', 2189 | 'alford', 2190 | 'mcpherson', 2191 | 'acevedo', 2192 | 'donovan', 2193 | 'barrera', 2194 | 'albert', 2195 | 'cote', 2196 | 'reilly', 2197 | 'compton', 2198 | 'raymond', 2199 | 'mooney', 2200 | 'mcgowan', 2201 | 'craft', 2202 | 'cleveland', 2203 | 'clemons', 2204 | 'wynn', 2205 | 'nielsen', 2206 | 'baird', 2207 | 'stanton', 2208 | 'snider', 2209 | 'rosales', 2210 | 'bright', 2211 | 'witt', 2212 | 'stuart', 2213 | 'hays', 2214 | 'holden', 2215 | 'rutledge', 2216 | 'kinney', 2217 | 'clements', 2218 | 'castaneda', 2219 | 'slater', 2220 | 'hahn', 2221 | 'emerson', 2222 | 'conrad', 2223 | 'burks', 2224 | 'delaney', 2225 | 'pate', 2226 | 'lancaster', 2227 | 'sweet', 2228 | 'justice', 2229 | 'tyson', 2230 | 'sharpe', 2231 | 'whitfield', 2232 | 'talley', 2233 | 'macias', 2234 | 'irwin', 2235 | 'burris', 2236 | 'ratliff', 2237 | 'mccray', 2238 | 'madden', 2239 | 'kaufman', 2240 | 'beach', 2241 | 'goff', 2242 | 'cash', 2243 | 'bolton', 2244 | 'mcfadden', 2245 | 'levine', 2246 | 'good', 2247 | 'byers', 2248 | 'kirkland', 2249 | 'kidd', 2250 | 'workman', 2251 | 'carney', 2252 | 'dale', 2253 | 'mcleod', 2254 | 'holcomb', 2255 | 'england', 2256 | 'finch', 2257 | 'head', 2258 | 'burt', 2259 | 'hendrix', 2260 | 'sosa', 2261 | 'haney', 2262 | 'franks', 2263 | 'sargent', 2264 | 'nieves', 2265 | 'downs', 2266 | 'rasmussen', 2267 | 'bird', 2268 | 'hewitt', 2269 | 'lindsay', 2270 | 'le', 2271 | 'foreman', 2272 | 'valencia', 2273 | 'oneil', 2274 | 'delacruz', 2275 | 'vinson', 2276 | 'dejesus', 2277 | 'hyde', 2278 | 'forbes', 2279 | 'gilliam', 2280 | 'guthrie', 2281 | 'wooten', 2282 | 'huber', 2283 | 'barlow', 2284 | 'boyle', 2285 | 'mcmahon', 2286 | 'buckner', 2287 | 'rocha', 2288 | 'puckett', 2289 | 'langley', 2290 | 'knowles', 2291 | 'cooke', 2292 | 'velazquez', 2293 | 'whitley', 2294 | 'noel', 2295 | 'vang', 2296 | ] 2297 | --------------------------------------------------------------------------------