Compare commits

...

25 Commits

Author SHA1 Message Date
897f802659 add kata 2025-02-15 00:52:14 +01:00
5f7c1ca61a finish math parser 2025-02-05 23:04:53 +01:00
69edf86b63 wip: parser 2025-02-05 16:59:40 +01:00
857a252fea WIP: math parser 2025-02-03 18:22:26 +01:00
b3ac5ee075 WIP: math parser 2025-02-03 17:13:32 +01:00
3ffb7f8181 add kata 2025-02-03 14:28:54 +01:00
03bdd63342 move to 4kyu 2025-02-03 13:52:29 +01:00
849d003a68 cleanup 2025-02-03 00:36:42 +01:00
ba4c999a99 remove sort that breaks everything 2025-02-03 00:33:48 +01:00
2ea2410fb3 fourth solution 2025-02-03 00:21:54 +01:00
ca92878a0a third solution 2025-02-03 00:03:07 +01:00
58ce2189f7 second solution 2025-02-02 23:52:53 +01:00
b768376ff0 first solution 2025-02-02 23:52:26 +01:00
69e937d98e add kata 2025-02-02 22:48:09 +01:00
1146914ec3 add kata 2025-02-02 22:08:01 +01:00
0ebad86104 add kata 2025-02-02 18:50:09 +01:00
978844ed75 add kata 2025-02-01 23:56:20 +01:00
2dab36b36e add kata 2025-02-01 23:50:01 +01:00
bbc549c3ea add kata 2025-02-01 23:26:21 +01:00
f03183055b remove any type 2025-02-01 23:15:40 +01:00
ddb9925e18 add kata 2025-02-01 22:56:58 +01:00
4dbb1fa75f add kata 2025-02-01 10:13:59 +01:00
a7b1b9d061 add kata 2025-01-31 23:05:40 +01:00
2b2fce0c42 add kata 2025-01-31 22:55:24 +01:00
9080e35d7a use mocha and tsx to run tests 2025-01-31 22:51:13 +01:00
46 changed files with 3119 additions and 4 deletions

View 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).

View 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;
};

View 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');
});
});

View 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.

View 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;
}

View 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'
}]);
});
});

View 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.

View 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);
}

View 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));
});

View 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.

View 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;
}

View 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);
});
});

View 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.

View 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
View 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, [] );
})
});

View 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.

View 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;
}

View 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]]);
});
});

View 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"`

View 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('');
}

View 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)"
);
});
});

View 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

View 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);
}

View 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")');
});
});

View 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.

View 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;
}

View 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);
});
});

View 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
View 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
View 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);
});
});

View 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.

View 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(',')})`;
}
};

View 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)");
})
});

View 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
(n1)
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
+(n1)
3
+(n2)
3
+...+1
3
=m if such a n exists or -1 if there is no such n.
Examples:
findNb(1071225) --> 45
findNb(91716553919377) --> -1

View 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;
}

View 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);
});
});

View 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!

View 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;
}

View 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');
});
});

View 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"
```

View 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(' ');
}

View 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
View File

@@ -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",

View File

@@ -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"
}
}

View File

@@ -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

View File

@@ -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. */