【C#】IF與Dictionary的Mapping比較

問題

在寫程式中常遇到Mapping,而一開始我都是用IF、Switch等來比較,會發現程式碼非常都長且不優雅。寫了LeetCode後一些集合類型都需要用上。以下例子只是講解if與dictionary兩者比較,當然沒做太多的例子呈現,所以要用哪個方法請自行判斷,方法很多種但需要用在對的事情上 : )

IF與Dictionary比較案例

情境呢,我們就說說例如數字[1]轉國字[一]吧,當然這資料比較算少,比較沒有說服力。在現實世界裡,會遇到龐大資料,這裡我就不說用哪個集合方法比較好,而是遇到什麼樣的事情在做出相對應方法。主要想表達兩者優雅度。

Main

    static void Main(string[] args)
    {
        string res = string.Empty;
        int target = 9;

        Stopwatch sw = new Stopwatch();
        sw.Reset();
        sw.Start();
        res = IfSolution(target);
        sw.Stop();
        string IfSecond = sw.Elapsed.TotalMilliseconds.ToString();

        sw.Reset();
        sw.Start();
        res = DictionarySolution(target);
        sw.Stop();
        string DictionarySecond = sw.Elapsed.TotalMilliseconds.ToString();

        Console.WriteLine($"If總共花費{IfSecond}秒找到{target}({res})");
        Console.WriteLine($"Dictionary總共花費{DictionarySecond}秒找到{target}({res})");
        Console.Read();
    }

IfSolution

    public static string IfSolution(int num)
    {
        if (num == 0) return "零";
        if (num == 1) return "一";
        if (num == 2) return "二";
        if (num == 3) return "三";
        if (num == 4) return "四";
        if (num == 5) return "五";
        if (num == 6) return "六";
        if (num == 7) return "七";
        if (num == 8) return "八";
        if (num == 9) return "九";

        return string.Empty;
    }

DictionarySolution

    public static string DictionarySolution(int num)
    {
        var dict = new Dictionary<int, string>()
        {
             {0, "零"},{1, "一"},{2, "二"},{3, "三"},{4, "四"},
             {5, "五"},{6, "六"},{7, "七"},{8, "八"},{9, "九"}
        };

        string res = string.Empty;
        dict.TryGetValue(num, out res);

        return res;
    }

執行五次所花費時間

If總共花費0.2695秒找到9(九)
Dictionary總共花費0.3656秒找到9(九)

If總共花費0.3785秒找到9(九)
Dictionary總共花費0.2875秒找到9(九)

If總共花費0.1898秒找到9(九)
Dictionary總共花費0.2199秒找到9(九)

If總共花費0.2701秒找到9(九)
Dictionary總共花費0.3236秒找到9(九)

If總共花費0.2346秒找到9(九)
Dictionary總共花費0.1968秒找到9(九)

結論

兩者比較看來還是傾向使用Dictionary方法,因資料小看不出差異點,實驗數據看看即可。

但越大資料上Dictionary會比較好,在查找效率上是O(1),Dictionary內部結構上也是採用HashTable方式。

Step by step(2) .Net Web API 2 撰寫 LineBot(Webhook) (使用套件)

續上篇Step by step(1) .Net Web API 2 撰寫 LineBot(Webhook) (使用套件)

看過後應該會對LineBot應用技巧會有點感覺,這篇會著重實作開發技巧以及新增幾個Action等介紹。

另外,Line似乎要把商業用途與開發分更明確,目前已經沒有Line Business Center,Messaging API申請方式有所異動,但其實申請方法差不多,請到LineDevelopers申請。

文章概要

1. 事件類別
   1.1 Common Fields
   1.2 Event Type
   1.3 Message Event
   1.4 程式範例
2. 實作功能
   2.1 上傳圖片
   2.2 DatetimePickerAction
3. 結論
4. 參考網址

1. 事件類別

當一個事件類型(Type)的判斷,例如:使用者(User)發了一個對話(Message),這個對話可能是文字(Text)、可能是圖片(Image)。

從上面例子來看我們知道類型有User、Message、(Text or Image),有這些型態可以定義要做什麼事情。

簡而言之,就是分析使用者的Event,然後在從Event內的Type讓程式去做某些事情。

建議:可把這些Type變成列舉(Enum)來做判斷,程式碼更好閱讀。

1.1 Common Fields

