Created
June 13, 2012 08:45
-
-
Save tomtung/2922865 to your computer and use it in GitHub Desktop.
Seven Languages in Seven Weeks
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/ruby | |
############# Day 1 ############# | |
# Print the string “Hello, world.” | |
puts 'Hello, world' | |
# For the string “Hello, Ruby”, find the index of the word“Ruby”. | |
puts 'Hello, Ruby'.index('Ruby') | |
# Print yourname ten times. | |
10.times {puts 'Tom'} | |
# Print the string “This is sentence number 1”, | |
# where the number 1 changes from 1 to 10. | |
(1..10).each {|i| puts "This is sentence number #{i}"} | |
# Bonus problem: If you're feeling the need for a little more, write | |
# a program that picks a random number. Let a player guess the | |
# number, telling the player whether the guess is too low or too high. | |
num = rand(10) | |
while true | |
print 'Guess: ' | |
guess = gets().to_i | |
if guess < num | |
puts 'Too small!' | |
elsif guess > num | |
puts 'Too large!' | |
else | |
puts 'Correct!' | |
break | |
end | |
end | |
############# Day 2 ############# | |
# Print the contents of an array of sixteen numbers, four numbers at a time | |
array = (1..16).to_a | |
array.each_slice(4){|x| p x} | |
# The Tree class was interesting, but it did not allow you to specify | |
# a new tree with a clean user interface. Let the initializer accept a | |
# nested structure with hashes and arrays. You should be able to | |
# specify a tree like this: {'grandpa' => {' dad' => {'child 1' => {}, 'child | |
# 2' => {} }, 'uncle' => {'child 3' => {}, 'child 4' => {} } } }. | |
class Tree | |
attr_accessor :children, :node_name | |
def initialize(args) | |
if not args.empty? | |
a = args.first | |
@node_name = a[0] | |
@children = | |
if a[1].empty? | |
[] | |
else | |
a[1].collect{|k,v| Tree.new({k=>v})} | |
end | |
end | |
end | |
def visit_all(&block) | |
visit &block | |
children.each {|c| c.visit_all &block} | |
end | |
def visit(&block) | |
block.call self | |
end | |
end | |
tree = Tree.new ({'grandpa' => | |
{ | |
'dad' => | |
{ | |
'child 1' => {}, | |
'child 2' => {} | |
}, | |
'uncle' => | |
{ | |
'child 3' => {}, | |
'child 4' => {} | |
} | |
} | |
}) | |
tree.visit_all{|node| puts node.node_name} | |
# Write a simple grep that will print the lines of a file having any | |
# occurrences of a phrase anywhere in that line. You will need to do | |
# a simple regular expression match and read lines from a file. (This | |
# is surprisingly simple in Ruby.) If you want, include line numbers. | |
def grep(filename, pattern) | |
f = File.new(filename) | |
f.each {|line| puts "#{f.lineno}:\t#{line}" if pattern === line} | |
end | |
grep("ruby.rb", /def/) | |
############# Day 3 ############# | |
# Modify the CSV application to support an each method to return a | |
# CsvRow object. Use method_missing on that CsvRow to return the value | |
# for the column for a given heading. | |
# For example, for the file: | |
# one, two | |
# lions, tigers | |
# allow an API that works like this: | |
# csv = RubyCsv.new | |
# csv.each {|row| puts row.one} | |
# This should print "lions". | |
module ActsAsCsv | |
def self.included(base) | |
base.extend ClassMethods | |
end | |
module ClassMethods | |
def acts_as_csv | |
include InstanceMethods | |
end | |
end | |
module InstanceMethods | |
class CsvRow | |
def method_missing(name, *args, &block) | |
value = self[name] | |
if value | |
value | |
else | |
super.method_missing(name, *args, &block) | |
end | |
end | |
def [](col_name) | |
index = @headers.find_index(col_name.to_s) | |
if index | |
contents[index] | |
else | |
nil | |
end | |
end | |
attr_accessor :headers,:contents | |
def initialize(headers, contents) | |
@headers = headers | |
@contents = contents | |
end | |
end | |
def read | |
@csv_contents = [] | |
filename = self.class.to_s.downcase + '.txt' | |
file = File.new(filename) | |
@headers = file.gets.chomp.split(',') | |
file.each do |row| | |
@csv_contents << row.chomp.split(',') | |
end | |
end | |
attr_accessor :headers, :csv_contents | |
def initialize | |
read | |
end | |
def each(&block) | |
@csv_contents. | |
collect{|contents| CsvRow.new(@headers, contents)}. | |
each(&block) | |
end | |
end | |
end | |
class RubyCsv | |
include ActsAsCsv | |
acts_as_csv | |
end | |
csv = RubyCsv.new | |
csv.each {|row| puts row.one} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/local/bin/io | |
/************ Day 1 ************/ | |
// Execute the code in a slot given its name. | |
Object getSlot("list") call(1,2,3,4) | |
/************ Day 2 ************/ | |
// Write a program to find the nth Fibonacci number. | |
// Recursion | |
fib := method(n, if(n < 3, 1, fib(n - 1) + fib(n - 2))) | |
// Loop | |
fib := method(n, | |
a := b := sum := 1 | |
for(i, 3, n, | |
sum = a + b; a = b; b = sum) | |
sum | |
) | |
// Change / to return 0 if the denominator is zero. | |
Number dividedBy := Number getSlot("/") | |
Number / = method(divisor, if(divisor == 0, 0, self dividedBy(divisor))) | |
// Add up all of the numbers in a two-dimensional array. | |
array2d := list(list(1,2,3), list(4,5,6), list(7,8,9)) | |
array2d sum2d := method(self map(a, a sum) sum) | |
// Add a slot called myAverage to a list that computes the average of all the numbers in a list. | |
List myAverage := method(sum / size) | |
// Write a prototype for a two-dimensional list: | |
// - dim(x, y) | |
// - set(x,y,value), get(x, y) | |
// - transpose | |
Matrix := Object clone do( | |
indexOf := method(x, y, | |
if(rowMajor, x * dim2 + y, y * dim1 + x)) | |
set := method(x, y, value, content atPut(indexOf(x, y), value)) | |
get := method(x, y, content at(indexOf(x, y))) | |
transposed := method( | |
m := Matrix clone | |
m dim1 = dim2; m dim2 = dim1 | |
m rowMajor = rowMajor not | |
m content = list() copy(content) | |
m | |
) | |
save := method(path, | |
File with(path) open write(self serialized) close | |
self | |
) | |
load := method(path, doFile(path)) | |
rowMajor := true | |
dim1 := 1 | |
dim2 := 1 | |
content := list() setSize(1) | |
) | |
dim := method(x, y, | |
m := Matrix clone | |
m rowMajor = true | |
m dim1 = x | |
m dim2 = y | |
m content = list() setSize(x*y) | |
m | |
) | |
// Write a program that gives you ten tries to guess a random number from 1-100. | |
number := Random value(1, 100) round | |
for(i, 1, 10, | |
guess := File standardInput readLine("Enter your guess: ") asNumber | |
if(guess == number, break) | |
distance := (guess - number) abs | |
if(?prevDistance and distance != prevDistance) then( | |
if(distance < prevDistance, | |
writeln("Hotter"), | |
writeln("Colder") | |
) | |
) | |
prevDistance := distance | |
) | |
writeln("The number is: ", number) | |
/************ Day 3 ************/ | |
// Enhance the XML program | |
// - to add spaces to show the indentation structure. | |
// - if the first argument is a map (use the curly brackets syntax), | |
// add attributes to the XML program. | |
OperatorTable addAssignOperator(":", "toAttrStr") | |
Builder := Object clone do ( | |
toAttrStr := method(key, value, | |
" " .. doString(key) .. "=\"" .. value .. "\"" | |
) | |
curlyBrackets := method( | |
call evalArgs join | |
) | |
indentNum := 0 | |
indent := method("\t" repeated(indentNum)) | |
forward := method( | |
name := call message name | |
attr := if( | |
call hasArgs and call argAt(0) name == "curlyBrackets", | |
doMessage(call argAt(0)), | |
"" | |
) | |
writeln(indent, "<", name, attr, ">") | |
indentNum = indentNum + 1 | |
call message arguments slice(if(attr isEmpty, 0, 1)) foreach (arg, | |
content := self doMessage(arg) | |
if(content, writeln(indent, content)) | |
) | |
indentNum = indentNum - 1 | |
writeln(indent, "</", name, ">") | |
) | |
) | |
// Create a list syntax that uses brackets. | |
curlyBrackets := method(call evalArgs) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/pl -t main -s | |
% | |
% Note: SWI-Prolog (rather than GNU-Prolog) is used here. | |
% You may need to change the path following the shebang. | |
% | |
% For ins/2, all_distinct/2, and transpose/2 | |
:- use_module(library(clpfd)) . | |
%%%%%%%%%%%%%%%%%%%% Day 1 %%%%%%%%%%%%%%%%%%%% | |
% | |
% Represent some of your favorite books and authors. | |
% | |
book_author(joel_on_software, joel_spolsky) . | |
book_author(javascript_the_good_parts, douglas_crockford) . | |
book_author(hackers_and_painters, paul_graham) . | |
book_author(effective_cpp, scott_meyers) . | |
book_author(more_effective_cpp, scott_meyers) . | |
book_author(godel_escher_bach, douglas_hofstadter) . | |
% | |
% Find all books in your knowledge base written by one author. | |
% | |
find_books:- | |
findall(Book, book_author(Book, scott_meyers), BookList), | |
writeln(BookList) . | |
%%%%%%%%%%%%%%%%%%%% Day 2 %%%%%%%%%%%%%%%%%%%% | |
% | |
% Reverse the elements of a list. | |
% | |
list_reverse(List, Reversed) :- | |
list_reverse(List, [], Reversed) . | |
list_reverse([], Reversed, Reversed) . | |
list_reverse([Head|Tail], Accumulator, Reversed) :- | |
list_reverse(Tail, [Head|Accumulator], Reversed) . | |
test_list_reverse :- | |
list_reverse([1,2,3,4,5], Reversed), | |
writeln(Reversed). | |
% | |
% Find the smallest element of a list. | |
% | |
list_min(Min, [Head|Tail]) :- list_min(Min, Head, Tail) . | |
list_min(Min, Min, []) . | |
list_min(Min, PrevMin, [Head|Tail]) :- | |
CurrMin is min(PrevMin, Head), | |
list_min(Min, CurrMin, Tail) . | |
test_list_min :- | |
list_min(Min, [2,4,3,1,5]), | |
writeln(Min) . | |
% | |
% Sort the elements of a list. | |
% | |
partition([], _, [], []) . | |
partition([Head|Tail], Pivot, [Head|TailLeft], Right) :- | |
Head =< Pivot, | |
partition(Tail, Pivot, TailLeft, Right) . | |
partition([Head|Tail], Pivot, Left, [Head|TailRight] ) :- | |
Head > Pivot, | |
partition(Tail, Pivot, Left, TailRight) . | |
quick_sort(List, Sorted) :- quick_sort(List, [], Sorted) . | |
quick_sort([], Sorted, Sorted) . | |
quick_sort([Pivot|Tail], PrevRightSorted, Sorted) :- | |
partition(Tail, Pivot, Left, Right), | |
quick_sort(Right, PrevRightSorted, RightSorted), | |
quick_sort(Left, [Pivot|RightSorted], Sorted) . | |
test_quick_sort :- | |
quick_sort([1,5,4,2,3,2,4,1,5], Sorted), | |
writeln(Sorted) . | |
%%%%%%%%%%%%%%%%%%%% Day 3 %%%%%%%%%%%%%%%%%%%% | |
% | |
% Modify the Sudoku solver to work on 9x9 puzzles. | |
% Make the Sudoku solver print prettier solutions. | |
% | |
split([], _, [], []) . | |
split(List, 0, [], List) . | |
split([Head|Tail], Count, [Head|TailLeft], Right) :- | |
Count1 is Count - 1, | |
split(Tail, Count1, TailLeft, Right) . | |
group([], _, []) . | |
group(List, Count, [Left|RightGrouped]) :- | |
split(List, Count, Left, Right), | |
group(Right, Count, RightGrouped) . | |
sudoku_n(Sudoku, N) :- | |
length(Sudoku, Length), | |
N is round(sqrt(Length)) . | |
sudoku_rows_columns(Sudoku, Rows, Columns) :- | |
sudoku_n(Sudoku, N), | |
group(Sudoku, N, Rows), | |
transpose(Rows, Columns) . | |
sudoku_blocks(Sudoku, Blocks) :- | |
sudoku_n(Sudoku, N), | |
M is round(sqrt(N)), | |
group(Sudoku, M, G1), | |
group(G1, M, G2), | |
group(G2, M, G3), | |
maplist(transpose, G3, T), | |
flatten(T, F), | |
group(F, N, Blocks) . | |
sudoku(Puzzle, Solution) :- | |
Solution = Puzzle, | |
sudoku_n(Puzzle, N), | |
Solution ins 1..N, | |
sudoku_rows_columns(Solution, Rows, Columns), | |
sudoku_blocks(Solution, Blocks), | |
maplist(all_distinct, Rows), | |
maplist(all_distinct, Columns), | |
maplist(all_distinct, Blocks) . | |
sudoku_print(Sudoku) :- | |
sudoku_rows_columns(Sudoku, Rows, _), | |
maplist(writeln, Rows) . | |
test_sudoku :- | |
Sudoku4x4 = | |
[_, _, 2, 3, | |
_, _, _, _, | |
_, _, _, _, | |
3, 4, _, _ | |
], | |
sudoku(Sudoku4x4, Solution4x4), | |
sudoku_print(Solution4x4), nl, | |
Sudoku9x9 = | |
[5, 3, _, _, 7, _, _, _, _, | |
6, _, _, 1, 9, 5, _, _, _, | |
_, 9, 8, _, _, _, _, 6, _, | |
8, _, _, _, 6, _, _, _, 3, | |
4, _, _, 8, _, 3, _, _, 1, | |
7, _, _, _, 2, _, _, _, 6, | |
_, 6, _, _, _, _, 2, 8, _, | |
_, _, _, 4, 1, 9, _, _, 5, | |
_, _, _, _, 8, _, _, 7, 9 | |
], | |
sudoku(Sudoku9x9, Solution9x9), | |
sudoku_print(Solution9x9) . | |
% | |
% Solve the Eight Queens problem by taking a list of queens. | |
% | |
n_queens(N, Columns) :- | |
findall(Row, between(1, N, Row), Rows), | |
permutation(Rows, Columns), | |
maplist(plus, Rows, Columns, Diag1), | |
maplist(plus, Rows, Diag2, Columns), | |
maplist(all_distinct, [Diag1, Diag2]) . | |
test_n_queen :- | |
n_queens(8, Columns), | |
writeln(Columns) . | |
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
main:- | |
find_books, nl, | |
test_list_reverse, nl, | |
test_list_min, nl, | |
test_quick_sort, nl, | |
test_sudoku, nl, | |
test_n_queen, nl, | |
halt . |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
exec scala "$0" "$@" | |
!# | |
/************ Day 1 ************/ | |
/* | |
Implement the game Tic-Tac-Toe. | |
*/ | |
class TicTacToe private(private val board: List[Char], val currentPlayer: Char) { | |
def this() = this(List.fill(9)(TicTacToe.Vacant), TicTacToe.Player1) | |
import TicTacToe._ | |
lazy val winner = { | |
val lines = { | |
val rows = board.grouped(3).toList | |
val columns = rows.transpose | |
val diagonals = List(0, 4, 8, 2, 4, 6).map(board).grouped(3) | |
(diagonals ++ rows ++ columns).map(_.mkString).toSet | |
} | |
if (lines.contains(Player1.toString * 3)) Some(Player1) | |
else if (lines.contains(Player2.toString * 3)) Some(Player2) | |
else None | |
} | |
lazy val boardFull = board.count(_ == Vacant) == 0 | |
def nextMove(position: Int) = { | |
assert(winner.isEmpty) | |
assert(board(position) == Vacant) | |
val nextBoard = board.take(position) ++ (currentPlayer :: board.drop(position + 1)) | |
val nextPlayer = currentPlayer match { | |
case Player1 => Player2 | |
case Player2 => Player1 | |
} | |
new TicTacToe(nextBoard, nextPlayer) | |
} | |
override def toString = toString(indexed = false) | |
def toString(indexed: Boolean) = { | |
val b = | |
if (indexed) | |
board.zipWithIndex.map({ | |
case ('_', i) => i.toString.charAt(0) | |
case (player, i) => player | |
}) | |
else | |
board | |
b.grouped(3).map(_.mkString(" ")).mkString("\n") | |
} | |
} | |
object TicTacToe { | |
val Player1 = 'X' | |
val Player2 = 'O' | |
val Vacant = '_' | |
def play(game: TicTacToe = new TicTacToe()) { | |
println(game) | |
println() | |
if (game.winner.isDefined) { | |
println(game.winner.get + " wins!") | |
} else if (game.boardFull) { | |
println("It's a tie!") | |
} else { | |
println("" + game.currentPlayer + "'s turn! Pick a position: ") | |
println(game.toString(indexed = true)) | |
val position = readInt() | |
println() | |
play(game.nextMove(position)) | |
} | |
} | |
} | |
TicTacToe.play() | |
println() | |
/************ Day 2 ************/ | |
/* | |
Use foldLeft to compute the total size of a list of strings. | |
*/ | |
println( | |
List("123", "4567", "8", "90").foldLeft(0)(_ + _.length) | |
) | |
println() | |
/* | |
Write a Censor trait with a method that will replace the curse words | |
Shoot and Darn with Pucky and Beans alternatives. Use a map to | |
store the curse words and their alternatives. | |
*/ | |
trait Censor { | |
val censorWords = | |
if (new java.io.File("censor.txt").exists()) { | |
io.Source.fromFile("censor.txt").getLines().map(line => { | |
val a = line.split(",") | |
assert(a.length == 2) | |
a(0) -> a(1) | |
}).toMap | |
} | |
else | |
Map("Shoot" -> "Pucky", "Darn" -> "Beans") | |
def censorText(text: String) = | |
(text /: censorWords)((text, censor) => text.replaceAll("(?i)" + censor._1, censor._2)) | |
} | |
object TestCensor extends Censor{ | |
val text = "Shoot Darn" | |
lazy val censoredText = censorText(text) | |
def print(){ | |
println("Original: " + text) | |
println("Censored: " + censoredText) | |
} | |
} | |
TestCensor.print() | |
println() | |
/************ Day 3 ************/ | |
/* | |
- Take the sizer application and add a message to count the number of links on the page. | |
- Make the sizer follow the links on a given page, and load them as well. | |
*/ | |
import java.nio.charset.CodingErrorAction | |
import scala.io._ | |
import scala.actors._ | |
import Actor._ | |
object PageLoader { | |
def load(url: String) = { | |
Source.fromURL(url)(Codec.default.onMalformedInput(CodingErrorAction.IGNORE)).mkString | |
} | |
// To simplify the problem, only links in the "complete" form are counted | |
val linkRegex = """(?i)<a\s[^>]*href="(http://[a-z./]+)"[^>]*>""".r | |
def extractLinks(source: String) = linkRegex.findAllIn(source).matchData.map(_.group(1)).toList | |
} | |
val urls = List( | |
"http://www.google.com", | |
"http://www.cnn.com" | |
) | |
def timeMethod(method: () => Unit) { | |
val start = System.nanoTime | |
method() | |
val end = System.nanoTime | |
println("Method took " + (end - start) / 1000000000.0 + " seconds.") | |
} | |
def getPageSizeSequentially() { | |
for (url <- urls) { | |
val source = PageLoader.load(url) | |
val links = PageLoader.extractLinks(source) | |
val totalSize = source.length + links.map(PageLoader.load(_).length).sum | |
println("Size for " + url + ": " + source.size + | |
", #Links: " + links.size + | |
", Total size: " + totalSize) | |
} | |
} | |
def getPageSizeConcurrently() { | |
val rootActor = self | |
for (url <- urls) { | |
actor { | |
val source = PageLoader.load(url) | |
val links = PageLoader.extractLinks(source) | |
val urlActor = self | |
links.foreach({ | |
link => | |
actor { | |
urlActor ! PageLoader.load(link).length | |
} | |
}) | |
val totalSize = source.length + { | |
for (i <- 1 to links.length) | |
yield receive { | |
case size: Int => | |
size | |
} | |
}.sum | |
rootActor !(url, source.length, links.size, totalSize) | |
} | |
} | |
for (i <- 1 to urls.size) { | |
receive { | |
case (url, size, linkCount, totalSize) => | |
println("Size for " + url + ": " + size + | |
", #Links: " + linkCount + | |
", Total size: " + totalSize) | |
} | |
} | |
} | |
println("Sequential run:") | |
timeMethod(getPageSizeSequentially) | |
println("Concurrent run") | |
timeMethod(getPageSizeConcurrently) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/local/bin/escript | |
% | |
% The next few lines are necessary for Day 3's OTP problem | |
% | |
-mode(compile) . | |
-behaviour(gen_server) . | |
-define(Log, loggerlog) . | |
-export([init/1, terminate/2, handle_cast/2, handle_call/3, handle_info/2, code_change/3]) . | |
%%%%%%%%%%%%%%%%%%%% Day 1 %%%%%%%%%%%%%%%%%%%% | |
% | |
% Write a function that uses recursion to return the number of words in a string. | |
% | |
is_letter(Char) -> | |
(Char >=$a) and (Char =< $z) or | |
(Char >= $A) and (Char =< $Z) or | |
(Char >= $0) and (Char =< $9) or | |
(Char == $-) . | |
count_words(String) -> count_words(String, false, 0) . | |
count_words([], _, Count) -> Count ; | |
count_words([Head|Tail], IsInWord, Count) -> | |
IsLetter = is_letter(Head), | |
case IsLetter and not IsInWord of | |
true -> | |
count_words(Tail, IsLetter, Count + 1); | |
false -> | |
count_words(Tail, IsLetter, Count) | |
end . | |
test_count_words() -> | |
Sentence = "AAM-4 is an example of fire-and-forget missles.", | |
io:put_chars(Sentence), io:nl(), | |
io:fwrite("#words = ~B~n", [count_words(Sentence)]) . | |
% | |
% Write a function that uses recursion to count to ten. | |
% | |
count_to(1) -> io:fwrite("~B ", [1]) ; | |
count_to(N) when is_integer(N) -> | |
count_to(N - 1), | |
io:fwrite("~B ", [N]). | |
test_count_to() -> | |
count_to(10), io:nl() . | |
% | |
% Write a function that uses matching to selectively print “success” | |
% or “error: message” given input of the form {error, Message} or success. | |
% | |
report(success) -> io:fwrite("success~n"); | |
report({error, Message}) -> io:fwrite("error: ~s~n", [Message]). | |
test_report() -> | |
report(success), | |
report({error, "The hard disk has exploded."}) . | |
%%%%%%%%%%%%%%%%%%%% Day 2 %%%%%%%%%%%%%%%%%%%% | |
% | |
% Consider a list of keyword-value tuples, such as [{erlang, "a functional | |
% language"}, {ruby, "an OO language"}]. Write a function that accepts | |
% the list and a keyword and returns the associated value for | |
% the keyword. | |
% | |
get_value([{Key, Value}|_], Key) -> Value; | |
get_value([_|Tail], Key) -> get_value(Tail, Key); | |
get_value([], _) -> error(badarg) . | |
test_get_value() -> | |
Map = [{erlang, "a functional language"}, {ruby, "an OO language"}], | |
io:fwrite("~p: ~p~n", [erlang, get_value(Map, erlang)]), | |
io:fwrite("~p: ~p~n", [ruby, get_value(Map, ruby)]) . | |
% | |
% Consider a shopping list that looks like [{item quantity price}, ...]. | |
% Write a list comprehension that builds a list of items of the form | |
% [{item total_price}, ...], where total_price is quantity times price. | |
% | |
compute_total_price(ShoppingList) -> | |
[{Item, Quantity * Price} || {Item, Quantity, Price} <- ShoppingList] . | |
test_compute_total_price() -> | |
ShoppingList = [{pencil, 10, 0.5}, {eraser, 2, 1}, {pen, 1, 5}], | |
io:fwrite("~p~n", [compute_total_price(ShoppingList)]) . | |
% | |
% Write a program that reads a tic-tac-toe board presented as a list | |
% or a tuple of size nine. Return the winner (x or o) if a winner | |
% has been determined, cat if there are no more possible moves, | |
% or no_winner if no player has won yet. | |
% | |
tictactoe_lines( | |
[ A,B,C, | |
D,E,F, | |
G,H,I ] | |
) -> | |
[ {A,B,C},{D,E,F},{G,H,I}, | |
{A,D,G},{B,E,H},{C,F,I}, | |
{A,E,I},{C,E,G} ] . | |
tictactoe_is_full(Board) -> | |
lists:all(fun(L) -> (L == x) or (L == o) end, Board) . | |
judge_tictactoe(Board) -> | |
Lines = tictactoe_lines(Board), | |
XWins = lists:member({x,x,x}, Lines), | |
OWins = lists:member({o,o,o}, Lines), | |
IsFull = tictactoe_is_full(Board), | |
if | |
XWins -> x; | |
OWins -> o; | |
IsFull -> cat; | |
true -> no_winner | |
end . | |
test_judge_tictactoe() -> | |
Board1 = | |
[null,null,null, | |
null,x,null, | |
null,o,null], | |
Board2 = | |
[x,o,o, | |
x,x,null, | |
x,null,o], | |
Board3 = | |
[o,x,x, | |
o,o,null, | |
o,null,x], | |
Board4 = | |
[o,x,x, | |
x,o,o, | |
x,o,x], | |
io:fwrite("~p ~p~n~p ~p~n~p ~p~n~p ~p~n", | |
[Board1, judge_tictactoe(Board1), | |
Board2, judge_tictactoe(Board2), | |
Board3, judge_tictactoe(Board3), | |
Board4, judge_tictactoe(Board4)]) . | |
%%%%%%%%%%%%%%%%%%%% Day 3 %%%%%%%%%%%%%%%%%%%% | |
% | |
% Monitor the translator_loop and restart it should it die. | |
% | |
translator_loop() -> | |
receive | |
"casa" -> | |
io:format("house~n"), | |
translator_loop(); | |
"blanca" -> | |
io:format("white~n"), | |
translator_loop(); | |
_ -> | |
io:format("I don't understand.~n"), | |
exit({translator, dont_understand}) | |
end. | |
translator_doctor_loop() -> | |
process_flag(trap_exit, true), | |
receive | |
new_translator -> | |
io:fwrite("Creating and monitoring translate service process ...~n"), | |
register(translator, spawn_link(fun()-> translator_loop() end)), | |
translator_doctor_loop(); | |
{'EXIT', From, Reason} -> | |
io:fwrite("The translation service ~p died with reason ~p. Restarting.~n", [From, Reason]), | |
self() ! new_translator, | |
translator_doctor_loop() | |
end . | |
test_monitor_translator() -> | |
DoctorPid = spawn(fun()-> translator_doctor_loop() end), | |
DoctorPid ! new_translator, timer:sleep(50), | |
translator ! "casa", translator ! "blanca", timer:sleep(50), | |
translator ! "blah", timer:sleep(50), | |
translator ! "casa", timer:sleep(50), | |
unregister(translator) . | |
% | |
% Make the Doctor process restart itself if it should die. | |
% | |
% No idea how this is possible. | |
% | |
% Make a monitor for the Doctor monitor. If either monitor dies, restart it. | |
% | |
doctor_loop() -> | |
receive | |
new_companion_doctor -> | |
spawn_link(fun()-> doctor_init() end), | |
doctor_loop(); | |
{'EXIT', From, Reason} -> | |
io:fwrite("The doctor ~p died with reason ~p. Restarting.~n", [From, Reason]), | |
self() ! new_companion_doctor, | |
doctor_loop() | |
end . | |
doctor_init() -> | |
io:fwrite("Doctor ~p spawned.~n", [self()]), | |
process_flag(trap_exit, true), | |
case whereis(doctor1) of | |
undefined -> | |
register(doctor1, self()), | |
io:fwrite("Doctor ~p registered as doctor1.~n", [self()]); | |
_ -> | |
register(doctor2, self()), | |
io:fwrite("Doctor ~p registered as doctor2.~n", [self()]) | |
end, | |
doctor_loop() . | |
test_companion_doctor() -> | |
spawn(fun() -> doctor_init() end) ! new_companion_doctor, timer:sleep(10), | |
exit(whereis(doctor1), kill), timer:sleep(10), | |
exit(whereis(doctor2), kill), timer:sleep(10), | |
exit(whereis(doctor1), kill), timer:sleep(10), | |
exit(whereis(doctor2), kill), timer:sleep(10) . | |
% | |
% Create a basic OTP server that logs messages to a file. | |
% | |
init([]) -> | |
disk_log:open([{name, ?Log}]), | |
{ok, []} . | |
terminate(normal, []) -> | |
disk_log:close(?Log) . | |
handle_cast({log, Term}, []) -> | |
disk_log:alog(?Log, Term), | |
{noreply, []} . | |
handle_call({log, Term}, _From, []) -> | |
{reply, disk_log:log(?Log, Term), []}; | |
handle_call({chunk, Continuation}, _From, []) -> | |
{reply, disk_log:chunk(?Log, Continuation), []}; | |
handle_call(terminate, _From, []) -> | |
{stop, normal, ok, []} . | |
handle_info(Msg, []) -> | |
io:format("Unexpected message: ~p~n",[Msg]), | |
{noreply, []} . | |
code_change(_, [], _) -> | |
{ok, []} . | |
% | |
logger_start_link() -> gen_server:start_link(?MODULE, [], []) . | |
logger_log(Pid, Term) -> | |
gen_server:call(Pid, {log, Term}) . | |
logger_alog(Pid, Term) -> | |
gen_server:cast(Pid, {log, Term}) . | |
logger_history(Pid) -> | |
% simplified here | |
Continuation = gen_server:call(Pid, {chunk, start}), | |
io:fwrite("~p~n", [Continuation]) . | |
logger_terminate(Pid) -> | |
gen_server:call(Pid, terminate) . | |
% | |
test_logger_otp() -> | |
{ok, Pid} = logger_start_link(), | |
logger_log(Pid, 1), | |
logger_log(Pid, 2), | |
logger_log(Pid, 3), | |
logger_alog(Pid, "a"), | |
logger_alog(Pid, "b"), | |
logger_alog(Pid, "c"), | |
timer:sleep(50), | |
logger_history(Pid), | |
logger_terminate(Pid) . | |
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
main(_) -> | |
test_count_words(), io:nl(), | |
test_count_to(), io:nl(), | |
test_report(), io:nl(), | |
test_get_value(), io:nl(), | |
test_compute_total_price(), io:nl(), | |
test_judge_tictactoe(), io:nl(), | |
test_monitor_translator(), io:nl(), | |
test_companion_doctor(), io:nl(), | |
test_logger_otp(), io:nl(), | |
io:nl() . |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/clj | |
;;; Day 1 | |
;; Implement a function called (big st n) that returns true | |
;; if a string st is longer than n characters. | |
(defn big [str n] | |
(> (count str) n)) | |
; Tests | |
(println (big "abc" 3)) | |
(println (big "abcd" 3)) | |
(println) | |
;; Write a function called (collection-type col) that returns :list, | |
;; :map, or :vector based on the type of collection col. | |
(defn collection-type [col] | |
(cond | |
(list? col) :list | |
(map? col) :map | |
(vector? col) :vector | |
(set? col) :set)) | |
; Tests | |
(println (collection-type `())) | |
(println (collection-type [])) | |
(println (collection-type #{})) | |
(println (collection-type {})) | |
(println) | |
;;; Day 2 | |
;; Implement an unless with an else condition using macros. | |
(defmacro unless | |
[test body & [else-body]] | |
(list 'if test else-body body)) | |
; Tests | |
(println (unless false "fail")) | |
(println (unless true "fail")) | |
(println (unless true "fail" "succeed")) | |
(println) | |
;; Write a type using defrecord that implements a protocol. | |
(defn sqr [x] (* x x)) | |
(defprotocol Figure | |
(area [this])) | |
(defrecord Square [side-length] | |
Figure | |
(area [this] | |
(sqr (:side-length this)))) | |
(defrecord Circle [radius] | |
Figure | |
(area [this] | |
(* Math/PI (sqr radius)))) | |
; Tests | |
(println (area (Square. 5))) | |
(println (area (Circle. 5))) | |
(println) | |
;;; Day 3 | |
;; Use refs to create a vector of accounts in memory. Create debit | |
;; and credit functions to change the balance of an account. | |
(def accounts | |
(into [] | |
(take 10 (repeatedly #(ref (rand-int 100)))))) | |
(defn credit [account amount] | |
(dosync | |
(alter account + amount))) | |
(defn debit [account amount] | |
(dosync | |
(alter account - amount))) | |
; Tests | |
(println (map deref accounts)) | |
(doseq [account accounts] | |
(credit account 100)) | |
(println (map deref accounts)) | |
(doseq [account accounts] | |
(debit account 50)) | |
(println (map deref accounts)) | |
(println) | |
;; Sleeping barber problem | |
; Some logging facilities to allow synchronized I/O | |
; Can't think of a better way without using 3rd party libraries (agent doesn't work) | |
(def logger | |
(let [logger (java.util.logging.Logger/getLogger "barber") | |
handler (java.util.logging.ConsoleHandler.)] | |
(.setUseParentHandlers logger false) | |
(.setFormatter handler | |
(proxy [java.util.logging.Formatter][] | |
(format [record] | |
(str | |
"[Thread-" (.getThreadID record) "]\t" | |
(.getMessage record) \newline)))) | |
(.addHandler logger handler) | |
logger | |
)) | |
(defn log [& content] | |
(.info logger (apply str (interpose " " content)))) | |
(defn log-arrive [arrive-time] | |
(log "[Customer] arrives at" arrive-time)) | |
(defn log-enqueue [arrive-time curr-waiting-list] | |
(log "[Customer]" arrive-time "now in waiting list:" (into [] curr-waiting-list))) | |
(defn log-leave [arrive-time curr-waiting-list] | |
(log "[Customer]" arrive-time "left, waiting list is full:" (into [] curr-waiting-list))) | |
(defn log-start-cut [arrive-time] | |
(log "[Barber] starts cutting hair for" arrive-time)) | |
(defn log-finish-cut [arrive-time] | |
(log "[Barber] finished cutting hair for" arrive-time)) | |
(def waiting-list (ref clojure.lang.PersistentQueue/EMPTY)) | |
(defn new-customer [arrive-time] | |
(log-arrive arrive-time) | |
; A temporary agent for logging (side-effect) | |
; This is why waiting-list is a ref rather than an atom | |
(let [log-agent (agent nil)] | |
(dosync | |
; Try to enqueue | |
(if (< (count @waiting-list) 3) | |
(do | |
(alter waiting-list conj arrive-time) | |
(send log-agent | |
(fn [_] (log-enqueue arrive-time @waiting-list)))) | |
(send log-agent | |
(fn [_] (log-leave arrive-time @waiting-list))))))) | |
; Alter a ref of queue with pop, return the dequeued head | |
(defn pop-head [queue] | |
(dosync | |
(when-let [head (peek @queue)] | |
(alter queue pop) | |
head))) | |
(def haircut-count (agent 0)) | |
(defn nudge-the-barber [] | |
(send haircut-count | |
#(if-let [customer (pop-head waiting-list)] | |
(do | |
(log-start-cut customer) | |
(Thread/sleep 200) | |
(log-finish-cut customer) | |
(inc %1)) | |
%1))) | |
(add-watch waiting-list :barber-watch | |
(fn [_ _ old-state new-state] | |
(when (not= old-state new-state) | |
(nudge-the-barber)))) | |
(loop [time 0] | |
(let [interval (+ 10 (rand-int 21)) | |
arrive-time (+ time interval)] | |
(when (<= arrive-time 10000) | |
(Thread/sleep interval) | |
; Customers come from different threads! | |
(future (new-customer arrive-time)) | |
(recur arrive-time)))) | |
(await haircut-count) | |
(println "Total haircuts:" @haircut-count) | |
(shutdown-agents) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment