引言
我們從一個連結串列的建構函式開始,“cons”,它接收一個必要引數“head”及一個可選引數“tail”(相對於支援這樣的實現的語言來說)。該建構函式返回一個連結串列的表示結構體,其中第一個元素為“head”,其餘的元素被包裹在“tail“連結串列中。空連結串列的話用JavaScript中的undefined或Python中的None來表示。
舉例來說,直到微小變化,”cons(1, cons(2, cons(3, cons(4))))"構造了一個含有4個元素的連結串列,”1 2 3 4“。
為了便於檢查連結串列中的內容,我們定義了”listToString“方法來將連結串列轉換為相應的字串,其中連結串列元素之間用空格隔開。
”myMap“方法接收一個一元函式”fn“和一個連結串列”list“。它循序遍歷連結串列中的每一個元素,並返回一個各元素都被”fn“轉化過了的連結串列。
”myReduce"方法會對輸入的連結串列從頭到尾使用一個reducer函式“fn”,然後返回最終結果。比如,假設連結串列為“cons(1, cons(2, cons(3,)))”,“myReduce(fn, accm, list)”應該返回執行“fn(fn(fn(accm, 1), 2), 3)”得到的結果。
上述的三個方法都是使用遞迴實現的,巧妙運用了連結串列的遞迴結構。
第1部分:實現“myReduceRight"
請實現“myReduceRight”方法。其類似於“myReduce”,不同之處在於它是從尾到頭對輸入的連結串列使用reducer函式“fn”的。比如,假設連結串列為“cons(1, cons(2, cons(3)))",”myReduceRight(fn, accm, list)"應該返回執行“fn(1, fn(2, fn(3, accm)))"得到的結果。
要求:
- 你需要使用遞迴來實現,而不是任何顯式的for/while迴圈;
- 你不能在你的實現中使用先前已定義好的”listToString“、”myMap“和”myReduce“方法;
- 你不能修改原始連結串列。
要檢查你的實現的正確性,可以驗證:
- ”
myReduceRight(xTimesTwoPlusY, 0, exampleList)
“應該得到“20”; - “
myReduceRight(unfoldCalculation, accm, exampleList)
"應該表示為”fn(1, fn(2, fn(3, fn(4, accm))))"; - “
myReduceRight(printXAndReturnY, 0, exampleList)
"應該按照輸入連結串列的逆序來列印內容。
第2部分:實現”myMap2“
請基於“myReduceRight"方法實現”myMap2“,其應該在功能性上同質於”myMap“。
對實現的基本要求:
- 你不能在你的實現中使用先前已定義好的”listToString“、”myMap“和”myReduce“方法;
- 你不能修改任何函式的輸入輸出特徵,包括”myReduceRight“的輸入輸出特徵;
- 你不能在借在”myReduceRight“中投機取巧來助力實現”myMap2“,例如向”myReduceRight“傳遞隱藏標誌以表示特殊處理;
- 你不能使用任何語言原生的特殊資料結構(舉例,C++中的”std::vector",Java中的“ArrayList”,Python中的“list”)。
如果你的實現滿足以下儘可能多的要求,你將獲得“加分”:
- 不要使用任何顯式的遞迴呼叫。特別地,避免在實現中宣告呼叫“myMap2”;
- 不要在實現中使用任何顯式的for/while迴圈。為此你需要探究下“myReduceRight”的巧妙用法;
- 不要修改原始連結串列。
以下是你可以遵循的幾個方向:
- 列表翻轉;
- 在reducer方法中修改連結串列;
- 巧妙地使用閉包和lambda函式來調整程式碼執行順序。特別地,考慮考慮延時執行,如
(() -> doSomething)()。
要檢查你的實現的正確性,可以驗證:
- “
listToString(myMap2(plusOne, exampleList))
”應該得到“2 3 4 5”; - “
myMap2(printAndReturn, exampleList)
”應該按照正確的次序列印連結串列內容(“1 2 3 4”分別各佔據一行而不是“4 3 2 1”)。
JavaScript程式碼模板:
// Refer to README for detailed instructions.
function cons(head, tail) {
return {
head: head,
tail: tail,
};
}
function listToString(list) {
if (!list) {
return '';
}
if (!list.tail) {
return list.head.toString();
}
return list.head.toString() + ' ' + listToString(list.tail);
}
function myMap(fn, list) {
if (!list) {
return undefined;
}
return cons(fn(list.head), myMap(fn, list.tail));
}
function myReduce(fn, accm, list) {
if (!list) {
return accm;
}
return myReduce(fn, fn(accm, list.head), list.tail);
}
function myReduceRight(fn, accm, list) {
// [BEGIN] YOUR CODE HERE
return undefined;
// [END] YOUR CODE HERE
}
function myMap2(fn, list) {
// [BEGIN] YOUR CODE HERE
return undefined;
// [END] YOUR CODE HERE
}
function main() {
let exampleList = cons(1, cons(2, cons(3, cons(4))));
let plusOne = (x) => x + 1;
let xTimesTwoPlusY = (x, y) => x * 2 + y;
let printXAndReturnY = (x, y) => {
console.log(x);
return y;
};
let unfoldCalculation = (x, y) => 'fn(' + x + ', ' + y + ')';
let printAndReturn = console.log;
console.log(listToString(exampleList), 'should be 1 2 3 4');
console.log(listToString(myMap(plusOne, exampleList)), 'should be 2 3 4 5');
console.log(myReduce(xTimesTwoPlusY, 0, exampleList), 'should be 26');
console.log(
myReduce(unfoldCalculation, 'accm', exampleList),
'should be fn(fn(fn(fn(accm, 1), 2), 3), 4)'
);
console.log(myReduceRight(xTimesTwoPlusY, 0, exampleList), 'should be 20');
console.log(
myReduceRight(unfoldCalculation, 'accm', exampleList),
'should be fn(1, fn(2, fn(3, fn(4, accm))))'
);
console.log('Below should output 4 3 2 1 each on a separate line:');
myReduceRight(printXAndReturnY, 0, exampleList);
console.log(listToString(myMap2(plusOne, exampleList)), 'should be 2 3 4 5');
console.log('The two outputs below should be equal:');
console.log('First output:');
myMap(printAndReturn, exampleList);
console.log('Second output:');
myMap2(printAndReturn, exampleList);
}
main();
Python程式碼模板:
# Refer to README for detailed instructions.
from __future__ import print_function
class LinkedList:
def __init__(self, head, tail):
self.head = head
self.tail = tail
def cons(head, tail=None):
return LinkedList(head, tail)
def listToString(list):
if list is None:
return ""
if list.tail is None:
return str(list.head)
return str(list.head) + " " + listToString(list.tail)
def myMap(fn, list):
if list is None:
return None
return cons(fn(list.head), myMap(fn, list.tail))
def myReduce(fn, accm, list):
if list is None:
return accm
return myReduce(fn, fn(accm, list.head), list.tail)
def myReduceRight(fn, accm, list):
# [BEGIN] YOUR CODE HERE
return None
# [END] YOUR CODE HERE
def myMap2(fn, list):
# [BEGIN] YOUR CODE HERE
return None
# [END] YOUR CODE HERE
def main():
exampleList = cons(1, cons(2, cons(3, cons(4))))
plusOne = lambda x: x + 1
xTimesTwoPlusY = lambda x, y: x * 2 + y
def printXAndReturnY(x, y):
print(x)
return y
def unfoldCalculation(x, y):
return "fn(%s, %s)" % (str(x), str(y))
printAndReturn = print
print(listToString(exampleList), "should be 1 2 3 4")
print(listToString(myMap(plusOne, exampleList)), "should be 2 3 4 5")
print(myReduce(xTimesTwoPlusY, 0, exampleList), "should be 26")
print(myReduce(unfoldCalculation, "accm", exampleList), "should be fn(fn(fn(fn(accm, 1), 2), 3), 4)")
print(myReduceRight(xTimesTwoPlusY, 0, exampleList), "should be 20")
print(myReduceRight(unfoldCalculation, "accm", exampleList), "should be fn(1, fn(2, fn(3, fn(4, accm))))")
print("Below should output 4 3 2 1 each on a separate line:");
myReduceRight(printXAndReturnY, 0, exampleList)
print(listToString(myMap2(plusOne, exampleList)), "should be 2 3 4 5")
print("The two outputs below should be equal:")
print("First output:")
myMap(printAndReturn, exampleList)
print("Second output:")
myMap2(printAndReturn, exampleList)
if __name__ == "__main__":
main()
最終實現:
JavaScript實現:
// Refer to README for detailed instructions.
function cons(head, tail) {
return {
head: head,
tail: tail,
};
}
function listToString(list) {
if (!list) {
return '';
}
if (!list.tail) {
return list.head.toString();
}
return list.head.toString() + ' ' + listToString(list.tail);
}
function myMap(fn, list) {
if (!list) {
return undefined;
}
return cons(fn(list.head), myMap(fn, list.tail));
}
function myReduce(fn, accm, list) {
if (!list) {
return accm;
}
return myReduce(fn, fn(accm, list.head), list.tail);
}
function myReduceRight(fn, accm, list) {
// [BEGIN] YOUR CODE HERE
if (!list) {
return accm;
}
// State-of-the-art trampoline trick to prevent recursion stack overflow
const trampoline = (fun) => {
return function trampolined(...args) {
var result = fun(...args);
while (typeof result == 'function') {
result = result();
}
return result;
};
};
const reverseOperation = (origList) => {
const reverseCons = (cons, acc = []) => {
if (!cons) {
return undefined;
}
acc.push(cons.head);
if (cons.tail instanceof Object) {
return reverseCons(cons.tail, acc);
} else {
return acc.reverse();
}
};
const recursCons = (jsList = []) => {
if (jsList.length <= 0) {
return undefined;
} else {
return {
head: jsList[0],
tail: recursCons(jsList.slice(1)),
};
}
};
// IMMUTABLE
const newList = Object.assign({}, origList);
// Get the reversed version of Linklist in another plain representation
const reversedJSList = trampoline(reverseCons)(newList, []);
// Back assign the reversed plain representation to Linklist
const reversedLinkList = trampoline(recursCons)(reversedJSList);
return reversedLinkList;
};
const innerReducer = (fn_, accm_, list_) => {
if (!list_) {
return accm_;
}
return innerReducer(fn_, fn_(list_.head, accm_), list_.tail);
};
return trampoline(innerReducer)(fn, accm, reverseOperation(list));
// [END] YOUR CODE HERE
}
function myMap2(fn, list) {
// [BEGIN] YOUR CODE HERE
// State-of-the-art trampoline trick to prevent recursion stack overflow
const trampoline = (fun) => {
return function trampolined(...args) {
var result = fun(...args);
while (typeof result == 'function') {
result = result();
}
return result;
};
};
const polishedFn = (cur, acc) => {
let newAcc = {};
newAcc.tail = Object.keys(acc).length > 0 ? acc : undefined;
newAcc.head = () => fn(cur); // delay to keep the map order
return newAcc;
};
let newList = Object.assign(list);
const storeList = myReduceRight(polishedFn, {}, newList);
const activateStore = (store) => {
if (!store) return undefined;
store.head = store.head instanceof Function ? store.head() : store.head;
store.tail = activateStore(store.tail);
return store;
};
return trampoline(activateStore)(storeList);
// [END] YOUR CODE HERE
}
function main() {
let exampleList = cons(1, cons(2, cons(3, cons(4))));
let plusOne = (x) => x + 1;
let xTimesTwoPlusY = (x, y) => x * 2 + y;
let printXAndReturnY = (x, y) => {
console.log(x);
return y;
};
let unfoldCalculation = (x, y) => 'fn(' + x + ', ' + y + ')';
let printAndReturn = console.log;
console.log(listToString(exampleList), 'should be 1 2 3 4');
console.log(listToString(myMap(plusOne, exampleList)), 'should be 2 3 4 5');
console.log(myReduce(xTimesTwoPlusY, 0, exampleList), 'should be 26');
console.log(
myReduce(unfoldCalculation, 'accm', exampleList),
'should be fn(fn(fn(fn(accm, 1), 2), 3), 4)'
);
console.log(myReduceRight(xTimesTwoPlusY, 0, exampleList), 'should be 20');
console.log(
myReduceRight(unfoldCalculation, 'accm', exampleList),
'should be fn(1, fn(2, fn(3, fn(4, accm))))'
);
console.log('Below should output 4 3 2 1 each on a separate line:');
myReduceRight(printXAndReturnY, 0, exampleList);
console.log(listToString(myMap2(plusOne, exampleList)), 'should be 2 3 4 5');
console.log('The two outputs below should be equal:');
console.log('First output:');
myMap(printAndReturn, exampleList);
console.log('Second output:');
myMap2(printAndReturn, exampleList);
}
main();
訣竅:使用蹦床函式trampoline最佳化遞迴呼叫。
列印結果:
'1 2 3 4' 'should be 1 2 3 4'
'2 3 4 5' 'should be 2 3 4 5'
26 'should be 26'
'fn(fn(fn(fn(accm, 1), 2), 3), 4)' 'should be fn(fn(fn(fn(accm, 1), 2), 3), 4)'
20 'should be 20'
'fn(1, fn(2, fn(3, fn(4, accm))))' 'should be fn(1, fn(2, fn(3, fn(4, accm))))'
'Below should output 4 3 2 1 each on a separate line:'
4
3
2
1
'2 3 4 5' 'should be 2 3 4 5'
'The two outputs below should be equal:'
'First output:'
1
2
3
4
'Second output:'
1
2
3
4
Python實現:
# Refer to README for detailed instructions.
from __future__ import print_function
import copy
import types
class LinkedList:
def __init__(self, head, tail):
self.head = head
self.tail = tail
def cons(head, tail=None):
return LinkedList(head, tail)
def listToString(list):
if list is None:
return ""
if list.tail is None:
return str(list.head)
return str(list.head) + " " + listToString(list.tail)
def myMap(fn, list):
if list is None:
return None
return cons(fn(list.head), myMap(fn, list.tail))
def myReduce(fn, accm, list):
if list is None:
return accm
return myReduce(fn, fn(accm, list.head), list.tail)
def myReduceRight(fn, accm, list):
# [BEGIN] YOUR CODE HERE
if list is None:
return accm
def reverseOperation(origList):
# Get the reversed version of Linklist in another plain representation
def reverseCons(cons, acc = []):
if cons is None:
return None
acc.append(cons.head)
if cons.tail != None:
return reverseCons(cons.tail, acc)
else:
return acc[::-1]
# Back assign the reversed plain representation to Linklist
def recursCons(pyList = []):
if len(pyList) <= 0:
return None
else:
return cons(pyList[0], recursCons(pyList[1:]))
newList = copy.deepcopy(origList)
reversedPyList = reverseCons(newList, []);
reversedLinkList = recursCons(reversedPyList);
return reversedLinkList
def innerReducer(fn_, accm_, list_):
if list_ is None:
return accm_
return innerReducer(fn_, fn_(list_.head, accm_), list_.tail)
return innerReducer(fn, accm, reverseOperation(list));
# [END] YOUR CODE HERE
def myMap2(fn, list):
# [BEGIN] YOUR CODE HERE
def polishedFn(cur, acc):
newAcc = cons(None)
newAcc.tail = acc if isinstance(acc, LinkedList) else None
newAcc.head = lambda: fn(cur) # delay to keep the map order
return newAcc
newList = copy.deepcopy(list)
storeList = myReduceRight(polishedFn, {}, newList);
def activateStore(store):
if store is None:
return None
store.head = store.head() if isinstance(store.head, types.FunctionType) else store.head
store.tail = activateStore(store.tail)
return store
return activateStore(storeList)
# [END] YOUR CODE HERE
def main():
exampleList = cons(1, cons(2, cons(3, cons(4))))
plusOne = lambda x: x + 1
xTimesTwoPlusY = lambda x, y: x * 2 + y
def printXAndReturnY(x, y):
print(x)
return y
def unfoldCalculation(x, y):
return "fn(%s, %s)" % (str(x), str(y))
printAndReturn = print
print(listToString(exampleList), "should be 1 2 3 4")
print(listToString(myMap(plusOne, exampleList)), "should be 2 3 4 5")
print(myReduce(xTimesTwoPlusY, 0, exampleList), "should be 26")
print(myReduce(unfoldCalculation, "accm", exampleList), "should be fn(fn(fn(fn(accm, 1), 2), 3), 4)")
print(myReduceRight(xTimesTwoPlusY, 0, exampleList), "should be 20")
print(myReduceRight(unfoldCalculation, "accm", exampleList), "should be fn(1, fn(2, fn(3, fn(4, accm))))")
print("Below should output 4 3 2 1 each on a separate line:");
myReduceRight(printXAndReturnY, 0, exampleList)
print(listToString(myMap2(plusOne, exampleList)), "should be 2 3 4 5")
print("The two outputs below should be equal:")
print("First output:")
myMap(printAndReturn, exampleList)
print("Second output:")
myMap2(printAndReturn, exampleList)
if __name__ == "__main__":
main()
備註:Python的trampoline蹦床函式實現有些複雜,直接遞迴處理了。可參考文章Tail recursion in Python, part 1: trampolines)。
列印結果:
1 2 3 4 should be 1 2 3 4
2 3 4 5 should be 2 3 4 5
26 should be 26
fn(fn(fn(fn(accm, 1), 2), 3), 4) should be fn(fn(fn(fn(accm, 1), 2), 3), 4)
20 should be 20
fn(1, fn(2, fn(3, fn(4, accm)))) should be fn(1, fn(2, fn(3, fn(4, accm))))
Below should output 4 3 2 1 each on a separate line:
4
3
2
1
2 3 4 5 should be 2 3 4 5
The two outputs below should be equal:
First output:
1
2
3
4
Second output:
1
2
3
4