Browse Source

12: Luhn.

Frédéric G. MARAND 1 month ago
parent
commit
14fabc805b

+ 13 - 0
luhn/.eslintignore

@@ -0,0 +1,13 @@
+!.meta
+
+# Protected or generated
+.git
+.vscode
+
+# When using npm
+node_modules/*
+
+# Configuration files
+.eslintrc.cjs
+babel.config.cjs
+jest.config.cjs

+ 38 - 0
luhn/.eslintrc.cjs

@@ -0,0 +1,38 @@
+module.exports = {
+  root: true,
+  parserOptions: {
+    tsconfigRootDir: __dirname,
+    project: ['./tsconfig.json'],
+  },
+  overrides: [
+    // Student provided files
+    {
+      files: ['*.ts'],
+      excludedFiles: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'],
+      extends: '@exercism/eslint-config-typescript',
+    },
+    // Exercism given tests
+    {
+      files: ['*.test.ts'],
+      excludedFiles: ['custom.test.ts'],
+      env: {
+        jest: true,
+      },
+      extends: '@exercism/eslint-config-typescript/maintainers',
+    },
+    // Student provided tests
+    {
+      files: ['custom.test.ts'],
+      env: {
+        jest: true,
+      },
+      extends: '@exercism/eslint-config-typescript',
+    },
+    // Exercism provided files
+    {
+      files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'],
+      excludedFiles: ['custom.test.ts'],
+      extends: '@exercism/eslint-config-typescript/maintainers',
+    },
+  ],
+}

+ 24 - 0
luhn/.exercism/config.json

@@ -0,0 +1,24 @@
+{
+  "authors": [
+    "CRivasGomez"
+  ],
+  "contributors": [
+    "masters3d",
+    "SleeplessByte",
+    "angelikatyborska"
+  ],
+  "files": {
+    "solution": [
+      "luhn.ts"
+    ],
+    "test": [
+      "luhn.test.ts"
+    ],
+    "example": [
+      ".meta/proof.ci.ts"
+    ]
+  },
+  "blurb": "Given a number determine whether or not it is valid per the Luhn formula.",
+  "source": "The Luhn Algorithm on Wikipedia",
+  "source_url": "https://en.wikipedia.org/wiki/Luhn_algorithm"
+}

+ 1 - 0
luhn/.exercism/metadata.json

@@ -0,0 +1 @@
+{"track":"typescript","exercise":"luhn","id":"8a3821f2932e4719b65dd012fa39788f","url":"https://exercism.org/tracks/typescript/exercises/luhn","handle":"Fairgame","is_requester":true,"auto_approve":false}

File diff suppressed because it is too large
+ 3 - 0
luhn/.yarn/releases/yarn-3.6.4.cjs


+ 45 - 0
luhn/HELP.md

@@ -0,0 +1,45 @@
+# Help
+
+## Running the tests
+
+Execute the tests with:
+
+```bash
+$ yarn test
+```
+
+## Skipped tests
+
+In the test suites all tests but the first have been skipped.
+
+Once you get a test passing, you can enable the next one by changing `xit` to
+`it`.
+
+## Submitting your solution
+
+You can submit your solution using the `exercism submit luhn.ts` command.
+This command will upload your solution to the Exercism website and print the solution page's URL.
+
+It's possible to submit an incomplete solution which allows you to:
+
+- See how others have completed the exercise
+- Request help from a mentor
+
+## Need to get help?
+
+If you'd like help solving the exercise, check the following pages:
+
+- The [TypeScript track's documentation](https://exercism.org/docs/tracks/typescript)
+- The [TypeScript track's programming category on the forum](https://forum.exercism.org/c/programming/typescript)
+- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5)
+- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
+
+Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
+
+To get help if you're having trouble, you can use one of the following resources:
+
+- [TypeScript QuickStart](https://www.typescriptlang.org/docs/handbook/release-notes/overview.html)
+- [ECMAScript 2015 Language Specification](https://www.ecma-international.org/wp-content/uploads/ECMA-262_6th_edition_june_2015.pdf) (pdf)
+- [Mozilla JavaScript Reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference)
+- [/r/typescript](https://www.reddit.com/r/typescript) is the TypeScript subreddit.
+- [StackOverflow](https://stackoverflow.com/questions/tagged/typescript) can be used to search for your problem and see if it has been answered already. You can also ask and answer questions.

+ 85 - 0
luhn/README.md

@@ -0,0 +1,85 @@
+# Luhn
+
+Welcome to Luhn on Exercism's TypeScript Track.
+If you need help running the tests or submitting your code, check out `HELP.md`.
+
+## Instructions
+
+Given a number determine whether or not it is valid per the Luhn formula.
+
+The [Luhn algorithm][luhn] is a simple checksum formula used to validate a variety of identification numbers, such as credit card numbers and Canadian Social Insurance Numbers.
+
+The task is to check if a given string is valid.
+
+## Validating a Number
+
+Strings of length 1 or less are not valid.
+Spaces are allowed in the input, but they should be stripped before checking.
+All other non-digit characters are disallowed.
+
+### Example 1: valid credit card number
+
+```text
+4539 3195 0343 6467
+```
+
+The first step of the Luhn algorithm is to double every second digit, starting from the right.
+We will be doubling
+
+```text
+4_3_ 3_9_ 0_4_ 6_6_
+```
+
+If doubling the number results in a number greater than 9 then subtract 9 from the product.
+The results of our doubling:
+
+```text
+8569 6195 0383 3437
+```
+
+Then sum all of the digits:
+
+```text
+8+5+6+9+6+1+9+5+0+3+8+3+3+4+3+7 = 80
+```
+
+If the sum is evenly divisible by 10, then the number is valid.
+This number is valid!
+
+### Example 2: invalid credit card number
+
+```text
+8273 1232 7352 0569
+```
+
+Double the second digits, starting from the right
+
+```text
+7253 2262 5312 0539
+```
+
+Sum the digits
+
+```text
+7+2+5+3+2+2+6+2+5+3+1+2+0+5+3+9 = 57
+```
+
+57 is not evenly divisible by 10, so this number is not valid.
+
+[luhn]: https://en.wikipedia.org/wiki/Luhn_algorithm
+
+## Source
+
+### Created by
+
+- @CRivasGomez
+
+### Contributed to by
+
+- @masters3d
+- @SleeplessByte
+- @angelikatyborska
+
+### Based on
+
+The Luhn Algorithm on Wikipedia - https://en.wikipedia.org/wiki/Luhn_algorithm

+ 4 - 0
luhn/babel.config.cjs

@@ -0,0 +1,4 @@
+module.exports = {
+  presets: ['@exercism/babel-preset-typescript'],
+  plugins: [],
+}

+ 19 - 0
luhn/jest.config.cjs

@@ -0,0 +1,19 @@
+module.exports = {
+  verbose: true,
+  projects: ['<rootDir>'],
+  testMatch: [
+    '**/__tests__/**/*.[jt]s?(x)',
+    '**/test/**/*.[jt]s?(x)',
+    '**/?(*.)+(spec|test).[jt]s?(x)',
+  ],
+  testPathIgnorePatterns: [
+    '/(?:production_)?node_modules/',
+    '.d.ts$',
+    '<rootDir>/test/fixtures',
+    '<rootDir>/test/helpers',
+    '__mocks__',
+  ],
+  transform: {
+    '^.+\\.[jt]sx?$': 'babel-jest',
+  },
+}

