aht
aht

Reputation: 59

Choosing one element from list according to preference

I'm given an unordered list of objects of this form

class Element {
  String property; // has value from the set ("A", "B", "C", "D")
}

where no two elements have the same property value.
I want to choose one element according to a set of rules like the following and return its property value:

  1. If there is an element whose property has value "A", then pick that element.
  2. Else, if there is an element whose property has value "B", then pick that element.
  3. Else, if there is an element whose property has value "C", then pick that element.
  4. Finally if no element has been chosen yet, then throw an exception.

This is one solution I've come up with for this problem:

String chooseElementProperty(List<Element> elements) {
  // Iterates the list and creates a set of all the property values.
  // example: [{property: 'A'}, {property: 'B'}] -> {'A', 'B'}
  Set<String> propertyValues = pluckPropertyValues(elements);

  if (propertyValues.contains("A"))
    return "A";
  if (propertyValues.contains("B"))
    return "B";
  if (propertyValues.contains("C"))
    return "C";
  throw new IllegalArgumentException("Invalid input!");
}

My question is: What is a better/cleaner way to do this that makes this method more extensible where it's easier to change my preference if in the future I want to choose "B" over "A"?

Upvotes: 4

Views: 1225

Answers (5)

Marco13
Marco13

Reputation: 54659

The current answers seem to assume that the order is "A", "B", "C", and thus, may have overlooked the last, crucial statement:

What is a better/cleaner way to do this that makes this method more extensible where it's easier to change my preference if in the future I want to choose "B" over "A"?

Beyond that, the solutions may be considered as ... "tricky" ... or complex, from a computational point of view. I'd vote for a solution that is similar to yours, but without the creation of the list of properties, and basically just puts the preferences into a list to iterate over it:

import java.util.Arrays;
import java.util.List;

class Element
{
    String property;

    Element(String property)
    {
        this.property = property;
    }
}
public class PickByPreference
{
    public static void main(String[] args)
    {
        List<Element> elements = Arrays.asList(
            new Element("B"),
            new Element("C"),
            new Element(null),
            new Element("A"));
        System.out.println(chooseElementProperty(elements));
    }

    static String chooseElementProperty(List<Element> elements) 
    {
        // Change this to change the preferences
        // (or pass it in as a parameter)
        List<String> preferences = Arrays.asList("A", "B", "C");

        for (String preference : preferences)
        {
            for (Element element : elements)
            {
                if (preference.equals(element.property))
                {
                    return element.property;
                }
            }
        }
        throw new IllegalArgumentException("Invalid input!");
    }
}

Edit: In terms of asymptotic complexity, extracting the properties into a Set may be beneficial, though, particularly if you have many elements. In this case, the implementation would be

static String chooseElementProperty(List<Element> elements) 
{
    // Change this to change the preferences
    // (or pass it in as a parameter)
    List<String> preferences = Arrays.asList("A", "B", "C");
    Set<String> propertyValues = pluckPropertyValues(elements);
    for (String preference : preferences)
    {
        if (propertyValues.contains(preference))
        {
            return element.property;
        }
    }
    throw new IllegalArgumentException("Invalid input!");
}

Upvotes: 2

Pritam Banerjee
Pritam Banerjee

Reputation: 18933

The previous answers are correct.

My two cents would be to avoid contains() as it uses indexOf()

So use

if (propertyValues.indexOf("A") > -1)
   return "A";
if (propertyValues.indexOf("B") > -1)
   return "B";
if (propertyValues.indexOf("C") > -1)
   return "C";

Upvotes: 1

Bohemian
Bohemian

Reputation: 425198

Try this:

List<String> targets = Arrays.asList("A", "B", "C");
return elements.stream()
  .map(this::pluckPropertyValues)
  .flatMap(Set::stream)
  .filter(targets::contains)
  .sorted(Comparator.comparing(targets::indexOf))
  .findFirst()
  .orElseThrow(IllegalArgumentException::new);

This will work for an arbitrary list of targets, with preferences based on their position in the list.

Disclaimer: Code may not compile or work as it was thumbed in on my phone (but there's a reasonable chance it will work)

Upvotes: 2

Kevin Anderson
Kevin Anderson

Reputation: 4592

Rather than having the desired property values hard-coded into chooseElementProperty, provide them in an additional argument:

String chooseElementProperty(List<Element> elements, List<String> prefs) {
  // Iterates the list and creates a set of all the property values.
  // example: [{property: 'A'}, {property: 'B'}] -> {'A', 'B'}
  Set<String> propertyValues = pluckPropertyValues(elements);

  for (String s: prefs) {
      if (propertyValues.contains(s))
          return s;
  }
  throw new IllegalArgumentException("Invalid input!");
}

Here I've used a List<String>, but you could also use a String[] or even a String... varargs if you prefer.

Upvotes: 4

Mohd
Mohd

Reputation: 5613

Assuming all the property values are letters, you can sort the list and provide a comparator to sort by propery then validate the property of the first item

Collections.sort(elements, new Comparator<Element>() {
    @Override
    public int compare(Element a, Element b) {
        return a.property.compareTo(b.property);
    }
});
List<String> check = Arrays.asList("A", "B", "C");
String tmp = elements.get(0).property;
if check.contains(tmp) 
    return tmp
else
    throw new IllegalArgumentException("Invalid input!");

You can also sort using lambda:

Collections.sort(elements, (e1, e1) -> e1.property.compareTo(e2.property));

Upvotes: 3

Related Questions