Alqoritm

DFS alqoritminin real proyekt üzərində tətbiqi

Birbaşa mətləbə keçəcəm. Deməli, iş yoldaşıma proyektdə belə bir şey lazım oldu; web səhifəsi var idi, həmin  web səhifənin menusunu admin panelində aydın və anlaşıqlı şəkildə göstərmək lazım idi ki, yeni menu/alt-menu əlavə etmək, mövcud menunu silmək yaxud menularda dəyişikliklər etmək daha rahat olsun. Və bu məsələ üçün optimal həll variantı fikirləşmək lazım idi.

Bu məsələni, həmin web səhifə əvəzinə öz blogumdan nümunə verməklə göstərəcəm və kodlaşdırılmasına da bu nümunə üzərindən davam edəcəyik.

Deməli mənim blogumun menusu təxminən belədir:

Blog Menu

Və indi bizə lazımdır ki, menu admin paneldə aşağıdakı formada görünsün:

MENU
    1 Home
    2 Certification Exam
        2.1 Exam preparation: step by step
        2.2 Exam Topics
        2.3 Exam Experience
    3 Sample questions
        3.1 Oracle
            3.1.1 1Z0-803 (OCA 7)
            3.1.2 1Z0-804 (OCP 7)
            3.1.3 1Z0-805 (Upgrade to Java SE 7)
            3.1.4 1Z0-808 (OCA 8)
            3.1.5 1Z0-809 (OCP 8)
            3.1.6 1Z0-810 (Upgrade SE 7 to 8 OCP)
            3.1.7 1Z0-813 (Upgrade to SE 8 OCP)
    4 My Books
        4.1 My Certification Notes
    5 Articles
    6 News
    7 Mix
    8 Feedbacks
    9 About

Menu ilə bağlı məlumatlar verilənlər bazasında aşağıdakı formada saxlanılır:

