I need to take data and combine to insert into an table as a menu code. The menu code takes the form LNNLLLL (L = Letter and N=Number) and it is made up of MenU Cycle (hard-coded, one off project), Week Number (1-3), Day Number (1-7 - Monday=1), MealCode, Customer Type Code (3 Letters). The following SELECT query returns exactly what I want insert. The hardcoded date is a known date from which the week number and day number can be derived ie it is a Week 1 Monday.
SELECT group_concat('C' || CASE
WHEN (CAST(julianday(mealdate) AS INT) - cast(julianday('2022-08-22') AS INT)) % 21 <= 6 THEN 1
WHEN (CAST(julianday(mealdate) AS INT) - cast(julianday('2022-08-22') AS INT)) % 21 <= 13 THEN 2
ELSE 3
END ||
CASE WHEN (CAST(julianday(mealdate) AS INT) - cast(julianday('2022-08-22') AS INT)) % 21 + 1 IN (1, 8, 15) THEN 1
WHEN (CAST(julianday(mealdate) AS INT) - cast(julianday('2022-08-22') AS INT)) % 21 + 1 IN (2, 9, 16) THEN 2
WHEN (CAST(julianday(mealdate) AS INT) - cast(julianday('2022-08-22') AS INT)) % 21 + 1 IN (3, 10, 17) THEN 3
WHEN (CAST(julianday(mealdate) AS INT) - cast(julianday('2022-08-22') AS INT)) % 21 + 1 IN (4, 11, 18) THEN 4
WHEN (CAST(julianday(mealdate) AS INT) - cast(julianday('2022-08-22') AS INT)) % 21 + 1 IN (5, 12, 19) THEN 5
WHEN (CAST(julianday(mealdate) AS INT) - cast(julianday('2022-08-22') AS INT)) % 21 + 1 IN (6, 13, 20) THEN 6
WHEN (CAST(julianday(mealdate) AS INT) - cast(julianday('2022-08-22') AS INT)) % 21 + 1 IN (7, 14, 21) THEN 7
END ||
MealCode ||
(SELECT CustTypeCodeNew FROM CustomerNew WHERE CustCodeNew LIKE SalesOrderNew.CustomerCode)) AS MenuCode
FROM SalesOrderNew
GROUP BY SalesOrderID
Here is some output from that (I've added MealDate and MealCode to the select just to give better context to the resulting MenuCode column which is the one that needs inserted.
MealDate MealCode MenuCode
2022-08-23 K C12KNRE
2022-08-23 K C12KRES
2022-08-23 K C12KSHO
2022-08-23 K C12KRES
2022-08-25 T C14TNRE
2022-08-25 T C14TRES
2022-08-25 L C14LNRE
2022-08-25 L C14LNRE
2022-08-25 T C14TNRE
The MenuCode column is currently filled with NULL values.
However if I put add a line above to INSERT INTO SalesOrderNew (MenuCode) I get this error Result: NOT NULL constraint failed: SalesOrderNew.CustomerCode. It does not seem to like the subselect that used within the group_contact. How do I go about resolving this?
Related
I am working on developing a MILP mathematical model which deals with cyclic tasks. I at a stage where I have to design constraints in this regard. here is the simplified version of the problem.
the are 4 tasks of A type: [A1, A2, A3, A4]. I need a constraint that
first make sure the tasks are in order
second, in the planning horizon, after task A4 is again A1. this cycle will go on till the planning horizon ends.
I have created constraints to verify the sequence of tasks:
X_A: = 1 if task A is being done
index a: tasks {1, 2, .... , A}
sum over planning horizon(X_a) >= sum over planning horizon(X_a+1) for all a in {1,2, ... A-1}
I am stuck on writing a constraint to make sure within the planning horizon after the last task it goes and start the first task and repeat the cycle.
I have used Google_ortool's CP-SAT solver (python API) to solve the problem.
So if your total horizon length where you need to plan the task = 51 units,
and your tasks are tasks = {"a1" : 2, "a2" : 3, "a3" : 7, "a4" : 9, "a5" : 1} where "a1" is task and 2 is the length of the task, then solution will look like :
(cycle, task ==> whether task was performed ==> task duration)
(0, 'a1') ==> 1 ==> 2
(0, 'a2') ==> 1 ==> 3
(0, 'a3') ==> 1 ==> 7
(0, 'a4') ==> 1 ==> 9
(0, 'a5') ==> 1 ==> 1
(1, 'a1') ==> 1 ==> 2
(1, 'a2') ==> 1 ==> 3
(1, 'a3') ==> 1 ==> 7
(1, 'a4') ==> 1 ==> 9
(1, 'a5') ==> 1 ==> 1
(2, 'a1') ==> 1 ==> 2
(2, 'a2') ==> 1 ==> 3
(2, 'a3') ==> 0 ==> 0
(2, 'a4') ==> 0 ==> 0
(2, 'a5') ==> 0 ==> 0
total length = 49, which is less than the horizon length 51, we
cant take up a3 because its length is 7 and 49 + 7 >= 51. So we
stop at a2 only.
code listing in python
from ortools.sat.python import cp_model as cp
import numpy as np
planning_horizon_length = 51
# tasks and their corresponding duration
tasks = {"a1" : 2,
"a2" : 3,
"a3" : 7,
"a4" : 9,
"a5" : 1
}
total_task_length = sum(tasks.values())
# how many cycles we would require
num_cycles = int(np.ceil(planning_horizon_length / total_task_length))
model = cp.CpModel()
# decision variable : for each cycle - task whether it will be done or not
cycle_tasks = []
dv_cycle_task = {}
for i in range(num_cycles):
for j in tasks:
dv_cycle_task[(i, j)] = model.NewBoolVar("cycle : " + str(i) + " & " + "task : " + str(j))
cycle_tasks.append((i, j))
# precedence constraint : for each cycle, a1 should come before a2 and so on
for i, j in enumerate(cycle_tasks):
if i < len(cycle_tasks) - 1:
model.Add(dv_cycle_task[cycle_tasks[i]] >= dv_cycle_task[cycle_tasks[i + 1]])
# capture total run length
total_run_length = model.NewIntVar(0, planning_horizon_length, "")
model.Add(total_run_length == sum(dv_cycle_task[(i, j)] * tasks[j] for (i, j) in cycle_tasks))
# total run length should be lower than planning_horizon_length
model.Add(total_run_length <= planning_horizon_length)
# we want total run length should be as close to planning_horizon_length i.e. 51
model.Maximize(total_run_length - planning_horizon_length)
solver = cp.CpSolver()
solver.Solve(model)
# objective function value : how extra we went
solver.Value(total_run_length)
# inspect the solution
for (i, j) in cycle_tasks:
print(str((i, j)) + " ==> " + str(solver.Value(dv_cycle_task[(i, j)])) + " ==> " + str(solver.Value(dv_cycle_task[(i, j)]) * tasks[j]))
i have an sqlite database that contains a column with BLOB data.
these BLOB data are 4 byte width. i want to split the 4 bytes appart and convert each part to an integer value to calculate with it.
i found out, that i can use SUBSTR(val, start, length) to take the BLOB value appart. the result is still of type BLOB.
but how can i convert the BLOB/byte to an integer value?
is there a built-in function that can convert byte BLOB values to an integer?
or is there a way to convert a hex-string-value into an integer value, so i could play with HEX(val) or QUOTE(val)
CREATE TEMP TABLE IF NOT EXISTS test AS SELECT x'cafe1a7e' AS val;
SELECT (val)
, TYPEOF(val)
, HEX(val)
, QUOTE(val)
, TYPEOF(HEX(val))
, TYPEOF(QUOTE(val))
, CAST(val AS INT)
, CAST(HEX(val) AS INT)
, CAST(QUOTE(val) AS INT)
, SUBSTR(val, 1, 1)
, TYPEOF(SUBSTR(val, 1, 1))
, HEX(SUBSTR(val, 1, 1))
, HEX(SUBSTR(val, 2, 1))
, HEX(SUBSTR(val, 3, 2))
, val + val
, SUBSTR(val, 1, 1) + 1
, CAST(SUBSTR(val, 1, 1) AS INT)
FROM test;
DROP TABLE test;
You can convert one hex digit at a time using instr:
SELECT hex(b), n, printf("%04X", n)
FROM (SELECT b,
(instr("123456789ABCDEF", substr(hex(b), -1, 1)) << 0) |
(instr("123456789ABCDEF", substr(hex(b), -2, 1)) << 4) |
(instr("123456789ABCDEF", substr(hex(b), -3, 1)) << 8) |
(instr("123456789ABCDEF", substr(hex(b), -4, 1)) << 12) |
(instr("123456789ABCDEF", substr(hex(b), -5, 1)) << 16) |
(instr("123456789ABCDEF", substr(hex(b), -6, 1)) << 20) |
(instr("123456789ABCDEF", substr(hex(b), -7, 1)) << 24) |
(instr("123456789ABCDEF", substr(hex(b), -8, 1)) << 28) AS n
FROM (SELECT randomblob(4) AS b))
Example output:
D91F8E91|3642723985|D91F8E91
(Simplification of idea from [1].)
There is no built in function that I know of, so this is how I do it - if you know how many bytes you want to convert:
--creates table h2i with numbers 0 to 255 in hex and int
CREATE TEMP TABLE bits (bit INTEGER PRIMARY KEY);INSERT INTO bits VALUES (0);INSERT INTO bits VALUES (1);
CREATE TEMP TABLE h2i (h TEXT, i INT);
INSERT INTO h2i (h, i) SELECT printf('%02X',num),num FROM (SELECT b7.bit * 128 + b6.bit * 64 + b5.bit * 32 + b4.bit * 16 + b3.bit * 8 + b2.bit * 4 + b1.bit * 2 + b0.bit AS num FROM bits b7, bits b6, bits b5,bits b4, bits b3, bits b2, bits b1, bits b0) as nums;
SELECT
HEX(SUBSTR(val, 1, 1)),h2i0.i
,HEX(SUBSTR(val, 2, 1)),h2i1.i
,HEX(SUBSTR(val, 3, 2)),h2i2.i*256+h2i3.i
,HEX(SUBSTR(val, 1, 4)),h2i0.i*16777216+h2i1.i*65536+h2i2.i*256+h2i3.i
FROM test
JOIN h2i h2i0 ON h2i0.h=HEX(SUBSTR(val, 1, 1))
JOIN h2i h2i1 ON h2i1.h=HEX(SUBSTR(val, 2, 1))
JOIN h2i h2i2 ON h2i2.h=HEX(SUBSTR(val, 3, 1))
JOIN h2i h2i3 ON h2i3.h=HEX(SUBSTR(val, 4, 1))
;
#rayzinnz, thank you for the hint.
in the meantime i gave up.
i puzzled together a kind of a solution, but i never got it work to set the initial x'cafe1a7e' value from outside the WITH RECURSIVE construction.
WITH RECURSIVE fx(val_hex, val_int, iter) AS (
VALUES(HEX(x'cafe1a7e'), 0, 0)
UNION ALL
SELECT
SUBSTR(val_hex, 1, LENGTH(val_hex) - 1),
val_int + (
CASE SUBSTR(val_hex, -1)
WHEN '0' THEN 0
WHEN '1' THEN 1
WHEN '2' THEN 2
WHEN '3' THEN 3
WHEN '4' THEN 4
WHEN '5' THEN 5
WHEN '6' THEN 6
WHEN '7' THEN 7
WHEN '8' THEN 8
WHEN '9' THEN 9
WHEN 'A' THEN 10
WHEN 'B' THEN 11
WHEN 'C' THEN 12
WHEN 'D' THEN 13
WHEN 'E' THEN 14
WHEN 'F' THEN 15
ELSE 0
END << (iter * 4)
),
iter + 1
FROM fx
WHERE val_hex != ''
LIMIT 9
)
--SELECT * FROM fx
SELECT val_int FROM fx WHERE val_hex == ''
;
the BLOB value there is hardcoded.
maybe you find a way.
A Dutch bank account consists of 9 digits e.g.: 1334.36.915.
To check whether the bank account is valid we use the so called ‘11-proef’ (11-test).
In this test each digit is multiplied with its place in the row.The result of this multiplication is added up.
(1*9)+(3*8)+(3*7)+(4*6)+(3*5)+(6*4)+(9*3)+(1*2)+(5*1) = R
This result has to be dividable by 11. That means the remainder of the division must be 0.
If the R is dividable by 11 the bank account number is valid!
Can someone help with this question?
A Dutch bank account number does not consist of 9 digits anymore, we now use Iban Numbers. If you want to do a check on it, you should have a look at https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN and implement that check.
For now, you can however still to the 11-check on the last 9 or 10 digits, but it's not guaranteed that will still work for new bank accounts in the future.
If you still want to do a 11-check, you can create a function like this:
CREATE OR REPLACE FUNCTION elfproof (accountnummer IN varchar2)
RETURN VARCHAR2
AS
multiplier int:= 10;
outcome varchar2(10);
total int := 0;
BEGIN
FOR i IN 1 .. 9
LOOP
multiplier := multiplier - 1;
total := total + (multiplier * TO_NUMBER (SUBSTR (accountnummer, i, 1)));
END LOOP;
IF MOD (total, 11) = 0
THEN
outcome := 'good';
ELSE
outcome := 'bad';
END IF;
return outcome;
END;
This gives the remainder of division on 11:
SELECT
MOD (SUM (TO_NUMBER (SUBSTR (str, LEVEL, 1)) * (10 - LEVEL)), 11) remdiv11
FROM
(
SELECT
REPLACE ('1334.36.915', '.') str
FROM
DUAL
) d
CONNECT BY LEVEL <= LENGTH (str)
and IBAN check;) :
SELECT
DECODE (MOD (TO_NUMBER (LISTAGG (n, '') WITHIN GROUP (ORDER BY l)), 97), 1, 'OK', 'Fail') AS iban_check
FROM
(
SELECT
TO_CHAR (CASE
WHEN ASCII (c) >= 65 THEN ASCII (c) - 55
ELSE ASCII (c) - 48 END) n, c, l
FROM
(
SELECT
SUBSTR (str, LEVEL, 1) c, LEVEL l
FROM
(
SELECT
SUBSTR (s, 5) || SUBSTR (s, 1, 4) str
FROM
(
SELECT
REPLACE ('GB82 WEST 1234 5698 7654 32', ' ') s
FROM
DUAL
)
)
CONNECT BY LEVEL <= LENGTH (str)
)
)
I have a table called Trucks with two date columns: Arrival and Released. I can calculate the average number of days between those dates like so:
SELECT avg(julianday(released) - julianday(arrival))
FROM Trucks
However, I only want to count weekdays--that is, I want to ignore Saturdays and Sundays. Is there any way to do this in SQLite? I have seen solutions for more robust RDBMSs like Oracle and MSSQL, but none that work for SQLite.
The raw difference in days must be adjusted depending (only) on the week days of the arrival and released days:
rel arr|0 1 2 3 4 5 6
-------+-+-+-+-+-+-+-
0 |2 0 0 0 0 0 1
1 |2 2 0 0 0 0 1
2 |2 2 2 0 0 0 1
3 |2 2 2 2 0 0 1
4 |2 2 2 2 2 0 1
5 |2 2 2 2 2 2 1
6 |2 2 2 2 2 2 2
This number can be computed with a simple expression (here: the inner CASE expression):
SELECT
AVG(julianday(released) - julianday(arrival) -
CASE WHEN julianday(released) = julianday(arrival) THEN 0
ELSE (CAST((julianday(released) - julianday(arrival)) / 7 AS INTEGER) * 2
) +
CASE WHEN strftime('%w', arrival) <= strftime('%w', released) THEN 2
ELSE strftime('%w', arrival) = '6'
END
END)
FROM trucks
(A boolean expression like x='6' returns 0 or 1.)
Okay, I figured out a very messy solution using lots of nested CASE statements. It checks the weekday number of released and then the weekday number of arrival and does a calculation to figure out how many weeks have passed. After that, I add 0, 1, or 2 as a base number of weekend days that must have passed between those two days (ie from Friday to Monday is always +2 weekend days, even if less than a whole week has passed between the dates).
Here it is, in case anyone finds it useful. Quite possibly the ugliest SQL I have ever written. If anyone figures out a better way, please let me know.
(Edited for simplicity based on CL's feedback)
SELECT
avg(
julianday(released) - julianday(arrival) -
CASE WHEN julianday(released) = julianday(arrival) THEN 0 ELSE
CASE strftime('%w', released)
WHEN '0' THEN
CASE strftime('%w', arrival)
WHEN '0' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '1' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 0)
WHEN '2' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 0)
WHEN '3' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 0)
WHEN '4' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 0)
WHEN '5' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 0)
WHEN '6' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 1)
END
WHEN '1' THEN
CASE strftime('%w', arrival)
WHEN '0' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '1' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '2' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 0)
WHEN '3' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 0)
WHEN '4' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 0)
WHEN '5' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 0)
WHEN '6' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 1)
END
WHEN '2' THEN
CASE strftime('%w', arrival)
WHEN '0' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '1' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '2' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '3' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 0)
WHEN '4' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 0)
WHEN '5' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 0)
WHEN '6' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 1)
END
WHEN '3' THEN
CASE strftime('%w', arrival)
WHEN '0' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '1' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '2' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '3' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '4' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 0)
WHEN '5' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 0)
WHEN '6' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 1)
END
WHEN '4' THEN
CASE strftime('%w', arrival)
WHEN '0' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '1' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '2' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '3' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '4' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '5' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 0)
WHEN '6' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 1)
END
WHEN '5' THEN
CASE strftime('%w', arrival)
WHEN '0' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '1' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '2' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '3' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '4' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '5' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '6' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 1)
END
WHEN '6' THEN
CASE strftime('%w', arrival)
WHEN '0' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '1' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '2' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '3' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '4' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '5' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '6' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
END
END
END
), avg(julianday(released)-julianday(arrival))
from trucks
(Note: the avg(julianday(released)-julianday(arrival)) at the end is just for testing purposes to show that the new calculated average is in fact less than a straight average of the difference between the two dates).
I found this post while searching to see if SQLite had a weekday function. If you use the top answer you're not likely to get a very solid value for days of the week. Here is the code I used for my project, which calculates workdays between ticket creation and closure in SpiceWorks:
SELECT t.id
,t.summary
,t.category
,u.email
,(CAST(strftime('%j', t.closed_at) as INTEGER) - CAST(strftime('%j', t.created_at) as INTEGER) - /*I can't figure it out, but julianday() wasn't giving me the correct distances for some numbers. I used the day of the year, instead, which resolved things as expected*/
CASE
WHEN CAST(strftime('%W', t.closed_at) as INTEGER) - CAST(strftime('%W', t.created_at) as INTEGER) = 0 THEN 0 /*If they are in the same week then there is no weekend to count. If you closed a ticket, that was opened on a Monday, on Saturday, then you must of worked on Saturday and therefore counts as work day the same as if you finished it on Monday*/
WHEN CAST(strftime('%w', t.created_at) as INTEGER) = 0 THEN (CAST(strftime('%W', t.closed_at) as INTEGER) - CAST(strftime('%W', t.created_at) as INTEGER)) * 2 - 1 /*If they made their ticket on Sunday, don't count that Sunday*/
WHEN CAST(strftime('%w', t.created_at) as INTEGER) = 6 THEN (CAST(strftime('%W', t.closed_at) as INTEGER) - CAST(strftime('%W', t.created_at) as INTEGER)) * 2 - 2 /*If they made their ticket on Saturday, don't count that Saturday*/
ELSE (CAST(strftime('%W', t.closed_at) as INTEGER) - CAST(strftime('%W', t.created_at) as INTEGER)) * 2 /*Ignoring the possibility for closed dates that happen before open dates, take the number of weeks between each date and assume each had a Saturday and Sunday within them*/
END) as 'Week Days Elapsed' /*This equation in full represents: Full_Distance_In_Days_Between - Number of weekend days calculated*/
FROM Tickets as t
LEFT JOIN Users as u
on t.created_by = u.id
WHERE strftime('%Y-%m', t.created_at) = strftime('%Y-%m', 'now')
What is the shortest possible calculation f(i, n, len, offset) that wraps a range of integers starting from n (>=0) with length len, given a certain offset?
i offset 0 offset 1 offset 2 offset 15
10 -> 10 -> 15 -> 14 -> 13
11 -> 11 -> 10 -> 15 -> 14
12 -> 12 -> 11 -> 10 -> 11
13 -> 13 -> 12 -> 11 -> 10
14 -> 14 -> 13 -> 12 -> 11
15 -> 15 -> 14 -> 13 -> 12
So f(10, 10, 5, 1) = 15, f(15, 10, 5, 1) = 14 and f(10, 10, 5, 2) = 14.
Bonus karma for negative numbers or negative offsets or ranges that cross 0.
I don't know about "shortest possible", but this seems to work:
f(int n, int base, int len, int offset)
{ int r = n - offset;
if (r < base)
r += len;
return r;
}
It does require adding the base argument, though, because otherwise you have no idea where n is with respect to the range (e.g. is 15 at the bottom of the 15-20 range or the top of 10-15?). So your examples would become f(10, 10, 6, 1), f(15, 10, 6, 1), etc...
Haven't checked whether that works for negative numbers and/or ranges spanning 0, and it also fails if offset > len, but that can be worked around by adding offset %= len to normalize the input parameters.