+ 92 - 0
luhn/luhn.test.ts

@@ -0,0 +1,92 @@
+import { valid } from './luhn'
+
+xit = it
+describe('Luhn', () => {
+  it('single digit strings can not be valid', () => {
+    expect(valid('1')).toBeFalsy()
+  })
+
+  xit('a single zero is invalid', () => {
+    expect(valid('0')).toBeFalsy()
+  })
+
+  xit('a simple valid SIN that remains valid if reversed', () => {
+    expect(valid('059')).toBeTruthy()
+  })
+
+  xit('a simple valid SIN that becomes invalid if reversed', () => {
+    expect(valid('59')).toBeTruthy()
+  })
+
+  xit('a valid Canadian SIN', () => {
+    expect(valid('055 444 285')).toBeTruthy()
+  })
+
+  xit('invalid Canadian SIN', () => {
+    expect(valid('055 444 286')).toBeFalsy()
+  })
+
+  xit('invalid credit card', () => {
+    expect(valid('8273 1232 7352 0569')).toBeFalsy()
+  })
+
+  xit('invalid long number with an even remainder', () => {
+    expect(valid('1 2345 6789 1234 5678 9012')).toBeFalsy()
+  })
+
+  xit('invalid long number with a remainder divisible by 5', () => {
+    expect(valid('1 2345 6789 1234 5678 9013')).toBeFalsy()
+  })
+
+  xit('valid number with an even number of digits', () => {
+    expect(valid('095 245 88')).toBeTruthy()
+  })
+
+  xit('valid number with an odd number of spaces', () => {
+    expect(valid('234 567 891 234')).toBeTruthy()
+  })
+
+  xit('valid strings with a non-digit added at the end become invalid', () => {
+    expect(valid('059a')).toBeFalsy()
+  })
+
+  xit('valid strings with punctuation included become invalid', () => {
+    expect(valid('055-444-285')).toBeFalsy()
+  })
+
+  xit('valid strings with symbols included become invalid', () => {
+    expect(valid('055# 444$ 285')).toBeFalsy()
+  })
+
+  xit('single zero with space is invalid', () => {
+    expect(valid(' 0')).toBeFalsy()
+  })
+
+  xit('more than a single zero is valid', () => {
+    expect(valid('0000 0')).toBeTruthy()
+  })
+
+  xit('input digit 9 is correctly converted to output digit 9', () => {
+    expect(valid('091')).toBeTruthy()
+  })
+
+  xit('very long input is valid', () => {
+    expect(valid('9999999999 9999999999 9999999999 9999999999')).toBeTruthy()
+  })
+
+  xit('valid luhn with an odd number of digits and non zero first digit', () => {
+    expect(valid('109')).toBeTruthy()
+  })
+
+  xit("using ascii value for non-doubled non-digit isn't allowed", () => {
+    expect(valid('055b 444 285')).toBeFalsy()
+  })
+
+  xit("using ascii value for doubled non-digit isn't allowed", () => {
+    expect(valid(':9')).toBeFalsy()
+  })
+
+  xit("non-numeric, non-space char in the middle with a sum that's divisible by 10 isn't allowed", () => {
+    expect(valid('59%59')).toBeFalsy()
+  })
+})

