在日常生活中,人們經常使用列表:待辦事項列表,購物清單,十佳榜單,最后十名榜單等。計算機也在使用列表,尤其是列表中元素保存的是太多時。當不需要一個很長的序列中查找元素,或對其進行排序時,列表顯得尤為有用。反之,如果數據結構非常復雜,列表的作用就沒有那么大了。
本章展示了如果創建一個簡單的列表類,我們首先給列表給出抽象的數據類型定義,然后描述如何實現抽象數據類型(ADT),最后,分析幾個列表適合解決的實際問題。
一,列表的抽象數據類型定義
為了設計列表的抽象數據類型,需要給出列表的定義,包括列表應該有哪些屬性,應該在列表上執行哪些操作。
列表是一組有序的數據,每個列表中的數據項稱為元素。在javascript中,列表中的元素可以是任意數據類型。列表中可以保存多少元素並沒有限定(在實際使用時會受到程序內存的限制)。
不包含任何元素的列表稱為空列表。列表中包含元素的個數稱為列表的length。在內部的實現上,用一個變量的listSize保存列表中元素的個數。可以在列表的末尾append一個元素,也可以給列表起始位置insert一個元素。使用remove方法從列表中刪除元素,使用clear方法清空列表中的所有的元素。
還可以使用toString()方法顯示列表中所有的元素,使用getElement()方法顯示當前元素。 列表中擁有描述元素位置的屬性。列表有前有后(front和end),使用next()方法可以從當前元素移動到下一個元素,使用prev()方法可以移動當前元素到上一個元素。還可以使用moveTo(n)方法直接移動到指定的位置。currPos屬性表示列表中當前的位置。
列表的抽象數據類型並未指明列表的存儲結構,在本章的實現中,我們使用一個dataStore來存儲元素。
列表的抽象數據定義。
listSize(屬性) | 列表中元素的個數 |
pos(屬性) | 列表的當前位置 |
length(屬性) | 返回列表中元素的個數 |
clear(方法) | 清空列表中所有的元素 |
toString(方法) | 返回列表中的字符串形式 |
getElement(方法) | 返回當前位置的元素 |
insert(方法 ) | 在現有元素后插入新元素 |
append(方法) | 在列表末尾增加新元素 |
remove(方法) | 從列表中刪除元素 |
front(方法) | 將列表中的當前元素設置到第一個元素 |
end(方法) | 將列表中的當前元素位置移動到最后一個 |
prev(方法) | 將當前位置后移一位 |
next(方法) | 將當前位置前移一位 |
currPos(方法) | 返回列表的當前位置 |
moveTo(方法) | 將當前位置移動到指定位置 |
二,實現列表類
根據上面定義的列表抽象數據類型,可以直接實現一個List類,讓我們從定義構造函數開始,雖然它本身並不是列表抽象數據類型的一部分。
function List() { this.listSize = 0; this.pos = 0; this.dataStore = [];//創建一個空數組保存列表的元素 this.clear = clear; this.find = find; this.toString = toString; this.insert = insert; this.append = append; this.remove = remove; this.front = front; this.end = end; this.prev = prev; this.next = next; this.length = length; this.currPos = currPos; this.moveTo = moveTo; this.getElement = getElement; this.contains = contains; }
1.append: 給列表添加元素
我們實現的第一個方法是append(),該方法給列表的下一個位置添加一個新的元素,這個位置剛好等於變量listSize的值。
function append(element) { this.dataStore[this.listSize++] = element; }
當新元素就位后,變量的listSize就增加1
2.remove:從列表中刪除元素
接下來我們看一下如何從列表中刪除一個元素。remove()方法是List類中較難實現的一個方法。首先,要在列表中找到該元素,然后刪除它。並且調整底層數組對象以填補刪除元素后留下的空白。好消息是可以使用splice()方法來簡化這一過程,我們先從一個輔助方法find()開始,該方法用於查找要刪除的元素。
3.find在列表中查找某一元素
find()方法通過數組對象dataStore進行迭代,查找給定的元素。如果找到,就返回該元素的位置。如果找不到,返回-1.這是在數組中找不到元素時返回的標准值。我們可以在remove()方法中利用此值做錯誤校驗。
remove方法使用find()方法返回的位置對數組dataStore進行截取。數組改變后,將變量listSize的值減1。以反映列表的最新長度。如果元素刪除成功,則返回true,否則返回false.
function remove(element) { var foundAt = this.find(element); if (foundAt > -1) { this.dataStore.splice(foundAt,1); --this.listSize; return true; } return false; }
4.length列表中有多少個元素
length()返回列表中元素的個數。
function length() { return this.listSize; }
5.toStirng:顯示列表中的元素
現在可以創建一個方法,用來顯示列表中元素。下面是一段簡短的代碼,實現了toString()方法。
function toString() { return this.dataStore; }
嚴格的來說,此方法返回的是一個數組,而不是一個字符串,但它的目的是為了顯示當前的一個狀態,因此返回一個數組就足夠了。
我們暫時測試下目前實現的代碼,檢驗我們之前創建的方法。
var names = new List(); names.append('錘錘'); names.append('牛牛'); names.append('豆豆'); console.log(names.dataStore);//["錘錘", "牛牛", "豆豆"] names.remove('牛牛'); console.log(names.dataStore);//["錘錘", "豆豆"]
6.insert: 向列表中插入一個元素
接下來我們要討論的方法是insert()。如果在前面的列表中刪除“牛牛”,現在又想將它放回原來的位置,該怎么辦?insert()方法需要知道元素插入到什么位置(因此,現在的實現方法是假設插入到列表中某個元素之后,知道足額寫后,就可以定義insert()方法啦)。
function insert(element, after) { var insertPos = this.find(after); if (insertPos > -1) { this.dataStore.splice(insertPos + 1, 0 ,element); ++this.listSize; return true } return false; }
實現的過程中,insert()方法使用到find()方法,find()方法會找到傳入的after參數在列表中的位置,找到該位置后,使用splice()得到將元素插入該位置后,然后將變量listSize加1並返回true.表明插入成功。
7.clear:清空列表中的所有元素。
接下來,我們需要一個方法清空列表中所有元素,為插入新元素騰出空間。
function clear() { delete this.dataStore; this.dataStore = []; this.listSize = this.pos = 0; }
clear()方法使用delete操作符刪除數組dataStore,接着創建新的空數組。最后一行將listSize和pos值設為0,表明是一個空的新列表。
8.判定給定值是否在列表中
當需要判斷一個給定值是否在列表中時,contains()方法就變得很有用。下面是該方法定義:
function contains(element) { for (var i = 0; i < this.dataStore.length; ++i) { if (this.dataStore[i] == element) { return true; } } return false; }
9.遍歷列表
最后的一組方法允許用戶在列表上自由移動,最后一個方法getElement()返回列表當前的元素
function front() { this.pos = 0; } function end() { this.pos = this.listSize - 1; } function prev() { if (this.pos > 0) { --this.pos; } } function next() { if (this.pos < this.listSize - 1) { ++this.pos; } } function currPos() { return this.pos; } function moveTo(position) { this.pos = position; } function getElement() { return this.dataStore[this.pos] }
現在我們創建一個由姓名組成的列表,來展示怎么使用它。
var names = new List(); names.append('錘錘'); names.append('牛牛'); names.append('豆豆'); names.append('羊羊'); names.append('兔兔'); names.append('花花');
現在移動到列表中的第一個元素,並且顯示它。
names.front(); console.log(names.getElement());//顯示 “錘錘”
接下來向前移動一個單位並顯示它:
names.next(); console.log(names.getElement()); //顯示 “牛牛”
上面的代碼展示這些行為實際上是迭代器的概念,這也是接下來要討論的內容。
3.使用迭代器訪問列表
使用迭代器,可以不必關系數據的內部存儲方式。以實現對列表的遍歷。前面提到的方法fornt(),end(),prev(),next()和currPos就實現了List類的一個迭代器。以下是和使用數組索引的方式相比,使用迭代器的一些優點。
- 訪問列表元素時不必關心底層的數據存儲結構
- 當為列表添加一個元素時,索引的值就不對了,此時只用更新列表,而不用更新迭代器。
- 可以用不同類型的數據存儲方式實現List類,迭代器為訪問列表里的元素提供了統一的方式。
了解了這些優點后,我使用一個迭代器遍歷列表的例子:
for(names.front(); names.currPos() < names.length(); names.next()) { console.log(names.getElement()) }
在for循環的一開始,將列表中的當前位置設置為第一個元素。只要currPos的值小於列表的長度,就一直循環,每一次循環都調用next()方法將當前的位置向前移動一位。
同理,還可以從后向前遍歷列表,代碼如下:
for (names.front(); names.currPos() >= 0; names.prev()) { console.log(names.getElement()) }
循環從列表的最后一個元素開始,當 當前位置大於或等於0時,調用prev()方法后移一位。
迭代器只是用來在列表上隨意移動,而不應該和人和為列表增加或刪除元素的方法一起使用。
四,一個基於列表的應用。
1.讀取文本
為了展示如何使用列表,我們實現一個類似Redbox的租賃自助查詢系統。
var _movies = "(1)RogueTrader(《魔鬼營業員》)NNN(2)TradingPlaces(《顛倒乾坤》)NNN(3)WallStreet(《華爾街》)NNN(4)WallStreet2:MoneyNeverSleeps(《華爾街2:金錢永不眠》)NNN(5)BoilerRoom(《開水房》)NNN(6)GlengarryGlen Ross(《拜金一族》)NNN(7)Enron:TheSmartestGuysInTheRoom(《安然風暴:屋內聰明人》)NNN(8)Trader(《交易員》)NNN(9)AmericanPsycho(《美國精神病人》)NNN(10)BonfireoftheVanities(《虛榮的篝火》)"; var movies = _movies.split("NNN");
上面的split()程序將字符串分割為數組,多一個空格,雖然多一個空格無傷大雅,但是在比較字符串時是個災難,因此,我們需要在循環里使用trim()方法刪除每個數組元素末尾的空格。要是有一個函數能將這些操作封裝起來就再好不過了。
function createArr(file){ var arr = file.split("NNN"); for (var i = 0; i < arr.length; ++i) { arr[i] = arr[i].trim(); } return arr; }
2.使用列表管理影碟租賃
下面要將數組保存到一個列表,代碼如下:
var movieList = new List(); for (var i = 0; i < movies.length; ++i) { movieList.append(movies[i]) }
現在可以寫一個函數來顯示影碟清單了:
function displayList(list) { for (list.front(); list.currPos() < list.length(); list.next()) { console.log(list.getElement()) } }
displayList()函數對於原生的數據類型沒有什么問題,比如由字符串組成的列表。但是它用不了自定義類型。比如我們將下面定義的Customer對象。讓我對它稍微做修改,讓它可以發現列表是由Customer對象組成的。這樣就可以對應的進行顯示了。下面是重新定義的displayList()函數:
function displayList(list) { for (list.front(); list.currPos() < list.length(); list.next()) { if (list.getElement() instanceof Customer) { console.log(list.getElement()['name'] + ", " + list.getElement()["movie"] ); } else { console.log(list.getElement()) } } }
對於列表中的每一個元素,都使用instanceof操作符判斷元素是否是Customer對象。如果是,就使用name和movie做索引。得到客戶檢出的值。如果不是,就返回該元素即可。
現在已經有了movies,還需要創建一個新的列表,Customers,用來保存在系統中檢出電影的客戶:
var customers = new List();
該列表包含Customer對象,該對象由用戶的姓名和用戶檢出電影組成。下面是Costomer對象的構造函數。
function Customer(name, movie) { this.name = name; this.movie = movie; }
接下來,需要創建一個允許用戶檢出的電影函數。該函數有兩個參數:客戶端姓名和客戶想要檢出的電影。如果改電影目前可以租賃,該方法會從清單里刪除該元素,同時加入客戶列表customers.這個操作會用到列表的contains()方法。
下面是用於檢出電影的函數:
function checkout(name, movie, filmList, customerList) { if (movieList.contains(movie)) { var c = new Customer(name, movie); customerList.append(c); filmList.remove(movie) } else { console.log(movie + "is not available") } }
下面使用簡單的代碼測試checkout函數
var movies = createArr(_movies); var movieList = new List(); var customers = new List(); for (var i = 0; i < movies.length; ++i) { movieList.append(movies[i]) } //顯示xx displayList(movieList) checkout("RogueTrader","交易員",movieList,customers); //xx displayList(customers)
對於列表的方法,就更新至此,可以根據自己的需要,增加更健壯的方法去實現。
符:測試代碼
function List() { this.listSize = 0; this.pos = 0; this.dataStore = [];//創建一個空數組保存列表的元素 this.clear = clear; this.find = find; this.toString = toString; this.insert = insert; this.append = append; this.remove = remove; this.front = front; this.end = end; this.prev = prev; this.next = next; this.length = length; this.currPos = currPos; this.moveTo = moveTo; this.getElement = getElement; this.contains = contains; } //給列表添加元素 function append(element) { this.dataStore[this.listSize++] = element; } //find查找方法 function find(element) { for (var i = 0;i < this.dataStore.length; ++i) { if (this.dataStore[i] == element) { return i; } } return -1; } //remove方法 function remove(element) { var foundAt = this.find(element); if (foundAt > -1) { this.dataStore.splice(foundAt,1); --this.listSize; return true; } return false; } //length function length() { return this.listSize; } //toString function toString() { return this.dataStore; } //insert function insert(element, after) { var insertPos = this.find(after); if (insertPos > -1) { this.dataStore.splice(insertPos + 1, 0 ,element); ++this.listSize; return true } return false; } //clear function clear() { delete this.dataStore; this.dataStore = []; this.listSize = this.pos = 0; } //contains判斷給定值是否在列表中 function contains(element) { for (var i = 0; i < this.dataStore.length; ++i) { if (this.dataStore[i] == element) { return true; } } return false; } function front() { this.pos = 0; } function end() { this.pos = this.listSize - 1; } function prev() { if (this.pos > 0) { --this.pos; } } function next() { if (this.pos < this.listSize - 1) { ++this.pos; } } function currPos() { return this.pos; } function moveTo(position) { this.pos = position; } function getElement() { return this.dataStore[this.pos] } //var names = new List(); //names.append('錘錘'); //names.append('牛牛'); //names.append('豆豆'); //names.append('羊羊'); //names.append('兔兔'); //names.append('花花'); //console.log(names.dataStore); //names.front(); //console.log(names.getElement());//顯示 “錘錘” //names.next(); //console.log(names.getElement()); //顯示 “豆豆” // for(names.front(); names.currPos() < names.length(); names.next()) { // console.log(names.getElement()) // } var _movies = "(1)RogueTrader(《魔鬼營業員》) NNN(2)TradingPlaces(《顛倒乾坤》)NNN(3)WallStreet(《華爾街》)NNN(4)WallStreet2:MoneyNeverSleeps(《華爾街2:金錢永不眠》)NNN(5)BoilerRoom(《開水房》)NNN(6)GlengarryGlen Ross(《拜金一族》)NNN(7)Enron:TheSmartestGuysInTheRoom(《安然風暴:屋內聰明人》)NNN(8)Trader(《交易員》)NNN(9)AmericanPsycho(《美國精神病人》)NNN(10)BonfireoftheVanities(《虛榮的篝火》) "; function createArr(file){ var arr = file.split("NNN"); for (var i = 0; i < arr.length; ++i) { arr[i] = arr[i].trim(); } return arr; } var movies = createArr(_movies) //將數組保存到一個列表 var movieList = new List(); for (var i = 0; i < movies.length; ++i) { movieList.append(movies[i]) } //console.log(movieList) var customers = new List(); //顯示影碟清單 function displayList(list) { for (list.front(); list.currPos() < list.length(); list.next()) { if (list.getElement() instanceof Customer) { console.log(list.getElement()['name'] + ", " + list.getElement()["movie"] ); } else { console.log(list.getElement()) } } } //displayList(movieList.dataStore) function Customer(name, movie) { this.name = name; this.movie = movie; } //檢出電影的函數 function checkout(name, movie, filmList, customerList) { if (movieList.contains(movie)) { var c = new Customer(name, movie); customerList.append(c); filmList.remove(movie) } else { console.log(movie + "is not available") } } displayList(movieList)
(本章完結)
上一章:第二章:javascript: 數組 下一章:第四章:javascript: 棧