thongkorn โพสต์ 2019-1-21 12:35:02

[VB.NET] การจัดอันดับ หรือ Ranking ด้วย Linq และ Dictionary Implement

http://www.g2gnet.com/webboard/images/vbnet/rankingdict.jpg

บทความนี้แอดมินไปเจอคำถามอยู่ใน StackOverFlow ตัวแอดมินกลับสนใจมันขึ้นมา ก็เลยลองนำมาคิดดู ... การจัดอันดับ (Ranking) ในที่นี้หมายถึงการที่เรารับชุดข้อมูลใดๆเข้ามา ซึ่งอาจจะไม่ใช่ DataBase ที่สามารถ Query และจัดเรียงข้อมูลก่อนนำมาแสดงผล แต่เป็นแหล่งข้อมูลจากที่อื่น เช่น XML หรือ JSON เพื่อทำการคำนวณหาว่าข้อมูลแต่ละชุด ที่อยู่ในแต่ละแถวนั้นอยู่อันดับที่เท่าไหร่ คำนวณได้ทั้งแบบมากไปหาน้อย หรือ น้อยไปหามาก ... โค้ดตัวอย่างแอดมินนำมาให้ได้ศึกษากัน 2 แบบ แบบแรกคือใช้ Linq ส่วนแบบที่ 2 จะใช้ Dictionary ขอให้ทุกๆท่านลองนำมาเปรียบเทียบดูกันเองล่ะกันครับ ... (หมายเหตุ: ที่ต้องเขียนคำอธิบายโค้ดเป็นภาษาอังกฤษ เพราะแอดมินนำไปโพสต์ที่เว็บต่างประเทศครับ) ...

โค้ด Linq Implement ...
' / --------------------------------------------------------------------------------
' / Developer : Mr.Surapon Yodsanga (Thongkorn Tubtimkrob)
' / eMail : thongkorn@hotmail.com
' / URL: http://www.g2gnet.com (Khon Kaen - Thailand)
' / Facebook: https://www.facebook.com/g2gnet (For Thailand)
' / Facebook: https://www.facebook.com/commonindy (Worldwide)
' / Purpose: How to make Ranking in DataGridView with out DataBase.
' / Microsoft Visual Basic .NET (2010)
' /
' / This is open source code under @CopyLeft by Thongkorn Tubtimkrob.
' / You can modify and/or distribute without to inform the developer.
' / --------------------------------------------------------------------------------
Public Class frmRankingDataGrid
    Dim dt As DataTable
    Dim MaxRow As Integer = 9

    Public Sub New()

      ' This call is required by the designer.
      InitializeComponent()

      ' Add any initialization after the InitializeComponent() call.
      Call SetupDGVData()
    End Sub

    ' / --------------------------------------------------------------------------------
    Private Sub btnPopulate_Click(sender As System.Object, e As System.EventArgs) Handles btnProcess.Click
      Call InitGrid()
      Call FillData()
    End Sub

    ' / --------------------------------------------------------------------------------
    '// Initialize DataGridView @Run Time
    Private Sub SetupDGVData()
      With dgvData
            .RowHeadersVisible = False
            .AllowUserToAddRows = False
            .AllowUserToDeleteRows = False
            .AllowUserToResizeRows = False
            .MultiSelect = True
            .SelectionMode = DataGridViewSelectionMode.FullRowSelect
            .ReadOnly = True
            .Font = New Font("Tahoma", 9)
            .RowHeadersVisible = True
            ' Autosize Column
            .AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill
            .AutoResizeColumns()
            '// Even-Odd Color
            .AlternatingRowsDefaultCellStyle.BackColor = Color.AliceBlue
            ' Adjust Header Styles
            With .ColumnHeadersDefaultCellStyle
                .BackColor = Color.Navy
                .ForeColor = Color.Black ' Color.White
                .Font = New Font("Tahoma", 9, FontStyle.Bold)
            End With
      End With
    End Sub

    Private Sub frmRankingDataGrid_FormClosed(sender As Object, e As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed
      Me.Dispose()
      Application.Exit()
    End Sub

    Private Sub frmRankingDataGrid_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
      Call InitGrid()
      Call FillData()
    End Sub

    ' / --------------------------------------------------------------------------------
    Private Sub InitGrid()
      dt = New DataTable
      dt.Columns.Add("A", GetType(Double))
      dt.Columns.Add("B", GetType(Double))
      dt.Columns.Add("C", GetType(Double))
      dt.Columns.Add("D", GetType(Double))
      dt.Columns.Add("E", GetType(Double))
      dt.Columns.Add("Average", GetType(Double))
      dt.Columns.Add("Dummy", GetType(Double)) '<-- Visible = False
      dt.Columns.Add("Row") '<-- Visible = False
      dt.Columns.Add("Ranking", GetType(Integer))
      dgvData.DataSource = dt
    End Sub

    ' / --------------------------------------------------------------------------------
    ' / Sample Data
    Private Sub FillData()
      Randomize()
      Dim RandomClass As New Random()
      For i As Integer = 0 To MaxRow
            Dim dr As DataRow = dt.NewRow()
            '// RandomClass.NextDouble(). A double-precision floating point number that is greater than or equal to 0.0, and less than 1.0.
            dr(0) = RandomClass.Next(0, 99) + Format(RandomClass.NextDouble(), "0.0")
            dr(1) = RandomClass.Next(0, 99) + Format(RandomClass.NextDouble(), "0.0")
            dr(2) = RandomClass.Next(0, 99) + Format(RandomClass.NextDouble(), "0.0")
            dr(3) = RandomClass.Next(0, 99) + Format(RandomClass.NextDouble(), "0.0")
            dr(4) = RandomClass.Next(0, 99) + Format(RandomClass.NextDouble(), "0.0")
            Dim SumCol As Double = 0
            For iCol As Integer = 0 To 4 'MaxCol
                SumCol = SumCol + dr(iCol)
            Next
            dr(5) = Format(SumCol / 5, "0.00")
            dgvData.DataSource = dt
            '//
            dt.Rows.Add(dr)
      Next
      Try
            '// Create Dictionary with double keys.
            Dim dict As New Dictionary(Of Double, Integer)
            For i As Integer = 0 To MaxRow
                '// Column index = 6 is dummy to get double value.
                '// Prevent have some duplicate key.
                '// Example:
                '// dgvData.Item(5, i).Value = 44.1 ... but
                '// CovertDoubleFloat(44.1) = 44.099998474121094
                dgvData.Item(6, i).Value = CovertDoubleFloat(dgvData.Item(5, i).Value).ToString("G15")
                dict.Add(dgvData.Item(6, i).Value, i)
            Next
            ' Get list of keys.
            Dim keys As List(Of Double) = dict.Keys.ToList
            ' Sort the keys Accesending. (A -> Z)
            keys.Sort()
            ' Descending Z -> A
            keys.Reverse()
            ' Loop over the sorted keys.
            Dim dVal As Double
            Dim Count As Integer = 0
            For Each dVal In keys
                'MsgBox(dVal & " - " & dict.Item(dVal))
                dgvData.Item(6, Count).Value = dVal
                dgvData.Item(7, Count).Value = dict.Item(dVal)
                Count += 1
            Next
            '// Hidden column
            dgvData.Columns(6).Visible = False
            dgvData.Columns(7).Visible = False
            '// Change position for Ranking.
            For iSource As Integer = 0 To MaxRow
                For iDest As Integer = 0 To MaxRow
                  If dgvData.Item(7, iSource).Value = iDest Then
                        dgvData.Item(8, iDest).Value = iSource + 1
                        Exit For
                  End If
                Next
            Next

      Catch ex As Exception
            MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning)
      End Try
    End Sub

    ' / --------------------------------------------------------------------------------
    '// https://msdn.microsoft.com/en-us/library/kc01y017(v=vs.110).aspx
    Public Function CovertDoubleFloat(ByVal doubleVal As Double) As Double
      Dim singleVal As Single = 0
      ' Double to Single conversion cannot overflow.
      singleVal = System.Convert.ToSingle(doubleVal)
      ' Conversion from Single to Double cannot overflow.
      CovertDoubleFloat = System.Convert.ToDouble(singleVal)
    End Function

    Private Sub btnExit_Click(sender As System.Object, e As System.EventArgs) Handles btnExit.Click
      Me.Close()
    End Sub

