- renamed LW to the correct name
- copied two nets as resources
- removed unnecessary code in NetworkNode
- Created MainGUI
- changed some code in SmileLib
This commit is contained in:
2024-01-04 23:37:46 +01:00
parent 42a54947a5
commit c2cd4e3fb2
18 changed files with 694 additions and 192 deletions

View File

@@ -1,38 +0,0 @@
package net.berack.upo.ai;
import java.util.Scanner;
import java.util.function.Function;
import net.berack.upo.ai.problem1.Puzzle8GUI;
import net.berack.upo.ai.problem2.TrisGUI;
public class Main {
public static void main(String[] args) {
var value = read("What do you want to play?\n1. Puzzle8\n2. Tris\n", new Scanner(System.in), num -> num > 0 && num < 2);
var window = switch (value) {
case 1 -> new Puzzle8GUI();
case 2 -> new TrisGUI();
default -> null;
};
if(window != null) {
window.toFront();
window.requestFocus();
}
}
private static int read(String out, Scanner in, Function<Integer, Boolean> control) {
var ret = 0;
do {
try {
System.out.print(out);
var str = in.nextLine();
ret = Integer.parseInt(str);
} catch (NumberFormatException ignore) {}
} while(!control.apply(ret));
return ret;
}
}

View File

@@ -0,0 +1,92 @@
package net.berack.upo.ai;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JSeparator;
import net.berack.upo.ai.decision.PrototypeGUI;
import net.berack.upo.ai.decision.VehicleGUI;
import net.berack.upo.ai.problem1.Puzzle8GUI;
import net.berack.upo.ai.problem2.TrisGUI;
import net.berack.upo.ai.problem3.LikelihoodWeightingGUI;
/**
* Classe che rappresenta il main di tutto il progetto
* In essa si può navigare in tutti gli esercizi e testarli
* @author Berack
*/
public class MainGUI extends JFrame {
public final Puzzle8GUI Puzzle8GUI = new Puzzle8GUI();
public final TrisGUI TrisGUI = new TrisGUI();
public final LikelihoodWeightingGUI LikelihoodWeightingGUI = new LikelihoodWeightingGUI();
public final PrototypeGUI PrototypeGUI = new PrototypeGUI();
public final VehicleGUI VehicleGUI = new VehicleGUI();
public static void main(String[] args) {
new MainGUI();
}
private final JMenuBar menuBar = new JMenuBar();
/**
* Crea una finestra con nulla da mostrare, ma si può visualizzare uno degli esercizi tramite
* la barre dei menù che permette di cambiare da un esercizio all'altro
*/
private MainGUI() {
super("Progetto per AI");
this.buildMenu();
this.setJMenuBar(menuBar);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(400, 400);
this.setResizable(false);
this.setLocationRelativeTo(null);
this.setVisible(true);
}
/**
* Crea la barra dei menu aggiungendo tutti i menù passati in input
* @param menus una lista di menù da aggiungere
*/
private void buildMenu(JMenu...menus) {
menuBar.removeAll();
var menu = new JMenu("View");
var puzzle = new JMenuItem("Puzzle8 Game");
puzzle.addActionListener(action -> this.setPanel(Puzzle8GUI));
var tris = new JMenuItem("Tris Game");
tris.addActionListener(action -> this.setPanel(TrisGUI));
var lw = new JMenuItem("Likelihood Weighting");
lw.addActionListener(action -> this.setPanel(LikelihoodWeightingGUI));
var prototype = new JMenuItem("Prototype net");
prototype.addActionListener(action -> this.setPanel(PrototypeGUI));
var vehicle = new JMenuItem("Vehicle net");
vehicle.addActionListener(action -> this.setPanel(VehicleGUI));
menu.add(puzzle);
menu.add(tris);
menu.add(lw);
menu.add(new JSeparator());
menu.add(prototype);
menu.add(vehicle);
menuBar.add(menu);
for(var m : menus) menuBar.add(m);
}
/**
* Cambia il pannello principale con quello passato in input
* @param panel il pannello da mostrare
*/
private void setPanel(MyPanel panel) {
this.setContentPane(panel);
this.buildMenu(panel.getMenu());
this.pack();
this.invalidate();
this.validate();
this.repaint();
}
}

