001/* 002 003 ngs-fca Formal concept analysis for genomics. 004 Copyright (c) 2014-2015 National Marrow Donor Program (NMDP) 005 006 This library is free software; you can redistribute it and/or modify it 007 under the terms of the GNU Lesser General Public License as published 008 by the Free Software Foundation; either version 3 of the License, or (at 009 your option) any later version. 010 011 This library is distributed in the hope that it will be useful, but WITHOUT 012 ANY WARRANTY; with out even the implied warranty of MERCHANTABILITY or 013 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 014 License for more details. 015 016 You should have received a copy of the GNU Lesser General Public License 017 along with this library; if not, write to the Free Software Foundation, 018 Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 019 020 > http://www.gnu.org/licenses/lgpl.html 021 022*/ 023package org.nmdp.ngs.fca; 024 025import java.util.ArrayList; 026import java.util.List; 027 028import com.google.common.base.Objects; 029 030import org.dishevelled.bitset.MutableBitSet; 031 032/** 033 * Formal concept. 034 */ 035public final class Concept implements Partial<Concept> { 036 private final MutableBitSet extent; 037 private final MutableBitSet intent; 038 039 /** 040 * Construct a concept with given objects (extent) and attributes (intent). 041 * 042 * @param extent objects 043 * @param intent attributes 044 */ 045 public Concept(final MutableBitSet extent, final MutableBitSet intent) { 046 this.extent = extent; 047 this.intent = intent; 048 } 049 050 /** 051 * Retrieve the bitset representation of the concept's objects. 052 * 053 * @return extent 054 */ 055 public MutableBitSet extent() { 056 return extent; 057 } 058 059 /** 060 * Retrieve the bitset representation of the concept's attributes. 061 * 062 * @return intent 063 */ 064 public MutableBitSet intent() { 065 return intent; 066 } 067 068 /** 069 * Decode an object list from its bit membership. 070 * 071 * @param bits where each set bit represents membership in the given group 072 * @param group list of all members 073 * @return immutable list of members 074 */ 075 // todo: lists should be typed 076 public static List decode(final MutableBitSet bits, final List group) { 077 List members = new ArrayList(); 078 079 for (long i = bits.nextSetBit(0); i >= 0; i = bits.nextSetBit(i + 1)) { 080 members.add(group.get((int) i)); 081 } 082 return members; 083 } 084 085 /** 086 * Encode bit membership from a list of objects. 087 * 088 * @param members to encode 089 * @param group list of all members 090 * @return bits where each set bit represents membership in the group 091 */ 092 public static MutableBitSet encode(final List members, final List group) { 093 MutableBitSet bits = new MutableBitSet(); 094 095 for (Object object : members) { 096 int index = group.indexOf(object); 097 if (index >= 0) { 098 bits.flip(index); 099 } 100 } 101 return bits; 102 } 103 104 public static Builder builder() { 105 return new Builder(); 106 } 107 108 /** 109 * Builder class for Concept. 110 */ 111 public static final class Builder<G extends Comparable, M extends Comparable> { 112 private MutableBitSet extent, intent; 113 114 public Builder() { 115 extent = new MutableBitSet(); 116 intent = new MutableBitSet(); 117 } 118 119 public Builder withObjects(final List<G> chosen, final List<G> from) { 120 extent = encode(chosen, from); 121 return this; 122 } 123 124 public Builder withAttributes(final List<M> chosen, final List<G> from) { 125 intent = encode(chosen, from); 126 return this; 127 } 128 129 public Concept build() { 130 return new Concept(extent, intent); 131 } 132 } 133 134 /** 135 * Define the partial ordering of two concepts. 136 * 137 * @param that that concept 138 * @return partial ordering of this and that concept 139 */ 140 @Override 141 public Order relation(final Concept that) { 142 MutableBitSet meet = (MutableBitSet) new MutableBitSet().or(this.intent).and(that.intent); 143 144 if (this.intent.equals(that.intent)) { 145 return Partial.Order.EQUAL; 146 } 147 if (this.intent.equals(meet)) { 148 return Partial.Order.LESS; 149 } 150 if (that.intent.equals(meet)) { 151 return Partial.Order.GREATER; 152 } 153 return Partial.Order.NONCOMPARABLE; 154 } 155 156 @Override 157 public boolean equals(final Object right) { 158 if (!(right instanceof Concept)) { 159 return false; 160 } 161 162 if (right == this) { 163 return true; 164 } 165 166 Concept concept = (Concept) right; 167 return concept.extent == this.extent && concept.intent == this.intent; 168 } 169 170 @Override 171 public int hashCode() { 172 return Objects.hashCode(extent, intent); 173 } 174 175 @Override 176 public String toString() { 177 return intent.toString(); 178 } 179 180 @Override 181 public Concept intersect(final Concept that) { 182 MutableBitSet and = (MutableBitSet) new MutableBitSet().or(this.intent).and(that.intent); 183 return new Concept(new MutableBitSet(), and); 184 } 185 186 @Override 187 public Concept union(final Concept that) { 188 MutableBitSet or = (MutableBitSet) new MutableBitSet().or(this.extent).or(that.extent); 189 return new Concept(or, this.intent()); 190 } 191 192 @Override 193 public double measure() { 194 return extent.cardinality(); 195 } 196}