Compare commits
25 Commits
baa21b1199
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 897f802659 | |||
| 5f7c1ca61a | |||
| 69edf86b63 | |||
| 857a252fea | |||
| b3ac5ee075 | |||
| 3ffb7f8181 | |||
| 03bdd63342 | |||
| 849d003a68 | |||
| ba4c999a99 | |||
| 2ea2410fb3 | |||
| ca92878a0a | |||
| 58ce2189f7 | |||
| b768376ff0 | |||
| 69e937d98e | |||
| 1146914ec3 | |||
| 0ebad86104 | |||
| 978844ed75 | |||
| 2dab36b36e | |||
| bbc549c3ea | |||
| f03183055b | |||
| ddb9925e18 | |||
| 4dbb1fa75f | |||
| a7b1b9d061 | |||
| 2b2fce0c42 | |||
| 9080e35d7a |
261
1kyu/functional-sql/description.md
Normal file
261
1kyu/functional-sql/description.md
Normal file
@@ -0,0 +1,261 @@
|
||||
In this Kata we are going to mimic the SQL syntax with JavaScript (or TypeScript).
|
||||
|
||||
To do this, you must implement the query() function. This function returns and object with the next methods:
|
||||
|
||||
{
|
||||
select: ...,
|
||||
from: ...,
|
||||
where: ...,
|
||||
orderBy: ...,
|
||||
groupBy: ...,
|
||||
having: ...,
|
||||
execute: ...
|
||||
}
|
||||
The methods are chainable and the query is executed by calling the execute() method.
|
||||
|
||||
⚠️ Note: The order of appearance of a clause in a query doesn't matter. However, when it comes time for you to run the query, you MUST execute the clauses in this logical order: from first, then where, then groupBy, then having, then select and finally orderBy.
|
||||
|
||||
SELECT * FROM numbers
|
||||
var numbers = [1, 2, 3];
|
||||
query().select().from(numbers).execute(); //[1, 2, 3]
|
||||
|
||||
//clauses order does not matter
|
||||
query().from(numbers).select().execute(); //[1, 2, 3]
|
||||
Of course, you can make queries over object collections:
|
||||
|
||||
var persons = [
|
||||
{name: 'Peter', profession: 'teacher', age: 20, maritalStatus: 'married'},
|
||||
{name: 'Michael', profession: 'teacher', age: 50, maritalStatus: 'single'},
|
||||
{name: 'Peter', profession: 'teacher', age: 20, maritalStatus: 'married'},
|
||||
{name: 'Anna', profession: 'scientific', age: 20, maritalStatus: 'married'},
|
||||
{name: 'Rose', profession: 'scientific', age: 50, maritalStatus: 'married'},
|
||||
{name: 'Anna', profession: 'scientific', age: 20, maritalStatus: 'single'},
|
||||
{name: 'Anna', profession: 'politician', age: 50, maritalStatus: 'married'}
|
||||
];
|
||||
|
||||
//SELECT * FROM persons
|
||||
query().select().from(persons).execute(); // [{name: 'Peter',...}, {name: 'Michael', ...}]
|
||||
You can select some fields:
|
||||
|
||||
function profession(person) {
|
||||
return person.profession;
|
||||
}
|
||||
|
||||
//SELECT profession FROM persons
|
||||
query().select(profession).from(persons).execute(); //select receives a function that will be called with the values of the array //["teacher","teacher","teacher","scientific","scientific","scientific","politician"]
|
||||
If you repeat a SQL clause (except where() or having()), an exception will be thrown
|
||||
|
||||
query().select().select().execute(); //Error('Duplicate SELECT');
|
||||
query().select().from([]).select().execute(); //Error('Duplicate SELECT');
|
||||
query().select().from([]).from([]).execute(); //Error('Duplicate FROM');
|
||||
query().select().from([]).where().where() //This is an AND filter (see below)
|
||||
You can omit any SQLclause:
|
||||
|
||||
var numbers = [1, 2, 3];
|
||||
|
||||
query().select().execute(); //[]
|
||||
query().from(numbers).execute(); // [1, 2, 3]
|
||||
query().execute(); // []
|
||||
You can apply filters:
|
||||
|
||||
function isTeacher(person) {
|
||||
return person.profession === 'teacher';
|
||||
}
|
||||
|
||||
//SELECT profession FROM persons WHERE profession="teacher"
|
||||
query().select(profession).from(persons).where(isTeacher).execute(); //["teacher", "teacher", "teacher"]
|
||||
|
||||
//SELECT * FROM persons WHERE profession="teacher"
|
||||
query().select().from(persons).where(isTeacher).execute(); //[{person: 'Peter', profession: 'teacher', ...}, ...]
|
||||
|
||||
function name(person) {
|
||||
return person.name;
|
||||
}
|
||||
|
||||
//SELECT name FROM persons WHERE profession="teacher"
|
||||
query().select(name).from(persons).where(isTeacher).execute();//["Peter", "Michael", "Peter"]
|
||||
Agrupations are also possible:
|
||||
|
||||
//SELECT * FROM persons GROUP BY profession <- Bad in SQL but possible in this kata
|
||||
query().select().from(persons).groupBy(profession).execute();
|
||||
[
|
||||
["teacher",
|
||||
[
|
||||
{
|
||||
name: "Peter",
|
||||
profession: "teacher"
|
||||
...
|
||||
},
|
||||
{
|
||||
name: "Michael",
|
||||
profession: "teacher"
|
||||
...
|
||||
}
|
||||
]
|
||||
],
|
||||
["scientific",
|
||||
[
|
||||
{
|
||||
name: "Anna",
|
||||
profession: "scientific"
|
||||
},
|
||||
...
|
||||
]
|
||||
]
|
||||
...
|
||||
]
|
||||
You can mix where() with groupBy():
|
||||
|
||||
//SELECT * FROM persons WHERE profession='teacher' GROUP BY profession
|
||||
query().select().from(persons).where(isTeacher).groupBy(profession).execute();
|
||||
Or with select():
|
||||
|
||||
function professionGroup(group) {
|
||||
return group[0];
|
||||
}
|
||||
|
||||
//SELECT profession FROM persons GROUP BY profession
|
||||
query().select(professionGroup).from(persons).groupBy(profession).execute(); //["teacher","scientific","politician"]
|
||||
Another example:
|
||||
|
||||
function isEven(number) {
|
||||
return number % 2 === 0;
|
||||
}
|
||||
|
||||
function parity(number) {
|
||||
return isEven(number) ? 'even' : 'odd';
|
||||
}
|
||||
|
||||
var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
|
||||
//SELECT * FROM numbers
|
||||
query().select().from(numbers).execute(); //[1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
|
||||
//SELECT * FROM numbers GROUP BY parity
|
||||
query().select().from(numbers).groupBy(parity).execute(); //[["odd",[1,3,5,7,9]],["even",[2,4,6,8]]]
|
||||
Multilevel grouping:
|
||||
|
||||
function isPrime(number) {
|
||||
if (number < 2) {
|
||||
return false;
|
||||
}
|
||||
var divisor = 2;
|
||||
for(; number % divisor !== 0; divisor++);
|
||||
return divisor === number;
|
||||
}
|
||||
|
||||
function prime(number) {
|
||||
return isPrime(number) ? 'prime' : 'divisible';
|
||||
}
|
||||
|
||||
//SELECT * FROM numbers GROUP BY parity, isPrime
|
||||
query().select().from(numbers).groupBy(parity, prime).execute(); // [["odd",[["divisible",[1,9]],["prime",[3,5,7]]]],["even",[["prime",[2]],["divisible",[4,6,8]]]]]
|
||||
orderBy should be called after groupBy, so the values passed to orderBy function are the grouped results by the groupBy function.
|
||||
|
||||
Filter groups with having():
|
||||
|
||||
function odd(group) {
|
||||
return group[0] === 'odd';
|
||||
}
|
||||
|
||||
//SELECT * FROM numbers GROUP BY parity HAVING odd(number) = true <- I know, this is not a valid SQL statement, but you can understand what I am doing
|
||||
query().select().from(numbers).groupBy(parity).having(odd).execute(); //[["odd",[1,3,5,7,9]]]
|
||||
You can order the results:
|
||||
|
||||
var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
|
||||
function descendentCompare(number1, number2) {
|
||||
return number2 - number1;
|
||||
}
|
||||
|
||||
//SELECT * FROM numbers ORDER BY value DESC
|
||||
query().select().from(numbers).orderBy(descendentCompare).execute(); //[9,8,7,6,5,4,3,2,1]
|
||||
from() supports multiple collections:
|
||||
|
||||
var teachers = [
|
||||
{
|
||||
teacherId: '1',
|
||||
teacherName: 'Peter'
|
||||
},
|
||||
{
|
||||
teacherId: '2',
|
||||
teacherName: 'Anna'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
var students = [
|
||||
{
|
||||
studentName: 'Michael',
|
||||
tutor: '1'
|
||||
},
|
||||
{
|
||||
studentName: 'Rose',
|
||||
tutor: '2'
|
||||
}
|
||||
];
|
||||
|
||||
function teacherJoin(join) {
|
||||
return join[0].teacherId === join[1].tutor;
|
||||
}
|
||||
|
||||
function student(join) {
|
||||
return {studentName: join[1].studentName, teacherName: join[0].teacherName};
|
||||
}
|
||||
|
||||
//SELECT studentName, teacherName FROM teachers, students WHERE teachers.teacherId = students.tutor
|
||||
query().select(student).from(teachers, students).where(teacherJoin).execute(); //[{"studentName":"Michael","teacherName":"Peter"},{"studentName":"Rose","teacherName":"Anna"}]
|
||||
Finally, where() and having() admit multiple AND and OR filters:
|
||||
|
||||
function tutor1(join) {
|
||||
return join[1].tutor === "1";
|
||||
}
|
||||
|
||||
//SELECT studentName, teacherName FROM teachers, students WHERE teachers.teacherId = students.tutor AND tutor = 1
|
||||
query().select(student).from(teachers, students).where(teacherJoin).where(tutor1).execute(); //[{"studentName":"Michael","teacherName":"Peter"}] <- AND filter
|
||||
|
||||
var numbers = [1, 2, 3, 4, 5, 7];
|
||||
|
||||
function lessThan3(number) {
|
||||
return number < 3;
|
||||
}
|
||||
|
||||
function greaterThan4(number) {
|
||||
return number > 4;
|
||||
}
|
||||
|
||||
//SELECT * FROM number WHERE number < 3 OR number > 4
|
||||
query().select().from(numbers).where(lessThan3, greaterThan4).execute(); //[1, 2, 5, 7] <- OR filter
|
||||
|
||||
var numbers = [1, 2, 1, 3, 5, 6, 1, 2, 5, 6];
|
||||
|
||||
function greatThan1(group) {
|
||||
return group[1].length > 1;
|
||||
}
|
||||
|
||||
function isPair(group) {
|
||||
return group[0] % 2 === 0;
|
||||
}
|
||||
|
||||
function id(value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
function frequency(group) {
|
||||
return { value: group[0], frequency: group[1].length };
|
||||
}
|
||||
|
||||
//SELECT number, count(number) FROM numbers GROUP BY number HAVING count(number) > 1 AND isPair(number)
|
||||
query().select(frequency).from(numbers).groupBy(id).having(greatThan1).having(isPair).execute(); // [{"value":2,"frequency":2},{"value":6,"frequency":2}])
|
||||
Requirements Recap
|
||||
Clause ⚠️ Must be executed... Arg(s) Count Arg Type Repeatable?
|
||||
from First 1 or More (=> cartesian product of specified tables) Table(s) (i.e., arrays) No
|
||||
where Second 1 or More (=> to be logically OR'd) Functions Yes (each repetition is a logical AND)
|
||||
groupBy Third 1 or More (=> groups by the 1st fn, then, within each subgroup, groups by the 2nd fn, ...) Functions No
|
||||
having Fourth 1 or More (=> to be logically OR'd) Functions Yes (each repetition is a logical AND)
|
||||
select Fifth 0 (selects everything) or 1 Function No
|
||||
orderBy Last 1 Function No
|
||||
execute - None (just executes the entire query) - -
|
||||
If any of the unrepeatable clauses are repeated in the query, your solution MUST raise an Error object with the error message "duplicate " followed by the name of the duplicated clause. If the clause is multi-word, merge it into one (ex: groupby).
|
||||
|
||||
For example, if the groupBy clause is duplicated, you should throw an Error with the exact string message "duplicate groupby" (capitalization doesn't matter).
|
||||
232
1kyu/functional-sql/solution.ts
Normal file
232
1kyu/functional-sql/solution.ts
Normal file
@@ -0,0 +1,232 @@
|
||||
type GroupedData = Map<string, any[] | GroupedData>;
|
||||
|
||||
class Query {
|
||||
private data: any[];
|
||||
private selector: (obj: any) => any = (obj) => obj;
|
||||
private filterGroups: ((obj: any) => boolean)[][] = [];
|
||||
private groupBySelectors: ((obj: any) => any)[] = [];
|
||||
private groupFilterGroups: ((obj: any) => boolean)[][] = [];
|
||||
private orderFunction: (obj1: any, obj2: any) => number = (obj1, obj2) => 0;
|
||||
|
||||
constructor() {
|
||||
this.data = [];
|
||||
}
|
||||
|
||||
// If you repeat a SQL clause (except where() or having()), an exception will be thrown
|
||||
private _selected = false;
|
||||
private _fromed = false;
|
||||
private _grouped = false;
|
||||
private _ordered = false;
|
||||
|
||||
select(newSelector?: (obj: any) => any) {
|
||||
if (this._selected) {
|
||||
throw new Error('Duplicate SELECT');
|
||||
}
|
||||
|
||||
if (newSelector) {
|
||||
this.selector = newSelector;
|
||||
};
|
||||
|
||||
this._selected = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
from(...dataRows: any[][]) {
|
||||
if (this._fromed) {
|
||||
throw new Error('Duplicate FROM');
|
||||
}
|
||||
|
||||
this._fromed = true;
|
||||
|
||||
// cartesian product of all data arrays
|
||||
dataRows.forEach((dataRow) => {
|
||||
if (this.data.length === 0) {
|
||||
this.data = dataRow;
|
||||
return;
|
||||
}
|
||||
|
||||
const newData: any[] = [];
|
||||
this.data.forEach((obj) => {
|
||||
dataRow.forEach((newObj) => {
|
||||
newData.push([obj, newObj]);
|
||||
});
|
||||
})
|
||||
|
||||
this.data = newData;
|
||||
})
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
where(...newFilters: ((obj: any) => boolean)[]) {
|
||||
this.filterGroups.push(newFilters);
|
||||
return this;
|
||||
}
|
||||
|
||||
groupBy(...newGroupBySelectors: ((obj: any) => any)[]) {
|
||||
if (this._grouped) {
|
||||
throw new Error('Duplicate GROUPBY');
|
||||
}
|
||||
|
||||
this._grouped = true;
|
||||
this.groupBySelectors = newGroupBySelectors;
|
||||
return this;
|
||||
}
|
||||
|
||||
having(...newGroupFilters: ((obj: any) => boolean)[]) {
|
||||
this.groupFilterGroups.push(newGroupFilters);
|
||||
return this;
|
||||
}
|
||||
|
||||
orderBy(newOrderFunction: (obj1: any, obj2: any) => number) {
|
||||
if (this._ordered) {
|
||||
throw new Error('Duplicate ORDERBY');
|
||||
}
|
||||
|
||||
this._ordered = true;
|
||||
this.orderFunction = newOrderFunction;
|
||||
return this;
|
||||
}
|
||||
|
||||
execute() {
|
||||
// filter
|
||||
this.data = this.executeFilter();
|
||||
|
||||
// group by
|
||||
const groupedMap = this.executeGroupBy();
|
||||
|
||||
// filter groups
|
||||
this.executeHaving(groupedMap);
|
||||
|
||||
const selected = this.executeSelect(groupedMap);
|
||||
|
||||
const result = this.executeOrderBy(selected);
|
||||
return result;
|
||||
}
|
||||
|
||||
private executeFilter() {
|
||||
// this.filterGroups is a list of groups of filters
|
||||
// filters in the same group are ORed
|
||||
// groups are ANDed
|
||||
let filteredData = this.data;
|
||||
this.filterGroups.forEach((filterGroup) => {
|
||||
filteredData = filteredData.filter((obj) => {
|
||||
return filterGroup.some((filter) => filter(obj));
|
||||
});
|
||||
})
|
||||
return filteredData;
|
||||
}
|
||||
|
||||
private groupOnce(data: any[], groupBySelector: (obj: any) => any) {
|
||||
const groupedData = new Map<string, any[] | GroupedData>();
|
||||
data.forEach((obj) => {
|
||||
const key = groupBySelector(obj);
|
||||
if (!groupedData.has(key)) {
|
||||
groupedData.set(key, []);
|
||||
}
|
||||
(groupedData.get(key) as any[]).push(obj);
|
||||
});
|
||||
|
||||
return groupedData;
|
||||
}
|
||||
|
||||
private groupRecursively(data: any[], groupBySelectors: ((obj: any) => any)[]) {
|
||||
|
||||
const [groupBySelector, ...restGroupBySelectors] = groupBySelectors;
|
||||
const groupedData = this.groupOnce(data, groupBySelector);
|
||||
if (restGroupBySelectors.length === 0) {
|
||||
return groupedData;
|
||||
}
|
||||
|
||||
Array.from(groupedData.keys()).forEach((key) => {
|
||||
const group = groupedData.get(key) as any[];
|
||||
groupedData.set(key, this.groupRecursively(group, restGroupBySelectors) as Map<string, any[]>);
|
||||
});
|
||||
|
||||
return groupedData;
|
||||
}
|
||||
|
||||
private executeGroupBy(filteredData: any[] = this.data) {
|
||||
if (this.groupBySelectors.length === 0) {
|
||||
return new Map<string, any[]>([['', filteredData]]);
|
||||
}
|
||||
|
||||
const groupedData = this.groupRecursively(filteredData, this.groupBySelectors);
|
||||
return groupedData;
|
||||
}
|
||||
|
||||
private executeHaving(groupedData: GroupedData) {
|
||||
if (this.groupFilterGroups.length === 0) return groupedData;
|
||||
// filter groups
|
||||
// groupFilterGroups is a list of groups of filters
|
||||
// filters in the same group are ORed
|
||||
// groups are ANDed
|
||||
|
||||
// first flatten the map to arrays
|
||||
let flattened: { flatGroup: any[], key: string }[] = [];
|
||||
Array.from(groupedData.keys()).forEach((key) => {
|
||||
const group = groupedData.get(key) as GroupedData | any[];
|
||||
const flattenedGroup = Array.isArray(group) ?
|
||||
group
|
||||
: this.flattenMapToArrays(group as GroupedData, this.selector);
|
||||
flattened.push({ flatGroup: [key, flattenedGroup], key });
|
||||
});
|
||||
|
||||
this.groupFilterGroups.forEach((groupFilters) => {
|
||||
flattened = flattened.filter((group) => {
|
||||
const toKeep = groupFilters.some((filter) => filter(group.flatGroup));
|
||||
if (!toKeep) groupedData.delete(group.key);
|
||||
return toKeep;
|
||||
})
|
||||
});
|
||||
|
||||
return groupedData;
|
||||
}
|
||||
|
||||
private flattenMapToArrays(groupedData: GroupedData, selector?: (obj: any) => any) {
|
||||
// flatten maps to arrays
|
||||
const result: any[] = [];
|
||||
Array.from(groupedData.keys()).forEach((key) => {
|
||||
const group = groupedData.get(key) as GroupedData | any[];
|
||||
const flattened = Array.isArray(group) ?
|
||||
selector ? (group as any[]).map(selector)
|
||||
: group as any[]
|
||||
: this.flattenMapToArrays(group as GroupedData, selector)
|
||||
result.push([key, flattened]);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private executeSelect(groupedData: GroupedData) {
|
||||
const keys = Array.from(groupedData.keys());
|
||||
if (keys.length === 1 && keys[0] === '') {
|
||||
return (groupedData.get('') as any[])?.map(this.selector);
|
||||
}
|
||||
|
||||
return Array.from(keys).map((key) => {
|
||||
const group = groupedData.get(key) as GroupedData | any[];
|
||||
if (this.groupBySelectors.length === 0) {
|
||||
// no group by was done,
|
||||
// selector selects from objects
|
||||
return [key, (group as any[]).map(this.selector)];
|
||||
}
|
||||
|
||||
const flattenedGroup = Array.isArray(group) ?
|
||||
group
|
||||
: this.flattenMapToArrays(group as GroupedData, this.selector);
|
||||
// selector selects from groups
|
||||
return this.selector([key, flattenedGroup]);
|
||||
});
|
||||
}
|
||||
|
||||
private executeOrderBy(selected: any[]) {
|
||||
return selected.sort(this.orderFunction);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function query() {
|
||||
const q = new Query();
|
||||
return q;
|
||||
};
|
||||
632
1kyu/functional-sql/tests.ts
Normal file
632
1kyu/functional-sql/tests.ts
Normal file
@@ -0,0 +1,632 @@
|
||||
import {
|
||||
query
|
||||
} from './solution';
|
||||
import {
|
||||
expect
|
||||
} from "chai";
|
||||
|
||||
describe("SQL tests", function() {
|
||||
it("Basic SELECT tests", () => {
|
||||
var numbers = [1, 2, 3];
|
||||
expect(query().select().from(numbers).execute()).to.deep.equal(numbers);
|
||||
expect(query().select().execute()).to.deep.equal([], 'No FROM clause produces empty array');
|
||||
expect(query().from(numbers).execute()).to.deep.equal(numbers, 'SELECT can be omited');
|
||||
expect(query().execute()).to.deep.equal([]);
|
||||
expect(query().from(numbers).select().execute()).to.deep.equal(numbers, 'The order does not matter');
|
||||
});
|
||||
|
||||
it("Basic SELECT and WHERE over objects", () => {
|
||||
var persons = [{
|
||||
name: 'Peter',
|
||||
profession: 'teacher',
|
||||
age: 20,
|
||||
maritalStatus: 'married'
|
||||
},
|
||||
{
|
||||
name: 'Michael',
|
||||
profession: 'teacher',
|
||||
age: 50,
|
||||
maritalStatus: 'single'
|
||||
},
|
||||
{
|
||||
name: 'Peter',
|
||||
profession: 'teacher',
|
||||
age: 20,
|
||||
maritalStatus: 'married'
|
||||
},
|
||||
{
|
||||
name: 'Anna',
|
||||
profession: 'scientific',
|
||||
age: 20,
|
||||
maritalStatus: 'married'
|
||||
},
|
||||
{
|
||||
name: 'Rose',
|
||||
profession: 'scientific',
|
||||
age: 50,
|
||||
maritalStatus: 'married'
|
||||
},
|
||||
{
|
||||
name: 'Anna',
|
||||
profession: 'scientific',
|
||||
age: 20,
|
||||
maritalStatus: 'single'
|
||||
},
|
||||
{
|
||||
name: 'Anna',
|
||||
profession: 'politician',
|
||||
age: 50,
|
||||
maritalStatus: 'married'
|
||||
}
|
||||
];
|
||||
|
||||
expect(query().select().from(persons).execute()).to.deep.equal(persons);
|
||||
|
||||
function profession(person: any) {
|
||||
return person.profession;
|
||||
}
|
||||
|
||||
// SELECT profession FROM persons
|
||||
expect(query().select(profession).from(persons).execute()).to.deep.equal(["teacher", "teacher", "teacher", "scientific", "scientific", "scientific", "politician"]);
|
||||
expect(query().select(profession).execute()).to.deep.equal([], 'No FROM clause produces empty array');
|
||||
|
||||
function isTeacher(person: any) {
|
||||
return person.profession === 'teacher';
|
||||
}
|
||||
|
||||
// SELECT profession FROM persons WHERE profession="teacher"
|
||||
expect(query().select(profession).from(persons).where(isTeacher).execute()).to.deep.equal(["teacher", "teacher", "teacher"]);
|
||||
|
||||
// SELECT * FROM persons WHERE profession="teacher"
|
||||
expect(query().from(persons).where(isTeacher).execute()).to.deep.equal(persons.slice(0, 3));
|
||||
|
||||
function name(person: any) {
|
||||
return person.name;
|
||||
}
|
||||
|
||||
// SELECT name FROM persons WHERE profession="teacher"
|
||||
expect(query().select(name).from(persons).where(isTeacher).execute()).to.deep.equal(["Peter", "Michael", "Peter"]);
|
||||
expect(query().where(isTeacher).from(persons).select(name).execute()).to.deep.equal(["Peter", "Michael", "Peter"]);
|
||||
});
|
||||
|
||||
it("GROUP BY tests", () => {
|
||||
var persons = [{
|
||||
name: 'Peter',
|
||||
profession: 'teacher',
|
||||
age: 20,
|
||||
maritalStatus: 'married'
|
||||
},
|
||||
{
|
||||
name: 'Michael',
|
||||
profession: 'teacher',
|
||||
age: 50,
|
||||
maritalStatus: 'single'
|
||||
},
|
||||
{
|
||||
name: 'Peter',
|
||||
profession: 'teacher',
|
||||
age: 20,
|
||||
maritalStatus: 'married'
|
||||
},
|
||||
{
|
||||
name: 'Anna',
|
||||
profession: 'scientific',
|
||||
age: 20,
|
||||
maritalStatus: 'married'
|
||||
},
|
||||
{
|
||||
name: 'Rose',
|
||||
profession: 'scientific',
|
||||
age: 50,
|
||||
maritalStatus: 'married'
|
||||
},
|
||||
{
|
||||
name: 'Anna',
|
||||
profession: 'scientific',
|
||||
age: 20,
|
||||
maritalStatus: 'single'
|
||||
},
|
||||
{
|
||||
name: 'Anna',
|
||||
profession: 'politician',
|
||||
age: 50,
|
||||
maritalStatus: 'married'
|
||||
}
|
||||
];
|
||||
|
||||
function profession(person: any) {
|
||||
return person.profession;
|
||||
}
|
||||
|
||||
// SELECT * FROM persons GROUPBY profession <- Bad in SQL but possible in JavaScript
|
||||
expect(query().select().from(persons).groupBy(profession).execute()).to.deep.equal([
|
||||
["teacher", [{
|
||||
"name": "Peter",
|
||||
"profession": "teacher",
|
||||
"age": 20,
|
||||
"maritalStatus": "married"
|
||||
}, {
|
||||
"name": "Michael",
|
||||
"profession": "teacher",
|
||||
"age": 50,
|
||||
"maritalStatus": "single"
|
||||
}, {
|
||||
"name": "Peter",
|
||||
"profession": "teacher",
|
||||
"age": 20,
|
||||
"maritalStatus": "married"
|
||||
}]],
|
||||
["scientific", [{
|
||||
"name": "Anna",
|
||||
"profession": "scientific",
|
||||
"age": 20,
|
||||
"maritalStatus": "married"
|
||||
}, {
|
||||
"name": "Rose",
|
||||
"profession": "scientific",
|
||||
"age": 50,
|
||||
"maritalStatus": "married"
|
||||
}, {
|
||||
"name": "Anna",
|
||||
"profession": "scientific",
|
||||
"age": 20,
|
||||
"maritalStatus": "single"
|
||||
}]],
|
||||
["politician", [{
|
||||
"name": "Anna",
|
||||
"profession": "politician",
|
||||
"age": 50,
|
||||
"maritalStatus": "married"
|
||||
}]]
|
||||
]);
|
||||
|
||||
function isTeacher(person: any) {
|
||||
return person.profession === 'teacher';
|
||||
}
|
||||
|
||||
// SELECT * FROM persons WHERE profession='teacher' GROUPBY profession
|
||||
expect(query().select().from(persons).where(isTeacher).groupBy(profession).execute()).to.deep.equal([
|
||||
["teacher", [{
|
||||
"name": "Peter",
|
||||
"profession": "teacher",
|
||||
"age": 20,
|
||||
"maritalStatus": "married"
|
||||
}, {
|
||||
"name": "Michael",
|
||||
"profession": "teacher",
|
||||
"age": 50,
|
||||
"maritalStatus": "single"
|
||||
}, {
|
||||
"name": "Peter",
|
||||
"profession": "teacher",
|
||||
"age": 20,
|
||||
"maritalStatus": "married"
|
||||
}]]
|
||||
]);
|
||||
|
||||
function professionGroup(group: any) {
|
||||
return group[0];
|
||||
}
|
||||
|
||||
// SELECT profession FROM persons GROUPBY profession
|
||||
expect(query().select(professionGroup).from(persons).groupBy(profession).execute()).to.deep.equal(["teacher", "scientific", "politician"]);
|
||||
|
||||
function name(person: any) {
|
||||
return person.name;
|
||||
}
|
||||
|
||||
// SELECT * FROM persons WHERE profession='teacher' GROUPBY profession, name
|
||||
expect(query().select().from(persons).groupBy(profession, name).execute()).to.deep.equal([
|
||||
["teacher", [
|
||||
["Peter", [{
|
||||
"name": "Peter",
|
||||
"profession": "teacher",
|
||||
"age": 20,
|
||||
"maritalStatus": "married"
|
||||
}, {
|
||||
"name": "Peter",
|
||||
"profession": "teacher",
|
||||
"age": 20,
|
||||
"maritalStatus": "married"
|
||||
}]],
|
||||
["Michael", [{
|
||||
"name": "Michael",
|
||||
"profession": "teacher",
|
||||
"age": 50,
|
||||
"maritalStatus": "single"
|
||||
}]]
|
||||
]],
|
||||
["scientific", [
|
||||
["Anna", [{
|
||||
"name": "Anna",
|
||||
"profession": "scientific",
|
||||
"age": 20,
|
||||
"maritalStatus": "married"
|
||||
}, {
|
||||
"name": "Anna",
|
||||
"profession": "scientific",
|
||||
"age": 20,
|
||||
"maritalStatus": "single"
|
||||
}]],
|
||||
["Rose", [{
|
||||
"name": "Rose",
|
||||
"profession": "scientific",
|
||||
"age": 50,
|
||||
"maritalStatus": "married"
|
||||
}]]
|
||||
]],
|
||||
["politician", [
|
||||
["Anna", [{
|
||||
"name": "Anna",
|
||||
"profession": "politician",
|
||||
"age": 50,
|
||||
"maritalStatus": "married"
|
||||
}]]
|
||||
]]
|
||||
]);
|
||||
|
||||
function age(person: any) {
|
||||
return person.age;
|
||||
}
|
||||
|
||||
function maritalStatus(person: any) {
|
||||
return person.maritalStatus;
|
||||
}
|
||||
|
||||
// SELECT * FROM persons WHERE profession='teacher' GROUPBY profession, name, age
|
||||
expect(query().select().from(persons).groupBy(profession, name, age, maritalStatus).execute()).to.deep.equal([
|
||||
["teacher", [
|
||||
["Peter", [
|
||||
[20, [
|
||||
["married", [{
|
||||
"name": "Peter",
|
||||
"profession": "teacher",
|
||||
"age": 20,
|
||||
"maritalStatus": "married"
|
||||
}, {
|
||||
"name": "Peter",
|
||||
"profession": "teacher",
|
||||
"age": 20,
|
||||
"maritalStatus": "married"
|
||||
}]]
|
||||
]]
|
||||
]],
|
||||
["Michael", [
|
||||
[50, [
|
||||
["single", [{
|
||||
"name": "Michael",
|
||||
"profession": "teacher",
|
||||
"age": 50,
|
||||
"maritalStatus": "single"
|
||||
}]]
|
||||
]]
|
||||
]]
|
||||
]],
|
||||
["scientific", [
|
||||
["Anna", [
|
||||
[20, [
|
||||
["married", [{
|
||||
"name": "Anna",
|
||||
"profession": "scientific",
|
||||
"age": 20,
|
||||
"maritalStatus": "married"
|
||||
}]],
|
||||
["single", [{
|
||||
"name": "Anna",
|
||||
"profession": "scientific",
|
||||
"age": 20,
|
||||
"maritalStatus": "single"
|
||||
}]]
|
||||
]]
|
||||
]],
|
||||
["Rose", [
|
||||
[50, [
|
||||
["married", [{
|
||||
"name": "Rose",
|
||||
"profession": "scientific",
|
||||
"age": 50,
|
||||
"maritalStatus": "married"
|
||||
}]]
|
||||
]]
|
||||
]]
|
||||
]],
|
||||
["politician", [
|
||||
["Anna", [
|
||||
[50, [
|
||||
["married", [{
|
||||
"name": "Anna",
|
||||
"profession": "politician",
|
||||
"age": 50,
|
||||
"maritalStatus": "married"
|
||||
}]]
|
||||
]]
|
||||
]]
|
||||
]]
|
||||
]);
|
||||
|
||||
function professionCount(group: any) {
|
||||
return [group[0], group[1].length];
|
||||
}
|
||||
|
||||
// SELECT profession, count(profession) FROM persons GROUPBY profession
|
||||
expect(query().select(professionCount).from(persons).groupBy(profession).execute()).to.deep.equal([
|
||||
["teacher", 3],
|
||||
["scientific", 3],
|
||||
["politician", 1]
|
||||
]);
|
||||
|
||||
function naturalCompare(value1: any, value2: any) {
|
||||
if (value1 < value2) {
|
||||
return -1;
|
||||
} else if (value1 > value2) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// SELECT profession, count(profession) FROM persons GROUPBY profession ORDER BY profession
|
||||
expect(query().select(professionCount).from(persons).groupBy(profession).orderBy(naturalCompare).execute()).to.deep.equal([
|
||||
["politician", 1],
|
||||
["scientific", 3],
|
||||
["teacher", 3]
|
||||
]);
|
||||
});
|
||||
|
||||
it("Number tests", () => {
|
||||
function isEven(number: number) {
|
||||
return number % 2 === 0;
|
||||
}
|
||||
|
||||
function parity(number: number) {
|
||||
return isEven(number) ? 'even' : 'odd';
|
||||
}
|
||||
|
||||
function isPrime(number: number) {
|
||||
if (number < 2) {
|
||||
return false;
|
||||
}
|
||||
var divisor = 2;
|
||||
for (; number % divisor !== 0; divisor++);
|
||||
return divisor === number;
|
||||
}
|
||||
|
||||
function prime(number: number) {
|
||||
return isPrime(number) ? 'prime' : 'divisible';
|
||||
}
|
||||
|
||||
var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
|
||||
// SELECT * FROM numbers
|
||||
expect(query().select().from(numbers).execute()).to.deep.equal(numbers);
|
||||
|
||||
// SELECT * FROM numbers GROUP BY parity
|
||||
expect(query().select().from(numbers).groupBy(parity).execute()).to.deep.equal([
|
||||
["odd", [1, 3, 5, 7, 9]],
|
||||
["even", [2, 4, 6, 8]]
|
||||
]);
|
||||
|
||||
// SELECT * FROM numbers GROUP BY parity, isPrime
|
||||
expect(query().select().from(numbers).groupBy(parity, prime).execute()).to.deep.equal([
|
||||
["odd", [
|
||||
["divisible", [1, 9]],
|
||||
["prime", [3, 5, 7]]
|
||||
]],
|
||||
["even", [
|
||||
["prime", [2]],
|
||||
["divisible", [4, 6, 8]]
|
||||
]]
|
||||
]);
|
||||
|
||||
function odd(group: any) {
|
||||
return group[0] === 'odd';
|
||||
}
|
||||
|
||||
// SELECT * FROM numbers GROUP BY parity HAVING
|
||||
expect(query().select().from(numbers).groupBy(parity).having(odd).execute()).to.deep.equal([
|
||||
["odd", [1, 3, 5, 7, 9]]
|
||||
]);
|
||||
|
||||
function descendentCompare(number1: number, number2: number) {
|
||||
return number2 - number1;
|
||||
}
|
||||
|
||||
// SELECT * FROM numbers ORDER BY value DESC
|
||||
expect(query().select().from(numbers).orderBy(descendentCompare).execute()).to.deep.equal([9, 8, 7, 6, 5, 4, 3, 2, 1]);
|
||||
|
||||
function lessThan3(number: number) {
|
||||
return number < 3;
|
||||
}
|
||||
|
||||
function greaterThan4(number: number) {
|
||||
return number > 4;
|
||||
}
|
||||
|
||||
// SELECT * FROM number WHERE number < 3 OR number > 4
|
||||
expect(query().select().from(numbers).where(lessThan3, greaterThan4).execute()).to.deep.equal([1, 2, 5, 6, 7, 8, 9]);
|
||||
});
|
||||
|
||||
it("Frequency tests", () => {
|
||||
var persons = [
|
||||
['Peter', 3],
|
||||
['Anna', 4],
|
||||
['Peter', 7],
|
||||
['Michael', 10]
|
||||
];
|
||||
|
||||
function nameGrouping(person: any) {
|
||||
return person[0];
|
||||
}
|
||||
|
||||
function sumValues(value: any) {
|
||||
return [value[0], value[1].reduce(function(result: any, person: any) {
|
||||
return result + person[1];
|
||||
}, 0)];
|
||||
}
|
||||
|
||||
function naturalCompare(value1: any, value2: any) {
|
||||
if (value1 < value2) {
|
||||
return -1;
|
||||
} else if (value1 > value2) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// SELECT name, sum(value) FROM persons ORDER BY naturalCompare GROUP BY nameGrouping
|
||||
expect(query().select(sumValues).from(persons).orderBy(naturalCompare).groupBy(nameGrouping).execute()).to.deep.equal([
|
||||
["Anna", 4],
|
||||
["Michael", 10],
|
||||
["Peter", 10]
|
||||
]);
|
||||
|
||||
var numbers = [1, 2, 1, 3, 5, 6, 1, 2, 5, 6];
|
||||
|
||||
function id(value: any) {
|
||||
return value;
|
||||
}
|
||||
|
||||
function frequency(group: any) {
|
||||
return {
|
||||
value: group[0],
|
||||
frequency: group[1].length
|
||||
};
|
||||
}
|
||||
|
||||
// SELECT number, count(number) FROM numbers GROUP BY number
|
||||
expect(query().select(frequency).from(numbers).groupBy(id).execute()).to.deep.equal([{
|
||||
"value": 1,
|
||||
"frequency": 3
|
||||
}, {
|
||||
"value": 2,
|
||||
"frequency": 2
|
||||
}, {
|
||||
"value": 3,
|
||||
"frequency": 1
|
||||
}, {
|
||||
"value": 5,
|
||||
"frequency": 2
|
||||
}, {
|
||||
"value": 6,
|
||||
"frequency": 2
|
||||
}]);
|
||||
|
||||
function greatThan1(group: any) {
|
||||
return group[1].length > 1;
|
||||
}
|
||||
|
||||
function isPair(group: any) {
|
||||
return group[0] % 2 === 0;
|
||||
}
|
||||
|
||||
// SELECT number, count(number) FROM numbers GROUP BY number HAVING count(number) > 1 AND isPair(number)
|
||||
expect(query().select(frequency).from(numbers).groupBy(id).having(greatThan1).having(isPair).execute()).to.deep.equal([{
|
||||
"value": 2,
|
||||
"frequency": 2
|
||||
}, {
|
||||
"value": 6,
|
||||
"frequency": 2
|
||||
}]);
|
||||
});
|
||||
|
||||
it("Join tests", () => {
|
||||
var teachers = [{
|
||||
teacherId: '1',
|
||||
teacherName: 'Peter'
|
||||
},
|
||||
{
|
||||
teacherId: '2',
|
||||
teacherName: 'Anna'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
var students = [{
|
||||
studentName: 'Michael',
|
||||
tutor: '1'
|
||||
},
|
||||
{
|
||||
studentName: 'Rose',
|
||||
tutor: '2'
|
||||
}
|
||||
];
|
||||
|
||||
function teacherJoin(join: any) {
|
||||
return join[0].teacherId === join[1].tutor;
|
||||
}
|
||||
|
||||
function student(join: any) {
|
||||
return {
|
||||
studentName: join[1].studentName,
|
||||
teacherName: join[0].teacherName
|
||||
};
|
||||
}
|
||||
|
||||
// SELECT studentName, teacherName FROM teachers, students WHERE teachers.teacherId = students.tutor
|
||||
expect(query().select(student).from(teachers, students).where(teacherJoin).execute()).to.deep.equal([{
|
||||
"studentName": "Michael",
|
||||
"teacherName": "Peter"
|
||||
}, {
|
||||
"studentName": "Rose",
|
||||
"teacherName": "Anna"
|
||||
}]);
|
||||
|
||||
var numbers1 = [1, 2];
|
||||
var numbers2 = [4, 5];
|
||||
|
||||
expect(query().select().from(numbers1, numbers2).execute()).to.deep.equal([
|
||||
[1, 4],
|
||||
[1, 5],
|
||||
[2, 4],
|
||||
[2, 5]
|
||||
]);
|
||||
|
||||
function tutor1(join: any) {
|
||||
return join[1].tutor === "1";
|
||||
}
|
||||
|
||||
// SELECT studentName, teacherName FROM teachers, students WHERE teachers.teacherId = students.tutor AND tutor = 1
|
||||
expect(query().select(student).from(teachers, students).where(teacherJoin).where(tutor1).execute()).to.deep.equal([{
|
||||
"studentName": "Michael",
|
||||
"teacherName": "Peter"
|
||||
}]);
|
||||
expect(query().where(teacherJoin).select(student).where(tutor1).from(teachers, students).execute()).to.deep.equal([{
|
||||
"studentName": "Michael",
|
||||
"teacherName": "Peter"
|
||||
}]);
|
||||
|
||||
});
|
||||
|
||||
it("Duplication exception tests", () => {
|
||||
function checkError(fn: any, duplicate: any) {
|
||||
try {
|
||||
fn();
|
||||
expect(false).to.equal(false, 'An error should be throw');
|
||||
} catch (e) {
|
||||
expect(e instanceof Error).to.equal(true);
|
||||
expect((e as Error).message).to.equal('Duplicate ' + duplicate);
|
||||
}
|
||||
}
|
||||
|
||||
function id(value: any) {
|
||||
return value;
|
||||
}
|
||||
|
||||
checkError(function() {
|
||||
query().select().select().execute();
|
||||
}, 'SELECT');
|
||||
checkError(function() {
|
||||
query().select().from([]).select().execute();
|
||||
}, 'SELECT');
|
||||
checkError(function() {
|
||||
query().select().from([]).from([]).execute();
|
||||
}, 'FROM');
|
||||
checkError(function() {
|
||||
query().select().from([]).orderBy(id).orderBy(id).execute();
|
||||
}, 'ORDERBY');
|
||||
checkError(function() {
|
||||
query().select().groupBy(id).from([]).groupBy(id).execute();
|
||||
}, 'GROUPBY');
|
||||
});
|
||||
});
|
||||
41
2kyu/evaluate-mathematical-expression/description.md
Normal file
41
2kyu/evaluate-mathematical-expression/description.md
Normal file
@@ -0,0 +1,41 @@
|
||||
Instructions
|
||||
Given a mathematical expression as a string you must return the result as a number.
|
||||
|
||||
Numbers
|
||||
Number may be both whole numbers and/or decimal numbers. The same goes for the returned result.
|
||||
|
||||
Operators
|
||||
You need to support the following mathematical operators:
|
||||
|
||||
Multiplication *
|
||||
Division / (as floating point division)
|
||||
Addition +
|
||||
Subtraction -
|
||||
Operators are always evaluated from left-to-right, and * and / must be evaluated before + and -.
|
||||
|
||||
Parentheses
|
||||
You need to support multiple levels of nested parentheses, ex. (2 / (2 + 3.33) * 4) - -6
|
||||
|
||||
Whitespace
|
||||
There may or may not be whitespace between numbers and operators.
|
||||
|
||||
An addition to this rule is that the minus sign (-) used for negating numbers and parentheses will never be separated by whitespace. I.e all of the following are valid expressions.
|
||||
|
||||
1-1 // 0
|
||||
1 -1 // 0
|
||||
1- 1 // 0
|
||||
1 - 1 // 0
|
||||
1- -1 // 2
|
||||
1 - -1 // 2
|
||||
1--1 // 2
|
||||
|
||||
6 + -(4) // 2
|
||||
6 + -( -4) // 10
|
||||
And the following are invalid expressions
|
||||
|
||||
1 - - 1 // Invalid
|
||||
1- - 1 // Invalid
|
||||
6 + - (4) // Invalid
|
||||
6 + -(- 4) // Invalid
|
||||
Validation
|
||||
You do not need to worry about validation - you will only receive valid mathematical expressions following the above rules.
|
||||
359
2kyu/evaluate-mathematical-expression/solution.ts
Normal file
359
2kyu/evaluate-mathematical-expression/solution.ts
Normal file
@@ -0,0 +1,359 @@
|
||||
|
||||
|
||||
type TokenType = 'number' | 'operator' | 'parenthesis' | 'decimal';
|
||||
type TokenValue = number | '+' | '-' | '*' | '/' | '(' | ')' | '.';
|
||||
|
||||
type Token = NumberToken | OperatorToken | ParenthesisToken | DecimalToken;
|
||||
|
||||
type OperatorChars = '+' | '-' | '*' | '/';
|
||||
type OperatorToken = {
|
||||
type: 'operator',
|
||||
value: OperatorChars,
|
||||
}
|
||||
|
||||
const isOperatorChar = (char: string): char is OperatorChars => {
|
||||
return char === '+' || char === '-' || char === '*' || char === '/';
|
||||
}
|
||||
|
||||
type NumberToken = {
|
||||
type: 'number',
|
||||
value: string,
|
||||
}
|
||||
|
||||
type ParenthesisChars = '(' | ')';
|
||||
type ParenthesisToken = {
|
||||
type: 'parenthesis',
|
||||
value: ParenthesisChars,
|
||||
}
|
||||
|
||||
const isParenthesisChar = (char: string): char is ParenthesisChars => {
|
||||
return char === '(' || char === ')';
|
||||
}
|
||||
|
||||
type DecimalChars = '.';
|
||||
type DecimalToken = {
|
||||
type: 'decimal',
|
||||
value: DecimalChars,
|
||||
}
|
||||
|
||||
const isDecimalChar = (char: string): char is DecimalChars => {
|
||||
return char === '.';
|
||||
}
|
||||
|
||||
export const tokenize = (expression: string): Token[] => {
|
||||
const tokens: Token[] = [];
|
||||
const chars = expression.split('');
|
||||
let done = false;
|
||||
let i = 0;
|
||||
while (!done) {
|
||||
if (i >= chars.length) {
|
||||
done = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
const char = chars[i];
|
||||
if (char === ' ') {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
// parenthesis
|
||||
if (isParenthesisChar(char)) {
|
||||
tokens.push({ type: 'parenthesis', value: char });
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
// operator
|
||||
if (isOperatorChar(char)) {
|
||||
tokens.push({ type: 'operator', value: char });
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
// number
|
||||
if (char.match(/\d/)) {
|
||||
let number = '';
|
||||
while (chars[i] && chars[i].match(/\d/)) {
|
||||
number += chars[i];
|
||||
i++;
|
||||
}
|
||||
tokens.push({ type: 'number', value: number });
|
||||
continue;
|
||||
}
|
||||
// decimal
|
||||
if (isDecimalChar(char)) {
|
||||
tokens.push({ type: 'decimal', value: char });
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new Error('Invalid character');
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
abstract class ASTNodeCommon {
|
||||
abstract readonly type: string;
|
||||
abstract get value(): number;
|
||||
}
|
||||
|
||||
type ASTNode = NumberNode | OperatorNode | GroupNode | NegationNode;
|
||||
|
||||
class NumberNode extends ASTNodeCommon {
|
||||
private _value: number;
|
||||
type = 'numbernode';
|
||||
constructor(numValue: number) {
|
||||
super();
|
||||
this._value = numValue;
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
static fromToken(token: NumberToken): NumberNode {
|
||||
return new NumberNode(parseFloat(token.value));
|
||||
}
|
||||
|
||||
static fromTokens(tokens: [NumberToken, DecimalToken, NumberToken]): NumberNode {
|
||||
return new NumberNode(parseFloat(tokens[0].value + '.' + tokens[2].value));
|
||||
}
|
||||
}
|
||||
|
||||
class OperatorNode extends ASTNodeCommon {
|
||||
type = 'operatornode';
|
||||
constructor(public operator: OperatorChars, public left: ASTNode, public right: ASTNode) {
|
||||
super();
|
||||
}
|
||||
|
||||
get value(): number {
|
||||
const left = this.left.value as number;
|
||||
const right = this.right.value as number;
|
||||
switch (this.operator) {
|
||||
case '+':
|
||||
return left + right;
|
||||
case '-':
|
||||
return left - right;
|
||||
case '*':
|
||||
return left * right;
|
||||
case '/':
|
||||
return left / right;
|
||||
default:
|
||||
throw new Error('Invalid operator');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GroupNode extends ASTNodeCommon {
|
||||
type = 'groupnode';
|
||||
|
||||
constructor(public child: ASTNode) {
|
||||
super();
|
||||
}
|
||||
|
||||
get value(): number {
|
||||
return this.child.value as number;
|
||||
}
|
||||
|
||||
static fromTokens(tokens: (Token | ASTNode)[]): GroupNode {
|
||||
const node = parse(tokens);
|
||||
return new GroupNode(node);
|
||||
}
|
||||
}
|
||||
class NegationNode extends ASTNodeCommon {
|
||||
type = 'negationnode';
|
||||
|
||||
constructor(public child: NumberNode | GroupNode) {
|
||||
super();
|
||||
}
|
||||
|
||||
get value(): number {
|
||||
return -(this.child.value as number);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms number tokens into NumberNodes
|
||||
*/
|
||||
function parseNumbers(tokens: (Token | ASTNode)[]): (Token | ASTNode)[] {
|
||||
let i = 0;
|
||||
while (i < tokens.length) {
|
||||
const token = tokens[i];
|
||||
|
||||
if (token.type !== 'number') {
|
||||
i++;
|
||||
continue;
|
||||
};
|
||||
|
||||
// check if it is a decimal
|
||||
const nextToken = tokens[i + 1];
|
||||
const nextNextToken = tokens[i + 2];
|
||||
const nextTokenType = nextToken?.type;
|
||||
const nextNextTokenType = nextNextToken?.type;
|
||||
|
||||
if (nextTokenType === 'decimal' && nextNextTokenType === 'number') {
|
||||
// it is a decimal number
|
||||
const numberNode = NumberNode.fromTokens([
|
||||
token as NumberToken,
|
||||
nextToken as DecimalToken,
|
||||
nextNextToken as NumberToken
|
||||
]);
|
||||
tokens.splice(i, 3, numberNode);
|
||||
i += 2;
|
||||
continue;
|
||||
} else {
|
||||
// it is a regular number
|
||||
const numberNode = NumberNode.fromToken(token as NumberToken);
|
||||
tokens.splice(i, 1, numberNode);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches highest level parenthesis and replaces them with GroupNodes.
|
||||
* Only the highest level parenthesis are required because GroupNode will
|
||||
* recursively parse the rest of the expression.
|
||||
*/
|
||||
function parseGroups(tokens: (Token | ASTNode)[]): (Token | ASTNode)[] {
|
||||
// match highest level parenthesis
|
||||
let i = 0;
|
||||
const parenthesisPairs: [number, number][] = [];
|
||||
const startParenthesis: number[] = [];
|
||||
while (i < tokens.length) {
|
||||
const token = tokens[i];
|
||||
|
||||
if (token.type !== 'parenthesis') {
|
||||
i++;
|
||||
continue;
|
||||
};
|
||||
|
||||
// it is a parenthesis
|
||||
if (token.value === '(') {
|
||||
startParenthesis.push(i);
|
||||
} else {
|
||||
const start = startParenthesis.pop();
|
||||
if (startParenthesis.length === 0) {
|
||||
if (start === undefined) {
|
||||
throw new Error('Invalid parenthesis');
|
||||
}
|
||||
parenthesisPairs.push([start, i]);
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
// slice out parenthesis pairs and replace with GroupNode
|
||||
for (const [start, end] of parenthesisPairs) {
|
||||
const groupNode = GroupNode.fromTokens(tokens.slice(start + 1, end));
|
||||
tokens.splice(start, end - start + 1, groupNode);
|
||||
// update pairs after splice
|
||||
for (let i = 0; i < parenthesisPairs.length; i++) {
|
||||
const [start2, end2] = parenthesisPairs[i];
|
||||
parenthesisPairs[i] = [start2 - (end - start), end2 - (end - start)];
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
function parseNegation(tokens: (Token | ASTNode)[]): (Token | ASTNode)[] {
|
||||
let i = 0;
|
||||
while (i < tokens.length) {
|
||||
const token = tokens[i];
|
||||
|
||||
if (token.type !== 'operator') {
|
||||
i++;
|
||||
continue;
|
||||
};
|
||||
|
||||
if (token.value === '-') {
|
||||
const prevToken = tokens[i - 1] as Token;
|
||||
if (!prevToken || isOperatorChar(prevToken.value)) {
|
||||
// it is a negation
|
||||
const child = tokens[i + 1] as NumberNode | GroupNode;
|
||||
const negationNode = new NegationNode(child);
|
||||
tokens.splice(i, 2, negationNode);
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
function parseMultDiv(tokens: (Token | ASTNode)[]): (Token | ASTNode)[] {
|
||||
let i = 0;
|
||||
while (i < tokens.length) {
|
||||
const token = tokens[i];
|
||||
|
||||
if (token.type !== 'operator') {
|
||||
i++;
|
||||
continue;
|
||||
};
|
||||
|
||||
if (token.value === '*' || token.value === '/') {
|
||||
const left = tokens[i - 1] as ASTNode;
|
||||
const right = tokens[i + 1] as ASTNode;
|
||||
const operator = token.value as OperatorChars;
|
||||
const operatorNode = new OperatorNode(operator, left, right);
|
||||
tokens.splice(i - 1, 3, operatorNode);
|
||||
continue;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
function parseAddSub(tokens: (Token | ASTNode)[]): (Token | ASTNode)[] {
|
||||
let i = 0;
|
||||
while (i < tokens.length) {
|
||||
const token = tokens[i];
|
||||
|
||||
if (token.type !== 'operator') {
|
||||
i++;
|
||||
continue;
|
||||
};
|
||||
|
||||
if (token.value === '+' || token.value === '-') {
|
||||
const left = tokens[i - 1] as ASTNode;
|
||||
const right = tokens[i + 1] as ASTNode;
|
||||
const operator = token.value as OperatorChars;
|
||||
const operatorNode = new OperatorNode(operator, left, right);
|
||||
tokens.splice(i - 1, 3, operatorNode);
|
||||
continue;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
export function parse(tokens: (Token | ASTNode)[]): ASTNode {
|
||||
// array that we will save intermediate results in
|
||||
const transform = [...tokens];
|
||||
|
||||
// first pass: parse groups (parenthesis)
|
||||
parseGroups(transform);
|
||||
|
||||
// second pass: parse numbers and decimals
|
||||
parseNumbers(transform);
|
||||
|
||||
// third pass: negation
|
||||
parseNegation(transform);
|
||||
|
||||
// fourth pass: multiplication and division
|
||||
parseMultDiv(transform);
|
||||
|
||||
// fifth pass: addition and subtraction
|
||||
parseAddSub(transform);
|
||||
|
||||
console.log(JSON.stringify(transform[0], null, 2));
|
||||
return transform[0] as ASTNode;
|
||||
}
|
||||
|
||||
export function calc(expression: string): number {
|
||||
// evaluate `expression` and return result
|
||||
return parse(tokenize(expression)).value;
|
||||
}
|
||||
117
2kyu/evaluate-mathematical-expression/tests.ts
Normal file
117
2kyu/evaluate-mathematical-expression/tests.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { calc, tokenize } from './solution';
|
||||
import { expect } from "chai";
|
||||
|
||||
var tests: [string, number][] = [
|
||||
['1+1', 2],
|
||||
['1 - 1', 0],
|
||||
['1* 1', 1],
|
||||
['1 /1', 1],
|
||||
['-123', -123],
|
||||
['123', 123],
|
||||
['2 /2+3 * 4.75- -6', 21.25],
|
||||
['12* 123', 1476],
|
||||
['2 / (2 + 3) * 4.33 - -6', 7.732],
|
||||
['(2 + (2 + 2) / 4) * 3', 9],
|
||||
['(1 - 2) + -(-(-(-4)))', 3],
|
||||
['((2.33 / (2.9+3.5)*4) - -6)', 7.45625]
|
||||
];
|
||||
|
||||
describe("calc", function () {
|
||||
it("should evaluate correctly", () => {
|
||||
tests.forEach(function (m) {
|
||||
var x = calc(m[0]);
|
||||
var y = m[1];
|
||||
expect(x).to.equal(y, 'Expected: "' + m[0] + '" to be ' + y + ' but got ' + x);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("tokenize", function () {
|
||||
it("should tokenize correctly", () => {
|
||||
expect(tokenize('1 + 1')).to.deep.equal([{
|
||||
type: 'number',
|
||||
value: '1'
|
||||
}, {
|
||||
type: 'operator',
|
||||
value: '+'
|
||||
}, {
|
||||
type: 'number',
|
||||
value: '1'
|
||||
}]);
|
||||
expect(tokenize('1 - 1')).to.deep.equal([{
|
||||
type: 'number',
|
||||
value: '1'
|
||||
}, {
|
||||
type: 'operator',
|
||||
value: '-'
|
||||
}, {
|
||||
type: 'number',
|
||||
value: '1'
|
||||
}]);
|
||||
expect(tokenize('1 * 1')).to.deep.equal([{
|
||||
type: 'number',
|
||||
value: '1'
|
||||
}, {
|
||||
type: 'operator',
|
||||
value: '*'
|
||||
}, {
|
||||
type: 'number',
|
||||
value: '1'
|
||||
}]);
|
||||
expect(tokenize('1 / 1')).to.deep.equal([{
|
||||
type: 'number',
|
||||
value: '1'
|
||||
}, {
|
||||
type: 'operator',
|
||||
value: '/'
|
||||
}, {
|
||||
type: 'number',
|
||||
value: '1'
|
||||
}]);
|
||||
expect(tokenize('-3.1415')).to.deep.equal([{
|
||||
type: 'operator',
|
||||
value: '-'
|
||||
}, {
|
||||
type: 'number',
|
||||
value: '3'
|
||||
}, {
|
||||
type: 'decimal',
|
||||
value: '.'
|
||||
}, {
|
||||
type: 'number',
|
||||
value: '1415'
|
||||
}]);
|
||||
expect(tokenize('3.1415')).to.deep.equal([{
|
||||
type: 'number',
|
||||
value: '3'
|
||||
}, {
|
||||
type: 'decimal',
|
||||
value: '.'
|
||||
}, {
|
||||
type: 'number',
|
||||
value: '1415'
|
||||
}]);
|
||||
expect(tokenize('(3 + 5) / 3')).to.deep.equal([{
|
||||
type: 'parenthesis',
|
||||
value: '('
|
||||
}, {
|
||||
type: 'number',
|
||||
value: '3'
|
||||
}, {
|
||||
type: 'operator',
|
||||
value: '+'
|
||||
}, {
|
||||
type: 'number',
|
||||
value: '5'
|
||||
}, {
|
||||
type: 'parenthesis',
|
||||
value: ')'
|
||||
}, {
|
||||
type: 'operator',
|
||||
value: '/'
|
||||
}, {
|
||||
type: 'number',
|
||||
value: '3'
|
||||
}]);
|
||||
});
|
||||
});
|
||||
26
4kyu/smallest-possible-sum/description.md
Normal file
26
4kyu/smallest-possible-sum/description.md
Normal file
@@ -0,0 +1,26 @@
|
||||
Description
|
||||
Given an array X of positive integers, its elements are to be transformed by running the following operation on them as many times as required:
|
||||
|
||||
if X[i] > X[j] then X[i] = X[i] - X[j]
|
||||
|
||||
When no more transformations are possible, return its sum ("smallest possible sum").
|
||||
|
||||
For instance, the successive transformation of the elements of input X = [6, 9, 21] is detailed below:
|
||||
|
||||
X_1 = [6, 9, 12] # -> X_1[2] = X[2] - X[1] = 21 - 9
|
||||
X_2 = [6, 9, 6] # -> X_2[2] = X_1[2] - X_1[0] = 12 - 6
|
||||
X_3 = [6, 3, 6] # -> X_3[1] = X_2[1] - X_2[0] = 9 - 6
|
||||
X_4 = [6, 3, 3] # -> X_4[2] = X_3[2] - X_3[1] = 6 - 3
|
||||
X_5 = [3, 3, 3] # -> X_5[1] = X_4[0] - X_4[1] = 6 - 3
|
||||
The returning output is the sum of the final transformation (here 9).
|
||||
|
||||
Example
|
||||
solution([6, 9, 21]) #-> 9
|
||||
Solution steps:
|
||||
[6, 9, 12] #-> X[2] = 21 - 9
|
||||
[6, 9, 6] #-> X[2] = 12 - 6
|
||||
[6, 3, 6] #-> X[1] = 9 - 6
|
||||
[6, 3, 3] #-> X[2] = 6 - 3
|
||||
[3, 3, 3] #-> X[1] = 6 - 3
|
||||
Additional notes:
|
||||
There are performance tests consisted of very big numbers and arrays of size at least 30000. Please write an efficient algorithm to prevent timeout.
|
||||
59
4kyu/smallest-possible-sum/solution.ts
Normal file
59
4kyu/smallest-possible-sum/solution.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
// idea: do not ever sort
|
||||
// simply keep looping from left to right and back to left
|
||||
// whenever a smaller number is found,
|
||||
// subtract as many multiples of that number from the rest of the array
|
||||
|
||||
const subtractMultiples = (a: number, b: number): number => {
|
||||
// subtract a from b as many times as possible
|
||||
const mod = b % a;
|
||||
if (mod === 0) {
|
||||
// figure out how many times a can be subtracted from b
|
||||
const div = b / a - 1;
|
||||
return b - a * div;
|
||||
} else {
|
||||
return mod;
|
||||
}
|
||||
}
|
||||
|
||||
const operate = (arr: number[]): boolean => {
|
||||
|
||||
let step = 1;
|
||||
let i = 0;
|
||||
let done = false;
|
||||
let hasChangedDuringLoop = false;
|
||||
while (!done) {
|
||||
if (i + step >= arr.length || i + step < 0) {
|
||||
// reverse direction
|
||||
step = -step;
|
||||
if (!hasChangedDuringLoop) {
|
||||
done = true;
|
||||
}
|
||||
hasChangedDuringLoop = false;
|
||||
}
|
||||
|
||||
const first = arr[i];
|
||||
const next = arr[i + step];
|
||||
|
||||
if (first > next) {
|
||||
arr[i] = subtractMultiples(next, first);
|
||||
hasChangedDuringLoop = true;
|
||||
} else if (first < next) {
|
||||
arr[i + step] = subtractMultiples(first, next);
|
||||
hasChangedDuringLoop = true;
|
||||
}
|
||||
|
||||
i += step;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function solution(numbers: number[]): number {
|
||||
let done = false;
|
||||
while (!done) {
|
||||
done = operate(numbers);
|
||||
}
|
||||
|
||||
// sum the numbers
|
||||
return numbers.reduce((acc, num) => acc + num, 0);
|
||||
}
|
||||
16
4kyu/smallest-possible-sum/tests.ts
Normal file
16
4kyu/smallest-possible-sum/tests.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
// See https://www.chaijs.com for how to use Chai.
|
||||
import { assert } from "chai";
|
||||
|
||||
import { solution } from "./solution";
|
||||
|
||||
describe('Example test', function () {
|
||||
it('[6,9,12]', () => assert.strictEqual(solution([6, 9, 21]), 9));
|
||||
it('[1,21,55]', () => assert.strictEqual(solution([1, 21, 55]), 3));
|
||||
it('[3,13,23,7,83]', () => assert.strictEqual(solution([3, 13, 23, 7, 83]), 5));
|
||||
it('[4,16,24]', () => assert.strictEqual(solution([4, 16, 24]), 12));
|
||||
it('[30,12]', () => assert.strictEqual(solution([30, 12]), 12));
|
||||
it('[60,12,96,48,60,24,72,36,72,72,48]', () => assert.strictEqual(solution([60, 12, 96, 48, 60, 24, 72, 36, 72, 72, 48]), 132));
|
||||
it('[71,71,71,71,71,71,71,71,71,71,71,71,71]', () => assert.strictEqual(solution([71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71]), 71 * 13));
|
||||
it('[11,22]', () => assert.strictEqual(solution([11,22]), 22));
|
||||
it('[9]', () => assert.strictEqual(solution([9]), 9));
|
||||
});
|
||||
25
5kyu/a-chain-adding-function/description.md
Normal file
25
5kyu/a-chain-adding-function/description.md
Normal file
@@ -0,0 +1,25 @@
|
||||
We want to create a function that will add numbers together when called in succession.
|
||||
```ts
|
||||
add(1)(2); // == 3
|
||||
```
|
||||
We also want to be able to continue to add numbers to our chain.
|
||||
```ts
|
||||
add(1)(2)(3); // == 6
|
||||
add(1)(2)(3)(4); // == 10
|
||||
add(1)(2)(3)(4)(5); // == 15
|
||||
```
|
||||
and so on.
|
||||
|
||||
A single call should be equal to the number passed in.
|
||||
```ts
|
||||
add(1); // == 1
|
||||
```
|
||||
We should be able to store the returned values and reuse them.
|
||||
```ts
|
||||
var addTwo = add(2);
|
||||
addTwo; // == 2
|
||||
addTwo + 5; // == 7
|
||||
addTwo(3); // == 5
|
||||
addTwo(3)(5); // == 10
|
||||
```
|
||||
We can assume any number being passed in will be valid whole number.
|
||||
8
5kyu/a-chain-adding-function/solution.ts
Normal file
8
5kyu/a-chain-adding-function/solution.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
export default function add(x: number) {
|
||||
const sum = (y: number): any => add(x + y);
|
||||
// set .valueOf to return the current sum when the object is coerced to a primitive
|
||||
// for example, when it is compared to a number
|
||||
sum.valueOf = () => x;
|
||||
return sum;
|
||||
}
|
||||
14
5kyu/a-chain-adding-function/tests.ts
Normal file
14
5kyu/a-chain-adding-function/tests.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import add from './solution';
|
||||
import {assert} from "chai";
|
||||
|
||||
describe('solution', () => {
|
||||
it('should work when called once', () => {
|
||||
assert(add(1) == 1);
|
||||
});
|
||||
it('should work when called twice', () => {
|
||||
assert(add(1)(2) == 3);
|
||||
});
|
||||
it('should work when called 5 times', () => {
|
||||
assert(add(1)(2)(3)(4)(5) == 15);
|
||||
});
|
||||
});
|
||||
27
5kyu/buddy-pairs/description.md
Normal file
27
5kyu/buddy-pairs/description.md
Normal file
@@ -0,0 +1,27 @@
|
||||
Buddy pairs
|
||||
You know what divisors of a number are. The divisors of a positive integer n are said to be proper when you consider only the divisors other than n itself. In the following description, divisors will mean proper divisors. For example for 100 they are 1, 2, 4, 5, 10, 20, 25, and 50.
|
||||
|
||||
Let s(n) be the sum of these proper divisors of n. Call buddy two positive integers such that the sum of the proper divisors of each number is one more than the other number:
|
||||
|
||||
(n, m) are a pair of buddy if s(m) = n + 1 and s(n) = m + 1
|
||||
|
||||
For example 48 & 75 is such a pair:
|
||||
|
||||
Divisors of 48 are: 1, 2, 3, 4, 6, 8, 12, 16, 24 --> sum: 76 = 75 + 1
|
||||
Divisors of 75 are: 1, 3, 5, 15, 25 --> sum: 49 = 48 + 1
|
||||
Task
|
||||
Given two positive integers start and limit, the function buddy(start, limit) should return the first pair (n m) of buddy pairs such that n (positive integer) is between start (inclusive) and limit (inclusive); m can be greater than limit and has to be greater than n
|
||||
|
||||
If there is no buddy pair satisfying the conditions, then return "Nothing" or (for Go lang) nil or (for Dart) null; (for Lua, Pascal, Perl, D) [-1, -1]; (for Erlang {-1, -1}).
|
||||
|
||||
Examples
|
||||
(depending on the languages)
|
||||
|
||||
buddy(10, 50) returns [48, 75]
|
||||
buddy(48, 50) returns [48, 75]
|
||||
or
|
||||
buddy(10, 50) returns "(48 75)"
|
||||
buddy(48, 50) returns "(48 75)"
|
||||
Notes
|
||||
for C: The returned string will be free'd.
|
||||
See more examples in "Sample Tests:" of your language.
|
||||
33
5kyu/buddy-pairs/solution.ts
Normal file
33
5kyu/buddy-pairs/solution.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
|
||||
const properDivisors = (n: number): number[] => {
|
||||
const divs: number[] = [];
|
||||
for (let i = 1; i <= Math.sqrt(n); i++) {
|
||||
if (n % i === 0) {
|
||||
divs.push(i);
|
||||
const otherDivisor = n / i;
|
||||
if (otherDivisor !== i && otherDivisor !== n) {
|
||||
divs.push(otherDivisor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return divs;
|
||||
}
|
||||
|
||||
const sumOfDivisors = (n: number): number => {
|
||||
const divs = properDivisors(n)
|
||||
return divs.reduce((acc, div) => acc + div, 0);
|
||||
}
|
||||
|
||||
export function buddy(start: number, limit: number): number[] {
|
||||
for (let n = start; n <= limit; n++) {
|
||||
const sumN = sumOfDivisors(n);
|
||||
const m = sumN - 1;
|
||||
const sumM = sumOfDivisors(m);
|
||||
if (m > n && sumM === n + 1) {
|
||||
return [n, m];
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
19
5kyu/buddy-pairs/tests.ts
Normal file
19
5kyu/buddy-pairs/tests.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { assert } from "chai";
|
||||
import { buddy } from "./solution";
|
||||
|
||||
|
||||
describe("buddy", function() {
|
||||
|
||||
function testing(start: number, limit: number, expect:number[]) {
|
||||
let actual = buddy(start, limit);
|
||||
assert.deepEqual(actual, expect);
|
||||
}
|
||||
|
||||
it("Basic tests", function() {
|
||||
testing(10, 50, [48, 75] );
|
||||
testing(1071625, 1103735, [1081184, 1331967] );
|
||||
testing(57345, 90061, [62744, 75495] );
|
||||
testing(2382, 3679, [] );
|
||||
|
||||
})
|
||||
});
|
||||
89
5kyu/closest-and-smallest/description.md
Normal file
89
5kyu/closest-and-smallest/description.md
Normal file
@@ -0,0 +1,89 @@
|
||||
Input
|
||||
a string strng of n positive numbers (n = 0 or n >= 2)
|
||||
Let us call weight of a number the sum of its digits. For example 99 will have "weight" 18, 100 will have "weight" 1.
|
||||
|
||||
Two numbers are "close" if the difference of their weights is small.
|
||||
|
||||
Task:
|
||||
For each number in strng calculate its "weight" and then find two numbers of strng that have:
|
||||
|
||||
the smallest difference of weights ie that are the closest
|
||||
with the smallest weights
|
||||
and with the smallest indices (or ranks, numbered from 0) in strng
|
||||
Output:
|
||||
an array of two arrays, each subarray in the following format:
|
||||
[number-weight, index in strng of the corresponding number, original corresponding number in strng]
|
||||
|
||||
or a pair of two subarrays (Haskell, Clojure, FSharp) or an array of tuples (Elixir, C++)
|
||||
|
||||
or a (char*) in C or a string in some other languages mimicking an array of two subarrays or a string
|
||||
|
||||
or a matrix in R (2 rows, 3 columns, no columns names)
|
||||
|
||||
The two subarrays are sorted in ascending order by their number weights if these weights are different, by their indexes in the string if they have the same weights.
|
||||
|
||||
Examples:
|
||||
Let us call that function closest
|
||||
|
||||
strng = "103 123 4444 99 2000"
|
||||
the weights are 4, 6, 16, 18, 2 (ie 2, 4, 6, 16, 18)
|
||||
|
||||
closest should return [[2, 4, 2000], [4, 0, 103]] (or ([2, 4, 2000], [4, 0, 103])
|
||||
or [{2, 4, 2000}, {4, 0, 103}] or ... depending on the language)
|
||||
because 2000 and 103 have for weight 2 and 4, their indexes in strng are 4 and 0.
|
||||
The smallest difference is 2.
|
||||
4 (for 103) and 6 (for 123) have a difference of 2 too but they are not
|
||||
the smallest ones with a difference of 2 between their weights.
|
||||
....................
|
||||
|
||||
strng = "80 71 62 53"
|
||||
All the weights are 8.
|
||||
closest should return [[8, 0, 80], [8, 1, 71]]
|
||||
71 and 62 have also:
|
||||
- the smallest weights (which is 8 for all)
|
||||
- the smallest difference of weights (which is 0 for all pairs)
|
||||
- but not the smallest indices in strng.
|
||||
....................
|
||||
|
||||
strng = "444 2000 445 544"
|
||||
the weights are 12, 2, 13, 13 (ie 2, 12, 13, 13)
|
||||
|
||||
closest should return [[13, 2, 445], [13, 3, 544]] or ([13, 2, 445], [13, 3, 544])
|
||||
or [{13, 2, 445}, {13, 3, 544}] or ...
|
||||
444 and 2000 have the smallest weights (12 and 2) but not the smallest difference of weights;
|
||||
they are not the closest.
|
||||
Here the smallest difference is 0 and in the result the indexes are in ascending order.
|
||||
...................
|
||||
|
||||
closest("444 2000 445 644 2001 1002") --> [[3, 4, 2001], [3, 5, 1002]] or ([3, 4, 2001],
|
||||
[3, 5, 1002]]) or [{3, 4, 2001}, {3, 5, 1002}] or ...
|
||||
Here the smallest difference is 0 and in the result the indexes are in ascending order.
|
||||
...................
|
||||
|
||||
closest("239382 162 254765 182 485944 468751 49780 108 54")
|
||||
The weights are: 27, 9, 29, 11, 34, 31, 28, 9, 9.
|
||||
closest should return [[9, 1, 162], [9, 7, 108]] or ([9, 1, 162], [9, 7, 108])
|
||||
or [{9, 1, 162}, {9, 7, 108}] or ...
|
||||
108 and 54 have the smallest difference of weights too, they also have
|
||||
the smallest weights but they don't have the smallest ranks in the original string.
|
||||
..................
|
||||
|
||||
closest("54 239382 162 254765 182 485944 468751 49780 108")
|
||||
closest should return [[9, 0, 54], [9, 2, 162]] or ([9, 0, 54], [9, 2, 162])
|
||||
or [{9, 0, 54}, {9, 2, 162}] or ...
|
||||
Notes :
|
||||
If n == 0 closest("") should return []
|
||||
|
||||
or ([], []) in Haskell, Clojure, FSharp
|
||||
|
||||
or [{}, {}] in Elixir or '(() ()) in Racket
|
||||
|
||||
or {{0,0,0}, {0,0,0}} in C++
|
||||
|
||||
or "[(), ()]" in Go, Nim,
|
||||
|
||||
or "{{0,0,0}, {0,0,0}}" in C, NULL in R
|
||||
|
||||
or "" in Perl.
|
||||
|
||||
See Example tests for the format of the results in your language.
|
||||
60
5kyu/closest-and-smallest/solution.ts
Normal file
60
5kyu/closest-and-smallest/solution.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
const weight = (s: string): number => s.split('').reduce((acc, cur) => acc + parseInt(cur), 0);
|
||||
|
||||
const sortWeightInfo = <T extends WeightInfo | DiffInfo>(a: T, b: T): number => {
|
||||
if (a[0] !== b[0]) {
|
||||
return a[0] - b[0];
|
||||
} else if (a[1] !== b[1]) {
|
||||
return a[1] - b[1];
|
||||
} else {
|
||||
return a[2] - b[2];
|
||||
}
|
||||
}
|
||||
|
||||
type WeightInfo = [
|
||||
number, // weight
|
||||
number, // index
|
||||
number // number
|
||||
];
|
||||
type DiffInfo = [
|
||||
number, // weight difference
|
||||
number, // weight sum
|
||||
number, // index sum
|
||||
number, // index of first number
|
||||
number // index of second number
|
||||
];
|
||||
|
||||
export function closest(strng: string): number[][] {
|
||||
if (strng === '') return [];
|
||||
|
||||
const weights: WeightInfo[] = strng
|
||||
.split(' ')
|
||||
.map((s, i) => [weight(s), i, parseInt(s)]);
|
||||
|
||||
// calculate all weight differences, weight sum, and index sum
|
||||
const diffs: DiffInfo[] = [];
|
||||
for (const [weight, index, num] of weights) {
|
||||
for (let i = index + 1; i < weights.length; i++) {
|
||||
if(i >= weights.length) break;
|
||||
|
||||
const [weight2, index2, num2] = weights[i];
|
||||
const weightDiff = Math.abs(weight - weight2);
|
||||
const weightSum = weight + weight2;
|
||||
const indexSum = index + index2;
|
||||
diffs.push([weightDiff, weightSum, indexSum, index, index2]);
|
||||
}
|
||||
}
|
||||
|
||||
// sort by weight difference, weight sum, and index sum
|
||||
diffs.sort(sortWeightInfo);
|
||||
|
||||
const minimumDiff = diffs[0]
|
||||
const firstNumIndex = minimumDiff[3];
|
||||
const secondNumIndex = minimumDiff[4];
|
||||
|
||||
const result = [
|
||||
weights[firstNumIndex],
|
||||
weights[secondNumIndex]
|
||||
].sort(sortWeightInfo)
|
||||
|
||||
return result;
|
||||
}
|
||||
14
5kyu/closest-and-smallest/tests.ts
Normal file
14
5kyu/closest-and-smallest/tests.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { closest } from './solution';
|
||||
import { assert } from "chai";
|
||||
// chai.config.truncateThreshold = 0;
|
||||
|
||||
describe("Fixed Tests", function() {
|
||||
it("Basic tests closest", function() {
|
||||
assert.deepEqual(closest(""), []);
|
||||
assert.deepEqual(closest("103 123 4444 99 2000"), [[2, 4, 2000], [4, 0, 103]]);
|
||||
assert.deepEqual(closest("456899 50 11992 176 272293 163 389128 96 290193 85 52"), [[13, 9, 85], [14, 3, 176]]);
|
||||
assert.deepEqual(closest("239382 162 254765 182 485944 134 468751 62 49780 108 54"), [[8, 5, 134], [8, 7, 62]]);
|
||||
assert.deepEqual(closest("241259 154 155206 194 180502 147 300751 200 406683 37 57"), [[10, 1, 154], [10, 9, 37]]);
|
||||
assert.deepEqual(closest("89998 187 126159 175 338292 89 39962 145 394230 167 1"), [[13, 3, 175], [14, 9, 167]]);
|
||||
});
|
||||
});
|
||||
32
5kyu/common-denominators/description.md
Normal file
32
5kyu/common-denominators/description.md
Normal file
@@ -0,0 +1,32 @@
|
||||
Common denominators
|
||||
|
||||
You will have a list of rationals in the form
|
||||
|
||||
`{ {numer_1, denom_1} , ... {numer_n, denom_n} } `
|
||||
or
|
||||
`[ [numer_1, denom_1] , ... [numer_n, denom_n] ] `
|
||||
or
|
||||
`[ (numer_1, denom_1) , ... (numer_n, denom_n) ] `
|
||||
where all numbers are positive ints. You have to produce a result in the form:
|
||||
|
||||
`(N_1, D) ... (N_n, D)`
|
||||
or
|
||||
`[ [N_1, D] ... [N_n, D] ]`
|
||||
or
|
||||
`[ (N_1', D) , ... (N_n, D) ]`
|
||||
or
|
||||
`{{N_1, D} ... {N_n, D}}`
|
||||
or
|
||||
`"(N_1, D) ... (N_n, D)"`
|
||||
depending on the language (See Example tests) in which D is as small as possible and
|
||||
|
||||
`N_1/D == numer_1/denom_1 ... N_n/D == numer_n,/denom_n.`
|
||||
Example:
|
||||
`convertFracs [(1, 2), (1, 3), (1, 4)] `shouldBe` [(6, 12), (4, 12), (3, 12)]`
|
||||
Note:
|
||||
Due to the fact that the first translations were written long ago - more than 6 years - these first translations have only irreducible fractions.
|
||||
|
||||
Newer translations have some reducible fractions. To be on the safe side it is better to do a bit more work by simplifying fractions even if they don't have to be.
|
||||
|
||||
Note for Bash:
|
||||
input is a string, e.g `"2,4,2,6,2,8"` output is then `"6 12 4 12 3 12"`
|
||||
32
5kyu/common-denominators/solution.ts
Normal file
32
5kyu/common-denominators/solution.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Finds the greatest common divisor of two numbers
|
||||
* using Euclid's algorithm.
|
||||
*/
|
||||
const gcd = (a: number, b: number): number => {
|
||||
return a === 0 ? b : gcd(b % a, a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the least common multiple of two numbers.
|
||||
*/
|
||||
const lcm = (a: number, b: number): number => {
|
||||
return a * b / gcd(a, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplifies a fraction to its simplest form.
|
||||
*/
|
||||
const simplify = (numerator: number, denominator: number): [number, number] => {
|
||||
const divisor = gcd(numerator, denominator);
|
||||
return [numerator / divisor, denominator / divisor];
|
||||
}
|
||||
|
||||
export const convertFrac = (lst: [number, number][]): string => {
|
||||
const simplifiedList = lst.map(([numerator, denominator]) => simplify(numerator, denominator));
|
||||
// find the least common multiple of all the denominators
|
||||
const lcmDenominator = simplifiedList.reduce((acc, [_, denominator]) => lcm(acc, denominator), 1);
|
||||
return simplifiedList
|
||||
.map(([numerator, denominator]) => [numerator * lcmDenominator / denominator, lcmDenominator])
|
||||
.map(([numerator, denominator]) => `(${numerator},${denominator})`)
|
||||
.join('');
|
||||
}
|
||||
19
5kyu/common-denominators/tests.ts
Normal file
19
5kyu/common-denominators/tests.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { convertFrac } from './solution';
|
||||
import { assert } from "chai";
|
||||
|
||||
describe("Fixed Tests", function () {
|
||||
it("Basic tests convertFrac", function () {
|
||||
assert.strictEqual(
|
||||
convertFrac([[1, 2], [1, 3], [1, 4]]),
|
||||
"(6,12)(4,12)(3,12)"
|
||||
);
|
||||
assert.strictEqual(
|
||||
convertFrac([[69, 130], [87, 1310], [3, 4]]),
|
||||
"(18078,34060)(2262,34060)(25545,34060)"
|
||||
);
|
||||
assert.strictEqual(
|
||||
convertFrac([[1, 2], [4, 5], [3, 4], [6, 9], [7, 10]]),
|
||||
"(30,60)(48,60)(45,60)(40,60)(42,60)"
|
||||
);
|
||||
});
|
||||
});
|
||||
8
5kyu/count-ip-addresses/description.md
Normal file
8
5kyu/count-ip-addresses/description.md
Normal file
@@ -0,0 +1,8 @@
|
||||
Implement a function that receives two IPv4 addresses, and returns the number of addresses between them (including the first one, excluding the last one).
|
||||
|
||||
All inputs will be valid IPv4 addresses in the form of strings. The last address will always be greater than the first one.
|
||||
|
||||
Examples
|
||||
* With input "10.0.0.0", "10.0.0.50" => return 50
|
||||
* With input "10.0.0.0", "10.0.1.0" => return 256
|
||||
* With input "20.0.0.10", "20.0.1.0" => return 246
|
||||
17
5kyu/count-ip-addresses/solution.ts
Normal file
17
5kyu/count-ip-addresses/solution.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Regards an IP address as a number with 4 digits.
|
||||
* Each digit can be between 0 and 255.
|
||||
* The result of this function is the total number of possible IPs 'lower' than the given IP.
|
||||
*/
|
||||
const ipToNum = (ip: string): number => {
|
||||
const digits = ip.split('.').map(Number) as [number, number, number, number];
|
||||
console.log(digits);
|
||||
if(digits.length !== 4) {
|
||||
throw new Error('Invalid IP address');
|
||||
}
|
||||
return digits.reduce((sum, digit) => sum * 256 + digit, 0);
|
||||
}
|
||||
|
||||
export function ipsBetween(start: string, end: string): number {
|
||||
return ipToNum(end) - ipToNum(start);
|
||||
}
|
||||
11
5kyu/count-ip-addresses/tests.ts
Normal file
11
5kyu/count-ip-addresses/tests.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
// See https://www.chaijs.com for how to use Chai.
|
||||
import { assert } from "chai";
|
||||
import { ipsBetween } from "./solution";
|
||||
|
||||
// TODO Add your tests here
|
||||
describe("example", function() {
|
||||
it("test", function() {
|
||||
assert.equal(ipsBetween("10.0.0.0", "10.0.0.50"), 50, 'ipsBetween("10.0.0.0", "10.0.0.50")');
|
||||
assert.equal(ipsBetween("20.0.0.10", "20.0.1.0"), 246, 'ipsBetween("20.0.0.10", "20.0.1.0")');
|
||||
});
|
||||
});
|
||||
21
5kyu/going-to-zero-or-to-infinity/description.md
Normal file
21
5kyu/going-to-zero-or-to-infinity/description.md
Normal file
@@ -0,0 +1,21 @@
|
||||
Consider the following numbers (where n! is factorial(n)):
|
||||
|
||||
u1 = (1 / 1!) * (1!)
|
||||
u2 = (1 / 2!) * (1! + 2!)
|
||||
u3 = (1 / 3!) * (1! + 2! + 3!)
|
||||
...
|
||||
un = (1 / n!) * (1! + 2! + 3! + ... + n!)
|
||||
Which will win: 1 / n! or (1! + 2! + 3! + ... + n!)?
|
||||
|
||||
Are these numbers going to 0 because of 1/n! or to infinity due to the sum of factorials or to another number?
|
||||
|
||||
Task
|
||||
Calculate (1 / n!) * (1! + 2! + 3! + ... + n!) for a given n, where n is an integer greater or equal to 1.
|
||||
|
||||
Your result should be within 10^-6 of the expected one.
|
||||
|
||||
Remark
|
||||
Keep in mind that factorials grow rather rapidly, and you need to handle large inputs.
|
||||
|
||||
Hint
|
||||
You could try to simplify the expression.
|
||||
36
5kyu/going-to-zero-or-to-infinity/solution.ts
Normal file
36
5kyu/going-to-zero-or-to-infinity/solution.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
|
||||
export function factorial(n: number): number {
|
||||
let result = n;
|
||||
for (let i = n - 1; i > 0; i--) {
|
||||
result *= i;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// inclusive both sides
|
||||
export function partialFactorial(to: number, from: number): number {
|
||||
if (to === from) return 1;
|
||||
|
||||
if (to > from) {
|
||||
throw new Error('to must be less than from');
|
||||
}
|
||||
|
||||
let result = from;
|
||||
for (let i = from - 1; i >= to; i--) {
|
||||
result *= i;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function going(n: number): number {
|
||||
// your code
|
||||
let result = 1 / n;
|
||||
|
||||
for (let i = 2; i <= n; i++) {
|
||||
result += 1 / partialFactorial(i, n);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
14
5kyu/going-to-zero-or-to-infinity/tests.ts
Normal file
14
5kyu/going-to-zero-or-to-infinity/tests.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import {going} from './solution'
|
||||
import {assert} from "chai";
|
||||
|
||||
function testing(n:number, expected:number) {
|
||||
assert.approximately(going(n), expected, 1e-6)
|
||||
}
|
||||
|
||||
describe("Fixed Tests going", function() {
|
||||
it("Basic tests", function() {
|
||||
testing(5, 1.275);
|
||||
testing(6, 1.2125);
|
||||
testing(7, 1.173214);
|
||||
});
|
||||
});
|
||||
25
5kyu/k-primes/description.md
Normal file
25
5kyu/k-primes/description.md
Normal file
@@ -0,0 +1,25 @@
|
||||
A natural number is called k-prime if it has exactly k prime factors, counted with multiplicity. For example:
|
||||
|
||||
k = 2 --> 4, 6, 9, 10, 14, 15, 21, 22, ...
|
||||
k = 3 --> 8, 12, 18, 20, 27, 28, 30, ...
|
||||
k = 5 --> 32, 48, 72, 80, 108, 112, ...
|
||||
A natural number is thus prime if and only if it is 1-prime.
|
||||
|
||||
Task:
|
||||
Complete the function count_Kprimes (or countKprimes, count-K-primes, kPrimes) which is given parameters k, start, end (or nd) and returns an array (or a list or a string depending on the language - see "Solution" and "Sample Tests") of the k-primes between start (inclusive) and end (inclusive).
|
||||
|
||||
Example:
|
||||
countKprimes(5, 500, 600) --> [500, 520, 552, 567, 588, 592, 594]
|
||||
Notes:
|
||||
|
||||
The first function would have been better named: findKprimes or kPrimes :-)
|
||||
In C some helper functions are given (see declarations in 'Solution').
|
||||
For Go: nil slice is expected when there are no k-primes between start and end.
|
||||
Second Task: puzzle (not for Shell)
|
||||
Given a positive integer s, find the total number of solutions of the equation a + b + c = s, where a is 1-prime, b is 3-prime, and c is 7-prime.
|
||||
|
||||
Call this function puzzle(s).
|
||||
|
||||
Examples:
|
||||
puzzle(138) --> 1 because [2 + 8 + 128] is the only solution
|
||||
puzzle(143) --> 2 because [3 + 12 + 128] and [7 + 8 + 128] are the solutions
|
||||
106
5kyu/k-primes/solution.ts
Normal file
106
5kyu/k-primes/solution.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
|
||||
// sieve of eratosthenes
|
||||
const primesBelow = (n: number): number[] => {
|
||||
const primes: number[] = [];
|
||||
const isPrime: boolean[] = Array(n).fill(true);
|
||||
isPrime[0] = false;
|
||||
isPrime[1] = false;
|
||||
for (let i = 2; i < n; i++) {
|
||||
if (isPrime[i]) {
|
||||
primes.push(i);
|
||||
for (let j = i * i; j < n; j += i) {
|
||||
isPrime[j] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return primes;
|
||||
}
|
||||
|
||||
// prime factorization using trial division
|
||||
const primeFactors = (n: number, primesList: number[]): number[] => {
|
||||
const factors: number[] = [];
|
||||
const sqrtN = Math.sqrt(n);
|
||||
const primes = [...primesList];
|
||||
let currPrime = primes.shift() as number;
|
||||
while (currPrime <= sqrtN) {
|
||||
if (n % currPrime === 0) {
|
||||
factors.push(currPrime);
|
||||
n /= currPrime;
|
||||
} else {
|
||||
currPrime = primes.shift() as number;
|
||||
}
|
||||
}
|
||||
|
||||
if (n > 1) {
|
||||
factors.push(n);
|
||||
}
|
||||
|
||||
return factors;
|
||||
}
|
||||
|
||||
const generatePrimeFactors = (upper: number): number[] => {
|
||||
// this stores the number of times a number is marked as a prime factor
|
||||
const factors: number[] = Array(upper + 1).fill(0);
|
||||
|
||||
for (let p = 2; p <= upper; p++) {
|
||||
if (factors[p] === 0) {
|
||||
// p is prime
|
||||
// mark all multiples of p
|
||||
for (let j = p; j <= upper; j += p) {
|
||||
// also mark all multiples of p^2, p^3, ...
|
||||
for(let k = j; k <= upper; k *= p) {
|
||||
factors[k]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return factors;
|
||||
}
|
||||
|
||||
// create static class which can be called a single time for a very large range
|
||||
export class KPrimes {
|
||||
private static limit = 10001000;
|
||||
private static primeFactors = generatePrimeFactors(KPrimes.limit);
|
||||
|
||||
public static countKprimes(k: number, start: number, nd: number): number[] {
|
||||
if(nd > KPrimes.limit) {
|
||||
throw new Error(`nd: ${nd} is greater than the limit: ${KPrimes.limit}`);
|
||||
}
|
||||
// filter out the numbers that have k prime factors
|
||||
const result = [];
|
||||
for (let i = start; i <= nd; i++) {
|
||||
if (KPrimes.primeFactors[i] === k) {
|
||||
result.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// using modified sieve of eratosthenes
|
||||
export const countKprimes = (k: number, start: number, nd: number): number[] => {
|
||||
return KPrimes.countKprimes(k, start, nd);
|
||||
}
|
||||
|
||||
export const puzzle = (s: number): number => {
|
||||
const onePrimes = countKprimes(1, 0, s);
|
||||
const threePrimes = countKprimes(3, 0, s);
|
||||
const sevenPrimes = countKprimes(7, 0, s);
|
||||
|
||||
let count = 0;
|
||||
|
||||
onePrimes.forEach(onePrime => {
|
||||
threePrimes.forEach(threePrime => {
|
||||
sevenPrimes.forEach(sevenPrime => {
|
||||
if (onePrime + threePrime + sevenPrime === s) {
|
||||
count++;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return count;
|
||||
}
|
||||
21
5kyu/k-primes/tests.ts
Normal file
21
5kyu/k-primes/tests.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { countKprimes, puzzle } from './solution'
|
||||
import { assert, config } from "chai";
|
||||
config.truncateThreshold = 0
|
||||
|
||||
function testing(actual: number | number[], expected: number | number[]) {
|
||||
assert.deepEqual(actual, expected);
|
||||
}
|
||||
|
||||
describe("Fixed Tests", function () {
|
||||
it("Basic tests countKprimes", function () {
|
||||
testing(countKprimes(2, 0, 100), [4, 6, 9, 10, 14, 15, 21, 22, 25, 26, 33, 34, 35, 38, 39, 46, 49, 51, 55, 57, 58, 62, 65, 69, 74, 77, 82, 85, 86, 87, 91, 93, 94, 95]);
|
||||
testing(countKprimes(3, 0, 100), [8, 12, 18, 20, 27, 28, 30, 42, 44, 45, 50, 52, 63, 66, 68, 70, 75, 76, 78, 92, 98, 99]);
|
||||
testing(countKprimes(5, 1000, 1100), [1020, 1026, 1032, 1044, 1050, 1053, 1064, 1072, 1092, 1100]);
|
||||
testing(countKprimes(5, 500, 600), [500, 520, 552, 567, 588, 592, 594]);
|
||||
});
|
||||
|
||||
it("Basic tests puzzle", function () {
|
||||
testing(puzzle(138), 1);
|
||||
testing(puzzle(143), 2);
|
||||
});
|
||||
});
|
||||
18
5kyu/vector-class/description.md
Normal file
18
5kyu/vector-class/description.md
Normal file
@@ -0,0 +1,18 @@
|
||||
Create a Vector object that supports addition, subtraction, dot products, and norms. So, for example:
|
||||
|
||||
a = new Vector([1, 2, 3])
|
||||
b = new Vector([3, 4, 5])
|
||||
c = new Vector([5, 6, 7, 8])
|
||||
|
||||
a.add(b) # should return a new Vector([4, 6, 8])
|
||||
a.subtract(b) # should return a new Vector([-2, -2, -2])
|
||||
a.dot(b) # should return 1*3 + 2*4 + 3*5 = 26
|
||||
a.norm() # should return sqrt(1^2 + 2^2 + 3^2) = sqrt(14)
|
||||
a.add(c) # throws an error
|
||||
If you try to add, subtract, or dot two vectors with different lengths, you must throw an error!
|
||||
|
||||
Also provide:
|
||||
|
||||
a toString method, so that using the vectors from above, a.toString() === '(1,2,3)' (in Python, this is a __str__ method, so that str(a) == '(1,2,3)')
|
||||
an equals method, to check that two vectors that have the same components are equal
|
||||
Note: the test cases will utilize the user-provided equals method.
|
||||
40
5kyu/vector-class/solution.ts
Normal file
40
5kyu/vector-class/solution.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
export class Vector {
|
||||
private elements: number[];
|
||||
|
||||
constructor(components: number[]) {
|
||||
this.elements = components;
|
||||
}
|
||||
|
||||
public add(vector: Vector): Vector {
|
||||
if (vector.elements.length !== this.elements.length) {
|
||||
throw new Error('Vectors must have the same length');
|
||||
}
|
||||
return new Vector(this.elements.map((element, i) => element + vector.elements[i]));
|
||||
}
|
||||
|
||||
public subtract(vector: Vector): Vector {
|
||||
if (vector.elements.length !== this.elements.length) {
|
||||
throw new Error('Vectors must have the same length');
|
||||
}
|
||||
return new Vector(this.elements.map((element, i) => element - vector.elements[i]));
|
||||
}
|
||||
|
||||
public dot(vector: Vector): number {
|
||||
if (vector.elements.length !== this.elements.length) {
|
||||
throw new Error('Vectors must have the same length');
|
||||
}
|
||||
return this.elements.reduce((sum, element, i) => sum + element * vector.elements[i], 0);
|
||||
}
|
||||
|
||||
public norm(): number {
|
||||
return Math.sqrt(this.elements.reduce((sum, element) => sum + element ** 2, 0));
|
||||
}
|
||||
|
||||
public equals(vector: Vector): boolean {
|
||||
return this.elements.every((element, i) => element === vector.elements[i]);
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return `(${this.elements.join(',')})`;
|
||||
}
|
||||
};
|
||||
37
5kyu/vector-class/tests.ts
Normal file
37
5kyu/vector-class/tests.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { assert } from "chai";
|
||||
import { Vector } from "./solution";
|
||||
|
||||
describe("Tests", () => {
|
||||
it("Simple Equality Test", () => {
|
||||
let a = new Vector([1,2]);
|
||||
let b = new Vector([3,4]);
|
||||
assert.isFalse(a.equals(b));
|
||||
});
|
||||
|
||||
it("Simple Add Test", function() {
|
||||
let a = new Vector([1, 2, 3]);
|
||||
let b = new Vector([3, 4, 5]);
|
||||
assert.isTrue(a.add(b).equals(new Vector([4,6, 8])));
|
||||
})
|
||||
|
||||
it("Simple Norm Test", function () {
|
||||
assert.approximately(new Vector([1,2,3]).norm(), Math.sqrt(14), 0.1);
|
||||
});
|
||||
|
||||
it("Simple Subtract Test", function() {
|
||||
let a = new Vector([1, 2, 3]);
|
||||
let b = new Vector([3, 4, 5]);
|
||||
assert.isTrue(a.subtract(b).equals(new Vector([-2,-2,-2])));
|
||||
});
|
||||
|
||||
it("Simple Dot Test", function() {
|
||||
let a = new Vector([1, 2, 3]);
|
||||
let b = new Vector([3, 4, 5]);
|
||||
assert.strictEqual(a.dot(b), 26);
|
||||
})
|
||||
|
||||
it("Simple ToString Test", function() {
|
||||
let a = new Vector([1, 2, 3]);
|
||||
assert.strictEqual(a.toString(), "(1,2,3)");
|
||||
})
|
||||
});
|
||||
63
6kyu/build-a-pile-of-cubes/description.md
Normal file
63
6kyu/build-a-pile-of-cubes/description.md
Normal file
@@ -0,0 +1,63 @@
|
||||
Your task is to construct a building which will be a pile of n cubes. The cube at the bottom will have a volume of
|
||||
n
|
||||
3
|
||||
n
|
||||
3
|
||||
, the cube above will have volume of
|
||||
(
|
||||
n
|
||||
−
|
||||
1
|
||||
)
|
||||
3
|
||||
(n−1)
|
||||
3
|
||||
and so on until the top which will have a volume of
|
||||
1
|
||||
3
|
||||
1
|
||||
3
|
||||
.
|
||||
|
||||
You are given the total volume m of the building. Being given m can you find the number n of cubes you will have to build?
|
||||
|
||||
The parameter of the function findNb (find_nb, find-nb, findNb, ...) will be an integer m and you have to return the integer n such as
|
||||
n
|
||||
3
|
||||
+
|
||||
(
|
||||
n
|
||||
−
|
||||
1
|
||||
)
|
||||
3
|
||||
+
|
||||
(
|
||||
n
|
||||
−
|
||||
2
|
||||
)
|
||||
3
|
||||
+
|
||||
.
|
||||
.
|
||||
.
|
||||
+
|
||||
1
|
||||
3
|
||||
=
|
||||
m
|
||||
n
|
||||
3
|
||||
+(n−1)
|
||||
3
|
||||
+(n−2)
|
||||
3
|
||||
+...+1
|
||||
3
|
||||
=m if such a n exists or -1 if there is no such n.
|
||||
|
||||
Examples:
|
||||
findNb(1071225) --> 45
|
||||
|
||||
findNb(91716553919377) --> -1
|
||||
10
6kyu/build-a-pile-of-cubes/solution.ts
Normal file
10
6kyu/build-a-pile-of-cubes/solution.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export function findNb(m: number): number {
|
||||
// start with 1 and just keep adding cubes until we reach larger or equal to m
|
||||
let sum = 0;
|
||||
let i = 1;
|
||||
while (sum < m) {
|
||||
sum += i ** 3;
|
||||
i++;
|
||||
}
|
||||
return sum === m ? i - 1 : -1;
|
||||
}
|
||||
11
6kyu/build-a-pile-of-cubes/tests.ts
Normal file
11
6kyu/build-a-pile-of-cubes/tests.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { findNb } from './solution';
|
||||
import { assert } from "chai";
|
||||
|
||||
describe("Fixed Tests nbMonths", function() {
|
||||
it("Basic tests", function() {
|
||||
assert.strictEqual(findNb(4183059834009), 2022);
|
||||
assert.strictEqual(findNb(24723578342962), -1);
|
||||
assert.strictEqual(findNb(135440716410000), 4824);
|
||||
assert.strictEqual(findNb(40539911473216), 3568);
|
||||
});
|
||||
});
|
||||
18
6kyu/find-the-missing-letter/description.md
Normal file
18
6kyu/find-the-missing-letter/description.md
Normal file
@@ -0,0 +1,18 @@
|
||||
Find the missing letter
|
||||
Write a method that takes an array of consecutive (increasing) letters as input and that returns the missing letter in the array.
|
||||
|
||||
You will always get an valid array. And it will be always exactly one letter be missing. The length of the array will always be at least 2.
|
||||
The array will always contain letters in only one case.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
['a','b','c','d','f'] -> 'e'
|
||||
['O','Q','R','S'] -> 'P'
|
||||
```
|
||||
|
||||
(Use the English alphabet with 26 letters!)
|
||||
|
||||
Have fun coding it and please don't forget to vote and rank this kata! :-)
|
||||
|
||||
I have also created other katas. Take a look if you enjoyed this kata!
|
||||
11
6kyu/find-the-missing-letter/solution.ts
Normal file
11
6kyu/find-the-missing-letter/solution.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export function findMissingLetter(array: string[]): string {
|
||||
const alphabet = 'abcdefghijklmnopqrstuvwxyz';
|
||||
// convert array to alphabet index values
|
||||
const indexes = array.map((char) => alphabet.indexOf(char.toLowerCase()));
|
||||
// find the missing index
|
||||
const missingIndex = indexes.find((index, i) => indexes[i + 1] - index > 1) as number;
|
||||
|
||||
const isInputUpperCase = array[0] === array[0].toUpperCase();
|
||||
const missingLetter = alphabet[missingIndex + 1];
|
||||
return isInputUpperCase ? missingLetter.toUpperCase() : missingLetter;
|
||||
}
|
||||
9
6kyu/find-the-missing-letter/tests.ts
Normal file
9
6kyu/find-the-missing-letter/tests.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { findMissingLetter } from './solution';
|
||||
import { assert } from "chai";
|
||||
|
||||
describe("solution", function(){
|
||||
it("exampleTests", function(){
|
||||
assert.equal(findMissingLetter(['a','b','c','d','f']), 'e');
|
||||
assert.equal(findMissingLetter(['O','Q','R','S']), 'P');
|
||||
});
|
||||
});
|
||||
13
6kyu/replace-with-alphabet-position/description.md
Normal file
13
6kyu/replace-with-alphabet-position/description.md
Normal file
@@ -0,0 +1,13 @@
|
||||
Welcome.
|
||||
|
||||
In this kata you are required to, given a string, replace every letter with its position in the alphabet.
|
||||
|
||||
If anything in the text isn't a letter, ignore it and don't return it.
|
||||
|
||||
"a" = 1, "b" = 2, etc.
|
||||
|
||||
Example
|
||||
```
|
||||
Input = "The sunset sets at twelve o' clock."
|
||||
Output = "20 8 5 19 21 14 19 5 20 19 5 20 19 1 20 20 23 5 12 22 5 15 3 12 15 3 11"
|
||||
```
|
||||
8
6kyu/replace-with-alphabet-position/solution.ts
Normal file
8
6kyu/replace-with-alphabet-position/solution.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export function alphabetPosition(text: string): string {
|
||||
const alphabet = 'abcdefghijklmnopqrstuvwxyz';
|
||||
const indexArray = [...text]
|
||||
.map((char) => alphabet.indexOf(char.toLowerCase()))
|
||||
.filter((index) => index !== -1)
|
||||
.map((index) => index + 1);
|
||||
return indexArray.join(' ');
|
||||
}
|
||||
10
6kyu/replace-with-alphabet-position/tests.ts
Normal file
10
6kyu/replace-with-alphabet-position/tests.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { assert } from "chai";
|
||||
|
||||
import { alphabetPosition } from "./solution";
|
||||
|
||||
describe("Tests", () => {
|
||||
it("test", () => {
|
||||
assert.equal(alphabetPosition("The sunset sets at twelve o' clock."), "20 8 5 19 21 14 19 5 20 19 5 20 19 1 20 20 23 5 12 22 5 15 3 12 15 3 11");
|
||||
assert.equal(alphabetPosition("The narwhal bacons at midnight."), "20 8 5 14 1 18 23 8 1 12 2 1 3 15 14 19 1 20 13 9 4 14 9 7 8 20");
|
||||
});
|
||||
});
|
||||
492
package-lock.json
generated
492
package-lock.json
generated
@@ -15,6 +15,7 @@
|
||||
"chai": "^5.1.2",
|
||||
"mocha": "^11.1.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
},
|
||||
@@ -31,6 +32,414 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz",
|
||||
"integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz",
|
||||
"integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz",
|
||||
"integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz",
|
||||
"integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz",
|
||||
"integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz",
|
||||
"integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz",
|
||||
"integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz",
|
||||
"integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz",
|
||||
"integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz",
|
||||
"integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz",
|
||||
"integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz",
|
||||
"integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz",
|
||||
"integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz",
|
||||
"integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz",
|
||||
"integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz",
|
||||
"integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||
@@ -583,6 +992,46 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz",
|
||||
"integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.23.1",
|
||||
"@esbuild/android-arm": "0.23.1",
|
||||
"@esbuild/android-arm64": "0.23.1",
|
||||
"@esbuild/android-x64": "0.23.1",
|
||||
"@esbuild/darwin-arm64": "0.23.1",
|
||||
"@esbuild/darwin-x64": "0.23.1",
|
||||
"@esbuild/freebsd-arm64": "0.23.1",
|
||||
"@esbuild/freebsd-x64": "0.23.1",
|
||||
"@esbuild/linux-arm": "0.23.1",
|
||||
"@esbuild/linux-arm64": "0.23.1",
|
||||
"@esbuild/linux-ia32": "0.23.1",
|
||||
"@esbuild/linux-loong64": "0.23.1",
|
||||
"@esbuild/linux-mips64el": "0.23.1",
|
||||
"@esbuild/linux-ppc64": "0.23.1",
|
||||
"@esbuild/linux-riscv64": "0.23.1",
|
||||
"@esbuild/linux-s390x": "0.23.1",
|
||||
"@esbuild/linux-x64": "0.23.1",
|
||||
"@esbuild/netbsd-x64": "0.23.1",
|
||||
"@esbuild/openbsd-arm64": "0.23.1",
|
||||
"@esbuild/openbsd-x64": "0.23.1",
|
||||
"@esbuild/sunos-x64": "0.23.1",
|
||||
"@esbuild/win32-arm64": "0.23.1",
|
||||
"@esbuild/win32-ia32": "0.23.1",
|
||||
"@esbuild/win32-x64": "0.23.1"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||
@@ -688,6 +1137,19 @@
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/get-tsconfig": {
|
||||
"version": "4.10.0",
|
||||
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz",
|
||||
"integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"resolve-pkg-maps": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "10.4.5",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
||||
@@ -1135,6 +1597,16 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-pkg-maps": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
|
||||
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
@@ -1402,6 +1874,26 @@
|
||||
"node": ">=0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx": {
|
||||
"version": "4.19.2",
|
||||
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz",
|
||||
"integrity": "sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "~0.23.0",
|
||||
"get-tsconfig": "^4.7.5"
|
||||
},
|
||||
"bin": {
|
||||
"tsx": "dist/cli.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.7.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"chai": "^5.1.2",
|
||||
"mocha": "^11.1.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,13 +20,13 @@ Each kata has a `tests.ts` file.
|
||||
To run the tests for a specific kata, run the following command:
|
||||
|
||||
```bash
|
||||
npx mocha -r ts-node/register path/to/specific/tests.ts
|
||||
npx mocha --import=tsx path/to/specific/tests.ts
|
||||
```
|
||||
|
||||
If you're in the right folder:
|
||||
|
||||
```bash
|
||||
npx mocha -r ts-node/register tests.ts
|
||||
npx mocha --import=tsx ./tests.ts
|
||||
```
|
||||
|
||||
## Adding a new kata
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
"target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||
@@ -25,7 +25,7 @@
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
"module": "ESNext", /* Specify what module code is generated. */
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
|
||||
Reference in New Issue
Block a user