Usunięcie zduplikowanych obiektów z Arraylisty

0

Cześć wszystkim to moje pierwsze pytani tutaj, więc mam nadzieję że będzie prawidłowo zadane :)
Tworzę, jako pierwszy "projekt", prostą grę RPG w consoli, jestem na etapie tworzenia części odpowiedzialnej za ekwipunek, loot z potwora, i podnoszeni itemków, obecnie w moim programie gracz class player i potwór class Monster mają zmienną arrayList<Item> Backpack, kiedy gra się zaczyna to do Backpack wpadają jakieś obiekty klasy Item, i na tym etapie pojawia się problem, po zabiciu potwora chce zebrać z niego loota, wiec do mojego Backpack chce dodać jakiś item, jak w elegancki sposób sprawdzić czy dany item jest w Backpack i jeśli jest to nie dodawać go po raz drugi a tylko zwiększyć jego wartość/ilość, obecnie mam działające rozwiązanie, przy podnoszeniu loota w zagnieżdżonej pętli sprawdzam czy dany item jest już w moim backpacku, jeśli jest to zamiast dodawać go drugi raz, zwiększam wartość zmiennej amount która należy do danego obiektu klasy Item który jest w moim plecaku, to działa, ale wydaje mi się że jest to mało praktyczne, bo jesli mój ekwipunek zwiększy sie do kilkudziesięciu itemów wtedy funkcja którą zrobiłem będzie musiała iterować za każdym razem po tych obiektach, a jeśli z potwora wypadnie na przykład 10 itemów, wtedy będzie musiała robić to 10x, jaki sposób rozwiązania tego problemu możecie mi zaproponować? Czytałem już na ten temat, ludzie proponują użycie HashMapy, ale ciężko mi pojąć jak mogłoby to wyglądać? musiałbym zrobić HashMape<Item.ItemName, Integer Amount> czy coś takigo? poniżej wstawiam kawałki kodu

