Given a set of items (sized anywhere from 1 to 100) and a number of bins (1 to 15.) Each item having a subset of bins the item can be assigned to and a preference ordering of which bin is best, second best, etc., just for it. Items also have a natural order, represented below by naming, e.g., item1 before item2. Each bin has a capacity between 1 and 5 (every item has identical weight, i.e., 1.)
An example input could be three bins and six items (- indicates a bin is not in the item's usable set, i.e., can't be packed with it):
| bin1 bin2 bin3 | bin1 bin2 bin3
------------------------ ----------------------------
item1 | 1 2 - capacity | 4 4 5
item2 | - 1 2
item3 | 2 1 3
item4 | 1 2 3
item5 | 1 - 2
item6 | 1 2 3
The goals are (in order with each goal completely overriding any lower goal when there's a conflict, e.g., packing five items is always better than four no matter what number of bins are used or preferences ignored):
maximize number of items packed
pack items in their natural order, e.g., if total bin capacity is one and there are two items, item1 will be packed and item2 not
minimize number of bins used
pack each item according to its bin preferences and natural order, i.e, item1 in its first preference and item2 in its second is better than item1 in its second and item2 in its first
in cases where two solutions are indistinguishable by these goals, either solution is acceptable to rank higher, e.g, as a side-effect of implementation or just arbitrary tie-breaking.
So the input above would be packed as:
| bin1 bin2 bin3
------------------------
item1 | x
item2 | x
item3 | x
item4 | x
item5 | x
item6 | x
The question then is for what to read/review to help me come up with algorithm ideas for solving this problem with the input sizes from the first paragraph and a time constraint of a few seconds, i.e., not brute force (or at least any brute force I've conceived of so far.) I'm using Ruby and C but language isn't overly relevant at this stage of woods stumbling.
I'll be grateful of any reading suggestions, ideas on combinations of algorithms, or just thoughts on clarifying the problem statement...
Update 1
To be less unclear, while there are many algorithms that cover various parts of this my difficulty is in finding (or perhaps recognizing) information handling all the criteria together, especially minimizing the number of bins used when there is excess capacity and conflicting item-to-bin sets and item preferences, which is hopefully more clearly shown in the following example:
| bin1 bin2 bin3 | bin1 bin2 bin3
------------------------ ----------------------------
item1 | 1 2 3 capacity | 3 2 3
item2 | 1 2 3
item3 | - 1 2
While bin1 is the most preferred, item3 can't be placed in it at all, and while bin2 is the next most preferred for all items, it can hold only two of the three items. So the correct set of assignments (x) is actually the least preferred bin:
| bin1 bin2 bin3
------------------------
item1 | x
item2 | x
item3 | x
Update 2
I reworked the description with information on how the goals relate and removed the variable of bin priority as it only makes finding an answer less likely and can be worked around elsewhere in the system I'm working on.
Suppose there are n items and b bins, and each bin has size s. The ordering of constraints you have added actually simplifies the problem a great deal.
They mean specifically that we should always pick items 1, 2, ..., m for the largest m <= n that will fit in the allotted number of bins (since picking a smaller number would necessarily produce a worse solution by rule 1). Items will be packed in bins in this order, possibly with some bins left incompletely filled (since rearranging items within a bin or across bins would produce a worse solution by rule 2). There are 2 cases:
m < n, meaning that we can't fit all the items. In that case, all b bins will be tightly packed with the 1st m items in that order, and we are done.
m = n, in which case we can fit all the items. We now consider subcases of this case.
In this case, it may be possible that packing bins tightly will leave a final block of 0 < e <= b of the bins completely empty. In that case, discard those final e empty bins and proceed (since using more bins would produce a worse solution by rule 3). In any case, call the final number of bins remaining r. (r = b - e.)
We now know exactly which items and which bins we will be using. We also know the order in which the items must be packed. Because of the ordering constraint, we can regard the decisions about which bins are to be left incompletely filled as the problem of how to inject "start-next-bin" instructions into the ordered list 1, 2, ... n of items. We can inject up to r-1 of these instructions.
This problem can be solved in O(nrs) time using dynamic programming. Essentially we compute the function:
f(i, j, k) = the score of the best solution in which the first i items occupy the first j boxes, with exactly k items in the jth box.
The recurrence is:
f(i, j, 0) = max(f(i, j-1, k)) over all 0 <= k <= s
f(i, j, k > 0) = f(i-1, j, k-1) + q(i, j)
Where q(i, j) is the quality score of assigning item i to box j. (As I mentioned in the comments on your post, you need to decide on some way to assign scores for a placement of any item i into any box j, presumably based on how well i's preferences are met. If it's easier to work with "badness" values than quality values, just change the max() to a min() and the -infinity boundary values below to infinity.)
The first equation says that the best score of a solution for the first i items whose rightmost bin is empty is equal to the best score that can be found by considering every solution for the first i items without that bin. These candidate solutions consist of all the ways that the previous bin can be packed, including leaving it empty too.
The second equation says that the best score for the first i items whose rightmost bin is not empty is found simply by adding the quality score for placing the last item to the best score for placing the first i-1 items in the same number of bins.
The boundary conditions are:
f(0, 0, 0) = 0
f(i, 0, k) = -infinity for all other i and k
After calculating the values of f(i, j, k) for each 0 <= i <= n, 0 <= j <= r and 0 <= k <= s and storing them in a table, f(n, r, s) will give the optimal score of the final solution. Although this only gives the score of the maximum, the actual optimal solution(s) can be found by tracing back through the f(i, j, k) matrix from the end, at each k = 0 step looking for the predecessor state (i.e. the alternative under the max()) that must have led to the current state. (It may happen that several alternatives under the max() give equal scores, in which case multiple optimal solutions exist, and any of these paths can be followed to find just one of them.)
This reminds me of the "Match" algorithm used to place medical school graduates in residency programs. What if you treat the items like students, their bin preferences like the rank lists, and the bins like hospitals?
Basically, you go through the list of items, and for each item, find the bin it prefers most. Check with the bin: do you have room for this item, and if not, do you prefer it more than any items you currently have?
If no, cross this bin off the item's list, and move to the item's next choice.
If yes, place this item in the bin, and put the displaced item (if any) back in the unmatched pool.
The difference between your problem and the residency match is that you wouldn't fix the bin's preferences up front. Instead you would use a rule that prefers items that bring the bin closest to 100% full.
My only concern is that this modification might make the algorithm unstable. But it's such a simple algorithm, it's probably worth trying.
This is a bipartite matching problem and can be solved in polynomial time.
http://en.wikipedia.org/wiki/Matching_(graph_theory)#Maximum_matchings_in_bipartite_graphs
Related
I had an algorithm to solve the problem where professor has to sort the students by their class score like 1 for good and 0 for bad. in minimum number of swaps where only adjacent students can be swapped. For Example if Students are given in sequence [0,1,0,1] only one swap is required to do [0,0,1,1] or in case of [0,0,0,0,1,1,1,1] no swap is required.
From the problem description I immediately know that it was a classic min adjacent swap problem or count inversion problem that we can find in merge sort. I tried my own algorithm as well as the one listed here or this site but none passed all the tests.
The most number of test cases were passed when I try to sort the array in reverse order. I also tried to sort the array in the order based on whether the first element of the array is 0 or 1. For example is the first element is 1 then I should sort the array in descending order else in ascending order as the students can be in any grouping, still none worked. Some test cases always failed. The thing was when I sort it in ascending order the one test case that was failing in case of reverse sorting passed along with some others but not all. So I don't know what I was doing wrong.
It feels to me that term "sorting" is an exaggeration when it comes to an array of 0's and 1's. You can simply count 0's and 1's in O(n) and produce an output.
To address "minimal swaps" part, I constructed a toy example; two ideas came to my mind. So, the example. We're sorting students a...f:
a b c d e f
0 1 0 0 1 1
a c d b e f
0 0 0 1 1 1
As you see, there isn't much of a sorting here. Three 0's, three 1's.
First, I framed this as an edit distance problem. I. e. you need to convert abcdef into acdbef using only "swap" operation. But how does you come up with acdbef in the first place? My hypothesis here is that you merely need to drag 0's and 1's to opposite sides of an array without disturbing their order. E. g.
A B C D
0 0 ... 0 ... 1 ... 0 ... 1 ... 1 1
0 0 0 0 ... 1 1 1 1
A C B D
I'm not 100% sure if it works and really yields you minimal swaps. But it seems reasonable - why would you spend an additional swap for e. g. A and C?
Regarding if you should place 0's first or last - I don't see an issue with running the same algorithm twice and comparing the amount of swaps.
Regarding how to find the amount of swaps, or even the sequence of swaps - thinking in terms of edit distances can help you with the latter. Finding just numbers of swaps can be a simplified form of edit distance too. Or perhaps something even more simple - e. g. find something (a 0 or 1) that is nearest to its "cluster", and move it. Then repeat until the array is sorted.
If we had to sort the zeros before the ones, this would be straightforward inversion counting.
def count(lst):
total = 0
ones = 0
for x in lst:
if x:
ones += 1
else:
total += ones
return total
Since sorting the ones before the zeros is also an option, we just need to run this algorithm twice, once interchanging the roles of zeros and ones, and take the minimum.
There are many typical questions like https://softwareengineering.stackexchange.com/questions/150616/return-random-list-item-by-its-weight
Imagine more advanced problem.
You have N sources of pair (item_id, weight) information. Let's call them Shards. Shards contain lists of pairs (item_id, weight).
And you have central node, let's call it Central.
The problem is: on Central choose random item from The Big List (the list virtually merged from all lists on all shards) according to their weight through all weights.
For example, we have two shards:
+-------+---------+--------+
| shard | item_id | weight |
+-------+---------+--------+
| 1 | 1 | 7 |
| 1 | 2 | 4 |
| 1 | 3 | 2 |
| 2 | 4 | 5 |
| 2 | 5 | 1 |
+-------+---------+--------+
(Let item_id will be unique through all shards.)
First problem:
How to choose item_id randomly but weighted through all shards?
I.e. total_weight == 7+4+2+5+1 == 19, so 1 will be chosen with probability of 7/19, 2 - 4/19, 3 - 2/19 and so on.
Second problem:
How to range all items from all shards randomly, but weighted through all shards?
I.e. ideal ranging will be: 1, 4, 2, 3, 5 (according to their weights),
but there may be another ranging like 1, 2, 4, 3, 5, but slightly less frequently than previous,
...
and worst case 5, 3, 2, 4, 1 can also appear, but with very-very little probability.
Is there common problem in computer science for this?
I think your two questions are independent and should have been separate questions. Also I'm not sure that I have understood them correctly. But here we go:
Sharded weighted random query
If your reference to sharding has to do with distributing the item store over multiple network hosts and attempting to do some sort of network parallel random select, then you can use the modified reservoir sample algorithm which I outline at the end of this answer.
That algorithm was originally developed for use in a redundant network where the various storage hosts are not necessarily reachable directly from the central host and the connectivity is a graph, not a tree. In that case, you need to be able to deal with hosts which do not respond (which will bias an individual query, but if the network faults are infrequent and random will hopefully not bias a long series of queries). It's also necessary to deal with the possibility that a host is queried twice; in the outlined algorithm, I simply ignore the second and subsequent queries on the assumption that if a query reaches a host, then the response is likely to be returned to the querying host. That could be completely wrong, but it makes the problem much easier and again it's probably unbiased over a sufficiently large number of queries.
Without the complications, if the central host can reliably connect to every storage host, the algorithm is straight-forward. The central host queries all storage hosts in parallel, and each storage host returns a tuple of the total weight of objects it stores, and one object randomly selected according to those weights. (It uses some standard weighted random selection algorithm to do that. Which algorithm is used will depend on how often the objects and weights change.)
The central host maintains two variables: total, the sum of weights from servers which have responded (initially 0), and candidate, a random object which might be returned (initially some sentinel indicating "no object").
It handles the responses one at a time, in any order (such as the order it receives them). For each response <weight, object>, it does the following:
total ← total + weight
r ← a random integer in the range [0, total)
if r < weight: candidate ← object
When it decides that all remote servers have responded, it returns candidate.
Weighted random shuffle
(At least, I think you're asking for a weighted random shuffle).
I was going to suggest using the standard Fisher-Yates algorithm modified with weights, which I think will yield the sampling behaviour you expect. To do that, you start with the objects in any arbitrary order, and then for each value of i from 1 to n:
select j, the index of a (weighted) random element from the objects starting at i, and swap objects i and j.
To do that, you need to maintain the CDF of the successively smaller suffixes, which you can do in O(log N) by keeping the objects in a binary tree. (Or you can do it much more simply in O(N), if N is smallish.)
However, I did a quick search on SO before hitting the Post button, and concluded that this brilliant answer is actually better, because it achieves O(N log N) without any complicated data structure: For each object, you generate a random number from an exponential distribution whose rate is the corresponding weight. Then you sort the objects according to those random numbers, and the result is the random shuffle.
For your first problem, you can do something like
// Your pairs (item_id, weight).
ArrayList<Pair> theBigList;
// The total weight of your data. Get it updated.
int totalWeight;
public int weightedSearch() {
// Local copy of the value.
int total = totalWeight;
Random r = new Random();
// Random integer in the interval [1 - total]
int random = r.nextInt(total) + 1;
for (int i = 0; true; i++) {
if (theBigList.get(i).weigth < total)
total -= theBigList.get(i).weigth;
else
return theBigLits.get(i).itemId;
}
}
The random search, but weighted, is given by the random integer generated. In your example, if random is between 1 and 7 (7/19 prob.), your first element will be returned; if it is between 8 and 11 (4/19 prob.), second element is returned, etc.
Tip: Get your heavy items in the beginning, so you have more probability to return faster the weighted search (your loop ends before).
There are N sticks placed in a straight line. Bob is planning to take few of these sticks. But whatever number of sticks he is going to take, he will take no two successive sticks.(i.e. if he is taking a stick i, he will not take i-1 and i+1 sticks.)
So given N, we need to calculate how many different set of sticks he could select. He need to take at least stick.
Example : Let N=3 then answer is 4.
The 4 sets are: (1, 3), (1), (2), and (3)
Main problem is that I want solution better than simple recursion. Can their be any formula for it? As am not able to crack it
It's almost identical to Fibonacci. The final solution is actually fibonacci(N)-1, but let's explain it in terms of actual sticks.
To begin with we disregard from the fact that he needs to pick up at least 1 stick. The solution in this case looks as follows:
If N = 0, there is 1 solution (the solution where he picks up 0 sticks)
If N = 1, there are 2 solutions (pick up the stick, or don't)
Otherwise he can choose to either
pick up the first stick and recurse on N-2 (since the second stick needs to be discarded), or
leave the first stick and recurse on N-1
After this computation is finished, we remove 1 from the result to avoid counting the case where he picks up 0 sticks in total.
Final solution in pseudo code:
int numSticks(int N) {
return N == 0 ? 1
: N == 1 ? 2
: numSticks(N-2) + numSticks(N-1);
}
solution = numSticks(X) - 1;
As you can see numSticks is actually Fibonacci, which can be solved efficiently using for instance memoization.
Let the number of sticks taken by Bob be r.
The problem has a bijection to the number of binary vectors with exactly r 1's, and no two adjacent 1's.
This is solveable by first placing the r 1's , and you are left with exactly n-r 0's to place between them and in the sides. However, you must place r-1 0's between the 1's, so you are left with exactly n-r-(r-1) = n-2r+1 "free" 0's.
The number of ways to arrange such vectors is now given as:
(1) = Choose(n-2r+1 + (r+1) -1 , n-2r+1) = Choose(n-r+1, n-2r+1)
Formula (1) is deriving from number of ways of choosing n-2r+1
elements from r+1 distinct possibilities with replacements
Since we solved it for a specific value of r, and you are interested in all r>=1, you need to sum for each 1<=r<=n
So, the solution of the problem is given by the close formula:
(2) = Sum{ Choose(n-r+1, n-2r+1) | for each 1<=r<=n }
Disclaimer:
(A close variant of the problem with fixed r was given as HW in the course I am TAing this semester, main difference is the need to sum the various values of r.
This question was asked in TopCoder - SRM 577. Given 1 <= a < b <= 1000000, what is the minimum count of numbers to be inserted between a & b such that no two consecutive numbers will share a positive divisor greater than 1.
Example:
a = 2184; b = 2200. We need to insert 2 numbers 2195 & 2199 such that the condition holds true. (2184,2195,2199,2200)
a = 7; b= 42. One number is sufficient to insert between them. The number can be 11.
a = 17;b = 42. The GCD is already 1, so no need to insert any number.
Now, the interesting part is that for the given range [1,1000000] we never require more than 2 elements to be inserted between a and b. Even more, the 2 numbers are speculated to be a+1 and b-1 though it yet to be proven.
Can anyone prove this?
Can it be extended to larger range of numbers also? Say, [1,10^18] etc
Doh, sorry. The counterexample I have is
a=3199611856032532876288673657174760
b=3199611856032532876288673657174860
(Would be nice if this stupid site allowed everyone to edit its posts)
Each number has some factorization. If a, b each have a little number of distinct prime factors (DPF), and distance between them is large, it is certain there will be at least one number between them, whose set of DPF s has no elements in common with the two. So this will be our one-number pick n, such that gcd(a,n) == 1 and gcd(n,b) == 1. The higher we go, the more prime factors there are, potentially, and the probability for even gcd(a,b)==1 is higher and higher, and also for the one-num-in-between solution.
When will one-num solution not be possible? When a and b are highly-composite - have a lot of DPF s each - and are situated not too far from each other, so each intermediate number has some prime factors in common with one or two of them. But gcd(n,n+1)==1 for any n, always; so picking one of a+1 or b-1 - specifically the one with smallest amount of DPF s - will decrease the size of combined DPF set, and so picking one number between them will be possible. (... this is far from being rigorous though).
This is not a full answer, more like an illustration. Let's try this.
-- find a number between the two, that fulfills the condition
gg a b = let fs=union (fc a) (fc b)
in filter (\n-> null $ intersect fs $ fc n) [a..b]
fc = factorize
Try it:
Main> gg 5 43
[6,7,8,9,11,12,13,14,16,17,18,19,21,22,23,24,26,27,28,29,31,32,33,34,36,37,38,39
,41,42]
Main> gg 2184 2300
[2189,2201,2203,2207,2209,2213,2221,2227,2237,2239,2243,2251,2257,2263,2267,2269
,2273,2279,2281,2287,2291,2293,2297,2299]
Plenty of possibilities for just one number to pick between 5 and 43, or between 2184 and 2300. But what about the given pair, 2184 and 2200?
Main> gg 2184 2200
[]
No one number exists to put in between them. But obviously, gcd (n,n+1) === 1:
Main> gg 2185 2200
[2187,2191,2193,2197,2199]
Main> gg 2184 2199
[2185,2189,2195]
So having picked one adjacent number, we indeed have plenty of possibilities for the 2nd number. Your question is, to prove that it is always the case.
Let's look at their factorizations:
Main> mapM_ (print.(id&&&factorize)) [2184..2200]
(2184,[2,2,2,3,7,13])
(2185,[5,19,23])
(2186,[2,1093])
(2187,[3,3,3,3,3,3,3])
(2188,[2,2,547])
(2189,[11,199])
(2190,[2,3,5,73])
(2191,[7,313])
(2192,[2,2,2,2,137])
(2193,[3,17,43])
(2194,[2,1097])
(2195,[5,439])
(2196,[2,2,3,3,61])
(2197,[13,13,13])
(2198,[2,7,157])
(2199,[3,733])
(2200,[2,2,2,5,5,11])
It is obvious that the higher the range, the easier it is to satisfy the condition, because the variety of contributing prime factors is greater.
(a+1) won't always work by itself - consider 2185, 2200 case (similarly, for 2184,2199 the (b-1) won't work).
So if we happen to get two highly composite numbers as our a and b, picking an adjacent number to either one will help, because usually it will have only few factors.
This answer addresses that part of the question which asks for a proof that a subset of {a,a+1,b-1,b} will always work. The question says: “Even more, the 2 numbers are speculated to be a+1 and b-1 though it yet to be proven. Can anyone prove this?”. This answer shows that no such proof can exist.
An example that disproves that a subset of {a,a+1,b-1,b} always works is {105, 106, 370, 371} = {3·5·7, 2·53, 2·5·37, 7·53}. Let (x,y) denote gcd(x,y). For this example, (a,b)=7, (a,b-1)=5, (a+1,b-1)=2, (a+1,b)=53, so all of the sets {a,b}; {a, a+1, b}; {a,b-1,b}; and {a, a+1, b-1,b} fail.
This example is a result of the following reasoning: We want to find a,b such that every subset of {a,a+1,b-1,b} fails. Specifically, we need the following four gcd's to be greater than 1: (a,b), (a,b-1), (a+1,b-1), (a+1,b). We can do so by finding some e,f that divide even number a+1 and then construct b such that odd b is divisible by f and by some factor of a, while even b-1 is divisible by e. In this case, e=2 and f=53 (as a consequence of arbitrarily taking a=3·5·7 so that a has several small odd-prime factors).
a=3199611856032532876288673657174860
b=3199611856032532876288673657174960
appears to be a counterexample.
I have an array which contains a list of different sizes of materials : {4,3,4,1,7,8} . However, the bin can accomodate materials upto size 10. I need to find out the minimum number of bins needed to pack all the elements in the array.
For the above array, you can pack in 3 bins and divide them as follows: {4,4,1}, {3,7} , {8} . There are other possible arrangements that also fit into three stock pipes, but it cannot be done with fewer
I am trying to solve this problem through recursion in order to understand it better.
I am using this DP formulation (page 20 of the pdf file)
Consider an input (n1;:::;nk) with n = ∑nj items
Determine set of k-tuples (subsets of the input) that can be packed into a single bin
That is, all tuples (q1;:::;qk) for which OPT(q1;:::;qk) = 1
Denote this set by Q For each k-tuple q , we have OPT(q) = 1
Calculate remaining values by using the recurrence : OPT(i1;:::;ik) = 1 +
minOPT(i1 - q1;:::;ik - qk)
I have made the code, and it works fine for small data set. But if increase the size of my array to more than 6 elements, it becomes extremely slow -- takes about 25 seconds to solve an array containing 8 elements Can you tell me if theres anything wrong with the algorithm? I dont need an alternative solution --- just need to know why this is so slow, and how it can be improved
Here is the code I have written in C++ :
void recCutStock(Vector<int> & requests, int numStocks)
{
if (requests.size() == 0)
{
if(numStocks <= minSize)
{
minSize = numStocks;
}
// cout<<"GOT A RESULT : "<<numStocks<<endl;
return ;
}
else
{
if(numStocks+1 < minSize) //minSize is a global variable initialized with a big val
{
Vector<int> temp ; Vector<Vector<int> > posBins;
getBins(requests, temp, 0 , posBins); // 2-d array(stored in posBins) containing all possible single bin formations
for(int i =0; i < posBins.size(); i++)
{
Vector<int> subResult;
reqMinusPos(requests, subResult, posBins[i]); // subtracts the initial request array from the subArray
//displayArr(subResult);
recCutStock(subResult, numStocks+1);
}
}
else return;
}
}
The getBins function is as follows :
void getBins(Vector<int> & requests, Vector<int> current, int index, Vector<Vector<int> > & bins)
{
if (index == requests.size())
{
if(sum(current,requests) <= stockLength && sum(current, requests)>0 )
{
bins.add(current);
// printBins(current,requests);
}
return ;
}
else
{
getBins(requests, current, index+1 , bins);
current.add(index);
getBins(requests, current, index+1 , bins);
}
}
The dynamic programming algorithm is O(n^{2k}) where k is the number of distinct items and n is the total number of items. This can be very slow irrespective of the implementation. Typically, when solving an NP-hard problem, heuristics are required for speed.
I suggest you consider Next Fit Decreasing Height (NFDH) and First Fit Decreasing Height (FFDH) from Coffman et al. They are 2-optimal and 17/10-optimal, respectively, and they run in O(n log n) time.
I recommend you first try NFDH: sort in decreasing order, store the result in a linked list, then repeatedly try to insert the items starting from the beginning (largest values first) until you have filled the bin or there is no more items that can be inserted. Then go to the next bin and so on.
References:
Owen Kaser, Daniel Lemire, Tag-Cloud Drawing: Algorithms for Cloud Visualization, Tagging and Metadata for Social Information Organization (WWW 2007), 2007. (See Section 5.1 for a related discussion.)
E. G. Coffman, Jr., M. R. Garey, D. S. Johnson, and R. E. Tarjan. Performance bounds for level-oriented two-dimensional packing algorithms. SIAM J. Comput., 9(4):808–826, 1980.
But if increase the size of my array to more than 6 elements, it
becomes extremely slow -- takes about 25 seconds to solve an array
containing 8 elements Can you tell me if theres anything wrong with
the algorithm?
That's normal with brute force. Brute force does not scale at all.
In your case: Bin size = 30, total items = 27, at least 3 bins are needed.
You could try first fit decreasing, and it works!
More ways to improve: With 3 bins and 27 size units, you will have 3 units of space left over. Which means you can ignore the item of size 1; if you fit the others into 3 bins, it will fit somewhere. That leaves you with 26 size units. That means you will have at least two units empty in one bin. If you had items of size 2, you could ignore them as well because they would fit. If you had two items of size 2, you could ignore items of size 3 as well.
You have two items of size 7 + 3 which is exactly the bin size. There is always an optimal solution where these two are in the same bin: If the "7" were with other items, their size would be 3 or less, so you could swap them with the "3" if it is in another bin.
Another method: If you have k items >= bin size / 2 (you can't have two items equal to bin size / 2 at this point), then you need k bins. This might increase the minimum number of bins that you estimated initially which in turn increases the guaranteed empty space in all bins which increases the minimum size of leftover space in one bin. If for j = 1, 2, ..., k you can fit all items with them into j bins that could possibly fit into the same bin, then this is optimal. For example, if you had sizes 8, 1, 1 but no size 2, then 8+1+1 in a bin would be optimal. Since you have 8 + 4 + 4 left, and nothing fits with the 8, "8" alone in its bin is optimal. (If you had items of sizes 8, 8, 8, 2, 1, 1, 1 and nothing else of size 2, packing them into three bins would be optimal).
More things to try if you have large items: If you have a large item, and the largest item that fits with it is as large or larger than any combination of items that would fit, then combining them is optimal. If there is more space, then this can be repeated.
So all in all, a bit of thinking reduced the problem to fitting two items of sizes 4, 4 into one or more bins. With larger problems, every little bit helps.
I've written a bin-packing solution and I can recommend best-fit with random order.
After doing what you can to reduce the problem, you are left with the problem to fit n items into k bins if possible, into k + 1 bins otherwise, or into k + 2 bins etc. If k bins fail, then you know that you will have more empty space in an optimal solution of k + 1 bins, which may make it possible to remove more small items, so that's the first thing to do.
Then you try some simple methods: First fit descending, next fit descending. I tried a reasonably fast variation of first fit descending: As long as the two largest items fit, add the largest item. Otherwise, find the single item or the largest combination of two items that fit, and add the single item or the larger of that combination. If any of these algorithms fits your items into k bins, you solved the problem.
And eventually you need brute force. You can decide: Do you attempt to fit everything into k bins, or do you attempt to prove it isn't possible? You will have some empty space to play with. Let's say 10 bins of size 100 and items of total size 936, that would leave you 64 units of empty space. If you put only items of size 80 into your first bin, then 20 of your 64 units are already gone, making it much harder to find a solution from there. So you don't try things in random order. You first try combinations for the first bin that fill it completely or close to completely. Since small items make it easier to fill containers completely you try not to use them in the first bins but leave them for later, when you have less choice of items. And when you've found items to put into a bin, try one of the simple algorithms to see if they can finish it. For example, if first fit descending put 90 units into the first bin, and you just managed to put 99 units in there, it is quite possible that this is enough improvement to fit everything.
On the other hand, if there is very little space (10 bins, total item size 995 for example) you may want to prove that fitting the items is not possible. You don't need to care about optimising the algorithm to find a solution quickly, because you need to try all combinations to see they don't work. Obviously you need with these numbers to fit at least 95 units into the first bin and so on; that might make it easy to rule out solutions quickly. If you proved that k bins are not achievable, then k+1 bins should be a much easier target.