diff --git a/package.json b/package.json
index 83cc4ef..24f6bbe 100644
--- a/package.json
+++ b/package.json
@@ -63,6 +63,7 @@
"@types/enzyme": "^3.10.5",
"@types/enzyme-adapter-react-16": "^1.0.6",
"@types/jest": "^26.0.5",
+ "@types/ramda": "^0.27.17",
"@types/react": "^16.9.42",
"@types/react-dom": "^16.9.8",
"@types/react-redux": "^7.1.9",
@@ -86,6 +87,7 @@
"loki": "^0.24.0",
"postcss-loader": "^3.0.0",
"prettier": "^2.0.5",
+ "ramda": "^0.27.1",
"react-test-renderer": "^16.13.1",
"redux-mock-store": "^1.5.4",
"style-loader": "^1.2.1",
diff --git a/public/index.html b/public/index.html
index 7d4e559..d3bf9cd 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1,12 +1,14 @@
-
-
-
- Document
-
-
-
-
+
+
+
+ Otus project Game of Life
+
+
+
+
+
+
diff --git a/src/lesson14/immutability.test.ts b/src/lesson14/immutability.test.ts
new file mode 100644
index 0000000..6d1f528
--- /dev/null
+++ b/src/lesson14/immutability.test.ts
@@ -0,0 +1,56 @@
+import {
+ OriginalTeam,
+ ExpectedTeam,
+ originalTeamToExpectedTeam,
+ originalTeamToExpectedTeamObject,
+ originalArrayToExpectedArray,
+} from './immutability';
+
+// Задание 1
+test('team to team', () => {
+ const originalTeam: OriginalTeam = Object.freeze({
+ size: 15,
+ name: 'Tampa Bay Roosters',
+ league: 'Minor',
+ });
+
+ const expectedTeam: ExpectedTeam = {
+ name: 'New York Badgers',
+ league: 'Minor',
+ roster: 25,
+ };
+
+ expect(originalTeamToExpectedTeamObject(originalTeam)).toEqual(
+ expectedTeam,
+ );
+});
+
+// Задание 2
+test('array to array', () => {
+ const originalArray = Object.freeze([1, 2, 3, 4]);
+
+ const expectedArray = ['two', 3, 4, 5];
+
+ expect(originalArrayToExpectedArray(originalArray)).toEqual(expectedArray);
+});
+
+// Задание 3
+test('team to team deep', () => {
+ const originalTeam = Object.freeze({
+ name: 'Tampa Bay Roosters',
+ captain: {
+ name: 'Bryan Downey',
+ age: 27,
+ },
+ });
+
+ const expectedTeam = {
+ name: 'Tampa Bay Roosters',
+ captain: {
+ name: 'Bryan Downey',
+ age: 28,
+ },
+ };
+
+ expect(originalTeamToExpectedTeam(originalTeam)).toEqual(expectedTeam);
+});
diff --git a/src/lesson14/immutability.ts b/src/lesson14/immutability.ts
new file mode 100644
index 0000000..43873d3
--- /dev/null
+++ b/src/lesson14/immutability.ts
@@ -0,0 +1,59 @@
+// Задание 1
+export type OriginalTeam = {
+ size: number;
+ name: string;
+ league: string;
+};
+
+export type ExpectedTeam = {
+ name: string;
+ league: string;
+ roster: number;
+};
+
+export const originalTeamToExpectedTeamObject = (
+ originalTeam: OriginalTeam,
+): ExpectedTeam => {
+ const withoutSize = Object.entries(originalTeam).filter(
+ (value) => value[0] !== 'size',
+ );
+ const newValueKeyOfName = withoutSize.map((value) => {
+ if (value[0] === 'name' && value[1] !== 'New York Badgers')
+ value[1] = 'New York Badgers';
+ return value;
+ });
+ const teamNew = Object.fromEntries(
+ (newValueKeyOfName as unknown) as string[],
+ );
+
+ return { ...teamNew, roster: 25 };
+};
+
+// Задание 2
+type SomeArray = Array;
+
+export const originalArrayToExpectedArray = (
+ originalArray: SomeArray,
+): SomeArray => {
+ const cloneArrayWithoutFirstElement = originalArray.slice(2);
+ return ['two', ...cloneArrayWithoutFirstElement, 5];
+};
+
+// Задание 3
+
+export type Team = {
+ name: string;
+ captain: {
+ name: string;
+ age: number;
+ };
+};
+
+export const originalTeamToExpectedTeam = (originalTeam: Team): Team => {
+ const cloneOriginalTeam = Object.assign({}, originalTeam);
+ const newCaptain = {
+ name: 'Bryan Downey',
+ age: 28,
+ };
+ return { ...cloneOriginalTeam, captain: newCaptain };
+};
diff --git a/src/lesson14/pureFunctions.test.ts b/src/lesson14/pureFunctions.test.ts
new file mode 100644
index 0000000..8d3fe54
--- /dev/null
+++ b/src/lesson14/pureFunctions.test.ts
@@ -0,0 +1,36 @@
+import { getTopName, Team, QsObj, createQs, parseQs } from './pureFunctions';
+
+test('getTopName', () => {
+ const teams: Team[] = [
+ { name: 'Lions', score: 5 },
+ { name: 'Tigers', score: 4 },
+ { name: 'Bears', score: 6 },
+ { name: 'Monkeys', score: 2 },
+ ];
+
+ expect(getTopName(teams)).toBe('Bears');
+});
+
+test('createQs', () => {
+ const qsObj: QsObj = {
+ page: '2',
+ pageSize: '10',
+ total: '205',
+ somethingElse: 'value',
+ };
+
+ expect(createQs(qsObj)).toBe(
+ '?page=2&pageSize=10&total=205&somethingElse=value',
+ );
+});
+
+test('parseQs', () => {
+ const qs = '?page=2&pageSize=10&total=205&somethingElse=value';
+
+ expect(parseQs(qs)).toEqual({
+ page: '2',
+ pageSize: '10',
+ total: '205',
+ somethingElse: 'value',
+ });
+});
diff --git a/src/lesson14/pureFunctions.ts b/src/lesson14/pureFunctions.ts
new file mode 100644
index 0000000..eaeda57
--- /dev/null
+++ b/src/lesson14/pureFunctions.ts
@@ -0,0 +1,31 @@
+// Задание 1
+export type Team = { name: string; score: number };
+
+export const getTopName = (teams: Team[]): string => {
+ const copyTeams = teams.map((value) => Object.assign({}, value));
+ const sorted = copyTeams.sort((x, y) => (x.score > y.score ? 1 : -1));
+ return sorted[sorted.length - 1].name;
+};
+
+// Задание 2
+export type QsObj = Record;
+
+export const createQs = (qsObj: QsObj): string => {
+ const objectToArray = Object.entries(qsObj);
+ const elementJoinEquals = objectToArray.map((val) => val.join('='));
+ const stringJoinAmp = elementJoinEquals.join('&');
+ const propsWithSymbol = stringJoinAmp.replace(/^/, '?');
+ return propsWithSymbol;
+};
+
+// Задание 3
+
+export const parseQs = (qs: string): QsObj => {
+ const propsWithoutSymbol = qs.replace(/\?/y, '');
+ const stringSplitAmp = propsWithoutSymbol.split('&');
+ const elementSplitEquals = stringSplitAmp.map((v) => v.split('='));
+ const desiredObject = Object.fromEntries(
+ (elementSplitEquals as unknown) as string[],
+ );
+ return desiredObject;
+};
diff --git a/src/lesson14/ramdaPureFunctions.test.ts b/src/lesson14/ramdaPureFunctions.test.ts
new file mode 100644
index 0000000..98e353b
--- /dev/null
+++ b/src/lesson14/ramdaPureFunctions.test.ts
@@ -0,0 +1,42 @@
+import {
+ getTopName,
+ Team,
+ QsObj,
+ createQs,
+ parseQs,
+} from './ramdaPureFunctions';
+
+test('getTopName', () => {
+ const teams: Team[] = [
+ { name: 'Lions', score: 5 },
+ { name: 'Tigers', score: 4 },
+ { name: 'Bears', score: 6 },
+ { name: 'Monkeys', score: 2 },
+ ];
+
+ expect(getTopName(teams)).toBe('Bears');
+});
+
+test('createQs', () => {
+ const qsObj: QsObj = {
+ page: '2',
+ pageSize: '10',
+ total: '205',
+ somethingElse: 'value',
+ };
+
+ expect(createQs(qsObj)).toBe(
+ '?page=2&pageSize=10&total=205&somethingElse=value',
+ );
+});
+
+test('parseQs', () => {
+ const qs = '?page=2&pageSize=10&total=205&somethingElse=value';
+
+ expect(parseQs(qs)).toEqual({
+ page: '2',
+ pageSize: '10',
+ total: '205',
+ somethingElse: 'value',
+ });
+});
diff --git a/src/lesson14/ramdaPureFunctions.ts b/src/lesson14/ramdaPureFunctions.ts
new file mode 100644
index 0000000..3422d17
--- /dev/null
+++ b/src/lesson14/ramdaPureFunctions.ts
@@ -0,0 +1,40 @@
+import { compose, last, sortBy, prop, map, join, replace, split } from 'ramda';
+
+// Задание 1
+export type Team = { name: string; score: number };
+
+const copyTeams = (teams: Team[]) =>
+ teams.map((value) => Object.assign({}, value));
+const sortByObj = sortBy((t) => t.score);
+const getName = prop('name');
+
+export const getTopName = compose(getName, last, sortByObj, copyTeams);
+
+// Задание 2
+export type QsObj = Record;
+export type CreateQs = (x: QsObj) => string;
+
+const objectToArray = (qsObj: QsObj): string[][] =>
+ Object.entries(qsObj as any);
+const elementJoinEquals = map((val: string[]) => val.join('='));
+const stringJoinAmp = join('&');
+const propsWithSymbol = replace(/^/, '?');
+export const createQs: CreateQs = compose(
+ propsWithSymbol,
+ stringJoinAmp,
+ elementJoinEquals,
+ objectToArray,
+);
+
+// Задание 3
+const propsWithoutSymbol = replace(/\?/y, '');
+const stringSplitAmp = split('&');
+const elementSplitEquals = map((v: string) => v.split('='));
+const desiredObject = (desiredObject: string[][]) =>
+ Object.fromEntries(desiredObject as any);
+export const parseQs = compose(
+ desiredObject,
+ elementSplitEquals,
+ stringSplitAmp,
+ propsWithoutSymbol,
+);