天天看點

自己動手,實作一種類似List<T>的資料結構(二)

前言:

首先,小匹夫要祝各位看官聖誕快樂,新年愉快~。上一篇文章《自己動手,實作一種類似List<T>的資料結構(一)》 介紹了一下不依靠List<T>實作的各種接口,仿造一個輕量級資料結構的過程。可能有的看官會有一些疑問,例如一些功能可以通過Linq提供的拓展來實作呀。此言不虛但也不全對,為了我們在工作中能友善的操作集合而提供的這些拓展方法(包括我們自己也可以建構的拓展方法),例如 Where,Any,Max,All...balalbala等等這些方法都是針對IEnumerable的對象進行擴充的,也就是說需要實作 IEnumerable接口。但是前面已經說了,小匹夫的用意是不實作各種List<T>繼承的接口。另外小匹夫的初衷是仿造和拓展 List<T>,将工作中需要使用到的各種功能內建到一個類中,是以有些現成的拓展方法不需要,有一些沒有的方法小匹夫也會自己實作一下(當然不是通過給現成的類添加拓展方法這種方式)。當然這篇文章介紹的東西還不成熟,需要慢慢完善,小匹夫也是把這個當做一個學習和實踐的機會。好啦,解釋完畢,那就介紹下今天的内容吧:

  1. 實作的方法的名稱和說明清單
  2. 增加了3個委托來抽象3種情況。
  3. Map:通過委托把EggArray<T>中的每個值映射到一個新的EggArray<T>中。
  4. Difference:傳回的值來自EggArray<T>中,但同時不是傳入的Other裡面的值
  5. Invoke:在EggArray<T>的每個元素上執行methodName方法。
  6. Pluck:萃取EggArray<T>中某字段值,傳回一個數組,由于字段類型不确定,是以需要裝箱。
  7. Shuffle:傳回一個随機亂序的T[]副本。

那麼下面我們就書接上文,繼續我們仿照和拓展List<T>的步伐。

Underscore.js的前緣

咦,這不是一篇關于Csharp的文章嗎?怎麼把JS給幹出來?哈哈,當然技術上并沒有什麼必然的關系,隻不過是小匹夫之前使用過cocos2d這套遊戲引擎開發過遊戲,有一段時間也很癡迷于cocos2d-js這種使用JS就能開發原生遊戲的能力。是以也接觸了一些js庫,對Underscore.js更是情有獨鐘。是以一提到要模仿List<T>這種内部其實是Array的資料結構,一個靈感就是為何不嘗試實作一些Underscore.js數組部分的若幹功能呢?是以下表EggArray<T>的新增方法中有部分借鑒于Underscore.js。

新增方法表

新增方法 說明
First  傳回EggArray<T> 的第一個元素。傳遞 n參數将傳回數組中從第一個元素開始的n個元素
Last  傳回EggArray<T> 的最後一個元素。傳遞 n參數将傳回數組中從最後一個元素開始的n個元素
Slice  切割
Get  預留
Set
AddFirst  将對象添加到 EggArray<T> 的起始處。
RemoveLast  從 EggArray<T> 中移除特定對象的最後一個比對項。
ContainsStrict  确定某元素是否在 EggArray<T> 中。(嚴格判斷是否是同一個對象)
IndexOfStrict  搜尋指定的對象,并傳回整個 EggArray<T> 中第一個比對項的從零開始的索引。(同上)
TryGet  擷取指定類型對象
LastIndexOf  搜尋指定的對象,并傳回整個 EggArray<T> 中第一個比對項的從結尾開始的索引。
Map  通過委托把EggArray<T>中的每個值映射到一個新的EggArray<T>中
Filter  周遊EggArray<T>中的每個值,傳回包含所有通過predicate真值檢測的元素值。
Without  傳回一個删除所有values值後的 EggArray<T>副本。
Find 在EggArray<T>中逐項查找,傳回第一個通過predicate疊代函數真值檢測的元素值
Every 如果EggArray<T>中的所有元素都通過predicate的真值檢測就傳回true。
Some 如果EggArray<T>中有任何一個元素通過 predicate 的真值檢測就傳回true。
Partition 拆分一個EggArray<T>為兩個數組:  第一個數組其元素都滿足predicate疊代函數, 而第二個的所有元素均不能滿足predicate疊代函數
Difference 傳回的值來自EggArray<T>中,但同時不是傳入的Other裡面的值
Uniq 傳回 EggArray<T>去重後的副本
Invoke 在EggArray<T>的每個元素上執行methodName方法。
Pluck 萃取EggArray<T>中元素某屬性值,傳回一個數組。
Shuffle 傳回一個随機亂序的T[]副本
Sample 從 EggArray<T>中産生一個随機樣本。傳遞一個數字表示從EggArray<T>中傳回n個随機元素。否則将傳回一個單一的随機項。

