Реализация дерева Radix (Trie) для поиска Cutomer в Java

Я работаю над проектом, и мне нужно искать данные миллионов клиентов. Я хочу реализовать алгоритм поиска radix(trie). Я прочитал и реализовал систему счисления для простых коллекций строк. Но здесь у меня есть коллекция клиентов, и я хочу найти ее по имени или по номеру мобильного телефона.

Класс клиентов:

public class Customer {

    String name;
    String mobileNumer;


    public Customer (String name, String phoneNumer) {
        this.name = name;
        this.mobileNumer = phoneNumer;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPhoneNumer() {
        return mobileNumer;
    }
    public void setPhoneNumer(String phoneNumer) {
        this.mobileNumer = phoneNumer;
    }



}

Класс RadixNode:

import java.util.HashMap;
import java.util.Map;

class RadixNode {
    private final Map<Character, RadixNode> child = new HashMap<>();
    private final Map<Customer, RadixNode> mobileNum = new HashMap<>();
    private boolean endOfWord;

    Map<Character, RadixNode> getChild() {
        return child;
    }

    Map<Customer, RadixNode> getChildPhoneDir() {
        return mobileNum;
    }

    boolean isEndOfWord() {
        return endOfWord;
    }

    void setEndOfWord(boolean endOfWord) {
        this.endOfWord = endOfWord;
    }
}

Класс основания:

class Radix {
    private RadixNode root;

    Radix() {
        root = new RadixNode();
    }

    void insert(String word) {
        RadixNode current = root;

        for (int i = 0; i < word.length(); i++) {
            current = current.getChild().computeIfAbsent(word.charAt(i), c -> new RadixNode());
        }
        current.setEndOfWord(true);
    }

    void insert(Customer word) {
        RadixNode current = root;
        System.out.println("==========================================");
        System.out.println(word.mobileNumer.length());
        for (int i = 0; i < word.mobileNumer.length(); i++) {
            current = current.getChildPhoneDir().computeIfAbsent(word.mobileNumer.charAt(i), c -> new RadixNode());
            System.out.println(current);
        }
        current.setEndOfWord(true);
    }

    boolean delete(String word) {
        return delete(root, word, 0);
    }

    boolean containsNode(String word) {
        RadixNode current = root;

        for (int i = 0; i < word.length(); i++) {
            char ch = word.charAt(i);
            RadixNode node = current.getChild().get(ch);
            if (node == null) {
                return false;
            }
            current = node;
        }
        return current.isEndOfWord();
    }

    boolean isEmpty() {
        return root == null;
    }

    private boolean delete(RadixNode current, String word, int index) {
        if (index == word.length()) {
            if (!current.isEndOfWord()) {
                return false;
            }
            current.setEndOfWord(false);
            return current.getChild().isEmpty();
        }
        char ch = word.charAt(index);
        RadixNode node = current.getChild().get(ch);
        if (node == null) {
            return false;
        }
        boolean shouldDeleteCurrentNode = delete(node, word, index + 1) && !node.isEndOfWord();

        if (shouldDeleteCurrentNode) {
            current.getChild().remove(ch);
            return current.getChild().isEmpty();
        }
        return false;
    }

    public void displayContactsUtil(RadixNode curNode, String prefix) 
    { 

        // Check if the string 'prefix' ends at this Node 
        // If yes then display the string found so far 
        if (curNode.isEndOfWord()) 
            System.out.println(prefix); 

        // Find all the adjacent Nodes to the current 
        // Node and then call the function recursively 
        // This is similar to performing DFS on a graph 
        for (char i = 'a'; i <= 'z'; i++) 
        { 
            RadixNode nextNode = curNode.getChild().get(i); 
            if (nextNode != null) 
            { 
                    displayContactsUtil(nextNode, prefix + i); 
            } 
        } 
    }


