Skip to content

Commit

Permalink
Fix list accumulate (#1463)
Browse files Browse the repository at this point in the history
* Fix accumulate and add tests for it

* Update gitignore to ignore yarn errors
  • Loading branch information
leeyi45 authored Aug 11, 2023
1 parent 7161478 commit 8d3b2e3
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 11 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ coverage/
.vscode/
tsconfig.tsbuildinfo
test-report.html

yarn-error.log
54 changes: 54 additions & 0 deletions src/stdlib/__tests__/__snapshots__/list.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,32 @@ Object {
}
`;

exports[`accumulate works from right to left: expectResult 1`] = `
Object {
"alertResult": Array [],
"code": "accumulate((curr, acc) => curr + acc, '1', list('4','3','2'));",
"displayResult": Array [],
"numErrors": 0,
"parsedErrors": "",
"result": "4321",
"resultStatus": "finished",
"visualiseListResult": Array [],
}
`;

exports[`accumulate works properly: expectResult 1`] = `
Object {
"alertResult": Array [],
"code": "accumulate((curr, acc) => curr + acc, 0, list(2, 3, 4, 1));",
"displayResult": Array [],
"numErrors": 0,
"parsedErrors": "",
"result": 10,
"resultStatus": "finished",
"visualiseListResult": Array [],
}
`;

exports[`accumulate: expectResult 1`] = `
Object {
"alertResult": Array [],
Expand Down Expand Up @@ -789,6 +815,34 @@ Object {
}
`;
exports[`length works with empty lists: expectResult 1`] = `
Object {
"alertResult": Array [],
"code": "const xs = list();
length(xs);",
"displayResult": Array [],
"numErrors": 0,
"parsedErrors": "",
"result": 0,
"resultStatus": "finished",
"visualiseListResult": Array [],
}
`;
exports[`length works with populated lists: expectResult 1`] = `
Object {
"alertResult": Array [],
"code": "const xs = list(1,2,3,4);
length(xs);",
"displayResult": Array [],
"numErrors": 0,
"parsedErrors": "",
"result": 4,
"resultStatus": "finished",
"visualiseListResult": Array [],
}
`;
exports[`list creates list: expectResult 1`] = `
Object {
"alertResult": Array [],
Expand Down
41 changes: 41 additions & 0 deletions src/stdlib/__tests__/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,47 @@ test('list_to_string', () => {
).toMatchInlineSnapshot(`"[1,[2,[3,null]]]"`)
})

describe('accumulate', () => {
test('works properly', () => {
return expectResult(
stripIndent`
accumulate((curr, acc) => curr + acc, 0, list(2, 3, 4, 1));
`,
{ chapter: Chapter.SOURCE_2, native: true }
).toMatchInlineSnapshot(`10`)
})

it('works from right to left', () => {
return expectResult(
stripIndent`
accumulate((curr, acc) => curr + acc, '1', list('4','3','2'));`,
{ chapter: Chapter.SOURCE_2, native: true }
).toMatchInlineSnapshot('"4321"')
})
})

describe('length', () => {
test('works with populated lists', () => {
return expectResult(
stripIndent`
const xs = list(1,2,3,4);
length(xs);
`,
{ chapter: Chapter.SOURCE_2, native: true }
).toMatchInlineSnapshot('4')
})

test('works with empty lists', () => {
return expectResult(
stripIndent`
const xs = list();
length(xs);
`,
{ chapter: Chapter.SOURCE_2, native: true }
).toMatchInlineSnapshot('0')
})
})

// assoc removed from Source
test.skip('assoc', () => {
return expectResult(
Expand Down
26 changes: 16 additions & 10 deletions src/stdlib/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function pair<H, T>(x: H, xs: T): Pair<H, T> {

// is_pair returns true iff arg is a two-element array
// LOW-LEVEL FUNCTION, NOT SOURCE
export function is_pair(x: any) {
export function is_pair(x: any): x is Pair<any, any> {
return array_test(x) && x.length === 2
}

Expand Down Expand Up @@ -55,7 +55,7 @@ export function tail(xs: any) {

// is_null returns true if arg is exactly null
// LOW-LEVEL FUNCTION, NOT SOURCE
export function is_null(xs: List) {
export function is_null(xs: List): xs is null {
return xs === null
}

Expand All @@ -71,7 +71,7 @@ export function list(...elements: any[]): List {

// recurses down the list and checks that it ends with the empty list null
// LOW-LEVEL FUNCTION, NOT SOURCE
export function is_list(xs: List) {
export function is_list(xs: List): xs is List {
while (is_pair(xs)) {
xs = tail(xs)
}
Expand Down Expand Up @@ -129,14 +129,20 @@ export function set_tail(xs: any, x: any) {
}
}

export function accumulate<T, U>(acc: (each: T, result: U) => any, init: U, xs: List): U {
const recurser = (xs: List, result: U): U => {
if (is_null(xs)) return result

return recurser(tail(xs), acc(head(xs), result))
/**
* Accumulate applies given operation op to elements of a list
* in a right-to-left order, first apply op to the last element
* and an initial element, resulting in r1, then to the second-last
* element and r1, resulting in r2, etc, and finally to the first element
* and r_n-1, where n is the length of the list. `accumulate(op,zero,list(1,2,3))`
* results in `op(1, op(2, op(3, zero)))`
*/
export function accumulate<T, U>(op: (each: T, result: U) => U, initial: U, sequence: List): U {
// Use CPS to prevent stack overflow
function $accumulate(xs: typeof sequence, cont: (each: U) => U): U {
return is_null(xs) ? cont(initial) : $accumulate(tail(xs), x => cont(op(head(xs), x)))
}

return recurser(xs, init)
return $accumulate(sequence, x => x)
}

export function length(xs: List): number {
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"src/stdlib/**/*.js",
"node_modules",
"dist",
"sicp_publish"
"sicp_publish",
],
"types": [
"typePatches",
Expand Down

0 comments on commit 8d3b2e3

Please sign in to comment.