org.apache.jena.atlas.lib.tuple.TupleMap Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jena-base Show documentation
Show all versions of jena-base Show documentation
This module contains non-RDF library code and the common system runtime.
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.jena.atlas.lib.tuple;
import static java.lang.String.format;
import java.util.ArrayList ;
import java.util.Arrays;
import java.util.Collections ;
import java.util.List;
import java.util.StringJoiner;
import org.apache.jena.atlas.AtlasException;
import org.apache.jena.atlas.lib.ListUtils ;
import org.apache.jena.atlas.lib.StrUtils ;
/**
* General descriptor of a reordering (mapping) of slots in tuples
*
* Naming: map is convert to the reordered form, unmap is get back.
*
*
* map(tuple) is equivalent to
* create tuple(getSlotIdx(0) , getSlotIdx(1), ... getSlotIdx(n-1)) ;
*
*
* A {@code TupleMap} holds twp maps: the "getTransform" and the "putTransform".
* The "getTransform" is here to get the item from in the mapper Tuple.
* In the case is {@code SPO{@literal ->}POS} this is
* {@code 0{@literal <-}1, 1{@literal <-}2, 2{@literal <-}0}
* and the "putTransform" is where to place the items: {@code 0{@literal ->}2, 1{@literal ->}0, 2{@literal ->}1}.
*/
final
public class TupleMap {
/*
* Naming. getTransform (from src), putTransform(into dst)
* And these are mutual inverses: unmap process is to swap use of getTransform and putTransform
* See getSlotIdx and putSlotIdx
*
* These are then equivalent
*
* int j = getTransform[i] ; elts[i] = src.get(j) ;
* int j = putTransform[i] ; elts[j] = src.get(i) ;
*
* The code tends to use this style (getTransform - getSlotIdx, mapIdx)
* int j = getTransform[i] ;
* dst[i] = src[j] ;
*
* See apply and applyArray
*
* Warning : map and unmap here do not correspond to fetch and map in
* ColumnMap. That has confusing/inconsistent usage.
*/
// SPO->POS: get:{0<-1, 1<-2, 2<-0} put:{0->2, 1->0, 2->1}
// Map by where to fetch from source.
// For SPO -> POS, get from 1 to go into f(0)=1, f(1)=2, f(2)=0
// i.e. the location to fetch the mapped element from.
private final int[] getTransform ;
// Map by insertion into destination.
// So SPO->POS is f(0)=2, f(1)=0, f(2)=1
// i.e. the location of the element after mapping.
private final int[] putTransform ; // putTransform, insertOrder
private final int len ;
private final String label;
/**
* Construct a mapping that maps the input (one col, one char) to the output
*/
public static TupleMap create(String input, String output) {
return new TupleMap(input + "->" + output, compileMapping(input, output));
}
/**
* Construct a mapping, with label, that maps the input (one col, one char) to the output
*/
public static TupleMap create(String label, String input, String output) {
return new TupleMap(label, compileMapping(input, output));
}
/**
* Construct a mapping that maps the input to the output
*/
public static TupleMap create(String label, List input, List output) {
return new TupleMap(label, compileMapping(input, output));
}
/**
* Construct a mapping that maps the input to the output
*/
public static TupleMap create(String label, T[] input, T[] output) {
return new TupleMap(label, compileMapping(input, output));
}
/**
* Construct a mapping - the elements are the mappings of a tuple
* originally in the order 0,1,2,... so SPO{@literal ->}POS is 2,0,1 (SPO{@literal ->}POS so S{@literal ->}2,
* P{@literal ->}0, O{@literal ->}1) and not 1,2,0 (which is the extraction mapping). The label is
* just a label and is not interpreted here.
*/
private TupleMap(String label, int... elements) {
this.len = elements.length ;
this.label = label;
this.putTransform = new int[elements.length];
Arrays.fill(putTransform, -1);
this.getTransform = new int[elements.length];
Arrays.fill(getTransform, -1);
for ( int i = 0 ; i < elements.length ; i++ ) {
int x = elements[i];
if ( x < 0 || x >= elements.length )
throw new IllegalArgumentException("Out of range: " + x);
// Checking
if ( putTransform[i] != -1 || getTransform[x] != -1 )
throw new IllegalArgumentException("Inconsistent: " + ListUtils.str(elements));
putTransform[i] = x; // The elements are the putTransform.
getTransform[x] = i;
}
}
private TupleMap(String label, int[] getTransform, int[] putTransform) {
this.label = label ;
this.len = getTransform.length ;
this.getTransform = getTransform ;
this.putTransform = putTransform ;
}
/** Return a {@code TupleMap} that maps in the opposite direction
*
* this.reverseMap().map is the same as this.unmap
* this.reverseMap().unmap is the same as this.map
*
*/
public TupleMap reverse() {
return new TupleMap("Reverse:"+label, putTransform, getTransform) ;
}
/** Length of mapping */
public int length() {
return len;
}
/**
* Get the index of the i'th slot as it appears from a mapping : for
* SPO{@literal ->}POS : 0'th slot is P so 0 returns 1 (the location in the tuple before mapping)
* The 0'th mapped slot is {@code tuple.get(tupleMap.getSlotIdx(0))}.
*/
public int getSlotIdx(int idx) {
return getTransform[idx];
}
/**
* Get the index of the i'th slot as it appears after unmapping : SPO{@literal ->}POS :
* 0'th slot is S from POS so 0 returns 2
*/
public int putSlotIdx(int idx) {
return putTransform[idx];
}
/**
* Get the index of the i'th slot as it appears from a mapping : for
* SPO{@literal ->}POS : 0'th slot is P so 0 returns 1 (the location in the tuple before mapping)
* Equivalent to {@link #getSlotIdx}.
* The 0'th mapped slot is {@code tuple.get(tupleMap.mapIdx(0))}.
* Mapping a tuple is {@code map(tuple) == create(tuple.mapIdx(0) , tuple.mapIdx(1), ... tuple.mapIdx(n-1))}
*/
public int mapIdx(int idx) {
return getSlotIdx(idx) ;
}
/**
* Get the index of the i'th slot as it appears after unmapping : SPO{@literal ->}POS :
* 0'th slot is S from POS so 0 returns 2
* Equivalent to {@link #putSlotIdx}.
* The 0'th unmapped slot is {@code tuple.get(tupleMap.unmapIdx(0))}.
* Unmapping a tuple is {@code unmap(tuple) == create(tuple.unmapIdx(0) , tuple.unmapIdx(1), ... tuple.unmapIdx(n-1))}
*/
public int unmapIdx(int idx) {
return putSlotIdx(idx) ;
}
/** Apply to an unmapped tuple to get a tuple with the tuple mapping applied.
*/
public Tuple map(Tuple src) {
return apply(src, getTransform) ;
}
/** Apply to a mapped tuple to get a tuple with the tuple mapping reverse-applied. */
public Tuple unmap(Tuple src) {
return apply(src, putTransform) ;
}
/** Apply to an unmapped tuple to get a tuple with the tuple mapping applied.
* Returns the destination array.
*/
public T[] map(T[] src, T[] dst) {
if ( src == dst )
throw new IllegalArgumentException("Source and destination are the same array") ;
applyArray(src, dst, getTransform) ;
return dst ;
}
/** Apply to a mapped tuple to get a tuple with the tuple mapping reverse-applied.
* Returns the destination array.
*/
public T[] unmap(T[] src, T[] dst) {
if ( src == dst )
throw new IllegalArgumentException("Source and destination are the same array") ;
applyArray(src, dst, putTransform) ;
return dst ;
}
/** Apply an index transformation */
private static Tuple apply(Tuple src, int[] getTransform) {
if ( src.len() != getTransform.length )
throw new IllegalArgumentException("Lengths do not match: Tuple:"+src.len()+"; transform:"+getTransform.length) ;
// Fast-track 1,2,3,4 ?
// // All this to avoid the temp array.
// switch(src.len()) {
// case 0: return src ;
// case 1: return src ;
// case 2: {
// T x1 = src.get(getTransform[0]);
// T x2 = src.get(getTransform[1]);
// return TupleFactory.create2(x1, x2) ;
// }
// case 3: {
// T x1 = src.get(getTransform[0]);
// T x2 = src.get(getTransform[1]);
// T x3 = src.get(getTransform[2]);
// return TupleFactory.create3(x1, x2, x3) ;
// }
// case 4: {
// T x1 = src.get(getTransform[0]);
// T x2 = src.get(getTransform[1]);
// T x3 = src.get(getTransform[2]);
// T x4 = src.get(getTransform[3]);
// return TupleFactory.create4(x1, x2, x3, x4) ;
// }
// }
@SuppressWarnings("unchecked")
T[] elts = (T[])new Object[src.len()] ;
for ( int i = 0 ; i < src.len() ; i++ ) {
int j = getTransform[i] ;
elts[i] = src.get(j) ;
}
return TupleFactory.tuple(elts) ;
}
/** Apply an index transformation */
private static void applyArray(T[] src, T[] dst, int[] transform) {
for ( int i = 0 ; i < src.length ; i++ ) {
int j = transform[i] ;
dst[i] = src[j] ;
}
}
/**
* Apply to an unmapped tuple to get the i'th slot after mapping :
* SPO{@literal ->}POS : 0'th slot is P from SPO
*/
public T mapSlot(int idx, Tuple tuple) {
checkLength(tuple) ;
idx = getSlotIdx(idx) ;
return tuple.get(idx) ;
}
/**
* Apply to a mapped tuple to get the i'th slot as it appears after
* mapping : SPO{@literal ->}POS : 0'th slot is S from POS
*/
public T unmapSlot(int idx, Tuple tuple) {
checkLength(tuple) ;
idx = putSlotIdx(idx) ;
return tuple.get(idx);
}
/**
* Apply to an unmapped tuple to get the i'th slot after mapping :
* SPO{@literal ->}POS : 0'th slot is P from SPO
*/
public T mapSlot(int idx, T[] tuple) {
return tuple[getSlotIdx(idx)] ;
}
/**
* Apply to a mapped tuple to get the i'th slot as it appears after
* mapping : SPO{@literal ->}POS : 0'th slot is S from POS
*/
public T unmapSlot(int idx, T[] tuple) {
return tuple[putSlotIdx(idx)] ;
}
/** Compile a mapping encoded as single characters e.g. "SPO", "POS" */
private static int[] compileMapping(String domain, String range) {
List input = StrUtils.toCharList(domain);
List output = StrUtils.toCharList(range);
return compileMapping(input, output);
}
/**
* Compile a mapping, encoded two list, the domain and range of the mapping
* function
*/
private static int[] compileMapping(T[] domain, T[] range) {
return compileMapping(Arrays.asList(domain), Arrays.asList(range));
}
/** Compile a mapping */
private static int[] compileMapping(List domain, List range) {
if ( domain.size() != range.size() )
throw new AtlasException("Bad mapping: lengths not the same: " + domain + " -> " + range);
int[] cols = new int[domain.size()];
boolean[] mapped = new boolean[domain.size()];
// Arrays.fill(mapped, false) ;
for ( int i = 0 ; i < domain.size() ; i++ ) {
T input = domain.get(i);
int j = range.indexOf(input);
if ( j < 0 )
throw new AtlasException("Bad mapping: missing mapping: " + domain + " -> " + range);
if ( mapped[j] )
throw new AtlasException("Bad mapping: duplicate: " + domain + " -> " + range);
cols[i] = j;
mapped[j] = true;
}
return cols;
}
/** Access to the getTransform */
/*package-testing*/ List transformGet() {
return arrayToList(getTransform) ;
}
/** Access to the putTransform */
/*package-testing*/ List transformPut() {
return arrayToList(putTransform) ;
}
private static List arrayToList(int[] array) {
List list = new ArrayList<>(array.length) ;
for ( int x : array )
list.add(x) ;
return Collections.unmodifiableList(list) ;
}
/** Is this mapping the same (has the same effect) as {@code other}? */
public boolean sameMapping(TupleMap other) {
// Only need to check one array
return Arrays.equals(getTransform, other.getTransform) ;
}
@Override
public String toString() {
// return label ;
return format("%s:%s:%s", label, mapStr(putTransform, "->"), mapStr(getTransform, "<-"));
}
private static Object mapStr(int[] map, String arrow) {
StringJoiner j = new StringJoiner(", ", "{", "}");
for ( int i = 0 ; i < map.length ; i++ ) {
j.add(format("%d%s%d", i, arrow, map[i]));
}
return j.toString();
}
public String getLabel() {
return label;
}
private static boolean CHECKING = true ;
private final void checkLength(Tuple> tuple) {
if ( CHECKING ) {
if ( tuple.len() != length() )
throw new IllegalArgumentException("Tuple length "+tuple.len()+": not of length "+length()) ;
}
}
}