    public boolean displayContacts(String str) 
    { 
        RadixNode prevNode = root; 

        // 'flag' denotes whether the string entered 
        // so far is present in the Contact List 

        String prefix = ""; 
        int len = str.length(); 

        // Display the contact List for string formed 
        // after entering every character 
        int i; 
        for (i = 0; i < len; i++) 
        { 
            // 'str' stores the string entered so far 
            prefix += str.charAt(i); 

            // Get the last character entered 
            char lastChar = prefix.charAt(i); 

            // Find the Node corresponding to the last 
            // character of 'str' which is pointed by 
            // prevNode of the Trie 
            RadixNode curNode = prevNode.getChild().get(lastChar); 

            // If nothing found, then break the loop as 
            // no more prefixes are going to be present. 
            if (curNode == null) 
            { 
                System.out.println("No Results Found for \"" + prefix + "\""); 
                i++; 
                break; 
            } 

            // If present in trie then display all 
            // the contacts with given prefix. 
            System.out.println("Suggestions based on \"" + prefix + "\" are"); 
            displayContactsUtil(curNode, prefix); 

            // Change prevNode for next prefix 
            prevNode = curNode; 
        } 

        for ( ; i < len; i++) 
        { 
            prefix += str.charAt(i); 
            System.out.println("No Results Found for \""  + prefix + "\""); 
        }

        return true;
    }


    public void displayContactsUtil(RadixNode curNode, String prefix, boolean isPhoneNumber) 
    { 

        // Check if the string 'prefix' ends at this Node 
        // If yes then display the string found so far 
        if (curNode.isEndOfWord()) 
            System.out.println(prefix); 

        // Find all the adjacent Nodes to the current 
        // Node and then call the function recursively 
        // This is similar to performing DFS on a graph 
        for (char i = '0'; i <= '9'; i++) 
        { 
            RadixNode nextNode = curNode.getChildPhoneDir().get(i); 
            if (nextNode != null) 
            { 
                    displayContactsUtil(nextNode, prefix + i); 
            } 
        } 
    }

    public boolean displayContacts(String str, boolean isPhoneNumber) 
    { 
        RadixNode prevNode = root; 

        // 'flag' denotes whether the string entered 
        // so far is present in the Contact List 

        String prefix = ""; 
        int len = str.length(); 

        // Display the contact List for string formed 
        // after entering every character 
        int i; 
        for (i = 0; i < len; i++) 
        { 
            // 'str' stores the string entered so far 
            prefix += str.charAt(i); 

            // Get the last character entered 
            char lastChar = prefix.charAt(i); 

            // Find the Node corresponding to the last 
            // character of 'str' which is pointed by 
            // prevNode of the Trie 
            RadixNode curNode = prevNode.getChildPhoneDir().get(lastChar); 

            // If nothing found, then break the loop as 
            // no more prefixes are going to be present. 
            if (curNode == null) 
            { 
                System.out.println("No Results Found for \"" + prefix + "\""); 
                i++; 
                break; 
            } 

            // If present in trie then display all 
            // the contacts with given prefix. 
            System.out.println("Suggestions based on \"" + prefix + "\" are"); 
            displayContactsUtil(curNode, prefix, isPhoneNumber); 

            // Change prevNode for next prefix 
            prevNode = curNode; 
        } 

        for ( ; i < len; i++) 
        { 
            prefix += str.charAt(i); 
            System.out.println("No Results Found for \""  + prefix + "\""); 
        }

        return true;
    } 


}

Я пытался искать в коллекции, но застрял. Любая помощь / предложение будут оценены.


person TAB    schedule 22.03.2019    source источник
comment
Если вы хотите выполнить поиск по 2 разным критериям (имя или номер телефона), то вам придется сохранить в памяти 2 попытки, по одной для каждого критерия поиска. Отвечает ли это на ваш вопрос ?   -  person m.raynal    schedule 01.04.2019
comment
@m.raynal спасибо за ваши комментарии. У меня есть список объектов клиентов, которые имеют около 18 столбцов/полей различных примитивных типов. Я хочу найти клиента по имени клиента или номеру телефона. Как я могу поддерживать этот список с помощью поиска? Над этим я немного работаю.   -  person TAB    schedule 01.04.2019


Ответы (1)


Я предлагаю вам 2 способа сделать это.

Первый способ: с помощью одного дерева.
Можно хранить все, что вам нужно, в одном дереве. С вашим классом клиентов все в порядке, и вот возможная реализация RadixNode.
Я считаю, что не может быть двух клиентов с одинаковым именем или номером телефона. Если это не так (например, возможность иметь людей с одинаковым именем и разными номерами телефонов), сообщите мне об этом в комментарии, который я отредактирую.
Важно понимать, что если вы хотите иметь два различные способы поиска клиента, и вы используете одну попытку, каждый клиент будет отображаться в вашей попытке дважды. Один раз в конце пути, соответствующего его имени, и один раз после конца пути, соответствующего его номеру телефона.

import java.util.HashMap;
import java.util.Map;

class RadixNode {
    private Map<Character, RadixNode> children;
    private Customer customer;