+ 25 - 0
luhn/luhn.ts

@@ -0,0 +1,25 @@
+export function valid(digitString: string): boolean {
+    digitString = digitString.replace(/ /g, '');
+    if (digitString.length < 2) {
+        return false;
+    }
+    if (digitString.match(/\D/)) {
+        return false;
+    }
+    const len = digitString.length;
+    const zero = 0x30; // ASCII '0'
+    let sum = 0;
+    for (let i = len - 1; i >= 0; i--) {
+        // We know digit is in [0,9] because we filtered against \D above.
+        let digit = (digitString.codePointAt(i) ?? zero) - zero
+        if ((len - i) % 2 == 0) {
+            digit *= 2;
+            if (digit > 9) {
+                digit -= 9;
+            }
+        }
+        sum += digit;
+        sum %= 10;
+    }
+    return sum % 10 === 0;
+}

+ 32 - 0
luhn/package.json

@@ -0,0 +1,32 @@
+{
+  "name": "@exercism/typescript-luhn",
+  "version": "1.0.0",
+  "description": "Exercism exercises in Typescript.",
+  "private": true,
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/exercism/typescript"
+  },
+  "type": "module",
+  "engines": {
+    "node": "^18.16.0 || >=20.0.0"
+  },
+  "devDependencies": {
+    "@exercism/babel-preset-typescript": "^0.4.0",
+    "@exercism/eslint-config-typescript": "^0.6.0",
+    "@types/jest": "^29.5.12",
+    "@types/node": "~18.16.16",
+    "babel-jest": "^29.5.0",
+    "core-js": "~3.30.2",
+    "eslint": "^8.42.0",
+    "jest": "^29.7.0",
+    "typescript": "^5.4.3"
+  },
+  "scripts": {
+    "test": "yarn lint:types && jest --no-cache",
+    "lint": "yarn lint:types && yarn lint:ci",
+    "lint:types": "yarn tsc --noEmit -p .",
+    "lint:ci": "eslint . --ext .tsx,.ts"
+  },
+  "packageManager": "yarn@3.6.4"
+}

+ 28 - 0
luhn/tsconfig.json