各位看官可以看到,增加了許多挺有趣的功能。為了能将表中的功能名字變成真正的功能,我們還需要對上一篇文章中的變量&屬性部分做一些增改,如下我們增加了3個委托來抽象3種情況。

//定義三個委托來處理具體邏輯
public delegate void IterationHandler(T item);
public delegate bool IterationBoolHandler(T item);
public delegate T IterationVauleHandler(T item);      

同時為了能測試我們的功能,我們還要定義一個用來被當做元素測試的類。

//被測試類
public class TargetClass
{
    public int id;
    public string name;

    public TargetClass(int id)
    {
        this.id = id;
        this.name = "NO. " + id;
    }

    public void Hi()
    {
        Debug.Log ("say hi");
    }
}      

同時還要有一個測試的環境,因為小匹夫是用mac做unity3d的開發,是以就直接使用unity3d的環境了。

/// <summary>
/// Egg array test.Based on Unity3D,各個元素的id為0-9
/// </summary>
using UnityEngine;
using System.Collections;
using EggToolkit;
public class EggArrayTest : MonoBehaviour {
    EggArray<TargetClass> testArray = new EggArray<TargetClass>();
    // Use this for initialization
    void Start () {

        for(int i = 0; i < 10; i++)
        {
            TargetClass test = new TargetClass(i);
            testArray.Add(test);
        }
//        Test_Difference();
//        Test_Invoke();
//        Test_Pluck();
//        Test_Shuffle();
//        Test_Map();
    }
    
    void Update () {
    
    }
}      

下面就讓小匹夫帶領大家分析幾個具體的函數,并進行下測試吧。

Map:

使用了IterationVauleHandler這個委托,即需要傳回一個T類型的值。

//通過委托把EggArray<T>中的每個值映射到一個新的EggArray<T>中
public EggArray<T> Map(EggArray<T>.IterationVauleHandler handler)
{
    EggArray<T> targetArray = new EggArray<T>(this.capacity);
    for(int i = 0; i < this.count; i++)
    {
        T t = handler(this.items[i]);
        targetArray.Add(t);
    }
    return targetArray;
}      

在EggArrayTest中實作Test_Map這個方法:

void Test_Map()
{
    EggArray<TargetClass> newArray = testArray.Map(delegate(TargetClass item) {
        TargetClass newItem = new TargetClass(1);
        newItem.id = item.id * 10;
        return newItem;
    });
    newArray.Foreach(delegate(TargetClass item) {
        Debug.Log (item.id);
    });
}

//原元素的id為0-9,輸出為0,10,20...90
      

自己動手,實作一種類似List&lt;T&gt;的資料結構(二)

Difference:

調用了Filter方法,其中Filter方法的參數是一個IterationBoolHandler委托,即一個傳回bool值的委托。具體可以看Filter的實作。

/// <summary>
/// Difference the specified others.
///輸出不包含others中元素的EggArray<T>
/// </summary>
/// <param name="others">Others.</param>
public EggArray<T> Difference(EggArray<T> others)
{
    EggArray<T> targetArray = new EggArray<T>();
    targetArray = 
    this.Filter(delegate(T item) {
        bool b = !others.Contains(item);
        return b;
    });
    return targetArray;
}      

在EggArrayTest中實作Test_Difference這個方法:

//作為參數傳入的EggArray<T>由testArray的第5,第9這2個元素組成
void Test_Difference()
{
    EggArray<TargetClass> differentArray = new EggArray<TargetClass>();
    differentArray.Add(testArray.Get(5));
    differentArray.Add(testArray.Get(9));
    testArray.Difference(differentArray).Foreach(delegate(TargetClass item) {
        Debug.Log(item.name);
    });
}
//輸出缺少no. 5,no. 9這兩個name      
自己動手,實作一種類似List&lt;T&gt;的資料結構(二)