End Class
โค้ด Dictionary Implement ...
' / --------------------------------------------------------------------------------
' / Developer : Mr.Surapon Yodsanga (Thongkorn Tubtimkrob)
' / eMail : thongkorn@hotmail.com
' / URL: http://www.g2gnet.com (Khon Kaen - Thailand)
' / Facebook: https://www.facebook.com/g2gnet (For Thailand)
' / Facebook: https://www.facebook.com/commonindy (Worldwide)
' / Purpose: How to make Ranking in DataGridView with Dictionary.
' / Microsoft Visual Basic .NET (2010)
' /
' / This is open source code under @CopyLeft by Thongkorn Tubtimkrob.
' / You can modify and/or distribute without to inform the developer.
' / --------------------------------------------------------------------------------
Public Class frmRankingDictionary
    Dim dt As DataTable
    Dim MaxRow As Integer = 9

    ' / --------------------------------------------------------------------------------
    Private Sub btnPopulate_Click(sender As System.Object, e As System.EventArgs) Handles btnProcess.Click
      Call InitGrid()
      Call FillData()
    End Sub

    ' / --------------------------------------------------------------------------------
    '// Initialize DataGridView @Run Time
    Private Sub SetupDGVData()
      With dgvData
            .RowHeadersVisible = False
            .AllowUserToAddRows = False
            .AllowUserToDeleteRows = False
            .AllowUserToResizeRows = False
            .MultiSelect = True
            .SelectionMode = DataGridViewSelectionMode.FullRowSelect
            .ReadOnly = True
            .Font = New Font("Tahoma", 9)
            ' Autosize Column
            .AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill
            .AutoResizeColumns()
            '// Even-Odd Color
            .AlternatingRowsDefaultCellStyle.BackColor = Color.AliceBlue
            ' Adjust Header Styles
            With .ColumnHeadersDefaultCellStyle
                .BackColor = Color.Navy
                .ForeColor = Color.Black ' Color.White
                .Font = New Font("Tahoma", 9, FontStyle.Bold)
            End With
      End With
    End Sub

    ' / --------------------------------------------------------------------------------
    ' / Sample Data
    Private Sub FillData()
      Randomize()
      Dim RandomClass As New Random()
      For i As Integer = 0 To MaxRow
            Dim dr As DataRow = dt.NewRow()
            dr(0) = i + 1
            '// RandomClass.Next(0, 99) Random integer value between 0 - 99.
            '// RandomClass.NextDouble(). A double-precision floating point number that is greater than or equal to 0.0, and less than 1.0.
            dr(1) = RandomClass.Next(0, 99) + Format(RandomClass.NextDouble(), "0.0")
            dr(2) = RandomClass.Next(0, 99) + Format(RandomClass.NextDouble(), "0.0")
            dr(3) = RandomClass.Next(0, 99) + Format(RandomClass.NextDouble(), "0.0")
            dr(4) = RandomClass.Next(0, 99) + Format(RandomClass.NextDouble(), "0.0")
            Dim SumCol As Double = 0
            For iCol As Integer = 1 To 4 'MaxCol
                SumCol = SumCol + dr(iCol)
            Next
            dr(5) = Format(SumCol / 4, "0.00")
            dgvData.DataSource = dt
            '//
            dt.Rows.Add(dr)
      Next
      Try
            '// Dictionary. This collection allows fast key lookups. A generic type, it can use any types for its keys and values.
            '// Important: Dictionary can't have the duplicate key.
            '//
            '// Create Dictionary with double keys.
            Dim dict As New Dictionary(Of Double, Integer)
            '// Key and Value Pairs for Dummies.
            For i As Integer = 0 To MaxRow
                '// Column index = 6 is dummy to get double value.
                dgvData.Item(6, i).Value = dgvData.Item(5, i).Value
                '// Key, Value
                dict.Add(dgvData.Item(6, i).Value, i)
            Next
            '/ Get list of keys.
            Dim keys As List(Of Double) = dict.Keys.ToList
            '/ Sort the keys ascending. (A -> Z)
            keys.Sort()
            '/ Descending Z -> A
            keys.Reverse()
            '//
            Dim dVal As Double
            Dim Count As Integer = 0
            '/ Loop over the sorted keys.
            For Each dVal In keys
                dgvData.Item(6, Count).Value = dVal
                dgvData.Item(7, Count).Value = dict.Item(dVal)
                Count += 1
            Next
            '// Hidden column
            dgvData.Columns(6).Visible = False
            dgvData.Columns(7).Visible = False
            '// Change position for Ranking.
            For iSource As Integer = 0 To MaxRow
                For iDest As Integer = 0 To MaxRow
                  If dgvData.Item(7, iSource).Value = iDest Then
                        dgvData.Item(8, iDest).Value = iSource + 1
                        Exit For
                  End If
                Next
            Next

      Catch ex As Exception
            MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning)
      End Try
    End Sub

    Private Sub frmRankingDictionary_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
      Call SetupDGVData()
      Call InitGrid()
      Call FillData()
    End Sub

    Private Sub InitGrid()
      '//
      dt = New DataTable
      dt.Columns.Add("#Row", GetType(String))
      '// Define input with integer type.
      dt.Columns.Add("A", GetType(Double))
      dt.Columns.Add("B", GetType(Double))
      dt.Columns.Add("C", GetType(Double))
      dt.Columns.Add("D", GetType(Double))
      dt.Columns.Add("Average", GetType(Double))
      dt.Columns.Add("Dummies", GetType(Double)) '<-- Visible = False
      dt.Columns.Add("Row") '<-- Visible = False
      dt.Columns.Add("Ranking", GetType(Integer))
      dgvData.DataSource = dt
    End Sub

    Private Sub frmRankingDictionary_FormClosed(sender As Object, e As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed
      Me.Dispose()
      Application.Exit()
    End Sub

    Private Sub btnExit_Click(sender As System.Object, e As System.EventArgs) Handles btnExit.Click
      Me.Close()
    End Sub

End Class
ดาวน์โหลดโค้ดฉบับเต็ม VB.NET (2010) ได้ที่นี่ ...

anuyut1995 โพสต์ 2019-1-21 13:07:06

Admin ครับ รบกวนถามหน่อยครับ พอดีจะ ศึกษาเรื่อง Vb2010 With Excel พอมีตัวอย่างให้ศึกษาไหมครับ
ขอบคุณร่วงหน้าครับ

thongkorn โพสต์ 2019-1-21 13:43:53

anuyut1995 ตอบกลับเมื่อ 2019-1-21 13:07
Admin ครับ รบกวนถามหน่อยครับ พอดีจะ ศึกษาเรื่อง Vb2010 With Ex ...

การนำไฟล์ MS Excel มาทำงานเป็นเหมือนฐานข้อมูล (DataBase Excel) ...

anuyut1995 โพสต์ 2019-1-21 15:19:52

Admin ครับ ขอสอบถามอีก นิดดดดด นึงครับ แบบที่ Admin ส่งมาให้ เป็นการ เชื่อมต่อกับ เพิ่ม ขอรบกวน การ ลบ อีกหน่อยได้ไหมครับ
ขอบคุณร่วงหน้าครับ ขอบคุณความรู้ดีๆนะครับ

puklit โพสต์ 2019-1-21 15:51:42

แก้ไขครั้งสุดท้ายโดย puklit เมื่อ 2019-1-21 16:01

anuyut1995 ตอบกลับเมื่อ 2019-1-21 15:19
Admin ครับ ขอสอบถามอีก นิดดดดด นึงครับ แบบที่ Admin ส่งมาใ ...
ท่านลองประยุกต์ดูครับ
โค้ดของอาจารย์ ทำเชื่อมต่อไว้ให้แล้ว มีตัวอย่าง Insert แล้ว
เหลือแค่ท่านลองแค่เปลี่ยน คำสั่ง SQL command เท่านั้น

DELETE FROM WHERE condition
หรือถ้าต้องการจัดการข้อมูลใน Sheet (Range / Cell value / Formular / Chart)
ผมได้ทำตัวอย่างไว้แล้วที่
การ Export ข้อมูลจาก Datagridview ไป Excel file (แบบใส่ไข่)
http://www.g2gnet.com/webboard/forum.php?mod=viewthread&tid=80&extra=page%3D3

anuyut1995 โพสต์ 2019-1-21 16:16:01

ผมลองมาหลายวันแล้วก็ยังไม่ได้นะครับ ติดเริ่องการลบ พาชี้ทางให้เด็กเดินหน่อยได้ไหมครับ

puklit โพสต์ 2019-1-21 17:01:57

anuyut1995 ตอบกลับเมื่อ 2019-1-21 16:16
ผมลองมาหลายวันแล้วก็ยังไม่ได้นะครับ ติดเริ่องการลบ พาชี้ทางให้เด็กเดินหน่อยได้ไหมครับ

ก่อนอื่น ผมต้องขออภัยครับ ที่ไม่ได้หาข้อมูลเพิ่มก่อนตอบ
เบื้องต้นสรุปคร่าว ๆ คือ การเชื่อมต่อไฟล์ Excel ผ่านทาง OLEDBสามารถทำได้เพียง INSERT & UPDATE เท่านั้นไม่สามารถ DELETE ได้



หากต้องการลบแถวต้องวิธีทำผ่านทาง Library Excel interop แทน

anuyut1995 โพสต์ 2019-1-22 07:58:40

พอมีวิธีอื่น บ้างไหมครับ ช่วยแนะนำหน่อยครับ

puklit โพสต์ 2019-1-22 10:02:38

แก้ไขครั้งสุดท้ายโดย puklit เมื่อ 2019-1-22 10:08

anuyut1995 ตอบกลับเมื่อ 2019-1-22 07:58
พอมีวิธีอื่น บ้างไหมครับ ช่วยแนะนำหน่อยครับ
ท่านลองไปดูเนื้อหาของ Library ดังต่อไปนี้ ผมให้ไป 2 ตัว ท่านสามารถเลือกใช้ตัวใดตัวหนึ่งก็ได้ สามารถเข้าถึงจัดการไฟล์ Excel ได้เหมือนกัน
Microsoft.Office.Interop.Excel หรือ EPPlus

การลบแถวโดยตรงเมื่อเข้าถึง Sheet ข้อมูล ผ่าน Library
1. แล้วก็ Loop หาข้อมูล พอเจอข้อมูลก็ลบแถวออก
2. หลังจากลบแถวออก สั่งบันทึกไฟล์อีกครั้ง ก่อนปิดไฟล์ Excel
(ข้อนี้ผมไม่รู้ว่า Performance จะช้าหรือเร็ว ขึ้นอยู่กับข้อมูล)

จากโค้ดข้างล่างนี้ผมลองทำตัวอย่างมาให้ดูในการหา Rows Index ของ Excel ผ่าน DataSet (ที่ได้ทำสำเนาข้อมูลจากไฟล์ Excel มาเก็บใน DataSet)
ท่านสามารถนำไปเขียนต่อยอด โดยการ Loop หรือ ใช้ LinQ (SQL WHERE Clause) ผ่าน DataSet เพื่อเก็บ Rows Index ของ Excel ที่ต้องการลบ
แล้วสั่งการลบผ่าน Library Microsoft.Office.Interop.Excel หรือ EPPlus

จากนี้ท่านคงเห็นแล้วนะครับ สามารถลบได้แต่ Performance สู้ Database โดยตรงไม่ได้ เพราะต้องทำขั้นตอนมากกว่า

ตัวอย่างโค้ดเป็นเพียงการ หา Rows Index จากไฟล์ Excel เท่านั้น
Dim Conn As New OleDbConnection
      Conn.ConnectionString = CreateConnString(MyPath(Application.StartupPath) + "Book1.xls")

      Dim strSQL As String = "SELECT * FROM "
      Dim DA = New OleDbDataAdapter(strSQL, Conn)

      Try
            Conn.Open()
            Dim DS = New DataSet
            DS.Clear()
            DA.Fill(DS, "MySheet")
            Conn.Close()

            '// Clear screen
            Call ClearScreen()

            Dim msg As String
            msg = String.Empty

            If Not DS.Tables("MySheet").Rows.Count = 0 Then

                For Each dr As DataRow In DS.Tables(0).Rows

                  msg += dr("MemberID").ToString() & " " _
                           & dr("Firstname").ToString() & " " _
                           & dr("Lastname").ToString() & " " _
                           & dr("MemberShip").ToString() & " " _
                           & DS.Tables(0).Rows.IndexOf(dr) + 2 _
                           & vbNewLine

                Next

                MessageBox.Show(msg)

            End If

      Catch ex As Exception
            MsgBox("Error: " & ex.Message)
            Conn.Close()
      End Try

ตัวอย่างโค้ดที่รันออกมา MessageBox ที่ผมวงสีเขียวคือ ค่า Rows Index ที่จะใช้อ้างอิงในการลบแถวผ่าน Library ที่ผมได้ให้ไปครับ


อย่างที่อาจารย์ได้บอกไว้ Excel มันก็สามารถทำฐานข้อมูลได้ แต่มันก็มีข้อจำกัดในการใช้งานเหมือนกัน
MS- Excel สามารถทำได้ในรูปแบบ MS-Word หรือ MS-Access แต่ไม่เก่งเท่า MS-Access ในด้านฐานข้อมูล สุดท้่ายอยู่ที่ท่านจะเลือกใช้อะไรในการเก็บ
ส่วนตัวผมใช้ MS-Excel ในการ Import/Export และสร้าง Report เท่านั้น ส่วนฐานข้อมูลก็ต้องยกหน้าที่ให้ MS-Access หรือ Database ตัวอื่น ๆ

anuyut1995 โพสต์ 2019-1-22 10:36:06

ขอบคุณครับสำหรับความรู้
หน้า: [1] 2
ดูในรูปแบบกติ: [VB.NET] การจัดอันดับ หรือ Ranking ด้วย Linq และ Dictionary Implement