@@ -0,0 +1,28 @@
+{
+  "display": "Configuration for Exercism TypeScript Exercises",
+  "compilerOptions": {
+    // Allows you to use the newest syntax, and have access to console.log
+    // https://www.typescriptlang.org/tsconfig#lib
+    "lib": ["ESNEXT", "dom"],
+    // Make sure typescript is configured to output ESM
+    // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm
+    "module": "ES2020",
+    // Since this project is using babel, TypeScript may target something very
+    // high, and babel will make sure it runs on your local Node version.
+    // https://babeljs.io/docs/en/
+    "target": "ESNext", // ESLint doesn't support this yet: "es2022",
+
+    "strict": true,
+    "esModuleInterop": true,
+    "skipLibCheck": true,
+    "forceConsistentCasingInFileNames": true,
+
+    // Because we'll be using babel: ensure that Babel can safely transpile
+    // files in the TypeScript project.
+    //
+    // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats
+    "isolatedModules": true
+  },
+  "include": ["*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx"],
+  "exclude": ["node_modules"]
+}

+ 14 - 3
package.json

@@ -1,14 +1,25 @@
 {
+  "version": "1.0.0",
+  "description": "Exercism exercises in Typescript.",
+  "private": true,
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/exercism/typescript"
+  },
+  "type": "module",
+  "engines": {
+    "node": "^18.16.0 || >=20.0.0"
+  },
   "devDependencies": {
     "@exercism/babel-preset-typescript": "^0.4.0",
     "@exercism/eslint-config-typescript": "^0.6.0",
     "@types/jest": "^29.5.12",
-    "@types/node": "~18.16.16",
+    "@types/node": "^20.12",
     "babel-jest": "^29.5.0",
-    "core-js": "~3.30.2",
+    "core-js": "^3.36",
     "eslint": "^8.42.0",
     "jest": "^29.7.0",
-    "typescript": "^5.4.2"
+    "typescript": "^5.4.3"
   },
   "scripts": {
     "test": "yarn lint:types && jest --no-cache",

+ 15 - 8
yarn.lock

@@ -1453,10 +1453,12 @@
   dependencies:
     undici-types "~5.26.4"
 
-"@types/node@~18.16.16":
-  version "18.16.20"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.20.tgz#b27be1ceb267bfb47d8bac024ff6379998f62207"
-  integrity sha512-nL54VfDjThdP2UXJXZao5wp76CDiDw4zSRO8d4Tk7UgDqNKGKVEQB0/t3ti63NS+YNNkIQDvwEAF04BO+WYu7Q==
+"@types/node@^20.12":
+  version "20.12.2"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.2.tgz#9facdd11102f38b21b4ebedd9d7999663343d72e"
+  integrity sha512-zQ0NYO87hyN6Xrclcqp7f8ZbXNbRfoGWNcMvHTPQp9UUrwI0mI7XBz+cu7/W6/VClYo2g63B0cjull/srU7LgQ==
+  dependencies:
+    undici-types "~5.26.4"
 
 "@types/semver@^7.3.12":
   version "7.5.8"
@@ -2011,6 +2013,11 @@ core-js@^3.30.2:
   resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.36.0.tgz#e752fa0b0b462a0787d56e9d73f80b0f7c0dde68"
   integrity sha512-mt7+TUBbTFg5+GngsAxeKBTl5/VS0guFeJacYge9OmHb+m058UwwIm41SE9T4Den7ClatV57B6TYTuJ0CX1MAw==
 
+core-js@^3.36:
+  version "3.36.1"
+  resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.36.1.tgz#c97a7160ebd00b2de19e62f4bbd3406ab720e578"
+  integrity sha512-BTvUrwxVBezj5SZ3f10ImnX2oRByMxql3EimVqMysepbC9EeMUOpLwdy6Eoili2x6E4kf+ZUB5k/+Jv55alPfA==
+
 core-js@~3.30.2:
   version "3.30.2"
   resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.30.2.tgz#6528abfda65e5ad728143ea23f7a14f0dcf503fc"
@@ -4332,10 +4339,10 @@ typed-array-length@^1.0.5:
     is-typed-array "^1.1.13"
     possible-typed-array-names "^1.0.0"
 
-typescript@^5.4.2:
-  version "5.4.2"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.2.tgz#0ae9cebcfae970718474fe0da2c090cad6577372"
-  integrity sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==
+typescript@^5.4.3:
+  version "5.4.3"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.3.tgz#5c6fedd4c87bee01cd7a528a30145521f8e0feff"
+  integrity sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==
 
 unbox-primitive@^1.0.2:
   version "1.0.2"

Some files were not shown because too many files changed in this diff