user:使用者
group:群組
room:房間

1.2 Event Type

message:訊息事件 (此事件還有一層如1.3介紹)
follow:使用者加入機器人為好友事件
unfollow:使用者封鎖機器人為好友事件
join:機器人加入群組事件
leave:機器人離開群組事件
postback:使用者透過Template Message回應的事件
beacon:透過 LINE Beacon 所觸發的事件

補充:什麼是LINE Beacon?

1.3 Message Event

text:文字
image:圖片
video:影片
audio:聲音
location:位置資訊
sticker:貼圖

1.4 程式範例

如何知道Common Fields、Event Type、Message Event?

// 接收Json資料
string postData = Request.Content.ReadAsStringAsync().Result;
var ReceivedMessage = Utility.Parsing(postData);

// CommonFields
string CommonFieldsType = ReceivedMessage.events.FirstOrDefault().source.type;

// EventType
string EventType = ReceivedMessage.events.FirstOrDefault().type;

// MessageEvent
string MessageEventType = ReceivedMessage.events.FirstOrDefault().message.type;

假設使用者發出"test"文字訊息,接收Type程式範例如下:

string postData = Request.Content.ReadAsStringAsync().Result;
var ReceivedMessage = Utility.Parsing(postData);
Bot bot = new Bot(ChannelAccessToken);
string Lineid = ReceivedMessage.events.FirstOrDefault().source.userId;

string CommonFieldsType = ReceivedMessage.events.FirstOrDefault().source.type;
string EventType = ReceivedMessage.events.FirstOrDefault().type;
string MessageEventType = ReceivedMessage.events.FirstOrDefault().message.type;

bot.PushMessage(Lineid, $"CommonFields:{CommonFieldsType}");
bot.PushMessage(Lineid, $"EventType:{EventType}");
bot.PushMessage(Lineid, $"MessageEvent:{MessageEventType}");

2. 實作功能

上篇沒說到的一些實作,未來有新的實作或技巧都會更新上來。

2.1 上傳圖片

當使用者與Bot對話會有一個ContentID,再利用此ContentID反查使用者所發送的資訊。

在程式範例當中先抓取ContentID,接著接收圖片並上傳至空間,在發送圖片網址給使用者。

if(ReceivedMessage.events.FirstOrDefault().message.type =="image")
{
    string ContentID = ReceivedMessage.events.FirstOrDefault().message.id.ToString();
    // 取得使用者bytedata
    byte[] filebody = Utility.GetUserUploadedContent(ContentID, ChannelAccessToken);
    string filename = $"/image/{Guid.NewGuid()}.png";
    var path = HttpContext.Current.Request.MapPath(filename);
    File.WriteAllBytes(path, filebody);

    bot.PushMessage(Lineid, "您上傳圖片為下方:");
    bot.PushMessage(Lineid, new Uri($"https://{HttpContext.Current.Request.Url.Host}{filename}"));
}

2.2 DatetimePickerAction

讓使用者選擇時間。當使用者選擇日期後再從postback.Params去解析使用者選擇的時間,為Postback Event。

詳細格式一定要看Line Developers

節錄自Line Developers的Datetime picker action。

var Template = new List<TemplateActionBase>();

Template.Add(new DateTimePickerAction()
{
    label = "test", // 標籤文字
    mode = "date", // 有三個mode:date、time、datetime
    data = "Postbackdata", // Postback資料
    initial = "2017-09-28", // 時間初始值
    max = "2017-12-31", // 時間最大值
    min = "2017-01-01" // 時間最小值
});

var ButtonsTemplate = new ButtonsTemplate()
{
    thumbnailImageUrl = new Uri("https://pics.iclope.com/news/test-cigarette-electronique.jpg"),
    title = "標題",
    text = "內容文字",
    altText = "當不支援裝置的文字",
    actions = Template
};

// 接收Postback資料
if (ReceivedMessage.events.FirstOrDefault().type == "postback")
{
    //判斷data為"Postbackdata"
    if (ReceivedMessage.events.FirstOrDefault().postback.data == "Postbackdata")
    {
        // 使用者選擇時間
        string Date = ReceivedMessage.events.FirstOrDefault().postback.Params.date;
        bot.PushMessage(Lineid, $"您選擇日期為{Date}");
        return Ok();
    }
}

bot.PushMessage(Lineid, ButtonsTemplate);

3. 結論

