It's not clear whether you want a single function to produce the final output, or whether you want one function (seqn) dedicated to outputting the boundary values and a separate function to format the output of seqn.
I'm going to work on the basis that a single function to print a formatted list of intervals is the goal, which I'll achieve with stepwise adjustments to your original script:
The Basics
To start, it's a good idea to declare variables as --local inside a function to guarantee that you're not overwriting an existing variable with the same name unless that's your specific intention, which doesn't seem likely to be the case here:
function seqn --argument first increment last
set --local carr ( seq $first $increment $last )
Having defined an array, it seems a shame to have to call seq a second time just to generate the indices. Instead, you can loop through the values of the array:
for cval in $carr
You referenced a program (pyp) in the next line that isn't part of FiSH nor one that's bundled with macOS as standard. You can't assume that someone will know what that program does, so a note explaining what it is (and where to obtain it from) will limit the confusion. Thankfully, you seemed to only use it to increment a numerical value. Since we're no longer referencing the array's indices, we won't have to perform any arithmetic on them, but I've commented-out the modified line that now demonstrates how to perform FiSH arithmetic.
# set i1 ( math $i + 1 )
Printing information to stdout that is generated output from a function is best done with printf, which allows one to control how it is formatted. It doesn't insert anything you don't tell it to, which includes spaces and newlines. echo is convenient to evaluate variables quickly when debugging.
Using printf, we can have each $cval item printed to stdout twice, separated by a newline character and followed by a hyphen:
printf -- '-%s\n%s' $cval $cval
For example, it will output :
-1
1
which might not look so useful right now. The next iteration of the loop will print starting immediately after the last printed character of the previous iteration, so the output will now look like this:
-1
1-301
301
and then this:
-1
1-301
301-601
601
Then close the for loop, and close the function:
end
end
Calling seqn 1 300 3800 results in the following output:
-1
1-301
301-601
601-901
901-1201
1201-1501
1501-1801
1801-2101
2101-2401
2401-2701
2701-3001
3001-3301
3301-3601
3601
The first and last lines are consequences of how the formatting was achieved, and can be easily rectified by iterating over all the $carr values except the first and the last. Instead, we deal with these two separately by bringing them outside of the for loop, printing the respective values immediately prior to entering and immediately after exiting the loop:
function seqn --argument first increment last
set --local carr ( seq $first $increment $last )
printf -- '%s' $carr[1]
for cval in $carr[2..-2]
# set i1 ( math $i + 1 )
printf -- '-%s\n%s' $cval $cval
end
printf -- '-%s' $carr[-1]
end
Calling seqn 1 300 3800 now outputs the desire result:
1-301
301-601
601-901
901-1201
1201-1501
1501-1801
1801-2101
2101-2401
2401-2701
2701-3001
3001-3301
3301-3601
Improvements
The above function is what one would term a naive implementation, in that it is essentially the most basic formulation for the algorithm that exposes each thought process along the way towards achieving the objective. Whilst naive implementations are generally easy-to-follow and simple to understand what's happening at each line of the script, it doesn't necessarily result in the most efficient way to do something nor the one with the fewest lines of code. Which of these attributes ends up being the most important will be up to you, but you'll only have the option to choose once you know what they are.
Consider the following array defined like so:
> set A 1 2 3 4 5
Printing two copies of this list that groups them together by index is not obviously achievable without looping through them. That is, we wish to achieve the following output:
1 1 2 2 3 3 4 4 5 5
without having to iterate through the array ourselves. Here are some common things one might try:
> echo $A $A
1 2 3 4 5 1 2 3 4 5
> echo $A$A
11 21 31 41 51 12 22 32 42 52 13 23 33 43 53 14 24 34 44 54 15 25 35 45 55
> echo {$A,$A}
1 1 2 1 3 1 4 1 5 1 1 2 2 2 3 2 4 2 5 2 1 3 2 3 3 3 4 3 5 3 1 4 2 4 3 4 4 4 5 4 1 5 2 5 3 5 4 5 5 5
If you're not familiar with Cartesian Products in the context of the shell, it's worth learning about them. That said, that reference document isn't exhaustive, and doesn't contain the solution we're after, so I'll just tell you:
> echo $A{,}
1 1 2 2 3 3 4 4 5 5
This is incredibly efficient and directly relevant to the purpose of seqn. If I borrow the line from the seqn function that prints a single iteration of $carr using the printf command, namely:
printf -- '-%s\n%s' $cval $cval
and substitute the arguments with the cartesian product operating on $A, then you can see why this is useful:
> printf -- '-%s\n%s' $A{,}
-1
1-2
2-3
3-4
4-5
5
As before, we will handle the first and last items of the array separately, but this can replace the entire for loop:
function seqn --argument first increment last
set --local carr ( seq $first $increment $last )
printf -- '%s' $carr[1]
printf -- '-%s\n%s' $carr[2..-2]{,}
printf -- '-%s' $carr[-1]
end
This might not seem obvious now if printf is not familiar to you, but these can combine and simplify into a single call:
function seqn --argument first increment last
set --local carr ( seq $first $increment $last )
printf '%s-%s\n' $carr[1] $carr[2..-2]{,} $carr[-1]
end
Final Tweaks (for the obsessive)
Calling seqn is designed to output intervals (ranges) of numbers where the values of the equivalent call to seq serve to become the boundary values of these intervals. Therefore, to me, it seems like the output from the following call is incomplete:
> seqn 1 300 3800
1-301
301-601
601-901
901-1201
1201-1501
1501-1801
1801-2101
2101-2401
2401-2701
2701-3001
3001-3301
3301-3601
as I would expect the final range to include:
3601-3800
Perhaps you disagree, but in case you don't, the solution is a simple tweak to the call made to seq. If $increment divides exactly the arithmetic difference ($last - $first), then $last will appear as the final number in the list generated by seq. If $increment does not divide ($last - $first), then $last won't appear.
This is inconvenient since appending the value $last to the $carr array will complete the list in a lot of cases, but in instances where the increment divides the entire range, we'd have two copies of $last at the end of $carr. One way to handle this is to do a quick check to see whether we need to append a value or not:
set --local carr ( seq $first $increment $last )
[ "$carr[-1]" -ne "$last" ]
and set --append carr $last
That's a perfectly good way to do it, and it's clear what we're doing.
But I'm going to make the call to seq with the calculated value of ($last - 1) in place of $last. The resulting $carr array definitely won't contain $last, but, importantly, every other number that would have otherwise been present will still be present (I'm assuming we're dealing only with integers). Then I can append the value $last as part of the array declaration:
set --local carr ( seq $first $increment (
math $last - 1 ) ) $last
Another option is to set the -t parameter for seq, which instructs it to terminate the list with whatever string value is passed as the parameter:
set --local carr ( seq -t $last $first $increment (
math $last - 1 ) )
The reason I'm bothering with this is that it allows me to do away with declaring a variable at the function level, and create a temporary variable attached to a single command only, in this case, the printf command:
function seqn
I=( seq -t $argv[3 1 2] (
math $argv[3] - 1 )
){,} printf '%s-%s\n' $I[2..-2]
end
I've replaced the named argument variables with the default $argv simply for brevity. The printf call has also been simplified a little more by moving the cartesian product so that it operates on the temporary variable at the point of declaration, which creates exactly two copies of every item in the list allowing us to do the simpler manoeuvre of omitting one value at either end.
Calling seqn 1 300 3800 now outputs:
1-301
301-601
601-901
901-1201
1201-1501
1501-1801
1801-2101
2101-2401
2401-2701
2701-3001
3001-3301
3301-3601
3601-3800