CREATE TABLE IF NOT EXISTS `menu` (
  `id` int(11) NOT NULL,
  `parent_id` int(11) NOT NULL,
  `name` varchar(50) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
id parent_id name
0 -1 MENU
1 0 Home
2 0 Certification Exam
3 0 Sample questions
4 0 My Books
5 0 Articles
6 0 News
7 0 Mix
8 0 Feedbacks
9 0 About
10 2 Exam preparation: step by step
11 2 Exam Topics
12 2 Exam Experience
13 3 Oracle
14 13 1Z0-803 (OCA 7)
15 13 1Z0-804 (OCP 7)
16 13 1Z0-805 (Upgrade to Java SE 7)
17 13 1Z0-808 (OCA 8)
18 13 1Z0-809 (OCP 8)
19 13 1Z0-810 (Upgrade SE 7 to 8 OCP)
20 13 1Z0-813 (Upgrade to SE 8 OCP)
21 4 My Certification Notes

Menu və alt-menuların sayı dəyişkəndir, hər vaxt artıb-azala bilər. Hər yeni alt-menu əlavə edildikcə, parent_id kimi həmin alt-menunun aid olduğu menunun id dəyəri əlavə edilir. id dəyəri isə hər menu üçün unikaldır, təkrarlanmır. Ona görə də dinamik bir həll üsulu tapmaq lazım idi. Biz buna DFS alqoritmini tətbiq etdik (“Preorder Tree Walk” versiyası da ağlıma gəldi, amma onu araşdırmadım, kimin başqa maraqlı həll variantı olsa qeyd edə bilər). DFS alqoritmi ilə bağlı detallara çox getməyəcəm, sadəcə kod üzərində tətbiqini göstərməyə çalışacam. Qısaca DFS (Depth-First-Search) alqoritmi qraflar üzərində ən çox istifadə edilən axtarış alqoritmlərindən biridir:

DFS

Biz bu alqoritm vasitəsilə menunun alt menularını axtaracağıq.

Sual yarana bilər ki, bəs bu menunun qrafla nə əlaqəsi var yaxud necə əlaqələndirəcəyik? Biz əgər məlumat bazasında mövcud olan menumuzu qraf üzərində təsvir etsək aşağıdakı mənzərə alınacaq:

Blog Menu Graph

Amma problemi qraf şəklinə salıb həll etmək istəyiriksə, dəyərləri saxlamaq üçün qraflara uyğun data strukturlardan istifadə etməliyik. Adətən iki cür data struktur istifadə edilir:

  • Adjacency Matrix
  • Adjacency List

Biz “adjacency list” istifadə edəcəyik, amma bir az daha optimallaşdırılmış formada. Adjacency list üçün adətən massiv istifadə edilir, massivin elementləri LinkedList`lərdən ibarət olur. Amma bizdə məlumat daha dinamikdir, məlumat bazasında id sütununun dəyəri ardıcıl artmaya da bilər yaxud hər hansı id silindikdə fərqli mənzərə yarana bilər. Yəni, qısaca desək id`nin dəyəri menunun sayından bir neçə dəfə böyük bir ədəd də ola bilər. Bu baxımdan massiv istifadə etmək optimal deyil, əvəzində Map`lərdən istifadə edəcəyik. Kod üzərindən baxdıqda daha aydın olacaq.

Proyektin strukturu:

project structure - display website menu

 

Main.java

package az.mm.displaymenu;

import java.util.List;
import java.util.Map;

/**
 *
 * @author MM <[email protected]>
 */
public class Main {
    
    private final Map<Integer, List<Submenu>> adjMap;

    public Main() {
        adjMap = DBConnection.getMenu();
    }

    public static void main(String[] args) {
        Main m = new Main();
        m.displayMenu(0, "", "", "MENU");
    }
    
    void displayMenu(int pId, String padding, String rowNum, String name) {
        System.out.printf("%s%s %s%n", padding, rowNum, name);
        int row = 1;
        if(adjMap.get(pId) != null)
            for (Submenu s: adjMap.get(pId)) 
                if (!s.isVisited()) {
                    s.setVisited(true);
                    String newPadding = padding + "\t";
                    String newRowNum = "".equals(rowNum) ? String.valueOf(row++) : (rowNum + "." + row++);
                    displayMenu(s.to(), newPadding, newRowNum, s.name());
                }
    }
}

 

DBConnection.java

package az.mm.displaymenu;

import java.io.*;
import java.sql.*;
import java.util.*;

/**
 *
 * @author MM <[email protected]>
 */
public class DBConnection {
    
    static {
        createMenu();
    }

    private static Connection getDBConnection() {
        Connection connection = null;
        try {
            DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/?user=root&password=root&serverTimezone=UTC");
        } catch (SQLException e) {
            System.err.println(e);
        }

        return connection;
    }

    private static void createMenu(){
        try (Connection connection = getDBConnection();
             BufferedReader br = new BufferedReader(new FileReader("src/main/resources/create.sql")); ) {

            String str;
            StringBuilder sb = new StringBuilder();
            while ((str = br.readLine()) != null) 
                sb.append(str);
            
            for(String sql: sb.toString().split(";"))
                try(PreparedStatement preparedStatement = connection.prepareStatement(sql);){
                    preparedStatement.execute();
                }

        } catch (Exception e) {
            System.err.println(e);
        }
    }

    public static Map<Integer, List<Submenu>> getMenu() {
        Map<Integer, List<Submenu>> adjMap = new LinkedHashMap();
        String sql = "Select * from test.menu;";

        try (Connection connection = getDBConnection();
                PreparedStatement preparedStatement = connection.prepareStatement(sql);
                ResultSet rs = preparedStatement.executeQuery();) {

            while (rs.next()) {
                Submenu s = new Submenu(rs.getInt("parent_id"), rs.getInt("id"), rs.getString("name"));
                if (adjMap.containsKey(s.from())) 
                    adjMap.get(s.from()).add(s);
                else {
                    List<Submenu> menuList = new LinkedList();
                    menuList.add(s);
                    adjMap.put(s.from(), menuList);
                }
            }

        } catch (SQLException e) {
            System.err.println(e);
        }

        return adjMap;
    }
}

 

Submenu.java

package az.mm.displaymenu;

/**
 *
 * @author MM <[email protected]>
 */
public class Submenu {
    
    private final int from;
    private final int to;
    private final String name;
    private boolean visited;

    public Submenu(int from, int to, String name) {
        this.from = from;
        this.to = to;
        this.name = name;
    }

    public int from() {
        return from;
    }

    public int to() {
        return to;
    }

    public String name() {
        return name;
    }

    public boolean isVisited() {
        return visited;
    }

    public void setVisited(boolean visited) {
        this.visited = visited;
    }

}

 

create.sql

CREATE DATABASE IF NOT EXISTS `test`;
USE `test`;

CREATE TABLE IF NOT EXISTS `menu` (
  `id` int(11) NOT NULL,
  `parent_id` int(11) NOT NULL,
  `name` varchar(50) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DELETE FROM `menu`;
INSERT INTO `menu` (`id`, `parent_id`, `name`) VALUES
    (0, -1, 'MENU'),
    (1, 0, 'Home'),
    (2, 0, 'Certification Exam'),
    (3, 0, 'Sample questions'),
    (4, 0, 'My Books'),
    (5, 0, 'Articles'),
    (6, 0, 'News'),
    (7, 0, 'Mix'),
    (8, 0, 'Feedbacks'),
    (9, 0, 'About'),
    (10, 2, 'Exam preparation: step by step'),
    (11, 2, 'Exam Topics'),
    (12, 2, 'Exam Experience'),
    (13, 3, 'Oracle'),
    (14, 13, '1Z0-803 (OCA 7)'),
    (15, 13, '1Z0-804 (OCP 7)'),
    (16, 13, '1Z0-805 (Upgrade to Java SE 7)'),
    (17, 13, '1Z0-808 (OCA 8)'),
    (18, 13, '1Z0-809 (OCP 8)'),
    (19, 13, '1Z0-810 (Upgrade SE 7 to 8 OCP)'),
    (20, 13, '1Z0-813 (Upgrade to SE 8 OCP)'),
    (21, 4, 'My Certification Notes');

 

pom.xml

...
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.11</version>
</dependency>
....

 

Proyektin github linki:

https://github.com/mmushfiq/display-website-menu-using-dfs-algorithm

About the author

Mushfiq Mammadov

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.

 

This site uses Akismet to reduce spam. Learn how your comment data is processed.