Khi làm việc với List chúng ta có "cons" pattern hay còn có tên là prepend method.
Method này được biểu diễn như sau:
Trong đó x là phần tử được gắn vào đầu của List sau khi method này được gọi (thay vì nối vào cuối).def ::[B >: A] (x: B): List[B] = new scala.collection.immutable.::(x, this)
Do vậy nếu gọi
1::List(2, 3)
Thì ta có kết quả là List[Int] = List(1, 2, 3) thay vì List[Int] = List(2, 3, 1) khi gọi method append (:::)
Một chú ý quan trọng trong scala, khi gặp các method bắt đầu từ dấu ":", thì method này được apply từ phải qua trái, thay vì từ trái qua phải. Với ví dụ trên, có thể viết lại thành List(2,3).::(1)
Nhưng khi làm việc với pattern matching trên List, chúng ta thường triển khai như sau:
Để ý khi chúng ta dùng case x::xs1, chúng ta đang không sử dụng method prepend của List, mà đang sử dụng một constructor.//reverse một List def rev[T](xs: List[T]): List[T] = xs match { case List() => xs case x :: xs1 => rev(xs1) ::: List(x) }
Khi sử dụng pattern matching trên List, chúng ta đang sử dụng scala.:: constructor, không phải sử dụng method prepend của List. Do vậy khi gọi x::xs1, ta đã tạo ra một instance của class scala.::, để dễ hiểu ta có thế viết lại bàm reverse List như sau:
b. Lấy chiều dài của List là một thao tác tốn nhiều resource, ta phải duyệt qua hết các phần tử của List cho đến phần tử cuối cùng để lấy chiều dài, do vậy nếu không có nhu cầu đặc biệt cần lấy chiều dài của List, để kiểm tra List có rỗng hay không, thay vì kiểm l.length == 0, nên dùng l.isEmpty.//reverse một List def rev[T](xs: List[T]): List[T] = xs match { case List() => xs case scala.::(_, _) => rev(xs.tail) ::: List(xs.head) }
c. Cũng vì lí do trên, nên trong hầu hết trường hợp, để thêm một phần tử mới vào List, ta nên dùng method prepend (::), thay vì append(::). Và cố gắng sắp xếp dữ liệu để các phần tử thường xuyên được dùng nằm ở đầu của List thay vì ở cuối.
No comments:
Post a Comment