LineBot功能越來越多,面對商業行為受到更大的挑戰,往後有新功能或技巧會上來更新 : )

往後程式碼會往Github放,目前先用直接下載的方式囉

Source Code

4. 參考網址

Line developers

Step by step(1) .Net Web API 2 撰寫 LineBot(Webhook) (使用套件)

先感謝董大偉(David)老師提供LineBot套件,在操作上得心應手,講解非常細心 : )

文章概要

1. 註冊LineBot
2. WebAPI設定
   2.1 前置作業
   2.2 Webhook服務空間設定(以Azure為例)
3. Webhook程式撰寫
   3.1 ReplyMessage
   3.2 PushMessage
   3.3 ButtonsTemplate
   3.4 CarouselTemplate
   3.5 ConfirmTemplate
   3.6 Channel Secret應用
4. 結論
5. 參考網址

1. 註冊LineBot

先到Line Business Center網站,登入您的Line帳號。

(Line Business Center 2017/09/21關閉服務,會用別的服務來代替,屆時在改變方法註冊)

我們在[帳號清單]底下建立一個Messaging API帳號,點選[開始使用Developer Trial],剩下資訊請自行設定。



在帳號清單底下可以看到剛剛建立的項目,點選[LINE@ MANAGER],剩下資料可自行設定,主要把[Messaging API設定]打開。


會警告說開啟Bot將會消失一對一的功能,本來就是要已開發Bot為主,設定上請跟以下圖片一樣,我們需要用Webhook傳訊。

在回到[帳號清單]可以發現有新的選項[Line Developers],點選,接著可以看到Webhook設定,那麼最主要[Channel Access Token],點選[ISSUE]將會產生新的一組,務必別外流,程式面需要用到該Token,當然更嚴謹需要用到[Channel Secret]之後一併說明。請先自行加入該Bot為好友。


2. WebAPI設定

2.1 前置作業

我是用VS2015示範,新增空白Web API,加入一個Controller(範例命名名稱為LineBotController),在專案屬性裡設定部分,把[ChannelAccessToken]與[ChannelSecret]值做設定,後面的值請自行更改。





在套件部分請在nuget安裝好。

2.2 Webhook服務空間設定(以Azure為例)

到這裡為止應該都是簡單的設定,很多人應該會卡在這項,Line為了安全性,Webhook需要HTTPS服務空間,在此範例我是用Azure免費的就夠測試了,等之後專案開發在去考慮服務空間問題。

至於哪裡還有提供HTTPS我給幾個選項:1. Ngrok 2. Letsencrypt

其中Ngrok比較容易實現,剩下可能再自行找找或自己有憑證先行架設好。

Azure範例:

為了測試Webhook回應成功,在LineBotController底下程式碼如下。

public class LineBotController : ApiController
{
    [HttpPost]
    public IHttpActionResult POST()
    {
        try
        {
            return Ok();
        }
        catch
        {
            return Ok();
        }
    }
}

Azure自行註冊好,先專案點選發行(如果想更新程式點選發行,等於上傳到Azure空間),剩下自行設定,不在描述,Azure已提供最簡單的方式來做發行 : ) 這邊如果講解不清楚可以去找找資料,Webhook網址應該會是https:{YourDomainName}/{api}/{ControllerName}。



接著,回到Line Developers後台,把Webhook服務網址貼上再點選[VERIFY],如果設定都無誤的話會是成功的狀態。

到目前為止都是設定,設定都好的話接下來就是Webhook程式撰寫 : )

3. Webhook程式撰寫

程式撰寫部分會先從簡單的開始,逐一做介紹,補充一下,您的LineId並非唯一,LineId可能在不同的Bot上所對應的ID不一定相同,這點需要注意,另外有些功能電腦版不支援,例如Template功能等。

例如:
UserA對於Bot A的LineId為A
UserA對於Bot B的LineId為B

3.1 ReplyMessage

這主要可以回覆使用者訊息,當使用者發出訊息時候利用接收到的ReplyToken來做回覆。

