diff --git a/.eslintrc.json b/.eslintrc.json index 2bc4506..aaf7b32 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -238,6 +238,7 @@ "template-curly-spacing": [ "error", "never" - ] + ], + "prefer-template": ["error"] } } diff --git a/memoization/README.md b/memoization/README.md new file mode 100644 index 0000000..1d9309f --- /dev/null +++ b/memoization/README.md @@ -0,0 +1,9 @@ +# Memoization + +Мемоизация - техника для оптимизации программ. + +На практике, это обертка над функцией, запоминающая результат вычисления функции с конкретными аргументами. При повторном вызове с теми же аргументами, вычисление не произойдет, результат возьмется из кеша. + +## Links + +- diff --git a/memoization/java-script/1-memoize.js b/memoization/java-script/1-memoize.js new file mode 100644 index 0000000..8a83630 --- /dev/null +++ b/memoization/java-script/1-memoize.js @@ -0,0 +1,43 @@ +'use strict'; + +// Reference: https://github.com/HowProgrammingWorks/Memoization/blob/master/JavaScript/1-memoize.js + +// Для мемоизации нужно хранилище значений. Для этой задачи подойдет ассоциативный массив. +// В таком случае, нужен механизм, который позволит определить, что результат для конкретных аргументов уже вычислен. +// Нам нужна контрольная сумма. Как вариант: склеить строковое представление аргументов с типами аргументов. +const argKey = (x) => `${x.toString()}:${typeof x}`; +const generateKey = (args) => args.map(argKey).join('|'); + +const memoize = (fn) => { + // Нам нужен ассоциативный массив, который будет просто хранить значения. + // Поэтому избавляемся от прототипа + const cache = Object.create(null); + + return (...args) => { + const key = generateKey(args); + const val = cache[key]; + if (val) return val; + const res = fn(...args); + cache[key] = res; + return res; + }; +}; + +// Usage +const sumSeq = (a, b) => { + console.log('Calculate sum'); + let r = 0; + for (let i = a; i < b; i++) r += i; + return r; +}; + +const mSumSeq = memoize(sumSeq); + +console.log('First call mSumSeq(2, 5)'); +console.log('Value:', mSumSeq(2, 5)); + +console.log('Second call mSumSeq(2, 5)'); +console.log('From cache:', mSumSeq(2, 5)); + +console.log('Call mSumSeq(2, 6)'); +console.log('Calculated:', mSumSeq(2, 6)); diff --git a/memoization/java-script/2-cache-size.js b/memoization/java-script/2-cache-size.js new file mode 100644 index 0000000..47d2831 --- /dev/null +++ b/memoization/java-script/2-cache-size.js @@ -0,0 +1,42 @@ +'use strict'; + +// Reference: https://github.com/HowProgrammingWorks/Memoization/blob/master/JavaScript/3-cacheSize.js + +const argKey = (x) => `${x.toString()}:${typeof x}`; +const generateKey = (args) => args.map(argKey).join('|'); + +const memoize = (fn, cacheSize) => { + const cache = new Map(); + + return (...args) => { + const key = generateKey(args); + + if (cache.has(key)) return cache.get(key); + + const result = fn(...args); + + // Когда кеш переполнен, удаляем ключ с начала + if (cache.size >= cacheSize) { + const firstKey = cache.keys().next().value; + console.log('Delete key:', firstKey); + cache.delete(firstKey); + } + + cache.set(key, result); + return result; + }; +}; + +// Usage +const max = (a, b) => (a > b ? a : b); +const mMax = memoize(max, 3); + +mMax(10, 8); +mMax(10, 8); +mMax(1, 15); +mMax(12, 3); +mMax(15, 2); +mMax(1, 15); +mMax(10, 8); +mMax(0, 0); +mMax(0, 0); diff --git a/memoization/java-script/exercises/1-a-expiration-cash.js b/memoization/java-script/exercises/1-a-expiration-cash.js new file mode 100644 index 0000000..ed24c68 --- /dev/null +++ b/memoization/java-script/exercises/1-a-expiration-cash.js @@ -0,0 +1,47 @@ +'use strict'; + +// Task: implement time expiration cash + +const argKey = (x) => `${x.toString()}:${typeof x}`; +const generateKey = (args) => args.map(argKey).join('|'); + +const memoize = (fn, cacheLifeTime) => { + const cache = new Map(); + + return (...args) => { + const key = generateKey(args); + const timeoutId = setTimeout(() => cache.delete(key), cacheLifeTime); + + if (cache.has(key)) { + const value = cache.get(key); + console.log('From cache: ', value.result); + + clearTimeout(value.timeoutId); + value.timeoutId = timeoutId; + + return value.result; + }; + + const result = fn(...args); + cache.set(key, { timeoutId, result }); + console.log('Calculated: ', result); + return result; + }; +}; + +// Usage + +const sum = (a, b) => a + b; +const memoizeSum = memoize(sum, 2000); + +memoizeSum(1, 2); +memoizeSum(1, 2); +memoizeSum(1, 2); + +memoizeSum(2, 4); +memoizeSum(2, 4); +setTimeout(() => memoizeSum(2, 4), 3000); +setTimeout(() => memoizeSum(2, 4), 5000); +setTimeout(() => memoizeSum(2, 4), 7000); +setTimeout(() => memoizeSum(2, 4), 9000); +setTimeout(() => memoizeSum(2, 4), 12000); diff --git a/memoization/java-script/exercises/1-b-expiration-cash.js b/memoization/java-script/exercises/1-b-expiration-cash.js new file mode 100644 index 0000000..9d941e8 --- /dev/null +++ b/memoization/java-script/exercises/1-b-expiration-cash.js @@ -0,0 +1,59 @@ +'use strict'; + +// Task: implement time expiration cash + +const argKey = (x) => `${x.toString()}:${typeof x}`; +const generateKey = (args) => args.map(argKey).join('|'); + +const memoize = (fn, cacheLifeTime) => { + const cache = new Map(); + + const clearCache = () => setTimeout(() => cache.clear(), cacheLifeTime); + let timeoutId = clearCache(); + + return (...args) => { + const key = generateKey(args); + + if (cache.has(key)) { + const value = cache.get(key); + console.log('From cache: ', value); + + clearTimeout(timeoutId); + timeoutId = clearCache(); + + return value; + }; + + const result = fn(...args); + cache.set(key, result); + console.log('Calculated: ', result); + return result; + }; +}; + +// Usage + +const sum = (a, b) => a + b; +const memoizeSum = memoize(sum, 2000); + +memoizeSum(1, 2); // Calculated +memoizeSum(1, 2); // From cache +memoizeSum(1, 2); // From cache + +memoizeSum(2, 4); // Calculated +memoizeSum(2, 4); // From cache + +setTimeout(() => { + memoizeSum(1, 2); // Calculated + memoizeSum(2, 4); // Calculated +}, 4000); + +setTimeout(() => { + memoizeSum(1, 2); // From cache + memoizeSum(2, 4); // From cache +}, 6000); + +setTimeout(() => { + memoizeSum(1, 2); // Calculated + memoizeSum(2, 4); // Calculated +}, 10000); diff --git a/memoization/java-script/exercises/2-max-records-count.js b/memoization/java-script/exercises/2-max-records-count.js new file mode 100644 index 0000000..a0d49a7 --- /dev/null +++ b/memoization/java-script/exercises/2-max-records-count.js @@ -0,0 +1,68 @@ +'use strict'; + +// Task: Implement memoize with max records count and removing least used + +const argKey = (x) => `${x.toString()}:${typeof x}`; +const generateKey = (args) => args.map(argKey).join('|'); + +const memoize = (fn, cacheSize) => { + const cache = new Map(); + + return (...args) => { + const key = generateKey(args); + + if (cache.has(key)) { + const value = cache.get(key); + console.log('From cache: ', value.result); + value.usages += 1; + + return value.result; + } + + if (cache.size >= cacheSize) { + const maxValue = {}; + + for (const [key, value] of cache.entries()) { + if (typeof maxValue.value === 'undefined') { + maxValue.key = key; + maxValue.value = value; + continue; + } + + if (value < maxValue.value) { + maxValue.key = key; + maxValue.value = value; + continue; + } + } + + console.log('Deleting from cache: ', maxValue.key); + console.log('usages: ', maxValue.value.usages); + cache.delete(maxValue.key); + } + + const result = fn(...args); + cache.set(key, { usages: 0, result }); + console.log('Calculated: ', result); + return result; + }; +}; + +// Usage + +const sum = (a, b) => a + b; +const memoizeSum = memoize(sum, 3); + +memoizeSum(1, 2); +memoizeSum(1, 2); + +memoizeSum(2, 2); +memoizeSum(2, 2); +memoizeSum(2, 2); + +memoizeSum(3, 3); +memoizeSum(3, 3); +memoizeSum(3, 3); +memoizeSum(3, 3); + +memoizeSum(4, 4); diff --git a/memoization/java-script/exercises/4-max-total-data-size.js b/memoization/java-script/exercises/4-max-total-data-size.js new file mode 100644 index 0000000..a720cb4 --- /dev/null +++ b/memoization/java-script/exercises/4-max-total-data-size.js @@ -0,0 +1,106 @@ +'use strict'; + +// Task: Implement memoize with max total stored data size +// Max stored data in each field implementation + +const typeToBytes = { + string: (string) => string.length * 2, + number: () => 8, + boolean: () => 4, + object: (object) => { + let bytes = 0; + + for (const key of object) { + const value = object[key]; + const handler = this[typeof value]; + + bytes += handler(value); + } + + return bytes; + }, +}; + +function convertSizeToBytes(size) { + const [value, memoryUnit] = getMemoryValueAndUnit(size); + + const multiplicatorMap = { + T: 1000, + G: 1000, + M: 1000, + K: 1000, + B: 1, + }; + + const multiplicator = multiplicatorMap[memoryUnit] || 1; + + return value * multiplicator; +} + +function getMemoryValueAndUnit(size) { + const amount = parseFloat(size, 10); + const digitsNumber = String(amount).length; + const memoryUnit = size.slice(digitsNumber, digitsNumber + 1).toUpperCase(); + + return [amount, memoryUnit]; +} + + +const argKey = (x) => `${x.toString()}:${typeof x}`; +const generateKey = (args) => args.map(argKey).join('|'); + +const memoize = (fn, maxCacheSize) => { + const cache = new Map(); + + return (...args) => { + const key = generateKey(args); + + if (cache.has(key)) { + const value = cache.get(key); + console.log('From cache: ', value); + + return value; + } + + const availableBytes = convertSizeToBytes(maxCacheSize); + const result = fn(...args); + console.log('Calculated: ', result); + const resultSize = typeToBytes[typeof result](result); + + if (resultSize > availableBytes) { + console.log(`Size of result more than cache can hold. Result size: ${resultSize}`); + return; + } + + cache.set(key, result); + return result; + }; +}; + +const sum = (a, b) => a + b; +const memoizedSum = memoize(sum, '20B'); + +memoizedSum('s', 'qsssss'); + +memoizedSum(1, 2); +memoizedSum(1, 2); + +memoizedSum(true, false); + +memoizedSum(2, 4); +memoizedSum(2, 4); +memoizedSum(2, 4); + +memoizedSum(3, 4); + +memoizedSum('22', 4); + +memoizedSum('qqqqq', 'qqqqqqq'); +memoizedSum('qqqqq', 'qqqqq'); + +memoizedSum(4, 4); + +memoizedSum('q', 4); + +memoizedSum(5, 4); +memoizedSum(5, 4); diff --git a/memoization/java-script/exercises/5-universal-callback.js b/memoization/java-script/exercises/5-universal-callback.js new file mode 100644 index 0000000..fdd2e36 --- /dev/null +++ b/memoization/java-script/exercises/5-universal-callback.js @@ -0,0 +1,64 @@ +'use strict'; + +const fs = require('fs'); + +// Task: implement universal memoize compatible with both sync and async function +// Callbacks implementation + +const argKey = (x) => `${x.toString()}:${typeof x}`; +const generateKey = (args) => args.map(argKey).join('|'); + +const memoize = (fn) => { + const cache = new Map(); + + return (...args) => { + const isAsync = typeof args[args.length - 1] === 'function'; + + if (isAsync) { + // Достаем из аргументов callback, не будем его включать в контрольную сумму + const callback = args.pop(); + const key = generateKey(args); + + if (cache.has(key)) { + const value = cache.get(key); + console.log('From cache'); + + return callback(value.error, value.data); + } + + fn(...args, (err, data) => { + console.log('Async calculated'); + cache.set(key, { err, data }); + callback(err, data); + }); + + return; + } + + + const key = generateKey(args); + + if (cache.has(key)) { + const value = cache.get(key); + console.log('From cache: ', value); + + return value; + } + + const result = fn(...args); + console.log('Calculated: ', result); + + cache.set(key, result); + return result; + }; +}; + +const memoizeRead = memoize(fs.readFile); + +memoizeRead('1-a-expiration-cash.js', 'utf8', (err, data) => { + console.log('data length:', data.length); + + memoizeRead('1-a-expiration-cash.js', 'utf8', (err, data) => { + console.log('data length:', data.length); + }); +}); diff --git a/memoization/java-script/exercises/6-universal-promise.js b/memoization/java-script/exercises/6-universal-promise.js new file mode 100644 index 0000000..67a3e06 --- /dev/null +++ b/memoization/java-script/exercises/6-universal-promise.js @@ -0,0 +1,88 @@ +'use strict'; + +const fs = require('fs'); + +// Task: implement universal memoize compatible with both sync and async function +// Promises implementation + +const argKey = (x) => `${x.toString()}:${typeof x}`; +const generateKey = (args) => args.map(argKey).join('|'); + +const memoize = (fn) => { + const cache = new Map(); + + return (...args) => { + const isAsync = fn.constructor.name === 'AsyncFunction'; + + if (isAsync) { + const key = generateKey(args); + + if (cache.has(key)) { + const value = cache.get(key); + console.log('From cache'); + + if (value.error) { + return Promise.reject(value.error); + } + + if (value.data) { + return Promise.resolve(value.data); + } + } + + return fn(...args) + .then((data) => { + console.log('Async calculated'); + cache.set(key, { error: null, data }); + return data; + }) + .catch((error) => { + cache.set(key, { error, data: null }); + return error; + }); + } + + const key = generateKey(args); + + if (cache.has(key)) { + const value = cache.get(key); + console.log('From cache: ', value); + + return value; + } + + const result = fn(...args); + console.log('Calculated: ', result); + + cache.set(key, result); + return result; + }; +}; + +const memoizeRead = memoize(fs.promises.readFile); + +memoizeRead('1-a-expiration-cash.js', 'utf8') + .then((data) => { + console.log('(1) data length:', data.length); + }) + .then(() => { + memoizeRead('1-a-expiration-cash.js', 'utf8').then((data) => { + console.log('(1-1) data length:', data.length); + }); + }); + +const sum = (a, b) => a + b; +const memoizeSum = memoize(sum, 3); + +memoizeSum(1, 2); +memoizeSum(1, 2); + +memoizeRead('1-a-expiration-cash.js', 'utf8').then((data) => { + console.log('data length:', data.length); +}); + +setTimeout(() => { + memoizeRead('1-a-expiration-cash.js', 'utf8').then((data) => { + console.log('(setTimeout) data length:', data.length); + }); +}, 1000) diff --git a/memoization/java-script/exercises/7-object.js b/memoization/java-script/exercises/7-object.js new file mode 100644 index 0000000..161c8ae --- /dev/null +++ b/memoization/java-script/exercises/7-object.js @@ -0,0 +1,12 @@ +// TODO +// 5. implement functional object with following properties methods and events: +// - memoized.clear() - clear cache +// - memoized.add(key, value) - add value to cach +// - memoized.del(key) - remove value from cach +// - memoized.get(key) - returns saved value +// - memoized.timeout: Number - cache timout +// - memoized.maxSize: Number - maximum cache size in bytes +// - memoized.maxCount: Number - maximum cache size in item count +// - memoized.on('add', Function) +// - memoized.on('del', Function) +// - memoized.on('clear', Function)