View File

@@ -0,0 +1,20 @@
package net.berack.upo.ai;
import javax.swing.JMenu;
import javax.swing.JPanel;
/**
* Classe utilizzata per far si che tutti i frame della finestra abbiano lo stesso metodo
* per la generazione dei menu.
* @author Berack
*/
public abstract class MyPanel extends JPanel {
/**
* Crea un menu da aggiungere alla lista, in modo che appaia solamente quando
* la finestra venga utilizzata.
* Questo metodo viene usato solo dalla finestra MainGUI
* @return il menu contestuale da aggiungere
*/
abstract public JMenu getMenu();
}

View File

@@ -1,5 +0,0 @@
package net.berack.upo.ai.decision;
public class ConsoleInterface {
}

View File

@@ -0,0 +1,20 @@
package net.berack.upo.ai.decision;
import javax.swing.JMenu;
import net.berack.upo.ai.MyPanel;
/**
* Classe che mostra le decisioni possibili, insieme ai guadagni
* per la rete Prototipo.xdsl
* @author Berack
*/
public class PrototypeGUI extends MyPanel {
@Override
public JMenu getMenu() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getMenu'");
}
}

View File

@@ -0,0 +1,19 @@
package net.berack.upo.ai.decision;
import javax.swing.JMenu;
import net.berack.upo.ai.MyPanel;
/**
* Classe che mostra le decisioni possibili, insieme ai guadagni
* per la rete Veicolo.xdsl
*/
public class VehicleGUI extends MyPanel {
@Override
public JMenu getMenu() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getMenu'");
}
}

View File