public static ArrayList<Item> Backpack = new ArrayList<>()
}```

classa Skeleton
```public class Skeleton extends Monsters {

  public static ArrayList<Item> Backpack;

  public Skeleton() {
       Backpack = new ArrayList<>();
       Backpack.add(new Weapon("Rusty sword", "Just a rusty sword", 3, 2 ));
       Backpack.add(new Armor("Leather Armor", "Old leather armor", 6, 3));
    }```

classa Item
```public class Item {

    public String ItemName;
    public String Description;
    public int ItemSize;
    public int ItemWeight;
    public int Amount;

    public Item(String ItemName, String Description, int ItemSize, int ItemWeight)
    {
        this.ItemName = ItemName;
        this.Description = Description;
        this.ItemSize = ItemSize;
        this.ItemWeight = ItemWeight;
    }

    public Item() {
    }
}```
2
       Backpack = new HashMap<>();
       Backpack.put(new Weapon("Rusty sword", "Just a rusty sword", 3, 2 ), 1);
       Backpack.put(new Armor("Leather Armor", "Old leather armor", 6, 3), 2);

Pamiętaj, że zaleca się żeby klucz w mapie był niemutowalny oraz miał dobrze zdefiniowany equals i hashcode, bo inaczej będą sie działy straszne, dziwne rzeczy

3

Odpowiadając na pytanie, aby usunąć duplikaty z listy, zamień ja na Set (wpisz w google java convert list to set).

Ale moim zdaniem pole Amount na itemie jest bez sensu. Item sam w sobie ma wiedzieć ile go jest? Dziwnie to brzmi.
Lepiej jak w stworzysz klasę odpowiedzialną za Backpack, a w niej dodasz właśnie wspomnianą

Map<Item,Integer> items = new HashMap<>()

Aby sprawdzić czy dany item jest już w kolekcji używasz metody equals, która musisz zdefiniować dla danego itemu (tutaj musisz zdecydować co identyfikuje, że dany item jest równy drugiemu).
Następnie uzyj metody computeIfPresent na mapie (googluj) :)

1

@AlexTen: ale to zliczanie na tym etapie nie jest dobrym pomysłem. Poszczególne przedmioty nadal powinny mieć „swoją osobowość”, inaczej mówiąc, powinny być rozróżnialne w grze. Identyczność jest cechą, która występuje tylko w pewnym kontekście. Dlatego lepiej jest trochę inaczej podejść do wyświetlania plecaka.

public class RpgBackpack {

	public static void main(String[] args) {
		Backback backback = new Backback();
		Item s1 = new Item("Zwyczajna szabla zwyczajna");
		Item s2 = new Item("Zwyczajna szabla zwyczajna");
		Item s3 = new Item("Zwyczajna szabla nie zwyczajna");

		backback.add(s1).add(s2).add(s3);

		System.out.println(backback.show());

	}
}


class Item {

	private final UUID id;
	private final String name;

	Item(String name) {
		this.id = UUID.randomUUID();
		this.name = name;
	}

	public String getName() {
		return name;
	}

	@Override
	public boolean equals(Object o) {
		if (this == o) return true;
		if (o == null || getClass() != o.getClass()) return false;
		Item item = (Item) o;
		return id.equals(item.id);
	}

	@Override
	public int hashCode() {
		return Objects.hash(id);
	}
}

class Backback {

	private final Set<Item> items;

	public Backback() {
		this.items = new HashSet<>();
	}

	public Backback add(Item item) {
		items.add(item);
		return this;
	}

	public Map<String, List<Item>> show() {
		return items.stream()
				.collect(Collectors.groupingBy(Item::getName).);
	}
	
	public Set<Item> showRaw(){
		return Set.copyOf(items);
	}
}

Każdy przedmiot ma dwa identyfikatory. id jest unikalny w całym świecie gry. Nazwa pozwala na przypisanie przedmiotu do kategorii. Dodajesz przedmioty do plecaka, jak leci, a w momencie, gdy chcesz pokazać jego zawartość, to tworzysz mapę, w której kluczem jest nazwa kategorii przedmiotu, a wartościami listy przedmiotów. W ten sposób:

  1. Zawsze masz wiedzę, do kogo należy dany przedmiot (trzeba tu jeszcze trochę popracować nad modelem).
  2. Nikt nie sklonuje ci przedmiotu, bo jak klon trafi do plecaka, to zniknie (Set zapewnia unikalność).
  3. Możesz dowolnie modelować wyświetlanie, bo docelowo można zamienić metody show i showRaw na show(ViewVisitor), która jako argument przyjmie coś, co będzie, potrafiło sobie poradzić z zawartością plecaka.
1

Ja bym zrobił po taniości - wrzucał wszystko jak leci, a później grupowanie + count na wyświetlaniu.

2

Możesz zrobić rozwiązanie klasy enterprise (zwane CQRS+ES): jedno źródło prawdy (wrzucasz wszystko jak leci) i dedykowane struktury danych pod konkretne widoki. Możesz użyć wzorca Observer, żeby odświeżać widoki, kiedy będą dodawane/odejmowane przedmioty. Modyfikujesz bezpośrednio tylko źródło prawdy, reszta jest tylko widokiem na te dane. Coś podobnego zaproponował @Koziołek, tylko w jego rozwiązaniu większy narzut jest przy odczycie, a w moim przy zapisie.

Pewnie (na 99%) w Twoim przypadku to jest overengineering, ale weź pod uwagę, że obojętnie jaka strukturę danych sobie wybierzesz, nie pokryje ona wszystkich access patterns (to jest w sumie najważniejszy hint). Pytanie pod jakie się optymalizujesz, jak często ten plecak będzie wyświetlany itd.

0

Panowie, dziękuję za pomoc, czerpałem z waszych wskazówek, ostatecznie mam Arrayliste Backpack, która zawiera Sloty, i w Slotach mam określone id itemka i ile go jest, wydaje mi się o dość racjonalne, i dodatkowo było dość proste, dziękuję za pomoc, była solidna!

1 użytkowników online, w tym zalogowanych: 0, gości: 1