///|
/// An immutable-style feature index built for repeated annotation queries.
pub struct FeatureIndex {
record : Record
entries : Array[FeatureIndexEntry]
} derive(Debug, Eq)
///|
struct FeatureIndexEntry {
position : Int
key : String
start : Int
end : Int
qualifiers : Array[Qualifier]
} derive(Debug, Eq)
///|
pub fn build_feature_index(record : Record) -> FeatureIndex {
let entries : Array[FeatureIndexEntry] = []
for position, feature in record.features {
entries.push({
position,
key: feature.key,
start: location_minimum(feature.location),
end: location_span(feature.location),
qualifiers: feature.qualifiers,
})
}
{ record, entries }
}
///|
pub fn FeatureIndex::count(self : FeatureIndex) -> Int {
self.entries.length()
}
///|
pub fn FeatureIndex::all(self : FeatureIndex) -> Array[Feature] {
self.record.features
}
///|
pub fn FeatureIndex::by_key(
self : FeatureIndex,
key : String,
) -> Array[Feature] {
let result : Array[Feature] = []
for entry in self.entries {
if entry.key == key {
result.push(self.record.features[entry.position])
}
}
result
}
///|
pub fn FeatureIndex::by_keys(
self : FeatureIndex,
keys : Array[String],
) -> Array[Feature] {
let result : Array[Feature] = []
for entry in self.entries {
if keys.contains(entry.key) {
result.push(self.record.features[entry.position])
}
}
result
}
///|
pub fn FeatureIndex::by_qualifier(
self : FeatureIndex,
name : String,
value : String,
) -> Array[Feature] {
let result : Array[Feature] = []
for entry in self.entries {
for qualifier in entry.qualifiers {
match qualifier.value {
Some(actual) if qualifier.name == name && actual == value =>
result.push(self.record.features[entry.position])
_ => ()
}
}
}
result
}
///|
pub fn FeatureIndex::with_qualifier(
self : FeatureIndex,
name : String,
) -> Array[Feature] {
let result : Array[Feature] = []
for entry in self.entries {
if entry.qualifiers.any(fn(item) { item.name == name }) {
result.push(self.record.features[entry.position])
}
}
result
}
///|
pub fn FeatureIndex::overlapping(
self : FeatureIndex,
start : Int,
end : Int,
) -> Array[Feature] {
let result : Array[Feature] = []
if start > end {
return result
}
for entry in self.entries {
if entry.start <= end && start <= entry.end {
result.push(self.record.features[entry.position])
}
}
result
}
///|
pub fn FeatureIndex::contained_in(
self : FeatureIndex,
start : Int,
end : Int,
) -> Array[Feature] {
let result : Array[Feature] = []
if start > end {
return result
}
for entry in self.entries {
if start <= entry.start && entry.end <= end {
result.push(self.record.features[entry.position])
}
}
result
}
///|
pub fn FeatureIndex::at(self : FeatureIndex, position : Int) -> Array[Feature] {
self.overlapping(position, position)
}
///|
pub fn FeatureIndex::keys(self : FeatureIndex) -> Array[String] {
let result : Array[String] = []
for entry in self.entries {
if !result.contains(entry.key) {
result.push(entry.key)
}
}
result
}
///|
pub fn FeatureIndex::positions(self : FeatureIndex, key : String) -> Array[Int] {
self.entries
.filter(fn(entry) { entry.key == key })
.map(fn(entry) { entry.position })
}
///|
pub fn FeatureIndex::qualifier_names(self : FeatureIndex) -> Array[String] {
let result : Array[String] = []
for entry in self.entries {
for qualifier in entry.qualifiers {
if !result.contains(qualifier.name) {
result.push(qualifier.name)
}
}
}
result
}
///|
pub fn FeatureIndex::summary_json(self : FeatureIndex) -> String {
let out = StringBuilder()
out.write_string("{\"count\":")
out.write_string(self.count().to_string())
out.write_string(",\"keys\":")
out.write_string(array_json(self.keys().map(fn(key) { json_string(key) })))
out.write_string(",\"qualifiers\":")
out.write_string(
array_json(self.qualifier_names().map(fn(name) { json_string(name) })),
)
out.write_string("}")
out.to_string()
}
///|
pub fn feature_key_counts(record : Record) -> Array[(String, Int)] {
let result : Array[(String, Int)] = []
for feature in record.features {
match result.search_by(fn(pair) { pair.0 == feature.key }) {
Some(index) => {
let current = result[index]
result[index] = (current.0, current.1 + 1)
}
None => result.push((feature.key, 1))
}
}
result
}
///|
pub fn feature_qualifier_counts(record : Record) -> Array[(String, Int)] {
let result : Array[(String, Int)] = []
for feature in record.features {
for qualifier in feature.qualifiers {
match result.search_by(fn(pair) { pair.0 == qualifier.name }) {
Some(index) => {
let current = result[index]
result[index] = (current.0, current.1 + 1)
}
None => result.push((qualifier.name, 1))
}
}
}
result
}
///|
pub fn feature_density(record : Record) -> Double {
let length = record.sequence_stats().length
if length == 0 {
0.0
} else {
record.features.length().to_double() / length.to_double()
}
}
///|
pub fn feature_coverage(record : Record) -> Int {
let covered : Array[Int] = []
for feature in record.features {
for position in location_coordinates(feature.location) {
if !covered.contains(position) {
covered.push(position)
}
}
}
covered.length()
}
///|
pub fn feature_coverage_percent(record : Record) -> Double {
let length = record.sequence_stats().length
if length == 0 {
0.0
} else {
feature_coverage(record).to_double() * 100.0 / length.to_double()
}
}
///|
pub fn source_feature(record : Record) -> Feature? {
match record.features.search_by(fn(feature) { feature.key == "source" }) {
Some(index) => Some(record.features[index])
None => None
}
}
///|
pub fn first_feature_by_key(record : Record, key : String) -> Feature? {
match record.features.search_by(fn(feature) { feature.key == key }) {
Some(index) => Some(record.features[index])
None => None
}
}
///|
pub fn feature_has_remote_location(feature : Feature) -> Bool {
location_is_remote(feature.location)
}
///|
pub fn feature_has_fuzzy_location(feature : Feature) -> Bool {
location_is_fuzzy(feature.location)
}