001package conexp.fx.core.math; 002 003import java.util.Arrays; 004 005/* 006 * #%L 007 * Concept Explorer FX 008 * %% 009 * Copyright (C) 2010 - 2023 Francesco Kriegel 010 * %% 011 * This program is free software: you can redistribute it and/or modify 012 * it under the terms of the GNU General Public License as 013 * published by the Free Software Foundation, either version 3 of the 014 * License, or (at your option) any later version. 015 * 016 * This program is distributed in the hope that it will be useful, 017 * but WITHOUT ANY WARRANTY; without even the implied warranty of 018 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 019 * GNU General Public License for more details. 020 * 021 * You should have received a copy of the GNU General Public 022 * License along with this program. If not, see 023 * <http://www.gnu.org/licenses/gpl-3.0.html>. 024 * #L% 025 */ 026 027import java.util.Collection; 028import java.util.HashMap; 029import java.util.HashSet; 030import java.util.Iterator; 031import java.util.Map; 032import java.util.Set; 033import java.util.concurrent.atomic.AtomicBoolean; 034import java.util.function.Function; 035import java.util.function.Predicate; 036 037import org.apache.commons.lang3.NotImplementedException; 038 039import conexp.fx.core.context.Context; 040import conexp.fx.core.context.Implication; 041 042@FunctionalInterface 043public interface SetClosureOperator<M> extends Function<Set<M>, Set<M>> { 044 045 @Override 046 public default Set<M> apply(final Set<M> set) { 047 return closure(set); 048 } 049 050 public Set<M> closure(Set<M> set); 051 052 public default boolean close(final Set<M> set) { 053 return !set.addAll(closure(set)); 054 } 055 056 public default boolean isClosed(final Set<M> set) { 057// return set.containsAll(closure(set)); 058 return set.size() == closure(set).size(); 059 } 060 061 @SafeVarargs 062 public static <T> SetClosureOperator<T> infimum(final SetClosureOperator<T>... closureOperators) { 063 return infimum(Arrays.asList(closureOperators)); 064 } 065 066 public static <T> SetClosureOperator<T> infimum(final Iterable<SetClosureOperator<T>> closureOperators) { 067 return set -> { 068 final Set<T> closure = new HashSet<T>(); 069 final Iterator<SetClosureOperator<T>> iterator = closureOperators.iterator(); 070 if (iterator.hasNext()) 071 closure.addAll(iterator.next().closure(set)); 072 while (iterator.hasNext()) 073 closure.retainAll(iterator.next().closure(set)); 074 return closure; 075 }; 076 } 077 078 @SafeVarargs 079 public static <T> SetClosureOperator<T> supremum(final SetClosureOperator<T>... closureOperators) { 080 return supremum(Arrays.asList(closureOperators)); 081 } 082 083 public static <T> SetClosureOperator<T> supremum(final Iterable<SetClosureOperator<T>> closureOperators) { 084 return set -> { 085 final Set<T> closure = new HashSet<T>(); 086 closure.addAll(set); 087 boolean changed = true; 088 while (changed) { 089 changed = false; 090 for (SetClosureOperator<T> clop : closureOperators) 091 changed |= closure.addAll(clop.closure(closure)); 092 } 093 return closure; 094 }; 095// @Override 096// public final boolean isClosed(final Set<M> set) { 097// return closureOperators.parallelStream().allMatch( 098// clop -> clop.isClosed(set)); 099// } 100 } 101 102 public static <G, M> SetClosureOperator<M> fromContext(final Context<G, M> cxt) { 103 return set -> cxt.rowAnd(cxt.colAnd(set)); 104 } 105 106 public static <G, M> SetClosureOperator<M> 107 joiningImplications(final Context<G, M> cxt, final Set<M> premises, final Set<M> conclusions) { 108 return set -> { 109 final HashSet<M> pintersection = new HashSet<>(set); 110 pintersection.retainAll(premises); 111 final Set<M> cattributes = cxt.rowAnd(cxt.colAnd(pintersection)); 112 cattributes.retainAll(conclusions); 113 final Set<M> closure = new HashSet<>(cattributes); 114 closure.addAll(set); 115 return closure; 116 }; 117 } 118 119 public static <G, M, C extends Set<M>> C implicativeClosure( 120 final Collection<Implication<G, M>> implications, 121 final int firstPremiseSize, 122 final boolean includePseudoClosures, 123 final boolean parallel, 124 final boolean bySize, 125 final Function<Set<M>, C> supplier, 126 final Set<M> set) { 127 final C closure = supplier.apply(set);// new HashSet<M>(set); 128 final AtomicBoolean changed = new AtomicBoolean(true); 129 final Predicate<Implication<G, M>> p; 130 if (includePseudoClosures) 131 p = i -> closure.size() > i.getPremise().size() && closure.containsAll(i.getPremise()); 132 else 133 p = i -> closure.size() >= i.getPremise().size() && closure.containsAll(i.getPremise()); 134 // && (closure.size() < i.getConclusion().size() || !closure.containsAll(i.getConclusion()))) 135 if (bySize) { 136 int size; 137 if (firstPremiseSize > 0) { 138 size = closure.size(); 139 (parallel ? implications.parallelStream() : implications.stream()) 140 .filter(i -> firstPremiseSize <= i.getPremise().size()) 141 .filter(p) 142 .sequential() 143 .forEach(i -> closure.addAll(i.getConclusion())); 144 changed.set(closure.size() != size); 145 } 146 while (changed.get()) { 147 size = closure.size(); 148 changed.set(false); 149 (parallel ? implications.parallelStream() : implications.stream()) 150 .filter(p) 151 .sequential() 152 .forEach(i -> closure.addAll(i.getConclusion())); 153 changed.set(closure.size() != size); 154 } 155 } else { 156 if (firstPremiseSize > 0) { 157 (parallel ? implications.parallelStream() : implications.stream()) 158 .filter(i -> firstPremiseSize <= i.getPremise().size()) 159 .filter(p) 160 .sequential() 161 .forEach(i -> changed.set(closure.addAll(i.getConclusion()) || changed.get())); 162 } 163 while (changed.get()) { 164 changed.set(false); 165 (parallel ? implications.parallelStream() : implications.stream()) 166 .filter(p) 167 .sequential() 168 .forEach(i -> changed.set(closure.addAll(i.getConclusion()) || changed.get())); 169 } 170 } 171 return closure; 172 } 173 174 public static <G, M, C extends Set<M>> SetClosureOperator<M> fromImplications( 175 final Collection<Implication<G, M>> implications, 176 final int firstPremiseSize, 177 final boolean includePseudoClosures, 178 final boolean parallel, 179 final boolean bySize, 180 final Function<Set<M>, C> supplier) { 181 return set -> implicativeClosure( 182 implications, 183 firstPremiseSize, 184 includePseudoClosures, 185 parallel, 186 bySize, 187 supplier, 188 set); 189 } 190 191 public static <G, M> SetClosureOperator<M> fromImplications( 192 final Collection<Implication<G, M>> implications, 193 final int firstPremiseSize, 194 final boolean includePseudoClosures, 195 final boolean parallel) { 196 return fromImplications(implications, firstPremiseSize, includePseudoClosures, parallel, false, HashSet<M>::new); 197 } 198 199 public static <G, M> SetClosureOperator<M> fromImplications( 200 final Collection<Implication<G, M>> implications, 201 final boolean includePseudoClosures, 202 final boolean parallel) { 203 return fromImplications(implications, 0, includePseudoClosures, parallel); 204 } 205 206 public static <G, M> SetClosureOperator<M> fromImplications(final Collection<Implication<G, M>> implications) { 207 return fromImplications(implications, false, true); 208 } 209 210 public static <G, M> SetClosureOperator<M> 211 fromImplicationSetLinClosure(final Collection<Implication<G, M>> implications) { 212 return set -> { 213 214 // Initialization 215 final Map<Implication<G, M>, Integer> count = new HashMap<Implication<G, M>, Integer>(); 216 final Map<M, Set<Implication<G, M>>> list = new HashMap<M, Set<Implication<G, M>>>(); 217 for (M attribute : set) 218 list.put(attribute, new HashSet<Implication<G, M>>()); 219 for (Implication<G, M> implication : implications) { 220 for (M attribute : implication.getPremise()) 221 list.put(attribute, new HashSet<Implication<G, M>>()); 222 for (M attribute : implication.getConclusion()) 223 list.put(attribute, new HashSet<Implication<G, M>>()); 224 } 225 // final Multimap<M, Implication<G, M>> list = HashMultimap.create(); 226 final Set<M> newdep = new HashSet<M>(); 227 final Set<M> update = new HashSet<M>(); 228 for (Implication<G, M> implication : implications) { 229 count.put(implication, implication.getPremise().size()); 230 if (implication.getPremise().isEmpty()) 231 newdep.addAll(implication.getConclusion()); 232 for (M attribute : implication.getPremise()) { 233 if (!list.containsKey(attribute)) 234 list.put(attribute, new HashSet<Implication<G, M>>()); 235 list.get(attribute).add(implication); 236 // list.put( 237 // attribute, 238 // implication); 239 } 240 } 241 newdep.addAll(set); 242 update.addAll(set); 243 244 // Computation 245 while (!update.isEmpty()) { 246 final M attribute = update.iterator().next(); 247 update.remove(attribute); 248 for (Implication<G, M> implication : list.get(attribute)) { 249 final Integer previous = count.get(implication); 250 count.put(implication, previous - 1); 251 if (count.get(implication) == 0) { 252 final Set<M> add = new HashSet<M>(); 253 add.addAll(implication.getConclusion()); 254 add.removeAll(newdep); 255 newdep.addAll(add); 256 update.addAll(add); 257 } 258 } 259 } 260 261 return newdep; 262 }; 263 } 264 265 public static <G, M> SetClosureOperator<M> 266 fromImplicationSetDowlingGalier(final Collection<Implication<G, M>> implications) { 267 throw new NotImplementedException(""); 268 } 269 270// public Set<M> closure(final Set<M> attributes) { 271// final Set<M> closure = new HashSet<M>(attributes); 272// final Map<Implication<M>, Integer> count = new HashMap<Implication<M>, Integer>(); 273// final Multimap<M, Implication<M>> list = HashMultimap.<M, Implication<M>> create(); 274// for (Implication<M> impl : this) { 275// final int psize = impl.getPremise().size(); 276// count.put(impl, psize); 277// if (psize == 0) 278// closure.addAll(impl.getConclusion()); 279// else 280// for (M im : impl.getPremise()) 281// list.put(im, impl); 282// } 283// List<M> update = new LinkedList<M>(attributes); 284// while (!update.isEmpty()) { 285// final M m = update.remove(0); 286// for (Implication<M> impl : list.get(m)) { 287// final int newCount = count.get(impl) - 1; 288// count.put(impl, newCount); 289// if (newCount == 0) { 290// final Set<M> add = new HashSet<M>(Sets.difference(impl.getConclusion(), closure)); 291// closure.addAll(add); 292// update.addAll(add); 293// } 294// } 295// } 296// return closure; 297// } 298 299 /** 300 * @param maxCard 301 * @param baseSet 302 * @return a closure operator, whose closed sets are those that do not exceed the given maximal cardinality, or 303 * contains all elements from the base set. 304 */ 305 public static <G, M> SetClosureOperator<M> byMaximalCardinality(final int maxCard, final Collection<M> baseSet) { 306 return new SetClosureOperator<M>() { 307 308 @Override 309 public boolean isClosed(final Set<M> set) { 310 return set.size() <= maxCard || set.containsAll(baseSet); 311 } 312 313 @Override 314 public boolean close(final Set<M> set) { 315 if (isClosed(set)) 316 return true; 317 set.addAll(baseSet); 318 return false; 319 } 320 321 @Override 322 public Set<M> closure(final Set<M> set) { 323 if (isClosed(set)) 324 return new HashSet<M>(set); 325 return new HashSet<M>(baseSet); 326 } 327 328 }; 329 } 330 331 /** 332 * @param minSupp 333 * @param cxt 334 * @return a closure operator, whose closed sets are those that have an extent with at least minSupp elements in the 335 * given formal context cxt, or contain all elements from the context's codomain. 336 */ 337 public static <G, M> SetClosureOperator<M> byMinimalSupport(final int minSupp, final Context<G, M> cxt) { 338 return new SetClosureOperator<M>() { 339 340 @Override 341 public boolean isClosed(final Set<M> set) { 342 return cxt.colAnd(set).size() >= minSupp || set.containsAll(cxt.colHeads()); 343 } 344 345 @Override 346 public boolean close(final Set<M> set) { 347 if (isClosed(set)) 348 return true; 349 set.addAll(cxt.colHeads()); 350 return false; 351 } 352 353 @Override 354 public Set<M> closure(final Set<M> set) { 355 if (isClosed(set)) 356 return new HashSet<M>(set); 357 return new HashSet<M>(cxt.colHeads()); 358 } 359 360 }; 361 } 362 363 /** 364 * @param elements 365 * @param baseSet 366 * @return a closure operator, whose closed sets are those that contain all given elements. 367 */ 368 public static <G, M> SetClosureOperator<M> containsAllFrom(final Collection<M> elements, final Set<M> baseSet) { 369 return new SetClosureOperator<M>() { 370 371 @Override 372 public boolean isClosed(final Set<M> set) { 373 return set.containsAll(elements); 374 } 375 376 @Override 377 public boolean close(final Set<M> set) { 378 if (isClosed(set)) 379 return true; 380 set.addAll(elements); 381 return false; 382 } 383 384 @Override 385 public Set<M> closure(final Set<M> set) { 386 if (isClosed(set)) 387 return new HashSet<M>(set); 388 Set<M> result = new HashSet<M>(set); 389 result.addAll(elements); 390 return result; 391 } 392 393 }; 394 } 395 396 /** 397 * @param elements 398 * @param baseSet 399 * @return a closure operator, whose closed sets are those, that are subsets of elements, or contain all elements from 400 * the baseSet. 401 */ 402 public static <G, M> SetClosureOperator<M> isSubsetOf(final Collection<M> elements, final Set<M> baseSet) { 403 return new SetClosureOperator<M>() { 404 405 @Override 406 public boolean isClosed(final Set<M> set) { 407 return elements.containsAll(set); 408 } 409 410 @Override 411 public boolean close(final Set<M> set) { 412 if (isClosed(set)) 413 return true; 414 set.addAll(baseSet); 415 return false; 416 } 417 418 @Override 419 public Set<M> closure(final Set<M> set) { 420 if (isClosed(set)) 421 return new HashSet<M>(set); 422 return new HashSet<M>(baseSet); 423 } 424 425 }; 426 } 427 428 public static <M> SetClosureOperator<M> bottom() { 429 return new SetClosureOperator<M>() { 430 431 @Override 432 public final boolean isClosed(final Set<M> set) { 433 return true; 434 } 435 436 @Override 437 public final boolean close(final Set<M> set) { 438 return true; 439 } 440 441 @Override 442 public final Set<M> closure(final Set<M> set) { 443 return new HashSet<M>(set); 444 } 445 }; 446 } 447 448 public static <M> SetClosureOperator<M> top(final Set<M> baseSet) { 449 return new SetClosureOperator<M>() { 450 451 @Override 452 public final boolean isClosed(final Set<M> set) { 453 return set.containsAll(baseSet) && baseSet.containsAll(set); 454 } 455 456 @Override 457 public final boolean close(final Set<M> set) { 458 set.retainAll(baseSet); 459 return !set.addAll(baseSet); 460 } 461 462 @Override 463 public final Set<M> closure(final Set<M> set) { 464 return new HashSet<M>(baseSet); 465 } 466 }; 467 } 468 469}