Invoke:

/// <summary>
/// Invoke the specified methodName.
/// 每個元素上執行methodName方法,若方法不存在則抛出exception
/// </summary>
/// <param name="methodName">Method name.</param>
public void Invoke(string methodName)
{
    Type t = typeof(T);
    var method = t.GetMethod(methodName);
    if(method == null)
        throw new Exception("沒有找到指定的方法哦~,可能不叫" + methodName);
    for(int i = 0; i < this.count; i++)
    {
        method.Invoke(this.items[i], null);
    }
}      

在EggArrayTest中實作Test_Invoke這個方法:

//調用TargetClass的HI()方法
void Test_Invoke()
{
    testArray.Invoke("Hi");
}

//輸出:say hi      
自己動手,實作一種類似List&lt;T&gt;的資料結構(二)

Pluck:

萃取EggArray<T>中某字段值,傳回一個數組,由于字段類型不确定,是以需要裝箱。當傳入的名稱無法查找到該字段時,抛出exception。

/// <summary>
/// Pluck the specified fieldName.
/// 萃取某字段值,傳回一個數組
/// 由于字段類型不确定,是以需要裝箱
/// </summary>
/// <param name="fieldName">Field name.</param>
public object[] Pluck(string fieldName)
{
    Type t = typeof(T);
    object[] targetArray = new object[this.count];
    var field = t.GetField(fieldName);
    if(field == null)
        throw new Exception("沒有找到指定的field哦~,可能不叫" + fieldName);
    for(int i = 0; i < this.count; i++)
    {
        object value = field.GetValue(this.items[i]);
        targetArray[i] = value;
    }
    return targetArray;
}      

在EggArrayTest中實作Test_Pluck這個方法:

//擷取各個元素 字段id的值
void Test_Pluck()
{
    object[] testObj = testArray.Pluck("id");
    string testString = string.Empty;
    for(int i = 0; i < testObj.Length; i++)
    {
        testString = testObj[i].ToString();
        Debug.Log ("field value is " + testString);
    }
}

//輸出為0-9      
自己動手,實作一種類似List&lt;T&gt;的資料結構(二)

Shuffle:

傳回一個随機亂序的T[],下面看代碼

/// <summary>
/// Shuffle this instance.
/// 傳回一個随機亂序的副本
/// </summary>
public T[] Shuffle()
{
    T[] shuffled = new T[this.count];
    Random random = new Random();
    for (int index = 0, rand; index < this.count; index++) {
        rand = random.Next(index);
        if (rand != index) 
            shuffled[index] = shuffled[rand];
        shuffled[rand] = this.items[index];
    }
    return shuffled;
}      

在EggArrayTest中實作Test_Shuffle這個方法:

//
void Test_Shuffle()
{
    TargetClass[] test = testArray.Shuffle();
    for(int i = 0; i < test.Length; i++)
    {
        Debug.Log (test[i].name);
    }
}

//預設順序為NO. 0 ~ NO. 9
//亂序後,見圖      
自己動手,實作一種類似List&lt;T&gt;的資料結構(二)

好了,這周就到這裡~小匹夫最近也在趕項目的途中,是以測試和修改的精力也被消耗了很多。過完元旦之後,再繼續~

末了還是要說一聲:各位元旦快樂~

完整的代碼和測試可以在這裡擷取:https://github.com/chenjd/Unity3D_EggArray

裝模作樣的聲明一下:本博文章若非特殊注明皆為原創,若需轉載請保留原文連結及作者資訊慕容小匹夫

自己動手,實作一種類似List&lt;T&gt;的資料結構(二)

本作品采用知識共享署名-非商業性使用-相同方式共享 2.5 中國大陸許可協定進行許可,我的部落格歡迎複制共享,但在同時,希望保留我的署名權陳嘉棟(慕容小匹夫),并且,不得用于商業用途。如您有任何疑問或者授權方面的協商,請給我留言。

知乎專欄:

Runtime

聯系方式:

Email:[email protected]

繼續閱讀