Như bạn có thể thấy, nó là một chức năng rất đơn giản.
Tuy nhiên, có một điều thực sự khó chịu về chức năng này.
Nó tự động in một dòng mới ‘\ n’ ở cuối dòng!
Hãy xem ví dụ này
print("Hello World!")
print("My name is Nguyenpv")
# output:# Hello World!
# My name is Nguyenpv
Như bạn có thể nhận thấy, hai chuỗi không được in lần lượt từng chuỗi trên cùng một dòng mà thay vào đó là các dòng riêng biệt.
Mặc dù đây có thể là những gì bạn thực sự muốn, nhưng không phải lúc nào cũng như vậy.
nếu bạn đến từ một ngôn ngữ khác, bạn có thể thoải mái hơn khi đề cập rõ ràng liệu một dòng mới có nên được in ra hay không.
Ví dụ: trong Java, bạn phải thể hiện rõ ràng mong muốn in một dòng mới bằng cách sử dụng chức năng println hoặc nhập ký tự dòng mới (\ n) bên trong chức năng in của bạn:
Python 3 cung cấp giải pháp đơn giản nhất, tất cả những gì bạn phải làm là cung cấp thêm một đối số cho hàm print.
# use the named argument "end" to explicitly specify the end of line string
print("Hello World!", end = '')
print("My name is Nguyenpv")
# output:
# Hello World!My name is Nguyenpv
Bạn có thể sử dụng đối số end để đề cập rõ ràng chuỗi cần được nối ở cuối dòng.
Bất cứ điều gì bạn cần làm là thêm đối số end sẽ là chuỗi kết thúc.
Vì vậy, nếu bạn cung cấp một chuỗi trống, thì sẽ không có ký tự dòng mới và không có khoảng trắng nào sẽ được thêm vào đầu vào của bạn.
Trong python 2, cách dễ nhất để tránh dòng mới kết thúc là sử dụng dấu phẩy ở cuối câu lệnh in của bạn
# no newlines but a space will be printed out
print "Hello World!",
print "My name is Nguyenpv"
# output
# Hello World! My name is Nguyenpv
Như bạn có thể thấy, mặc dù không có dòng mới, chúng tôi vẫn có một ký tự khoảng trắng giữa hai câu lệnh in.
Nếu bạn thực sự cần một không gian, thì đây là cách đơn giản nhất và đơn giản nhất để đi.
Nhưng nếu bạn muốn in mà không có dấu cách hoặc dòng mới thì sao?
Trong trường hợp này, bạn có thể sử dụng hàm sys.stdout.write từ module sys .
Hàm này sẽ chỉ in bất cứ thứ gì bạn nói rõ ràng để in.
Không có chuỗi kết thúc.
Không có phép thuật!
Hãy lấy một ví dụ
import sys
sys.stdout.write("Hello World!")
sys.stdout.write("My name is Nguyenpv")
# output
# Hello World!My name is Nguyenpv
Haiz, đoạn này thấy nhì nhằng, phức tạp hơn ngôn ngữ lập trình khác rồi đấy, ví dụ PHP, ở PHP thì chỉ cẩn dùng 1 hàm ví dụ “echo“, rồi muốn in như nào thì in : ))
Danh sách liên kết (DSLK) đơn (Singly linked list) là một cấu trúc dữ liệu cơ bản và có cực kì nhiều ứng dụng. Ý tưởng của DSLK đơn khá đơn giản. Tuy nhiên, để cài đặt cấu trúc DSLK đơn một cách ngắn gọn, hiệu quả thì không phải là điều hiển nhiên. Trong bài này, chúng ta sẽ thảo luận các cách cài đặt danh sách liên kết (trong C) sao cho hiệu quả và ngắn gọn.
Cài đặt DSLK trong C không thể tránh khỏi sử dụng con trỏ (pointer) và struct. Con trỏ là một kiểu dữ liệu cực kì mạnh của ngôn ngữ C. Nó cho phép người lập trình thao tác bộ nhớ rất hiệu quả. Những lập trình viên giỏi luôn là những lập trình viên biết thao tác con trỏ một cách thuần thục. Tuy nhiên, điểm yếu của con trỏ là khó học, khó nắm bắt và cũng khó debug các chương trình có con trỏ, nhất là đối với những người lần đầu tiếp xúc với khái niệm con trỏ.
Trong bài này, mình cũng không có gắng giải thích ý nghĩa, mặc dù có nhắc lại, con trỏ. Mình khuyến khích các bạn tự tìm hiểu, vì có rất nhiều nguồn, kể cả tiếng Anh và tiếng Việt. Phần cuối bài mình sẽ liên kết một số bài viết hay về con trỏ. Mục tiêu của bài này là minh họa tối đa khả năng kì diệu của con trỏ trong thao danh sách liên kết. Các bài khác trong blog mình thường minh họa bằng giả mã. Tuy nhiên, bài này mình sẽ trực tiếp sử dụng code C.
Con trỏ và struct
Một con trỏ (pointer) là một biến dùng để lưu trữ địa chỉ của một biến khác. Biến khác ở đây có thể là một biến thông thường như biến số nguyên, biến số thực, hoặc có thể là một mảng, một hàm, và có khi cũng chính là một con trỏ khác. Vì con trỏ là một biến nên nó cũng cần phải được lưu trữ ở đâu đó trong bộ nhớ; có nghĩa là bản thân biến con trỏ cũng có một địa chỉ trong bộ nhớ. Do đó, ta có thể dùng con trỏ đề lưu địa chỉ của một con trỏ khác. Tính chất này cũng chính là sự kì diệu của con trỏ mà mình sẽ minh họa trong bài này.
Cú pháp khai báo con trỏ trong C, nói một cách đơn giản (nhưng không hoàn toàn chính xác), gồm 3 phần: (i) kiểu của biến khác mà con trỏ lưu trữ địa chỉ, (2) dấu * và (3) tên của con trỏ. Con trỏ hàm thì khai báo hơi khác một chút, nhưng đây không phải vấn đề trọng tâm của bài viết. Ví dụ, khai báo một số con trỏ:
int *a; // pointer to an integer
float *b; // pointer to a float
char *c; // pointer to character
Khai báo thì như vậy, nhưng ý nghĩa của con trỏ thì không phải lúc nào cũng nhất quán. Ví dụ con trỏ int *a vừa có thể hiểu là con trỏ tới một biến số nguyên, vừa có thể hiểu là con trỏ tới phần tử đầu tiên của một mảng số nguyên.
Struct trong C cho phép chúng ta tập hợp một hoặc một vài kiểu dữ liêu khác nhau thành một kiểu dữ liệu mới. Các kiểu dữ liệu thành phần của một struct có thể là kiểu dữ liệu sẵn có như số nguyên, số thực, con trỏ, hoặc một kiểu struct đã định nghĩa trước đó. Tóm lại, struct cho phép chúng ta “gộp” một số kiểu dữ liệu đã có lại với nhau để tiện thao tác.
Trong DSLK, struct được sử dụng để khai báo một “mắt xích” của danh sách. Ví dụ ta muốn khai báo một mắt xích của một danh sách liên kết các biến kiểu int thì ta có thể khai báo như sau:
typedef struct llnode{
int x;
struct llnode *next;
} llnode;
Biến trong khai báo trên là dữ liệu của mỗi mắt xích và nó có kiểu int. Kiểu của còn có thể là con trỏ, hay một struct. Mỗi mắt xích thường được minh họa như trong Figure 1, trong đó, mũi tên ám chỉ con trỏ lưu địa chỉ của mắt xích tiếp theo (con trỏ next). Con trỏ này trong mắt xích cuối cùng của danh sách thường là Null. Figure 1: Biểu diễn một mắt xích của danh sách liên kết.
Để khởi tạo một mắt xích, ta sẽ dùng hàm malloc (viết tắt của memory allocation). Malloc là một hàm quản lí bộ nhớ của C. Bản thân hàm này cũng khá phức tạp. Bạn đọc xem thêm tại liên kết ở cuối bài.
llnode * create_new_node(int datum){
llnode * node = (llnode *)malloc(sizeof(llnode)); // allocate memory for a new linked list node
node->x = datum;
node->next = NULL;
return node;
}
Danh sách liên kết đơn
Danh sách liên kết đơn, về mặt trực quan, là cấu trúc dữ liệu tuyến tính giống như một cái xích dài liên kết các “mắt xích” với nhau. Mỗi mắt xích có dạng khai báo llnode ở trên. Figure 2 minh họa một danh sách liên kết với 5 phần tử.
Figure 2: Một danh sách liên kết với 5 phần tử.
Để theo dõi danh sách, ta sẽ dùng một con trỏ đặc biệt, gọi là con trỏ head. Con trỏ này lưu trữ địa chỉ của mắt xích đầu tiên của danh sách. Như đã nói ở trên, con trỏ của mắt xích cuối cùng của danh sách sẽ có giá trị Null. Ta có thể duyệt qua danh sách này bằng cách bắt đầu từ phần tử đầu tiên, đi theo con trỏ liên kết để đi tới nút tiếp theo. Đến khi ta gặp con trỏ Null thì ta đã duyệt xong danh sách. Đoạn code dưới đây là thủ tục walk_down để duyệt danh sách.
void walk_down(llnode *head){
while(head->next != NULL){
printf("%d," head->x);
head = head->next; // walk to the next node
}
}
Trong đoạn code trên, liệu ta có bị mất con trỏ head? Câu trả lời là không vì mỗi lần gọi một hàm walk_down như trên, một bản “copy” của con trỏ head sẽ được tạo ra, và hàm walk_down sẽ thay đổi bản copy này. Bản gốc vẫn không thay đổi. Nếu bạn thay đổi con trỏ head trong hàm main (hàm mà bạn tạo ra con trỏ này) hoặc con trỏ head là một biến toàn cục, thì con trỏ head sẽ bị thay đổi, hay bị mất. Điều gì sẽ xảy ra nếu như ta “làm mất” con trỏ head? Bạn sẽ mất dấu của danh sách và do đó, sẽ không thể thao tác được với danh sách nữa. Phần bộ nhớ của danh sách lúc này sẽ vẫn bị chiếm bởi danh sách, do đó, tạo ra rác (garbage) trong hệ thống. Phần rác này tồn tại cho đến khi chương trình kết thúc. Bài học rút ra: khi nào bạn không còn cần danh sách nữa thì sử dụng hàm free để giải phóng bộ nhớ, đừng bao giờ để mất dấu con trỏ head vì nó sẽ tạo ra rác.
Thêm phần tử mới vào danh sách liên kết
Khi thêm phần tử mới vào danh sách, nếu bài toán không có yêu cầu gì đặc biệt, thì bạn nên thêm vào đầu của danh sách. Nếu bạn thêm vào đuôi của danh sách (theo tư duy thông thường), thì ngoài phải trả thêm bộ nhớ để lưu trữ con trỏ đuôi của danh sách (nếu không lưu con trỏ này thì sẽ phải duyệt rất tốn thời gian), bạn phải xét trường hợp khi danh sách rỗng (con trỏ đuôi là Null), i.e, thêm if-then trong code. Điều này làm code vừa chậm vừa “tối sủa” (xem code ví dụ dưới đây).
llnode * head; // head is a global variable
llnode* insert_to_tail(llnode *tail, int datum){
llnode *node = create_new_node(datum); // create a new node
if( tail == NULL) { // the list is empty
tail = node;
head = tail;
} else{
tail->next = node;
tail= node;
}
return tail;
}
Tóm lại, luôn thêm vào đầu danh sách nếu có thể. Đoạn code sau minh họa thao tác chèn vào đầu. Rõ ràng đoạn code dưới đây đẹp hơn rất nhiều.
llnode * head; // head is a global variable
void insert_to_head(int datum){
llnode *node = create_new_node(datum); // create a new node
node->next = head; // dont need to care whether head is null or not
head = node;
}
Figure 3: Các bước chèn một mắt xích có giá trị 6 vào đầu DSLK trong Figure 2.
Xóa một phần tử khỏi danh sách liên kết
Giả sử bạn muốn xóa một mắt xích có trường dữ liệu có giá trị ra khỏi danh sách, bạn có thể làm như sau (xem minh họa trong Figure 4):
Duyệt danh sách (từ đầu) để tìm ra mắt xích có giá trị . Trong quá trình duyệt, bạn sẽ lưu trữ con trỏ prev, để trỏ tới mắt xích cha của mắt xích hiện tại. Mắt xích cha được hiểu là mắt xích có con trỏ trỏ vào nút hiện tại.
Cập nhật con trỏ prev để trỏ vào mắt xích con của mắt xích có dữ liệu .
Figure 4: Các bước xóa mắt xích có giá trị ra khỏi danh sách.
Code:
// Warning: this piece of code is BUGGY
void buggy_remove(int datum){
llnode *prev = NULL;
llnode *iterator = head;
while(iterator->x != datum){
prev = iterator;
head = iterator->next;
}
prev->next = iterator->next;
free(iterator); // officially remove the node having value datum
}
Đoạn code trên chỉ minh họa ý tưởng, và nó là đoạn code có bug, nghĩa là nó chỉ thành công trong một số trường hợp. Điều gì sẽ xảy ra nếu ta chạy đoạn code trên trong trường hợp mắt xích đầu tiên có giá trị datum? Khi đó đoạn code trong vòng while sẽ không thực hiện, và kết quả là prev vẫn là Null sau vòng lặp while. Đến đây bạn có thể gặp lỗi Segmentation fault. Bạn có thể sửa đổi code trên như sau:
// Warning: this piece of code is possibly buggy
void good_but_not_clean_remove( int datum){
llnode *prev = NULL;
llnode *iterator = head;
while(iterator->x != datum){
prev = iterator;
iterator = iterator->next;
}
if( prev == NULL){ // datum is the head
head = head->next;
} else {
prev->next = iterator->next;
}
free(iterator); // officially remove the node having value datum
}
Đoạn code này ok, có thể không có lỗi, nhưng trông không được “sạch sẽ” và chậm vì có if-then. Cách tốt hơn, không có if-then, đó là thao tác trên địa chỉ của con trỏ. Phương pháp này minh họa cách sử dụng con trỏ rất thông minh. Hiểu đoạn code này có thể khó nhưng một khi đã hiểu thì bạn sẽ thấy cái hay của nó.
void remove( int datum){
llnode **iterator = &(head); // take the address of the head pointer
while(*(iterator)->x != datum){
iterator = &((*iterator)->next);
}
*iterator = (*iterator)->next;
}
Nhắc lại, toán tử & là toán tử lấy địa chỉ của một biến, còn toán tử * là toán tử lấy giá trị của biến có địa chỉ lưu trong con trỏ. Nhìn vào đoạn code trên, sự khác biệt so với đoạn code good_but_not_clean_remove là chúng ta thao tác trên địa chỉ của con trỏ, thay vì thao tác trên con trỏ. Đoạn code trên được sửa đổi từ bài viết ở đây.
Đôi khi trong một số trường hợp, ta muốn xóa một nút khỏi danh sách khi mà ta biết trước địa chỉ của nút đó. Cụ thể, ta muốn đoạn code tương tự như sau:
Nếu chỉ đơn giản gán current_node = Null thì đoạn code trên sẽ không thực hiện đúng như mong muốn (bạn đọc nên thử và giải thích tại sao). Có hai cách để làm. Cách làm không mấy sạch sẽ là copy dữ liệu từ nút mắt xích sau đó vào nút hiện tại và ta xóa nút sau đó.
void remove_by_copy_data(llnode * current_node){
llnode *next_node = current_node->next;
current_node->x = next_node->x; // copy data from next to current
current_node->next = next_node->next;
free(next_node); // delete next_node
}
Tuy nhiên, cách này không áp dụng được nếu current_node là mắt xích cuối cùng của DSLK (bạn đọc có thể thử để kiểm tra). Cách tốt hơn, lấy ý tưởng hàm remove ở trên sử dụng địa chỉ của con trỏ, ta sẽ truyên vào hàm địa chỉ của current_node.
Cách này áp dụng được ngay cả khi current_node là mắt xích cuối cùng của DSLK.
Một số biến thể của DSLK đơn
Danh sách liên kết đôi
Biến thể đầu tiên là DSLK đôi (doubly linked list), trong đó, mỗi mắt xích có 2 con trỏ. Con trỏ đầu tiên trỏ vào mắt xích trước nó trong DSLK và con trỏ thứ hai trỏ vào mắt xích sau đó trong DSLK.
DSLK đôi so với DSLK đơn cần nhiều bộ nhớ hơn để lưu trữ thêm một con trỏ. Các thao tác xóa và thêm mới cũng lâu hơn vì ta còn phải cập nhật cả con trỏ prev mỗi khi ta cần xóa hoặc thêm vào DSLK. Về điểm mạnh, DSLK đôi mềm dẻo hơn vì từ một nút, ta có thể đi đến nút trước nó mà không phải duyệt từ đầu DSLK. Tính mềm dẻo này được Knuth [2] khai thác triệt để trong thiết kế cấu trúc Dancing Links, một cấu trúc dữ liệu hỗ trợ các thuật toán quay lui cho bài toán liệt kê tổ hợp.
Ngoài ra, ta có thể không cần con trỏ head để xác định nút đầu tiên của danh sách như DSLK đơn (tại sao?). Nếu con trỏ head bị mất, ta vẫn có thể tìm được phần tử đầu tiên của DSLK nếu ta được phép truy nhập đến một phần tử bất kì của DSLK. DSLK đôi cũng chính là cấu trúc đằng sau LinkedList của Java.
Danh sách liên kết vòng
Danh sách liên kết vòng (circularly linked list) là một biến thể khác của DSLK đơn, trong đó con trỏ next của mắt xích cuối cùng của danh sách trỏ vào mắt xích đầu tiên của danh sách để tạo thành một vòng tròn (xem Figure 6). Với cấu trúc này, khái niệm đầu và cuối không thực sự có ý nghĩa. Do đó, ta thường chỉ lưu một con trỏ đặc biệt để trỏ vào một nút nào đó trong danh sách.
Figure 6: Một DSLK vòng với 5 mắt xích.
Danh sách liên kết vòng thường được dùng trong các ứng dụng trong đó các thành phần tham gia được thực thi theo lượt. Ví dụ trong các game đánh bài khi mỗi người chơi được phép đi một lượt theo vòng. Một bài tập hay ứng dụng danh sách liên kết vòng là bài toán Josephus.
Liên kết về con trỏ trong C
Chú ý: Phần này sẽ liên tục được cập nhật. Bạn nào có tài liệu hay về con trỏ cũng như quản lí bộ nhớ thì comment xuống dưới để mình liên kết tới bạn đọc.
[1] T.H Cormen, C.E. Leiserson, R. Rivest, C. Stein. Introduction to Algorithms (2nd ed.), Chapter 10 . MIT Press and McGraw-Hill (2001). ISBN 0-262-03293-7.
[2] D.E. Knuth, Donald E. Dancing links. arXiv preprint cs/0011047 (2000).
Bài viết được sự cho phép của tác giả Lê Xuân Quỳnh
Nếu bạn biết về ARC là gì và tại sao lại rò rỉ bộ nhớ trong ứng dụng IOS thì bạn có thể chuyển sang phần 2. Vì đây là 1 bài viết rất dài, đề cập đến các nội dung sau:
Thế nào là rò rỉ bộ nhớ trong IOS(Memory Leaks)
Tại sao lại Memory Leaks
Cách mà ARC không thể giải phóng bộ nhớ
Memory Leaks trong hàm Closure
Giải pháp khả thi
Những tình huống đặc biệt(Singleton và static Classes Memory leaks)
Khác nhau giữa weak và unowned
Xác định leaks sử dụng Memory Graph Debugger
Một số quy tắc Thumbs
Swift sử dụng Automatic Reference Counting(ARC) để theo dõi và quản lý bộ nhớ ứng dụng. Trong đa số trường hợp, điều này nghĩa là sự quản lý chỉ làm việc trong Swift framework 1 cách tự động, và bạn không cần phải hiểu về quản lý bộ nhớ như nào. ARC tự động giải phóng bộ nhớ bởi việc khi 1 instances của class sẽ giải phóng khi không cần thiết nữa.
Phần 1: Memory leaks trong IOS
Một memory leak xảy ra khi bộ nhớ giữ chiếm giữ và không thể giải phóng bằng ARC bởi vì nó không phân biệt được thực sự có được sử dụng hay không. Và vấn đề phổ biến nhất là chúng ta tạo ra memory leaks khi tạo ra 1 vòng retained cycles – nắm giữ lẫn nhau xảy ra! Chúng ta sẽ trình bày kỹ hơn ở dưới.
Mỗi khi bạn tạo ra 1 đối tượng(object) từ class, ARC sẽ lưu thông tin về vùng bộ nhớ mà đối tượng này sử dụng. Khi 1 object cần được giải phóng, thì làm thế nào ARC biết được điều đó. Để hiểu về vấn đề này chúng ta sẽ tìm hiểu cách ARC làm việc.
Mọi biến khởi tạo sẽ mặc định là kiểu strong. (strong là kiểu ràng buộc mạnh, weak yếu). Khi bạn tạo ra instance của nó với kiểu strong, thì ARC sẽ tăng biến đếm lên 1. Hiểu nôm na là ARC tạo ra 1 biến count = 0, khi obj tạo ra thì tăng lên 1, khi giải phóng thì trừ đi 1. Bao giờ biến count này = 0 nghĩa là nó không cần nữa, và ARC sẽ tự động giải phóng biến này ra. Cùng xem ví dụ sau:
var referenceOne: Person? = Person()
chúng ta tạo ra 1 referenceOne của lớp Person. ARC sẽ tự động cấp phát bộ nhớ cho nó kiểu strong và tăng count của biến này thành 1.
Khi khởi tạo object
var reference2 = referenceOne
Sau khi thực hiện câu lệnh trên, chúng ta tạo 1 liên kết mạnh tới object Person bằng việc gán địa chỉ sang reference2. Như bạn thấy ở hình sau, biến count = 2 do 2 thằng cùng nắm giữ địa chỉ này.
cả 2 cùng gán tới object đã khởi tạo
referenceOne = nil
Chúng ta loại bỏ tham chiếu strong tới biến referenceOne bằng cách gán nil cho nó. Như hình sau biến count lại quay về là 1.
reference2 = nil
Tương tự, chúng ta bỏ tham chiếu strong tới biến reference2 và hiện tại biến count = 0.
Như vậy object đã tạo không còn ai tham chiếu, ARC sẽ xóa ra khỏi bộ nhớ. Đấy là cách mà ARC làm việc.
Thế tại sao bộ nhớ lại rò rỉ?
Như chúng ta hiểu ARC sẽ tự động xóa biến khỏi bộ nhớ khi count = 0, nhưng vì lý do nào đó mà count không bao giờ = 0 được, nên không giải phóng được bộ nhớ. Hãy đọc tiếp nhé!
Khi nào ARC cố gắng kiểm tra count của 1 biến RC(reference count)
Khi bạn làm việc với cocoa hay cocoa touch thì đơn giản là nó kiểm tra khi một hàm thoát khỏi vòng lặp trên thread. Luật kiểm tra như sau:
Nếu không ai tham chiếu tới 1 object thì ARC giải phóng
Nếu 1 object không có tham chiếu mạnh tới nó.
Sau đây là ví dụ cụ thể:
var user: User? = User() //1 tham chiếu tới user
var todo: Todo? = Todo() //1 tham chiếu tới todo
Như hình dưới, ARC tạo ra 1 tham chiếu strong tới user và todo.
user?.todo = todo //2 tham chiếu mạnh tới todo
todo?.associatedUser = user// 2 tham chiếu mạnh tới user
2 câu lệnh trên hoạt động như sau:
Tăng RC của todo lên 2, todo có 2 chủ sở hữu, 1 lần tạo ra và 1 lần gán.
Tăng RC của user lên 2, tương tự như trên.
user = nil //giảm RC của user còn 1
todo = nil //giảm RC của todo còn 1
Lý tưởng nhất là khi đặt user về nil thì đáng ra RC phải là 0. Tuy nhiên lúc này trong mỗi object lại tham chiếu mạnh tới nhau nên không thể giải phóng được. Và do vậy chúng ta tạo ra strong reference cycle – vòng tròn tham chiếu mạnh lẫn nhau không thể giải phóng được.
Giải pháp
Có 2 giải pháp, dùng tham chiếu yếu weak hoặc tham chiếu unowned.
Sửa lại code như sau:
var user: User? = User() //RC = 1 tới user
var todo: Todo? = Todo() //RC = 1 tới todo
Câu lệnh trên sẽ tăng RC cho mỗi biến lên 1.
user?.todo = todo //RC = 2 tới todo
todo?.associatedUser = user //RC = 1 tới user
RC = 2 tham chiếu mạnh tới todo, còn user có RC = 1 do associatedUser là tham chiếu yếu.
user = nil
Câu lệnh làm RC của user về 0.
ARC kiểm tra và xóa user ra khỏi bộ nhớ. Vậy todo còn 1 tham chiếu mạnh tới nó.
todo = nil
Lúc này todo có RC = 0 và bị xóa khỏi bộ nhớ.
Quy tắc: Khi 2 đối tượng tham chiếu lẫn nhau thì biến tham chiếu trở về weak hoặc unowned.
Memory Leaks trong Closure
Memory Leak in Closure = self refers to → object refers to → self
Closure là một hàm được tạo ra từ bên trong một hàm khác (hàm cha), nó có thể sử dụng các biến toàn cục, biến cục bộ của hàm cha và biến cục bộ của chính nó. Và khi sử dụng thì nó capture(chụp lấy) các biến hay hằng và ném vào trong scope của nó.
Thế nào là capturing?
Cách hoạt động:
Chúng ta tạo 2 biến a,b với 2 giá trị khác nhau 20 và 30.
Chúng ta tạo hà someClosure và captures a, b bởi tham chiếu mạnh.
Khi phương thức someMethodThatTakeClosure gọi closure và nó sẽ trả về 2 giá trị tổng của a, b captures từ hàm viewDidLoad. Giá trị là 50
Bài viết được sự cho phép của tác giả Nguyễn Chí Thức
Trong bài Python này bạn sẽ học về biến toàn cục (global), biến cục bộ (local), biến nonlocal trong Python và trường hợp sử dụng các biến này.
Biến toàn cục trong Python
Trong ngôn ngữ lập trình Python, một biến được khai báo bên ngoài hàm hoặc trong phạm vi toàn cục được gọi là biến toàn cục hay biến global. Biến toàn cục có thể được truy cập từ bên trong hoặc bên ngoài hàm.
Hãy xem ví dụ về cách tạo biến toàn cục trong Python.
x = "Biến toàn cục"#khai báo biến x#Gọi x từ trong hàm vidu()defvidu():
print("x trong hàm vidu() :", x)
vidu()
#Gọi x ngoài hàm vidu()
print("x ngoài hàm vidu():", x)
Trong ví dụ trên, ta khai báo biến x là biến toàn cục, và định nghĩa hàm vidu() để in biến x. Cuối cùng ta gọi hàm vidu() để in giá trị của biến x. Chạy code trên ta sẽ được kết quả là:
x trong hàm vidu(): Biến toàn cục
x ngoài hàm vidu(): Biến toàn cục
Chuyện gì sẽ xảy ra nếu bạn thay đổi giá trị của x trong hàm?
x = 2defvidu():
x=x*2
print(x)
vidu()
Nếu chạy code này bạn sẽ nhận được thông báo lỗi:
UnboundLocalError: local variable 'x' referenced before assignment
Lỗi này xuất hiện là do Python xử lý x như một biến cục bộ và x không được định nghĩa trong vidu().
Để thay đổi biến toàn cục trong một hàm bạn sẽ phải sử dụng từ khóa global. Chúng tôi sẽ nói kỹ hơn trong bài về từ khóa global.
Ở đây, chúng ta sẽ học cách dùng biến cục bộ và toàn cục trong cùng một code.
x = 2defvidu():global x
y = "Biến cục bộ"
x = x * 2
print(x)
print(y)
#Viết bởi uCode.vn
vidu()
Chạy code trên ta sẽ có đầu ra:
4
Biến cục bộ
Trong code trên, chúng ta khai báo x là biến toàn cục và y là biến cục bộ trong vidu() và dùng toán tử * để thay đổi biến toàn cục và in cả giá trị của x và y. Sau khi gọi hàm vidu() giá trị của x sẽ thành 4 vì được nhân đôi.
Ví dụ sử dụng biến toàn cục và cục bộ trùng tên:
x = 5defvidu():
x = 10
print("Biến x cục bộ:", x)
vidu()
print("Biến x toàn cục:", x)
Sau khi chạy code trên ta có đầu ra:
Biến x cục bộ: 10
Biến x toàn cục: 5
Trong code trên, chúng ta sử dụng cùng tên x cho cả biến cục bộ và biến toàn cục. Khi in cùng biến x chúng ta nhận được hai kết quả khác nhau vì biến được khai báo ở cả hai phạm vi, cục bộ (bên trong hàm vidu()) và toàn cục (bên ngoài hàm vidu()).
Khi chúng ta in biến trong hàm vidu() nó sẽ xuất ra Biến x cục bộ: 10, đây được gọi là phạm vi cục bộ của biến. Tương tự khi ta in biến bên ngoài hàm vidu() sẽ cho ra Biến x toàn cục: 5, đây là phạm vi toàn cục của biến.
Từ nonlocal này mình không biết dịch sang tiếng Việt sao cho chuẩn. Trong Python, biến nonlocal được sử dụng trong hàm lồng nhau nơi mà phạm vi cục bộ không được định nghĩa. Nói dễ hiểu thì biến nonlocal không phải biến local, không phải biến global, bạn khai báo một biến là nonlocal khi muốn sử dụng nó ở phạm vi rộng hơn local, nhưng chưa đến mức global.
Để khai báo biến nonlocal ta cần dùng đến từ khóa nonlocal.
Ví dụ:
defham_ngoai():
x = "Biến cục bộ"defham_trong():nonlocal x
x = "Biến nonlocal"
print("Bên trong:", x)
ham_trong()
print("Bên ngoài:", x)
hamngoai()
Chạy code trên bạn sẽ có đầu ra:
Bên trong: Biến nonlocal
Bên ngoài: Biến nonlocal
Trong code trên có một hàm lồng là ham_trong(), ta dùng từ khóa nonlocal để tạo biến nonlocal. Hàm ham_trong() được định nghĩa trong phạm vi của ham_ngoai().
Lưu ý: Nếu chúng ta thay đổi giá trị của biến nonlocal, sự thay đổi sẽ xuất hiện trong biến cục bộ.
Bài viết được sự cho phép của tác giả Nguyễn Hữu Đồng
Trước khi nói tới heap, hãy nhớ lại kiến thức cây nhị phân
Cây nhị phân là một cây, mỗi nút trên cây có tối đa hai nhánh con, nút thứ i sẽ có 2 con là 2i và 2i+1.
Cây nhị phân
Heap là một câu trúc cây nhị phân đầy đủ, mỗi nút trên cây đều chứa một nhãn có độ ưu tiên cao hơn các con của nó, nút gốc (root) là nút có độ ưu tiên cao nhất. Ví dụ heap min là cây là mọi con của nút i đều có giá trị >= heap[i], heap max thì mọi nút con đều nhỏ hơn nút cha.
Heap nhị phân được ứng dụng rộng rãi dùng để cài đặt một hàng đợi ưu tiên, hay là trong thuật toán Distra tìm đường đi ngắn nhất, và trong bài này ta sẽ sử dụng Binary Heap để sắp xếp mảng.
Các thao tác thường dùng trên Heap là
Tìm nút có độ ưu tiên cao nhất
Thêm một nút vào heap
Xóa bỏ nút gốc, nút có độ ưu tiên cao nhất.
Xây dựng heap từ tập có n phần tử
Để tìm nút có độ ưu tiên cao nhất, ta chỉ cần lấy nút gốc.
Để thêm một nút vào heap
Nếu heap rỗng thì ta chỉ cần thay gốc bằng nút đó
Nếu heap không rỗng
Chọn vị trí để thêm nút.
Giả sử heap có độ cao là h, và mọi nút ở độ cao h-1 có một nút nào đó chưa đủ 2 con (tổng số nút ở độ cao đó < 2^h) — thì gắn nút vào phía bên phải của nút ngoài cùng
Nếu ở độ cao h đã đầy đủ nút thì thêm nút vào độ cao h+1
Tiến hành vun đống nút dưới lên, nếu nút cha có độ ưu tiên thấp hơn nút con thì tiến hành đổi chỗ nút con và nút cha, sau đó lại xét tiếp nút cha đó cho tới khi nào thỏa mãn nút cha có độ ưu tiên hơn nút chon hoặc nút đang xét là nút cha thì thôi.
Để xóa nút gốc
Nếu cây đó chỉ có 1 nút thì chỉ xóa nút đó
Nếu cây có nhiều hơn 1 nút là tiến hành lấy nút dưới dùng bên phải thế vào nút gốc sau đó tiến hành quá trình down heap.
Quá trình down heap, so sánh độ ưu tiên với cả hai nút con(nếu có) , nếu độ ưu tiên thấp hơn 1 trong 2 nút con hoặc thấp hơn cả hai nút con thì tiến hành chọn nút có độ ưu tiên cao nhất trong 2 nút con và đổi vị trị với nó, cho tới khi nào nút đang xét là nút lá ( không có nút con)
Độ phức tạp.
Thao tác lấy nút gốc O(1)
Thao tác thêm nút mới vào cây O(log N)
Thao tác xóa nút gốc : Tổng O(log N)
Thao tác xóa nút gốc O(1)
Thao tác down heap O(log N)
Áp dụng tính chất của Heap để sắp xếp
Trong phần này mình sẽ triển khai thuật toán sắp xếp nhanh bằng cách sử dụng tính chất, nút gốc là nút có độ ưu tiên cao nhất, mình sẽ xây dựng một heap từ mảng a có n phần tử sau đó, lấy lần lượt các phần tử trong heap ra thì mình có có các giá trị lấy ra có độ ưu tiên giảm dần.
Ngôn ngữ thực hiện. : Go
Cấu trúc Heap : struct Heap có field Leng, là độ dài của Heap và field Value là giá trị của các phần tử trong heap.
type Heap struct {
Leng int
Value []int
}
Thao tác khởi tạo Heap : Mình sẽ xây dựng heap Min thiết lập Leng = 0, heap rỗng
Thao tác thêm một phần tử vào Heap : Thêm vào và sau đó vun đống từ dưới lên qua function UpHeap.
func (h *Heap) Insert(x int) *Heap {
h.Leng++
h.Value[h.Leng] = x
h.UpHeap(h.Leng)
return h
}
Thao tác UpHeap
func (h *Heap) UpHeap(i int) *Heap {
if (i == 1) || (h.Value[int(math.Floor(float64(i)/2))] <= h.Value[i]) {
return h
}
t := h.Value[i]
h.Value[i] = h.Value[int(math.Floor(float64(i)/2))]
h.Value[int(math.Floor(float64(i)/2))] = t
h.UpHeap(int(math.Floor(float64(i) / 2)))
return h
}
Thao tác xóa nút gốc : Sau khi loại bỏ nút gốc, ta lấy phần tử ngoài cùng ở độ cao cao nhất thế vào nút gốc sau đó thực hiện quá trình DownHeap từ nút gốc
func (h *Heap) RemoveRoot() *Heap {
h.Value[1] = h.Value[h.Leng]
h.Leng--
if h.Leng > 1 {
h.DownHeap(1)
}
return h
}
Thao tác DownHeap
func (h *Heap) DownHeap(i int) *Heap {
m := i * 2
if m > h.Leng {
return h
}
if h.Value[m] > h.Value[m+1] {
m++
}
if h.Value[m] < h.Value[i] {
t := h.Value[m]
h.Value[m] = h.Value[i]
h.Value[i] = t
h.DownHeap(m)
return h
}
return h
}
Hàm Main : Tạo mảng a sau đó đẩy mỗi phần tử của a vào Heap, sau đó lấy trong heap ra dần dần ta có kết quả theo độ ưu tiên giảm giần
func main() {
a := []int{8, 6, 4, 5, 7, 9, 2, 3, 2, 2, 6, 3, 6, 3, 6, 123, 6541, 3, 6, 3, 461, 35, 2}
for _, v := range a {
h.Insert(v)
}
for i := 1; i <= len(a); i++ {
fmt.Print(h.Value[1],)
h.RemoveRoot()
}
}
package main
import (
"fmt"
"math"
)
type Heap struct {
Leng int
Value []int
}
var h Heap
const nMax = 10000
const maxValue = 100000000
func init() {
h.Leng = 0
h.Value = make([]int, nMax+1, nMax+1)
}
func (h *Heap) Insert(x int) *Heap {
h.Leng++
h.Value[h.Leng] = x
h.UpHeap(h.Leng)
return h
}
func (h *Heap) UpHeap(i int) *Heap {
if (i == 1) || (h.Value[int(math.Floor(float64(i)/2))] <= h.Value[i]) {
return h
}
t := h.Value[i]
h.Value[i] = h.Value[int(math.Floor(float64(i)/2))]
h.Value[int(math.Floor(float64(i)/2))] = t
h.UpHeap(int(math.Floor(float64(i) / 2)))
return h
}
func (h *Heap) DownHeap(i int) *Heap {
m := i * 2
if m > h.Leng {
return h
}
if h.Value[m] > h.Value[m+1] {
m++
}
if h.Value[m] < h.Value[i] {
t := h.Value[m]
h.Value[m] = h.Value[i]
h.Value[i] = t
h.DownHeap(m)
return h
}
return h
}
func (h *Heap) RemoveRoot() *Heap {
h.Value[1] = h.Value[h.Leng]
h.Leng--
if h.Leng > 1 {
h.DownHeap(1)
}
return h
}
func main() {
a := []int{8, 6, 4, 5, 7, 9, 2, 3, 2, 2, 6, 3, 6, 3, 6, 123, 6541, 3, 6, 3, 461, 35, 2}
for _, v := range a {
h.Insert(v)
}
for i := 1; i <= len(a); i++ {
fmt.Print(h.Value[1], " ")
h.RemoveRoot()
}
}
Thuật toán Heap Sort ứng dụng tính chất khá đơn giản của Heap nhưng nếu là mình khi sort thì mình khi sort mình sẽ sử dụng QuickSort để hạn chế việc mất đi một mớ mem cho cái heap.
Mình sẽ thực hiện tiếp Quick Sort trong phần sau của bài, cảm ơn các bạn đã đọc 😀
Bài viết được sự cho phép của BBT Kinh nghiệm lập trình
Chào các bạn, hôm nay mình xin chia sẻ 1 mẹo nhỏ trong PHP: Destroy session_id của user khác từ server.
Tại sao cần làm việc này?
Bài toán đặt ra: Tại một thời điểm, chỉ cho phép người dùng có duy nhất 1 phiên đăng nhập trên hệ thống.
Mô tả chi tiết: Thực tế hiện tay rất nhiều hệ thống web ứng dụng bán license theo số lượng tài khoản sử dụng. Do vậy, nếu không có biện pháp ngăn chặn việc người dùng sử dụng chung tài khoản để làm việc trên hệ thống thì việc thất thoát doanh thu là khó tránh khỏi. Chính vì thế, hệ thống cần có phương án để ngăn chặn việc này, tại một thời điểm, chỉ cho phép người dùng có 1 phiên đăng nhập trên 1 thiết bị và thao tác trên hệ thống.
Cách thực hiện trên với PHP?
Mình sẽ nói tóm tắt các bước thực hiện, logic này có thể áp dụng tương tự với ngôn ngữ khác. Mục đích là sử dụng tài khoản hiện tại, để destroy session id khác trên server.
Bước 1:Commit session ID nếu nó đã tồn tại
Bước 2: Store current session id
Bước 3: Destroy session specified
Bước 4: Restore current session id
Ứng tuyển ngay các vị trí PHP tuyển dụng mới nhất trên TopDev
Code demo!
<?php
$session_id_to_destroy = ‘nill2if998vhplq9f3pj08vjb1’; // 1. commit session if it’s started. if (session_id()) { session_commit();
}
// 2. store current session id session_start(); $current_session_id = session_id(); session_commit();
// 4. restore current session id. If don’t restore it, your current session will refer to the session you just destroyed! session_id($current_session_id); session_start(); session_commit();
?>
Xong, một mẹo khá nhỏ nhưng đôi khi lại rất cần thiết cho hệ thống của bạn. Chúc các bạn áp dụng thành công!
Bài viết được sự cho phép của tác giả Phạm Văn Nguyên
Trong Python hoặc bất kỳ ngôn ngữ lập trình nào khác, đôi khi bạn muốn thêm thời gian trễ trong chương trình của mình trước khi bạn tiếp tục đến phần tiếp theo của mã.
Nếu đây là những gì bạn muốn làm, thì bạn nên sử dụng chức năng sleep từ module time .
Đầu tiên, tôi sẽ bắt đầu bằng cách thảo luận về cách sử dụng chức năng sleep của Python . Sau đó tôi sẽ nói nhiều hơn về một số câu hỏi thường gặp và cách thức thực hiện của function(hàm) sleep.
Hàm sleep
Giống như tôi đã đề cập, Sleep là một hàm tích hợp Python trong mô-đun time .
Vì vậy, để sử hàm sleep , bạn sẽ cần import module time trước.
Hàm sleep có một đối số là khoảng thời gian tính bằng giây mà bạn dừng.
Sử dụng hàn sleep trong Python 2 và Python 3 hoàn toàn giống nhau, vì vậy bạn sẽ không cần phải lo lắng về phiên bản Python nào mà mã của bạn đang chạy.
Nó thực sự rất đơn giản và dễ hiểu. Chúng ta hãy đi qua một số ví dụ.
Nhưng dù bằng cách nào, bạn có thể nghĩ về Cuộc gọi hệ thống như một API hoặc giao diện mà HĐH cung cấp cho các chương trình không gian người dùng để tương tác với HĐH.
Vậy HĐH sẽ làm gì khi nhận được System call sleep?
Về cơ bản, hệ điều hành sẽ làm là nó sẽ tạm dừng quá trình (chương trình của bạn) được lên lịch trên CPU trong khoảng thời gian mà bạn đã chỉ định.
Lưu ý rằng điều này hoàn toàn khác với việc thêm độ trễ bằng cách sử dụng vòng lặp giả không làm gì cả (đó là điều bạn KHÔNG BAO GIỜ nên làm).
Trong trường hợp giả cho vòng lặp, quy trình của bạn thực sự vẫn đang chạy trên CPU.
Nhưng trong trường hợp Sleep () , quy trình của bạn sẽ không hoạt động trong một thời gian cho đến khi HĐH bắt đầu lập lịch lại trên CPU.
Bài viết được sự cho phép của tác giả Trần Hữu Cương
Constructor Declarations
Trong Java, các đối tượng được khởi tạo thông qua hàm khởi tạo (constructor). Mỗi lần bạn tạo mới một đối tượng sẽ có ít nhất một hàm khởi tạo được thực thi.
Mỗi class đều có một hàm khởi tạo, nếu bạn không khai báo thì complier sẽ tự động khai báo một hàm khởi tạo không tham số cho bạn.
Có rất nhiều nguyên tắc khác nhau liên quan đến hàm khởi tạo (sẽ tìm hiểu ở bài sau). Ở bài này chúng ta sẽ chỉ tập trung vào các nguyên tắc khai báo cơ bản.
Hàm khởi tạo cũng được coi như một hàm với kiểu dữ liệu trả về của hàm khởi tạo chính là một thể hiện của class chứa nó. Hàm khởi tạo có thể đi kèm với các access modifier
voidPerson(){} // đây là một method, không khải constructorstaticPerson(){} // không thể đi kèm với từ khóa staticfinalPerson(){} // không thể đi kèm với từ khóa finalabstractPerson(){} // không thể đi kèm với từ khóa abstract
Một class có thể có nhiều hàm khởi tạo (tương tự như một class có thể có nhiều hàm cùng tên – tính đa hình) nhưng tham số đầu vào của các hàm khởi tạo phải khác nhau.
Khi bạn gọi lệnh new thì đầu tiên nó sẽ chạy vào hàm khởi tạo của đối tượng rồi sau đấy mới chạy các khối static, method…
Bài viết được sự cho phép của tác giả Kien Dang Chung
Trong những website lớn, các phần tìm kiếm hay nhập liệu rất cần những tính năng thông minh như gợi ý dựa trên các từ nhập vào, nó giúp cho nâng cao trải nghiệm người dùng, giúp tìm kiếm và nhập liệu trở lên đơn giản hơn. Google là một minh chứng không cần phải bàn cãi cho vấn đề này. Những năm đầu của thế kỉ 21, Google xuất hiện với chỉ duy nhất một ô tìm kiếm và tính năng gợi ý ngay lập tức khi từ khóa được đánh vào, nó đã giúp người dùng định hướng được ngay khi chỉ gõ vào 1-2 từ trong từ khóa.
Ở thời điểm đó, gợi ý khi tìm kiếm là một tính năng phức tạp và xa xỉ, nhưng khi công nghệ phần mềm phát triển, đặc biệt với phần mềm mã nguồn mở, những tính năng như vậy thật đơn giản để thực hiện. Bài viết này sẽ hướng dẫn bạn thực hiện các tìm kiếm thông minh hay gợi ý nhập liệu sử dụng thư viện Typeahead là một gói phần mềm mã nguồn mở của Twitter trong các ứng dụng Laravel.
Trong bài viết này chúng ta sẽ thực hiện một ví dụ tìm kiếm thông minh thông tin khách hàng bằng cách tạo ra dữ liệu mẫu khoảng 10,000 khách hàng. Như bạn thấy khi tìm kiếm hệ thống sẽ gợi ý các bản ghi trong database rất nhanh giúp người dùng có thể lựa chọn luôn chính xác người dùng. Typeahead sử dụng kết hợp Bloodhound cho tốc độ rất nhanh mặc dù dữ liệu là 10k bản ghi.
1. Giới thiệu về Typeahead
Typeahead.js là một thư viện Javascript rất linh hoạt, nó có thể làm nền tảng tốt để xây dựng các tính năng tìm kiếm gợi ý thông minh. Typeahead bao gồm hai thành phần: Typeahead: Phần chuyên xử lý giao diện người dùng
Hiển thị gợi ý đến người dùng ngay khi họ nhập liệu
Hiển thị các gợi ý ngay trên ô nhập liệu
Hỗ trợ các tùy chỉnh giao diện linh hoạt
Highlight các từ khóa trùng khớp trong phần gợi ý
Kích hoạt các sự kiện tùy chỉnh cho phép mở rộng các xử lý
Bloodhound: Bộ máy gợi ý nâng cao
Cho phép các dữ liệu được hardcode
Lấy dữ liệu từ trước để giảm độ trễ khi gợi ý
Sử dụng Local Storage giảm số lượng các request đến máy chủ.
Sử dụng rate limit và bộ đệm cho các request đến máy chủ làm giảm nhẹ tải dữ liệu
Bộ máy gợi ý Bloodhound sẽ được sử dụng để tính toán kết quả với các truy vấn cho trước và Typeahead sẽ sử dụng để render ra mã HTML. Cả hai thành phần này là độc lập, trong bài viết này chúng ta sẽ sử dụng cả hai để xây dựng công cụ tìm kiếm gợi ý thông minh.
1.1 Cài đặt Typeahead
Trước khi đi vào sử dụng Typeahead chúng ta cần cài đặt gói thư viện này, có ba cách thức để cài đặt.
Sử dụng npm
npm i typeahead
Tải gói thư viện dạng file zip
Vào đường dẫn Github của Typeahead chọn Clone or download và click vào Download zip. Giải nén ra chúng ta sẽ thấy trong thư mục dist có những file như sau:
bloodhound.js (chỉ có thành phần Bloodhound).
typeahead.bundle.js (Bao gồm cả Typeahead và Bloodhound).
typeahead.jquery.js (chỉ có thành phần Typeahead).
Các file có thêm min.js là các file được tối ưu hóa dung lượng.
Chú ý: Typeahead yêu cầu jquery phiên bản từ 1.9 trở lên. Trong bài viết này chúng ta sẽ sử dụng cách thứ ba cho nhanh, với dự án lớn nên sử dụng cách 1 để tối ưu hóa các tài nguyên với Laravel Mix.
1.2 Khởi tạo Typeahead
Typeahead có nhiều cách khởi tạo và sau đây là cách khởi tạo hay dùng nhất jQuery#typeahead(options, [*datasets]). Tính năng typeahead được áp dụng cho các input dạng text input[type=”text”] có hai tham số cho khởi tạo: options là các tùy chọn cấu hình, một số giá trị cần quan tâm như sau:
highlight: thêm thẻ <strong> vào các từ trùng khớp trong phần gợi ý. Mặc định là false.
hint: hiển thị cả từ gợi ý trong ô nhập liệu, mặc định true.
minLength: Số ký tự tối thiểu cần nhập khi tính năng gợi ý được bắt đầu, mặc định là 1.
classNames: Cho phép sử dụng tên class khác với mặc định.
dataset: một typeahead có thể có nhiều dataset, ví dụ khi bạn tìm kiếm trong một trang bán hàng có thể trả về gợi ý cho cả sản phẩm và các tin tức liên quan đến sản phẩm. Các dataset có một số các tùy chọn cấu hình như sau:
name: tên của dataset.
source: nguồn dữ liệu dùng cho gợi ý, có thể là một instance của Bloodhound, như ở phần đầu chúng ta có nói Typeahead chỉ xử lý giao diện người dùng và Bloodhound với là bộ máy thực hiện các gợi ý.
limit: Số gợi ý tối đa sẽ được hiển thị, mặc định là 5.
Bloodhound là bộ máy gợi ý cho Typeahead.js, sử dụng thành phần này mang lại nhiều tính năng nâng cao hơn vì nó có thể lấy dữ liệu từ một nguồn remote và sử dụng bộ đệm để tăng tốc.
Chúng ta sẽ thiết lập đường dẫn /find?q= trong phần Laravel, datumTokenizer cần một mảng JSON. Như vậy, chúng ta đã có dữ liệu và có thể sử dụng nó cho thiết lập source của typeahead như sau:
source: engine.ttAdapter()
1.4 Tạo mẫu cho các gợi ý
Typeahead cho phép sử dụng các template để thay đổi kiểu mẫu cho các gợi ý, bạn cũng có thể sử dụng bootstrap để style:
templates:{
empty:['<div class="list-group search-results-dropdown"><div class="list-group-item">Không có kết quả phù hợp.</div></div>'],
header:['<div class="list-group search-results-dropdown">'],
suggestion:function(data){return'<a href="'+ data.id +'" class="list-group-item">'+ data.name +'</a>'}}
Tham khảo công cụ Laragon cài đặt nhanh môi trường Laravel, chúng ta tạo ra một môi trường test có tên là typeahead và Laragon tự động tạo ra tên miền ảo typeahead.dev. Laragon cũng tự động tạo ra database tên typeahead cho chúng ta. Việc đầu tiên là thiết lập file .env:
Có nhiều các bảng khác được tạo ra do các bảng này được sử dụng cho xác thực người dùng (xem Laravel Authentication xác thực người dùng thật đơn giản). Tiếp theo chúng ta sẽ sử dụng Laravel Seeding để tạo ra 10000 dữ liệu customer mẫu trong database.
D:\Laragon\www\typeahead
λ php artisan make:seeder CustomersTableSeeder
Seeder created successfully.
Tạo file CustomerFactory.php trong thư mục database\factories:
Chúng ta đã tạo ra bảng customer với 10 nghìn dữ liệu khách hàng mẫu được đưa vào, tiếp theo chúng ta cần tạo ra một đường dẫn để thực hiện tìm kiếm khách hàng và trả về dữ liệu dạng JSON cho truy vấn Bloodhound, thêm route sau đây vào file routes\api.php:
Route::get('find','SearchController@find');
Tạo thêm một route trong routes\web.php để hiển thị thông tin chi tiết của khách hàng, ở đây chỉ thực hiện in ra màn hình thông tin mà không có tạo view, coi như bài tập thêm cho các bạn :).
Vào đường dẫn http://typeahead.dev/customer?q=jo chúng ta sẽ có kết quả là dữ liệu dạng JSON:
2.5 View
Tiếp đến chúng ta thay đổi welcome view nằm trong thư mục resources\views:
<!DOCTYPE html><html lang="vi"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible"content="IE=edge"><meta name="viewport"content="width=device-width, initial-scale=1"><meta name="description"content="Tìm kiếm thông minh sử dụng Typeahead trong ứng dụng Laravel"><meta name="author"content="FirebirD ['www.allaravel.com']"><title>Tìm kiếm thông minh trong Laravel sử dụng Typeahead - Allaravel.com</title><link rel="stylesheet"href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"><!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --><!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]--><style type="text/css">html{position:relative;min-height:100%;}body{margin-bottom:60px;}.footer{position:absolute;bottom:0;width:100%;height:60px;background-color:#f5f5f5;}body>.container{padding:60px 15px 0;}.container.text-muted{margin: 20px 0;}.footer>.container{padding-right:15px;padding-left:15px;}code{font-size:80%;}</style></head><body><!-- Fixed navbar --><nav class="navbar navbar-default navbar-fixed-top"><div class="container"><div class="navbar-header"><button type="button"class="navbar-toggle collapsed"data-toggle="collapse"data-target="#navbar"aria-expanded="false"aria-controls="navbar"><span class="sr-only">Toggle navigation</span><span class="icon-bar"></span><span class="icon-bar"></span><span class="icon-bar"></span></button><a class="navbar-brand"href="https://allaravel.com">Typeahead</a></div><div id="navbar"class="collapse navbar-collapse"><ul class="nav navbar-nav"><li class="active"><a href="#">Home</a></li><li><a href="#about">About</a></li><li><a href="#contact">Contact</a></li></ul></div><!--/.nav-collapse --></div></nav><!-- Begin page content --><div class="container"><div class="page-header"><h3>Ví dụ tìm kiếm thông minh sử dụng typeahead.js trong ứng dụng Laravel - Allaravel.com</h3></div><div class="row"><div class="col-md-12"><form class="form-inline typeahead"><div class="form-group"><input type="name"class="form-control search-input"id="name"autocomplete="off"placeholder="Nhập tên khách hàng"></div><button type="submit"class="btn btn-default">Tìm kiếm</button></form></div></div></div><footer class="footer"><div class="container"><p class="text-muted">Example in <a href="https://allaravel.com">allaravel.com</a></p></div></footer><script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script><script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/typeahead.js/0.11.1/typeahead.bundle.min.js"></script><script>jQuery(document).ready(function($){var engine =newBloodhound({
remote:{
url:'api/customer?q=%QUERY%',
wildcard:'%QUERY%'},
datumTokenizer: Bloodhound.tokenizers.whitespace('q'),
queryTokenizer: Bloodhound.tokenizers.whitespace
});$(".search-input").typeahead({
hint:true,
highlight:true,
minLength:1},{
source: engine.ttAdapter(),
name:'usersList',
templates:{
empty:['<div class="list-group search-results-dropdown"><div class="list-group-item">Không có kết quả phù hợp.</div></div>'],
header:['<div class="list-group search-results-dropdown">'],
suggestion:function(data){return'<a href="customer/'+ data.id +'" class="list-group-item">'+ data.name +'</a>'}}});});</script></body></html>
Kết quả khi vào http://typeahead.dev và thực hiện tìm kiếm khách hàng chúng ta được như sau:
3. Lời kết
Với việc sử dụng Typeahead trong ứng dụng Laravel, trải nghiệm người dùng được nâng cao hơn. Typeahead không chỉ sử dụng trong các phần tìm kiếm mà chúng ta có thể sử dụng trong các form nhập liệu giúp gợi ý thông tin nhập liệu. Hi vọng bài viết sẽ giúp ích cho các bạn trong các dự án riêng sử dụng Laravel, có bất kỳ thắc mắc hoặc góp ý các bạn comment cuối bài nhé.
Hãy bắt đầu bằng cách so sánh cả hai. Đây là một ví dụ về “switch” cổ điển:
switch($statusCode){case200:case300:$message=null;break;case400:$message='not found';break;case500:$message='server error';break;default:$message='unknown status code';break;}
Đoạn code ở dưới đây sẽ tương đương với ở trên khi dùng biểu thức “match“:
$message= match ($statusCode){200,300=>null,400=>'not found',500=>'server error',default=>'unknown status code',};
Trước hết, biểu thức khớp ngắn hơn đáng kể:
nó không yêu cầu break statement
nó có thể kết hợp các trường hợp giống nhau thành một bằng dấu phẩy
nó trả về một giá trị, vì vậy bạn chỉ phải gán giá trị một lần
Nhưng thậm chí còn nhiều hơn thế!
Không ép kiểu
match sẽ kiểm tra loại nghiêm ngặt thay vì kiểm tra lỏng lẻo. Giống như sử dụng === thay vì ==. Mọi người có thể sẽ không đồng ý liệu đó có phải là điều tốt hay không, nhưng đó là một chủ đề riêng chúng ta sẽ bàn sau.
$statusCode='200';$message= match ($statusCode){200=>null,default=>'unknown status code',};// Kết quả trả về// $message = 'unknown status code'
Giá trị không xác định gây ra lỗi
Nếu bạn quên kiểm tra giá trị và khi không có nhánh default được chỉ định, PHP sẽ đưa ra ngoại lệ UnhandledMatchError. Tuy kiểm tra chặt chẽ, nhưng nó sẽ ngăn chặn các lỗi nhỏ nhặt không được chú ý.
$statusCode=400;$message= match ($statusCode){200=>'perfect',};// UnhandledMatchError sẽ throw
Hiện tại chỉ có các biểu thức một dòng
Bạn chỉ có thể viết một biểu thức trên một dùng. Các khối biểu thức có thể sẽ được thêm vào tại một thời điểm nào đó, nhưng vẫn chưa rõ chính xác khi nào .
Ứng tuyển ngay các vị trí PHP tuyển dụng mới nhất trên TopDev
Kết hợp điều kiện
match có thể kết hợp nhiều điều kiện lại với nhau và viết trên 1 dòng ngăn cách bởi dấu phẩy
$message= match ($statusCode){200,300,301,302=>'combined expressions',};
Throwing exceptions
Trong PHP 8 có thay đổi về cách dùng throw trước đây throw chỉ bắt đầu từ câu lệnh thì giờ có thể bắt đầu từ biểu thức, nghĩa là bạn có thể throw exception ở giá trị 500 mà k phải cần đưa vào trong hàm
$message= match ($statusCode){200=>null,500=>thrownewServerError(),default=>'unknown status code',};
Pattern matching
Ok, có một điều nữa: Pattern matching. Đây là một kỹ thuật được sử dụng trong các ngôn ngữ lập trình khác, để cho phép kết hợp phức tạp hơn các giá trị đơn giản. Hãy nghĩ về nó như regex, nhưng đối với các biến thay vì văn bản.
Pattern matching không được hỗ trợ ngay bây giờ, vì đây là một tính năng khá phức tạp, nhưng Ilija Tovilo (tác giả RFC) đã đề cập đến nó như là một tính năng có thể có trong tương lai.
Vậy thì, switch hay là match?
match với phiên bản chặt chẽ và hiện đại hơn so với người anh switch
Trong một số trường hợp switch sẽ cung cấp linh hoạt hơn so với match, đặc biệt là với các khối code có nhiều dòng. Tuy nhiên, sự nghiêm ngặt của toán tử match là hấp dẫn và pattern matching sẽ là một yếu tố thay đổi cuộc chơi cho PHP.
Tôi thừa nhận tôi chưa bao giờ viết một switch statement trong những năm qua vì nhiều điều kỳ quặc của nó. Vì vậy, trong khi nó chưa hoàn hảo, nhưng có những trường hợp sử dụng được thì match sẽ là một sự lựa chọn tốt đối với tôi.
Cover Letter chuẩn Dev sẽ như thế nào? Nhiều ứng viên ngành IT cảm thấy khó khăn trong việc tìm kiếm một Cover Letter cho Dev với một format chuyên nghiệp. Vậy Cover Letter cho Developer là gì và đâu là những điểm cần lưu ý? Cùng TopDev điểm qua bài viết sau để giải đáp các thắc mắc đấy
Thế nào là Cover Letter cho Dev?
Cover Letter (hay còn gọi là Thư xin việc), là một loại văn bản được xây dựng dưới hình thức một trang. Cover Letter cho Dev truyền tải các thông tin về cá nhân, năng lực, trình độ, mục tiêu phát triển ngành lập trình đến với nhà tuyển dụng.
thư xin việc – Cover Letter được hiểu như thế nào?
Chất lượng của một Cover Letter cho Developer sẽ quyết định 30-50% kết quả ứng tuyển. Điều đó cho thấy, cần đầu tư cho chiếc Cover Letter của mình sao cho chỉn chủ nhất. Đừng xem thường vai trò của Cover Letter cho Dev. Vì biết đâu bạn sẽ bị “out” vì sự hời hợt đối với nó đấy!
Cover Letter không chỉ phản ánh bạn là ứng viên Developer phù hợp mà còn cho trong nhiều trường hợp, nó còn thể hiện bạn là người không phù hợp với vị trí đó.
Tips nâng cấp Cover Letter cho Developer
Hãy lưu tâm các tips sau để Cover Letter của bạn không trở nên quá tệ hại trong mắt nhà tuyển dụng.
Tips viết CV giúp ứng viên có thêm bí quyết hoàn thiện Cover Letter Developer
Cover Letter bám sát tính trọng tâm về nội dung
Ứng viên IT nên chú trọng đến việc chia sẻ về các thông tin quan trọng. Tránh liệt kê những nội dung thừa thải, không liên quan. Điều đó chứng tỏ bạn là người thiếu sự nhìn nhận, sàng lọc và phân tích thông tin.
Đồng thời, viết CV chuẩn đẹp giúp bạn định hình và có những trải nghiệm tốt hơn. Trường hợp bạn ứng tuyển các vị trí khác như freelancer it hay Senior Developer đều sẽ đạt hiệu quả ứng tuyển cao hơn.
Nội dung cần được triển khai theo format chuẩn và đúng định hướng. Không dài dòng, dàn trải với nhiều thông tin lan man, thiếu tính kết nối. Sự rời rạc về nội dung khiến giá trị của Cover Letter cho Developer bị giảm đi.
Trọng tâm còn nằm ở chỗ vấn đề ứng viên chia sẻ các kỹ năng cần thiết cho vị trí ứng tuyển. Đừng nói quá và phô trương quá nhiều kỹ năng. Bạn cần nhớ là nhà tuyển dụng cần chất lượng hơn số lượng.
Số liệu thực minh chứng cho các trải nghiệm trong Cover Letter
Những minh chứng rất quan trọng. Nhà tuyển dụng sẽ có ấn tượng hơn với nhiều ứng viên trình bày rõ ràng các minh chứng về các trải nghiệm của mình.
Đó có thể là:
+ Số liệu cho thấy tốc độ tăng trưởng dưới sự phồi hợp giải quyết các công việc giữa bạn và team trong một dự án.
+Lời đánh giá chuyên môn.
+ Các thành tích, giải thưởng từ quá trình phân tích, nghiên cứu chuyên sâu.
Kiểm tra lại nội dung Cover Letter cho Dev
Khó có thể nói có một công thức chung nào để được gọi là sự ấn tượng. Thế nhưng, việc rà soát lại các lỗi trong Cover Letter sẽ giúp cho thư xin việc IT được nâng cấp một cách toàn diện hơn.
Không nên dùng những từ ngữ quá phức tạp, có sắc thái biểu đạt quá cao. Điều này làm giảm đi tính hiệu quả của Cover Letter cho Developer . Thay vào đó hãy ưu tiên dùng các từ ngữ đơn giản, dễ hiểu. Ứng viên cần lượt bỏ đi những thông tin không quan trọng. Vì đôi khi, quá nhiều nội dung sẽ khiến nhà tuyển dụng khó nắm bắt được những điều bạn muốn truyền tải trong Cover Letter cho Dev.
Gợi ý các mẫu template Cover letter/mẫu thư xin việc cho Dev
Vietnam Web Summit 2020 (VWS2020) với hàng loạt chủ đề hấp dẫn về tính “quyền năng” của dữ liệu đặc biệt trong thời đại Digital Transformation! Sự phát triển mạnh mẽ và ứng dụng công nghệ đa dạng vào trong đời sống tạo ra một lượng lớn dữ liệu khách hàng, tạo nên những câu chuyện và rồi lại tác động trở lại đến quyết định của nhiều tổ chức, doanh nghiệp. Tuy nhiên trong thực tế, liệu những câu chuyện được kể bởi dữ liệu có phản ánh toàn bộ thực tế? Dữ liệu có phải là một ‘đấng toàn năng’ và chi phối mọi quyết định của doanh nghiệp?
Hãy để các diễn giả của Vietnam Web Summit 2020 giúp bạn ‘tỏ tường’ hơn về quyền năng của dữ liệu và lý do dữ liệu không phải là toàn năng. Ai sẽ thực hiện điều này?
◸Mr. Nguyễn Tấn Triều
CEO @USPA Technology Company◿
Câu trả lời mà bạn đang tìm kiếm có thể sẽ được tìm thấy dưới góc nhìn của “dataism”. Thông qua topic “Thinking with Dataism and LEO Customer Data Platform”, anh Nguyễn Tấn Triều sẽ cho bạn một góc nhìn tổng quan về ‘tôn giáo’ này cùng phương pháp LEO CDP.
◸Ms. Đặng Huỳnh Mai Anh
Data Science Manager @Amanotes◿
Tự thân dữ liệu không thể tạo ra câu chuyện nếu không có bàn tay của con người. Trong topic “Data Quality Control System – what it is and how it is employed in a music-tech company”, chị Đặng Huỳnh Mai Anh sẽ “bật mí” cách tận dụng triệt để loại ‘quyền năng’ này với một Data Quality Control System – một ‘cỗ máy’ quản trị chất lượng dữ liệu của một công ty đã chạm đến con số 1 tỷ lượt tải ứng dụng và với 95 triệu MAU.
◸Mr. Nghiêm Xuân Bách
Vietnam Country Manager @Cinnamon AI◿
“Data never sleep” – dữ liệu luôn được sản sinh liên tục, vì lẽ đó một Data Harvest Loop sẽ là giải pháp cho các doanh nghiệp. Để hiểu rõ hơn Data Harvest Loop là gì, doanh nghiệp sẽ ứng dụng như thế nào trong quá trình chuyển đổi, hãy để anh Nghiêm Xuân Bách tiết lộ cho bạn.
◸Ms. Kelly Tran
Client Partner – Monetization @Unity Technologies◿
Ở giai đoạn cuối cùng của quá trình chuyển hóa dữ liệu, chị Kelly Tran sẽ giải đáp lý do vì sao đôi lúc dữ liệu lại ‘nói dối’ với bạn, đặc biệt trong lĩnh vực gaming, thông qua topic “Why A/B testing by data go wrong in gaming”.
Với lượng topic lên đến con số hàng trăm, xoay quanh 6 nhóm chủ đề liên quan đến công nghệ web, VWS2020 hứa hẹn là điểm hẹn công nghệ hoành tráng nhất cuối năm 2020, nơi các tech-guys gặp gỡ và chia sẻ về những ứng dụng công nghệ mới và đón đầu xu hướng trong giai đoạn 5 năm tiếp theo!
==
Vietnam Web Summit 2020: LEAD THE AGE OF REVOLUTION TECHNOLOGIES
Vào tháng 12/2020, Vietnam Web Summit trở lại tại 2 thành phố TP.HCM và HN – nơi những ý tưởng sẽ gặp nhau và cùng đón đầu những xu hướng, công nghệ mới trong chặng đường 5 năm tiếp theo – một kỷ nguyên mới của công nghệ!
Bài viết gốc được sự cho phép của tác giả Nguyễn Chí Thức
IDE là gì?
IDE là viết tắt của Integrated Development Environment (môi trường phát triển tích hợp) được định nghĩa là một công cụ mã hóa giúp tự động hóa quá trình chỉnh sửa, biên dịch, kiểm thử mã nguồn và nó giúp nhà phát triển dễ dàng chạy, viết và debug code.
Nó được thiết kế đặc biệt để phát triển phần mềm bao gồm một số công cụ được sử dụng để phát triển và kiểm thử phần mềm.
Giới thiệu IDE phổ biến trong lập trình Python như sau:
PyCharm được phát triển bởi Jet Brains và đây là môi trường phát triển tích hợp đa nền tảng (IDE) được thiết kế đặc biệt cho Python. Đây là IDE được sử dụng rộng rãi nhất và có sẵn ở cả phiên bản trả phí và nguồn mở miễn phí.
PyCharm là một IDE Python hoàn hảo với một các tính năng phong phú như tự đồng hoàn thiện code, điều hướng project nhanh, test và debug nhanh, hỗ trợ phát triển từ xa, khả năng truy cập cơ sở dữ liệu, v.v.
Tính năng, đặc điểm:
Điều hướng mã thông minh
Đánh dấu lỗi
Trình gỡ lỗi (debug) mạnh mẽ
Hỗ trợ các framework phát triển web Python, ví dụ, Angular JS, Javascript
Spyder là một công cụ mã nguồn mở có sự công nhận cao trong thị trường IDE và phù hợp nhất với khoa học dữ liệu. Tên đầy đủ của Spyder là môi trường phát triển Python khoa học. Nó hỗ trợ tất cả các nền tảng quan trọng Linux, Windows và MacOS X.
Nó cung cấp một tập hợp các tính năng như trình soạn thảo mã cục bộ, trình xem tài liệu, trình thám hiểm biến, bảng điều khiển tích hợp, v.v. và hỗ trợ các mô-đun khoa học như NumPy, SciPy, v.v.
Tính năng, đặc điểm:
Làm nổi bật cú pháp đúng và hoàn thành mã tự động
Tích hợp mạnh mẽ với Python console
Hoạt động tốt trong chế độ chỉnh sửa đa ngôn ngữ và chế độ hoàn thành mã tự động
PyDev
PyDev được định nghĩa là một trong những IDE Python thường được sử dụng, là một plugin bên ngoài cho Eclipse. Đó là một lựa chọn tự nhiên của các nhà phát triển Python đến từ nền tảng Java và rất phổ biến trên thị trường với tư cách là trình thông dịch Python.
Pydev có một tính năng bao gồm tích hợp Django, hoàn thành mã tự động, thụt lề thông minh, v.v.
Tính năng, đặc điểm:
Các tham số mạnh như tái refactor, debug, phân tích mã và chức năng bao phủ mã.
Nó hỗ trợ các môi trường ảo, Mypy và định dạng màu đen.
Cũng hỗ trợ tích hợp PyLint, trình debug từ xa, tích hợp unit test, v.v.
Atom
Atom được phát triển bởi GitHub, ban đầu được bắt đầu như một nguồn mở, đa nền tảng. Nó dựa trên một framework, nghĩa là nó cho phép ứng dụng máy tính để bàn cross-platform sử dụng Chromium và Node.js và thường được gọi là “Text Editor Hack cho thế kỷ 21 st
Tính năng, đặc điểm:
Trực quan hóa kết quả trên Atom mà không cần mở bất kỳ cửa sổ nào khác.
Một plugin có tên “Markdown Preview Plus” cung cấp hỗ trợ tích hợp để chỉnh sửa và hiển thị các tệp Markdown.
Nó được định nghĩa là một IDE đa nền tảng được tích hợp các tính năng cần thiết và hỗ trợ phát triển tốt. Phiên bản cá nhân của nó là miễn phí. Phiên bản pro đi kèm bản dùng thử 30 ngày.
Nó có một số tính năng bao gồm tự động hoàn thành code, highlight cú pháp, thụt lề và debug.
Tính năng, đặc điểm:
Có phần tùy chình và cũng có thể mở rộng.
Hỗ trợ phát triển từ xa, test-driven development cùng với kiểm thử đơn vị.
Jupyter Notebook
Jupyter là một trong những trình soạn thảo sổ ghi chép IPython được sử dụng nhiều nhất được sử dụng trong ngành Khoa học dữ liệu. Nó là một ứng dụng web dựa trên cấu trúc máy chủ-máy khách và cho phép bạn tạo và thao tác với các tài liệu sổ ghi chép.
Tính năng, đặc điểm:
Hỗ trợ đánh dấu
Dễ dàng tạo và chỉnh sửa mã
Lý tưởng cho người mới bắt đầu trong khoa học dữ liệu
Thonny
Thonny là một IDE khác phù hợp nhất cho việc học và dạy lập trình. Nó là một phần mềm được phát triển tại Đại học Tartu và hỗ trợ hoàn thành mã và đánh dấu các lỗi cú pháp.
Tính năng, đặc điểm:
Trình debug đơn giản
Hỗ trợ đánh dấu lỗi và hoàn thành mã tự động
Rodeo
Rodeo được định nghĩa là một trong những IDE tốt nhất cho python được sử dụng rộng rãi nhất cho các dự án khoa học dữ liệu như lấy dữ liệu và thông tin từ các tài nguyên khác nhau.
Nó hỗ trợ chức năng đa nền tảng và cung cấp tự động hoàn thành mã.
Tính năng, đặc điểm:
Cho phép các chức năng so sánh dữ liệu, tương tác, vẽ đồ thị và kiểm tra dữ liệu.
Hoàn thành mã tự động, highlight cú pháp, trình điều hướng tệp trực quan, v.v.
Microsoft Visual Studio
Microsoft Visual Studio là một trình soạn thảo mã nguồn mở phù hợp nhất để phát triển và gỡ lỗi các dự án web và đám mây mới nhất. Nó có thị trường riêng cho các phần mở rộng.
Tính năng, đặc điểm:
Hỗ trợ mã hóa Python trong Visual studio
Có sẵn ở cả phiên bản trả phí và miễn phí
Eric Python
Eric Python là một trình soạn thảo được phát triển bằng chính Python và có thể được sử dụng cho cả công việc chuyên nghiệp và không chuyên nghiệp.
Tính năng, đặc điểm:
Cung cấp bố trí cửa sổ cấu hình, editor
Khả năng quản lý dự án nâng cao, kiểm soát phiên bản
Bài viết được sự cho phép của tác giả Nguyễn Văn Minh
Bạn đã bao giờ tự hỏi: Trong MySQL, câu lệnh CASE, câu lệnh IF và hàm IF khác nhau thế nào? Bạn có thấy phân vân khi chọn một trong ba thứ trên để viết query? Đây không phải câu hỏi mới nhưng nhiều bạn sẽ bỡ ngỡ khi tiếp xúc với nó. Nhất là khi bạn vừa bắt đầu tìm hiểu về MySQL và cơ sở dữ liệu quan hệ.
Nếu bạn muốn tìm hiểu cơ sở dữ liệu là gì và có những loại nào, hãy tham khảo bài viết này.
Cú pháp
Câu lệnh CASE
CASE value WHEN [compare_value] THEN result [WHEN [compare_value] THEN result ...] [ELSE result] END
CASE WHEN [condition] THEN result [WHEN [condition] THEN result ...] [ELSE result] END
Câu lệnh IF
IF condition1 THEN
{...statements to execute when condition1 is TRUE...}
[ ELSEIF condition2 THEN
{...statements to execute when condition1 is FALSE and condition2 is TRUE...} ]
[ ELSE
{...statements to execute when both condition1 and condition2 are FALSE...} ]
END IF;
Hàm IF
IF(expr1,expr2,expr3)
Đào sâu hơn
Nhìn vào cú pháp trên đây, ta có thể thấy, dường như hàm IF ít linh hoạt hơn câu lệnh CASE. Nếu bạn viết thế này:
SELECT IF(movie = 'The Matrix', 'high', 'low') AS suggestion
Thì bạn hoàn toàn có thể dùng CASE như thế này:
SELECT CASE WHEN movie = 'The Matrix' THEN 'high' ELSE 'low' END AS suggestion
Khá là giống nhau đúng không? Trừ việc hàm IF trông gọn gàng hơn chút ít. Nhưng nếu có nhiều hơn hai nhánh thì sao? Có lẽ bạn sẽ không muốn viết thế này:
SELECT IF(movie = 'The Matrix', 'high', IF(movie = 'Endgame', 'medium', 'low')) AS suggestion
Mà nên là thế này:
SELECT CASE movie
WHEN = 'The Matrix' THEN 'high'
WHEN = 'Endgame' THEN 'medium'
ELSE 'low'
END AS suggestion
Nó cũng tương tự như khi ta dùng switch để rẽ nhánh vậy, tự nhiên vào thoải mái hơn rất nhiều.
Có một điều bạn phải chú ý. Trong khi câu lệnh CASE là câu lệnh chuẩn của SQL thì hàm IF lại hoàn toàn không phải. Điều đó có nghĩa gì? Nếu bạn có ý định chuyển sang dùng SQL Server hay PostgreSQL chẳng hạn, hàm IF sẽ không còn hoạt động nữa.
Ở một diễn biến khác, câu lệnh IF là cái gì đó rất lạ lẫm với hai thứ trên. Nó được dùng khi viết thủ tục (procedure). Ví dụ:
CREATE FUNCTION get_suggestion (movie varchar(50))
RETURNS varchar(20)
BEGIN
IF movie = 'The Matrix' THEN
return 'high';
ELSEIF movie = 'Endgame' THEN
return 'medium';
ELSE
return 'low';
END IF;
END;
Do đó, chớ nên nhầm lẫn mục đích sử dụng của câu lệnh IF với hàm IF, hay thậm chí câu lệnh CASE. Chúng sinh ra vì những “sứ mệnh” khác nhau.
Bài viết được sự cho phép của tác giả Nguyễn Hữu Đồng
UUID là viết tắt của Universally Unique IDentifier. Mình chỉ hiểu đơn giản đây là một mã định danh duy nhất là một số gồm 128bit. Tổng cộng có 32 kí tự chia thành 5 phần cách nhau bởi 4 dấu gạch nối đơn cử
123e4567-e89b-12d3-a456-426655440000
Để generate ra một UUID thì bạn hoàn toàn có thể dùng 1package nào đó mà khỏi cần nghĩ nhiều, nhưng mình sẽ tự generate ra 1 fake uuid bằng cách random ra một slice byte gồm 16 phần tử (8 bit) và sau đó để cho đẹp mắt thì mình sẽ convert hệ thập phân về base16.
Vì sao là 16 phần tử và base 16. Thứ nhất mình muốn các kí tự trong uuid là chữ hoặc số A-Z 0–9, cộng thêm cty mình đang làm 1 dự án mang tên là HexSafe, mà hex là hexadecimal thập lục phân là 16 luôn. Và nếu số 8bit phân sang base16 thì chỉ tốn hai char nên mình sẽ random 16 số 8 bit.
b := make([]byte, 16)
_, err := rand.Read(b)
if err != nil {
return "", err
}
Sau đó mình sẽ lấy 4 số cho phần đầu, tiếp theo là 2,2,2 và cuối cùng là 6 số,
để chuyển qua hex thì quá easy luôn. Có sẵn fmt.Sprintf(“%X”,n) để convert n về base16 dạng in hoa.
Mặc dù là fake uuid, trên lý thuyết là có thể trùng lẫn nhau xác xuất 1/(256¹⁶)
và nếu nó xảy ra thì thôi ý trời :)) đến UUID còn có thể trùng nhau cơ mà.
CV đóng một vai trò rất quan trọng đối với việc thu hút nhà tuyển dụng. Một CV Template IT đúng chuẩn lại giúp gia tăng nhiều hơn các lợi thế ở ứng viên. Vậy liệu dân lập trình IT đã có những hình dung chính xác về CV online IT hay chưa? Cùng TopDev điểm qua top các mẫu CV Template IT (IT CV Template) đẹp chuẩn.
Những lưu ý ban đầu về CV Template IT
Dù đó là CV cho sinh viên IT mới ra trường hay CV ứng tuyển các vị trí Junior Developer, Senior Developer,… bạn vẫn phải đầu tư cho CV IT Developer của mình. Nó là tài liệu quan trọng ghi dấu ấn trong sự nghiệp phát triển của bạn. Đồng thời, dựa trên các thông tin bạn truyển tải, nhà tuyển dụng có thể đánh giá được các tiêu chí về trình độ, năng lực, tiềm năng phát triển,…
Hãy lưu tâm vấn đề này để CV Template IT của bạn chuẩn format, Phải nhớ nó không giống sơ yếu lý lịch đơn thuần.CV ngành IT nhấn mạnh các giá trị nổi bật về kỹ năng, trình độ chuyên môn, quan điểm – mục tiêu,…
Đồng thời, viết CV giúp bạn định hình và có những trải nghiệm tốt hơn. Trường hợp bạn ứng tuyển các vị trí khác như freelancer it hay Senior Developer đều sẽ đạt hiệu quả ứng tuyển cao hơn.
Lợi thế điểm nhấn có thể đến từ các lưu ý về kỹ năng và các khoa học chuyên sâu dưới đây:
Kỹ năng (Skills)
Kỹ năng duy trì các ứng dụng phần mềm khoa học hiện. Phát triển các ứng dụng mới hơn.
Kết nối và chia sẻ kinh nghiệm áp dụng các nguyên tắc, chiến thuật phát triển dựa trên các nền tảng lý thuyết.
Kỹ năng phân tích, xử lý – giải quyết vấn đề.
Kỹ năng giao tiếp
Đây là các kỹ năng khá quan trọng mà bạn cần bổ sung vào IT CV Template.
Bài viết được sự cho phép của tác giả Nguyễn Hữu Khanh
Toán tử instanceof trong Java là một toán tử được sử dụng để kiểm tra xem đối tượng này có phải là instance của một class hay interface nào đó hay không? Kết quả trả về của toán tử này sẽ là true nếu đối tượng đó là thể hiện của class mà các bạn đang check, ngược lại thì false.
Trong hàm main() của class này, mình initialize một đối tượng của class Application và sử dụng toán tử instanceof để kiểm tra xem đối tượng này có phải là instance của class Application này hay không? Các bạn sẽ thấy kết quả sẽ như sau:
Nếu các bạn viết code như sau:
package com.huongdanjava.java;
public class Application {
public static void main(String[] args) {
Application application = new Application();
System.out.println(application instanceof String);
}
}
thì IDE sẽ báo lỗi ngay:
Đây là trong trường hợp quá tường minh, quá rõ ràng, IDE có thể báo lỗi cho các bạn biết ngay.
Nhưng nếu bạn có một interface với hai implementation như sau:
package com.huongdanjava.java;
public interface Shape {
}
package com.huongdanjava.java;
public class Triangle implements Shape {
}
package com.huongdanjava.java;
public class Rectangle {
}
thì lúc này nếu các bạn initialize đối tượng của class Triangle nhưng lại đi kiểm tra đối tượng này có phải là thể hiện của class Rectangle,
package com.huongdanjava.java;
public class Application {
public static void main(String[] args) {
Shape shape = new Triangle();
System.out.println(shape instanceof Rectangle);
}
}
IDE sẽ không thể detect lỗi lúc compile time nhưng khi chạy các bạn sẽ thấy kết quả như sau:
Chúng ta sẽ thường sử dụng toán tử instanceof trong trường hợp kiểm tra xem tham số truyền vào của một phương thức có phải là instance của một class nào đó hay không? Ví dụ như:
Trong phương thức trên, tham số interface Shape có nhiều implementation khác nhau, trong phần body của phương thức, chúng ta sẽ check xem là instance được truyền vào phương thức này có phải là Triangle hay không? Nếu đúng thì xử lý code tiếp.
Kết quả:
package com.huongdanjava.java;
public class Application {
private void check(Shape shape) {
if (shape instanceof Triangle) {
Triangle triangle = (Triangle) shape;
System.out.println("This is triangle: " + triangle.toString());
}
}
public static void main(String[] args) {
Application application = new Application();
application.check(new Triangle());
}
}
Từ Java 14, các bạn có thể viết lại phương thức check() sử dụng pattern matching instanceof, đơn giản như sau:
private void check(Shape shape) {
if (shape instanceof Triangle triangle) {
System.out.println("This is triangle: " + triangle.toString());
}
}
Với cách viết mới, chúng ta không cần viết thêm một dòng code để cast instance về đối tượng mà chúng ta muốn nữa. Tất cả sẽ được thực hiện trong dòng lệnh if.
Factory Method Pattern giải quyết vấn đề này bằng cách định nghĩa một factory method cho việc tạo đối tượng, và các lớp con thừa kế có thể override phương thức này để chỉ rõ đối tượng nào sẽ được khởi tạo.
Bây giờ, mình sẽ có một ví dụ, cho các bạn dễ hiểu về factory design pattern này.
Mình có yêu cầu viết một ứng dụng, nạp tiền điện thoại cho các mạng điện thoại Việt Nam: Mobifone, VinaPhone, Viettel, Vietnamobile, Gmobile.
Request yêu cầu gởi đến ứng dụng của chúng ta gồm 2 tham số: số thoại cần nạp tiền và số tiền.
Thường trong bài viết này, chúng ta sẽ sử dụng một thiết bị modern GMS và sử dụng tập lệnh AT-Command xài lệnh USSD để nạp tiền vào tài khoản cho người dùng.
Nhưng có rắc rối ở đây là mỗi nhà mạng, đều có cấu trúc kiểm tra số dư tài khoản, hay cú pháp nạp tiền đều khác nhau.
Vậy làm sao khi gởi đến chúng ta sẽ điều hướng cho từng class nhà mạng.
Sơ đồ Design Pattern:
Đầu tiên, mình sẽ tạo một InterFace INetwork.cs C#: