【アルゴリズム】マージャン聴牌アルゴリズム、StreamAPIを利用して、スレッドプールを使って聴牌を探し、胡牌を判断する


効果
判断がでたらめである.
看板をさがす
マルチカード型の胡牌探し
今回の聴牌アルゴリズムはJava 8のStreamAPIの新しい特性を採用して牌組集合を処理し、従来の集合遍歴より性能が大幅に向上し、並列もサポートされている.これはマルチスレッドに良いサポートを提供している.また、牌組の符号化は集合工場を採用しており、下層省は修正の機能を省き、作成後は修正できず遍歴するしかなく、しかも付与時に便利である.
プロジェクトのソース、jarファイル、githubに行って取得してください.https://github.com/ZDG-Kinlon/MahjongCheckHuUtil
デッキクラス
Deck.JAvaはデッキを変換するためだけで、計算と出力の切り替えが便利です
package cn.mahjong;

import java.util.ArrayList;
import java.util.List;

public class Deck {
    //               
    public static final List DECK_NO = List.of(
            11, 12, 13, 14, 15, 16, 17, 18, 19,
            21, 22, 23, 24, 25, 26, 27, 28, 29,
            31, 32, 33, 34, 35, 36, 37, 38, 39,
            41, 42, 43, 44, 45, 46, 47
    );

    //               
    private static final List DECK_STR = List.of(
            "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ",
            "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ",
            "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ",
            "  ", "  ", "  ", "  ", "  ", "  ", "  "
    );

    /**
     *              
     * @param num
     * @return
     * @throws IndexOutOfBoundsException
     */
    public static String getDeckByStr(int num)
      throws IndexOutOfBoundsException {
        return DECK_STR.get(DECK_NO.indexOf(num));
    }

    /**
     *              
     * @param str
     * @return
     * @throws IndexOutOfBoundsException
     */
    public synchronized static Integer getDeckByNO(String str)
      throws IndexOutOfBoundsException {
        return DECK_NO.get(DECK_STR.indexOf(str));
    }

    /**
     *                 
     * @param stringList
     * @return
     * @throws IndexOutOfBoundsException
     */
    public static List toDeckNO(List stringList)
      throws IndexOutOfBoundsException {
        List out = new ArrayList<>();
        stringList.forEach(s -> out.add(getDeckByNO(s)));
        return out;
    }

    /**
     *               
     * @param intList
     * @return
     * @throws IndexOutOfBoundsException
     */
    public static List toDeckStr(List intList)
      throws IndexOutOfBoundsException {
        List out = new ArrayList<>();
        intList.forEach(s -> out.add(getDeckByStr(s)));
        return out;
    }
}

胡牌の類を判断する
CheckHu.JAvaはStreamAPIを利用してカードを処理し、まず7対の子、国士無双という特殊なカード型を判断し、成立しなければ3 n*2のカード型かどうかを検出するしかない.
3 nの牌型は刻牌とチェーン牌の2種類があり、刻牌は3枚の同じ牌で、チェーン牌は連続した同色牌である.
nは最大で4しかないので、札を外すときは最大4層のネストを使うことができ、全部で16種類の組み合わせで、札型が胡札であれば、絶対に最後の1枚まで分解できるものがあります.
カードを取り外す時、もしテストカードの取り外しに失敗したら、ロールバックして元に戻して、次の取り外しに影響しません.番号は10、20、30、40番札がないので、間接的に色を区別する役割を果たして、チェーンカードを取り外す時の色をまたがる問題を避けました.
Java 8に導入されたStreamAPIは、集合の処理において大きく強化されており、集合中の要素をExcelテーブルの各列のセルとして抽象化することができ、後続の一連の点方法は、テーブルの最初の行にあるフィルタリングと統計機能であり、容易に集合の処理を完了することができる.
Java 9が導入した集合ファクトリof()メソッドは,変更不可能でクエリーのみの集合を迅速に生成でき,効率が大幅に向上した.
package cn.mahjong;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.function.BooleanSupplier;
import java.util.stream.Collectors;

public class CheckHu implements Callable<Map<Integer, Boolean>> {
    private Integer newDeck;//   
    private List<Integer> checkDeckList;//     
    private Map<Integer, Long> map;//      

    public CheckHu(List<Integer> checkDeckList) {
        this.checkDeckList = checkDeckList;
    }

    public CheckHu(List<Integer> checkDeckList, Integer newDeck) {
        this.newDeck = newDeck;
        this.checkDeckList = checkDeckList;
    }

    /**
     *         
     *
     * @return
     * @throws Exception
     */
    @Override
    public Map<Integer, Boolean> call() throws Exception {
        return Map.of(newDeck, isHu());
    }

    /**
     *                 
     *
     * @return
     */
    public boolean isHu() {
        return init.getAsBoolean() && (isQiDuiZi.getAsBoolean() ||
           isGuoShiWuShuang.getAsBoolean() || is3n2.getAsBoolean());
    }

    //     
    private BooleanSupplier init = () -> {
        List<Integer> list = new ArrayList<>(this.checkDeckList);
        if (newDeck != null) list.add(newDeck);//           ,        
        Collections.sort(list);//    ,               
        this.checkDeckList = list;
        //           
        this.map = list.stream()
          .collect(Collectors.groupingBy(integer -> integer, Collectors.counting()));          
        return !check4count();//         4 
    };

    /**
     *          4 
     *
     * @return
     */
    private boolean check4count() {
    //              
        return this.map.values().stream()
          .max(Comparator.comparing(count -> count)).get().intValue() > 4;
    }

    //            Lambda   ,               7 ,            2          7 ,
    private BooleanSupplier isQiDuiZi = () -> this.map.keySet().size() == 7 && 
        this.map.values().stream().filter(count -> count == 2).count() == 7;

    //             Lambda   ,               13 ,          40     ,  40              13 
    private BooleanSupplier isGuoShiWuShuang = () -> this.map.keySet().size() == 13 && 
        this.map.keySet().stream()
            .filter(a -> a / 10 == 4 || a % 10 == 9 || a % 10 == 1).count() == 13;

    //        3n+2   Lambda   
    private BooleanSupplier is3n2 = () -> {
        int count = checkDeckList.size();//            3n+2,    2 14   
        if (count >= 2 && count <= 14 && (count - 2) % 3 == 0) {
            List<Integer> duiDeckList = new ArrayList<>();//            (2)     ,        
            this.map.forEach((a, b) -> {
                if (b > 1) duiDeckList.add(a);
            });
            for (Integer a : duiDeckList) {//    
                List<Integer> testList = new LinkedList<>(this.checkDeckList);
                testList.remove(a);//      ,           3n  
                testList.remove(a);
                if (_A(_A(_A(_A(testList)))).size() == 0) return true;//       
                if (_A(_A(_A(_B(testList)))).size() == 0) return true;//       
                if (_A(_A(_B(_A(testList)))).size() == 0) return true;//       
                if (_A(_A(_B(_B(testList)))).size() == 0) return true;//       
                if (_A(_B(_A(_A(testList)))).size() == 0) return true;//       
                if (_A(_B(_A(_B(testList)))).size() == 0) return true;//       
                if (_A(_B(_B(_A(testList)))).size() == 0) return true;//       
                if (_A(_B(_B(_B(testList)))).size() == 0) return true;//       
                if (_B(_A(_A(_A(testList)))).size() == 0) return true;//       
                if (_B(_A(_A(_B(testList)))).size() == 0) return true;//       
                if (_B(_A(_B(_A(testList)))).size() == 0) return true;//       
                if (_B(_A(_B(_B(testList)))).size() == 0) return true;//       
                if (_B(_B(_A(_A(testList)))).size() == 0) return true;//       
                if (_B(_B(_A(_B(testList)))).size() == 0) return true;//       
                if (_B(_B(_B(_A(testList)))).size() == 0) return true;//       
                if (_B(_B(_B(_B(testList)))).size() == 0) return true;//       
            }
        }
        return false;//    4 3n  ,             ,    3n+2  
    };

    /**
     *       
     *
     * @param list
     * @return
     */
    private List<Integer> _A(List<Integer> list) {
        for (Integer integer : list) {//     
            List<Integer> testList = new LinkedList<>(list);//       
            //    ,      ,         
            if (breakDeckList(testList, integer, integer, integer)) return testList;
        }
        return list;//    ,           
    }

    /**
     *       
     *
     * @param list
     * @return
     */
    private List<Integer> _B(List<Integer> list) {
        for (Integer integer : list) {//     
            if (integer / 10 == 4) continue;//        
            List<Integer> testList = new LinkedList<>(list);//       
            //    ,      ,         
            if (breakDeckList(testList, integer, integer + 1, integer + 2)) return testList;
        }
        return list;
    }

    /**
     *       
     *
     * @param list
     * @param i1
     * @param i2
     * @param i3
     * @return
     */
    private boolean breakDeckList(List<Integer> list, Integer i1, Integer i2, Integer i3) {
        //          ,         false
        return list.remove(i1) && list.remove(i2) && list.remove(i3);
    }
}

聴牌の種類を探しています
FindHu.JAvaはスレッドプールを使用して、すべてのカードの可能性を遍歴し、測定対象カードと組み合わせて完全なカード型にし、スレッドに渡して実行し、最後に結果がtrueのカード番号の集合に戻る
package cn.mahjong;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class FindHu implements Callable<Map<List<Integer>, List<Integer>>> {
    private List<Integer> integerList;//        

    public FindHu(List<Integer> integerList) {
        this.integerList = integerList;
    }

    /**
     *       
     * @return
     */
    public List<Integer> findHu() {
        List<CheckHu> checkHuList = new ArrayList<>();//      
        List<Integer> allDeck = Deck.DECK_NO;//         
        //        ,     
        allDeck.forEach(integer -> checkHuList.add(new CheckHu(this.integerList, integer)));
        //     ,        
        ExecutorService threadPool = Executors.newFixedThreadPool(allDeck.size());
        List<Future<Map<Integer, Boolean>>> futures = new ArrayList<>();//           
        try {
            //          ,    call  ,        
            checkHuList.forEach(checkHu -> futures.add(threadPool.submit(checkHu)));
        } finally {
            threadPool.shutdown();//     
        }
        List<Integer> out = new ArrayList<>();//         
        futures.forEach(f -> {
            try {
                f.get().forEach((a, b) -> {
                    if (b) out.add(a);//          ,    true             
                });
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });
        return out;//    
    }

    /**
     *              
     * @return
     * @throws Exception
     */
    @Override
    public Map<List<Integer>, List<Integer>> call() throws Exception {
        return Map.of(this.integerList, findHu());//key=     ,value=    
    }
}

複数のデッキの聴牌類を探しています
FindHus.JAvaは同様にスレッドプールを用いて,受信した各カードグループに対して聴牌のクラスを探す方法を呼び出し,マルチタスクを同時に実行し,スレッドプールの氾濫を避けるためにデフォルト設定を最大4つとし,実際のハードウェア状況に応じて調整することを提案した.
package cn.mahjong;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class FindHus {
    private List<List<Integer>> lists;

    public FindHus(List<List<Integer>> lists) {
        this.lists = lists;
    }

    /**
     *              
     * @return
     */
    public Map<List<Integer>, List<Integer>> findHu() {
        ExecutorService threadPool = Executors.newFixedThreadPool(4);//     
        List<FindHu> findHus = new ArrayList<>();//    
        lists.forEach(list -> findHus.add(new FindHu(list)));//             ,        
        List<Future<Map<List<Integer>, List<Integer>>>> futures = new ArrayList<>();//             
        try {
            findHus.forEach(findHu -> futures.add(threadPool.submit(findHu)));//         ,    call  ,        
        } finally {
            threadPool.shutdown();//     
        }
        Map<List<Integer>, List<Integer>> out = new HashMap<>();//         
        futures.forEach(f -> {
            try {
                f.get().forEach(out::put);//          ,         
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });
        return out;
    }
}

テスト例
胡牌判断方法1
public void checkDemo1() {
    try {
        //1.      ,      
        List stringList = List.of("  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ");
        //2.      ,          IndexOutOfBoundsException
        List integerList = Deck.toDeckNO(stringList);
        //3.     
        CheckHu checkHu = new CheckHu(integerList);
        //4.        
        boolean result = checkHu.isHu();
        //5.    ,   =true
        System.out.println(result);
    } catch (IndexOutOfBoundsException e) {
        e.printStackTrace();
    }
}

胡牌判断方法2
public void checkDemo2() {
    try {
        //1.      ,      
        List<String> stringList = List.of("  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ");
        //2.      
        String checkDeck = "  ";
        //3.      ,          IndexOutOfBoundsException
        List<Integer> integerList = Deck.toDeckNO(stringList);
        Integer checkDeckNO = Deck.getDeckByNO(checkDeck);
        //4.     ,    ,    
        CheckHu checkHu = new CheckHu(integerList, checkDeckNO);
        //5.        
        boolean result = checkHu.isHu();
        //6.    ,   =true
        System.out.println(result);
    } catch (IndexOutOfBoundsException e) {
        e.printStackTrace();
    }
}

トランプの聞き方を探す
public void findDemo1() {
    try {
        //1.      ,      
        List<String> stringList = List.of("  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ");
        //2.      ,          IndexOutOfBoundsException
        List<Integer> integerList = Deck.toDeckNO(stringList);
        //3.    
        FindHu findHu = new FindHu(integerList);
        //4.        
        List<Integer> result = findHu.findHu();
        //5.        ,          IndexOutOfBoundsException
        List<String> out = Deck.toDeckStr(result);
        //6.    
        out.forEach(str -> System.out.print(str + "\t"));
    } catch (IndexOutOfBoundsException e) {
        e.printStackTrace();
    }
}

マルチデッキの聴牌を探しています
public void findDemo2() {
    try {
        //1.      ,      ,          
        List<List<Integer>> lists = List.of(
                //   , 1
                Deck.toDeckNO(List.of("  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ")),
                //    , 13
                Deck.toDeckNO(List.of("  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ")),
                //13 9
                Deck.toDeckNO(List.of("  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ")),
                //13 8
                Deck.toDeckNO(List.of("  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ")),
                //10 8
                Deck.toDeckNO(List.of("  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ")),
                //13 7
                Deck.toDeckNO(List.of("  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ")),
                //10 7
                Deck.toDeckNO(List.of("  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ")),
                //10 6
                Deck.toDeckNO(List.of("  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ")),
                //7 5
                Deck.toDeckNO(List.of("  ", "  ", "  ", "  ", "  ", "  ", "  ")),
                //10 4
                Deck.toDeckNO(List.of("  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ", "  ")),
                //7 4
                Deck.toDeckNO(List.of("  ", "  ", "  ", "  ", "  ", "  ", "  ")),
                //4 3
                Deck.toDeckNO(List.of("  ", "  ", "  ", "  "))
        );
        //2.    ,        
        FindHus findHus = new FindHus(lists);
        findHus.setnThreads(16);//           ,   4
        //3.        
        Map<List<Integer>, List<Integer>> result = findHus.findHu();
        //4.         ,        ,   
        result.forEach((a, b) -> {
            List<String> out1 = Deck.toDeckStr(a);
            List<String> out2 = Deck.toDeckStr(b);
            System.out.print("   :");
            out1.forEach(e -> System.out.print(e + "\t"));
            System.out.print("
:"
); out2.forEach(e -> System.out.print(e + "\t")); System.out.println("
"
); }); } catch (IndexOutOfBoundsException e) { e.printStackTrace(); } }