Skip to content

Commit

Permalink
Tidy up List Ops exercise
Browse files Browse the repository at this point in the history
- Reduce difficulty (since most of the skeleton has been added)
- Move template helper methods to generator_plugin
- Add spacing between tests
  • Loading branch information
kahgoh committed Jan 29, 2024
1 parent 80af778 commit 3d3e2e5
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 134 deletions.
2 changes: 1 addition & 1 deletion config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1398,7 +1398,7 @@
"procs-blocks"
],
"prerequisites": [],
"difficulty": 5
"difficulty": 3
}
]
},
Expand Down
18 changes: 9 additions & 9 deletions exercises/practice/list-ops/.meta/src/example.cr
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
class ListOps(T)
def append(list1 : Array(T), list2 : Array(T)) : Array(T)
module ListOps(T)
def self.append(list1 : Array(T), list2 : Array(T)) : Array(T)
concat([list1, list2])
end

def concat(lists : Array(Array(T)))
def self.concat(lists : Array(Array(T)))
result = [] of T
lists.each { |sublist|
sublist.each { |elem| result << elem }
}
result
end

def filter(list : Array(T), function : T -> Bool) : Array(T)
def self.filter(list : Array(T), function : T -> Bool) : Array(T)
filtered = [] of T
list.each { |elem|
if function.call(elem)
Expand All @@ -21,25 +21,25 @@ class ListOps(T)
filtered
end

def length(list : Array(T)) : Int
def self.length(list : Array(T)) : Int
count = 0
list.each { |_| count = count + 1 }
count
end

def map(list : Array(T), function : T -> _)
def self.map(list : Array(T), function : T -> _)
mapped = [] of T
list.each { |elem| mapped << function.call(elem) }
mapped
end

def foldl(list : Array(T), initial : _, function : _, T -> _)
def self.foldl(list : Array(T), initial : _, function : _, T -> _)
acc = initial
list.each { |elem| acc = function.call(acc, elem) }
acc
end

def foldr(list : Array(T), initial : _, function : _, T -> _)
def self.foldr(list : Array(T), initial : _, function : _, T -> _)
acc = initial
copy = list.clone
until copy.empty?
Expand All @@ -48,7 +48,7 @@ class ListOps(T)
acc
end

def reverse(list : Array(T)) : Array(T)
def self.reverse(list : Array(T)) : Array(T)
reversed = [] of T
copy = list.clone
until copy.empty?
Expand Down
55 changes: 19 additions & 36 deletions exercises/practice/list-ops/.meta/test_template.ecr
Original file line number Diff line number Diff line change
Expand Up @@ -2,74 +2,57 @@ require "spec"
require "../src/*"

<%-
# These are uuids of the test cases that use nested arrays.
tests_with_nested = ["d6ecd72c-197f-40c3-89a4-aa1f45827e09", "40872990-b5b8-4cb8-9085-d91fc0d05d26"]
emptiable = ->(input : JSON::Any) {
if input.as_a.empty?
"[] of Int32"
else
input.to_s
end
}
nestable_emptiable = ->(input : JSON::Any) {
if input.as_a.empty?
"[] of Array(Int32)"
else
input.to_s.gsub("[]", "[] of Int32")
end
}
-%>
describe "<%-= to_capitalized(@json["exercise"].to_s) %>" do
<%- @json["cases"].as_a.each do |describe| -%>
describe "<%= describe["description"].to_s %>" do
<%- describe["cases"].as_a.each do |cases| -%>
<%= status() %> "<%= cases["description"] %>" do
<%- if tests_with_nested.any?(cases["uuid"]) -%>
list_ops = ListOps(Array(Int32)).new()
<%- else -%>
list_ops = ListOps(Int32).new()
<%- end -%>
<%- if cases["property"].to_s == "append" -%>
list_ops.<%= cases["property"] %>(<%= emptiable.call(cases["input"]["list1"]) %>, <%= emptiable.call(cases["input"]["list2"]) %>).should eq(<%= emptiable.call(cases["expected"]) %>)
ListOps.<%= cases["property"] %>(<%= list_ops_array(cases["input"]["list1"]) %>, <%= list_ops_array(cases["input"]["list2"]) %>).should eq(<%= list_ops_array(cases["expected"]) %>)
<%- end -%>
<%- if cases["property"].to_s == "concat" -%>
list_ops.<%= cases["property"] %>(<%= nestable_emptiable.call(cases["input"]["lists"]) %>).should eq(<%=
ListOps.<%= cases["property"] %>(<%= list_ops_nestable(cases["input"]["lists"]) %>).should eq(<%=
if tests_with_nested.any?(cases["uuid"])
nestable_emptiable.call(cases["expected"])
list_ops_nestable(cases["expected"])
else
emptiable.call(cases["expected"])
list_ops_array(cases["expected"])
end%>)
<%- end -%>
<%- if cases["property"].to_s == "filter" -%>
filter = <%= function(cases["input"]["function"], ["Int32"]) %>
list_ops.<%= cases["property"] %>(<%= emptiable.call(cases["input"]["list"]) %>, filter).should eq(<%= emptiable.call(cases["expected"]) %>)
filter = <%= list_ops_function(cases["input"]["function"], ["Int32"]) %>
ListOps.<%= cases["property"] %>(<%= list_ops_array(cases["input"]["list"]) %>, filter).should eq(<%= list_ops_array(cases["expected"]) %>)
<%- end -%>
<%- if cases["property"].to_s == "length" -%>
list_ops.<%= cases["property"] %>(<%= emptiable.call(cases["input"]["list"]) %>).should eq(<%= cases["expected"] %>)
ListOps.<%= cases["property"] %>(<%= list_ops_array(cases["input"]["list"]) %>).should eq(<%= cases["expected"] %>)
<%- end -%>
<%- if cases["property"].to_s == "map" -%>
mapper = <%= function(cases["input"]["function"], ["Int32"]) %>
list_ops.<%= cases["property"] %>(<%= emptiable.call(cases["input"]["list"]) %>, mapper).should eq(<%= emptiable.call(cases["expected"]) %>)
mapper = <%= list_ops_function(cases["input"]["function"], ["Int32"]) %>
ListOps.<%= cases["property"] %>(<%= list_ops_array(cases["input"]["list"]) %>, mapper).should eq(<%= list_ops_array(cases["expected"]) %>)
<%- end -%>
<%- if ["foldl", "foldr"].any?(cases["property"]) -%>
<%- # If the function performs a division, use Float64 -%>
<%- if cases["input"]["function"].to_s.matches?(/.*->.*\/.*/) -%>
folder = <%= function(cases["input"]["function"], ["Float64", "Int32"]) %>
list_ops.<%= cases["property"] %>(<%= emptiable.call(cases["input"]["list"]) %>, <%= cases["input"]["initial"] %>.to_f64, folder).should eq(<%= cases["expected"] %>.to_f64)
folder = <%= list_ops_function(cases["input"]["function"], ["Float64", "Int32"]) %>
ListOps.<%= cases["property"] %>(<%= list_ops_array(cases["input"]["list"]) %>, <%= cases["input"]["initial"] %>.to_f64, folder).should eq(<%= cases["expected"] %>.to_f64)
<%- else -%>
folder = <%= function(cases["input"]["function"], ["Int32", "Int32"]) %>
list_ops.<%= cases["property"] %>(<%= emptiable.call(cases["input"]["list"]) %>, <%= cases["input"]["initial"] %>, folder).should eq(<%= cases["expected"] %>)
folder = <%= list_ops_function(cases["input"]["function"], ["Int32", "Int32"]) %>
ListOps.<%= cases["property"] %>(<%= list_ops_array(cases["input"]["list"]) %>, <%= cases["input"]["initial"] %>, folder).should eq(<%= cases["expected"] %>)
<%- end -%>
<%- end -%>
<%- if cases["property"].to_s == "reverse" -%>
<%- if tests_with_nested.any?(cases["uuid"]) -%>
list_ops.<%= cases["property"] %>(<%= nestable_emptiable.call(cases["input"]["list"]) %>).should eq(<%= nestable_emptiable.call(cases["expected"]) %>)
ListOps.<%= cases["property"] %>(<%= list_ops_nestable(cases["input"]["list"]) %>).should eq(<%= list_ops_nestable(cases["expected"]) %>)
<%- else -%>
list_ops.<%= cases["property"] %>(<%= emptiable.call(cases["input"]["list"]) %>).should eq(<%= emptiable.call(cases["expected"]) %>)
ListOps.<%= cases["property"] %>(<%= list_ops_array(cases["input"]["list"]) %>).should eq(<%= list_ops_array(cases["expected"]) %>)
<%- end -%>
<%- end -%>
end

<%- end -%>
end

<%- end -%>
end
87 changes: 43 additions & 44 deletions exercises/practice/list-ops/spec/list_ops_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -4,116 +4,115 @@ require "../src/*"
describe "ListOps" do
describe "append entries to a list and return the new list" do
it "empty lists" do
list_ops = ListOps(Int32).new
list_ops.append([] of Int32, [] of Int32).should eq([] of Int32)
ListOps.append([] of Int32, [] of Int32).should eq([] of Int32)
end

pending "list to empty list" do
list_ops = ListOps(Int32).new
list_ops.append([] of Int32, [1, 2, 3, 4]).should eq([1, 2, 3, 4])
ListOps.append([] of Int32, [1, 2, 3, 4]).should eq([1, 2, 3, 4])
end

pending "empty list to list" do
list_ops = ListOps(Int32).new
list_ops.append([1, 2, 3, 4], [] of Int32).should eq([1, 2, 3, 4])
ListOps.append([1, 2, 3, 4], [] of Int32).should eq([1, 2, 3, 4])
end

pending "non-empty lists" do
list_ops = ListOps(Int32).new
list_ops.append([1, 2], [2, 3, 4, 5]).should eq([1, 2, 2, 3, 4, 5])
ListOps.append([1, 2], [2, 3, 4, 5]).should eq([1, 2, 2, 3, 4, 5])
end
end

describe "concatenate a list of lists" do
pending "empty list" do
list_ops = ListOps(Int32).new
list_ops.concat([] of Array(Int32)).should eq([] of Int32)
ListOps.concat([] of Array(Int32)).should eq([] of Int32)
end

pending "list of lists" do
list_ops = ListOps(Int32).new
list_ops.concat([[1, 2], [3], [] of Int32, [4, 5, 6]]).should eq([1, 2, 3, 4, 5, 6])
ListOps.concat([[1, 2], [3], [] of Int32, [4, 5, 6]]).should eq([1, 2, 3, 4, 5, 6])
end

pending "list of nested lists" do
list_ops = ListOps(Array(Int32)).new
list_ops.concat([[[1], [2]], [[3]], [[] of Int32], [[4, 5, 6]]]).should eq([[1], [2], [3], [] of Int32, [4, 5, 6]])
ListOps.concat([[[1], [2]], [[3]], [[] of Int32], [[4, 5, 6]]]).should eq([[1], [2], [3], [] of Int32, [4, 5, 6]])
end
end

describe "filter list returning only values that satisfy the filter function" do
pending "empty list" do
list_ops = ListOps(Int32).new
filter = ->(x : Int32) { x % 2 == 1 }
list_ops.filter([] of Int32, filter).should eq([] of Int32)
ListOps.filter([] of Int32, filter).should eq([] of Int32)
end

pending "non-empty list" do
list_ops = ListOps(Int32).new
filter = ->(x : Int32) { x % 2 == 1 }
list_ops.filter([1, 2, 3, 5], filter).should eq([1, 3, 5])
ListOps.filter([1, 2, 3, 5], filter).should eq([1, 3, 5])
end
end

describe "returns the length of a list" do
pending "empty list" do
list_ops = ListOps(Int32).new
list_ops.length([] of Int32).should eq(0)
ListOps.length([] of Int32).should eq(0)
end

pending "non-empty list" do
list_ops = ListOps(Int32).new
list_ops.length([1, 2, 3, 4]).should eq(4)
ListOps.length([1, 2, 3, 4]).should eq(4)
end
end

describe "return a list of elements whose values equal the list value transformed by the mapping function" do
pending "empty list" do
list_ops = ListOps(Int32).new
mapper = ->(x : Int32) { x + 1 }
list_ops.map([] of Int32, mapper).should eq([] of Int32)
ListOps.map([] of Int32, mapper).should eq([] of Int32)
end

pending "non-empty list" do
list_ops = ListOps(Int32).new
mapper = ->(x : Int32) { x + 1 }
list_ops.map([1, 3, 5, 7], mapper).should eq([2, 4, 6, 8])
ListOps.map([1, 3, 5, 7], mapper).should eq([2, 4, 6, 8])
end
end

describe "folds (reduces) the given list from the left with a function" do
pending "empty list" do
list_ops = ListOps(Int32).new
folder = ->(acc : Int32, el : Int32) { el * acc }
list_ops.foldl([] of Int32, 2, folder).should eq(2)
ListOps.foldl([] of Int32, 2, folder).should eq(2)
end

pending "direction independent function applied to non-empty list" do
list_ops = ListOps(Int32).new
folder = ->(acc : Int32, el : Int32) { el + acc }
list_ops.foldl([1, 2, 3, 4], 5, folder).should eq(15)
ListOps.foldl([1, 2, 3, 4], 5, folder).should eq(15)
end

pending "direction dependent function applied to non-empty list" do
list_ops = ListOps(Int32).new
folder = ->(acc : Float64, el : Int32) { el / acc }
list_ops.foldl([1, 2, 3, 4], 24.to_f64, folder).should eq(64.to_f64)
ListOps.foldl([1, 2, 3, 4], 24.to_f64, folder).should eq(64.to_f64)
end
end

describe "folds (reduces) the given list from the right with a function" do
pending "empty list" do
list_ops = ListOps(Int32).new
folder = ->(acc : Int32, el : Int32) { el * acc }
list_ops.foldr([] of Int32, 2, folder).should eq(2)
ListOps.foldr([] of Int32, 2, folder).should eq(2)
end

pending "direction independent function applied to non-empty list" do
list_ops = ListOps(Int32).new
folder = ->(acc : Int32, el : Int32) { el + acc }
list_ops.foldr([1, 2, 3, 4], 5, folder).should eq(15)
ListOps.foldr([1, 2, 3, 4], 5, folder).should eq(15)
end

pending "direction dependent function applied to non-empty list" do
list_ops = ListOps(Int32).new
folder = ->(acc : Float64, el : Int32) { el / acc }
list_ops.foldr([1, 2, 3, 4], 24.to_f64, folder).should eq(9.to_f64)
ListOps.foldr([1, 2, 3, 4], 24.to_f64, folder).should eq(9.to_f64)
end
end

describe "reverse the elements of the list" do
pending "empty list" do
list_ops = ListOps(Int32).new
list_ops.reverse([] of Int32).should eq([] of Int32)
ListOps.reverse([] of Int32).should eq([] of Int32)
end

pending "non-empty list" do
list_ops = ListOps(Int32).new
list_ops.reverse([1, 3, 5, 7]).should eq([7, 5, 3, 1])
ListOps.reverse([1, 3, 5, 7]).should eq([7, 5, 3, 1])
end

pending "list of lists is not flattened" do
list_ops = ListOps(Array(Int32)).new
list_ops.reverse([[1, 2], [3], [] of Int32, [4, 5, 6]]).should eq([[4, 5, 6], [] of Int32, [3], [1, 2]])
ListOps.reverse([[1, 2], [3], [] of Int32, [4, 5, 6]]).should eq([[4, 5, 6], [] of Int32, [3], [1, 2]])
end
end
end
18 changes: 9 additions & 9 deletions exercises/practice/list-ops/src/list_ops.cr
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
class ListOps(T)
module ListOps(T)
# Write your code for the 'Simple Cipher' exercise in this file.

def append(list1 : Array(T), list2 : Array(T)) : Array(T)
def self.append(list1 : Array(T), list2 : Array(T)) : Array(T)
end

def concat(lists : Array(Array(T)))
def self.concat(lists : Array(Array(T)))
end

def filter(list : Array(T), function : T -> Bool) : Array(T)
def self.filter(list : Array(T), function : T -> Bool) : Array(T)
end

def length(list : Array(T)) : Int
def self.length(list : Array(T)) : Int
end

def map(list : Array(T), function : T -> _)
def self.map(list : Array(T), function : T -> _)
end

def foldl(list : Array(T), initial : _, function : _, T -> _)
def self.foldl(list : Array(T), initial : _, function : _, T -> _)
end

def foldr(list : Array(T), initial : _, function : _, T -> _)
def self.foldr(list : Array(T), initial : _, function : _, T -> _)
end

def reverse(list : Array(T)) : Array(T)
def self.reverse(list : Array(T)) : Array(T)
end
end
Loading

0 comments on commit 3d3e2e5

Please sign in to comment.