    public RadixNode(){
        this.children = new Map<Character, RadixNode>();
        this.Customer = NULL;
    }
    Map<Character, RadixNode> getChildren() {
        return children;
    }
    boolean hasCustomer() {
        return this.customer != NULL;
    }
    Customer getCustomer() {
        return customer;
    }
    void setCustomer(Customer customer) {
        this.customer = customer;
    }
}

Как видите, есть только одна карта, в которой хранятся дочерние элементы узла. Это связано с тем, что мы можем видеть номер телефона в виде строки цифр, поэтому в этом дереве будут храниться все клиенты... дважды. Один раз на имя, один раз на номер телефона.
Теперь давайте посмотрим на функцию вставки. Вашей попытке понадобится корень, давайте назовем его root.

public void insert(RadixNode root, Customer customer){
    insert_with_name(root, customer, 0);
    insert_with_phone_nb(root, customer, 0);
}

public void insert_with_name(RadixNode node, Customer customer, int idx){
    if (idx == customer.getName().length()){
        node.setCustomer(customer);
    } else {
        Character current_char = customer.getName().chatAt(idx);
        if (! node.getChlidren().containsKey(current_char){
            RadixNode new_child = new RadixNode();
            node.getChildren().put(current_char, new_child);
        }
        insert_with_name(node.getChildren().get(current_char), customer, idx+1);
    }
}

Метод insert_with_phone_nb() аналогичен. Это будет работать, если у людей есть уникальные имена, уникальные номера телефонов и чье-то имя не может быть чьим-то номером телефона.
Как видите, метод является рекурсивным. Я советую вам строить древовидную структуру (и вообще все, что основано на древовидной структуре) рекурсивно, так как это делает код проще и чище.
Функция поиска почти копипаст функции вставки:

public void search_by_name(RadixNode node, String name, int idx){
    // returns NULL if there is no user going by that name
    if (idx == name.length()){
        return node.getCustomer();
    } else {
        Character current_char =  name.chatAt(idx);
        if (! node.getChlidren().containsKey(current_char){
            return NULL;
        } else {
            return search_by_name(node.getChildren().get(current_char), name, idx+1);
        }
    }
}

Второй способ: с 2 попытками
Принцип тот же, все, что вам нужно сделать, это повторно использовать приведенный выше код, но сохранить два отдельных узла root, каждый из которых будет строить дерево (один для имена, один для номеров телефонов). Единственным отличием будет функция insert (поскольку она будет вызывать insert_with_name и insert_with_phone_nb с двумя разными корнями) и функция поиска, которая также должна будет искать в правильном дереве.

public void insert(RadixNode root_name_trie, RadixNode root_phone_trie, Customer customer){
    insert_with_name(root_name_trie, customer, 0);
    insert_with_phone_nb(root_phone_trie, customer, 0);
}

Изменить: после уточнения комментариев могут быть клиенты с таким же именем, вот альтернативная реализация, позволяющая RadixNode содержать ссылки на несколько Customer.
Замените атрибут Customer customer в RadixNode на, для например, Vector<Customer>. Методы, конечно, должны быть изменены соответствующим образом, и поиск по имени будет возвращать вам вектор клиентов (возможно, пустой), так как этот поиск может привести к нескольким результатам.
В вашем случае я бы пойти на одну попытку, содержащую векторы клиентов. Таким образом, у вас может быть как поиск по имени и телефону (укажите номер как String), так и единая структура данных для поддержки.

person m.raynal    schedule 02.04.2019
comment
@mraynal спасибо за ответ с решением. Имя клиента может быть одинаковым для нескольких, т. е. несколько клиентов могут иметь одно и то же имя. Но номера телефонов всегда уникальны. - person TAB; 02.04.2019