001/* 002 * Copyright 2020 nils.hoffmann. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package de.isas.lipidomics.domain; 017 018import de.isas.lipidomics.palinom.exceptions.ParsingException; 019import de.isas.lipidomics.palinom.sumformula.SumFormulaVisitorParser; 020import java.util.EnumMap; 021import java.util.Map; 022import java.util.stream.Collectors; 023 024/** 025 * Accounting table for chemical element frequency. This is used to calculate 026 * sum formulas and total masses for a given chemical element distribution, e.g. 027 * in a lipid. 028 * 029 * @author nils.hoffmann 030 */ 031public final class ElementTable extends EnumMap<Element, Integer> { 032 033 public ElementTable(Map<Element, ? extends Integer> m) { 034 super(m); 035 } 036 037 /** 038 * Creates an empty element table. 039 */ 040 public ElementTable() { 041 super(Element.class); 042 } 043 044 /** 045 * Creates the element table from the provided sum formula. If an empty 046 * string is passed in this will create an empty table. 047 * 048 * @param sumFormula the sum formula to parse. 049 * @throws ParsingException if the sum formula does not conform with the 050 * SumFormula grammar. 051 */ 052 public ElementTable(String sumFormula) throws ParsingException { 053 this(); 054 if (!sumFormula.isEmpty()) { 055 SumFormulaVisitorParser parser = new SumFormulaVisitorParser(); 056 add(parser.parse(sumFormula)); 057 } 058 } 059 060 /** 061 * Adds the element counts of the provided table to this one. 062 * 063 * @param other the table to add to this one. 064 * @return a new element table. 065 */ 066 public ElementTable add(ElementTable other) { 067 other.entrySet().stream().filter((entry) -> { 068 return entry.getValue() != null; 069 }).forEach((entry) -> { 070 incrementBy(entry.getKey(), entry.getValue()); 071 }); 072 return this; 073 } 074 075 /** 076 * Increment the count of the provided element by one. 077 * 078 * @param element the element. 079 */ 080 public void increment(Element element) { 081 merge(element, 1, Integer::sum); 082 } 083 084 /** 085 * Increment the count of the provided element by the given number. 086 * 087 * @param element the element. 088 * @param increment the increment for the element. 089 */ 090 public void incrementBy(Element element, Integer increment) { 091 merge(element, increment, Integer::sum); 092 } 093 094 /** 095 * Decrements the current count for element by one. 096 * 097 * @param element the element to decrement the counts for. 098 */ 099 public void decrement(Element element) { 100 incrementBy(element, -1); 101 } 102 103 /** 104 * Decrement the count of the provided element by the given number. 105 * 106 * @param element the element. 107 * @param decrement the decrement for the element. 108 */ 109 public void decrementBy(Element element, Integer decrement) { 110 merge(element, -decrement, Integer::sum); 111 } 112 113 /** 114 * Negates the count stored in the table. E.g. '5' will become '-5', '-5' 115 * would become '5'. 116 * 117 * @param element the element count to negate. 118 */ 119 public void negate(Element element) { 120 Integer count = getOrDefault(element, 0); 121 put(element, -1 * count); 122 } 123 124 /** 125 * Subtracts all element counts in the provided element table from this 126 * table. 127 * 128 * @param elementTable the element table to subtract from this. 129 * @return this element table. 130 */ 131 public ElementTable subtract(ElementTable elementTable) { 132 elementTable.entrySet().stream().filter((entry) -> { 133 return entry.getValue() != null; 134 }).forEach((entry) -> { 135 decrementBy(entry.getKey(), entry.getValue()); 136 }); 137 return this; 138 } 139 140 /** 141 * Returns the sum formula for all elements in this table. 142 * 143 * @return the sum formula. Returns an empty string if the table is empty. 144 */ 145 public String getSumFormula() { 146 return entrySet().stream().filter((entry) -> { 147 return entry.getValue() != null && entry.getValue() > 0; 148 }).map((entry) -> { 149 return entry.getKey().getName() + ((entry.getValue() > 1) ? entry.getValue() : ""); 150 }).collect(Collectors.joining()); 151 } 152 153 /** 154 * Returns the individual total mass for the provided element. 155 * 156 * @param element the element to calculate the total mass for. 157 * @return the total mass for the given element, or 0. 158 */ 159 public Double getMass(Element element) { 160 Integer count = getOrDefault(element, 0); 161 if (count > 0) { 162 return count.doubleValue() * element.getMass(); 163 } 164 return 0.0d; 165 } 166 167 /** 168 * Returns the total summed mass per number of elements. 169 * 170 * @return the total summed mass for this element table. Returns 0 if the 171 * table is empty. 172 */ 173 public Double getMass() { 174 return keySet().stream().map((key) -> { 175 return getMass(key); 176 }).reduce(0.0d, Double::sum); 177 } 178 179 /** 180 * Returns an copy of all mappings in this table. 181 * 182 * @return the element table copy. 183 */ 184 public ElementTable copy() { 185 return new ElementTable(this); 186 } 187 188}