The previous lectures introduced Ruby's basic data objects "string" and "value". Ruby has more than these two data objects. The structure of data objects involved in most Ruby programs is much more complex. This lecture summarizes arrays, hashes, and regular expressions. Don't worry, the knowledge here will be briefly explained, and we will further study these three knowledge points in the future.
Ruby array
What is an array?
An array is an ordered list of elements of any type. You can define an array by placing a list of elements between parentheses, as follows:
irb :001 > [1, 'Bob', 4.33, 'another string']
You will notice that the above array has strings, integers and floating point numbers. Arrays can contain anything (even other arrays!). Now we need to save this array in a variable so that we can use it.
irb :002 > array = [1, 'Bob', 4.33, 'another string']
We want to find the first element of the array. We can only use this first method.
irb :003 > array.first => 1
What about the last element?
irb :004 > array.last => "another string"
But what if we want to find the third element?
An array is what we call an indexed list. This means that each slot in the array has a number. You can reference any element through index numbers. The syntax for doing this is [] type the array name and index directly in the parentheses that follow. Let's try.
irb :005 > array[3]
What do you want the above code to return? Type it in irb and see what you get.
Are you surprised? You may think you'll get back the number 4.33, but the result is "other string". That's because all array indexes start with number 0. Try this in irb.
irb :006 > array[2] => 4.33
It's more like now. It's a little strange, but you'll get used to it.
Modify array
Suppose you want to add or remove something from the array. There are several ways to help you perform these operations.
If you want to permanently fetch the last item from the array, you can use this pop method.
irb :007 > array.pop => "another string" irb :008 > array => [1, "Bob", 4.33]
Note that when we call the pop method, array modifies the original variable (that is, it is a method that mutates the caller), but the return value is the pop-up element, as shown in the first line above. This is a key difference that needs to be understood. Make sure to distinguish between the return value of the expression and the actual effect of the expression.
If you want to permanently add the item back to the array, you can use the push method and send it the parameters you want to add.
irb :009 > array.push("another string") => [1, "Bob", 4.33, "another string"]
Another way to do this is to use the shovel operator (< <).
irb :010 > array.pop => "another string" irb :011 > array << "another string" => [1, "Bob", 4.33, "another string"]
Both push and < < method mutation callers, so the original array is modified.
Usually, you have an array and want to operate on many elements in the array in the same way. Ruby has many ways to perform these types of operations.
The map method iterates over an array, applies a block to each element of the array, and returns a new array containing these results. The following irb session shows how to use a map to get the squares of all the numbers in the array. The collect method is an alias map - they do the same thing.
irb :001 > a = [1, 2, 3, 4] => [1, 2, 3, 4] irb :002 > a.map { |num| num**2 } => [1, 4, 9, 16] irb :003 > a.collect { |num| num**2 } => [1, 4, 9, 16] irb :004 > a => [1, 2, 3, 4]
You will notice that after executing these methods, the initial array does not change. These methods are not destructive (that is, they do not change the caller). How do you know which methods will change the caller and which will not? You must use methods and pay attention to the output in irb; That is, you must remember or know it by using it.
delete_at this method is helpful if you want to eliminate the value at an index from the array. You should be careful with this because it will destructively modify your array. When this method is called, you will permanently change the array.
irb :005 > a.delete_at(1) => 2 irb :006 > a => [1, 3, 4]
As a side note, sometimes you know the value to delete, but you don't know the index. In these cases, you will need to use the delete method. The delete method permanently deletes all instances of the supplied value from the array.
irb :007 > my_pets = ["cat", "dog", "bird", "cat", "snake"] => ["cat", "dog", "bird", "cat", "snake"] irb :008 > my_pets.delete("cat") => "cat" irb :009 > my_pets => ["dog", "bird", "snake"]
Another useful method is the uniq method. This will traverse the array, remove any duplicate values that exist, and then return the result as a new array.
irb :010 > b = [1, 1, 2, 2, 3, 3, 4, 4] => [1, 1, 2, 2, 3, 3, 4, 4] irb :011 > b.uniq => [1, 2, 3, 4] irb :012 > b => [1, 1, 2, 2, 3, 3, 4, 4]
Note again that the uniq method does not modify the original b array; It returns a new array with duplicates removed.
If! Adding the bang suffix () to this method can destructively perform uniq functions. Much like the delete method works.
irb :013 > b = [1, 1, 2, 2, 3, 3, 4, 4] => [1, 1, 2, 2, 3, 3, 4, 4] irb :014 > b.uniq! => [1, 2, 3, 4] irb :015 > b => [1, 2, 3, 4]
Uniq and uniq! These are two different approaches to Ruby Arrays. You can't simply attach a! To any method and implement destructive operations.
Iterative array
We discussed using each to iterate over arrays in the loop section. The Ruby standard library has many similar methods. Let's look at the select method. This method iterates over an array and returns a new array containing any items that return true to the provided expression. That's a bite. As usual, looking at the code is more helpful than using a pile of words.
irb :001 > numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] irb :002 > numbers.select { |number| number > 4 } => [5, 6, 7, 8, 9, 10] irb :003 > numbers => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10
The select method selects all numbers greater than 4 and returns them in the array. It does not change the caller (the original numbers array is unmodified).
The method is the same!
! The bang suffix () at the end of the method name usually indicates that the method will permanently change (or mutate) the caller. Unfortunately, this is not always the case. It's a good rule to be wary of any method with a bang suffix and be sure to check the Ruby document to see if its behavior is destructive (the word "destructive" here only means changing the caller).
Also, note that some methods like pop and push are destructive, but! Finally, No. It's a bit confusing at first, but as you write more programs in Ruby, you'll begin to understand which methods are destructive and which aren't.
Change caller
We discussed changing the caller before, and we created an example to match it. But we think this concept is so important that we want to introduce it in more depth. It's important to remember this concept because you might send a parameter to a method and change the parameter forever without knowing it. This may be the main source of confusion. That's why it's important to know what a method does with its parameters and what the method returns.
We also want to re-examine destructive methods in the context of using methods. Look at the following two methods to see if you can understand why the first method makes the caller mutate, while the second method does not.
def mutate(arr) arr.pop end def not_mutate(arr) arr.select { |i| i > 3 } end a = [1, 2, 3, 4, 5, 6] mutate(a) not_mutate(a) puts a
The last line outputs 1, 2, 3, 4, and 5. The mutate method pop performs a destructive operation on its parameters (that is, modifying the a array, even if a is initialized outside the method. Therefore, the 6 element is ejected from the original array. This is not_ The mutate method performs a non-destructive operation (that is, select), so the original variable is not modified.
Nested Array
We discussed earlier that arrays can contain anything. You can also create arrays that contain arrays. Suppose you are holding a beach volleyball tournament and want to track all the teams participating in the game. You can create an array like this.
irb :001 > teams = [['Joe', 'Steve'], ['Frank', 'Molly'], ['Dan', 'Sara']] => [["Joe", "Steve"], ["Frank", "Molly"], ["Dan", "Sara"]]
Then you can find the team through the index.
irb :002 > teams[1] => ["Frank", "Molly"]
You can also have a hash array! We won't introduce too many crazy examples here, but in irb
Compare arrays
You can use the = = operator to compare the equality of arrays.
irb :001 > a = [1, 2, 3] => [1, 2, 3] irb :002 > b = [2, 3, 4] => [2, 3, 4] irb :003 > a == b => false irb :004 > b.pop => 4 irb :005 > b.unshift(1) => [1, 2, 3] irb :006 > a == b => true
You will notice that we unshift used this method in this example. Unshift is much like the push method. However, instead of adding a value to the end of the list, you add a value to the beginning of the list unshift.
to_s
This to_ The s method is used to create a string representation of an array. Ruby does this behind the scenes when you print an array to the screen using string interpolation.
irb :001 > a = [1, 2, 3] => [1, 2, 3] irb :002 > "It's as easy as #{a}" => "It's as easy as [1, 2, 3]"
In order for our array to print correctly, Ruby is calling to_s we the method on the array and add it to the string.
Common array methods
This section will introduce you to some common methods built into Ruby's Array class. You should bookmark the document page because it may be something you want to refer to often.
include?
What should be included? Method to check whether the given parameter is included in the array. There is a question mark at the end of it, which usually means that you should expect it to return a Boolean value, true or false. (this method is called predicate.) just like the method ending with "!", it is only strictly in accordance with the Convention, not the attribute of the language.
irb :001 > a = [1, 2, 3, 4, 5] => [1, 2, 3, 4, 5] irb :002 > a.include?(3) => true irb :003 > a.include?(6) => false
flatten
This flattening method can be used to get an array containing nested arrays and create a one-dimensional array.
irb: 001 > a = [1, 2, [3, 4, 5], [6, 7]] => [1, 2, [3, 4, 5], [6, 7]] irb: 002 > a.flatten => [1, 2, 3, 4, 5, 6, 7]
Is flattening destructive? Find yourself in irb or by consulting the Array documentation.
each_index
each_index iterates through the array much like each method of the method, however, the variable represents the index number of the value at each index rather than each index. It passes the index of the element to the block, and you can use it at will. Returns the original array.
irb: 001 > a = [1, 2, 3, 4, 5] => [1, 2, 3, 4, 5] irb: 002 > a.each_index { |i| puts "This is index #{i}" } This is index 0 This is index 1 This is index 2 This is index 3 This is index 4 => [1, 2, 3, 4, 5]
each_with_index
Another useful way to work in a similar way is each_index is each_with_index.
irb: 001 > a = [1, 2, 3, 4, 5] => [1, 2, 3, 4, 5] irb: 002 > a.each_with_index { |val, idx| puts "#{idx+1}. #{val}" } 1. 1 2. 2 3. 3 4. 4 5. 5 => [1, 2, 3, 4, 5]
each_with_index enables us to manipulate values and indexes by passing two parameters to the code block. The first is the value and the second is the index. Then you can use them in blocks.
sort
It returns a sorted array.
irb :001 > a = [5, 3, 8, 2, 4, 1] => [5, 3, 8, 2, 4, 1] irb :002 > a.sort => [1, 2, 3, 4, 5, 8]
Test again in irb to see if the sort method is destructive. (no, but test it yourself.) we won't remind you to test this in the future, but when you see such a method in the future, ask yourself, "does this method return new data or is the original data modified?".
product
This method can be used to combine two arrays in an interesting way. It returns an array that is a combination of all elements in all arrays.
irb :001 > [1, 2, 3].product([4, 5]) => [[1, 4], [1, 5], [2, 4], [2, 5], [3, 4], [3, 5]]
There are many interesting methods to introduce, but we want you to experience the power of Ruby arrays and many convenient methods built into Ruby. If you've ever thought about "I want my array...", there may already be a way to do this. First, check the document.
each vs map
each
Each provides a simple way to iterate over collections in Ruby and is more popular than using a for loop. This each method is suitable for objects that allow iteration and is usually used with blocks. Given a block, each runs the code in the block once for each element in the collection and returns the collection that called it. If no block is given, it returns an Enumerator. Let's look at some simple examples:
irb :001 > a = [1, 2, 3] irb :002 > a.each { |e| puts e } 1 2 3 => [1, 2, 3]
The above shows the most common use of each. We iterate over each element a in the array and print it out. Finally, it returns [1, 2, 3].
We can also modify the element a and print it:
irb :003 > a = [1, 2, 3] irb :004 > a.each { |e| puts e + 2 } 3 4 5 => [1, 2, 3]
Similarly, we print out the modified value and return to the original set a.
This is the last example of a block free; Returns an enumerator:
irb :005 > a = [1, 2, 3] irb :006 > a.each => #<Enumerator: ...>
map
map also applies to objects that allow iterations. Like each, when a block is given, it calls the given block once for each element in the collection. Its really different each is the return value. map creates and returns a new array containing block return values. Let's look at its actual effect:
irb :007 > a = [1, 2, 3] irb :008 > a.map { |x| x**2 } => [1, 4, 9]
We square each element in the block and create a new array containing the return value of the block. Finally, a new array is returned.
To really check whether map creates a new array of return values of blocks, let's look at an example put with mapand:
irb :009 > a = [1, 2, 3] irb :010 > a.map { |x| puts x**2 } => [nil, nil, nil]
Because puts returns nil, each called block time nil is returned as the value in the newly created returned array.
Finally, if no block is given, the map returns an Enumerator:
irb :011 > a = [1, 2, 3] irb :012 > a.map => #<Enumerator: ...>
Each and map are important ways to understand, but they can be confusing at first. Another way to remember these methods: each is used for iterations and map transformations.
Arrays are very valuable data sets. They can be used to store many different types of data, and you will often see them in the field. Ruby's array class has many built-in methods that can be used to perform many daily functions used by programmers. Let's practice using arrays through some exercises.
Ruby hash
What is hash?
Hash is a data structure that stores items through an associated key. This is in contrast to arrays, which store items in an ordered index. Entries in a hash are often referred to as key value pairs. This creates an associative representation of the data.
Most commonly, hashes are created using symbols as keys and any data type as values. All key value pairs in the hash are enclosed in curly braces {} and separated by commas.
You can create a hash using two syntax. The old syntax used a = > symbol to separate keys and values.
irb :001 > old_syntax_hash = {:name => 'bob'} => {:name=>'bob'}
The newer syntax was introduced in Ruby Version 1.9 and is much simpler. As you can see, the result is the same.
irb :002 > new_hash = {name: 'bob'} => {:name=>'bob'}
You can also have a hash containing many key value pairs.
irb :003 > person = { height: '6 ft', weight: '160 lbs' } => {:height=>'6 ft', :weight=>'160 lbs'}
Suppose you want to add to an existing hash.
irb :004 > person[:hair] = 'brown' => "brown" irb :005 > person => {:height=>'6 ft', :weight=>'160 lbs', :hair=>'brown'} irb :006> person[:age] = 62 => 62 irb :007> person => {:height=>'6 ft', :weight=>'160 lbs', :hair=>'brown', :age=>62}
What if you want to delete something from the existing hash?
irb :008 > person.delete(:age) => 62 irb :009 > person => {:height=>'6 ft', :weight=>'160 lbs', :hair=>'brown'}
Now how do I retrieve a piece of information from the hash?
irb :010 > person[:weight] => "160 lbs"
What if you want to merge two hashes together?
irb :011 > person.merge!(new_hash) => {:height=>'6 ft', :weight=>'160 lbs', :hair=>'brown', :name=>'bob'}
Note that we use the bang suffix (!) to make this change destructive. We could have chosen to use this merge method, which will return a new merge hash, but will not modify the original hash by person.
Iterative hash
Because there can be multiple elements in a hash, sometimes you want to traverse the hash to perform some operations on each element. Iterative hashes are similar to iterative arrays, but with some small differences. We'll use this method again in each, and this time we'll create one
New files to test this.
# iterating_over_hashes.rb person = {name: 'bob', height: '6 ft', weight: '160 lbs', hair: 'brown'} person.each do |key, value| puts "Bob's #{key} is #{value}" end
We each use this method as before, but this time we assign a variable to both the key and the value. In this example, we set the key of the key variable and the value of the variable. Run this program Ruby iterating from the command line_ over_ Hashes.rb to see the results. The output is:
Bob's name is bob Bob's height is 6 ft Bob's weight is 160 lbs Bob's hair is brown
Hash as an optional parameter
You may remember that in Chapter 3 on methods, we talked about the ability to assign default parameters to your methods so that the output is always consistent. You can also use hashes to accept optional parameters when creating methods. This is helpful when you want to provide more flexibility and expressiveness to your approach. More options, if you like! Let's create a method to do this.
# optional_parameters.rb def greeting(name, options = {}) if options.empty? puts "Hi, my name is #{name}" else puts "Hi, my name is #{name} and I'm #{options[:age]}" + " years old and I live in #{options[:city]}." end end greeting("Bob") greeting("Bob", {age: 62, city: "New York City"})
We're using Ruby hash empty? Method to detect whether the options parameter (hash) passed in anything. You haven't seen this method yet, but you can infer its function. You can also check the ruby documentation to find this method. Finally, we called this method twice. Do not use optional parameters once, and send optional parameters with hash the second time. You can see that using this feature can make your method more expressive and dynamic.
Finally, to add a small turning point, you can also pass parameters to the greeting method like this:
greeting("Bob", age: 62, city: "New York City")
Note that when the hash is the last parameter, braces {} are not required, and the effect is the same as the previous example. Rails developers typically use this Convention. Just understanding this concept can help you decipher some previously mysterious rails code!
Hashes vs Arrays
This chapter and the previous chapter introduce two very important and widely used data structures: hash and array. You may be a little overwhelmed when you see all the different ways to represent data in code. Don't be too afraid. Divide these things into small parts and then apply them. Then add more widgets as you move. It's impossible to know everything from the beginning, so try to learn something well, and then learn from it.
When deciding whether to use hash or array, ask yourself a few questions:
Does this data need to be associated with a specific label? If yes, use hash. If the data does not have natural labels, the array usually works.
Does order matter? If yes, an array is used. Starting with Ruby 1.9, hashes also maintain order, but usually the order items are stored in an array.
Do I need a "stack" or "queue" structure? Arrays are good at mimicking simple "first in first out" queues or "last in first out" stacks.
As you grow as a developer, your familiarity with these two data structures will naturally affect the data structures you use when seeking to solve specific problems. The key is to practice and experiment each data structure to find out which data structure is the most effective in some cases.
A note on hash keys
So far, we have been using symbols as keys in all hashes we create. We do this because it is the most common use case. However, you can use different data types for keys. Let's have a look.
irb :001 > {"height" => "6 ft"} # string as key => {"height"=>"6 ft"} irb :002 > {["height"] => "6 ft"} # array as key => {["height"]=>"6 ft"} irb :003 > {1 => "one"} # integer as key => {1=>"one"} irb :004 > {45.324 => "forty-five point something"} # float as key => {45.324=>"forty-five point something"} irb :005 > {{key: "key"} => "hash as a key"} # hash as key => {{:key=>"key"}=>"hash as a key"}
Very strange. So you can see that hashes can be very diverse, and you can store almost anything you want in them. Also note that = > when we deviate from using symbols as keys, we are forced to use the old style (i.e. use).
Common hash methods
Let's take a look at some common methods attached to Ruby's Hash class.
has_key?
This has_key? Method allows you to check whether the hash contains a specific key. It returns a Boolean value.
irb :001 > name_and_age = { "Bob" => 42, "Steve" => 31, "Joe" => 19} => {"Bob"=>42, "Steve"=>31, "Joe"=>19} irb :002 > name_and_age.has_key?("Steve") => true irb :003 > name_and_age.has_key?("Larry") => false
select
The select method allows you to pass a block and will return any key value pairs evaluated as true when the block is run.
irb :004 > name_and_age.select { |k,v| k == "Bob" } => {"Bob"=>42} irb :005 > name_and_age.select { |k,v| (k == "Bob") || (v == 19) } => {"Bob"=>42, "Joe"=>19}
fetch
The fetch method allows you to pass a given key. If the key exists, it will return the value of the key. If the key does not exist, you can also specify the return option. Look at the Ruby documentation here and see what else is possible.
irb :006 > name_and_age.fetch("Steve") => 31 irb :007 > name_and_age.fetch("Larry") => KeyError: key not found: "Larry" from (irb):32:in `fetch' from (irb):32 from /usr/local/rvm/rubies/ruby-2.5.3/bin/irb:16:in `<main>' irb :008 > name_and_age.fetch("Larry", "Larry isn't in this hash") => "Larry isn't in this hash"
to_a
This to_ The a method returns the array version of the hash when called. Let's see its actual effect. However, it does not permanently modify the hash.
irb :009 > name_and_age.to_a => [["Bob", 42], ["Steve", 31], ["Joe", 19]] irb :010 > name_and_age => {"Bob"=>42, "Steve"=>31, "Joe"=>19}
Key and value
Finally, if you just want to retrieve all keys or all values from the hash, you can easily do this:
irb :0011 > name_and_age.keys => ["Bob", "Steve", "Joe"] irb :0012 > name_and_age.values => [42, 31, 19]
Note that the return value is in array format. Because it returns an array, you can do some interesting things, such as printing out all the keys in the hash: name_and_age.keys.each { |k| puts k }.
Description of hash order
In previous Ruby versions, you couldn't rely on hashes to maintain order. Starting with ruby 1.9, hashes maintain their storage order. It's important to understand this because if you've ever used an older version of Ruby (any version before Ruby 1.9), you can't rely on hash values in any particular order.
Now you have a good start and can learn all the wonderful things hash can do. The concept of key value pairs often appears in other technologies, so it's best to hold on to it. I think you know what will happen next... More practice!
What are symbols
In Ruby, symbols are very similar to string objects. Symbols are also objects. They are generally used as name tags, representing the name of objects such as methods, etc. To create a symbol, simply add: at the beginning of the identifier.
sym = :foo # Indicates the symbol ": foo" sym2 = :"foo" # ditto
The functions that symbols can achieve can also be realized by most strings. However, in the process of simply judging "equality" such as hash keys, using symbols will be more efficient than strings, so we will often use symbols in practical programming.
In addition, symbols and strings can be converted to each other. Use to for symbols_ S method, you can get the corresponding string. Instead, use to for strings_ SYM method, you can get the corresponding symbol. The difference between string and symbol will not be repeated here. Specifically, we can communicate within the group.
Using symbol objects as keys in hashes is more efficient than string objects.
3 regular expression
When processing strings in Ruby, we often use regular expression. Using regular expressions, the following functions can be implemented very simply.
- Match string to pattern
- Use pattern to split strings
Pattern and matching
Sometimes we need to process strings according to specific patterns, such as "find the line containing ○ string" or "extract ○ and ○" ×× String between. The process of judging whether the string is applicable to a pattern is called matching. If the string is applicable to the pattern, it is called successful matching.
For a simple example, we need to find out all the names containing peng in the names array,
names = ['peng', 'jun', 'pen', 'pengeong'] names.each do |name| if /peng/ =~ name puts name end end
Similarly, we can also query all names that do not contain peng
names = ['peng', 'jun', 'pen', 'pengeong'] names.each do |name| if !(/peng/ =~ name) puts name end end
4 Summary
Array and hash are the most commonly used data structures in Ruby, and regular expression is the most convenient and efficient string processing method.