/** * Copyright 2016 Nabarun Mondal * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.noga.njexl.lang.extension; import com.noga.njexl.lang.*; import com.noga.njexl.lang.extension.datastructures.ListSet; import com.noga.njexl.lang.extension.datastructures.XList; import java.lang.reflect.Array; import java.util.*; /** * Class to handle Logical Collection Operations * Created by noga on 10/03/15. */ public final class SetOperations { public static final JexlArithmetic arithmatic = new JexlArithmetic(true); public enum SetRelation { /** * Independent */ INDEPENDENT, /** * Subset */ SUBSET, /** * Superset */ SUPERSET, /** * Equal */ EQUAL, /** * Overlapping Sets */ OVERLAP; /** * Finds if String "s" is a representation of relation "r" or not * @param s the string : * <pre> * * "><" , "I" : independent * "<>" , "O" : overlap * "=","==" , "E" : equal * "<" , "SUB" : subset * "<=" , "SUBEQ" : subset equals * ">" , "SUP" : superset * ">=" , "SUPEQ" : superset equals * * </pre> * @param r set relation * @return true if it is, false if it is not */ public static boolean is(String s, SetRelation r) { switch (s.trim().toUpperCase()) { case "><": case "I": return (INDEPENDENT == r); case "<>": case "O": return (OVERLAP == r); case "=": case "==": case "E": return (EQUAL == r); case "<": case "SUB": return (SUBSET == r); case "<=": case "SUBEQ": return (SUBSET == r || EQUAL == r); case ">": case "SUP": return (SUPERSET == r); case ">=": case "SUPEQ": return (SUPERSET == r || EQUAL == r); } return false; } } /** * Set Intersection * @param s1 set 1 * @param s2 set 2 * @return Intersection of the sets */ public static Set set_i(Set s1, Set s2) { ListSet i = new ListSet(); Set b = s1; Set s = s2; if (s2.size() > s1.size()) { b = s2; s = s1; } for (Object o : s) { if (b.contains(o)) { i.add(o); } } return i; } /** * Set Union * @param s1 set 1 * @param s2 set 2 * @return Union of the sets */ public static Set set_u(Set s1, Set s2) { Set b = s1; Set s = s2; if (s2.size() > s1.size()) { b = s2; s = s1; } ListSet u = new ListSet(b); for (Object o : s) { u.add(o); } return u; } /** * Set Difference * @param s1 Collection 1 * @param s2 Collection 2 * @return difference of the sets : s1 - s2 */ public static Set set_d(Collection s1, Collection s2) { ListSet d = new ListSet(s1); for (Object o : s2) { if (d.contains(o)) { d.remove(o); } } return d; } /** * Set Difference * @param s1 set 1 * @param s2 set 2 * @return difference of the sets : s1 - s2 */ public static Set set_d(Set s1, Set s2) { ListSet d = new ListSet(s1); for (Object o : s2) { if (d.contains(o)) { d.remove(o); } } return d; } /** * Set Symmetric Difference : s1-s2 Union s2-s1 * @param s1 set 1 * @param s2 set 2 * @return Symmetric Difference of the sets */ public static Set set_sym_d(Set s1, Set s2) { Set d12 = set_d(s1, s2); Set d21 = set_d(s2, s1); Set ssd = set_u(d12, d21); return ssd; } /** * Finds set relation between two objects, * after casting them into set * @param o1 object 1 * @param o2 object 2 * @return the relation between them */ public static SetRelation set_relation(Object o1, Object o2) { Set s1 = TypeUtility.set(o1); Set s2 = TypeUtility.set(o2); return set_relation(s1,s2); } /** * Finds relation between two sets * @param s1 set 1 * @param s2 set 2 * @return the relation between them */ public static SetRelation set_relation(Set s1, Set s2) { Set ssd = set_sym_d(s1, s2); if (ssd.isEmpty()) { return SetRelation.EQUAL; } if (s1.size() == 0) { return SetRelation.SUBSET; } if (s2.size() == 0) { return SetRelation.SUPERSET; } Set i = set_i(s1, s2); if (i.isEmpty()) { return SetRelation.INDEPENDENT; } Set ssd_1_i = set_sym_d(i, s1); if (ssd_1_i.isEmpty()) { return SetRelation.SUBSET; } Set ssd_2_i = set_sym_d(i, s2); if (ssd_2_i.isEmpty()) { return SetRelation.SUPERSET; } return SetRelation.OVERLAP; } /** * Given two sets, finds if the relation passed holds for them * @param s1 set 1 * @param s2 set 2 * @param relation the relation * @return true if s1 and s2 abide by relation, else false */ public static boolean is_set_relation(Set s1, Set s2, String relation) { SetRelation actual = set_relation(s1, s2); return SetRelation.is(relation, actual); } /** * Converts a list into a multi-set * @param args the arguments * @return a multi-set */ public static HashMap<Object, List> multiset(Object... args) { Interpreter.AnonymousParam anon = null; XList list = new XList(); if (args.length > 1) { if (args[0] instanceof Interpreter.AnonymousParam) { anon = (Interpreter.AnonymousParam) args[0]; args = TypeUtility.shiftArrayLeft(args, 1); } } for (int i = 0; i < args.length; i++) { List l = TypeUtility.from(args[i]); list.addAll(l); } HashMap<Object, List> m = new HashMap(); if (anon != null) { int i = 0; for (Object o : list) { anon.setIterationContext(list, o, i); Object ret = anon.execute(); if (!m.containsKey(ret)) { m.put(ret, new XList()); } List values = m.get(ret); values.add(o); i++; } } else { for (Object o : list) { if (!m.containsKey(o)) { m.put(o, new XList()); } List values = m.get(o); values.add(o); } } return m; } /** * Finds difference of two lists * @param l left side list * @param r right side list * @return the difference in multi-set-diff form, key : ( left_count, right_count) */ public static HashMap<Object, int[]> list_diff(Object l, Object r) { Map<Object, List> mset1 = multiset(l); Map<Object, List> mset2 = multiset(r); return mset_diff(mset1,mset2); } /** * Finds list diff with anonymous parameter * @param anon the anonymous parameter * @param mset1 multi-set 1 * @param mset2 multi-set 2 * @return the difference */ public static HashMap mset_diff(Interpreter.AnonymousParam anon, Map<Object, List> mset1, Map<Object, List> mset2) { HashMap diff = mset_diff(mset1, mset2); if ( anon == null ){ return diff; } Object context = new Object[]{ mset1, mset2 }; HashMap result = new HashMap(); for ( Object k : diff.keySet() ){ Object[] values = new Object[]{ mset1.get(k) , mset2.get(k) } ; anon.setIterationContext(context, values, k); Object ret = anon.execute(); boolean same = TypeUtility.castBoolean(ret,false) ; if ( !same ){ result.put(k,values); } } return result; } /** * Finds diff between two multi-sets * @param mset1 multi-set 1 * @param mset2 mutli-set 2 * @return the diff */ public static HashMap<Object, int[]> mset_diff(Map<Object, List> mset1, Map<Object,List> mset2) { HashMap<Object, int[]> diff = new HashMap<>(); for (Object k : mset1.keySet()) { int[] v = new int[2]; v[0] = mset1.get(k).size(); v[1] = 0; diff.put(k, v); } for (Object k : mset2.keySet()) { int[] v = diff.get(k); if (v == null) { v = new int[2]; v[1] = mset2.get(k).size(); v[0] = 0; } v[1] = mset2.get(k).size(); diff.put(k, v); } return diff; } /** * Finds relation between two lists * @param o1 left object to be casted as list * @param o2 right object to be casted as list * @return the relation between them : left R right */ public static SetRelation list_relation(Object o1, Object o2) { HashMap m1 = multiset(o1); HashMap m2 = multiset(o2); return mset_relation(m1,m2); } /** * Finds multi-set relation * @param mset1 multi-set 1 * @param mset2 multi-set 2 * @return the relation */ public static SetRelation mset_relation(Map<Object, List> mset1, Map<Object, List> mset2) { HashMap<Object, int[]> d = mset_diff(mset1, mset2); if (d.isEmpty()) { return SetRelation.EQUAL; } if (mset1.isEmpty()) { return SetRelation.SUBSET; } if (mset2.isEmpty()) { return SetRelation.SUPERSET; } boolean eq = true; boolean sub = true; boolean sup = true; boolean overlap = false; for (Object o : d.keySet()) { int[] values = d.get(o); if (values[0] != 0 && values[1] != 0) { overlap = true; } if (values[0] != values[1]) { eq = false; } if (values[0] > values[1]) { sub = false; } if (values[0] < values[1]) { sup = false; } } if (!overlap) { return SetRelation.INDEPENDENT; } if (eq) { return SetRelation.EQUAL; } if (sub) { return SetRelation.SUBSET; } if (sup) { return SetRelation.SUPERSET; } return SetRelation.OVERLAP; } /** * Finds if the two multi-sets are related by relation * @param mset1 left multi-set * @param mset2 right multi-set * @param relation the relation R to be used to compare * @return true if left R right, else false */ public static boolean is_mset_relation(Map<Object, List> mset1, Map<Object, List> mset2, String relation) { SetRelation actual = mset_relation(mset1, mset2); return SetRelation.is(relation, actual); } /** * Finds relationship between two dictionaries * @param m1 left dict * @param m2 right dict * @return relation */ public static SetRelation dict_relation(Map m1, Map m2){ SetRelation setRelation = set_relation(m1.keySet(), m2.keySet()); if ( SetRelation.INDEPENDENT == setRelation || SetRelation.OVERLAP == setRelation ){ return setRelation ; } Map s = m1; Map b = m2; if ( SetRelation.SUPERSET == setRelation ){ s = m2; b = m1; } for ( Object k : s.keySet() ){ Object l = s.get(k); Object r = b.get(k); if ( ! arithmatic.equals(l,r) ){ return SetRelation.OVERLAP ; } } return setRelation ; } /** * Checks if two dicts are abiding relation * @param m1 left dict * @param m2 right dict * @param relation relation between them * @return true if m1 R m2, else false */ public static boolean is_dict_relation(Map m1, Map m2,String relation){ SetRelation actual = dict_relation(m1, m2); return SetRelation.is(relation,actual); } /** * Finds list intersection * @param l1 list 1 * @param l2 list 2 * @return intersection */ public static List list_i(Object l1, Object l2) { HashMap m1 = multiset(l1); HashMap m2 = multiset(l2); HashMap<Object, int[]> d = mset_diff(m1, m2); XList l = new XList(); for (Object o : d.keySet()) { int[] var = d.get(o); int t = Math.min(var[0], var[1]); for (int i = 0; i < t; i++) { l.add(o); } } return l; } /** * Finds list union * @param l1 list 1 * @param l2 list 2 * @return union */ public static List list_u(Object l1, Object l2) { HashMap m1 = multiset(l1); HashMap m2 = multiset(l2); HashMap<Object, int[]> d = mset_diff(m1, m2); XList l = new XList(); for (Object o : d.keySet()) { int[] var = d.get(o); int t = Math.max(var[0], var[1]); for (int i = 0; i < t; i++) { l.add(o); } } return l; } /** * Finds list difference * @param l1 list 1 * @param l2 list 2 * @return difference */ public static List list_d(Object l1, Object l2) { HashMap m1 = multiset(l1); HashMap m2 = multiset(l2); HashMap<Object, int[]> d = mset_diff(m1, m2); XList l = new XList(); for (Object o : d.keySet()) { int[] var = d.get(o); int t = var[0] - var[1]; for (int i = 0; i < t; i++) { l.add(o); } } return l; } /** * Finds list symmetric difference * @param l1 list 1 * @param l2 list 2 * @return symmetric difference */ public static List list_sym_d(Object l1, Object l2) { List i = list_i(l1, l2); List u = list_u(l1, l2); return list_d(u, i); } /** * Finds list join * @param a list 1 * @param b list 2 * @return join of the two lists */ public static List join(Object a, Object b) { List left = TypeUtility.from(a); List right = TypeUtility.from(b); if (left == null || right == null) { return null; } int ls = left.size(); int rs = right.size(); if (ls == 0) { return right; } if (rs == 0) { return left; } XList r = new XList(); for (int i = 0; i < ls; i++) { List item = TypeUtility.from(left.get(i)); for (int j = 0; j < rs; j++) { XList l = new XList(item); l.add(right.get(j)); r.add(l); } } return r; } public static final String SEP = "\u2205" ; /** * Finds list division : * See more here : http://www.mathcs.emory.edu/~cheung/Courses/377/Syllabus/4-RelAlg/division.html * Not enirely sure if the algorithm is correct in any case * @param a list 1 * @param b list 2 * @return division of the two lists */ public static List division(Object a, Object b) { List l = TypeUtility.from(a); List r = TypeUtility.from(b); HashMap<String,Object> left = new HashMap<>(); for ( Object o : l ){ String i = TypeUtility.castString(o, SEP); left.put(i,o); } HashMap<String,Object> right = new HashMap<>(); for ( Object o : r ){ String i = TypeUtility.castString(o, SEP); right.put(i,o); } HashMap<String,Integer> tmp = new HashMap<>(); HashMap<String,List> result = new HashMap<>(); for ( String leftKey : left.keySet() ){ for ( String rightKey : right.keySet() ){ int i = leftKey.lastIndexOf( SEP + rightKey ); if ( i >= 0 ){ // here is a match : String key = leftKey.substring(0,i); if ( !tmp.containsKey(key)){ tmp.put(key,0); List item = list_d( left.get(leftKey) , right.get(rightKey) ); result.put(key, item ); } int n = tmp.get(key); tmp.put( key, n + 1 ); } } } List div = new ArrayList<>(); int count = right.size(); for ( String key : result.keySet() ){ int c = tmp.get(key); if ( c == count ){ List item = result.get(key); if ( item.size() == 1 ){ div.add( item.get(0)); }else{ div.add(item); } } } if ( div.isEmpty() ) return Collections.EMPTY_LIST ; return div; } /** * Arbitrary list join * @param args the lists * @return a joined list of lists */ public static List join_c(Object... args) { Interpreter.AnonymousParam anon = null; XList join = new XList(); if (args.length > 1) { if (args[0] instanceof Interpreter.AnonymousParam) { anon = (Interpreter.AnonymousParam) args[0]; args = TypeUtility.shiftArrayLeft(args, 1); } } // the list of lists over which join would happen XList[] argsList = new XList[args.length]; for (int i = 0; i < args.length; i++) { argsList[i] = TypeUtility.from(args[i]); } Iterator[] iterators = new Iterator[argsList.length]; for (int i = 0; i < argsList.length; i++) { Iterator itr = argsList[i].iterator(); iterators[i] = itr; } Object[] tuple = new Object[argsList.length]; for (int i = iterators.length - 1; i >= 0; i--) { tuple[i] = iterators[i].next(); } int c = 0; boolean broken = false ; if (anon != null) { anon.setIterationContextWithPartial(argsList, tuple, c++,join); Object r = anon.execute(); if ( r instanceof JexlException.Continue ){ r = false ; } else if ( r instanceof JexlException.Break ){ JexlException.Break br = (JexlException.Break)r; r = true ; if ( br.hasValue ){ r = br.value ; } broken = true ; } if (TypeUtility.castBoolean(r, false)) { Object t = anon.getVar(Script._ITEM_); ; if ( t instanceof Object[] ){ join.add( new XList<>( (Object[])t ) ) ; }else{ join.add(t); } } } else { XList t = new XList(tuple); join.add(t); } boolean emptied = false; while (!broken) { emptied = true; for (int i = iterators.length - 1; i >= 0; i--) { Iterator itr = iterators[i]; if (!itr.hasNext()) { itr = argsList[i].iterator(); iterators[i] = itr; tuple[i] = itr.next(); continue; } tuple[i] = itr.next(); emptied = false; break; } if (emptied) { break; } if (anon != null) { anon.setIterationContextWithPartial(argsList, tuple, c++,join); Object r = anon.execute(); if ( r instanceof JexlException.Continue ){ r = false ; } else if ( r instanceof JexlException.Break ){ JexlException.Break br = (JexlException.Break)r; r = true ; if ( br.hasValue ){ r = br.value ; } broken = true ; } if (TypeUtility.castBoolean(r, false)) { Object t = anon.getVar(Script._ITEM_); ; if ( t instanceof Object[] ){ join.add( new XList<>( (Object[])t ) ) ; }else{ join.add(t); } } } else { XList t = new XList(tuple); join.add(t); } } return join; } /** * Given object c1 does it in some sense inside c2 or not * @param c1 the object which is to be inside * @param c2 the proposed container * @return true if it is, false otherwise */ public static Boolean in(Object c1, Object c2) { if (c2 == null) { return false; } if (c2 instanceof String) { if (c1 == null) { return false; } return ((String) c2).contains(c1.toString()); } if (c2 instanceof Set) { if (c1 instanceof Set) { return is_set_relation((Set) c1, (Set) c2, "<="); } if ( JexlArithmetic.isListOrArray(c1) ){ return arithmatic.lessThanOrEqual(c1,c2); } return ((Set) c2).contains(c1); } if ( c2 instanceof Map ){ if ( c1 instanceof Map ){ return is_dict_relation((Map)c1,(Map)c2,"<="); } if ( c1 instanceof Map.Entry ){ return ((Map)c2).entrySet().contains(c1); } if ( JexlArithmetic.isListOrSetOrArray(c1) ){ return in(c1,((Map)c2).keySet() ); } return ((Map) c2).containsKey(c1); } if (c2 instanceof Collection || c2.getClass().isArray()) { if (c1 != null && (c1 instanceof Collection || c1.getClass().isArray())) { Map m1 = multiset(c1); Map m2 = multiset(c2); return is_mset_relation(m1,m2,"<="); } if (c2 instanceof Collection) { return ((Collection) c2).contains(c1); } else { int len = Array.getLength(c2); for (int i = 0; i < len; i++) { Object o = Array.get(c2, i); if (arithmatic.equals(o, c1)) { return true; } } } } if ( c2 instanceof Iterator ){ Iterator itr = ((Iterator)c2) ; while ( itr.hasNext() ){ Object o = itr.next() ; if ( arithmatic.equals(o,c1) ){ return true ; } } } return false; } /** * Subtract one object from a dictionary * @param m1 the map object * @param o the object to be subtracted * @return a map object */ public static Map dict_subtract(Map m1, Object o){ Map m = new HashMap<>(); if ( o instanceof Map){ Map m2 = (Map)o; for ( Object k : m1.keySet() ){ Object v1 = m1.get(k); if ( m2.containsKey(k) ){ Object v2 = m2.get(k); boolean e = arithmatic.equals(v1,v2); if ( e ){ continue; } } // add that pair m.put(k, v1); } } else { Set s = TypeUtility.set(o); for ( Object k : m1.keySet() ){ Object v1 = m1.get(k); if ( s.contains(k) ){ continue; } // add that pair m.put(k, v1); } } return m; } /** * Generic union of 2 dictionaries * @param m1 : left map * @param m2 : right map * @return a map where all the tuples are present * When values match, one entry is added * When values do not match, an array of size 2 [left_value, right_value] is added */ public static Map dict_u(Map m1, Map m2){ Set u = set_u(m1.keySet(), m2.keySet() ); Map um = new HashMap<>(); for ( Object k : u ){ Object v1 = m1.get(k); Object v2 = m2.get(k); boolean left = m1.containsKey(k); boolean right = m2.containsKey(k); if ( left && right ) { if ( arithmatic.equals(v1,v2) ) { um.put(k, v1); }else{ um.put(k, new XList.Pair(v1,v2)); } continue; } if ( left ){ um.put(k, v1); }else{ um.put(k, v2); } } return um; } /** * Generic intersection of 2 dictionaries * @param m1 : left map * @param m2 : right map * @return a map where intersecting keys of the tuples are present : only when values match too */ public static Map dict_i(Map m1, Map m2){ Set i = set_i(m1.keySet(), m2.keySet() ); Map im = new HashMap<>(); for ( Object k : i ){ Object v1 = m1.get(k); Object v2 = m2.get(k); if ( arithmatic.equals(v1,v2) ){ im.put(k,v1); } } return im; } /** * Generic symmetric diff of 2 dictionaries * @param m1 : left map * @param m2 : right map * @return a map where all the symmetric diff tuples are present */ public static Map dict_sym_d(Map m1, Map m2){ Set sd = set_sym_d(m1.keySet(), m2.keySet()); Map sdm = new HashMap<>(); for ( Object k : sd ){ if ( m1.containsKey(k)){ sdm.put(k, m1.get(k)); continue; } if ( m2.containsKey(k)){ sdm.put(k, m2.get(k)); } } return sdm; } public static Set dict_divide(Map m, Object o){ Set s = new ListSet<>(); for ( Object k : m.keySet() ){ Object v = m.get(k); if ( arithmatic.equals(o,v)){ s.add(k); } } return s; } }