@@ -1,31 +1,25 @@
package net.berack.upo.ai.problem1;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import net.berack.upo.ai.problem1.Puzzle8.Move;
import java.awt.Color;
import java.awt.GridLayout;
import java.util.ArrayList;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import net.berack.upo.ai.MyPanel;
import net.berack.upo.ai.problem1.Puzzle8.Move;
/**
* Classe che permette di visualizzare graficamente il gioco Puzzle8
* In questa classe si trova un main che crea una istanza di questa finestra
*
* @author Berack96
*/
public class Puzzle8GUI extends JFrame {
public static void main(String[] args) {
new Puzzle8GUI();
}
public class Puzzle8GUI extends MyPanel {
/**
* Carica staticamente le immagini necessarie per giocare al gioco
@@ -50,49 +44,20 @@ public class Puzzle8GUI extends JFrame {
* Come default viene creato il puzzle "GOAL" ovvero non mescolato.
*/
public Puzzle8GUI() {
super("Puzzle 8 game");
super();
var grid = new GridLayout(Puzzle8.LENGTH, Puzzle8.LENGTH);
grid.setHgap(6);
grid.setVgap(grid.getHgap());
var panel = new JPanel(grid);
this.setLayout(grid);
for(var i = 0; i < Puzzle8.LENGTH; i++) {
for(var j = 0; j < Puzzle8.LENGTH; j++) {
var comp = new MyComponent(Puzzle8.LENGTH, j, i);
panel.add(comp);
this.add(comp);
this.buttons[j][i] = comp;
}
}
this.add(panel);
this.attachMenu();
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.pack();
this.setResizable(false);
this.setLocationRelativeTo(null);
this.setVisible(true);
}
/**
* Metodo utile per non mettere tutto nel costruttore.
* Qui viene creata la menubar
*/
private void attachMenu() {
var menuBar = new JMenuBar();
var menu1 = new JMenu("Game");
var item1 = new JMenuItem("Shuffle");
item1.addActionListener(action -> this.shuffleGame());
menu1.add(item1);
var item2 = new JMenuItem("Show solution");
item2.addActionListener(action -> this.solveGame());
menu1.add(item2);
menuBar.add(menu1);
this.setJMenuBar(menuBar);
}
/**
@@ -194,4 +159,18 @@ public class Puzzle8GUI extends JFrame {
});
}
}
@Override
public JMenu getMenu() {
var menu = new JMenu("Game");
var item1 = new JMenuItem("Shuffle");
item1.addActionListener(action -> this.shuffleGame());
menu.add(item1);
var item2 = new JMenuItem("Show solution");
item2.addActionListener(action -> this.solveGame());
menu.add(item2);
return menu;
}
}

View File

@@ -1,18 +1,17 @@
package net.berack.upo.ai.problem2;
import java.awt.Color;
import java.awt.GridLayout;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import java.awt.Color;
import java.awt.GridLayout;
import net.berack.upo.ai.MyPanel;
/**
* Classe che permette di visualizzare graficamente il gioco Tris
@@ -20,11 +19,7 @@ import java.awt.GridLayout;
*
* @author Berack96
*/
public class TrisGUI extends JFrame {
public static void main(String[] args) {
new TrisGUI();
}
public class TrisGUI extends MyPanel {
/**
* Caricamento statico delle immagini
@@ -57,54 +52,17 @@ public class TrisGUI extends JFrame {
* Come default viene abilitata la AI come secondo giocatore.
*/
public TrisGUI() {
super("Tris");
super();
var grid = new GridLayout(Tris.LENGTH, Tris.LENGTH);
var panel = new JPanel(grid);
this.setLayout(grid);
for(var i = 0; i < Tris.LENGTH; i++) {
for(var j = 0; j < Tris.LENGTH; j++) {
var comp = new MyComponent(Tris.LENGTH, j, i);
panel.add(comp);
this.add(comp);
this.buttons[j][i] = comp;
}
}
this.add(panel);
this.attachMenu();
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.pack();
this.setResizable(false);
this.setLocationRelativeTo(null);
this.setVisible(true);
}
/**
* Metodo utile per non mettere tutto nel costruttore.
* Qui viene creata la menubar
*/
private void attachMenu() {
var menuBar = new JMenuBar();
var menu1 = new JMenu("Game");
var item1 = new JMenuItem("Reset");
item1.addActionListener(action -> this.reset());
menu1.add(item1);
var separator = new JSeparator();
menu1.add(separator);
var item2 = new JCheckBoxMenuItem("AI Enabled");
item2.setSelected(this.ai != null);
item2.addChangeListener(action -> this.ai = item2.getState()? new TrisAi(this.tris):null);
menu1.add(item2);
this.aiFirst = new JCheckBoxMenuItem("AI First");
this.aiFirst.setSelected(false);
menu1.add(this.aiFirst);
menuBar.add(menu1);
this.setJMenuBar(menuBar);
}
/**
@@ -175,4 +133,26 @@ public class TrisGUI extends JFrame {
});
}
}
@Override
public JMenu getMenu() {
var menu = new JMenu("Game");
var item1 = new JMenuItem("Reset");
item1.addActionListener(action -> this.reset());
menu.add(item1);
var separator = new JSeparator();
menu.add(separator);
var item2 = new JCheckBoxMenuItem("AI Enabled");
item2.setSelected(this.ai != null);
item2.addChangeListener(action -> this.ai = item2.getState()? new TrisAi(this.tris):null);
menu.add(item2);
this.aiFirst = new JCheckBoxMenuItem("AI First");
this.aiFirst.setSelected(false);
menu.add(this.aiFirst);
return menu;
}
}

View File

@@ -1,16 +1,18 @@
package net.berack.upo.ai.problem3;
import java.security.SecureRandom;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import smile.Network;
/**
* Calcolo dei valori tramite l'algoritmo del Likelyhood Weighting
* Calcolo dei valori tramite l'algoritmo del Likelihood Weighting
* @author Berack
*/
public class LikelyhoodWeighting {
public class LikelihoodWeighting {
public final Network net;
private final Map<Integer, NetworkNode> nodes = new HashMap<>();
@@ -19,14 +21,14 @@ public class LikelyhoodWeighting {
* Inizializza un nuovo oggetto che calcolerà i valori per la rete inserita
* @param net la rete a cui calcolare i valori
*/
public LikelyhoodWeighting(Network net) {
public LikelihoodWeighting(Network net) {
this.net = Objects.requireNonNull(net);
}
/**
* Recupera i valori del nodo dopo averli calcolati
* Nel caso in cui non si abbia ancora fatto {@link #updateNetwork(int)} allora restituirà
* una eccezione di tiop UnsupportedOperationException
* una eccezione di tipo UnsupportedOperationException
* @param node il nodo da vedere
* @return l'array di valori da restituire
*/
@@ -35,6 +37,17 @@ public class LikelyhoodWeighting {
return nodes.get(node).values;
}
/**
* Permette di recuperare tutti i nodi.
* Nel caso in cui non si abbia ancora fatto {@link #updateNetwork(int)} allora restituirà
* una eccezione di tipo UnsupportedOperationException
* @return Una collezione di tutti i nodi.
*/
public Collection<NetworkNode> getAllNodes() {
if(nodes.size() == 0) throw new UnsupportedOperationException("You should run first updateNetwork method");
return this.nodes.values();
}
/**
* Calcola i valori possibili per la rete.
* Per poterli vedere utilizzare il metodo {@link #getNodeValue(int)}
@@ -74,4 +87,9 @@ public class LikelyhoodWeighting {
node.values[i] /= sum;
}
}
@Override
public String toString() {
return this.nodes.values().toString();
}
}

View File

@@ -0,0 +1,141 @@
package net.berack.upo.ai.problem3;
import java.awt.Dimension;
import java.awt.FileDialog;
import java.awt.Font;
import javax.swing.GroupLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import net.berack.upo.ai.MyPanel;
/**
* Classe usata per far vedere il risultato di una run di lw su un network
* @author Berack
*/
public class LikelihoodWeightingGUI extends MyPanel {
private LikelihoodWeighting lw = null;
private final JPanel scroll = new JPanel();
private final int totalRuns = 1000;
/**
* Crea il pannello con gli elementi di default.
* Siccome non c'è nessun network, il pannello sarà vuoto finchè non si apriranno dei network
*/
public LikelihoodWeightingGUI() {
this.scroll.setPreferredSize(new Dimension(500, 400));
this.add(this.scroll);
}
/**
* Crea una finestra di dialog per la richiesta di un file.
* Il file richiesto è in forma .xdsl
*/
public void openFile() {
var parent = this.getParent();
while(parent.getParent() != null) parent = parent.getParent();
if(!(parent instanceof JFrame)) throw new IllegalArgumentException(parent.getClass().getName());
var dialog = new FileDialog((JFrame) parent, "Select net");
dialog.setLocationRelativeTo(null);
dialog.setFile("*.xdsl");
dialog.setMode(FileDialog.LOAD);
dialog.setVisible(true);
if(dialog.getFile() != null) this.openFile(dialog.getDirectory() + dialog.getFile());
}
/**
* Carica il file indicato nella finestra, esegue l'algoritmo e mostra i risultati
* @param fileName il nome del file
*/
public void openFile(String fileName) {
try {
var net = SmileLib.getNetworkFrom(fileName);
this.lw = new LikelihoodWeighting(net);
this.updateLW();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Esegue l'algoritmo sul network corrente e mostra i risultati
*/
private void updateLW() {
this.lw.updateNetwork(totalRuns);
var nodes = this.lw.getAllNodes();
var panel = new JPanel();
var layout = new GroupLayout(panel);
panel.setLayout(layout);
layout.setAutoCreateGaps(true);
var gLabel = layout.createParallelGroup();
var gBarch = layout.createParallelGroup();
var vGroup = layout.createSequentialGroup();
for(var node : nodes) {
var label = new JLabel(node.name);
var barch = new OutcomeChartGUI(node, i -> {
if(node.evidence == i) node.net.clearEvidence(node.handle);
else node.net.setEvidence(node.handle, i);
this.updateLW();
});
var font = label.getFont();
label.setFont(new Font(font.getName(), font.getStyle(), font.getSize() + 4));
gLabel.addComponent(label);
gBarch.addComponent(barch);
vGroup.addGroup(layout.createParallelGroup()
.addComponent(label)
.addComponent(barch));
}
var hGroup = layout.createSequentialGroup();
hGroup.addGroup(gLabel).addGroup(gBarch);
layout.setVerticalGroup(vGroup);
layout.setHorizontalGroup(hGroup);
this.scroll.removeAll();
this.scroll.add(panel);
this.repaint();
this.invalidate();
this.validate();
this.repaint();
}
@Override
public JMenu getMenu() {
var menu = new JMenu("File");
var open = new JMenuItem("Open");
open.addActionListener(action -> this.openFile());
var net1 = new JMenuItem("WetGrass net");
net1.addActionListener(action -> this.openFile("lw/WetGrass.xdsl"));
var net2 = new JMenuItem("Malaria net");
net2.addActionListener(action -> this.openFile("lw/Malaria.xdsl"));
menu.add(open);
menu.add(new JSeparator());
menu.add(net1);
menu.add(net2);
return menu;
}
}

View File

@@ -16,6 +16,7 @@ import smile.Network;
public class NetworkNode {
final int handle;
final String name;
final String[] outcomes;
final double[] definition;
final int evidence;
@@ -36,12 +37,16 @@ public class NetworkNode {
NetworkNode(Network net, int handle, Map<Integer, NetworkNode> nodes) {
this.handle = handle;
this.type = net.getNodeType(handle);
this.name = net.getNodeId(handle);
this.net = net;
this.outcomes = net.getOutcomeIds(handle);
this.values = new double[this.outcomes.length];
this.evidence = net.isEvidence(handle)? net.getEvidence(handle) : -1;
if(this.isEvidence()) this.values[this.evidence] = 1.0d;
if(this.isEvidence()) {
this.values[this.evidence] = 1.0d;
this.sample = this.evidence;
}
var parentsHandle = net.getParents(handle);
this.parents = new NetworkNode[parentsHandle.length];
@@ -111,11 +116,10 @@ public class NetworkNode {
var tot = this.definition.length;
for(var p : this.parents) {
var pIndex = p.isEvidence()? p.evidence : p.sample;
if(pIndex < 0) throw new IllegalArgumentException("Parent"); // in theory impossible since Topological sorted
if(p.sample < 0) throw new IllegalArgumentException("Parent"); // in theory impossible since Topological sorted
tot /= p.outcomes.length;
init += tot * pIndex;
init += tot * p.sample;
}
return init;
@@ -132,4 +136,9 @@ public class NetworkNode {
return true;
}
@Override
public String toString() {
return this.net.getNodeId(this.handle) + "->" + Arrays.toString(this.values);
}
}

View File

@@ -0,0 +1,79 @@
package net.berack.upo.ai.problem3;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.Label;
import java.util.function.Consumer;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
/**
* Classe che rappresenta gli outcome di un nodo di un network.
* I valori deli outcome vengono visualizzati con un grafico.
* @author Berack
*/
public class OutcomeChartGUI extends JPanel {
public static final Color[] COLORS = {
new Color(255,127,0),
new Color(0,127,255),
new Color(0,255,127),
new Color(255,0,127),
new Color(127,0,255),
new Color(127,255,0),
};
/**
* Crea il JPanel da visualizzare a partire da un NetworkNode appropriamente inizializzato.
* Quando verrà visualizzato, il nodo avrà il nome degli output e i suoi valori in %
* con una barra colorata per indicare la grandezza visivamente.
*
* @param node il nodo di cui si vogliono visualizzare gli outcome
* @param action una azione da fare nel caso in cui venga premuto su un outcome
*/
public OutcomeChartGUI(NetworkNode node, Consumer<Integer> action) {
var labels = node.outcomes;
var values = node.values;
if(labels.length != values.length) throw new IllegalArgumentException("Arrays length myst be equals!");
this.setBorder(BorderFactory.createCompoundBorder(new EmptyBorder(1, 0, 0, 0), BorderFactory.createLineBorder(Color.GRAY)));
var layout = new GridLayout(labels.length, 2);
this.setLayout(layout);
for(var i = 0; i < labels.length; i++) {
var value = values[i] * 100;
var lName = new JButton(labels[i]);
var lValue = new Label(String.format("% 4.2f%%", value));
var barchart = new Label();
var size = barchart.getPreferredSize();
final var index = i;
lName.setContentAreaFilled(false);
lName.setFocusable(false);
lName.addActionListener(a -> action.accept(index));
if(node.evidence == i) lName.setForeground(Color.RED);
size.width = (int) (value * 1.5);
barchart.setPreferredSize(size);
barchart.setBackground(COLORS[i % COLORS.length]);
var panel1 = new JPanel();
panel1.setLayout(new GridLayout(1, 2));
panel1.add(lName);
panel1.add(lValue);
var panel2 = new JPanel();
panel2.setLayout(new BorderLayout());
panel2.add(barchart, BorderLayout.LINE_START);
this.add(panel1);
this.add(panel2);
}
}
}

View File

@@ -1,9 +1,11 @@
package net.berack.upo.ai.problem3;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import smile.Network;
/**
@@ -18,18 +20,19 @@ import smile.Network;
*/
public class SmileLib {
public static final String RESOURCE_PATH;
static {
var loader = SmileLib.class.getClassLoader();
var wrongPath = loader.getResource("").getFile();
var path = wrongPath.substring(1);
try {
RESOURCE_PATH = URLDecoder.decode(path, "ASCII");
var jsmile = "jsmile";
var loader = SmileLib.class.getClassLoader();
var resource = loader.getResource(jsmile);
var uri = resource.toURI();
var path = Path.of(uri).toString();
System.setProperty("jsmile.native.library", path);
} catch (Exception e) {
throw new RuntimeException("Decodification of path failed!\n" + e.getMessage());
e.printStackTrace();
System.exit(0);
}
System.setProperty("jsmile.native.library", RESOURCE_PATH + "jsmile");
new smile.License(
"SMILE LICENSE 02a07eb5 5c5fa64a 2a276459 " +
"THIS IS AN ACADEMIC LICENSE AND CAN BE USED " +
@@ -60,8 +63,12 @@ public class SmileLib {
public static Network getNetworkFrom(String file) {
var net = new Network();
try {
net.readFile(RESOURCE_PATH + file);
} catch (smile.SMILEException e) {
var loader = SmileLib.class.getClassLoader();
var in = loader.getResourceAsStream(file);
var str = new String(in.readAllBytes(), StandardCharsets.UTF_8);
net.readString(str);
} catch (Exception e) {
net.readFile(file);
}
@@ -78,15 +85,15 @@ public class SmileLib {
* @return una lista ordinata di nodi
*/
public static List<NetworkNode> buildListFrom(Network net) {
var nodes = new HashMap<Integer, NetworkNode>();
var list = new ArrayList<NetworkNode>();
var nodes = new HashMap<Integer, NetworkNode>();
var list = new ArrayList<NetworkNode>();
for(var handle : net.getAllNodes()) {
var node = new NetworkNode(net, handle, nodes);
list.add(node);
nodes.put(handle, node);
}
return list;
for(var handle : net.getAllNodes()) {
var node = new NetworkNode(net, handle, nodes);
list.add(node);
nodes.put(handle, node);
}
return list;
}
}