public class LineBotController : ApiController
{
    [HttpPost]
    public IHttpActionResult POST()
    {
        string ChannelAccessToken = Properties.Settings.Default.ChannelAccessToken;

        try
        {
            // 解析Json資料
            string postData = Request.Content.ReadAsStringAsync().Result;
            var ReceivedMessage = Utility.Parsing(postData);
            Bot bot = new Bot(ChannelAccessToken);

            string Lineid = ReceivedMessage.events.FirstOrDefault().source.userId;
            // 得到使用者資訊 (可以知道使用者狀態消息、暱稱等)
            var UserInfo = bot.GetUserInfo(Lineid);
            
            // 1.【ReplyMessage】
            string Message = "";
            Message = $"{UserInfo.displayName} 你好,你說了:{ReceivedMessage.events.FirstOrDefault().message.text}";
            Utility.ReplyMessage(ReceivedMessage.events.FirstOrDefault().replyToken, Message, ChannelAccessToken);

            return Ok();
        }
        catch
        {
            return Ok();
        }
    }
}

3.2 PushMessage

PushMessage非常簡單,只要知道對方LineId即可發出訊息,也可以發出貼圖訊息,如果要知道有那些貼圖可用,請參考此網站

public class LineBotController : ApiController
{
    [HttpPost]
    public IHttpActionResult POST()
    {
        string ChannelAccessToken = Properties.Settings.Default.ChannelAccessToken;

        try
        {
            // 解析Json資料
            string postData = Request.Content.ReadAsStringAsync().Result;
            var ReceivedMessage = Utility.Parsing(postData);
            Bot bot = new Bot(ChannelAccessToken);

            string Lineid = ReceivedMessage.events.FirstOrDefault().source.userId;
            // 得到使用者資訊 (可以知道使用者狀態消息、暱稱等)
            var UserInfo = bot.GetUserInfo(Lineid);
            
            // 2.【PushMessage】
            bot.PushMessage(Lineid, "PushMessage");
            bot.PushMessage(Lineid, 1, 113);

            return Ok();
        }
        catch
        {
            return Ok();
        }
    }
}

3.3 ButtonsTemplate

ButtonsTemplate呈現,主要讓使用者不用用輸入文字方式來進行動作,以點選Button方式進行動作。

注意:Template上選單的圖片網址需為HTTPS

這些動作會有URI、Postback、Message等,我先把這些方法寫成介面,程式碼可以更簡潔,等等看應用後會更清楚ButtonsTemplate再做什麼。

interface ITemplateAction
{
    void Message(string label, string text);
    void Uri(string label, string uri);
    void Postback(string label, string data, string text);
}
public class AddAction : ITemplateAction
{
    List<TemplateActionBase> templateactions = new List<TemplateActionBase>();

    public AddAction(List<TemplateActionBase> templateactions)
    {
        this.templateactions = templateactions;
    }

    /// <summary>
    /// 訊息
    /// </summary>
    /// <param name="label">按鈕文字</param>
    /// <param name="text">文字</param>
    public void Message(string label, string text)
    {
        templateactions.Add(new MessageActon()
        { label = label, text = text });
    }

    /// <summary>
    /// PostBack
    /// </summary>
    /// <param name="label">按鈕文字</param>
    /// <param name="data">PostBack Data</param>
    /// <param name="text">文字</param>
    public void Postback(string label, string data, string text)
    {
        templateactions.Add(new PostbackActon()
        { label = label, data = data, text = text == "" ? null : text });
    }

    /// <summary>
    /// URI
    /// </summary>
    /// <param name="label">標籤</param>
    /// <param name="uri">網址</param>
    public void Uri(string label, string uri)
    {
        templateactions.Add(new UriActon()
        { label = label, uri = new Uri(uri) });
    }
}

如此一來要增加Action更容易了,介紹一下Action所需用途。

Message:要使用者說出某些對話。
URI:可以連結該網址。
Postback:例如您想要在背後運行就要用此方式,當然跟Message也很像也可以發出一個對話,但通常我都拿來背後運行比較多,也就是說,使用者點選Postback的Action,後端會接送一串Postback文字,看這些文字要做什麼事情由您決定。

