第三章:javascript: 列表


在日常生活中,人們經常使用列表:待辦事項列表,購物清單,十佳榜單,最后十名榜單等。計算機也在使用列表,尤其是列表中元素保存的是太多時。當不需要一個很長的序列中查找元素,或對其進行排序時,列表顯得尤為有用。反之,如果數據結構非常復雜,列表的作用就沒有那么大了。

本章展示了如果創建一個簡單的列表類,我們首先給列表給出抽象的數據類型定義,然后描述如何實現抽象數據類型(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類的一個迭代器。以下是和使用數組索引的方式相比,使用迭代器的一些優點

  1. 訪問列表元素時不必關心底層的數據存儲結構
  2. 當為列表添加一個元素時,索引的值就不對了,此時只用更新列表,而不用更新迭代器。
  3. 可以用不同類型的數據存儲方式實現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: 棧


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM