List-ops: Update to latest version
This commit is contained in:
@@ -1,13 +0,0 @@
|
||||
!.meta
|
||||
|
||||
# Protected or generated
|
||||
.git
|
||||
.vscode
|
||||
|
||||
# When using npm
|
||||
node_modules/*
|
||||
|
||||
# Configuration files
|
||||
.eslintrc.cjs
|
||||
babel.config.cjs
|
||||
jest.config.cjs
|
||||
@@ -1,38 +0,0 @@
|
||||
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',
|
||||
},
|
||||
],
|
||||
}
|
||||
Generated
+12418
-15275
File diff suppressed because one or more lines are too long
Generated
+536
-467
File diff suppressed because it is too large
Load Diff
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"arcanis.vscode-zipfs",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode"
|
||||
]
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"cSpell.words": ["exercism"],
|
||||
"search.exclude": {
|
||||
"**/.yarn": true,
|
||||
"**/.pnp.*": true
|
||||
}
|
||||
}
|
||||
Binary file not shown.
-874
File diff suppressed because one or more lines are too long
@@ -1 +1,3 @@
|
||||
yarnPath: .yarn/releases/yarn-3.6.0.cjs
|
||||
compressionLevel: mixed
|
||||
|
||||
enableGlobalCache: true
|
||||
|
||||
@@ -2,18 +2,23 @@
|
||||
|
||||
## Running the tests
|
||||
|
||||
Before trying to execute the tests, ensure the assignment folder is set-up correctly by following the installation steps, namely `corepack yarn install` and the Editor SDK setup.
|
||||
|
||||
Execute the tests with:
|
||||
|
||||
```bash
|
||||
$ yarn test
|
||||
$ corepack 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`.
|
||||
Once you get a test passing, you can enable the next one by changing `xit` to `it`.
|
||||
Additionally tests may be grouped using `xdescribe`.
|
||||
Enable the group by changing that to `describe`.
|
||||
Finally, some exercises may have optional tests `it.skip`.
|
||||
Remove `.skip` to execute the optional test.
|
||||
|
||||
## Submitting your solution
|
||||
|
||||
@@ -30,6 +35,7 @@ It's possible to submit an incomplete solution which allows you to:
|
||||
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)
|
||||
|
||||
|
||||
@@ -17,9 +17,11 @@ The precise number and names of the operations to be implemented will be track d
|
||||
- `filter` (_given a predicate and a list, return the list of all items for which `predicate(item)` is True_);
|
||||
- `length` (_given a list, return the total number of items within it_);
|
||||
- `map` (_given a function and a list, return the list of the results of applying `function(item)` on all items_);
|
||||
- `foldl` (_given a function, a list, and initial accumulator, fold (reduce) each item into the accumulator from the left using `function(accumulator, item)`_);
|
||||
- `foldr` (_given a function, a list, and an initial accumulator, fold (reduce) each item into the accumulator from the right using `function(item, accumulator)`_);
|
||||
- `reverse` (_given a list, return a list with all the original items, but in reversed order_);
|
||||
- `foldl` (_given a function, a list, and initial accumulator, fold (reduce) each item into the accumulator from the left_);
|
||||
- `foldr` (_given a function, a list, and an initial accumulator, fold (reduce) each item into the accumulator from the right_);
|
||||
- `reverse` (_given a list, return a list with all the original items, but in reversed order_).
|
||||
|
||||
Note, the ordering in which arguments are passed to the fold functions (`foldl`, `foldr`) is significant.
|
||||
|
||||
Using core language features to build and deconstruct arrays via destructuring, and using the array literal `[]` are allowed, but no functions from the `Array.prototype` should be used.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module.exports = {
|
||||
presets: ['@exercism/babel-preset-typescript'],
|
||||
presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.37' }]],
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// @ts-check
|
||||
|
||||
import tsEslint from 'typescript-eslint'
|
||||
import config from '@exercism/eslint-config-typescript'
|
||||
import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs'
|
||||
|
||||
export default [
|
||||
...tsEslint.config(...config, {
|
||||
files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'],
|
||||
extends: maintainersConfig,
|
||||
}),
|
||||
{
|
||||
ignores: [
|
||||
// # Protected or generated
|
||||
'.git/**/*',
|
||||
'.vscode/**/*',
|
||||
|
||||
//# When using npm
|
||||
'node_modules/**/*',
|
||||
|
||||
// # Configuration files
|
||||
'babel.config.cjs',
|
||||
'jest.config.cjs',
|
||||
],
|
||||
},
|
||||
]
|
||||
@@ -16,4 +16,7 @@ module.exports = {
|
||||
transform: {
|
||||
'^.+\\.[jt]sx?$': 'babel-jest',
|
||||
},
|
||||
moduleNameMapper: {
|
||||
'^(\\.\\/.+)\\.js$': '$1',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,44 +1,63 @@
|
||||
import { List } from './list-ops'
|
||||
import { describe, xdescribe, it, expect, xit } from '@jest/globals'
|
||||
import { List } from './list-ops.ts'
|
||||
import type { MatcherFunction } from 'expect'
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace jest {
|
||||
interface Matchers<R> {
|
||||
toHaveValues(...expected: unknown[]): CustomMatcherResult
|
||||
type JestUtils = {
|
||||
utils: {
|
||||
printReceived(object: unknown): string
|
||||
}
|
||||
}
|
||||
|
||||
const toHaveValues: MatcherFunction<unknown[]> = function (
|
||||
this: JestUtils,
|
||||
received: unknown,
|
||||
...expected: unknown[]
|
||||
) {
|
||||
if (typeof received !== 'object' || received === null) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () =>
|
||||
`Expected ${this.utils.printReceived(received)} to be a non-null object`,
|
||||
}
|
||||
}
|
||||
|
||||
if (!('forEach' in received) || typeof received.forEach !== 'function') {
|
||||
return {
|
||||
pass: false,
|
||||
message: (): string => `Implement .forEach(callback) on your list`,
|
||||
}
|
||||
}
|
||||
|
||||
const values: unknown[] = []
|
||||
received.forEach((item: unknown) => {
|
||||
values.push(item)
|
||||
})
|
||||
|
||||
const pass = JSON.stringify(values) === JSON.stringify(expected)
|
||||
return {
|
||||
pass,
|
||||
message: (): string =>
|
||||
pass
|
||||
? ''
|
||||
: `Expected to see the following values: ${JSON.stringify(
|
||||
expected,
|
||||
)}, actual: ${JSON.stringify(values)}`,
|
||||
}
|
||||
}
|
||||
|
||||
expect.extend({
|
||||
toHaveValues(
|
||||
received: ReturnType<typeof List.create>,
|
||||
...expected: unknown[]
|
||||
): jest.CustomMatcherResult {
|
||||
if (!('forEach' in received)) {
|
||||
return {
|
||||
pass: false,
|
||||
message: (): string => `Implement .forEach(callback) on your list`,
|
||||
}
|
||||
}
|
||||
|
||||
const values: unknown[] = []
|
||||
received.forEach((item) => {
|
||||
values.push(item)
|
||||
})
|
||||
|
||||
const pass = JSON.stringify(values) === JSON.stringify(expected)
|
||||
return {
|
||||
pass,
|
||||
message: (): string =>
|
||||
pass
|
||||
? ''
|
||||
: `Expected to see the following values: ${JSON.stringify(
|
||||
expected
|
||||
)}, actual: ${JSON.stringify(values)}`,
|
||||
}
|
||||
},
|
||||
toHaveValues,
|
||||
})
|
||||
|
||||
declare module 'expect' {
|
||||
interface AsymmetricMatchers {
|
||||
toHaveValues(...expected: unknown[]): void
|
||||
}
|
||||
interface Matchers<R> {
|
||||
toHaveValues(...expected: unknown[]): R
|
||||
}
|
||||
}
|
||||
|
||||
describe('append entries to a list and return the new list', () => {
|
||||
it('empty lists', () => {
|
||||
const list1 = List.create()
|
||||
@@ -85,12 +104,12 @@ describe('concat lists and lists of lists into new list', () => {
|
||||
describe('filter list returning only values that satisfy the filter function', () => {
|
||||
it('empty list', () => {
|
||||
const list1 = List.create()
|
||||
expect(list1.filter<number>((el) => el % 2 === 1)).toHaveValues()
|
||||
expect(list1.filter<number>(el => el % 2 === 1)).toHaveValues()
|
||||
})
|
||||
|
||||
it('non empty list', () => {
|
||||
const list1 = List.create(1, 2, 3, 5)
|
||||
expect(list1.filter<number>((el) => el % 2 === 1)).toHaveValues(1, 3, 5)
|
||||
expect(list1.filter<number>(el => el % 2 === 1)).toHaveValues(1, 3, 5)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -109,61 +128,61 @@ describe('returns the length of a list', () => {
|
||||
describe('returns a list of elements whose values equal the list value transformed by the mapping function', () => {
|
||||
it('empty list', () => {
|
||||
const list1 = List.create()
|
||||
expect(list1.map<number>((el) => ++el)).toHaveValues()
|
||||
expect(list1.map<number>(el => ++el)).toHaveValues()
|
||||
})
|
||||
|
||||
it('non-empty list', () => {
|
||||
const list1 = List.create(1, 3, 5, 7)
|
||||
expect(list1.map<number>((el) => ++el)).toHaveValues(2, 4, 6, 8)
|
||||
expect(list1.map<number>(el => ++el)).toHaveValues(2, 4, 6, 8)
|
||||
})
|
||||
})
|
||||
|
||||
describe('folds (reduces) the given list from the left with a function', () => {
|
||||
xit('empty list', () => {
|
||||
it('empty list', () => {
|
||||
const list1 = List.create()
|
||||
expect(list1.foldl<number, number>((acc, el) => el * acc, 2)).toEqual(2)
|
||||
})
|
||||
|
||||
xit('direction independent function applied to non-empty list', () => {
|
||||
it('direction independent function applied to non-empty list', () => {
|
||||
const list1 = List.create(1, 2, 3, 4)
|
||||
expect(list1.foldl<number, number>((acc, el) => acc + el, 5)).toEqual(15)
|
||||
})
|
||||
|
||||
xit('direction dependent function applied to non-empty list', () => {
|
||||
it('direction dependent function applied to non-empty list', () => {
|
||||
const list1 = List.create(1, 2, 3, 4)
|
||||
expect(list1.foldl<number, number>((acc, el) => el / acc, 24)).toEqual(64)
|
||||
})
|
||||
})
|
||||
|
||||
describe('folds (reduces) the given list from the right with a function', () => {
|
||||
xit('empty list', () => {
|
||||
it('empty list', () => {
|
||||
const list1 = List.create()
|
||||
expect(list1.foldr<number, number>((acc, el) => el * acc, 2)).toEqual(2)
|
||||
})
|
||||
|
||||
xit('direction independent function applied to non-empty list', () => {
|
||||
it('direction independent function applied to non-empty list', () => {
|
||||
const list1 = List.create(1, 2, 3, 4)
|
||||
expect(list1.foldr<number, number>((acc, el) => acc + el, 5)).toEqual(15)
|
||||
})
|
||||
|
||||
xit('direction dependent function applied to non-empty list', () => {
|
||||
it('direction dependent function applied to non-empty list', () => {
|
||||
const list1 = List.create(1, 2, 3, 4)
|
||||
expect(list1.foldr<number, number>((acc, el) => el / acc, 24)).toEqual(9)
|
||||
})
|
||||
})
|
||||
|
||||
describe('reverse the elements of a list', () => {
|
||||
xit('empty list', () => {
|
||||
it('empty list', () => {
|
||||
const list1 = List.create()
|
||||
expect(list1.reverse()).toHaveValues()
|
||||
})
|
||||
|
||||
xit('non-empty list', () => {
|
||||
it('non-empty list', () => {
|
||||
const list1 = List.create(1, 3, 5, 7)
|
||||
expect(list1.reverse()).toHaveValues(7, 5, 3, 1)
|
||||
})
|
||||
|
||||
xit('list of lists is not flattened', () => {
|
||||
it('list of lists is not flattened', () => {
|
||||
const list1 = List.create([1, 2], [3], [], [4, 5, 6])
|
||||
expect(list1.reverse()).toHaveValues([4, 5, 6], [], [3], [1, 2])
|
||||
})
|
||||
|
||||
@@ -1,22 +1,94 @@
|
||||
type ListValue = number
|
||||
type ListValues = ListValue[]
|
||||
type Scalar<T> = T extends List<infer U> ? U : T
|
||||
|
||||
type ConsturctorValue = ListValue | List
|
||||
type ConsturctorValues = ConsturctorValue[]
|
||||
export class List<T> {
|
||||
public values: T[]
|
||||
|
||||
constructor(values: T[]) {
|
||||
this.values = values
|
||||
}
|
||||
|
||||
export class List {
|
||||
public values: ListValue[]
|
||||
public constructor(...values: ListValues) { this.values = values }
|
||||
public static create<T = number>(...values: List<T>[]): List<T>
|
||||
public static create<T = number>(...values: T[]): List<T>
|
||||
public static create<T = number>(...values: T[]): List<T> {
|
||||
// Do *not* construct any array literal ([]) in your solution.
|
||||
// Do *not* construct any arrays through new Array in your solution.
|
||||
// DO *not* use any of the Array.prototype methods in your solution.
|
||||
|
||||
public static create = (...values: ConsturctorValues): List => {
|
||||
if (values[0] instanceof List) {
|
||||
return values[0].append(List.create(...values.slice(1)))
|
||||
} else if (typeof values[0] === 'number') {
|
||||
return new List(values[0]).append(List.create(...values.slice(1)))
|
||||
} else {
|
||||
return new List()
|
||||
// You may use the destructuring and spreading (...) syntax from Iterable.
|
||||
let _values: T[] = []
|
||||
for (let value of values) {
|
||||
if (value instanceof List) {
|
||||
_values = [..._values, ...value.values]
|
||||
} else {
|
||||
_values = [..._values, value]
|
||||
}
|
||||
}
|
||||
return new List(_values)
|
||||
}
|
||||
|
||||
append(list: List<T>): List<T> {
|
||||
return new List([...this.values, ...list.values])
|
||||
}
|
||||
|
||||
forEach(cb: (value: T) => void) {
|
||||
for (let value of this.values) cb(value)
|
||||
}
|
||||
|
||||
concat(...lists: List<T>[]): List<T> {
|
||||
let values: T[] = [...this.values]
|
||||
for (let list of lists) {
|
||||
values = [...values, ...list.values]
|
||||
}
|
||||
return new List(values)
|
||||
}
|
||||
|
||||
filter<T1 extends T>(fn: (el: T1) => boolean): List<T>
|
||||
filter(fn: (el: T) => boolean): List<T>
|
||||
filter(fn: (el: T) => boolean): List<T> {
|
||||
let values: T[] = []
|
||||
for (let value of this.values) {
|
||||
if (!fn(value)) continue
|
||||
values = [...values, value]
|
||||
}
|
||||
return new List(values)
|
||||
}
|
||||
|
||||
map<T1 extends T>(fn: (el: T1) => T1): List<T>
|
||||
map(fn: (el: T) => T): List<T>
|
||||
map<T1 extends T>(fn: (el: T) => T1): List<T> {
|
||||
let values: T[] = []
|
||||
for (let value of this.values) {
|
||||
values = [...values, fn(value)]
|
||||
}
|
||||
return new List(values)
|
||||
}
|
||||
|
||||
foldl<T1 extends T, T2>(fn: (acc: T2, value: T1) => T2, seed: T2): List<T>
|
||||
foldl<T2>(fn: (acc: T2, el: T) => T2, seed: T2): T2
|
||||
foldl<T2>(fn: (acc: T2, el: T) => T2, seed: T2): T2 {
|
||||
let result = seed
|
||||
for (let value of this.values) {
|
||||
result = fn(result, value)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
foldr<T1 extends T, T2>(fn: (acc: T2, value: T1) => T2, seed: T2): List<T>
|
||||
foldr<T2>(fn: (acc: T2, el: T) => T2, seed: T2): T2
|
||||
foldr<T2>(fn: (acc: T2, el: T) => T2, seed: T2): T2 {
|
||||
let result = seed
|
||||
for (let i = this.length() - 1; i >= 0; i -= 1) {
|
||||
result = fn(result, this.values[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
reverse(): List<T> {
|
||||
let values: T[] = []
|
||||
for (let i = this.length() - 1; i >= 0; i -= 1) {
|
||||
values = [...values, this.values[i]]
|
||||
}
|
||||
return new List(values)
|
||||
}
|
||||
|
||||
length(): number {
|
||||
@@ -24,30 +96,4 @@ export class List {
|
||||
for (let _ of this.values) count++
|
||||
return count
|
||||
}
|
||||
|
||||
append(list: List): List { return new List(...[...this.values, ...list.values]); }
|
||||
|
||||
forEach(callback: (value: unknown) => void): void {
|
||||
for (const value of this.values) {
|
||||
callback(value)
|
||||
}
|
||||
}
|
||||
|
||||
concat(...lists: List[]): List {
|
||||
if (!lists.length) return this
|
||||
return this.append(lists[0]).concat(...lists.slice(1))
|
||||
}
|
||||
|
||||
filter<T extends ListValue>(predicate: (value: ListValue) => boolean): List {
|
||||
if (!this.length()) return this
|
||||
if (predicate(this.values[0])) {
|
||||
return List.create(this.values[0]).concat(List.create(...this.values.slice(1)).filter<T>(predicate));
|
||||
}
|
||||
return List.create(...this.values.slice(1)).filter<T>(predicate)
|
||||
}
|
||||
|
||||
map<T extends ListValue>(predicate: (value: ListValue) => ListValue): List {
|
||||
if (!this.length()) return this
|
||||
return List.create(predicate(this.values[0])).concat(List.create(...this.values.slice(1)).map<T>(predicate));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,26 +12,24 @@
|
||||
"node": "^18.16.0 || >=20.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@exercism/babel-preset-typescript": "^0.4.0",
|
||||
"@exercism/eslint-config-typescript": "^0.5.0",
|
||||
"@types/jest": "^29.5.3",
|
||||
"@types/node": "~18.16.16",
|
||||
"babel-jest": "^29.5.0",
|
||||
"core-js": "~3.30.2",
|
||||
"eslint": "^8.42.0",
|
||||
"jest": "^29.5.0",
|
||||
"typescript": "~5.0.4"
|
||||
"@exercism/babel-preset-typescript": "^0.5.0",
|
||||
"@exercism/eslint-config-typescript": "^0.7.0",
|
||||
"@jest/globals": "^29.7.0",
|
||||
"@types/node": "~22.0.0",
|
||||
"babel-jest": "^29.7.0",
|
||||
"core-js": "~3.37.1",
|
||||
"eslint": "^9.8.0",
|
||||
"expect": "^29.7.0",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.3.3",
|
||||
"typescript": "~5.5.4",
|
||||
"typescript-eslint": "^7.17.0"
|
||||
},
|
||||
"scripts": {
|
||||
"watch": "jest --no-cache --watch",
|
||||
"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"
|
||||
"test": "corepack yarn lint:types && jest --no-cache",
|
||||
"lint": "corepack yarn lint:types && corepack yarn lint:ci",
|
||||
"lint:types": "corepack yarn tsc --noEmit -p .",
|
||||
"lint:ci": "corepack yarn eslint . --ext .tsx,.ts"
|
||||
},
|
||||
"packageManager": "yarn@3.6.0",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.22.9",
|
||||
"@types/mocha": "^10.0.1"
|
||||
}
|
||||
"packageManager": "yarn@4.3.1"
|
||||
}
|
||||
|
||||
@@ -3,20 +3,24 @@
|
||||
"compilerOptions": {
|
||||
// Allows you to use the newest syntax, and have access to console.log
|
||||
// https://www.typescriptlang.org/tsconfig#lib
|
||||
"lib": ["ESNEXT", "dom"],
|
||||
"lib": ["ES2020", "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",
|
||||
"module": "Node16",
|
||||
// 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",
|
||||
"target": "ES2020", // ESLint doesn't support this yet: "es2022",
|
||||
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
|
||||
// Because jest-resolve isn't like node resolve, the absolute path must be .ts
|
||||
"allowImportingTsExtensions": true,
|
||||
"noEmit": true,
|
||||
|
||||
// Because we'll be using babel: ensure that Babel can safely transpile
|
||||
// files in the TypeScript project.
|
||||
//
|
||||
|
||||
+3731
-3612
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user