// 3.【ButtonsTemplate】
//接收PostBack資料
if (ReceivedMessage.events.FirstOrDefault().postback != null)
{
    string PostBack = ReceivedMessage.events.FirstOrDefault().postback.data;
    bot.PushMessage(Lineid, $"我收到你postback資料為{PostBack}");
}
else
{
    var Template = new List<TemplateActionBase>();
    AddAction action = new AddAction(Template);

    action.Message("文字標籤", "點選後發送的文字");
    action.Uri("連到Google", "https://www.google.com.tw");
    action.Postback("Postback1", "Postback1", null);
    action.Postback("Postback2", "Postback2", "使用者點選後所要呈現的文字");

    var ButtonsTemplate = new ButtonsTemplate()
    {
        thumbnailImageUrl = new Uri("https://pics.iclope.com/news/test-cigarette-electronique.jpg"),
        title = "標題",
        text = "內容文字",
        altText = "當電腦版看到的文字",
        actions = Template
    };

    // 發送ButtonsTemplate
    bot.PushMessage(Lineid, ButtonsTemplate);
}

3.4 CarouselTemplate

算是ButtonsTemplate延伸,其實就是多選單而已,Action都一樣。但值得注意的是,我在開發期間發現一個問題,也就是說選單規定是要同一列的,最多五個選單,但是列表的Action都要一致,否則會出錯。

例如:選單 * Action數量 其中Action數量要一致,選單不能大於五個。

// 4.【CarouselTemplate】
List<TemplateActionBase> ta1 = new List<TemplateActionBase>();
List<TemplateActionBase> ta2 = new List<TemplateActionBase>();
List<TemplateActionBase> ta3 = new List<TemplateActionBase>();

AddAction act1 = new AddAction(ta1);
AddAction act2 = new AddAction(ta2);
AddAction act3 = new AddAction(ta3);

//需要一致Action數量
act1.Message("Test1", "Test1");
act1.Message("Test2", "Test2");

act2.Message("Test3", "Test3");
act2.Message("Test4", "Test4");

act3.Message("Test5", "Test5");
act3.Message("Test6", "Test6");

var ct1 = new Column()
{
    title = "選單一",
    text = "選單一說明",
    thumbnailImageUrl = new Uri("https://pics.iclope.com/news/test-cigarette-electronique.jpg"),
    actions = ta1
};

var ct2 = new Column()
{
    title = "選單二",
    text = "選單二說明",
    thumbnailImageUrl = new Uri("https://pics.iclope.com/news/test-cigarette-electronique.jpg"),
    actions = ta2
};

var ct3 = new Column()
{
    title = "選單三",
    text = "選單三說明",
    thumbnailImageUrl = new Uri("https://pics.iclope.com/news/test-cigarette-electronique.jpg"),
    actions = ta3
};

List<Column> CarouselTemplateSub = new List<Column>();

CarouselTemplateSub.Add(ct1);
CarouselTemplateSub.Add(ct2);
CarouselTemplateSub.Add(ct3);

var CarouselTemplate = new CarouselTemplate()
{
    altText = "當電腦版看到的文字",
    columns = CarouselTemplateSub
};

bot.PushMessage(Lineid, CarouselTemplate);

3.5 ConfirmTemplate

像我們網頁上的確認視窗,有同意跟不同意選項,當然Action可以自己自定義,應用面很廣,很常用的Template。

// 5.【ConfirmTemplate】
if (ReceivedMessage.events.FirstOrDefault().postback != null)
{
    string PostBack = ReceivedMessage.events.FirstOrDefault().postback.data;
    bot.PushMessage(Lineid, $"{PostBack}");
}
else
{
    var ConfirmActon = new List<TemplateActionBase>();

    AddAction action = new AddAction(ConfirmActon);
    action.Postback("是", "您選擇是", null);
    action.Postback("不是", "您選擇不是", null);

    ConfirmTemplate ConfirmTemplate = new ConfirmTemplate();
    ConfirmTemplate.text = "內容文字";
    ConfirmTemplate.altText = "電腦版所看到文字";
    ConfirmTemplate.actions = ConfirmActon;

    bot.PushMessage(Lineid, ConfirmTemplate);
}

3.6 Channel Secret應用

我在這篇文章看到後才知道Channel Secret應用,雖然Line是HTTPS資料傳輸,也要防範CSRF攻擊,Line傳給Webhook資料時會在Header夾帶一個X-Line-Signature簽名,這個Signature是由Channel Secret作為私鑰,與Request Body進行HMAC-SHA256進行加密計算。只有開發人員能夠驗證這組X-Line-Signature是否有效。詳細可看Line Developer文件。

