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}