Is there an eval() function in Java?
我有一个类似以下的字符串:
1 |
现在,我必须使用字符串获取
我知道在其他一些语言中,
如何用Java做到这一点?
您可以使用
1 2 3 | ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName("js"); Object result = engine.eval("4*5"); |
也许有更好的方法,但这是可行的。
没有标准的Java类或方法可以满足您的需求。您的选择包括:
-
选择并使用一些第三方表达式评估库。例如,JEL或此处列出的六个库中的任何一个。
-
使用
eval 方法将Java类源代码中的表达式包装起来,将其发送给Java编译器,然后加载生成的编译类。 -
使用一些可以从Java调用的脚本语言作为表达式评估器。可能包括Javascript,BeanShell等。
-
从头开始编写自己的表达式求值器。
第一种方法可能是最简单的。如果您从不受信任的用户那里获取要评估的表达式,则第二种方法和第三种方法都有潜在的安全风险。 (考虑代码注入。)
在实际使用情况中,几乎没有必要或需要能够将
首先问问自己,您要评估的
-
我的程序的另一部分生成了它:因此,您希望程序的一部分决定要执行的操作的类型,而不是执行该操作,而第二部分则执行所选的操作。不必生成然后评估
String ,而是根据您的特定情况使用策略,命令或生成器设计模式。 -
它是用户输入的内容:用户可以输入任何内容,包括执行命令时可能导致程序行为异常,崩溃,公开应为机密的信息,破坏持久性信息(例如数据库的内容)等的命令。讨厌防止这种情况的唯一方法是自己解析
String ,检查它是否不是恶意的,然后对其进行评估。但是,自己解析它是请求的eval 函数将完成的大部分工作,因此您自己一无所有。更糟糕的是,检查任意Java 是否不是恶意的是不可能的,因为检查这是暂停问题。 -
它是用户输入,但是要评估的允许文本的语法和语义受到极大限制:没有任何通用工具可以轻松地为您选择的任何受限语法和语义实现通用解析器和评估器。您需要做的是为您选择的语法和语义实现一个解析器和评估器。如果任务很简单,则可以手动编写一个简单的递归下降或有限状态机解析器。如果任务很困难,则可以使用编译器(例如ANTLR)为您完成一些工作。
-
我只想实现一个桌面计算器!:一项家庭作业,对吗?如果您可以使用提供的
eval 函数实现对输入表达式的求值,那将不会是一项家庭作业,对吗?您的程序将为三行。您的讲师可能希望您为简单的算术解析器/评估器编写代码。有一个众所周知的算法,调车场,您可能会发现它很有用。
我可以建议您使用Exp4j。从以下示例代码中可以很容易理解:
1 2 3 4 5 6 | Expression e = new ExpressionBuilder("3 * sin(y) - 2 / (x - 2)") .variables("x","y") .build() .setVariable("x", 2.3) .setVariable("y", 3.14); double result = e.evaluate(); |
使用Java 9,我们可以访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | import jdk.jshell.JShell; import java.lang.StringBuilder; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.IOException; public class Eval { public static void main(String[] args) throws IOException { try(JShell js = JShell.create(); BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) { js.onSnippetEvent(snip -> { if (snip.status() == jdk.jshell.Snippet.Status.VALID) { System.out.println("?" + snip.value()); } }); System.out.print(">"); for (String line = br.readLine(); line != null; line = br.readLine()) { js.eval(js.sourceCodeAnalysis().analyzeCompletion(line).source()); System.out.print(">"); } } } } |
样品运行:
1 2 3 4 5 6 7 8 9 | > 1 + 2 / 4 * 3 ? 1 > 32 * 121 ? 3872 > 4 * 5 ? 20 > 121 * 51 ? 6171 > |
稍加操作,但这就是Java当前必须提供的功能
作为先前的答案,Java中没有为此的标准API。
您可以将groovy jar文件添加到路径中,然后groovy.util.Eval.me(" 4 * 5")完成您的工作。
不,您不能在Java(或任何编译语言)中使用通用的" eval"。除非您愿意编写Java编译器和JVM在Java程序内部执行。
是的,您可以像上面的那样拥有一些库来评估数字代数表达式-请参阅此线程进行讨论。
解决问题的一种有趣方法是自己编写一个eval()函数!
我为你做了!
您只需在代码内键入FunctionSolver.solveByX(function,value)即可??使用FunctionSolver库。函数属性是一个字符串,代表您要求解的函数,值属性是自变量的值
函数的值(必须为x)。
如果要解决一个包含多个独立变量的函数,可以使用FunctionSolver.solve(function,values),其中values属性是HashMap(String,Double),其中包含所有独立属性(如Strings)及其各自的值(作为Doubles)。
另一则信息:我已经编写了FunctionSolver的简单版本,因此它仅支持返回双精度值并接受一个或两个双精度值作为字段的Math方法(如果您好奇,只需使用FunctionSolver.usableMathMethods()即可) (这些方法是:bs,sin,cos,tan,atan2,sqrt,log,log10,pow,exp,min,max,copySign,signum,IEEEremainder,acos,asin,atan,cbrt,ceil,cosh,expm1,floor ,hypot,log1p,nextAfter,nextDown,nextUp,random,rint,sinh,tanh,toDegrees,toRadians,ulp)。而且,该库支持以下运算符:* / +-^(即使Java通常不支持^运算符)。
最后一件事:创建该库时,我必须使用反射来调用Math方法。我认为这真的很酷,如果您对此感兴趣的话,请看一下!
就是这样,这里是代码(和库):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 | package core; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; public abstract class FunctionSolver { public static double solveNumericExpression (String expression) throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { return solve(expression, new HashMap<>()); } public static double solveByX (String function, double value) throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { HashMap<String, Double> values = new HashMap<>(); values.put("x", value); return solveComplexFunction(function, function, values); } public static double solve (String function, HashMap<String,Double> values) throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { return solveComplexFunction(function, function, values); } private static double solveComplexFunction (String function, String motherFunction, HashMap<String, Double> values) throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { int position = 0; while(position < function.length()) { if (alphabetic.contains(""+function.charAt(position))) { if (position == 0 || !alphabetic.contains(""+function.charAt(position-1))) { int endIndex = -1; for (int j = position ; j < function.length()-1 ; j++) { if (alphabetic.contains(""+function.charAt(j)) && !alphabetic.contains(""+function.charAt(j+1))) { endIndex = j; break; } } if (endIndex == -1 & alphabetic.contains(""+function.charAt(function.length()-1))) { endIndex = function.length()-1; } if (endIndex != -1) { String alphabeticElement = function.substring(position, endIndex+1); if (Arrays.asList(usableMathMethods()).contains(alphabeticElement)) { //Start analyzing a Math function int closeParenthesisIndex = -1; int openedParenthesisquantity = 0; int commaIndex = -1; for (int j = endIndex+1 ; j < function.length() ; j++) { if (function.substring(j,j+1).equals("(")) { openedParenthesisquantity++; }else if (function.substring(j,j+1).equals(")")) { openedParenthesisquantity--; if (openedParenthesisquantity == 0) { closeParenthesisIndex = j; break; } }else if (function.substring(j,j+1).equals(",") & openedParenthesisquantity == 0) { if (commaIndex == -1) { commaIndex = j; }else{ throw new IllegalArgumentException("The argument of math function (which is"+alphabeticElement+") has too many commas"); } } } if (closeParenthesisIndex == -1) { throw new IllegalArgumentException("The argument of a Math function (which is"+alphabeticElement+") hasn't got the closing bracket )"); } String functionArgument = function.substring(endIndex+2,closeParenthesisIndex); if (commaIndex != -1) { double firstParameter = solveComplexFunction(functionArgument.substring(0,commaIndex),motherFunction,values); double secondParameter = solveComplexFunction(functionArgument.substring(commaIndex+1),motherFunction,values); Method mathMethod = Math.class.getDeclaredMethod(alphabeticElement, new Class< ? >[] {double.class, double.class}); mathMethod.setAccessible(true); String newKey = getNewKey(values); values.put(newKey, (Double) mathMethod.invoke(null, firstParameter, secondParameter)); function = function.substring(0, position)+newKey +((closeParenthesisIndex == function.length()-1)?(""):(function.substring(closeParenthesisIndex+1))); }else { double firstParameter = solveComplexFunction(functionArgument, motherFunction, values); Method mathMethod = Math.class.getDeclaredMethod(alphabeticElement, new Class< ? >[] {double.class}); mathMethod.setAccessible(true); String newKey = getNewKey(values); values.put(newKey, (Double) mathMethod.invoke(null, firstParameter)); function = function.substring(0, position)+newKey +((closeParenthesisIndex == function.length()-1)?(""):(function.substring(closeParenthesisIndex+1))); } }else if (!values.containsKey(alphabeticElement)) { throw new IllegalArgumentException("Found a group of letters ("+alphabeticElement+") which is neither a variable nor a Math function:"); } } } } position++; } return solveBracketsFunction(function,motherFunction,values); } private static double solveBracketsFunction (String function,String motherFunction,HashMap<String, Double> values) throws IllegalArgumentException{ function = function.replace("",""); String openingBrackets ="([{"; String closingBrackets =")]}"; int parenthesisIndex = 0; do { int position = 0; int openParenthesisBlockIndex = -1; String currentOpeningBracket = openingBrackets.charAt(parenthesisIndex)+""; String currentClosingBracket = closingBrackets.charAt(parenthesisIndex)+""; if (contOccouranceIn(currentOpeningBracket,function) != contOccouranceIn(currentClosingBracket,function)) { throw new IllegalArgumentException("Error: brackets are misused in the function"+function); } while (position < function.length()) { if (function.substring(position,position+1).equals(currentOpeningBracket)) { if (position != 0 && !operators.contains(function.substring(position-1,position))) { throw new IllegalArgumentException("Error in function: there must be an operator following a"+currentClosingBracket+" breacket"); } openParenthesisBlockIndex = position; }else if (function.substring(position,position+1).equals(currentClosingBracket)) { if (position != function.length()-1 && !operators.contains(function.substring(position+1,position+2))) { throw new IllegalArgumentException("Error in function: there must be an operator before a"+currentClosingBracket+" breacket"); } String newKey = getNewKey(values); values.put(newKey, solveBracketsFunction(function.substring(openParenthesisBlockIndex+1,position),motherFunction, values)); function = function.substring(0,openParenthesisBlockIndex)+newKey +((position == function.length()-1)?(""):(function.substring(position+1))); position = -1; } position++; } parenthesisIndex++; }while (parenthesisIndex < openingBrackets.length()); return solveBasicFunction(function,motherFunction, values); } private static double solveBasicFunction (String function, String motherFunction, HashMap<String, Double> values) throws IllegalArgumentException{ if (!firstContainsOnlySecond(function, alphanumeric+operators)) { throw new IllegalArgumentException("The function"+function+" is not a basic function"); } if (function.contains("**") | function.contains("//") | function.contains("--") | function.contains("+*") | function.contains("+/") | function.contains("-*") | function.contains("-/")) { /* * ( -+ , +- , *- , *+ , /- , /+ )> Those values are admitted */ throw new IllegalArgumentException("Operators are misused in the function"); } function = function.replace("",""); int position; int operatorIndex = 0; String currentOperator; do { currentOperator = operators.substring(operatorIndex,operatorIndex+1); if (currentOperator.equals("*")) { currentOperator+="/"; operatorIndex++; }else if (currentOperator.equals("+")) { currentOperator+="-"; operatorIndex++; } operatorIndex++; position = 0; while (position < function.length()) { if ((position == 0 && !(""+function.charAt(position)).equals("-") && !(""+function.charAt(position)).equals("+") && operators.contains(""+function.charAt(position))) || (position == function.length()-1 && operators.contains(""+function.charAt(position)))){ throw new IllegalArgumentException("Operators are misused in the function"); } if (currentOperator.contains(function.substring(position, position+1)) & position != 0) { int firstTermBeginIndex = position; while (firstTermBeginIndex > 0) { if ((alphanumeric.contains(""+function.charAt(firstTermBeginIndex))) & (operators.contains(""+function.charAt(firstTermBeginIndex-1)))){ break; } firstTermBeginIndex--; } if (firstTermBeginIndex != 0 && (function.charAt(firstTermBeginIndex-1) == '-' | function.charAt(firstTermBeginIndex-1) == '+')) { if (firstTermBeginIndex == 1) { firstTermBeginIndex--; }else if (operators.contains(""+(function.charAt(firstTermBeginIndex-2)))){ firstTermBeginIndex--; } } String firstTerm = function.substring(firstTermBeginIndex,position); int secondTermLastIndex = position; while (secondTermLastIndex < function.length()-1) { if ((alphanumeric.contains(""+function.charAt(secondTermLastIndex))) & (operators.contains(""+function.charAt(secondTermLastIndex+1)))) { break; } secondTermLastIndex++; } String secondTerm = function.substring(position+1,secondTermLastIndex+1); double result; switch (function.substring(position,position+1)) { case"*": result = solveSingleValue(firstTerm,values)*solveSingleValue(secondTerm,values); break; case"/": result = solveSingleValue(firstTerm,values)/solveSingleValue(secondTerm,values); break; case"+": result = solveSingleValue(firstTerm,values)+solveSingleValue(secondTerm,values); break; case"-": result = solveSingleValue(firstTerm,values)-solveSingleValue(secondTerm,values); break; case"^": result = Math.pow(solveSingleValue(firstTerm,values),solveSingleValue(secondTerm,values)); break; default: throw new IllegalArgumentException("Unknown operator:"+currentOperator); } String newAttribute = getNewKey(values); values.put(newAttribute, result); function = function.substring(0,firstTermBeginIndex)+newAttribute+function.substring(secondTermLastIndex+1,function.length()); deleteValueIfPossible(firstTerm, values, motherFunction); deleteValueIfPossible(secondTerm, values, motherFunction); position = -1; } position++; } }while (operatorIndex < operators.length()); return solveSingleValue(function, values); } private static double solveSingleValue (String singleValue, HashMap<String, Double> values) throws IllegalArgumentException{ if (isDouble(singleValue)) { return Double.parseDouble(singleValue); }else if (firstContainsOnlySecond(singleValue, alphabetic)){ return getValueFromVariable(singleValue, values); }else if (firstContainsOnlySecond(singleValue, alphanumeric+"-+")) { String[] composition = splitByLettersAndNumbers(singleValue); if (composition.length != 2) { throw new IllegalArgumentException("Wrong expression:"+singleValue); }else { if (composition[0].equals("-")) { composition[0] ="-1"; }else if (composition[1].equals("-")) { composition[1] ="-1"; }else if (composition[0].equals("+")) { composition[0] ="+1"; }else if (composition[1].equals("+")) { composition[1] ="+1"; } if (isDouble(composition[0])) { return Double.parseDouble(composition[0])*getValueFromVariable(composition[1], values); }else if (isDouble(composition[1])){ return Double.parseDouble(composition[1])*getValueFromVariable(composition[0], values); }else { throw new IllegalArgumentException("Wrong expression:"+singleValue); } } }else { throw new IllegalArgumentException("Wrong expression:"+singleValue); } } private static double getValueFromVariable (String variable, HashMap<String, Double> values) throws IllegalArgumentException{ Double val = values.get(variable); if (val == null) { throw new IllegalArgumentException("Unknown variable:"+variable); }else { return val; } } /* * FunctionSolver help tools: * */ private static final String alphabetic ="abcdefghilmnopqrstuvzwykxy"; private static final String numeric ="0123456789."; private static final String alphanumeric = alphabetic+numeric; private static final String operators ="^*/+-"; //--> Operators order in important! private static boolean firstContainsOnlySecond(String firstString, String secondString) { for (int j = 0 ; j < firstString.length() ; j++) { if (!secondString.contains(firstString.substring(j, j+1))) { return false; } } return true; } private static String getNewKey (HashMap<String, Double> hashMap) { String alpha ="abcdefghilmnopqrstuvzyjkx"; for (int j = 0 ; j < alpha.length() ; j++) { String k = alpha.substring(j,j+1); if (!hashMap.containsKey(k) & !Arrays.asList(usableMathMethods()).contains(k)) { return k; } } for (int j = 0 ; j < alpha.length() ; j++) { for (int i = 0 ; i < alpha.length() ; i++) { String k = alpha.substring(j,j+1)+alpha.substring(i,i+1); if (!hashMap.containsKey(k) & !Arrays.asList(usableMathMethods()).contains(k)) { return k; } } } throw new NullPointerException(); } public static String[] usableMathMethods () { /* * Only methods that: * return a double type * present one or two parameters (which are double type) */ Method[] mathMethods = Math.class.getDeclaredMethods(); ArrayList<String> usableMethodsNames = new ArrayList<>(); for (Method method : mathMethods) { boolean usable = true; int argumentsCounter = 0; Class< ? >[] methodParametersTypes = method.getParameterTypes(); for (Class< ? > parameter : methodParametersTypes) { if (!parameter.getSimpleName().equalsIgnoreCase("double")) { usable = false; break; }else { argumentsCounter++; } } if (!method.getReturnType().getSimpleName().toLowerCase().equals("double")) { usable = false; } if (usable & argumentsCounter<=2) { usableMethodsNames.add(method.getName()); } } return usableMethodsNames.toArray(new String[usableMethodsNames.size()]); } private static boolean isDouble (String number) { try { Double.parseDouble(number); return true; }catch (Exception ex) { return false; } } private static String[] splitByLettersAndNumbers (String val) { if (!firstContainsOnlySecond(val, alphanumeric+"+-")) { throw new IllegalArgumentException("Wrong passed value: <<"+val+">>"); } ArrayList<String> response = new ArrayList<>(); String searchingFor; int lastIndex = 0; if (firstContainsOnlySecond(""+val.charAt(0), numeric+"+-")) { searchingFor = alphabetic; }else { searchingFor = numeric+"+-"; } for (int j = 0 ; j < val.length() ; j++) { if (searchingFor.contains(val.charAt(j)+"")) { response.add(val.substring(lastIndex, j)); lastIndex = j; if (searchingFor.equals(numeric+"+-")) { searchingFor = alphabetic; }else { searchingFor = numeric+"+-"; } } } response.add(val.substring(lastIndex,val.length())); return response.toArray(new String[response.size()]); } private static void deleteValueIfPossible (String val, HashMap<String, Double> values, String function) { if (values.get(val) != null & function != null) { if (!function.contains(val)) { values.remove(val); } } } private static int contOccouranceIn (String howManyOfThatString, String inThatString) { return inThatString.length() - inThatString.replace(howManyOfThatString,"").length(); } } |
编写自己的库并不像您想的那么难。这是分步码算法的链接,其中逐步介绍了算法。虽然,您将必须首先解析令牌的输入。
还有两个其他问题也可以为您提供一些信息:
将字符串转换为数学表达式?
在Java中解析数学表达式的一个好的库是什么?
因为有很多答案,所以我在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | package evaluation; import java.math.BigInteger; import java.util.EmptyStackException; import java.util.Scanner; import java.util.Stack; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; public class EvalPlus { private static Scanner scanner = new Scanner(System.in); public static void main(String[] args) { System.out.println("This Evaluation is based on BODMAS rule "); evaluate(); } private static void evaluate() { StringBuilder finalStr = new StringBuilder(); System.out.println("Enter an expression to evaluate:"); String expr = scanner.nextLine(); if(isProperExpression(expr)) { expr = replaceBefore(expr); char[] temp = expr.toCharArray(); String operators ="(+-*/%)"; for(int i = 0; i < temp.length; i++) { if((i == 0 && temp[i] != '*') || (i == temp.length-1 && temp[i] != '*' && temp[i] != '!')) { finalStr.append(temp[i]); } else if((i > 0 && i < temp.length -1) || (i==temp.length-1 && temp[i] == '!')) { if(temp[i] == '!') { StringBuilder str = new StringBuilder(); for(int k = i-1; k >= 0; k--) { if(Character.isDigit(temp[k])) { str.insert(0, temp[k] ); } else { break; } } Long prev = Long.valueOf(str.toString()); BigInteger val = new BigInteger("1"); for(Long j = prev; j > 1; j--) { val = val.multiply(BigInteger.valueOf(j)); } finalStr.setLength(finalStr.length() - str.length()); finalStr.append("(" + val +")"); if(temp.length > i+1) { char next = temp[i+1]; if(operators.indexOf(next) == -1) { finalStr.append("*"); } } } else { finalStr.append(temp[i]); } } } expr = finalStr.toString(); if(expr != null && !expr.isEmpty()) { ScriptEngineManager mgr = new ScriptEngineManager(); ScriptEngine engine = mgr.getEngineByName("JavaScript"); try { System.out.println("Result:" + engine.eval(expr)); evaluate(); } catch (ScriptException e) { System.out.println(e.getMessage()); } } else { System.out.println("Please give an expression"); evaluate(); } } else { System.out.println("Not a valid expression"); evaluate(); } } private static String replaceBefore(String expr) { expr = expr.replace("(","*("); expr = expr.replace("+*","+").replace("-*","-").replace("**","*").replace("/*","/").replace("%*","%"); return expr; } private static boolean isProperExpression(String expr) { expr = expr.replaceAll("[^()]",""); char[] arr = expr.toCharArray(); Stack<Character> stack = new Stack<Character>(); int i =0; while(i < arr.length) { try { if(arr[i] == '(') { stack.push(arr[i]); } else { stack.pop(); } } catch (EmptyStackException e) { stack.push(arr[i]); } i++; } return stack.isEmpty(); } } |
请随时在这里找到更新的要点。如果有任何问题,也请发表评论。谢谢。
在JavaSE中,没有什么可以做的;您必须找到第三方库或编写自己的库。
以下解决了该问题:
1 2 3 4 |