嗨!
在本文中,我将尝试展示解决方案对一个不是超级艰巨的任务的不同,并且还想向您展示一些技巧(也许是hacky,但它在JS而不是TS;)中使用reduce函数。
首先,让我们看一下任务,一个超长,有点疯狂的解决方案,我尝试
初始任务(书店)
像往常一样 - 这是来自可爱且有用的exercism.org门户的任务,并且有一个指令:
要尝试鼓励流行5本书系列的不同书籍的更多销售,一家书店决定为多本书购买提供折扣。
五本书中的任何一本副本的价格为$8。
但是,如果您购买了两本不同的书,那么这两本书就可以享受5%的折扣。
如果您购买了3本不同的书,则获得10%的折扣。
如果您购买了4本不同的书,则获得20%的折扣。
如果您购买了全部5,则获得25%的折扣。
...其余的说明是here
目标 - 找到更好的客户折扣取决于他或她正在购买的书籍。
BookDispenser类的宽松解决方案
因此,在这个解决方案中,我尝试将重点放在下降算法上,而是使用BookDispenser
Machine
使用OO方法
const getDiscountMultiplier = (discountInPercent) => 100 - discountInPercent;
const ONE_COPY_COST = 8;
const TWO_COPIES_DISCOUNT_PERCENT = 5;
const TWO_COPIES_DISCOUNT = getDiscountMultiplier(TWO_COPIES_DISCOUNT_PERCENT);
const TWO_COPIES_COST = 2 * ONE_COPY_COST * TWO_COPIES_DISCOUNT;
const THREE_COPIES_DISCOUNT_PERCENT = 10;
const THREE_COPIES_DISCOUNT = getDiscountMultiplier(THREE_COPIES_DISCOUNT_PERCENT);
const THREE_COPIES_COST = 3 * ONE_COPY_COST * THREE_COPIES_DISCOUNT;
const FOUR_COPIES_DISCOUNT_PERCENT = 20;
const FOUR_COPIES_DISCOUNT = getDiscountMultiplier(FOUR_COPIES_DISCOUNT_PERCENT);
const FOUR_COPIES_COST = 4 * ONE_COPY_COST * FOUR_COPIES_DISCOUNT;
const FIVE_COPIES_DISCOUNT_PERCENT = 25;
const FIVE_COPIES_DISCOUNT = getDiscountMultiplier(FIVE_COPIES_DISCOUNT_PERCENT);
const FIVE_COPIES_COST = 5 * ONE_COPY_COST * FIVE_COPIES_DISCOUNT;
const bestPriceForOneBook = Math.min(1 * ONE_COPY_COST * 100);
const bestPriceForTwoBooks = Math.min(
2 * bestPriceForOneBook,
1 * TWO_COPIES_COST
)
const bestPriceForThreeBooks = Math.min(
3 * bestPriceForOneBook,
1 * bestPriceForTwoBooks + 1 * bestPriceForOneBook,
1 * THREE_COPIES_COST
)
const bestPriceForFourBooks = Math.min(
4 * bestPriceForOneBook,
2 * bestPriceForTwoBooks,
1 * bestPriceForThreeBooks + 1 * bestPriceForOneBook,
1 * FOUR_COPIES_COST
)
const bestPriceForFiveBooks = Math.min(
5 * bestPriceForOneBook,
2 * bestPriceForTwoBooks + 1 * bestPriceForThreeBooks,
1 * bestPriceForThreeBooks + 1 * bestPriceForTwoBooks,
1 * bestPriceForFourBooks + 1 * bestPriceForOneBook,
1 * FIVE_COPIES_COST
)
//TODO: try to make it countable itself
const bestPricePerBookAmount = new Map([
[1, bestPriceForOneBook],
[2, bestPriceForTwoBooks],
[3, bestPriceForThreeBooks],
[4, bestPriceForFourBooks],
[5, bestPriceForFiveBooks],
])
class BookDispenser {
_booksByCount;
_dispenserOrder;
constructor(books) {
this._booksByCount = BookDispenser._getBooksByCount(books);
this._dispenserOrder = BookDispenser._countDispenserOrder(this._booksByCount);
}
_booksLeft() {
let stillBooksLeft = false;
for (let book = 1; book <= 5; book++) {
const booksLeft = this._booksByCount.get(book);
if (booksLeft > 0) {
stillBooksLeft = true;
break;
}
}
return stillBooksLeft;
}
dispense(numberOfBooks) {
let numberOfDispersedBooks = 0;
for (let index = 0; index < this._dispenserOrder.length; index++) {
const bookToDispense = this._dispenserOrder[index];
numberOfDispersedBooks += this.dispenseBook(bookToDispense);
if (numberOfDispersedBooks == numberOfBooks) break;
}
return numberOfDispersedBooks;
}
dispenseBook(bookNumber) {
const booksCount = this._booksByCount.get(bookNumber);
if (booksCount > 0) {
this._booksByCount.set(bookNumber , booksCount - 1);
return 1;
}
return 0;
}
getBatch(booksPerBatch, numberOfDispenses) {
const booksBatches = [];
let dispenses = 0;
let batch;
while ((!numberOfDispenses || dispenses < numberOfDispenses) &&
(batch = this.dispense(booksPerBatch))
) {
dispenses++;
// log({booksPerBatch, batch, dispenses, still: dispenses < numberOfDispenses });
booksBatches.push(batch);
}
return booksBatches;
}
getBatchReduced(booksPerBatch, dispenses = 1) {
const basketVariants = [];
const batch = this.getBatch(booksPerBatch, dispenses);
if (booksPerBatch >= 1) {
const reducedBatch = this.getBatch(
booksPerBatch === 1 ? 1 : booksPerBatch - 1
);
if (reducedBatch.length > 0) {
batch.push(...reducedBatch);
}
}
if (batch.length !== 0) {
basketVariants.push(...batch);
}
return basketVariants;
}
static _getBooksByCount = (books) => {
const booksByCount = new Map();
for (let book = 1; book <= 5; book++) {
const booksInBasket = books.filter(bookInBasket => bookInBasket === book).length;
booksByCount.set(book, booksInBasket);
}
return booksByCount;
}
static _countDispenserOrder(booksByCount) {
const bookEntries = [];
for (let entry of booksByCount.entries()) {
bookEntries.push(entry)
}
return bookEntries
.sort((a, b) => b[1] - a[1] )
.map(({0:a}) => a)
}
}
const cost = (books) => {
if (books.length === 0) return 0;
const basketVariants = [];
const addBatchIfNotAlreadyInVariants = (batch) => {
const isExists = basketVariants.some(basket => {
return ''+basket === ''+batch;
})
if (!isExists) {
basketVariants.push(batch);
}
}
for (let booksBatch = 5; booksBatch >= 1; booksBatch--) {
// TODO: fix dispenses; should be calculated, not constant
for (let dispenses = 1; dispenses < 10; dispenses++) {
const bookDispenser = new BookDispenser(books);
const reducedBatch = bookDispenser.getBatchReduced(booksBatch, dispenses);
addBatchIfNotAlreadyInVariants(reducedBatch);
}
const batch = new BookDispenser(books).getBatch(booksBatch);
addBatchIfNotAlreadyInVariants(batch);
}
log({ basketVariants });
return Math.min(...basketVariants.map(basket => {
return basket.reduce((sum, b) => sum + bestPricePerBookAmount.get(b), 0);
}))
};
const log = value => console.log(value);
是的,有点结束,我知道:)
也许在这里可以指出的是地图不是对象的使用,您可以在Mozilla web site上阅读的好处/不同。不同的...
超短解决方案
基于LittleArmstrong的出色超级简洁解决方案!
const cost = (books) => books.reduce((grouped, book) => {
grouped[book-1] = (grouped?.[book-1] ?? 0) + 1; return grouped;
}, []).reduce((grouped, amount) => {
for (let i = 0; i < amount; i++) grouped[i] = (grouped?.[i] ?? 0) + 1; return grouped;
}, []).map(
(amount, _ , group) => (amount === 5 & group.includes(3)) ? (group[group.indexOf(3)] += 1, amount - 1) : amount).map(amount => 800 * amount * (1 - ({2: 0.05, 3: 0.1, 4: 0.2, 5: 0.25}?.[amount] ?? 0))).reduce((acc, cur) => acc += cur, 0);
是的,这几乎是不可读的,我知道:)
结论
在此示例中,我向您展示了两个针对同一任务的极性解决方案,当然,事实介于:)
之间请注意,reduce
功能的使用初始值 累加器。这绝对不是微不足道的,但可能是可能的。
pros ?该代码看起来更功能,并链接。
cons ?在MDN规范中,它看起来只是骇人听闻,很难阅读,并且没有突出显示。
请随时在评论中提供解决方案,甚至在exercism.org网站上更好。
快乐的编程和重构!让我们继续关注!