Comment $\rightarrow$ answer.
Without loss of generality, we assume that all weights are strictly positive (otherwise, we can take all non-positively-weighted items ahead of time and change $W$ as appropriate).
From here, one can use an approach reminiscent of Randomized Quick-Select to solve this in expected linear time. First, define a tie breaking procedure for weights that consistently breaks ties between equal weighted items. For example, one can use the ordering of items as given in the input.
Now repeat the following procedure. Pick a random element $k$ and check if the sum of weights of all elements no heavier than $k$ (using the above tie-breaking rules) is less than $W$. If so, the optimal solution must contain all such elements, so put them all aside and recurse on the subproblem of elements heavier than k, with $W$ decreased appropriately. Otherwise, the optimal solution must be a strict subset of these elements, so discard all larger elements and recurse with the same $W$ you started with. The base case of the recursion is one where you return the empty set when $W$ is less than the cost of the cheapest item (or if no items remain).
Each round takes time linear in the input size for that round, but it takes a constant number of rounds (in expectation) to reduce the size of the instance by a constant factor, so the total running time intuitively should be expected linear. Indeed, the usual analysis of Randomized Quickselect works identically here, too, proving the result.
Here's a non-tail-recursive implementation in Python.
def uniformKnapsack(items, W):
import random
def subProcedure(remaining, new_W):
# base case
if remaining == [] or min(i[0] for i in remaining) > new_W:
return []
# sample random item
random_item = random.choice(remaining)
# split input into larger and smaller subsets based on the item
smaller = [i for i in remaining if i < random_item]
not_smaller = [i for i in remaining if random_item <= i]
# compute the total cost of all smaller items
smaller_cost = sum(i[0] for i in smaller)
if smaller_cost <= new_W:
# include all smaller elements and recurse on the rest with a smaller W
return smaller + subProcedure(not_smaller, new_W - smaller_cost)
else:
# recurse on the smaller subset with the same W
return subProcedure(smaller, new_W)
# convert a list of items (a, b, c) to a list of the form ((a, 0), (b, 1), (c, 2)) for
# consistent tie breaking in comparison operations
augmented_items = [(j, i) for (i, j) in enumerate(items)]
# compute the solution over the augmented items
solution = subProcedure(augmented_items, W)
# return the un-augmented items from the solution
return [i[0] for i in solution]