/** * Copyright (c) 2009 International Health Terminology Standards Development * Organisation * * 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. */ /** * Copyright CSIRO Australian e-Health Research Centre (http://aehrc.com). * All rights reserved. Use is subject to license terms and conditions. */ package au.csiro.snorocket.core; import java.util.Arrays; import java.util.BitSet; import au.csiro.snorocket.core.util.IConceptSet; import au.csiro.snorocket.core.util.ReadonlyConceptSet; import au.csiro.snorocket.core.util.SparseConceptHashSet; import au.csiro.snorocket.core.util.SparseConceptSet; public class R extends R1 { R(int concepts, int roles) { super(concepts, roles); } R(int concepts, int roles, R rr) { super(concepts, roles, rr); } /** * Record A [ r.B * * @param A * @param r * @param B */ void store(int A, int r, int B) { getB(A, r).add(B); getA(B, r).add(A); } } abstract class AR { protected int CONCEPTS; protected int ROLES; AR(final int concepts, final int roles) { CONCEPTS = concepts; ROLES = roles + 1; } } /** * Implements R, which is of the form RoleMap<ConceptMap<ConceptSet>> * where the two maps are complete/total (ie there is an entry for every key), * thus it's faster and more space-efficient to just use an array (flattened * from 2D to 1D). * * @author law223 * */ abstract class R1 extends AR { final private BitSet currentRoles; final private IConceptSet[] base; private IConceptSet[] data; R1(final int concepts, final int roles) { this(concepts, roles, null); } /** * The initialState is assumed to be static and the local state is lazily * initialised from it as lookup* and get* are called. * * @param concepts * @param roles * @param initialState * used as a base state */ R1(int concepts, int roles, R1 initialState) { super(concepts, roles); this.data = new IConceptSet[(CONCEPTS * ROLES) << 1]; this.currentRoles = new BitSet(ROLES); // This is relatively cheap to initialise up front (unlike this.data) if (null != initialState) { this.base = initialState.data; currentRoles.or(initialState.currentRoles); } else { this.base = null; } } /** * This should only ever be called when the relationships wrap an initial * state and no other methods have been called. * * @param relationships */ public void subtract(R1 relationships) { if (null == base) { throw new AssertionError(""); } for (int i = 0; i < base.length; i++) { if (null == base[i]) { continue; } final IConceptSet set = data[i] = new SparseConceptHashSet(); set.addAll(base[i]); if (null != relationships.data[i]) { set.removeAll(relationships.data[i]); } } } public boolean containsRole(int role) { return currentRoles.get(role); } /** * Returns {B | A [ r.B} or {B | (A,B) in R(r)} * * @param A * @param r * @return */ protected IConceptSet getB(int A, int r) { if (A >= CONCEPTS) { resizeConcepts(A); } if (r >= ROLES) { resizeRoles(r); } final int index = indexOf(A, r); if (null == data[index]) { data[index] = new SparseConceptSet(); addRole(r); if (null != base && index < base.length && null != base[index]) { data[index].addAll(base[index]); } } return data[index]; } public void addRole(int r) { currentRoles.set(r); } /** * * @param B * @param r * @return Set of concepts A such that A [ r.B */ public IConceptSet lookupA(int B, int r) { if (B >= CONCEPTS || r >= ROLES) { return IConceptSet.EMPTY_SET; } final int index = indexOf(B, r) + 1; if (null == data[index]) { if (null != base && index < base.length && null != base[index]) { data[index] = new SparseConceptSet(); data[index].addAll(base[index]); return new ReadonlyConceptSet(data[index]); } else { return IConceptSet.EMPTY_SET; } } else { return new ReadonlyConceptSet(data[index]); } } /** * * @param A * @param r * @return Set of concepts B such that A [ r.B */ public IConceptSet lookupB(int A, int r) { if (A >= CONCEPTS || r >= ROLES) { return IConceptSet.EMPTY_SET; } final int index = indexOf(A, r); if (null == data[index]) { if (null != base && index < base.length && null != base[index]) { data[index] = new SparseConceptSet(); data[index].addAll(base[index]); return new ReadonlyConceptSet(data[index]); } else { return IConceptSet.EMPTY_SET; } } else { return new ReadonlyConceptSet(data[index]); } } /** * Returns {A | A [ r.B} or {A | (A,B) in R(r)} * * Lazily initialise data from base as a side-effect of this call. * * @param B * @param r * @return */ protected IConceptSet getA(int B, int r) { if (B >= CONCEPTS) { resizeConcepts(B); } if (r >= ROLES) { resizeRoles(r); } // Note the "+1" in the following line: final int index = indexOf(B, r) + 1; if (null == data[index]) { data[index] = new SparseConceptSet(); addRole(r); if (null != base && index < base.length && null != base[index]) { data[index].addAll(base[index]); } } return data[index]; } private int indexOf(int concept, int role) { if (role >= ROLES) { throw new IllegalArgumentException("role " + role + " must be smaller than " + ROLES); } return ((concept * ROLES) + role) << 1; } public void clear() { Arrays.fill(data, null); currentRoles.clear(); } private void resizeConcepts(int maxConcept) { final IConceptSet[] oldData = data; CONCEPTS = maxConcept + 1; data = new IConceptSet[(CONCEPTS * ROLES) << 1]; System.arraycopy(oldData, 0, data, 0, oldData.length); } private void resizeRoles(int maxRole) { final int OLD_ROLES = ROLES; final IConceptSet[] oldData = data; ROLES = maxRole + 1; data = new IConceptSet[(CONCEPTS * ROLES) << 1]; for (int c = 0; c < CONCEPTS; c++) { for (int r = 0; r < OLD_ROLES; r++) { final int newI = ((c * ROLES) + r) << 1; final int oldI = ((c * OLD_ROLES) + r) << 1; data[newI] = oldData[oldI]; data[newI + 1] = oldData[oldI + 1]; } } } public void grow(int newTotalConcepts) { resizeConcepts(newTotalConcepts); } public String toString() { final StringBuilder sb = new StringBuilder(); boolean separator = false; for (int index = 0; index < data.length; index += 2) { if (null != data[index]) { if (separator) { sb.append(", "); } final int hash = index >> 1; int r = (int) Math.IEEEremainder(hash, ROLES); int A = (hash - r) / ROLES; sb.append(A).append(" [ ").append(r).append(".") .append(data[index]); separator = true; } } return sb.toString(); } }