Do you like spaced repetition learning? Have you used Anki or Quizlet? Whether or not spaced repetition works for you, periodically working on flash-card like questions can be a lot of fun, and just may help you retain information.
For this course, recall questions tied to language-independent concepts are found at the bottom of the course notes pages.
Additional recall questions tied to specific programming languages can be found at the Programming Languages Explorations companion website.
Here are some questions that might be found on an in-class paper-and-pencil exam.
nonlocal
keyword to do this, while JavaScript does not.let x = 3; let x = 3;
Identifier 'x' has already been declared
). In Rust, two variables are declared, with the second shadowing the first. You do get a warning though, saying the first variable is never used, but there is no error.switch
statement? times
method on integers, while C-like languages require a loop with a counter that you don’t care about!Array
but in other languages like Java, Rust, and Swift there exist many parameterized array types?x ? y : z
of a typical statically-typed language, what type checking and inference rules would a compiler be required to enforce? x
must be boolean and the types of y
and z
must be compatible. Inference: the type of the entire expression is the least general type of both y
and z
.def f(): yield 1; yield 2; yield 3
, what is wrong with writing for i in f: print(i)
? What should we write instead?len("Hello, 世界") == 9
, but in Rust, "Hello, 世界".len() == 13
. Why is this? x = x
for example, you have to early exit before destroying anything.std::fill
, that fills the first 100 elements of the C-style array a
of integers with 0s. std::fill(a, a+100, 0)
Object.setPrototypeOf(myObject, theNewPrototype)
Promise.all(arrayOfPromises).then(callback)
int
, float
, and double
have fixed bit sizes? If so, what are they? If not, why not? list(t)
, for some tuple t
, produces the list containing the elements of t
in order. Can you write a function in Haskell to do the same thing, i.e., build a list from some arbitrary tuple? If so, give the Haskell function that does it. If not, explain why Haskell can’t do this. [1,12] < [1,3]
true but [1,42] < [1,3]
false? Why are both expressions false in Python and Haskell? <
turns arrays into strings before comparing, and "1,12" < "1,3"
. Python and Haskell do numeric element-by-element comparison and 1=1
and 12 > 3
.Stream.of(1,2,3).collect(Collectors.averagingInt(f))
for some particular function f
. What should f
be here? x -> x
or Integer::new
IntStream.range(1, n+1).reduce((x, y) -> x * y)
is a good attempt to produce the factorial of n
, but it doesn’t produce an integer. What does it produce? How can you modify it (slightly) to produce the integer factorial? reduce
, passing in 1
as the first argument. You can also do an .orElse(1)
at the end.r
was a (Java) Reader object. How do you get the lowercased lines of the reader into an array, eliminating all duplicates? r.lines().distinct().map(String::toLowerCase).toArray(String[]::new)
if-let
, while-let
, or guard-else
constructs required to access an element of an optional array? If not, what is a more concise way to access the element? a
and integer i
, you get the convenient a?[i]
.indirect
enumeration, using the definition “A list is either empty or a value (called the head) connected to a list (called the tail).” (Just define the type, you don’t need to include any methods.) indirect enum MyList<T> { case empty case node(head: T, tail: MyList) }
"string"
, str
, String
, std::string
, String
, [Char]
), or if not, what related type is it a member of? For example, if you were asked about Swift, you would say: “nil
is not a member of the type String
, but it is a member of String?
”. null
∈ the Null type.None
∈ NoneType
.nullptr
is not in the std::string
type, but ∈ char*
and std::string*
.Nothing
∈ Maybe String
.int c(int n) { int steps = 0; while (n > 1) { n = n % 2 == 0 ? n / 2 : 3 * n + 1; steps++; } return steps; }When written recursively, the function body can reduced to a single expression! Express this function as a one-liner in JavaScript, Python, Java, C++, Swift, Kotlin, or Haskell: your choice! If using Haskell, use guards rather than an
if
expression. function c(n) { return n<=1 ? 0 : n%2===0 ? 1+c(n/2) : 1+c(3*n+1) }
def c(n): return 0 if n<=1 else 1+c(n//2) if n%2==0 else 1+c(3*n+1)
static int c(int n) { return n<=1 ? 0 : n%2==0 ? 1+c(n/2) : 1+c(3*n+1); }
int c(int n) { return n<=1 ? 0 : n%2==0 ? 1+c(n/2) : 1+c(3*n+1); }
func c(n: Int) -> Int { return n<=1 ? 0 : n%2==0 ? 1+c(n/2) : 1+c(3*n+1); }
fun c(n: Int): Int { return n<=1 ? 0 : n%2==0 ? 1+c(n/2) : 1+c(3*n+1); }
c n | n<=1 = 0 | even n = 1+c(n `div` 2) | otherwise = 1+c(3*n+1)
The tail-recursive way is better. Left as an exercise to you.
a = [10,20,30,30,40,30]
, draw a picture of the world after b = delete 30 a
that shows you understand persistent data structures. (delete
is from Data.List
). a ------> 10 ----> 20 ----> 30 ----> 30 ----> 40 ----> 30 ^ | b ------> 10 ----> 20 ---------------+
(/ 2)
and (/) 2
different functions, but (elem 21)
and (elem) 21
are the same function? /
is an infix operator, (/ 2)
is a right operator section which divides its argument by 2, but (/) 2
is a function which returns 2 divided by its argument! Alternatively, since elem
is a regular prefix operator, there is no section here, and (elem 2)
and (elem) 2
are just putting parentheses where they already belong anyway.split
to break a string into a list of characters, and join
to turn an array of characters into a string. How do you do these two operations in Swift? Array(s)
breaks a string into a list of chars, String(a)
joins a list of strings into a single string.Here are some problems that require some thinking, and some actual work. They may involve writing little scripts, or making sketches. They aren’t exactly short-answer problems.
(-b + sqrt(4 × a × c)) / (2 × a)Do you need a special symbol for unary negation? Why or why not?
function f() { return { x: 5 } }
let b = 8 let a = b + b (4 + 5).toString(16)
let place = "mundo" ["Hola", "Ciao"].forEach((command) => { console.log(command + ", " + place) })
const sayHello = function () { console.log("Hello") } (function() { console.log("Goodbye") }())What is being illustrated in each of the above? Go, Python, Scala, and Ruby all allow line endings to end statements and you don't hear people complaining about them the way they do about JavaScript. Pick one of these four languages and show why they don’t have problems with the four "problematic" cases of JavaScript.
f(2)
should return 2, but if local variables were allocated statically, it would return 3.
void f(int x) { int a = 2; int b; if (x < 0) { a = 3; return 0; } else { b = f(-x); return b + a; } }
var x = 100; function setX(n) {x = n;} function printX() {console.log(x);} function first() {setX(1); printX();} function second() {var x; setX(2); printX();} setX(0); first(); printX(); second(); printX();
var x = 1 function h() {var x = 9; return g();} function f() {return x;} function g() {var x = 3; return f();} print f() * h() - x
var x = 1 function h() {var x = 0; return g()} function f() {return x} function g() {var x = 8; return f()} print f() - h() + x
function f(a) { var x = a - 1 function g() { print x + 25 } h(g) } function h(p) { var x = 80; p() } f(7)
function g(h) { var x = 2; h() } function main() { var x = 5 function f() { print x + 3 } g(f) } main()
x = 3 def f(): print x def g(): x = 5 f() g()
x = 3 def f(); puts x; end def g(); x = 5; f(); end g()
public void fail() { class Failure extends RuntimeException {} throw new Failure(); }
std::array
and one with std::vector
, and use std::fill
if appropriate.IntStream.range(1, n).reduce((x, y) -> x * y)
is a good attempt to produce the factorial of n
, but it doesn’t produce an integer. What does it produce? How can you modify it (slightly) to produce the integer factorial?Operator(s) | Assoc | Arity | Fixity |
---|---|---|---|
§ # | 1 | Prefix | |
∀ ⊥ | 1 | Postfix | |
% • ¥ | L | 2 | Infix |
⊗ ∇ ⇒ | R | 2 | Infix |
# # A ⇒ # B ∇ C ¥ D % E ∇ F % G • H ⊥ ∀ ⊗ § I ∀
a–f(b)–c*d
can produce different values depending on how a compiler decides to order, or even parallelize operations. Give a small program in the language of your choice (or even one of your own design) that would produce different values for this expression for different evaluation orders. Please note that by "different evaluation orders" we do not mean that the compiler can violate the precedence and associativity rules of the language.TypeError
. What are the cases in which this error is thrown?Dog
were a subclass of class Animal
, then objects of class Dog[]
would be compatible with the type Animal[]
. Write a fragment of Java code that shows that this requires dynamic type checking. Include in your answer a well-written explanation that shows you truly understand the difference between static and dynamic types. Dog
and Rat
are subclasses of Animal, this code
Animal[] pets = new Dog[4]; pets[0] = new Rat();compiles fine but when executed throws an
ArrayStoreException
, that's right, a run-time typecheck error. This means Java is NOT 100% statically typed because this typecheck occurs at run time. A language can only be called 100% statically typed if all type conflicts are detected at compile time.local fun split [] = ([],[]) | split [h] = ([h], []) | split (x::y::t) = let val (s1,s2) = split t in (x::s1,y::s2) end fun merge c ([], x) = x | merge c (x, []) = x | merge c (h1::t1, h2::t2) = if c(h1,h2)<0 then h1::merge c(t1,h2::t2) else h2::merge c(h1::t1,t2); in fun sort c [] = [] | sort c x = let val (p, q) = split x in merge c(sort c p, sort c q) end; end;
c
within sort
split
merge
y
in the third clause of split
?sort
and explain why it is not what you would expect.A
and B
.
struct A {B* x; int y;}; struct B {A* x; int y;};Suppose the rules for this language stated that this language used structural equivalence for types. How would you feel if you were a compiler and had to type check an expression in which an
A
was used as a B
? What problem might you run into?struct { int n; char c; } A[10][10];If the address of
A[0][0]
is 1000 (decimal), what is the
address of A[3][7]
?
double *a[n]; double (*b)[n]; double (*c[n])(); double (*d())[n];
double *a[n]; double (*b)[n]; double (*c[n])(); double (*d())[n];
double (*f(double (*)(double, double[]), double)) (double, ...);Describe rigorously, in English, the type of
f
. To→ From↴ | Bool | Num | Str | Arr | Dict | Fun |
---|---|---|---|---|---|---|
Null | false | 0 | "" | [] | {} | |
Bool | false→0 true→1 | |||||
Num | 0→false else true | |||||
Str | ""→false else true | |||||
Arr | []→false else true | |||||
Dict | {}→false else true | |||||
Fun | true |
x
, make it so that *x == x
. If this is not possible, state why it is not possible.x
, make it so that *x == x
. If this is not possible, state why it is not possible.p == *p
. If it is not possible to do so, state why no such variable can be defined.(x += 7) *= z
but you can't say this in C. Explain the reason why, using precise, technical terminology. See if this same phenomenon holds for conditional expressions, too. What other languages behave like C++ in this respect?while (true) line := readLine(); if isAllBlanks(line) then exit end; consumeLine(line); end;Show how you might accomplish the same task using a while or repeat loop, if mid-test loops were not available. (Hint: one alternative duplicates part of the code; another introduces a Boolean flag variable.) How do these alternatives compare to the mid-test version?
If
in Java or C or
JavaScript, such that the call If(c, e1, e2)
would return e1
if c
evaluated to true, and e2
if it evaluated to false.
Show why, in these languages, such a function is absolutely not the same as
the conditional expression c?e1:e2
. You can show a code fragment
that would return different results based on whether the function
were called versus the conditional expression were evaluated.
(p==null ? null : p.value)
is null-safe, whereas
the call If(p==null, null, p.value)
is not: it throws a NullPointerException
if p
is null. A more blatant example can be seen in the difference between
(true ? 1 : formatMyHardDrive())
and
If(true, 1, formatMyHardDrive())
.
int first_zero_row = -1; /* assume no such row */ int i, j; for (i = 0; i < n; i++) { /* for each row */ for (j = 0; j < n; j++) { /* for each entry in the row */ if (A[i][j]) goto next; /* if non-zero go on to the next row */ } first_zero_row = i; /* went all through the row, you got it! */ break; /* get out of the whole thing */ next: ; } /* first_zero_row is now set */The intent of the code is to set
first_zero_row
to the index of the first all-zero row, if any, of an n × n matrix, or -1 if no such row exists. Do you find the example convincing? Is there a good structured alternative in C? In any language? Give answers in the form of a short essay. Include a good introductory section, a background section describing views on the goto
statement throughout history, a beefy section analyzing alternatives to Rubin's problem, and a good concluding section. Talk about solutions in at least three languages. You may find higher order functions such as every
, some
, and forEach
to be helpful.function ss(a) { function s(i, acc) { return i == a.length ? acc : s(i+1, a[i]*a[i]+acc) } return s(0, 0); }A possible Ruby solution is:
def ss(a) s = Proc.new{|i,acc| i==a.length ? acc : s[i+1, a[i]*a[i]+acc]} s[0, 0] end
i
of the array contains a function (or pointer to a function if your language demands it) that divides its argument by the square of i
.withoutEvens([4,6,true,null,2.3,"xyz"]) => [6,null,"xyz"]
Given: a list [a0, a1, ..., an-1]
,
Return: a0*a1 + a2*a3
+ a4*a5 + ....
For example, if given [3, 5, 0, 28, 4, 7]
we return
15 + 0 + 28 = 43. If there are an odd number of
elements in the list, assume there is an extra 1
for padding.
Here is, by the way, a non-tail-recursive formulation:
fun sum_of_prods [] = 0 | sum_of_prods (x::nil) = x | sum_of_prods (x::y::t) = x * y + sum_of_prods t;
eval
, that accepts an array of integers $a$, and a function $f$, and returns an array of functions, each of which, when called, invokes $f$ on the corresponding element of $a$. For example, if your function was called $g$, then calling g([6,3,1,8,7,9], dog)
would return an array of functions $p$ such that, for example, calling p[3]()
would invoke dog(8)
.
function g(a, f) { var b = []; for (var i = 0; i < a.length; i++) { b[i] = function(i){return function(){f(a[i])};}(i); } return b; }
square
and addSix
the obvious way, and we called this new method weird
, then:
[4, 6, 7, 3, 5, 2, 4].weird(addSix, square)would return the function
z
such that
z(2) == 55because the element at index 2 within the array is 7 and $7^2 + 6 = 55$.
Weekdays := Day_Set(False, True, True, True, True, True, False);
we could construct the aggregate directly in the variable Weekdays
. Give an example of an assignment statement that illustrates the necessity of constructing an aggregate in temporary storage (before copying to the target variable).
AngleReader
which displays a picture of a circle and notifies all its listeners
of the angle, in degrees, that the mouse cursor makes with the horizontal
axis of the circle as the mouse moves over it.
val dot = let fun transpose ... = in .... end;The transpose function should work like this
transpose ([x1,...,xn],[y1,..,yn]) = [(x1,y1),...,(xn,yn)]raising
Domain
if the arrays have different lengths. The body
of the definition of dot (between the in
and end
) should
contain only instances of the functions transpose
, o
, foldr
,
map
, op*
, op+
, and the value 0.
x = 1; y = [2, 3, 4]; function f(a, b) {b++; a = x + 1;} f(y[x], x); print x, y[0], y[1], y[2];
2 2 2 4
.
y[1]
, and that becomes 2 + 1 = 3, so it prints
2 2 3 4
.
b++
changes x
immediately
so x becomes 2. Then, since a refers to the expression
{y[x]
}, it will need to compute y[2]
which is 3.
The script prints 2 2 3 3
.
x = 1 y = [2, 3, 4] function f(a, b) {a--; b = x - 1} f(x, y[x]) print x, y[0], y[1], y[2]
x = 1; y = 2; function f(a, b) {a = 3; print b, x;} f(x, x + y); print x;
void do_something() { int i; printf("%d ", i++); } int main() { int j; for (j = 1; j <= 10; j++) do_something(); }Local variable
i
in subroutine do_something
is never initialized. On many systems, however, the program will display repeatable behavior, printing 0 1 2 3 4 5 6 7 8 9. Suggest an explanation. Also explain why the behavior on other systems might be different, or nondeterministic.
with Ada.Text_IO, Ada.Integer_Text_IO; use Ada.Text_IO, Ada.Integer_Text_IO; procedure P is A: Integer := 4; type T is access Integer; B: T := new Integer'(4); C: T := new Integer'(4); procedure Q (X: in out Integer; Y: T; Z: in out T) is begin X := 5; Y.all := 5; Z.all := 5; end Q; begin Q (A, B, C); Put (A); Put (B.all); Put (C.all); end P;
call f(2) print* 2 stop end subroutine f(x) x = x + 1 return end
nextOdd
(or next_odd
or nextodd
depending on the language’s culture). The first time you call this subroutine you get the value 1. The next time, you get a 3, then 5, then 7, and so on. Show a snippet of code that uses this subroutine from outside the module. Is it possible to make this module hack-proof? In other words, once you compile this module, can you be sure that malicious code can't do something to disrupt the sequence of values resulting from successive calls to this function?"MI"
, (2) a function which retrieves the value of this string, and (3) four other methods, each of which changes the string to another string according to the following:
"I"
, add "U"
, e.g. "MI"
→ "MIU"
.
"MIIU"
→ "MIIUIIU"
.
"III"
with "U"
, e.g. "MUIIIU"
→ "MUUU"
.
"UU"
, e.g. "MIUUUI"
→ "MIUI"
.
m
, s
, or y
?
Container
(here using C++ syntax):
template <class Item> class Container { public: unsigned numberOfItems() {return currentSize;} bool isEmpty() {return currentSize == 0;}; virtual void flush() = 0; ~Container() {flush();} private: unsigned currentSize; };
Here the idea is that each particular (derived) container class shall
implement its own flush()
operation (which makes sense because
different containers are flushed in different ways: there may be
arrays, linked lists, rings or hashtables used in the representation),
and when a container is destroyed its flush()
operation will
be automatically invoked. However, the idea is flawed and the
code as written causes a terrible thing to happen. What happens?
Person
and
subclasses for different jobs (e.g., programmer, manager,
ticket agent, flight attendant, supervisor, student, etc.)
is a lousy design. Give an alternative.
dict
, indexed by
semester name, whose values are dict
s mapping course numbers to grades.
Supply methods to read the id, read and write the name, read and
write the birthday, get the grade for a given course (in any semester),
and add an item to the transcript. Did you encapsulate (i.e. hide) the
properties inside the class so they were protected from direct access from
outside the class? Why or why not?
class Item { private int id; private static int numberOfItemsCreated = 0; public Item() {id = numberOfItemsCreated++;} // pretend that there are more members here... };As you can see, every item that gets created will get a unique id. Because this pattern occurs frequently, it might be nice to generalize this and make make something reusable out of it so we don't have to write this code inside every class that needs ids. Perhaps we need an interface or abstract class. Tell me why these two suggestions won't work with a detailed, technical answer. Then tell me something that will work. (Note: there is nothing wrong with the access modifiers above; the problems with my two suggestions have to do with the nature of interfaces and abstract classes.)
class C {protected: int x; ...}; C c;
then your job is to assign a new value to c.x
.
Assume there are no public operations of C
that
modify x
that you know of. Also, do not use
any preprocessor tricks (like #define protected
public
).
class C {int x;}; C c; C* p = &c; cout << p;and there is no compile-time nor link time error, despite the fact that
operator<<(C*)
is not a member of ostream
(since that class was declared before you declared C
), nor for
that matter did anyone declare the global function
ostream& operator<<(ostream&, C*);So why does it all work? Explain exactly what gets printed and why.
class Base { public int a; public String b; } class Derived extends Base { public float c; public int b; }Does the representation of a
Derived
object contain one b
field or two? If two, are both accessible, or only one? Under what circumstances? Answer for C++, Java, Python, Kotlin, Scala, or any similar language.Dog
is an abstract class in a C++ program, why is it acceptable to declare variables of type Dog*
, but not of type Dog
? Does this problem even make sense to ask in Java or Python?Put
procedure at the same time, their outputs may be interleaved. (This could produce amusing and even distasteful results, e.g. writing "sole" in parallel with "ash") Show how to set things up so only one task is writing at a time.
E
of task T
only if T
has not terminated.
if not T'Terminated then T.E; end if;
let countDown = function () { let i = 10; let update = function () { document.getElementById("t").innerHTML = i; if (i-- > 0) setTimeout(update, 1000); } update(); }The second argues that this is better:
let countDown = function () { let update = function (i) { document.getElementById("t").innerHTML = i; if (i-- > 0) setTimeout(function () {update(i)}, 1000); } update(10); }Your role is to figure out which programmer is right, if any. Make a fairly extensive list of the pros and cons of each approach. Your list will be graded on completeness, neatness, correct usage of terminology (don't forget to mention "anonymous function" and "closure"), and how articulately you express yourself. Bad grammar will impact your grade negatively. You may want to consider readability and (especially) performance.
Keep sharp by practicing your programming skills.
#
character.divmod
operator, write two solutions, one using the operator and one that does not.fringe
that takes a tree as argument and returns an ordered list of its leaves. Then we can say:
def same_fringe(t1, t2): return fringe(t1) == fringe(t2)
same_fringe
in as many languages as you can.
std:array
, and using std::vector
.f
and g
) such that every time you call f
, you get back 10 less than the result of the previous call to f
or g
, and every time you call g
, you get back three times the absolute value of the result of the last call to f
or g
. Here's a catch: you must arrange things so that any "state" you need is completely private to f and g.(*
and ending with *)
that do not contain *)
. Note comments "don't nest".select
statements and (2) using a mutex.There are a number of skills beyond just slinging code that will set you apart from peers. These include being able to explain what programs are saying (or doing), being able to explain why certain programs are incorrect or insecure, and being able to explain why certain programs are correct or secure. Here are a few such problems.
a = [lambda: i for i in range(10)] b = [a[i]() for i in range(10)]
At the end of this script, what is the value of b? Explain in detail why this is. Support your analysis with a sketch.
var
that you should never use in your own code:
var a = []; for (var i = 0; i < 10; i++) { a[i] = function () {return i;} } var b = []; for (var j = 0; j < 10; j++) { b[j] = a[j](); }
At the end of this script, what is the value of b? Explain in detail why this is. Support your analysis with a sketch.
dog="spike"; class <<dog; def bark; "arf"; end; end; dog.bark
"arf"
, since we essentially gave this one
dog its own bark
method, by attaching it to its own
singleton class.
class <<"sparky"; def bark; "woof"; end; end; "spraky".bark
class <<"sparky"; def bark; "woof"; end; end; "sparky".bark
dog="sparky"; class <<dog; def bark; "woof"; end; end; dog.bark
"woof"
, because we attached the bark method
to the object referenced by the variable dog, through
its metaclass.
class <<"sparky"; def bark; "woof"; end; end; "sparky".bark
NoMethodError
because the object in whose
metaclass we added the bark method WAS NOT the same
string object we tried to call bark
on.
class <<:sparky; def bark; "woof"; end; end; :sparky.bark
TypeError
because Ruby does not allow metaclasses
on symbol objects. Maybe this is because Ruby treats
symbols in such a super-special way an never puts them
in the object pool. It probably holds them as plain
old small integers.class <<2; def bark; "woof"; end; end; 2.bark
TypeError
for the same reason as for symbols.
class Counter attr_reader :val; @val = 0; def bump(); @val += 1; end end Counter.new.bump; Counter.new.val
def dot(a, b) a.zip(b).map{|x,y| x*y}.inject{|x,y| x+y} end
assert_equal(dot([], []), 0)
and it failed because her
method returned nil
, not 0.
def dot(a, b) a.zip(b).inject(0){|x,y| x+y[0]*y[1]} endEventually she wrote a unit test with the first array longer than the second and got a
TypeError
. She fixed that by raising
an ArgumentError
if the arguments to dot had different
lengths. Show her fixed up method.
def dot(a, b) raise ArgumentError if a.length != b.length a.zip(b).inject(0) {|x,y| x+y[0]*y[1]} end
class Array def *(a) ..............her code here.............. end endAnd the unit tests worked:
[3,4,2] * [1,5,0] == 23
for example.
But this fancy *
definition had a very nasty side effect,
which, if she did this in real production code, would have
probably broken something big time. What went down with
this definition?
*
operator in the array class; any new definition replaces
the old one. Old code using Array * int and Array * string will break badly,
because ints and strings can’t be converted to arrays.
class Pair implements Cloneable { private Object first, second; public Pair(Object x, Object y) {first = x; second = y;} public first() {return first;} public second() {return second;} }