原文链接:编程基础练习簿
课程目标
这堂课将练习编程的基础逻辑,包括:
- 输入、处理、输出
- 基本算术
- 控制流程
- 函式方法
- 循环 Loop
- 数组 Array
- 散列 Hash
- 对象 Object
- 档案 File
课程复盘
1. 输入、处理、输出
题目 2
交换变数 a 和变数 b 的值
1 2 3 4 5 6 7 8 9 10 11 12
| a = 1 b = 2 puts "a 是 #{a}" puts "b 是 #{b}" a, b = b, a puts "a 应该是 2,现在是 #{a}" puts "b 应该是 1,现在是 #{b}"
|
2. 基本算术
%
馀数,例如 20 % 3
是 2
注意,整数除整数还是整数。只有浮点数运算的结果才会是浮点数。
型别转换 (Type Conversion)
字符串和数字是不能直接相加的,会出现 TypeError 的错误,需要先做型别转换:
- 透过
to_i
可以将字符串转成整数,例如 "123".to_i
变成 123
- 透过
to_f
可以将整数转乘浮点数,例如 123.to_f
变成 123.0
- 透过
to_s
可以转成字符串,例如 123.to_s
变成 "123"
注意,在 gets 中,拿到的是「字符串」,即使你输入的是数字。所以如我要做数字计算,记得先做型别转换。
在 Rails controller 中,params 的值也都是字符串。
浮点数常用方法
四舍五入 round
1 2 3 4
| 1.4.round 1.5.round 1.6.round (-1.5).round
|
无条件进制 ceil
1 2 3 4
| 1.2.ceil #=> 2 2.0.ceil #=> 2 (-1.2).ceil #=> -1 (-2.0).ceil #=> -2
|
无条件舍去 floor
1 2 3 4
| 1.2.floor #=> 1 2.0.floor #=> 2 (-1.2).floor #=> -2 (-2.0).floor #=> -2
|
3. 控制流程
关系运算子
以下这些运算子,都会回传 Boolean 值,也就是 true
或 false
例如 "123" == "123"
会回传 true
,123 > 555
会回传 false
控制流程(Control Flow)
透过 Boolean 条件,就可以决定程序怎么执行
1 2 3 4 5 6 7 8 9 10
| if a > b elsif a == b else end
|
逻辑运算子
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| print "请输入一个数字x,然后按 Enter: " x = gets().to_i print "请输入一个数字y,然后按 Enter: " y = gets().to_i print "请输入一个数字z,然后按 Enter: " z = gets().to_i result = [x, y, z].max puts "最大的数是 #{result}"
|
事例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| print "请输入一个数字x,然后按 Enter: " x = gets.to_f print "请输入一个数字y,然后按 Enter: " y = gets.to_f print "请输入一个数字z,然后按 Enter: " z = gets.to_f if x >= y && x >= z result = x elsif y >= x && y >= z result = y else z >= x && z >= y result = z end puts "最大的数是 #{result}"
|
4. 方法 Method
在方法中用 return 返回一个值,让我们可以把它存进一个变量,在后面的程序来使用。
1 2 3
| def get_hello(name) return "Hello, #{name}" end
|
如果没有写 return,该方法的最后一行的值会自动被返回。例如上述程式的 return 可以省略:
1 2 3
| def get_hello(name) "Hello, #{name}" end
|
5. 循环 Loop
1 2 3 4 5 6 7
| # 题目: 列出 1 到 100 之间,所有 7 的倍数 i = 1 while i+=1 puts i if i % 7 == 0 end
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| print "请输入数字 N,然后按 Enter: " n = gets().to_i i = 0 j = 0 while ( i <= n ) j = 0 while ( j <= n ) puts "#{i} x #{j} = #{i*j}" j += 1 end i += 1 end
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 提示:从 2 开始到 N/2 不断去除这个数字,如果可以整除就表示不是质数 def is_prime(n) i = 2 while ( i <= n-1 ) return false if n <= 1 || n % i == 0 i += 1 end true end print "请输入数字 N,然后按 Enter: " n = gets().to_i if is_prime(n) puts "这是质数" else puts "这不是质数" end
|
6. 数组 Array
什么是数组?
- 一个有顺序的容器,用数字当作索引。
- 索引从 0 开始
- 原则上里面的元素,数据类型最好都是一样的,例如都存整数,或是都存字符串。
以下就是一个数组,用中括号前后包住,用逗号分隔:
array = [5,1,2,3,5]
数组里面还可以放数组,这样会变成二维数组:
1 2 3 4 5 6 7 8 9
| array = [ [1,2,3], [4,5,6], [7,8,9] ] array[0][0] # 这是 1 array[0][1] # 这是 2 array[2][2] # 这是 9
|
如何走访数组?
用 for
方法:
1 2 3
| for i in array puts i end
|
在 Ruby 中更习惯用 each
方法:
1 2 3
| array.each do |i| puts i end
|
如果走访时,需要索引值,可以改用 each_with_index
方法:
1 2 3 4 5
| array.each_with_index do |i, j| puts i puts j # j 从 0 开始数 end
|
如何操作数组?
array.size
会回传数组长度
array[1]
会读取第二个元素
array[1] = 999
会更改第二的元素的值
array[99999]
读取一个不存在的值,会回传 nil
array[0]
或 array.first
是数组的第一个元素
array[-1]
或 array.last
是数组的最后一个元素
array.push(999)
或 array << 999
会新增一个元素 999
到数组的最后面
array.unshift(999)
会新增一个元素 999
到数组的最前面
array.delete_at(i)
会删除索引值在 i 的元素
更多 Ruby 的 Array 方法请参考 Ruby Array API
字符串与数组
编程语言内部实作字符串的方式,就是将一个一个字符用数组连结起来。这就是为什么字符串也可以用数字索引操作:
1 2 3 4 5 6 7 8 9 10
| str = "ABCDEFG" str[0] # 得到 "A" str[1] = "x" str # 变成 "AxCDEFG" str.split("") # 用 "" 拆开字符串,得到数组 ["A", "B", "C", "D", "E", "F", "G"] ["x", "y", "z"].join(" ") # 用 " " 串接成字符串,得到 "x y z"
|
示例代码:
1 2 3 4 5 6 7 8 9 10
| # 给定一阵列内含数字,输出最大值 def find_max(array) array.max end arr = [8, 12, 36, 53, 9, 75, 3, 71, 59, 88] max = find_max(arr) puts "Max is #{max}" # 应该是 88
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| arr = [] while (true) print "请输入数字,结束请直接按 Enter: " user_input = gets if user_input == "\n" break else arr << user_input.to_i end end sum = 0 arr.each { |i| sum += i } average = sum / arr.size puts arr.to_s puts "总和是 #{sum}" puts "平均是 #{average}" puts "最大值是 #{arr.max}" puts "最小值是 #{arr.min}"
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| # 建构一个阵列有一百个的元素,内容是 0, 1, 4, 9, 16, 25...... 每个元素是该索引的平方 # 例如 N = 1,则数组是 [0] # 例如 N = 3,则数组是 [0,1,4] arr = [] print "请输入数字 N,然后按 Enter: " n = gets().to_i (0..n).each_with_index do |object, index| arr << index ** 2 end puts arr.to_s
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| # 建构一个阵列有一百个的元素,内容是 0, 1, 4, 9, 16, 25...... 每个元素是该索引的平方 arr = [] print "请输入数字 N,然后按 Enter: " n = gets.to_i i = 0 while i < n arr << i*i i += 1 end puts arr.to_s
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| def filter_even(arr) new_arr = [] arr.each do |i| if i % 2 == 0 new_arr << i end end new_arr.sort.uniq end arr = [7, 68, 42, 46, 9, 91, 77, 46, 86, 1] puts "#{filter_even(arr)}"
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| # 给定一数组内含数字,请实作选择排序法进行排序。 # 提示: 新建立一个空数组,然后走访本来的数组检查每一个元素,如果符合条件就塞进(push)进新的数组。 # https://zh.wikipedia.org/wiki/选择排序 def insertion_sort(arr) (0...arr.length).each do |i| min, index = arr[i], i (i...arr.length).each { |j| min, index = arr[j], j if arr[j] < min } arr[i], arr[index] = arr[index], arr[i] end arr end arr = [7, 68, 42, 46, 9, 91, 77, 46, 86, 1] answer = insertion_sort(arr) puts answer.to_s # 应该是 [1, 7, 9, 42, 46, 46, 68, 77, 86, 91]
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| # 给定一阵列内含数字,请输出 0~9 中不见的数字。例如 arr = [2,2,1,5,8,4],输出 [0,3,6,7,9] def find_missing(arr) missing_num = [] (0..9).each do |i| missing_num << i unless arr.include?(i) end missing_num end answer = find_missing( [2,2,1,5,8,4] ) puts answer.to_s # 应该是 [0,3,6,7,9]
|
7. 散列 Hash
什么是散列 Hash?
用键(key)当作索引的容器,例如
h = { "a" => 123, "b" => 456 }
h["a"] 就是 123
h["b"] 就是 456
h["qweqkleklwqen"]
如果读取一个不存在的key,结果会是 nil
h["new_key"] = 123
如果本来没有这个 key,就会直接新增一组 key-value
如何走访散列 Hash?
1 2 3 4
| h.each do |key, value| puts key puts value end
|
常用方法
h.keys
会回传一个数组包括所有 keys
h.values
会回传一个数组包括所有 values
h.merge(h2)
会合并散列 h2 到散列 h
更多 Ruby 的 Hash 方法请参考 Ruby Hash API
示例代码:
给定一 Hash,输出有最大 value 的 key
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| def find_max(hash) hash.each do |key, value| return key if value == hash.values.max end end h = { "a" => 71, "b" => 38, "c" => 21, "d" => 80, "e" => 10 } answer = find_max(h) puts "有最大 value 的是 #{answer}" # 应该是 d
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| # 给定一 Hash,输出 value 是偶数的 keys def find_even_keys(hash) result = [] hash.each do |key, value| result << key if value % 2 == 0 end result end h = { "a" => 71, "b" => 38, "c" => 21, "d" => 80, "e" => 10 } answer = find_even_keys(h) puts "有偶数 value 的 keys 有 #{answer}" # 应该是数组 [b,d,e]
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| # 计算一个阵列中各个元素的出现频率 def count(arr) h = {} arr.each do |i| a = arr.count(i) h[i] = a end return h # 回传一个 hash end arr = ["a", "d", "d", "c", "b", "c", "c", "c", "d", "d", "e", "e", "e", "d", "a", "c", "e", "a", "d", "e"] answer = count(arr) puts answer # 答案应该是 {"a"=>3, "d"=>6, "c"=>5, "b"=>1, "e"=>5}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| # 给定一个散列 Hash,请根据指定的键值进行过滤和排序 arr = [ { "name" => "Peter", "age" => 30 }, { "name" => "John", "age" => 15 }, { "name" => "David", "age" => 45 }, { "name" => "Steven", "age" => 22 }, { "name" => "Vincent", "age" => 6 }, ] def filter(arr) result_arr = [] arr.each do |h| if h["age"] >= 18 result_arr.push(h) end end result_arr.sort_by { |i| i["age"] } end puts "所有成年人,并由小到大: #{filter(arr)}" # 答案应该是 #[ # { "name" => "Steven", "age" => 22 }, # { "name" => "Peter", "age" => 30 }, # { "name" => "David", "age" => 45 } #]
|
8. 对象 Object
什么是对象?
对象(Object)也是一种数据类型,这种数据除了拥有属性,也有方法可以调用。
在 Ruby 之中,其实每种数据类型,例如 String、Integer、Float、Array、Hash 等等,也都是对象(Object)。
例如字符串的 size
方法和 split
方法:
"abc".size
会回传 3
"a,b,c,d".split(",")
会回传数组 ["a", "b", "c", "d"]
其中 .
就是调用方法的意思。
如何自订义对象?
需要透过类 Class 自订义 Object。Class 就像一种样板,定义出同一种类型的对象有哪些共同的属性和方法。
例如以下的代码定义了 Car
类:
1 2 3 4 5 6 7 8
| class Car attr_accessor :color def run puts "This #{@color} car is running" end end
|
有了类,就可以用 new
来产生出对象:
1 2 3 4 5 6 7
| car1 = Car.new car1.color = "red" car1.run car2 = Car.new car2.color = "blue" car2.run
|
内建的类
刚刚说在 Ruby 之中,每种数据类型,例如 String、Integer、Float、Array、Hash 等等,也都是对象(Object)。
这些对象,其实也都是从类产生出来的,例如
"foobar".class
会回传 String
是个类
String.new("foobar")
等同于 "foobar"
[1,2,3].class
会回传 Array
是个类
Array.new([1,2,3])
等同于 [1,2,3]
不过因为这些数据类型太常用了,所以就不需要写 .new
来产生,直接用符号表示就可以了。
什么是常数 (Constant)?
在 Ruby 中,大写开头的变量又叫做常数,指一个数值固定不变的常量。
例如:
STATUS = ["pending", "confirmed"]
Pi = 3.1415926
所有的类(Class)都是一种常数,所以这也是为什么命名类的时候一律都是大写开头。
9. 档案处理
什么是档案?
所有正在执行中的程序和数据,都是放在内存(memory)之中。内存存取速度快,但是容量小而且一关机数据就不见了。
档案则是存在硬盘上,容量大但存取慢,但是关机重来还是继续保存着。
所谓的读取档案就是将数据从硬盘放进内存里面,让程序可以操作。反之写入档案则是将内存的数据写入硬盘进行保存。
如何开档读取和写入
使用 Ruby 的 File API:
读取档案内容:
1 2
| file = File.open("foo.txt") doc = file.read("foo.txt")
|
写入档案:
1 2 3 4 5
| File.open("bar.txt", "w+") do |f| f << "aaa" f << "\n" f << "bbb" end
|
这样就会写入 bar.txt,内容是 :
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13
| doc = File.read("wordcount.txt") words = doc.downcase.scan(/\w+/) result = {} words.each do |i| result[i] = words.count(i) end puts result
|
1 2 3 4 5 6 7 8 9
| doc = File.read("wordcount.txt") words = doc.downcase.scan(/\w+/) result = words.each_with_object(Hash.new(0)) { |word, hash| hash[word] += 1 } puts result
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| require 'byebug' text = File.read("todos.txt") todos = [] text.each_line do |line| todos << line.chomp end todos.each_with_index do |todo, index| puts "#{index}: #{todo}" end while (true) print "请输入指令 1. add 2. remove 3. save,然后按 Enter: " command = gets.chomp if command == "add" print "请输入代办事项: " todos << gets elsif command == "remove" print "请输入要删除的编号: " choose = gets().to_i todos.delete_at(choose) elsif command == "save" puts "存盘离开" File.open("todos.txt", "w+") do |f| for a in todos f << a + "\n" end end todos.each_with_index do |todo, index| puts "#{index}: #{todo}" end break; else puts "看不懂,请再输入一次" end end
|
10. 推荐资源
如果这教程对你来说偏简单,推荐你接下来买一本 Ruby 基础教程, 人民邮电,如果你要找工作面试,请尽量念完这一本。
如果想继续练习基础编程,或是面试的公司有考算法,请练习以下的题库网站(请练习 Easy 程度程度即可,更高难度需要学完数据结构课程)
如果这教程对你来说还是偏难太过抽象,建议你可以找啊哈磊的小学生坐在马桶上都能看懂的编程入门书,会用更浅显的对话和例子,用一整本书的长度来讲这教程的内容。