MiniZinc: Obtain a super set of array of sets - constraint-programming

I am working on a constraint programming problem but stuck at a specific step and need suggestions.
My data has a bunch of orders with each order having some SKUs. I want to group these orders in different batches and then count unique SKUs in a batch/group. For e.g.
Order 1 - SKUs 1, 2, 3
Order 2 - SKUs 2, 5
Order 3 - SKUs 1, 3, 7
Order 4 - SKUs 3, 4, 6
Now, if I group Orders 1 & 4 in Batch 1 while Orders 2 & 3 in Batch 2 then following will be unique SKU count in each batch:
Batch 1 - SKUs 1, 2, 3, 4, 6 = 5 SKUs
Batch 2 - SKUs 1, 2, 3, 5, 7 = 5 SKUs
My code is as below
include "globals.mzn";
int: N_Orders = 14; % Number of orders
set of int: ORDERS = 1..N_Orders;
set of int: skuids = {1,2,3,4,5}; % Distinct sku ids across all orders
array[ORDERS] of set of skuids: oskuids = [{1,2,3},{1,3},{4},{4,5},{1},{1,4},{3,4},{5},{1,4,5},{1,2,3},{1,3},{4,5},{1},{1,4}]; % Distinct SKU ids in each order
% Orders per batch
ORDERS: x = 2;
% Batches needed
int: N_Batches = 7;
% Define array that contains batch for each order
array[ORDERS] of var 1..N_Batches: obatch;
constraint global_cardinality(obatch, [i | i in (1..N_Batches-1)], [x | i in 1..(N_Batches-1)]); % Total orders in batch set to 'x'
% Distinct skus in each batch
array[1..N_Batches] of var int: skus_in_batch;
constraint forall(i in 1..N_Batches)(
skus_in_batch[i] = card(array_union(o in ORDERS where obatch[o] = i)(oskuids[o]))
);
solve satisfy;
On running this code, I get following error:
MiniZinc: type error: no function or predicate with this signature found: `array_union(array[int] of var opt set of int)'
How can I modify code to give me the required result?

If I understand it correctly, you can simply use sum instead:
constraint forall(i in 1..N_Batches)(
skus_in_batch[i] = sum([obatch[o] = i | o in ORDERS])
);
The first solution is then
obatch = array1d(1..14 ,[7, 7, 6, 6, 5, 5, 4, 4, 3, 3, 2, 2, 1, 1]);
skus_in_batch = array1d(1..7 ,[2, 2, 2, 2, 2, 2, 2]);

Here is the answer I received from the architects of MiniZinc on another forum and realized that this method can be used at many other similar situations where we receive error due to unsupported option types -
The expression
skus_in_batch[i] = card(array_union(o in ORDERS where
obatch[o] = i)(oskuids[o]));
Is effectively equivalent to
skus_in_batch[i] = card(array_union([ if obatch[o] = i then oskuids[o] else top endif | o in ORDERS]]);
and hence fails since array_union is not able to handle the array of
optional sets that are created. You can simple rewrite it to below to
avoid option types.
skus_in_batch[i] = card(array_union([ if obatch[o] = i then oskuids[o] else {} endif | o in ORDERS]]);
or equivalently
skus_in_batch[i] = card(array_union(o in ORDERS)
(if obatch[o] = i then oskuids[o] else {} endif));

Related

how can I change values from a nested list accordingly?

I am making a game which requires me to randomly select 2 buildings from a pool of different buildings, and after choosing 1 of the 2 buildings the count from the building pool will be deducted. I am having troubles changing the values of the selected items in the nested loop. Is there any way I can change it? This is my current code:
building_list = [['house', 8], ['hospital', 8], ['shopping-center', 8], ['airport', 8], ['park', 8]]
building_list1 = random.choice(building_list)[0]
building_list2 = random.choice(building_list)[0]
If you have concern about the performance of removing the "chosen" building, then I will propose different approach to this problem. The reason is that to constant search and remove list item is not efficient.
Please let me know if you have question.
n = 2 # select 2 buildings to pop
while n: # n is not zero: means it's True
random.shuffle(building_list)
building_pop = building_list.pop() # remove last item from list is O(1)
print(build_pop)
# do whatever you want to this two building here <----
n -= 1
this looks complex but basically we are finding what the random item was then decrementing its value by 1 then getting the name of the building.
import random
building_list = [['house', 8], ['hospital', 8], ['shopping-center', 8],
['airport', 8], ['park', 8]]
building1 = random.choice(building_list)
building_list[building_list.index(building1)][1] -= 1
building_list1 = building1[0]
print(building1)
print(building_list1)
building2 = random.choice(building_list)
building_list[building_list.index(building2)][1] -= 1
building_list2 = building2[0]
print(building2)
print(building_list2)
I got this output:
['shopping-center', 7]
shopping-center
['hospital', 7]
hospital

Why does the insert function produce this result?

my_list = [1, 2]
for v in range(2):
my_list.insert(-1, my_list[v])
print(my_list)
The Result specifies 1, 1, 1, 2. Why isn't the result 1, 1, 2, 2 since v in the for loop would be 0 = 1 and 1 = 2?
list.insert(index, val) determines the index of the element before which to insert.
So in your first iteration you insert a value between the 1 and the 2.
Your new list looks like [1,1,2].
In the next iteration you operate on the changed list, thus you use the newly inserted 1, which is at v=1 now. Thus your list looks like
[1,1,1,2]
^ 1 2 ^
Here ^ are the original values and 1 and 2 are the iterations the values are added.

Extracting value from list created with conditional string in list comprehension

What I have:
series = ['foo', 'bar', 'baz', 'foo', 'baz', 'foo' ]
column = [1, 2, -3, -4, 5, -6]
list = [column[function(x)].count() for x in series]
list:
foo = 3
bar = 1
baz = 2
Works fine, each instance in series is counted.
Want only positive number instances counted as well, so:
list = [column[function(x)].count() for x in series if (x := function(x)) >= 0]
list:
foo = 1
bar = 1
baz = 1
Discovered Walrus Operator, but x in my case is a string, perhaps the core problem?
I do get a syntax error with Walrus portion of code.
I need both total & positive number counts, creating say a "total" & "positive totals" columns in function seems clunky, is there a way to do this with list comprehension.
Thank you in advance for your assistance.
Since you tagged pandas:
pd.Series(column).gt(0).groupby(series).agg({'count','sum'})
Output:
count sum
bar 1 1
baz 2 1
foo 3 1
You are calling function(x) where x is the result of function(x) already. Try:
vals = [column[y].count() for x in series if (y := function(x)) >= 0]
Notes:
Use a different variable name than x so that it is less confusing (this is probably also the source of your syntax error).
list is a type name, choose a different name for the list of values.

Nested for loop resets iterator even though the iterator is manipulated [duplicate]

This question already has answers here:
Scope of python variable in for loop
(10 answers)
Closed 9 years ago.
I am trying to do something as simple as changing the varible in which I am iterating over (i) but I am getting different behaviours in both Python and C.
In Python,
for i in range(10):
print i,
if i == 2:
i = 4;
I get 0 1 2 3 4 5 6 7 8 9, but the equivalent in C:
int i;
for (i = 0; i < 10; i++) {
printf("%d", i);
if (i == 2)
i = 4;
}
I get 01256789 (note that numbers 3 and 4 don't appear, as expected).
What's happening here?
Python has a few nice things happening in the background of for loops.
For example:
for i in range(10):
will constantly set i to be the next element in the range 0-10 no matter what.
If you want to do the equivalent in python you would do:
i = 0
while i < 10:
print(i)
if i == 2:
i = 4
else: # these line are
i += 1 # the correct way
i += 1 # note that this is wrong if you want 1,2,4,5,6,7,8,9
If you are trying to convert it to C then you have to remember that the i++ in the for loop will always add to the i.
The function range() creates a list.
For example, range(10) will create the following list: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].
When you say for i in range(10), first off all the list [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] will be generated, and then i will be assigned all the values from that list, in order.
It doesn't matter if you change the value of i because on the next iteration it will be assigned the next element from the list.
It C/C++ on the other hand, at the end of each iteration the value of i is incremented and then compared to the maximum allowed value (in this case 9) and if it is greater, then the loop ends.
When you call range(10) you create an iteratable list [0,1,2,3,4,5,6,7,8,9].
And the for loop just pick up one number after the other from the list at each turn, whether or not you haved changed the value of i.
Python gives you the elements in range(10), one after another. C repeatedly increments a variable.
Both of them don't really care what else you do with the variable inside the loop, but since the two language constructs do slightly different things, the outcome is different in some cases.
You can not do this by using range function.
you have to do it by using while loop only because for loop uses range function and in range function variable will get incremented by its internal method no matter what you specify in the loop it will get incremented by range list only.
for i in range(10):
... print i
... if i == 2:
... i = 4
... else:
... i += 1
...
0
1
2
3
4
5
6
7
8
9
An interesting example is here....
for i in range(10):
... print i
... i = i + 10
... print i
...
this will print...
0
10
1
11
2
12
3
13
4
14
5
15
6
16
7
17
8
18
9
19
It's because when you use the range() function in python. Your variable i will be go through the value in range. For example,
>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
However, the C language that you have written is just using the normally condition to change the value of i. There is no function involved.

finding onsets (and offsets) of number sequence

given the following cell array:
strings = {'str1', 'str2', 'str2', 'str1', 'str2', 'str2', 'str1', 'str1', 'str2'};
I want to find the onsets and offsets (first occurrence and last occurrence) of a particular value. For example, the following are the onsets and offsets for 'str1':
onset_str1 = [1, 4, 7, ];
offset_str1 = [1, 4, 8, ];
And here are the on- and offsets for 'str2':
onset_str2 = [2, 5, 9];
offset_str2 = [3, 6, 9];
Currently I do something like this:
[blub, ia, ic] = unique(strings, 'first');
all_str1 = find(ic == 1); % 1 4 7 8
all_str2 = find(ic == 2); % 2 3 5 6 9
Using all_str1 and all_str2 I would then look for consecutive values (using diff for example) and determine that way the on and offsets. However this kind of implementation feels 'hackish' to me.
What other ways are there to extract the on and offsets in my sequence efficiently?
[blub, ia, ic] = unique(strings, 'first');
ok, but next up, just use logicals and find to find the rising/falling edges:
N = numel(blub); % number of unique strings found
str_onsets=cell(N,1);
str_offsets=cell(N,1);
for ii=1:N
x=ic==ii;
str_onsets{ii} = find([true ~x(1:end-1)] & x);
str_offsets{ii}= find(x & [~x(2:end) true]);
end
or strfind if that's more clear to understand to you:
N = numel(blub); % number of unique strings found
str_onsets=cell(N,1);
str_offsets=cell(N,1);
for ii=1:N
x=ic==ii;
str_onsets{ii} = strfind([0 x],[0 1]);
str_offsets{ii}= strfind([x 0],[1 0]);
end

Resources