"A [[List]] which can be efficiently searched for occurrences of a given element, or for inclusions of a given sublist of elements. This interface provides operations for finding: - _occurrences_ of a single value in the list, and - _inclusions_ of a given sublist of values in the list. Occurrences and inclusions are identified by a list index at which the value or sublist of values occurs in the list. In the case of an inclusion, it is the index of the first matching value from the sublist. Inclusions may overlap. For example: \"yoyoyoyoyo\".inclusions(\"yoy\") produces the stream `{ 0, 2, 4, 6 }`. An empty list is considered to be included at every index, including the index [[size]] at the very end of the list. Thus: \"hello\".inclusions(\"\") produces the stream `{ 0, 1, 2, 3, 4, 5 }`, with 6 inclusions in a string of length 5. In particular: - `\"\".inclusions(\"x\")` is the empty stream `{}`, but - `\"\".inclusions(\"\")` is the stream `{ 0 }`." see (`class String`, `class Array`) since("1.2.0") shared interface SearchableList<Element> satisfies List<Element> { "Determines if the given [[value|element]] occurs at the given index in this list." shared default Boolean occursAt( "The index at which the value might occur." Integer index, "The value. If null, it is considered to occur at any index in this list with a null element." Element element) => let (elem = getFromFirst(index)) if (exists element, exists elem) then elem == element else element exists == elem exists; "The indexes in this list at which the given [[value|element]] occurs." shared default {Integer*} occurrences( "The value. If null, it is considered to occur at any index in this list with a null element." Element element, "The smallest index to consider." Integer from = 0, "The number of indexes to consider." Integer length = size-from) //performance of comprehension was //worse than hand-written iterable //=> { for (i in from:length) // if (occursAt(i, element)) i }; => object satisfies Iterable<Integer> { value len = outer.size; value max = from+length>len then len else from+length; iterator() => object satisfies Iterator<Integer> { variable value index = from; shared actual Integer|Finished next() { while (index<max) { if (occursAt(index, element)) { return index++; } else { index++; } } return finished; } }; shared actual Integer size { variable value size = 0; for (index in from:max-from) { if (occursAt(index,element)) { size++; } } return size; } empty => occurs(element, from, length); first => firstOccurrence(element, from, length); last => lastOccurrence(element, from, length); }; "Determines if the given [[value|element]] occurs as an element of this list, at any index that falls within the segment `from:length` defined by the optional [[starting index|from]] and [[length]]." shared default Boolean occurs( "The value. If null, it is considered to occur at any index in this list with a null element." Element element, "The smallest index to consider." Integer from = 0, "The number of indexes to consider." Integer length = size-from) => firstOccurrence(element, from, length) exists; "The first index in this list at which the given [[value|element]] occurs, that falls within the segment `from:length` defined by the optional [[starting index|from]] and [[length]]." shared default Integer? firstOccurrence( "The value. If null, it is considered to occur at any index in this list with a null element." Element element, "The smallest index to consider." Integer from = 0, "The number of indexes to consider." Integer length = size-from) { for (index in from:length) { if (occursAt(index,element)) { return index; } } else { return null; } } "The last index in this list at which the given [[value|element]] occurs, that falls within the range `size-length-from:length` defined by the optional [[starting index|from]], interpreted as a reverse index counting from the _end_ of the list, and [[length]]." shared default Integer? lastOccurrence( "The value. If null, it is considered to occur at any index in this list with a null element." Element element, "The smallest index to consider, interpreted as a reverse index counting from the _end_ of the list, where `0` is the last element of the list, and `size-1` is the first element of the list." Integer from = 0, "The number of indexes to consider." Integer length = size-from) { //TODO: refine reversed to return a SearchableList // => reversed.firstOccurrence(element, from, length) for (index in (size-length-from:length).reversed) { if (occursAt(index,element)) { return index; } } else { return null; } } "Determine if the given [[list|sublist]] occurs as a sublist at the given index of this list." shared default Boolean includesAt( "The index at which the [[sublist]] might occur." Integer index, List<Element> sublist) { if (0 <= index <= size-sublist.size) { variable value i = index; for (element in sublist) { if (exists element) { if (!occursAt(i, element)) { return false; } } else { assert (is Element null); if (!occursAt(i, null)) { return false; } } i++; } else { return true; } } else { return false; } } "The indexes in this list at which the given [[list|sublist]] occurs as a sublist, that are greater than or equal to the optional [[starting index|from]]." shared default {Integer*} inclusions(List<Element> sublist, "The smallest index to consider." Integer from = 0) //performance of comprehension was //worse than hand-written iterable /*=> { for (i in from:size+1-from-sublist.size) if (includesAt(i, sublist)) i };*/ => object satisfies Iterable<Integer> { value len = outer.size; iterator() => object satisfies Iterator<Integer> { value max = len+1-sublist.size; variable value index = from; shared actual Integer|Finished next() { while (index<max) { if (includesAt(index, sublist)) { return index++; } else { index++; } } return finished; } }; shared actual Integer size { variable value size = 0; for (index in from:len-from+1-sublist.size) { if (includesAt(index,sublist)) { size++; } } return size; } empty => includes(sublist, from); first => firstInclusion(sublist, from); last => lastInclusion(sublist, from); }; "Determine if the given [[list|sublist]] occurs as a sublist at some index in this list, at any index that is greater than or equal to the optional [[starting index|from]]." shared default Boolean includes(List<Element> sublist, "The smallest index to consider." Integer from = 0) => firstInclusion(sublist, from) exists; "The first index in this list at which the given [[list|sublist]] occurs as a sublist, that is greater than or equal to the optional [[starting index|from]]." shared default Integer? firstInclusion(List<Element> sublist, "The smallest index to consider." Integer from = 0) { for (index in from:size-from+1-sublist.size) { if (includesAt(index,sublist)) { return index; } } else { return null; } } "The last index in this list at which the given [[list|sublist]] occurs as a sublist, that falls within the range `0:size-from+1-sublist.size` defined by the optional [[starting index|from]], interpreted as a reverse index counting from the _end_ of the list." shared default Integer? lastInclusion(List<Element> sublist, "The smallest index to consider, interpreted as a reverse index counting from the _end_ of the list, where `0` is the last element of the list, and `size-1` is the first element of the list." Integer from = 0) { //TODO: refine reversed to return a SearchableList // => reversed.firstInclusion(element, from, length) for (index in (0:size-from+1-sublist.size).reversed) { if (includesAt(index,sublist)) { return index; } } else { return null; } } }