public class Signature : AuthorizeAttribute
{
    public override void OnAuthorization(HttpActionContext actionContext)
    {
        HttpRequestMessage Request = actionContext.Request;

        IEnumerable<string> headerValues;
        if (Request.Headers.TryGetValues("X-Line-Signature", out headerValues))
        {
            string lineSignature = headerValues.FirstOrDefault(),
                   reqBody = Request.Content.ReadAsStringAsync().Result;

            byte[] screct = Encoding.UTF8.GetBytes(Properties.Settings.Default.ChannelSecret),
                   body = Encoding.UTF8.GetBytes(reqBody),
                   hash = new HMACSHA256(screct).ComputeHash(body);
            string mySignature = Convert.ToBase64String(hash);

            if (mySignature == lineSignature) return;
        }
        HttpResponseMessage Response = Request.CreateResponse(HttpStatusCode.ExpectationFailed);
        Response.StatusCode = HttpStatusCode.InternalServerError;
        actionContext.Response = Response;
    }
}

自己定義好後Signature AuthorizeAttribute,只要在進入這Controller之前先檢查X-Line-Signature是否符合,如果不對會回應錯誤,反之,進入該Controller執行。

[HttpPost]
[Signature]
public IHttpActionResult POST()
{

}

4. 結論

此篇介紹是比較常見到的功能,當然還有更多功能還沒介紹,套件上董大偉老師一只有做更新,在開發上可以更多的應用。不過在開發上會有很多問題存在,例如最常見的如何知道使用者現在狀態是什麼? 例如處理問卷就會非常瑣碎,一來一往增加許多複雜度,必須記住使用者選項、狀態等,主要沒有網頁上的Session等幫你記住任何值,所有狀態、命令等都先存在資料庫,所以越多層的問答會非常麻煩,必須一層一層的拆,盡量依簡單方式來進行。ChatBot非常熱門,應用面很廣,甚至可以請假、點餐、消費等方式,算是一個溝通介接的窗口,相信往後ChatBot發展會更深入。

當然最重要的是Source Code可以下載。

5. 參考網址

LINE Developer 官方文件
.NET Walker
一步一步用 .NET Web API 撰寫 LINE Webhook (LINEBot)

【ASP.NET】公司內部寄信不能到外部問題(Exchange WebServices API 寄信)

首先先感謝某個重要的人囉,讓我了解有Exchange WebServices API這東西 : )

問題

想要從程式寄信(SMTP)至外部信箱都會出現錯誤(內部寄信可以成功),一只Reply不成功,用了老半天才知道系統權限問題,系統管理者也不可能給我權限。思考著同個SMTP Server為什麼我Outlook卻可以寄到外部信箱,而SMTP方法卻不行,接著找關鍵字,才知道公司的Outlook是用Exchange Server來處理,到Nuget找到了Exchange WebServices API,發現功能滿強大的,其實就是把我們平常Outlook操作變程式化,這裡我只有用到寄信的方法,其它方法這裡不做多介紹。

Exchange WebServices寄信方法

先到Nuget找到相關套件。

裝了套件之後就是來寫寄信程式囉,不難,我寫成一個Class,可自行更改需要的參數。

using System.Net;
using Microsoft.Exchange.WebServices.Data;

    public class SendmailByOutLook
    {
        /// <summary>
        /// Exchange寄信
        /// </summary>
        /// <param name="MailUser">Windows帳號</param>
        /// <param name="MailPasswords">Windows密碼</param>
        /// <param name="MailRecipient">收件人</param>
        /// <param name="MailSubject">信件標題</param>
        /// <param name="MailMain">信件內容</param>
        /// <param name="MailFrom">寄件人自定義名稱(需要有該帳號權限)</param>
        public void SendMail(string MailUser, string MailPasswords, string MailRecipient, 
        string MailSubject, string MailMain, string MailFrom)
        {
            ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010_SP1);
            //Exchange Server 網域(AD)帳號密碼
            service.Credentials = new NetworkCredential(MailUser, MailPasswords);
            service.AutodiscoverUrl(MailFrom);

            EmailMessage emailMessage = new EmailMessage(service);
            
            //下行可以刪除,會以你登入網域帳號(AD)對應到寄件人
            emailMessage.From = MailFrom; 
            //目前參數寫單一收件者,可自行更改成List等方式一次寄多人
            emailMessage.ToRecipients.Add(MailRecipient); 
            emailMessage.Subject = MailSubject;
            emailMessage.Body = new MessageBody(MailMain);

            //Send
            emailMessage.SendAndSaveCopy();
        }
    }

