List-ops: Update to latest version

This commit is contained in:
2025-04-23 08:52:01 +02:00
parent fba3e4794e
commit 4676dc4da3
19 changed files with 16921 additions and 20395 deletions
-13
View File
@@ -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
-38
View File
@@ -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',
},
],
}
+12418 -15275
View File
File diff suppressed because one or more lines are too long
+536 -467
View File
File diff suppressed because it is too large Load Diff
+7
View File
@@ -0,0 +1,7 @@
{
"recommendations": [
"arcanis.vscode-zipfs",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}
+7
View File
@@ -0,0 +1,7 @@
{
"cSpell.words": ["exercism"],
"search.exclude": {
"**/.yarn": true,
"**/.pnp.*": true
}
}
Binary file not shown.
File diff suppressed because one or more lines are too long
+3 -1
View File
@@ -1 +1,3 @@
yarnPath: .yarn/releases/yarn-3.6.0.cjs
compressionLevel: mixed
enableGlobalCache: true
+9 -3
View File
@@ -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)
+5 -3
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
module.exports = {
presets: ['@exercism/babel-preset-typescript'],
presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.37' }]],
plugins: [],
}
+26
View File
@@ -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',
],
},
]
+3
View File
@@ -16,4 +16,7 @@ module.exports = {
transform: {
'^.+\\.[jt]sx?$': 'babel-jest',
},
moduleNameMapper: {
'^(\\.\\/.+)\\.js$': '$1',
},
}
+65 -46
View File
@@ -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])
})
+86 -40
View File
@@ -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));
}
}
+17 -19
View File
@@ -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"
}
+7 -3
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff