Range modifications
The increasing range of all integers from a lower to an upper bound, including
each boundary, is shown by the lower-boundary separated from the
higher-boundary by a single dash. So 64-1234
is the range of all integers
from 64 to 1234, (including both 64 and 1234). 10-10
is the range covering
the single integer 10.
A sequence of ranges is shown by successive integer ranges that do not overlap,
and which have increasing lower bounds. Each range is separated by a single
comma. So 13-14,22-22,100999999-101000001
is a sequence of ranges that
contain the integers 13 14 22 100999999 101000000 and 101000001.
Empty ranges are removed. An empty sequence has an empty string representation.
Note: There are NO internal spaces in the sequence format.
- Task
Given an initial sequence of ranges write programs to add or remove an integer
from the sequence and display the resultant sequence.
Note:
- The initial sequence may be empty.
- Adding an int that is already covered should not change the sequence.
- removing an int that is not in the sequence should not change the sequence.
- The add and remove operations should print their result in the standard form mentioned.
- Solutions must work by modifying range boundaries, splitting and joining, as well as creating and deleting ranges.
Do not use algorithms that create and modify arrays of all the integer values within ranges.
Show the results, (including intermediate results), of performing the following
steps
- Ex0
Start with "" add 77 add 79 add 78 remove 77 remove 78 remove 79
- Ex1
Start with "1-3,5-5" add 1 remove 4 add 7 add 8 add 6 remove 7
- Ex2
Start with "1-5,10-25,27-30" add 26 add 9 add 7 remove 26 remove 9 remove 7
Requires Gdip Library
11l
T Sequence
[(Int, Int)] ranges
F (sequence_string)
I sequence_string != ‘’
L(r) sequence_string.split(‘,’)
V (b, e) = r.split(‘-’).map(x -> Int(x))
.ranges [+]= (b, e)
assert(.ranges == sorted(.ranges), ‘Sequence order error’)
F remove(rem)
L(&r) .ranges
V i = L.index
I rem C r[0] .. r[1]
I r[0] == rem
I r[1] > rem
r[0]++
E
.ranges.pop(i)
E I r[1] == rem
I r[0] < rem
r[1]--
E
.ranges.pop(i)
E
V (r1, splitrange) = (rem - 1, (rem + 1, r[1]))
r[1] = r1
.ranges.insert(i + 1, splitrange)
L.break
I r[0] > rem
L.break
F add(add)
L(&r) .ranges
V i = L.index
I add C r[0] .. r[1]
L.break
E I r[0] - 1 == add
r[0] = add
L.break
E I r[1] + 1 == add
r[1] = add
L.break
E I r[0] > add
.ranges.insert(i, (add, add))
L.break
L.was_no_break
.ranges.append((add, add))
.consolidate()
F consolidate()
‘Combine overlapping ranges’
L(i) 0 .< .ranges.len - 1
V this = i
V that = i + 1
I .ranges[this][1] + 1 >= .ranges[that][0]
I .ranges[this][1] >= .ranges[that][1]
.ranges[that] = .ranges[this]
.ranges[this] = (-1, -1)
E
.ranges[that] = (.ranges[this][0], .ranges[that][1])
.ranges[this] = (-1, -1)
.ranges = .ranges.filter(r -> r != (-1, -1))
F String()
R .ranges.map(r -> r[0]‘-’r[1]).join(‘,’)
F demo(opp_txt)
V by_line = opp_txt.split("\n")
V start = by_line.pop(0)
V ex = Sequence(start.split(‘ ’).last[1 .< (len)-1])
V lines = by_line.map(line -> line.ltrim(‘ ’).split(‘ ’))
V opps = lines.map(word -> (word[0], Int(word[1])))
print(‘Start: "’ex‘"’)
L(op, val) opps
I op == ‘add’
ex.add(val)
E
assert(op == ‘remove’)
ex.remove(val)
print(‘ #6 #2 => #.’.format(op, val, ex))
print()
demo(‘ Start with ""
add 77
add 79
add 78
remove 77
remove 78
remove 79’)
demo(‘ Start with "1-3,5-5"
add 1
remove 4
add 7
add 8
add 6
remove 7’)
demo(‘ Start with "1-5,10-25,27-30"
add 26
add 9
add 7
remove 26
remove 9
remove 7’)
- Output:
Start: "" add 77 => 77-77 add 79 => 77-77,79-79 add 78 => 77-79 remove 77 => 78-79 remove 78 => 79-79 remove 79 => Start: "1-3,5-5" add 1 => 1-3,5-5 remove 4 => 1-3,5-5 add 7 => 1-3,5-5,7-7 add 8 => 1-3,5-5,7-8 add 6 => 1-3,5-8 remove 7 => 1-3,5-6,8-8 Start: "1-5,10-25,27-30" add 26 => 1-5,10-30 add 9 => 1-5,9-30 add 7 => 1-5,7-7,9-30 remove 26 => 1-5,7-7,9-25,27-30 remove 9 => 1-5,7-7,10-25,27-30 remove 7 => 1-5,10-25,27-30
Action!
DEFINE PTR="CARD"
TYPE Range=[BYTE low,high]
TYPE Ranges=[
PTR data
INT count]
PROC InitRanges(Ranges POINTER rs PTR d)
rs.data=d rs.count=0
RETURN
PROC CheckIndex(Ranges POINTER rs INT index)
IF index<0 OR index>=rs.count THEN
Break()
FI
RETURN
PTR FUNC GetItemPtr(Ranges POINTER rs INT index)
CheckIndex(rs,index)
RETURN (rs.data+2*index)
PROC PrintRanges(Ranges POINTER rs)
Range POINTER r
INT i
IF rs.count=0 THEN
Print("empty")
ELSE
FOR i=0 TO rs.count-1
DO
IF i>0 THEN Print(",") FI
r=GetItemPtr(rs,i)
PrintF("%B-%B",r.low,r.high)
OD
FI
RETURN
PROC AppendRange(Ranges POINTER rs BYTE l,h)
Range POINTER r
rs.count==+1
r=GetItemPtr(rs,rs.count-1)
r.low=l r.high=h
RETURN
PROC InsertRange(Ranges POINTER rs INT index BYTE l,h)
Range POINTER r1,r2
INT i
IF index=rs.count THEN
AppendRange(rs,l,h)
RETURN
FI
CheckIndex(rs,index)
rs.count==+1
i=rs.count-1
WHILE i>index
DO
r1=GetItemPtr(rs,i-1)
r2=GetItemPtr(rs,i)
r2.low=r1.low r2.high=r1.high
i==-1
OD
r1=GetItemPtr(rs,index)
r1.low=l r1.high=h
RETURN
PROC DeleteRange(Ranges POINTER rs INT index)
Range POINTER r1,r2
INT i
CheckIndex(rs,index)
FOR i=index TO rs.count-2
DO
r1=GetItemPtr(rs,i+1)
r2=GetItemPtr(rs,i)
r2.low=r1.low r2.high=r1.high
OD
rs.count==-1
RETURN
BYTE FUNC InRange(Range POINTER r BYTE n)
IF r.low<=n AND n<=r.high THEN
RETURN (1)
FI
RETURN (0)
INT FUNC FindRange(Ranges POINTER rs BYTE n)
Range POINTER r
INT i
FOR i=0 TO rs.count-1
DO
r=GetItemPtr(rs,i)
IF n<=r.high THEN
RETURN (i)
FI
OD
RETURN (rs.count)
PROC Add(Ranges POINTER rs BYTE n)
Range POINTER r,r2
INT i
IF rs.count=0 THEN
AppendRange(rs,n,n) RETURN
FI
FOR i=0 TO rs.count-1
DO
r=GetItemPtr(rs,i)
IF n<r.low-1 THEN
InsertRange(rs,i,n,n) RETURN
ELSEIF n=r.low-1 THEN
r.low=n RETURN
ELSEIF n<=r.high THEN
RETURN
ELSEIF n=r.high+1 THEN
r.high=n
IF i<rs.count-1 THEN
r2=GetItemPtr(rs,i+1)
IF n=r2.low OR n+1=r2.low THEN
r.high=r2.high
DeleteRange(rs,i+1)
FI
FI
RETURN
ELSEIF i=rs.count-1 THEN
AppendRange(rs,n,n)
RETURN
FI
OD
RETURN
PROC Remove(Ranges POINTER rs BYTE n)
Range POINTER r
BYTE h
INT i
IF rs.count=0 THEN
RETURN
FI
FOR i=0 TO rs.count-1
DO
r=GetItemPtr(rs,i)
IF n<=r.low-1 THEN
RETURN
ELSEIF n=r.low THEN
r.low=n+1
IF r.low>r.high THEN
DeleteRange(rs,i)
FI
RETURN
ELSEIF n<r.high THEN
h=r.high
r.high=n-1
InsertRange(rs,i+1,n+1,h)
RETURN
ELSEIF n=r.high THEN
r.high=n-1
RETURN
FI
OD
RETURN
PROC TestAdd(Ranges POINTER rs BYTE n)
PrintF("%E Add %B -> ",n)
Add(rs,n)
PrintRanges(rs)
RETURN
PROC TestRemove(Ranges POINTER rs BYTE n)
PrintF("%E Remove %B -> ",n)
Remove(rs,n)
PrintRanges(rs)
RETURN
PROC Main()
CARD ARRAY d(20)
Ranges rs
InitRanges(rs,d)
PrintRanges(rs)
TestAdd(rs,77)
TestAdd(rs,79)
TestAdd(rs,78)
TestRemove(rs,77)
TestRemove(rs,78)
TestRemove(rs,79)
PutE() PutE()
InitRanges(rs,d)
AppendRange(rs,1,3)
AppendRange(rs,5,5)
PrintRanges(rs)
TestAdd(rs,1)
TestRemove(rs,4)
TestAdd(rs,7)
TestAdd(rs,8)
TestAdd(rs,6)
TestRemove(rs,7)
PutE() PutE()
InitRanges(rs,d)
AppendRange(rs,1,5)
AppendRange(rs,10,25)
AppendRange(rs,27,30)
PrintRanges(rs)
TestAdd(rs,26)
TestAdd(rs,9)
TestAdd(rs,7)
TestRemove(rs,26)
TestRemove(rs,9)
TestRemove(rs,7)
RETURN
- Output:
Screenshot from Atari 8-bit computer
empty Add 77 -> 77-77 Add 79 -> 77-77,79-79 Add 78 -> 77-79 Remove 77 -> 78-79 Remove 78 -> 79-79 Remove 79 -> empty 1-3,5-5 Add 1 -> 1-3,5-5 Remove 4 -> 1-3,5-5 Add 7 -> 1-3,5-5,7-7 Add 8 -> 1-3,5-5,7-8 Add 6 -> 1-3,5-8 Remove 7 -> 1-3,5-6,8-8 1-5,10-25,27-30 Add 26 -> 1-5,10-30 Add 9 -> 1-5,9-30 Add 7 -> 1-5,7-7,9-30 Remove 26 -> 1-5,7-7,9-25,27-30 Remove 9 -> 1-5,7-7,10-25,27-30 Remove 7 -> 1-5,10-25,27-30
AutoHotkey
RangeModifications(arr, Modify, v){
global steps ; optional line to track examples steps
if (Modify = "add")
arr.push([v, v])
if (Modify = "remove")
for i, obj in arr
if (v >= (start := obj.1)) && (v <= (stop := obj.2))
{
arr.RemoveAt(i)
if (start = v) && (v = stop)
continue
arr.push(start < v ? [start, v-1] : [v+1, v+1])
arr.push(v < stop ? [v+1, stop] : [v-1, v-1])
}
result := RangeConsolidation(arr)
steps .= Modify "`t" v "`t:`t" obj2string(result) "`n" ; optional line to track examples steps
return result
}
RangeConsolidation(arr){ ;-) borrowed my own function from http://www.rosettacode.org/wiki/Range_consolidation#AutoHotkey
arr1 := [], arr2 := [], result := []
for i, obj in arr
arr1[i,1] := min(arr[i]*), arr1[i,2] := max(arr[i]*) ; sort each range individually
for i, obj in arr1
if (obj.2 > arr2[obj.1])
arr2[obj.1] := obj.2 ; creates helper array sorted by range
i := 1
for start, stop in arr2
if (i = 1) || (start > result[i-1, 2] + 1) ; first or non overlapping range
result[i, 1] := start, result[i, 2] := stop, i++
else ; overlapping range
result[i-1, 2] := stop > result[i-1, 2] ? stop : result[i-1, 2]
return result
}
obj2string(arr){
for i, obj in arr
str .= obj.1 "-" obj.2 ","
return Trim(str, ",")
}
string2obj(str){
arr := []
for i, v in StrSplit(str, ",")
x := StrSplit(v, "-"), arr.push([x.1, x.2])
return arr
}
Examples:
arr := string2obj("")
steps .= "start with`t:`t" obj2string(arr) "`n"
arr := RangeModifications(arr, "Add", 77)
arr := RangeModifications(arr, "Add", 79)
arr := RangeModifications(arr, "Add", 78)
arr := RangeModifications(arr, "Remove", 77)
arr := RangeModifications(arr, "Remove", 78)
arr := RangeModifications(arr, "Remove", 79)
MsgBox % steps
steps := ""
arr := string2obj("1-3,5-5")
steps .= "start with`t:`t" obj2string(arr) "`n"
arr := RangeModifications(arr, "Add", 1)
arr := RangeModifications(arr, "Remove", 4)
arr := RangeModifications(arr, "Add", 7)
arr := RangeModifications(arr, "Add", 8)
arr := RangeModifications(arr, "add", 6)
arr := RangeModifications(arr, "Remove", 7)
MsgBox % steps
steps := ""
arr := string2obj("1-5,10-25,27-30")
steps .= "start with`t:`t" obj2string(arr) "`n"
arr := RangeModifications(arr, "Add", 26)
arr := RangeModifications(arr, "Add", 9)
arr := RangeModifications(arr, "Add", 7)
arr := RangeModifications(arr, "Remove", 26)
arr := RangeModifications(arr, "Remove", 9)
arr := RangeModifications(arr, "Remove", 7)
MsgBox % steps
return
- Output:
start with : Add 77 : 77-77 Add 79 : 77-77,79-79 Add 78 : 77-79 Remove 77 : 78-79 Remove 78 : 79-79 Remove 79 : start with : 1-3,5-5 Add 1 : 1-3,5-5 Remove 4 : 1-3,5-5 Add 7 : 1-3,5-5,7-7 Add 8 : 1-3,5-5,7-8 add 6 : 1-3,5-8 Remove 7 : 1-3,5-6,8-8 start with : 1-5,10-25,27-30 Add 26 : 1-5,10-30 Add 9 : 1-5,9-30 Add 7 : 1-5,7-7,9-30 Remove 26 : 1-5,7-7,9-25,27-30 Remove 9 : 1-5,7-7,10-25,27-30 Remove 7 : 1-5,10-25,27-30
C++
#include <algorithm>
#include <iomanip>
#include <iostream>
#include <list>
struct range {
range(int lo, int hi) : low(lo), high(hi) {}
int low;
int high;
};
std::ostream& operator<<(std::ostream& out, const range& r) {
return out << r.low << '-' << r.high;
}
class ranges {
public:
ranges() {}
explicit ranges(std::initializer_list<range> init) : ranges_(init) {}
void add(int n);
void remove(int n);
bool empty() const { return ranges_.empty(); }
private:
friend std::ostream& operator<<(std::ostream& out, const ranges& r);
std::list<range> ranges_;
};
void ranges::add(int n) {
for (auto i = ranges_.begin(); i != ranges_.end(); ++i) {
if (n + 1 < i->low) {
ranges_.emplace(i, n, n);
return;
}
if (n > i->high + 1)
continue;
if (n + 1 == i->low)
i->low = n;
else if (n == i->high + 1)
i->high = n;
else
return;
if (i != ranges_.begin()) {
auto prev = std::prev(i);
if (prev->high + 1 == i->low) {
i->low = prev->low;
ranges_.erase(prev);
}
}
auto next = std::next(i);
if (next != ranges_.end() && next->low - 1 == i->high) {
i->high = next->high;
ranges_.erase(next);
}
return;
}
ranges_.emplace_back(n, n);
}
void ranges::remove(int n) {
for (auto i = ranges_.begin(); i != ranges_.end(); ++i) {
if (n < i->low)
return;
if (n == i->low) {
if (++i->low > i->high)
ranges_.erase(i);
return;
}
if (n == i->high) {
if (--i->high < i->low)
ranges_.erase(i);
return;
}
if (n > i->low & n < i->high) {
int low = i->low;
i->low = n + 1;
ranges_.emplace(i, low, n - 1);
return;
}
}
}
std::ostream& operator<<(std::ostream& out, const ranges& r) {
if (!r.empty()) {
auto i = r.ranges_.begin();
out << *i++;
for (; i != r.ranges_.end(); ++i)
out << ',' << *i;
}
return out;
}
void test_add(ranges& r, int n) {
r.add(n);
std::cout << " add " << std::setw(2) << n << " => " << r << '\n';
}
void test_remove(ranges& r, int n) {
r.remove(n);
std::cout << " remove " << std::setw(2) << n << " => " << r << '\n';
}
void test1() {
ranges r;
std::cout << "Start: \"" << r << "\"\n";
test_add(r, 77);
test_add(r, 79);
test_add(r, 78);
test_remove(r, 77);
test_remove(r, 78);
test_remove(r, 79);
}
void test2() {
ranges r{{1,3}, {5,5}};
std::cout << "Start: \"" << r << "\"\n";
test_add(r, 1);
test_remove(r, 4);
test_add(r, 7);
test_add(r, 8);
test_add(r, 6);
test_remove(r, 7);
}
void test3() {
ranges r{{1,5}, {10,25}, {27,30}};
std::cout << "Start: \"" << r << "\"\n";
test_add(r, 26);
test_add(r, 9);
test_add(r, 7);
test_remove(r, 26);
test_remove(r, 9);
test_remove(r, 7);
}
int main() {
test1();
std::cout << '\n';
test2();
std::cout << '\n';
test3();
return 0;
}
- Output:
Start: "" add 77 => 77-77 add 79 => 77-77,79-79 add 78 => 77-79 remove 77 => 78-79 remove 78 => 79-79 remove 79 => Start: "1-3,5-5" add 1 => 1-3,5-5 remove 4 => 1-3,5-5 add 7 => 1-3,5-5,7-7 add 8 => 1-3,5-5,7-8 add 6 => 1-3,5-8 remove 7 => 1-3,5-6,8-8 Start: "1-5,10-25,27-30" add 26 => 1-5,10-30 add 9 => 1-5,9-30 add 7 => 1-5,7-7,9-30 remove 26 => 1-5,7-7,9-25,27-30 remove 9 => 1-5,7-7,10-25,27-30 remove 7 => 1-5,10-25,27-30
Go
package main
import (
"fmt"
"strings"
)
type rng struct{ from, to int }
type fn func(rngs *[]rng, n int)
func (r rng) String() string { return fmt.Sprintf("%d-%d", r.from, r.to) }
func rangesAdd(rngs []rng, n int) []rng {
if len(rngs) == 0 {
rngs = append(rngs, rng{n, n})
return rngs
}
for i, r := range rngs {
if n < r.from-1 {
rngs = append(rngs, rng{})
copy(rngs[i+1:], rngs[i:])
rngs[i] = rng{n, n}
return rngs
} else if n == r.from-1 {
rngs[i] = rng{n, r.to}
return rngs
} else if n <= r.to {
return rngs
} else if n == r.to+1 {
rngs[i] = rng{r.from, n}
if i < len(rngs)-1 && (n == rngs[i+1].from || n+1 == rngs[i+1].from) {
rngs[i] = rng{r.from, rngs[i+1].to}
copy(rngs[i+1:], rngs[i+2:])
rngs[len(rngs)-1] = rng{}
rngs = rngs[:len(rngs)-1]
}
return rngs
} else if i == len(rngs)-1 {
rngs = append(rngs, rng{n, n})
return rngs
}
}
return rngs
}
func rangesRemove(rngs []rng, n int) []rng {
if len(rngs) == 0 {
return rngs
}
for i, r := range rngs {
if n <= r.from-1 {
return rngs
} else if n == r.from && n == r.to {
copy(rngs[i:], rngs[i+1:])
rngs[len(rngs)-1] = rng{}
rngs = rngs[:len(rngs)-1]
return rngs
} else if n == r.from {
rngs[i] = rng{n + 1, r.to}
return rngs
} else if n < r.to {
rngs[i] = rng{r.from, n - 1}
rngs = append(rngs, rng{})
copy(rngs[i+2:], rngs[i+1:])
rngs[i+1] = rng{n + 1, r.to}
return rngs
} else if n == r.to {
rngs[i] = rng{r.from, n - 1}
return rngs
}
}
return rngs
}
func standard(rngs []rng) string {
if len(rngs) == 0 {
return ""
}
var sb strings.Builder
for _, r := range rngs {
sb.WriteString(fmt.Sprintf("%s,", r))
}
s := sb.String()
return s[:len(s)-1]
}
func main() {
const add = 0
const remove = 1
fns := []fn{
func(prngs *[]rng, n int) {
*prngs = rangesAdd(*prngs, n)
fmt.Printf(" add %2d => %s\n", n, standard(*prngs))
},
func(prngs *[]rng, n int) {
*prngs = rangesRemove(*prngs, n)
fmt.Printf(" remove %2d => %s\n", n, standard(*prngs))
},
}
var rngs []rng
ops := [][2]int{{add, 77}, {add, 79}, {add, 78}, {remove, 77}, {remove, 78}, {remove, 79}}
fmt.Printf("Start: %q\n", standard(rngs))
for _, op := range ops {
fns[op[0]](&rngs, op[1])
}
rngs = []rng{{1, 3}, {5, 5}}
ops = [][2]int{{add, 1}, {remove, 4}, {add, 7}, {add, 8}, {add, 6}, {remove, 7}}
fmt.Printf("\nStart: %q\n", standard(rngs))
for _, op := range ops {
fns[op[0]](&rngs, op[1])
}
rngs = []rng{{1, 5}, {10, 25}, {27, 30}}
ops = [][2]int{{add, 26}, {add, 9}, {add, 7}, {remove, 26}, {remove, 9}, {remove, 7}}
fmt.Printf("\nStart: %q\n", standard(rngs))
for _, op := range ops {
fns[op[0]](&rngs, op[1])
}
}
- Output:
Start: "" add 77 => 77-77 add 79 => 77-77,79-79 add 78 => 77-79 remove 77 => 78-79 remove 78 => 79-79 remove 79 => Start: "1-3,5-5" add 1 => 1-3,5-5 remove 4 => 1-3,5-5 add 7 => 1-3,5-5,7-7 add 8 => 1-3,5-5,7-8 add 6 => 1-3,5-8 remove 7 => 1-3,5-6,8-8 Start: "1-5,10-25,27-30" add 26 => 1-5,10-30 add 9 => 1-5,9-30 add 7 => 1-5,7-7,9-30 remove 26 => 1-5,7-7,9-25,27-30 remove 9 => 1-5,7-7,10-25,27-30 remove 7 => 1-5,10-25,27-30
jq
Also works with gojq, the Go implementation of jq (*)
In this entry, a closed interval [i,j] is represented by the JSON array [i, j], and a sequence of such intervals is represented as a sorted array of such arrays, with the understanding that [] represents the null sequence. i and j may be negative.
In order to show progress using the string representation of ranges, jq's "debug" filter is used to show intermediate steps.
(*) For gojq, a def of debug/1 may be required as follows:
def debug(msg): (msg | debug | empty), .;
# Conversion between string and JSON representations of ranges
# Allow negative integers
def r2j:
[ scan( "(-?[0-9]+)-(-?[0-9]+)|(-?[0-9]+)" )
| map(select(. != null) | tonumber)
| if length==1 then [first,first] end ] ;
def j2r:
map( if first == last then "\(first)"
else "\(first)-\(last)"
end )
| join(",");
# A quick determination of whether $n is in a sequence of intervals
def indexOf($n):
(first( range(0;length) as $i
| if .[$i][0] <= $n and $n <= .[$i][1] then $i
elif $n < .[$i][0] then -1
else empty
end ) // -1)
| if . == -1 then null else . end ;
def rangesAdd($n):
# Detect the special case where $i is the only integer in the gap between .[$i] and .[$i+1]
def tween($i):
-1 < $i and $i < length - 1 and .[$i][1] == $i - 1 and .[$i+1][0] == $i + 1;
# First handle the boundary cases:
if length == 0 then [[$n, $n]]
elif $n < .[0][0]-1 then [[$n,$n]] + .
elif $n == .[0][0]-1 then [[$n, .[0][1]]] + .[1:]
elif $n > .[-1][1]+1 then . + [[$n,$n]]
elif $n == .[-1][1]+1 then .[:-1] + [[.[-1][0], $n]]
else (map(first) | bsearch($n)) as $ix
| if $ix >= 0 then .
else (-2-$ix) as $i # $i >= 0 is the interval at the insertion point minus 1
| if tween($i)
then .[:$i] + [[.[$i][0], .[$i+1][1]]] + .[$i+2:] # coalesce
else .[$i] as $x # the preliminary checks above ensure 0 <= $i < $length-1
| if $x[0] <= $n and $n <= $x[1] # [_ $n _]
then .
elif $n == $x[1] + 1 # [ *] $n
then (if $i == 0 then null else .[$i-1:] end) + [[$x[0], $n]] + .[$i+1:]
elif $n == .[$i+1][0] - 1 # $n [* _]
then .[:$i+1] + [[$n, .[$i+1][1]]] + .[$i+2:]
else # assert($x[1] < $n and $n < .[$i+1][0]) # [] $n []
.[:$i+1] + [[$n,$n]] + .[$i+1:]
end
end
end
end ;
def rangesRemove($n):
# remove a value from a single interval
def remove($n):
. as [$a,$b]
| if $a == $b and $a == $n then null
elif $a == $n then [[$n+1, $b]]
elif $b == $n then [[$a, $n-1]]
else [[$a, $n-1], [$n+1, $b]]
end;
indexOf($n) as $ix
| if $ix then .[:$ix] + (.[$ix]|remove($n)) + .[$ix+1:]
else .
end ;
### Pretty printing
def lpad($len): tostring | ($len - length) as $l | (" " * $l) + .;
# Functions for showing intermediate results, in string form:
# Input: the JSON representation
def add(n):
rangesAdd(n)
| debug(" add \(n|lpad(3)) => \(j2r)");
def remove(n):
rangesRemove(n)
| debug(" remove \(n|lpad(3)) => \(j2r)");
# The tasks expressed as sequences of operations on the JSON representation
def s0:
add(77)
| add(79)
| add(78)
| remove(77)
| remove(78)
| remove(79)
;
def s1:
add(1)
| remove(4)
| add(7)
| add(8)
| add(6)
| remove(7)
;
def s2:
add(26)
| add(9)
| add(7)
| remove(26)
| remove(9)
| remove(7)
;
def ex0: "Starting with \(.)", "Ending with \(r2j | s0 | j2r)\n";
def ex1: "Starting with \(.)", "Ending with \(r2j | s1 | j2r)\n";
def ex2: "Starting with \(.)", "Ending with \(r2j | s2 | j2r)\n";
("" | ex0),
("1-3,5" | ex1),
("1-5,10-25,27-30" | ex2)
- Output:
Starting with ["DEBUG:"," add 77 => 77"] ["DEBUG:"," add 79 => 77,79"] ["DEBUG:"," add 78 => 77-79"] ["DEBUG:"," remove 77 => 78-79"] ["DEBUG:"," remove 78 => 79"] ["DEBUG:"," remove 79 => "] Ending with Starting with 1-3,5 ["DEBUG:"," add 1 => 1-3,5"] ["DEBUG:"," remove 4 => 1-3,5"] ["DEBUG:"," add 7 => 1-3,5,7"] ["DEBUG:"," add 8 => 1-3,5,7-8"] ["DEBUG:"," add 6 => 1-3,5-8"] ["DEBUG:"," remove 7 => 1-3,5-6,8"] Ending with 1-3,5-6,8 Starting with 1-5,10-25,27-30 ["DEBUG:"," add 26 => 1-5,10-30"] ["DEBUG:"," add 9 => 1-5,9-30"] ["DEBUG:"," add 7 => 1-5,7,9-30"] ["DEBUG:"," remove 26 => 1-5,7,9-25,27-30"] ["DEBUG:"," remove 9 => 1-5,7,10-25,27-30"] ["DEBUG:"," remove 7 => 1-5,10-25,27-30"] Ending with 1-5,10-25,27-30
Julia
Julia has iterator classes called a type of Range, such as integer UnitRanges, that are like the "10-10" of the task but are stated as 10:10, with a colon not a minus sign. This implementation uses Julia's UnitRange class internally.
import Base.parse, Base.print, Base.reduce
const RangeSequence = Array{UnitRange, 1}
function combine!(seq::RangeSequence, r::UnitRange)
isempty(seq) && return push!(seq, r)
if r.start < seq[end].start
reduce!(push!(seq, r))
elseif r.stop > seq[end].stop
if r.start <= seq[end].stop + 1
seq[end] = seq[end].start:r.stop
else
push!(seq, r)
end
end
return seq
end
function parse(::Type{RangeSequence}, s)
seq = UnitRange[]
entries = sort!(split(s, r"\s*,\s*"))
for e in entries
startstop = split(e, r"\:|\-")
if length(startstop) == 2
start, stop = tryparse(Int, startstop[1]), tryparse(Int, startstop[2])
start, stop = start <= stop ? (start, stop) : (stop, start)
start != nothing && stop != nothing && push!(seq, start:stop)
elseif (n = tryparse(Int, startstop[1])) != nothing
push!(seq, n:n)
end
end
return reduce!(seq)
end
reduce!(a::RangeSequence) = (s = sort(a); empty!(a); for r in s combine!(a, r) end; a)
reduce(a::RangeSequence) = (seq = UnitRange[]; for r in sort(a) combine!(seq, r) end; seq)
insertinteger!(seq::RangeSequence, n::Integer) = begin push!(seq, n:n); reduce!(seq) end
insertintegerprint!(seq, n) = println(" added $n => ", insertinteger!(seq, n))
removeintegerprint!(seq, n) = println(" removed $n => ", removeinteger!(seq, n))
function removeinteger!(seq::RangeSequence, n::Integer)
for (pos, r) in enumerate(seq)
if n in r
start, stop = r.start, r.stop
if start == stop == n
deleteat!(seq, pos:pos)
elseif stop == n
seq[pos] = start:stop-1
elseif start == n
seq[pos] = start+1:stop
elseif start < n < stop
seq[pos] = n+1:stop
insert!(seq, pos, start:n-1)
end
break
end
end
return seq
end
function print(io::IO, seq::RangeSequence)
return print(io, "\"" * join(map(r -> "$(r.start)-$(r.stop)", reduce(seq)), ",") * "\"")
end
const seq = parse(RangeSequence, "")
println("Start: $seq")
insertintegerprint!(seq, 77)
insertintegerprint!(seq, 79)
insertintegerprint!(seq, 78)
removeintegerprint!(seq, 77)
removeintegerprint!(seq, 78)
removeintegerprint!(seq, 79)
const seq2 = parse(RangeSequence, "1-3, 5-5")
println("Start: $seq2")
insertintegerprint!(seq2, 1)
removeintegerprint!(seq2, 4)
insertintegerprint!(seq2, 7)
insertintegerprint!(seq2, 8)
insertintegerprint!(seq2, 6)
removeintegerprint!(seq2, 7)
const seq3 = parse(RangeSequence, "1-5, 10-25, 27-30")
println("Start: $seq3")
insertintegerprint!(seq3, 26)
insertintegerprint!(seq3, 9)
insertintegerprint!(seq3, 7)
removeintegerprint!(seq3, 26)
removeintegerprint!(seq3, 9)
removeintegerprint!(seq3, 7)
println("Parse \"10-25, 1-5, 27-30\" => ", parse(RangeSequence, "10-25, 1-5, 27-30"))
println("Parse \"3-1,15-5,25-10,30-27\" => ", parse(RangeSequence, "3-1,15-5,25-10,30-27"))
- Output:
Start: "" added 77 => 77-77 added 79 => 77-77,79-79 added 78 => 77-79 removed 77 => 78-79 removed 78 => 79-79 removed 79 => Start: 1-3,5-5 added 1 => 1-3,5-5 removed 4 => 1-3,5-5 added 7 => 1-3,5-5,7-7 added 8 => 1-3,5-5,7-8 added 6 => 1-3,5-8 removed 7 => 1-3,5-6,8-8 Start: 1-5,10-25,27-30 added 26 => 1-5,10-30 added 9 => 1-5,9-30 added 7 => 1-5,7-7,9-30 removed 26 => 1-5,7-7,9-25,27-30 removed 9 => 1-5,7-7,10-25,27-30 removed 7 => 1-5,10-25,27-30 Parse "10-25, 1-5, 27-30" => 1-5,10-25,27-30 Parse "3-1,15-5,25-10,30-27" => 1-3,5-25,27-30
Nim
import algorithm, sequtils, strscans, strutils
type
Range = tuple[low, high: int]
Ranges = seq[Range]
func `$`(ranges: Ranges): string =
## Return the string representation of a list of ranges.
result.add '"'
for r in ranges:
result.addSep(",", 1)
result.add "$1-$2".format(r.low, r.high)
result.add '"'
proc initRanges(ranges: varargs[Range]): seq[Range] =
## Create a list of ranges with the given (potentially empty) ranges.
stdout.write "Start with "
var ranges = ranges.filterIt(it.low <= it.high)
if ranges.len <= 1:
echo ranges
return ranges
ranges.sort()
result = @[ranges[0]]
for newRange in ranges[1..^1]:
if newRange.low <= result[^1].high:
# Intersection is not empty.
if newRange.high > result[^1].low: result[^1].high = newRange.high
else:
# New range.
result.add newRange
echo result
proc initRanges(rangeString: string): seq[Range] =
## Create a list fo ranges from a string representation.
if rangeString.len == 0: return
var ranges: seq[Range]
for field in rangeString.split(','):
var r: Range
if field.scanf("$i-$i$.", r.low, r.high):
ranges.add r
else:
raise newException(ValueError, "Wrong range specification: " & field)
result = initRanges(ranges)
func contains(r: Range; val: int): bool =
## Return true if a range contains a value.
## Used by "in" operator.
val >= r.low and val <= r.high
proc add(ranges: var Ranges; val: int) =
## Add a value to a list of ranges.
stdout.write "add ", ($val).alignLeft(2), " → "
if ranges.len == 0:
ranges.add (val, val)
echo ranges
return
# Search the range immediately following the value.
var idx = -1
for i, r in ranges:
if val in r:
# Already in a range: no changes.
echo ranges
return
if val < r.low:
idx = i
break
if idx < 0:
# Not found, so to add at the end.
if ranges[^1].high == val - 1: ranges[^1].high = val # Extend last range.
else: ranges.add (val, val) # Add a range.
elif ranges[idx].low == val + 1:
# Just before a range.
ranges[idx].low = val
if idx > 0:
if ranges[idx-1].high >= val - 1:
# Merge two ranges.
ranges[idx].low = ranges[idx-1].low
ranges.delete(idx - 1)
elif idx > 0:
# Between two ranges.
if ranges[idx-1].high == val - 1: ranges[idx-1].high = val # Extend previous range.
else: ranges.insert((val, val), idx) # Insert a range.
else:
# At the beginning.
ranges.insert((val, val), 0)
echo ranges
proc remove(ranges: var Ranges; val: int) =
## remove a value from a list of ranges.
stdout.write "remove ", ($val).alignLeft(2), " → "
# Search the range containing the value.
var idx = - 1
for i, r in ranges:
if val in r:
idx = i
break
if val < r.low: break
if idx < 0:
# Not found.
echo ranges
return
let r = ranges[idx]
if r.low == val:
if r.high == val:
# Delete the range.
ranges.delete(idx)
else:
# Update the low value.
ranges[idx].low = val + 1
elif r.high == val:
# Update the high value.
ranges[idx].high = val - 1
else:
# Split the range.
ranges.insert(r, idx + 1)
ranges[idx].high = val - 1
ranges[idx+1].low = val + 1
echo ranges
var r = initRanges("")
r.add 77
r.add 79
r.add 78
r.remove 77
r.remove 78
r.remove 79
echo()
r = initRanges("1-3,5-5")
r.add 1
r.remove 4
r.add 7
r.add 8
r.add 6
r.remove 7
echo()
r = initRanges("1-5,10-25,27-30")
r.add 26
r.add 9
r.add 7
r.remove 26
r.remove 9
r.remove 7
- Output:
add 77 → "77-77" add 79 → "77-77,79-79" add 78 → "77-79" remove 77 → "78-79" remove 78 → "79-79" remove 79 → "" Start with "1-3,5-5" add 1 → "1-3,5-5" remove 4 → "1-3,5-5" add 7 → "1-3,5-5,7-7" add 8 → "1-3,5-5,7-8" add 6 → "1-3,5-8" remove 7 → "1-3,5-6,8-8" Start with "1-5,10-25,27-30" add 26 → "1-5,10-30" add 9 → "1-5,9-30" add 7 → "1-5,7-7,9-30" remove 26 → "1-5,7-7,9-25,27-30" remove 9 → "1-5,7-7,10-25,27-30" remove 7 → "1-5,10-25,27-30"
Perl
#!/usr/bin/perl
use strict;
use warnings;
my $ranges;
while( <DATA> )
{
my ($cmd) = /(\S.*\S)/ or print "\n";
$ranges = /Start with "(.*)"/ ? $1 :
/add (\d+)/ ? add($1) :
/remove (\d+)/ ? remove($1) :
$ranges;
$cmd and printf qq(%35s -> "%s"\n), $cmd, $ranges;
}
sub add
{
my $n = shift;
s/^$/$n-$n/ # empty
or s/-@{[$n-1]},@{[$n+1]}\b// # join 2 ranges
or s/\b@{[$n+1]}-/$n-/ # lower start
or s/-@{[$n-1]}\b/-$n/ # raise end
or s/^(?=(\d+)-(??{$n < $1 ? '' : 'X'}))/$n-$n,/ # add front
or s/-(\d+)\K$(??{$1 < $n ? '' : 'X'})/,$n-$n/ # add end
or s/\b(\d+),(\d+)\b(??{$1 < $n-1 && $n < $2+1 ? '' : 'X'
})/$1,$n-$n,$2/ # add middle
for $ranges;
$ranges;
}
sub remove
{
my $n = shift;
s/^$n-$n,?|,$n-$n\b// # single item
or s/\b$n(?=-)/$n+1/e # first from range
or s/-\K$n\b/$n-1/e # last from range
or s/\b(\d+)-(\d+)\b(??{$1 < $n && $n < $2 ? '' : 'X'
})/$1-@{[$n-1]},@{[$n+1]}-$2/ # split range
for $ranges;
$ranges;
}
__DATA__
Start with "1-3,5-5"
add 77
add 79
add 78
remove 77
remove 78
remove 79
Start with "1-3,5-5"
add 1
remove 4
add 7
add 8
add 6
remove 7
Start with "1-5,10-25,27-30"
add 26
add 9
add 7
remove 26
remove 9
remove 7
- Output:
Start with "1-3,5-5" -> "1-3,5-5" add 77 -> "1-3,5-5,77-77" add 79 -> "1-3,5-5,77-77,79-79" add 78 -> "1-3,5-5,77-79" remove 77 -> "1-3,5-5,78-79" remove 78 -> "1-3,5-5,79-79" remove 79 -> "1-3,5-5" Start with "1-3,5-5" -> "1-3,5-5" add 1 -> "1-3,5-5" remove 4 -> "1-3,5-5" add 7 -> "1-3,5-5,7-7" add 8 -> "1-3,5-5,7-8" add 6 -> "1-3,5-8" remove 7 -> "1-3,5-6,8-8" Start with "1-5,10-25,27-30" -> "1-5,10-25,27-30" add 26 -> "1-5,10-30" add 9 -> "1-5,9-30" add 7 -> "1-5,7-7,9-30" remove 26 -> "1-5,7-7,9-25,27-30" remove 9 -> "1-5,7-7,10-25,27-30" remove 7 -> "1-5,10-25,27-30"
Phix
with javascript_semantics function add(sequence ranges, atom v) -- -- eg {} + 9 --> {{9,9}} -- [1] -- {{3,5}} + 9 --> {{3,5},{9,9}} -- [1] -- {{3,5}} + 4 --> as-is -- [2] -- {{3,5}} + 2 --> {{2,5}} -- [3] -- {{3,5},{7,9}} + 6 --> {{3,9}} -- [4] -- {{3,5},{8,9}} + 6 --> {{3,6},{8,9}} -- [5] -- {{3,5},{8,9}} + 7 --> {{3,5},{7,9}} -- [3] -- {{3,5}} + 6 --> {{3,6}} -- [6] -- {{3,5}} + 1 --> {{1,1},{3,5}} -- [7] -- integer l = length(ranges) ranges = deep_copy(ranges) for i=1 to l+1 do if i>l then ranges &= {{v,v}} exit -- [1] end if atom nl,{lo,hi} = ranges[i] if v>=lo and v<=hi then exit -- [2] elsif v=lo-1 then ranges[i][1] = v exit -- [3] elsif v=hi+1 then if i<l then {nl,hi} = ranges[i+1] if nl=v+1 then ranges[i..i+1] = {{lo,hi}} exit -- [4] else ranges[i][2] = v exit -- [5] end if else ranges[i][2] = v exit -- [6] end if elsif v<lo then ranges[i..i-1] = {{v,v}} exit -- [7] end if end for return ranges end function function del(sequence ranges, atom v) -- -- eg {{1,2}} - 1 --> {{2,2}} -- [1] -- {{2,2}} - 2 --> {} -- [2] -- {{1,2}} - 2 --> {{1,1}} -- [3] -- {{1,3}} - 2 --> {{1,1},{3,3}} -- [4] -- {{2,3}} - 1 --> as-is -- [5] -- ranges = deep_copy(ranges) for i=1 to length(ranges) do atom {lo,hi} = ranges[i] if v>=lo and v<=hi then if v=lo then if v<hi then ranges[i][1] = lo+1 -- [1] else ranges[i..i] = {} end if -- [2] elsif v==hi then ranges[i][2] = hi-1 -- [3] else ranges[i..i] = {{lo,v-1},{v+1,hi}} -- [4] end if exit elsif v<hi then exit end if -- [5] end for return ranges end function constant tests = split(""" Start with "" add 77 add 79 add 78 remove 77 remove 78 remove 79 Start with "1-3,5-5" add 1 remove 4 add 7 add 8 add 6 remove 7 Start with "1-5,10-25,27-30" add 26 add 9 add 7 remove 26 remove 9 remove 7 Start with "13-14,22-22,100000999999-100001000000,100001000003-999999999999" remove 22 remove 100000999999 remove 100001000000 add 100001000001 add 100001000002 remove 100001000002 remove 100001000001 ""","\n",no_empty:=true) string range = "" sequence ranges -- range internal form for i=1 to length(tests) do string ti = tests[i] if match("Start with",ti) then {range} = scanf(trim(ti),"Start with \"%s\"")[1] ranges = split(range,",") ranges = vslice(apply(true,scanf,{ranges,{"%d-%d"}}),1) printf(1,"\n Start with: \"%s\"\n",{range}) -- ^^ ^^ else {string op, atom v} = scanf(trim(ti),"%s %d")[1] integer rid = routine_id(substitute(op,"remove","del")) ranges = rid(ranges,v) range = join(apply(true,sprintf,{{"%d-%d"},ranges}),",") printf(1," %9s %-12d -> \"%s\"\n",{op,v,range}) -- ^^ ^^ end if end for
- Output:
(Note that all double-quotes in the output were deliberately added in the last two printf() statements, mainly to prove there are no unnecessary spaces, etc, and are (see ^^) obviously trivial to remove.)
Start with: "" add 77 -> "77-77" add 79 -> "77-77,79-79" add 78 -> "77-79" remove 77 -> "78-79" remove 78 -> "79-79" remove 79 -> "" Start with: "1-3,5-5" add 1 -> "1-3,5-5" remove 4 -> "1-3,5-5" add 7 -> "1-3,5-5,7-7" add 8 -> "1-3,5-5,7-8" add 6 -> "1-3,5-8" remove 7 -> "1-3,5-6,8-8" Start with: "1-5,10-25,27-30" add 26 -> "1-5,10-30" add 9 -> "1-5,9-30" add 7 -> "1-5,7-7,9-30" remove 26 -> "1-5,7-7,9-25,27-30" remove 9 -> "1-5,7-7,10-25,27-30" remove 7 -> "1-5,10-25,27-30" Start with: "13-14,22-22,100000999999-100001000000,100001000003-999999999999" remove 22 -> "13-14,100000999999-100001000000,100001000003-999999999999" remove 100000999999 -> "13-14,100001000000-100001000000,100001000003-999999999999" remove 100001000000 -> "13-14,100001000003-999999999999" add 100001000001 -> "13-14,100001000001-100001000001,100001000003-999999999999" add 100001000002 -> "13-14,100001000001-999999999999" remove 100001000002 -> "13-14,100001000001-100001000001,100001000003-999999999999" remove 100001000001 -> "13-14,100001000003-999999999999"
Python
class Sequence():
def __init__(self, sequence_string):
self.ranges = self.to_ranges(sequence_string)
assert self.ranges == sorted(self.ranges), "Sequence order error"
def to_ranges(self, txt):
return [[int(x) for x in r.strip().split('-')]
for r in txt.strip().split(',') if r]
def remove(self, rem):
ranges = self.ranges
for i, r in enumerate(ranges):
if r[0] <= rem <= r[1]:
if r[0] == rem: # range min
if r[1] > rem:
r[0] += 1
else:
del ranges[i]
elif r[1] == rem: # range max
if r[0] < rem:
r[1] -= 1
else:
del ranges[i]
else: # inside, range extremes.
r[1], splitrange = rem - 1, [rem + 1, r[1]]
ranges.insert(i + 1, splitrange)
break
if r[0] > rem: # Not in sorted list
break
return self
def add(self, add):
ranges = self.ranges
for i, r in enumerate(ranges):
if r[0] <= add <= r[1]: # already included
break
elif r[0] - 1 == add: # rough extend to here
r[0] = add
break
elif r[1] + 1 == add: # rough extend to here
r[1] = add
break
elif r[0] > add: # rough insert here
ranges.insert(i, [add, add])
break
else:
ranges.append([add, add])
return self
return self.consolidate()
def consolidate(self):
"Combine overlapping ranges"
ranges = self.ranges
for this, that in zip(ranges, ranges[1:]):
if this[1] + 1 >= that[0]: # Ranges interract
if this[1] >= that[1]: # this covers that
this[:], that[:] = [], this
else: # that extends this
this[:], that[:] = [], [this[0], that[1]]
ranges[:] = [r for r in ranges if r]
return self
def __repr__(self):
rr = self.ranges
return ",".join(f"{r[0]}-{r[1]}" for r in rr)
def demo(opp_txt):
by_line = opp_txt.strip().split('\n')
start = by_line.pop(0)
ex = Sequence(start.strip().split()[-1][1:-1]) # Sequence("1-3,5-5")
lines = [line.strip().split() for line in by_line]
opps = [((ex.add if word[0] == "add" else ex.remove), int(word[1]))
for word in lines]
print(f"Start: \"{ex}\"")
for op, val in opps:
print(f" {op.__name__:>6} {val:2} => {op(val)}")
print()
if __name__ == '__main__':
demo("""
Start with ""
add 77
add 79
add 78
remove 77
remove 78
remove 79
""")
demo("""
Start with "1-3,5-5"
add 1
remove 4
add 7
add 8
add 6
remove 7
""")
demo("""
Start with "1-5,10-25,27-30"
add 26
add 9
add 7
remove 26
remove 9
remove 7
""")
- Output:
Start: "" add 77 => 77-77 add 79 => 77-77,79-79 add 78 => 77-79 remove 77 => 78-79 remove 78 => 79-79 remove 79 => Start: "1-3,5-5" add 1 => 1-3,5-5 remove 4 => 1-3,5-5 add 7 => 1-3,5-5,7-7 add 8 => 1-3,5-5,7-8 add 6 => 1-3,5-8 remove 7 => 1-3,5-6,8-8 Start: "1-5,10-25,27-30" add 26 => 1-5,10-30 add 9 => 1-5,9-30 add 7 => 1-5,7-7,9-30 remove 26 => 1-5,7-7,9-25,27-30 remove 9 => 1-5,7-7,10-25,27-30 remove 7 => 1-5,10-25,27-30
Raku
Quite a bit of this is just transforming syntax back and forth. Raku already has Ranges and Sequences as first class objects, though the syntax is different.
Demonstrate the task required examples: adding and removing integers, as well as an example adding and removing ranges to / from the sequence, in both native Raku syntax and the "Stringy" syntax required by the task.
Demo with some extremely large values / ranges. Capable of working with infinite ranges by default.
Won't handle negative numbers as written, mostly due the need to work around the syntax requirements for output.
my @seq;
-> $op, $string { printf "%20s -> %s\n", $op, $string } for
'Start', to-string( @seq = canonicalize "" ),
'add 77', to-string( @seq .= &add(77) ),
'add 79', to-string( @seq .= &add(79) ),
'add 78', to-string( @seq .= &add(78) ),
'remove 77', to-string( @seq .= &remove(77) ),
'remove 78', to-string( @seq .= &remove(78) ),
'remove 79', to-string( @seq .= &remove(79) );
say '';
-> $op, $string { printf "%20s -> %s\n", $op, $string } for
'Start', to-string( @seq = canonicalize "1-3,5-5" ),
'add 1', to-string( @seq .= &add(1) ),
'remove 4', to-string( @seq .= &remove(4) ),
'add 7', to-string( @seq .= &add(7) ),
'add 8', to-string( @seq .= &add(8) ),
'add 6', to-string( @seq .= &add(6) ),
'remove 7', to-string( @seq .= &remove(7) );
say '';
-> $op, $string { printf "%20s -> %s\n", $op, $string } for
'Start', to-string( @seq = canonicalize "1-5,10-25,27-30" ),
'add 26', to-string( @seq .= &add(26) ),
'add 9', to-string( @seq .= &add(9) ),
'add 7', to-string( @seq .= &add(7) ),
'remove 26', to-string( @seq .= &remove(26) ),
'remove 9', to-string( @seq .= &remove(9) ),
'remove 7', to-string( @seq .= &remove(7) );
say '';
-> $op, $string { printf "%30s -> %s\n", $op, $string } for
'Start', to-string( @seq = canonicalize "6-57,160-251,2700-7000000" ),
'add "2502-2698"', to-string( @seq .= &add("2502-2698") ),
'add 41..69', to-string( @seq .= &add(41..69) ),
'remove 17..30', to-string( @seq .= &remove(17..30) ),
'remove 4391..6527', to-string( @seq .= &remove("4391-6527") ),
'add 2699', to-string( @seq .= &add(2699) ),
'add 76', to-string( @seq .= &add(76) ),
'add 78', to-string( @seq .= &add(78) ),
'remove "70-165"', to-string( @seq .= &remove("70-165") ),
'remove 16..31', to-string( @seq .= &remove(16..31) ),
'add 1.417e16 .. 3.2e21', to-string( @seq .= &add(1.417e16.Int .. 3.2e21.Int) ),
'remove "4001-Inf"', to-string( @seq .= &remove("4001-Inf") );
sub canonicalize (Str $ranges) { sort consolidate |sort parse-range $ranges }
sub parse-range (Str $_) { .comb(/\d+|'Inf'/).map: { +$^α .. +$^ω } }
sub to-string (@ranges) { qq|"{ @ranges».minmax».join('-').join(',') }"| }
multi add (@ranges, Int $i) { samewith @ranges, $i .. $i }
multi add (@ranges, Str $s) { samewith @ranges, |parse-range($s) }
multi add (@ranges, Range $r) { @ranges > 0 ?? (sort consolidate |sort |@ranges, $r) !! $r }
multi remove (@ranges, Int $i) { samewith @ranges, $i .. $i }
multi remove (@ranges, Str $s) { samewith @ranges, |parse-range($s) }
multi remove (@ranges, Range $r) {
gather for |@ranges -> $this {
if $r.min <= $this.min {
if $r.max >= $this.min and $r.max < $this.max {
take $r.max + 1 .. $this.max
}
elsif $r.max < $this.min {
take $this
}
}
else {
if $r.max >= $this.max and $r.min <= $this.max {
take $this.min .. $r.min - 1
}
elsif $r.max < $this.max and $r.min > $this.min {
take $this.min .. $r.min - 1;
take $r.max + 1 .. $this.max
}
else {
take $this
}
}
}
}
multi consolidate() { () }
multi consolidate($this is copy, **@those) {
sub infix:<∪> (Range $a, Range $b) { Range.new($a.min,max($a.max,$b.max)) }
sub infix:<∩> (Range $a, Range $b) { so $a.max >= $b.min }
my @ranges = sort gather {
for consolidate |@those -> $that {
next unless $that;
if $this ∩ $that { $this ∪= $that }
else { take $that }
}
take $this;
}
for reverse ^(@ranges - 1) {
if @ranges[$_].max == @ranges[$_ + 1].min - 1 {
@ranges[$_] = @ranges[$_].min .. @ranges[$_ + 1].max;
@ranges[$_ + 1]:delete
}
}
@ranges
}
- Output:
Start -> "" add 77 -> "77-77" add 79 -> "77-77,79-79" add 78 -> "77-79" remove 77 -> "78-79" remove 78 -> "79-79" remove 79 -> "" Start -> "1-3,5-5" add 1 -> "1-3,5-5" remove 4 -> "1-3,5-5" add 7 -> "1-3,5-5,7-7" add 8 -> "1-3,5-5,7-8" add 6 -> "1-3,5-8" remove 7 -> "1-3,5-6,8-8" Start -> "1-5,10-25,27-30" add 26 -> "1-5,10-30" add 9 -> "1-5,9-30" add 7 -> "1-5,7-7,9-30" remove 26 -> "1-5,7-7,9-25,27-30" remove 9 -> "1-5,7-7,10-25,27-30" remove 7 -> "1-5,10-25,27-30" Start -> "6-57,160-251,2700-7000000" add "2502-2698" -> "6-57,160-251,2502-2698,2700-7000000" add 41..69 -> "6-69,160-251,2502-2698,2700-7000000" remove 17..30 -> "6-16,31-69,160-251,2502-2698,2700-7000000" remove 4391..6527 -> "6-16,31-69,160-251,2502-2698,2700-4390,6528-7000000" add 2699 -> "6-16,31-69,160-251,2502-4390,6528-7000000" add 76 -> "6-16,31-69,76-76,160-251,2502-4390,6528-7000000" add 78 -> "6-16,31-69,76-76,78-78,160-251,2502-4390,6528-7000000" remove "70-165" -> "6-16,31-69,166-251,2502-4390,6528-7000000" remove 16..31 -> "6-15,32-69,166-251,2502-4390,6528-7000000" add 1.417e16 .. 3.2e21 -> "6-15,32-69,166-251,2502-4390,6528-7000000,14170000000000000-3200000000000000000000" remove "4001-Inf" -> "6-15,32-69,166-251,2502-4000"
Wren
import "./fmt" for Fmt
var rangesAdd = Fn.new { |ranges, n|
if (ranges.count == 0) {
ranges.add(n..n)
return
}
for (i in 0...ranges.count) {
var r = ranges[i]
if (n < r.from - 1) {
ranges.insert(i, n..n)
return
} else if (n == r.from-1) {
ranges[i] = n..r.to
return
} else if (n <= r.to) {
return
} else if (n == r.to+1) {
ranges[i] = r.from..n
if (i < ranges.count - 1 && (n == ranges[i+1].from || n + 1 == ranges[i+1].from)) {
ranges[i] = r.from..ranges[i+1].to
ranges.removeAt(i+1)
}
return
} else if (i == ranges.count - 1) {
ranges.add(n..n)
return
}
}
}
var rangesRemove = Fn.new { |ranges, n|
if (ranges.count == 0) return
for (i in 0...ranges.count) {
var r = ranges[i]
if (n <= r.from - 1) {
return
} else if (n == r.from && n == r.to) {
ranges.removeAt(i)
return
} else if (n == r.from) {
ranges[i] = n+1..r.to
return
} else if (n < r.to) {
ranges[i] = r.from..n-1
ranges.insert(i+1, n+1..r.to)
return
} else if (n == r.to) {
ranges[i] = r.from..n-1
return
}
}
}
var standard = Fn.new { |ranges| Fmt.v("s", 0, ranges, 0, ",", "").replace("..", "-") }
var add = 0
var remove = 1
var fns = [
Fn.new { |ranges, n|
rangesAdd.call(ranges, n)
Fmt.print(" add $2d => $n", n, standard.call(ranges))
},
Fn.new { |ranges, n|
rangesRemove.call(ranges, n)
Fmt.print(" remove $2d => $n", n, standard.call(ranges))
}
]
var ranges = []
var ops = [ [add, 77], [add, 79], [add, 78], [remove, 77], [remove, 78], [remove, 79] ]
Fmt.print("Start: $q", standard.call(ranges))
for (op in ops) fns[op[0]].call(ranges, op[1])
ranges = [1..3, 5..5]
ops = [ [add, 1], [remove, 4], [add, 7], [add, 8], [add, 6], [remove, 7] ]
Fmt.print("\nStart: $q", standard.call(ranges))
for (op in ops) fns[op[0]].call(ranges, op[1])
ranges = [1..5, 10..25, 27..30]
ops = [ [add, 26], [add, 9], [add, 7], [remove, 26], [remove, 9], [remove, 7] ]
Fmt.print("\nStart: $q", standard.call(ranges))
for (op in ops) fns[op[0]].call(ranges, op[1])
- Output:
Start: "" add 77 => 77-77 add 79 => 77-77,79-79 add 78 => 77-79 remove 77 => 78-79 remove 78 => 79-79 remove 79 => Start: "1-3,5-5" add 1 => 1-3,5-5 remove 4 => 1-3,5-5 add 7 => 1-3,5-5,7-7 add 8 => 1-3,5-5,7-8 add 6 => 1-3,5-8 remove 7 => 1-3,5-6,8-8 Start: "1-5,10-25,27-30" add 26 => 1-5,10-30 add 9 => 1-5,9-30 add 7 => 1-5,7-7,9-30 remove 26 => 1-5,7-7,9-25,27-30 remove 9 => 1-5,7-7,10-25,27-30 remove 7 => 1-5,10-25,27-30