當然引用方法很簡單,自行試試看囉。

SendmailByOutLook sbo = new SendmailByOutLook();

sbo.SendMail(MailUser, MailPasswords,MailRecipient, MailSubject, MailMain, MailFrom);

參考網址

Working with the EWS Managed API 2.0

Exchange Server Developer

【LeetCode】#189 Rotate Array(C#)

● 問題

Rotate an array of n elements to the right by k steps.

For example, with n = 7 and k = 3, the array [1,2,3,4,5,6,7] is rotated to [5,6,7,1,2,3,4].

Note:
Try to come up as many solutions as you can, there are at least 3 different ways to solve this problem.


● 思考點

k是多少就向右旋轉多少次,後來找到資料可以用Array的Reverse方法,可以更加速理解(效能也比較好)。


● Solution

public class Solution {
    public void Rotate(int[] nums, int k) {
        k = k % nums.Length;
        Array.Reverse(nums);

        Array.Reverse(nums, 0, k);
        Array.Reverse(nums, k, nums.Length - k);
    }
}

【LeetCode】#172 Factorial Trailing Zeroes(C#)

● 問題

Given an integer n, return the number of trailing zeroes in n!.

Note: Your solution should be in logarithmic time complexity.


● 思考點

求出n!共出現幾個0,時間複雜度需要考慮進去O(logn),這題主要找出5出現幾次(因任一偶數乘以5將會出現0)。


● Solution

public class Solution {
    public int TrailingZeroes(int n) {
        
        int Result = 0;
        
        while(n >= 5)
        {
            n /= 5;
            Result += n;
        }
        
        return Result;
    }
}

【LeetCode】#171 Excel Sheet Column Number(C#)

● 問題

Given a column title as appear in an Excel sheet, return its corresponding column number.

Example:
A -> 1
B -> 2
C -> 3
...
Z -> 26
AA -> 27
AB -> 28


● 思考點

先找出ASCII的基準點'A',接下來我們知道A-Z總共26個字母,表示26進位轉換。

Example1:
AB:26*1(A) + 2(B) = 28
BC:26*2(B) + 3(C) = 55
ABC:(26 * 1(A) = 26)  ([26 + 2(B)] * 26 = 728) (728 + 3(C) = 731)      
Example2:
AB:26*1(A) + 1*2(B) = 28
BC:26*2(B) + 1*3(C) = 55
ABC:676*1(A) + 26*2(B) + 1*3(C) = 731      


● Solution

(1)

public class Solution {
    public int TitleToNumber(string s) {
        
        int result = 0;
        
        for(int i = 0; i < s.Length; i++)
        {
            result = result * 26 + (s[i] - 'A' + 1);
        }
        
        return result;
    }
}

(2)

public class Solution {
    public int TitleToNumber(string s)
    {
        int result = 0;
        int exp = s.Length - 1;

        for (int i = 0; i < s.Length; i++)
        {
            double tmp = (s[i] - 'A' + 1);
            tmp = tmp * Math.Pow(26, exp--);
            result += Convert.ToInt32(tmp);
        }

        return result;
    }
}

【LeetCode】#168 Excel Sheet Column Title(C#)

● 問題

Given a positive integer, return its corresponding column title as appear in an Excel sheet.


● 思考點

要先了解ASCII所對應的字元,而我們不可能預先知道A的ASCII,所以把A當作基準點,接著轉換成ASCII比較妥當。


● Solution

public class Solution {
    public string ConvertToTitle(int n) {
    
        var  result = "";
        
        while(n > 0)
        {
            n--;
            result = Convert.ToChar((n % 26) + 'A') + result;
            n /= 26;
        }
        
        return result;
    }
}

設計模式基本原則

