Search code examples
pythonlistsampledistinct-values

Find `n` distinct elements in list with duplicates, == is okay, === is not


I am trying to implement a differential evolution optimization algorithm and I need a way to take from a list distinct entities. They can be the same values, but can't be the same entities.

solutions = [[random.uniform(0, 1) for _ in range(30)] \
  for _ in range(100)]
for base_agent in solutions:
  agents = []
  while len(agents) < 3:
    p = solutions[random.randint(0, len(solutions))]
    if p in agents or p is base_agent:
      continue
    else:
      agents.add(p)
  if base_agent in agents \
    or agents[0] is agents[1] \
    or agents[1] is agents[2] \
    or agents[2] is agents[0]:
      print("Bad execution")
      exit(1)
print("Correct execution")

This is the (non-working) general idea of what I'm after.

I want base_agent to not be in agents, and I want agents to contain no duplicate entries. Entries can be the same value (e.g. they can ==), but they cannot be the same entity (e.g. they cannot ===). agents should have 3 elements within at the end of this while loop.

random.sample can give me, well, a random sample from a list, but it will still possibly include base_agent.

Any ideas?


Solution

  • The simplest code is to just use random.sample, excluding the single element you're trying to avoid. Sure, it involves slicing out all elements save the base_element, but O(n²) work isn't all that much of a problem when n is only 30:

    import random
    
    for i, base_agent in enumerate(solutions):
        agents = random.sample(solutions[:i] + solutions[i+1:], 3)
        # Do stuff with base_agent which is distinct from all elements of agents
    

    If you really hate slicing or using index values at all, you can do cheesy hacks with itertools.combinations to make it generate all the values that don't match the current base_agent for you:

    import random
    from itertools import combinations
    
    for base_agent, other_agents in zip(solutions, combinations(reversed(solutions), len(solutions) - 1)):
        agents = random.sample(other_agents, 3)
    

    Making combinations of "all but one element", using solutions in reverse order means each output from combinations is all the elements but the one you get on each iteration over solutions, so no slicing is needed.