Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1105,6 +1105,19 @@
"Exception handling"
]
},
{
"uuid" : "cfa5741c-9fe9-4cb5-a322-d77ba8145f4b",
"slug" : "change",
"core" : false,
"unlocked_by": "prime-factors",
"difficulty" : 8,
"topics": [
"Algorithms",
"Mathematics",
"Performance",
"Searching"
]
},
{
"uuid": "0e43944b-0a68-5680-ef11-70999d2df897c894476",
"slug": "twelve-days",
Expand Down
44 changes: 44 additions & 0 deletions exercises/change/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Change

Correctly determine the change to be given using the least number of coins.

The solution will need to accept a value of change to be given and an array of
coin denominations. The program returns the array of coin denominations to
produce the correct amount of change. For example, if change for 37 cents
is required from coins with the denominations of 1, 5, 10 and 25 then the
result is an array with the values: 1, 1, 10 and 25.

## Setup

Go through the setup instructions for ECMAScript to
install the necessary dependencies:

http://exercism.io/languages/ecmascript

## Requirements

Install assignment dependencies:

```bash
$ npm install
```

## Making the Test Suite Pass

Execute the tests with:

```bash
$ npm test
```

In the test suite, all tests but the first have been skipped.

Once you get a test passing, you can enable the next one by
changing `xtest` to `test`.

## Source

Unknown

## Submitting Incomplete Solutions
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
80 changes: 80 additions & 0 deletions exercises/change/change.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import Change from './change';

describe('Change', () => {
test('test change for 1 cent', () => {
const change = new Change();
const result = change.calculate([1, 5, 10, 25], 1);
expect(result).toEqual([1]);
});

xtest('test single coin change', () => {
const change = new Change();
const result = change.calculate([1, 5, 10, 25, 100], 25);
expect(result).toEqual([25]);
});

xtest('test multiple coin change', () => {
const change = new Change();
const result = change.calculate([1, 5, 10, 25, 100], 15);
expect(result).toEqual([5, 10]);
});

xtest('test change with Lilliputian Coins where a greedy algorithm fails', () => {
// https://en.wikipedia.org/wiki/Change-making_problem#Greedy_method
const change = new Change();
const result = change.calculate([1, 4, 15, 20, 50], 23);
expect(result).toEqual([4, 4, 15]);
});

xtest('test change with Lower Elbonia Coins where a greedy algorithm fails', () => {
// https://en.wikipedia.org/wiki/Change-making_problem#Greedy_method
const change = new Change();
const result = change.calculate([1, 5, 10, 21, 25], 63);
expect(result).toEqual([21, 21, 21]);
});

xtest('test large amount of change', () => {
const change = new Change();
const result = change.calculate([1, 2, 5, 10, 20, 50, 100], 999);
expect(result).toEqual([2, 2, 5, 20, 20, 50, 100, 100, 100, 100, 100, 100, 100, 100, 100]);
});

xtest('test possible change without unit coins available', () => {
const change = new Change();
const result = change.calculate([2, 5, 10, 20, 50], 21);
expect(result).toEqual([2, 2, 2, 5, 10]);
});

xtest('test another possible change without unit coins available', () => {
const change = new Change();
const result = change.calculate([4, 5], 27);
expect(result).toEqual([4, 4, 4, 5, 5, 5]);
});

xtest('test no coins make 0 change', () => {
const change = new Change();
const result = change.calculate([1, 5, 10, 21, 25], 0);
expect(result).toEqual([]);
});

xtest('error testing for change smaller than the smallest of coins', () => {
const change = new Change();
const message = 'The total 3 cannot be represented in the given currency.';
const test = () => { change.calculate([5, 10], 3); };
expect(test).toThrowError(Error, message);
});

xtest('error testing if no combination can add up to target', () => {
const change = new Change();
const message = 'The total 94 cannot be represented in the given currency.';
const test = () => { change.calculate([5, 10], 94); };
expect(test).toThrowError(Error, message);
});

xtest('negative change is rejected', () => {
const change = new Change();
const message = 'Negative totals are not allowed.';
const test = () => { change.calculate([1, 2, 5], -5); };
expect(test).toThrowError(Error, message);
});
});
120 changes: 120 additions & 0 deletions exercises/change/example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// data structure to hold each candidate solution that is generated
class Candidate {
constructor() {
this.wasSearched = false;
this.coins = [];
}

searched() {
this.wasSearched = true;
}

isSearched() {
return this.wasSearched;
}

getCoins() {
return this.coins;
}

addCoin(coin) {
const sortNum = (a, b) => a - b;

this.coins.push(coin);
this.coins.sort(sortNum);
}

getCoinCount() {
return this.coins.length;
}

getSum() {
const getSum = (total, num) => total + num;
return this.coins.reduce(getSum);
}
}

export default class Change {
constructor() {
this.candidates = [];
}
calculate(coinArray, target) {
const { candidates } = this;
// fill the array with 0 to start
candidates[target] = 0;
candidates.fill(0);

const isNumber = element => typeof (element) === 'number';

// save a new candidate to the candidates array
const saveCandidate = (candidate) => {
const sum = candidate.getSum();

if (sum <= target) {
if (!isNumber(candidates[sum])) {
if (candidates[sum].getCoinCount() > candidate.getCoinCount()) {
candidates[sum] = candidate;
}
} else {
candidates[sum] = candidate;
}
}
};

// initialize the candidate array with the given coins only
const initialize = () => {
coinArray.forEach((coin) => {
const candidate = new Candidate();
candidate.addCoin(coin);
saveCandidate(candidate);
});
};

// is everthing searched?
const isDone = () => candidates.every(
candidate => isNumber(candidate) || candidate.isSearched());

// get the next unsearched member of the candidate array
const getNext = () => candidates.find(
candidate => !isNumber(candidate) && !candidate.isSearched());

// for the candidate, generate another candate for each of the possible coins
const branch = (current) => {
coinArray.forEach((coin) => {
// make a new Candidate for coin type
const candidate = new Candidate();
// copy the curent coins into it and add the new coin type
current.getCoins().forEach((currentCoin) => {
candidate.addCoin(currentCoin);
});
candidate.addCoin(coin);
saveCandidate(candidate);
});
};

// validation checks up front
if (target === 0) return [];

if (target < 0) {
throw new Error('Negative totals are not allowed.');
}

if (target < Math.min.apply(null, coinArray)) {
throw new Error(`The total ${target} cannot be represented in the given currency.`);
}


initialize();

// process the arrange until everything is searched
while (!isDone()) {
const candidate = getNext();
branch(candidate);
candidate.searched();
}

// print the result
if (!isNumber(candidates[target])) return candidates[target].getCoins();
throw new Error(`The total ${target} cannot be represented in the given currency.`);
}
}
69 changes: 69 additions & 0 deletions exercises/change/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
"name": "xecmascript",
"version": "0.0.0",
"description": "Exercism exercises in ECMAScript 6.",
"author": "Katrina Owen",
"private": true,
"repository": {
"type": "git",
"url": "https://github.com/exercism/xecmascript"
},
"devDependencies": {
"babel-jest": "^20.0.3",
"babel-plugin-transform-builtin-extend": "^1.1.2",
"babel-preset-env": "^1.4.0",
"eslint": "^3.19.0",
"eslint-config-airbnb": "^15.0.1",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-jsx-a11y": "^5.0.1",
"eslint-plugin-react": "^7.0.1",
"jest": "^20.0.4"
},
"jest": {
"modulePathIgnorePatterns": [
"package.json"
]
},
"babel": {
"presets": [
"env"
],
"plugins": [
[
"babel-plugin-transform-builtin-extend",
{
"globals": [
"Error"
]
}
],
["transform-regenerator"]
]
},
"scripts": {
"test": "jest --no-cache ./*",
"watch": "jest --no-cache --watch ./*",
"lint": "eslint .",
"lint-test": "eslint . && jest --no-cache ./* "
},
"eslintConfig": {
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
"env": {
"es6": true,
"node": true,
"jest": true
},
"extends": "airbnb",
"rules": {
"import/no-unresolved": "off",
"import/extensions": "off"
}
},
"licenses": [
"MIT"
],
"dependencies": {}
}