● 基本原則(1-5又稱SOLID)

  1. 單一職責原則 (Single Responsibility Principle)

    一個類別(Class),應該只有一個引起它變化的原因。建議介面一定要做到單職責。

  2. 開放、封閉原則 (OCP:Open Closed Principle)

    對於擴展是開放的 (Open for extension)
    對於修改是封閉的 (Closed for modification)

  3. 里氏(Liskov)代換原則 (LSP:Liskov Substitution Principle)

    子類別必須能替換父類別。
    只要父類能出現的地方子類就可以出現,而且替換為子類也不會產生任何錯誤或異常,使用者可能根本就不需要知道是父類還是子類。但是,反過來就不行,有子類出現的地方,父類未必就能適應。

  4. 介面隔離原則 (ISP:Interface Segregation Principle)

    客戶端(Client)不應該依賴它不需要的接口。
    類別間的依賴關係應該建立在最小的接口上。

  5. 依賴倒轉原則 (DIP:Dependency Inversion Principle)

    高階模組不應該依賴於低階模組,兩者都該依賴抽象。
    抽象不應該依賴於具體實作方式。
    具體實作方式則應該依賴抽象。

  6. 迪米特法則 (LoD:Law of Demeter)也稱為最少知識原則(Least Knowledge Principle,LKP)

    最少知識原則 Principle of Least Knowledge。

    【舉例】
    一個類應該對自己需要耦合或調用的類知道得最少,你(被耦合或調用的類)的內部是如何復雜都和我沒關係,那是你的事情,我就知道你提供的這麼多public方法,我就調用這麼多,其他的我一概不關心。

  7. 合成/聚合重覆使用原則 (CARP)(Composite/Aggregate Reuse Principle)

    多用合成/聚合,少用繼承。
    在兩個物件有 has-a (has-parts、is-part-of)關係時 => 合成/聚合 (A has a B)
    當兩個物件有 is-a (is-a-kind-of)關係時 => 繼承 (Superman is a kind of Person)
    合成 (Composite):A、B兩物件有合成關係時,表示其中一個物件消失(ex:書本),另一個物件也會消失(ex:章節)。
    聚合 (Aggregate):A、B兩物件有聚合關係時,表示其中一個物件消失(ex:球隊),另一個物件不會消失(ex:球員)。

● 設計模式(分三大類)

一、 創建型設計模式 (Creational Design Patterns)

1. 簡單工廠模式 (Simple Factory Pattern)
2. 工廠方法模式 (Factory Method Pattern)
3. 抽象工廠模式 (Abstract Factory Pattern)
4. 建造者模式(生成器模式) (Builder Pattern)
5. 原型模式 (Prototype Pattern)
6. 單例模式 (Singleton Pattern)

二、 結構型設計模式 (Structural Design Patterns)

1. 適配器模式 (Adapter Pattern)
2. 橋接模式 (Bridge Pattern)
3. 組合模式 (Composite Pattern)
4. 裝飾模式 (Decorator Pattern)
5. 外觀模式 (Facade Pattern)
6. 享元模式 (Flyweight Pattern)
7. 代理模式 (Proxy Pattern)

三、 行為型設計模式 (Behavioral Design Patterns)

1. 責任鏈模式 (Chain-of-responsibility Pattern)
2. 命令模式 (Command Pattern)
3. 解釋器模式 (Interpreter Pattern)
4. 迭代器模式 (Iterator Pattern)
5. 中介者模式 (Mediator Pattern)
6. 備忘錄模式 (Memento Pattern)
7. 觀察者模式 (發佈/訂閱模式) (Observer Pattern)
8. 狀態模式 (State Pattern)
9. 策略模式 (Strategy Pattern)
10. 模板方法模式 (Template Method Pattern)
11. 訪問者模式 (Visitor Pattern)

【LeetCode】#136 Single Number(C#)

● 問題

Given an array of integers, every element appears twice except for one. Find that single one.

Note:
Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory?

● 思考點

在這陣列裡的數字都是成雙成對的,只有一個數字是出現一次的,用最好的計算時間找出該數字。
我一開始想到的是List或Dictionary方式來做,應該會採取Dictionary方式過濾。
找些資料後發現這題最好的方法是用XOR,也就是說重覆數字會變0,最後結果為出現一次的數字。

● Solution

【Dictionary】

public class Solution {
    public int SingleNumber(int[] nums) {
        
        var dict = new Dictionary<int, int>();
        
        for (var i = 0; i < nums.Length; i++)
        {
            if (dict.ContainsKey(nums[i]))
            {
                //重覆刪除
                dict.Remove(nums[i]);
            }
            else
            {
                dict.Add(nums[i], nums[i]);
            }
        }
        
        return dict.ElementAt(0).Value;
    }
}

【XOR】

public class Solution {
    public int SingleNumber(int[] nums) {
        
        var result = 0;
        
        for (var i = 0; i < nums.Length; i++)
        {
            //XOR重覆數字為0
            result ^= nums[i];
        }
        
        return result;
    }
}