1. 程式人生 > >Java實現的計算器(帶括號和錯誤輸入提示)

Java實現的計算器(帶括號和錯誤輸入提示)

雖然接觸Java有兩個月了,還沒有自己好好寫過像樣的程式都是照著教程抄寫除錯一些程式,這是我在網上一些簡單Java計算器原始碼增加解析括號功能和防止輸入錯誤功能,藉助Android計算器原始碼更改的計算器程式。雖然基本沒有自己發明的部分,但程式碼是自己除錯看懂新增備註的,所以記在這裡,以備後面查閱。後面準備自己寫一個Android版的計算器程式作為練手,菜鳥一個,,繼續加油。這個小程式對Java介面設計和堆疊有一定的訓練效果。

經過幾個晚上的除錯,終於完美出鍋了,可能還有bug,如果你發現了請告知我!話不多說,直接誒上程式碼

/**
 * Title:   Calculator計算器程式
 * @author  豌豆先生
 * @time    2015.5.13
 * Mail   
[email protected]
* */ import java.awt.BorderLayout; import java.awt.Color; import java.awt.FlowLayout; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.GridLayout; import java.awt.Insets; import java.awt.RenderingHints.Key; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.StringTokenizer; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.LayoutFocusTraversalPolicy; import org.omg.CORBA.PRIVATE_MEMBER; public class Calculator extends JFrame implements ActionListener { private ArrayList<String> list; private final String[] KEYS = { "(", ")", "^", "7", "8", "9", "4", "5", "6", "1", "2", "3", "0", ".", "π" }; private final String[] CLEAR = { "AC", "Backspace" }; private final String[] SYMBOL = { "/", "*", "-", "+" ,"="}; private JButton keys[] = new JButton[KEYS.length]; private JButton clear[] = new JButton[CLEAR.length]; private JButton symbol[] = new JButton[SYMBOL.length]; public JTextField resultText = new JTextField("0"); private boolean vbegin = true;// 控制輸入,true為重新輸入,false為接著輸入 private boolean equals_flag = true;// true為未輸入=,false表示輸入= private boolean isContinueInput = true;// true為正確,可以繼續輸入,false錯誤,輸入鎖定 final int MAXLEN = 500; final double PI = 3.141592657; public Calculator() { super(); init(); this.setBackground(Color.LIGHT_GRAY); this.setTitle("計算器:豌豆先生"); this.setLocation(500, 300); this.setResizable(false); this.pack(); } private void init() { resultText.setHorizontalAlignment(JTextField.RIGHT); resultText.setEditable(false); resultText.setBackground(Color.white); list = new ArrayList<String>(); initLayout();//介面設定 initActionEvent();//新增元件事件處理,button響應 } public void initLayout() { JPanel calckeysPanel = new JPanel(); calckeysPanel.setLayout(new GridLayout(5, 3, 3, 3)); for (int i = 0; i < KEYS.length; i++) { keys[i] = new JButton(KEYS[i]); calckeysPanel.add(keys[i]); keys[i].setForeground(Color.blue); } for (int i = 0; i < SYMBOL.length; i++) { symbol[i] = new JButton(SYMBOL[i]); symbol[i].setForeground(Color.red); } for (int i = 0; i < CLEAR.length; i++) { clear[i] = new JButton(CLEAR[i]); clear[i].setForeground(Color.red); } JPanel text = new JPanel(); text.setLayout(new BorderLayout()); text.add("Center", resultText); JPanel panel1 = new JPanel(); panel1.setLayout(new GridBagLayout()); GridBagConstraints gbc= new GridBagConstraints();//定義一個GridBagConstraints gbc.fill = GridBagConstraints.BOTH; gbc.insets = new Insets(3, 3, 3, 3); gbc.gridx = 0; gbc.gridy = 0; //gbc.gridwidth = 1; //gbc.gridheight = 1; panel1.add(symbol[0], gbc);// “/號” gbc.gridx = 1; gbc.gridy = 0; //gbc.gridwidth = 1; //gbc.gridheight = 1; panel1.add(clear[0], gbc);// "AC" gbc.gridx = 0; gbc.gridy = 1; //gbc.gridwidth = 1; //gbc.gridheight = 1; panel1.add(symbol[1], gbc);//"*" gbc.gridx = 1; gbc.gridy = 1; gbc.gridwidth = 1; gbc.gridheight = 2; panel1.add(clear[1], gbc);//“backspace” gbc.gridx = 0; gbc.gridy = 2; gbc.gridwidth = 1; gbc.gridheight = 1; panel1.add(symbol[2], gbc);//“-” gbc.gridx = 0; gbc.gridy = 3; gbc.ipady = 33; panel1.add(symbol[3], gbc);//"+" gbc.gridx = 1; gbc.gridy = 3; //gbc.ipadx = 10; //gbc.ipady = 33; panel1.add(symbol[4], gbc);//"=" getContentPane().setLayout(new BorderLayout(3, 3)); getContentPane().add("Center", calckeysPanel); getContentPane().add("East", panel1); getContentPane().add("North", text); } public void initActionEvent() { for (int i = 0; i < KEYS.length; i++) { keys[i].addActionListener(this); } for (int i = 0; i < CLEAR.length; i++) { clear[i].addActionListener(this); } for (int i = 0; i < SYMBOL.length; i++) { symbol[i].addActionListener(this); } } //組建發生操作時呼叫 public void actionPerformed(ActionEvent e) { String label = e.getActionCommand(); if (label.equals(CLEAR[1])) { handleBackspace(); } else if (label.equals(CLEAR[0])) { list.clear(); resultText.setText("0"); vbegin = true; equals_flag = true; } else { handle(label); } } private void handleBackspace() { String text = resultText.getText(); list.add(text); int i = text.length(); if (i > 0 && list.size() > 0) { text = text.substring(0, i - 1); list.remove(list.size() - 1); // 移除棧頂的那個元素 if (text.length() == 0) { list.clear(); resultText.setText("0"); vbegin = true; equals_flag = true; } else { resultText.setText(text); } } } public void handle(String key) { String text = resultText.getText(); if (equals_flag == false) { //&& "π0123456789.()+-*/^".indexOf(key) != -1 list.add(text); vbegin = false;// ????????????? } if (!list.isEmpty()) { TipChecker(list.get(list.size() - 1), key); } else { TipChecker("#", key); } if (isContinueInput && "π0123456789.()+-*/^".indexOf(key) != -1) { list.add(key); } // 若輸入正確,則將輸入資訊顯示到顯示器上 if (isContinueInput && "π0123456789.()+-*/^".indexOf(key) != -1) { if (equals_flag == false && ("+-*/^".indexOf(key) != -1)) { vbegin = false; equals_flag = true; printText(key); } else if (equals_flag == false && ("π0123456789.()".indexOf(key) != -1)) { vbegin = true; equals_flag = true; printText(key); } else { printText(key); } } else if (isContinueInput && equals_flag && key.equals("=")) { isContinueInput = false;// 表明不可以繼續輸入 equals_flag = false;// 表明已經輸入= vbegin = true;// 重新輸入標誌設定true process(resultText.getText()); // 整個程式的核心,計算表示式的值並顯示 list.clear(); } isContinueInput = true; } private void printText(String key) { if (vbegin) { resultText.setText(key);// 清屏後輸出 // firstDigit = false; } else { resultText.setText(resultText.getText() + key); } vbegin = false; } /* * 檢測函式,對str進行前後語法檢測 為Tip的提示方式提供依據,與TipShow()配合使用 編號 字元 其後可以跟隨的合法字元 1 ( * 數字|(|-|.|函式 2 ) 算符|)| ^ 3 . 數字|算符|)| ^ 4 數字 .|數字|算符|)| ^ 5 算符 * 數字|(|.|函式 6 ^ ( |. | 數字 7 函式 數字|(|. * * 小數點前後均可省略,表示0 數字第一位可以為0 */ private void TipChecker(String tipcommand1, String tipcommand2) { // Tipcode1表示錯誤型別,Tipcode2表示名詞解釋型別 int Tipcode1 = 0, Tipcode2 = 0; // 表示命令型別 int tiptype1 = 0, tiptype2 = 0; // 括號數 int bracket = 0; // “+-*/ ^”不能作為第一位 if (tipcommand1.compareTo("#") == 0 && (tipcommand2.compareTo("/") == 0 || tipcommand2.compareTo("*") == 0 || tipcommand2.compareTo("+") == 0 || tipcommand2.compareTo(")") == 0 || tipcommand2 .compareTo("^") == 0)) { Tipcode1 = -1; } // 定義儲存字串中最後一位的型別 else if (tipcommand1.compareTo("#") != 0) { if (tipcommand1.compareTo("(") == 0) { tiptype1 = 1; } else if (tipcommand1.compareTo(")") == 0) { tiptype1 = 2; } else if (tipcommand1.compareTo(".") == 0) { tiptype1 = 3; } else if ("0123456789".indexOf(tipcommand1) != -1) { tiptype1 = 4; } else if ("+-*/".indexOf(tipcommand1) != -1) { tiptype1 = 5; } else if ("^".indexOf(tipcommand1) != -1) { tiptype1 = 6; }else if ("π".indexOf(tipcommand1) != -1){ tiptype1 = 7; } // 定義欲輸入的按鍵型別 if (tipcommand2.compareTo("(") == 0) { tiptype2 = 1; } else if (tipcommand2.compareTo(")") == 0) { tiptype2 = 2; } else if (tipcommand2.compareTo(".") == 0) { tiptype2 = 3; } else if ("0123456789".indexOf(tipcommand2) != -1) { tiptype2 = 4; } else if ("+-*/".indexOf(tipcommand2) != -1) { tiptype2 = 5; } else if ("^".indexOf(tipcommand2) != -1) { tiptype2 = 6; }else if ("π".indexOf(tipcommand2) != -1){ tiptype2 = 7; } switch (tiptype1) { case 1: // 左括號後面直接接右括號,“+*/”(負號“-”不算),或者" ^" if (tiptype2 == 2 || (tiptype2 == 5 && tipcommand2.compareTo("-") != 0) || tiptype2 == 6) Tipcode1 = 1; break; case 2: // 右括號後面接左括號,數字,“+-*/^...π” if (tiptype2 == 1 || tiptype2 == 3 || tiptype2 == 4|| tiptype2 == 7) Tipcode1 = 2; break; case 3: // “.”後面接左括號,π if (tiptype2 == 1 || tiptype2 == 7) Tipcode1 = 3; // 連續輸入兩個“.” if (tiptype2 == 3) Tipcode1 = 8; break; case 4: // 數字後面直接接左括號和π if (tiptype2 == 1 || tiptype2 == 7) Tipcode1 = 4; break; case 5: // “+-*/”後面直接接右括號,“+-*/ ^” if (tiptype2 == 2 || tiptype2 == 5 || tiptype2 == 6) Tipcode1 = 5; break; case 6: // “ ^”後面直接接右括號,“+-*/ ^π” if (tiptype2 == 2 || tiptype2 == 5 || tiptype2 == 6 || tiptype2 == 7) Tipcode1 = 6; break; case 7: //"π"之後只能為"+-*/^)"不能為"π(.0123456789" if (tiptype2 == 1 || tiptype2 == 3 || tiptype2 == 4 || tiptype2 == 7){ Tipcode1 = 7; } break; } } // 檢測小數點的重複性,Tipconde1=0,表明滿足前面的規則 if (Tipcode1 == 0 && tipcommand2.compareTo(".") == 0) { int tip_point = 0; for (int i = 0; i < list.size(); i++) { // 若之前出現一個小數點點,則小數點計數加1 if (list.get(i).equals(".")) { tip_point++; } // 若出現以下幾個運算子之一,小數點計數清零 if (list.get(i).equals("^") || list.get(i).equals("/") || list.get(i).equals("*") || list.get(i).equals("-") || list.get(i).equals("+") || list.get(i).equals("(") || list.get(i).equals(")")) { tip_point = 0; } } tip_point++; // 若小數點計數大於1,表明小數點重複了 if (tip_point > 1) { Tipcode1 = 8; } } // 檢測右括號是否匹配 if (Tipcode1 == 0 && tipcommand2.compareTo(")") == 0) { int tip_right_bracket = 0; for (int i = 0; i < list.size(); i++) { // 如果出現一個左括號,則計數加1 if (list.get(i).equals("(")) { tip_right_bracket++; } // 如果出現一個右括號,則計數減1 if (list.get(i).equals(")")) { tip_right_bracket--; } } // 如果右括號計數=0,表明沒有響應的左括號與當前右括號匹配 if (tip_right_bracket == 0) { Tipcode1 = 10; } } // 檢查輸入=的合法性 if (Tipcode1 == 0 && tipcommand2.compareTo("=") == 0) { // 括號匹配數 int tip_bracket = 0; for (int i = 0; i < list.size(); i++) { if (list.get(i).equals("(")) { tip_bracket++; } if (list.get(i).equals(")")) { tip_bracket--; } } // 若大於0,表明左括號還有未匹配的 if (tip_bracket > 0) { Tipcode1 = 9; bracket = tip_bracket; } else if (tip_bracket == 0) { // 若前一個字元是以下之一,表明=號不合法 if ("+-*/".indexOf(tipcommand1) != -1) { Tipcode1 = 5; } } } if (Tipcode1 != 0) { isContinueInput = false;// 表明不可以繼續輸入 } } /* * 計算表示式 從左向右掃描,數字入number棧,運算子入operator棧 * +-基本優先順序為1, * ×÷基本優先順序為2, * √^基本優先順序為4 * 括號內層運算子比外層同級運算子優先順序高4 * 當前運算子優先順序高於棧頂壓棧, * 低於棧頂彈出一個運算子與兩個數進行運算 * 重複直到當前運算子大於棧頂 * 掃描完後對剩下的運算子與數字依次計算 */ public void process(String str) { int weightPlus = 0, topOp = 0, topNum = 0, flag = 1, weightTemp = 0; // weightPlus為同一()下的基本優先順序,weightTemp臨時記錄優先順序的變化 // topOp為weight[],operator[]的計數器;topNum為number[]的計數器 // flag為正負數的計數器,1為正數,-1為負數 int weight[]; // 儲存operator棧中運算子的優先順序,以topOp計數 double number[]; // 儲存數字,以topNum計數 char ch, ch_gai, operator[];// operator[]儲存運算子,以topOp計數 String num;// 記錄數字,str以+-*/() ! ^分段,+-*/() ^字元之間的字串即為數字 weight = new int[MAXLEN]; number = new double[MAXLEN]; operator = new char[MAXLEN]; String expression = str.replace("π",String.valueOf(PI));//將字串中的π用PI // 建議用split代替字串分割 StringTokenizer expToken = new StringTokenizer(expression, "+-*/()^"); int i = 0; while (i < expression.length()) { ch = expression.charAt(i); // 判斷正負數 if (i == 0) { if (ch == '-') flag = -1; } else if (expression.charAt(i - 1) == '(' && ch == '-') flag = -1; // 取得數字,並將正負符號轉移給數字,E是科學計數 if (ch <= '9' && ch >= '0' || ch == '.' || ch == 'E') { num = expToken.nextToken();//分割後的StringTokenizer中的下一個索引資料 ch_gai = ch; // 取得整個數字 while (i < expression.length() && (ch_gai <= '9' && ch_gai >= '0' || ch_gai == '.' || ch_gai == 'E')) { ch_gai = expression.charAt(i++); } // 將指標退回之前的位置,即每個數字的末尾位置 if (i >= expression.length()) i -= 1; else { i -= 2; } if (num.compareTo(".") == 0) number[topNum++] = 0; // 將正負符號轉移給數字 else { number[topNum++] = Double.parseDouble(num) * flag; flag = 1; } } // 計算運算子的優先順序 if (ch == '(') weightPlus += 4; if (ch == ')') weightPlus -= 4; if (ch == '-' && flag == 1 || ch == '+' || ch == '*' || ch == '/' || ch == '^') { switch (ch) { // +-的優先順序最低,為1 case '+': case '-': weightTemp = 1 + weightPlus; break; // x/的優先順序稍高,為2 case '*': case '/': weightTemp = 2 + weightPlus; break; default: weightTemp = 4 + weightPlus; break; } // 如果當前優先順序大於堆疊頂部元素,則直接入棧 if (topOp == 0 || weight[topOp - 1] < weightTemp) { weight[topOp] = weightTemp; operator[topOp] = ch; topOp++; // 否則將堆疊中運算子逐個取出,直到當前堆疊頂部運算子的優先順序小於當前運算子 } else { while (topOp > 0 && weight[topOp - 1] >= weightTemp) { switch (operator[topOp - 1]) { // 取出數字陣列的相應元素進行運算 case '+': number[topNum - 2] += number[topNum - 1]; break; case '-': number[topNum - 2] -= number[topNum - 1]; break; case '*': number[topNum - 2] *= number[topNum - 1]; break; // 判斷除數為0的情況 case '/': if (number[topNum - 1] == 0) { // showError(1, str_old); return; } number[topNum - 2] /= number[topNum - 1]; break; case '^': number[topNum - 2] = Math.pow(number[topNum - 2], number[topNum - 1]); break; // 計算時進行角度弧度的判斷及轉換 } // 繼續取堆疊的下一個元素進行判斷 topNum--; topOp--; } // 將運算子入堆疊 weight[topOp] = weightTemp; operator[topOp] = ch; topOp++; } } i++; } // 依次取出堆疊的運算子進行運算 while (topOp > 0) { // +-x直接將陣列的後兩位數取出運算 switch (operator[topOp - 1]) { case '+': number[topNum - 2] += number[topNum - 1]; break; case '-': number[topNum - 2] -= number[topNum - 1]; break; case '*': number[topNum - 2] *= number[topNum - 1]; break; // 涉及到除法時要考慮除數不能為零的情況 case '/': if (number[topNum - 1] == 0) { // showError(1, str_old); return; } number[topNum - 2] /= number[topNum - 1]; break; case '^': number[topNum - 2] = Math.pow(number[topNum - 2], number[topNum - 1]); break; } // 取堆疊下一個元素計算 topNum--; topOp--; } // 如果是數字太大,提示錯誤資訊 if (number[0] > 7.3E306) { // showError(3, str_old); return; } // 輸出最終結果 resultText.setText(String.valueOf(FP(number[0]))); } public double FP(double n) { // NumberFormat format=NumberFormat.getInstance(); //建立一個格式化類f // format.setMaximumFractionDigits(18); //設定小數位的格式 DecimalFormat format = new DecimalFormat("0.#############"); return Double.parseDouble(format.format(n)); } public static void main(String args[]) { Calculator calculator = new Calculator(); calculator.setVisible(true); calculator.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } }


相關推薦

Java實現計算器(括號錯誤輸入提示)

雖然接觸Java有兩個月了,還沒有自己好好寫過像樣的程式都是照著教程抄寫除錯一些程式,這是我在網上一些簡單Java計算器原始碼增加解析括號功能和防止輸入錯誤功能,藉助Android計算器原始碼更改的計算器程式。雖然基本沒有自己發明的部分,但程式碼是自己除錯看懂新增備註的,所

Java實現圖的深度廣度優先遍歷算法

lan 圖結構 廣度搜索 源代碼下載 源代碼 earch isempty 學習 ole 概述: 近期要學習寫網絡爬蟲。所以把圖的深度和廣度搜索都再溫習一下。 圖結構展示: 實現過程: 首先,我們來看看圖結構在代碼中的實現。有三塊邏輯: 1.圖中的節點

JavaScript 函數調用時括號括號的區別

span script nod 才會 彈出 產生 都是 必須 得到 function countBodyChildren(){ var body_element = document.getElementsByTagName("body")[0]; ale

Java實現的選擇排序冒泡排序

auth main sta -i str public java index 選擇 選擇排序 package cn.hxd.sort; /** * 選擇排序 * @author Administrator * */ public class SelectionSo

Java實現深度遍歷廣度遍歷數及其應用

fss blog nac emd fan 深度遍歷 apu soc use dc9mr6賦炮炮窖屠韌http://docstore.docin.com/hhmg5158wbx7ax睪躍茁胤駁諭http://shufang.docin.com/sina_6372926856g

java實現計算器(最低版1.0,只能實現從左到右依次計算)——初學者入門

軟體:eclipse 才開始用java設計東西,我覺得這個程式適用於初步想設計計算器的同學用,雖然很簡單,但我會慢慢更新自己所寫計算器,實現更強的邏輯。 例如:61-7*4=216(從左至右的簡單邏輯,還不完善) 程式碼:(有詳細註釋) //大佬不適合看! package l

Java實現陣列去重兩陣列交併集

前言 Java平臺 陣列去重 基本的陣列去重法 HashMap實現陣列去重 兩陣列交集 基本的兩陣列求交法 HashMap版的兩陣列求交法 兩陣列並集 基本的兩陣列求並法 HashMap版的兩陣列求並法 Matlab平臺 Matlab處理陣列去重

Java實現圖的深度廣度優先遍歷演算法

<link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/

js中new函式後括號括號的區別

用new建立建構函式的例項時,通常情況下new 的建構函式後面需要帶括號(譬如:new Parent())。 有些情況下new的建構函式後帶括號和不帶括號的情況一致,譬如: function Parent(){ this.num = 1; } co

Java 實現深度遍歷廣度遍歷數及其應用

一、深度遍歷和廣度遍歷原理及實現 1、深度優先 英文縮寫為DFS即Depth First Search.其過程簡要來說是對每一個可能的分支路徑深入到不能再深入為止,而且每個節點只能訪問一次。對於上面的例子來說深度優先遍歷的結果就是:A,B,D,E,I,C,F,G

java實現直接插入排序希爾排序

直接插入排序和希爾排序,把這兩個放一起是便於記憶,這兩個排序是差不多的,希爾排序也只是對插入排序進行一點修改: 首先是看一下我們的插入排序: package sort; public class InsertSort { public static void main

Java實現檔案上傳下載

上面的博文我寫了Java對檔案操作的功能https://blog.csdn.net/qq_24380635/article/details/83273359,這次記錄一下檔案上傳和下載的功能。看看兩者有什麼不同,就可以知道檔案操作和檔案上傳下載有什麼不同了。我也是一點點懂,也

js函式括號括號賦給物件屬性的區別

注意: 1.js為物件新增函式時,不要在函式後面加()。一旦加了括號是表示將函式的返回值賦給物件的屬性。 例:function test(){   document.writeln("我是js函式") } var obj = new Object(); obj.info=new function(

java 實現kafka訊息生產者消費者

一、概述 kafka原理這東西就不再贅述了,除了官網網上也是能找到一大堆,直接上程式碼,這裡實現的基本需求是 producer類利用for迴圈來產生訊息,然後consumer類來消費這些訊息

基於memcached for java 實現通用分散式快取叢集分散式快取

前提:基於memcached client for java 的基礎進行的二次封裝,實現快取儲存的兩種模式:通用分散式快取和叢集分散式快取。以下是對於memcached client for Java 二次封裝的UML圖。 對於memcached的客戶端初始化在Ca

【資料結構】Java實現圖的DFSBFS

圖的深度優先遍歷(DFS)和廣度優先遍歷(BFS),DFS利用遞迴來實現比較易懂,DFS非遞迴就是將需要的遞迴的元素利用一個棧Stack來實現,以達到遞迴時候的順序,而BFS則是利用一個佇列Queue來實現。 package DataStructure;

Java實現圖片去噪灰度的類

用於實現對圖片去噪和灰度化。 package org.eye; import java.awt.Color; import java.awt.color.ColorSpace; import java.awt.image.BufferedImage; import jav

java實現kafka訊息傳送接收

之前寫了一篇關於kafka叢集搭建的點選開啟連結。想了解的可以看下。今天這個實現是和前面叢集對應的。使用的是新版的API。屬性如果想定製自己的,需要到官方網址上面去檢視一下對應的值。推介大家多去看看官方的介紹和demo。網上有些翻譯過來的例子並不完善,最好是知己知彼,才能百戰

java實現計算器and圖形介面

import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; im

Java實現RabbitMQ客戶端消費者端的簡單例項

1、定義RabbitMQ是流行的開源訊息佇列系統,用erlang語言開發。RabbitMQ是AMQP(高階訊息佇列協議)的標準實現。幾個概念說明:(1)Broker:簡單來說就是訊息佇列伺服器實體。(2)Exchange:訊息交換機,它指定訊息按什麼規則,路由到哪個佇列。(3