如何使用Java的TreeSet实现一个按元素字段排序的表格?

huangapple go评论73阅读模式
英文:

How to implement a sorted table (order by a field of the element) by using java TreeSet?

问题

I used TreeSet for this and it works in a per snapshot style. In other words, sort once displays once.

现在,我想要实现一个实时排序的表格。

每当任何元素的值发生变化时,排序表格都将相应更新。

为了使排序在每次更新时都起作用,我尝试删除元素然后再次将其添加到TreeSet中。

quotes.remove(quote);
quotes.add(quote);

它不起作用,因为我必须在compareTo()中实现排序逻辑,但它会破坏识别对象的remove()方法的契约。TreeSet不会调用equals()和hashcode(),如Java文档中所描述。

有什么建议吗?请提供建议。

code:

import java.util.Set;
import java.util.TreeSet;

public class TreeSetTest {

	public static void main(String args[]) {		
		TreeSetTest test = new TreeSetTest();
		test.onQuoteUpdate("appl", 1000d);
		test.onQuoteUpdate("msft", 2000d);
		test.onQuoteUpdate("face", 3000d);
		test.printTopStocks();
		test.onQuoteUpdate("msft", 5000d);
		test.printTopStocks();
	}
	
	private Set<Quote> quotes = new TreeSet<Quote>();
	
	public void onQuoteUpdate(String symbol, double turnover) {
		final Quote quote = new Quote(symbol, turnover);
        quotes.remove(quote);
		quotes.add(quote);
	}
	
	public void printTopStocks() {
		System.out.println("--Top Stocks By Turnover--");
		for (final Quote quote : quotes) {
			System.out.println(quote);
		}
	}
	public static class Quote implements Comparable<Quote> {
		
		private String symbol;
		
		private double turnover;
		
		public Quote(String symbol, double turnover) {
			this.symbol = symbol;
			this.turnover = turnover;
		}
		
		@Override
		public int compareTo(Quote o) {
			return Double.compare(o.turnover, turnover);
//			return symbol.compareTo(o.symbol);
		}
		
	}
}

Update 1:

如建议尝试了以下方法:

public static void main(String args[]) {		
	TreeMapTest test = new TreeMapTest();
	test.onQuoteUpdate("appl", 1000d);
	test.onQuoteUpdate("msft", 2000d);
	test.onQuoteUpdate("face", 3000d);
	test.printTopStocks();
	test.onQuoteUpdate("face", 50d);
	test.printTopStocks();
}

public int compareTo(Quote o) {
	if(o.symbol.equals(symbol)) return 0;
	return Double.compare(o.turnover, turnover);
}

remove() 返回 false,最终在Set中有四个元素(期望是3)。

--Top Stocks By Turnover--
Quote [symbol=face, turnover=3000.0]
Quote [symbol=msft, turnover=2000.0]
Quote [symbol=appl, turnover=1000.0]
remove symbol face : false
add symbol face : true
--Top Stocks By Turnover--
Quote [symbol=face, turnover=3000.0]
Quote [symbol=msft, turnover=2000.0]
Quote [symbol=appl, turnover=1000.0]
Quote [symbol=face, turnover=50.0]

Update 2:

我尝试了PriorityQueue,以下是代码:
https://code.sololearn.com/cb38Eo036c8y/#java

它不起作用,因为PriorityQueue不按顺序存储元素。排序仅在从队列中轮询元素时起作用。

Update 3:

尝试了user54321的建议,使用自定义集合(请参见下面的答案)。但是,如果有两个或更多元素的“turnover”值相同,它看起来并不好。

我的要求很普通。似乎JDK中的任何集合都不适合我的情况。

Update 4:

user54321的解决方案适用于我的临时需求。
https://code.sololearn.com/c14Ybab7AOFm/#java

英文:

I used TreeSet for this and it works in a per snapshot style. In other words, sort once displays once.

Now, I want to implement a realtime sorted table.

Whenever there is a value change in any elements, the sorted table will be updated accordingly.

To make the sorting work on a per update style, I tried to remove the element and add it to the TreeSet again.

quotes.remove(quote);
quotes.add(quote);

It doesn't work because I have to implement the sorting logic in compareTo() but it breaks the contract for identifying the object which makes the remove() work. TreeSet never call equals() and hashcode() as described in the Java Doc.

Any idea? Please advise.

code:

import java.util.TreeSet;
public class TreeSetTest {
public static void main(String args[]) {		
TreeSetTest test = new TreeSetTest();
test.onQuoteUpdate(&quot;appl&quot;, 1000d);
test.onQuoteUpdate(&quot;msft&quot;, 2000d);
test.onQuoteUpdate(&quot;face&quot;, 3000d);
test.printTopStocks();
test.onQuoteUpdate(&quot;msft&quot;, 5000d);
test.printTopStocks();
}
private Set&lt;Quote&gt; quotes = new TreeSet&lt;Quote&gt;();
public void onQuoteUpdate(String symbol, double turnover) {
final Quote quote = new Quote(symbol, turnover);
quotes.remove(quote);
quotes.add(quote);
}
public void printTopStocks() {
System.out.println(&quot;--Top Stocks By Turnover--&quot;);
for (final Quote quote : quotes) {
System.out.println(quote);
}
}
public static class Quote implements Comparable&lt;Quote&gt; {
private String symbol;
private double turnover;
public Quote(String symbol, double turnover) {
this.symbol = symbol;
this.turnover = turnover;
}
@Override
public int compareTo(Quote o) {
return Double.compare(o.turnover, turnover);
//			return symbol.compareTo(o.symbol);
}
}
}

Update 1:

As proposed I tried this:

public static void main(String args[]) {		
TreeMapTest test = new TreeMapTest();
test.onQuoteUpdate(&quot;appl&quot;, 1000d);
test.onQuoteUpdate(&quot;msft&quot;, 2000d);
test.onQuoteUpdate(&quot;face&quot;, 3000d);
test.printTopStocks();
test.onQuoteUpdate(&quot;face&quot;, 50d);
test.printTopStocks();
}
public int compareTo(Quote o) {
if(o.symbol.equals(symbol)) return 0;
return Double.compare(o.turnover, turnover);
}

The remove() return false which eventually there are four elements (expected 3) in the Set.

--Top Stocks By Turnover--
Quote [symbol=face, turnover=3000.0]
Quote [symbol=msft, turnover=2000.0]
Quote [symbol=appl, turnover=1000.0]
remove symbol face : false
add    symbol face : true
--Top Stocks By Turnover--
Quote [symbol=face, turnover=3000.0]
Quote [symbol=msft, turnover=2000.0]
Quote [symbol=appl, turnover=1000.0]
Quote [symbol=face, turnover=50.0]

Update 2:

I tried PriorityQueue and here is the code:
https://code.sololearn.com/cb38Eo036c8y/#java

It doesn't work because PriorityQueue doesn't store elements in order. The ordering only works when you poll element from the Queue.

Update 3:

Tried user54321's suggestion that by using a custom collection(see below answer). However, it doesn't look good if there are two more elements having the same value of 'turnover'.

My requirement is a very ordinary one. It seems that none of a collection from JDK fits my case.

Update 4:

The solution from user54321 fits for my interim need.
https://code.sololearn.com/c14Ybab7AOFm/#java

答案1

得分: 1

删除了我之前添加的答案。看起来在这种情况下使用了错误的数据结构。

原因如下。

当添加或删除项时,TreeSet会使用compareTo()在可用元素中进行二进制搜索。

在你的情况下,

添加前3个元素后,集合如下所示。
[{appl, 1000d}, {msft, 2000d}, {face, 3000d}]

现在当你尝试移除元素{face, 50d}时,

它从{msft, 2000d}开始搜索,
compareTo()的结果中确定{face, 50d}应该位于{msft, 2000d}之前。

并继续向元素的开头搜索(下一个是{appl, 1000d})。

由于搜索未找到{face, 3000d},该元素保持未被移除。

接下来当你添加元素{face,50}时,类似的搜索发生,因为搜索没有找到{face, 3000}
它将{face, 50}添加到开头。

现在集合看起来是这样的。
[{face, 50}, {appl, 1000d}, {msft, 2000d}, {face, 3000d}]

问题在于compareTo()不能同时考虑符号和交易额进行合理的排序。
TreeSet可用于获取唯一元素的排序集合。

如果你需要按特定排序条件获取不同对象的排序集合,在这种情况下是交易额值,你可以使用PriorityQueue

更新:在自定义数据结构中使用List和Set

问题在于我们必须保持两个条件。

  1. 符号必须唯一
  2. 集合必须按交易额值排序

QuotecompareTo()中一次只能检查一个条件,不能同时检查两个条件。
因此在这种情况下,我们可能需要使用自定义数据结构。

首先只使用turnovercompareTo()中;

    @Override
public int compareTo(Quote o) {
return Double.compare(o.turnover, turnover);
}

然后实现自定义数据结构。
注意我们使用HashSet仅跟踪符号。
使用List以便保留重复的交易额值。

static class QuoteCollection {
Set<String> symbols = new HashSet<>();
List<Quote> quotes = new LinkedList<>();
public void onQuoteUpdate(Quote q) {
if (symbols.contains(q.getSymbol())) {
// 这需要实现quotes.equals()
quotes.remove(q);
} else {
symbols.add(q.getSymbol());
}
insertToCollection(q);
}
// 在正确的位置插入以保持排序
private void insertToCollection(Quote q) {
int index = Collections.binarySearch(quotes, q);
if (index < 0)
index = ~index; // 按位取反找到要插入的位置,如果它在列表中不可用
quotes.add(index, q);
}
public List<Quote> getQuotes() {
return quotes;
}
}

然后在main()中使用它。注意printTopStocks()稍作修改。

public static void main(String args[]) {
Main test = new Main();
QuoteCollection quoteCollection = new QuoteCollection();
quoteCollection.onQuoteUpdate(new Quote("appl", 1000d));
quoteCollection.onQuoteUpdate(new Quote("msft", 2000d));
quoteCollection.onQuoteUpdate(new Quote("face", 3000d));
test.printTopStocks(quoteCollection.getQuotes());
quoteCollection.onQuoteUpdate(new Quote("face", 50d));
test.printTopStocks(quoteCollection.getQuotes());
}
public void printTopStocks(List<Quote> quotes) {
System.out.println("--Top Stocks By Turnover--");
for (final Quote quote : quotes) {
System.out.println(quote);
}
}

这种方法确实涉及数据复制。但是可以在线性时间复杂度内维护排序集合(因为它使用了List.remove()

英文:

Deleted my previously added answer. Looks like a wrong data structure is being used for the scenario.

Here is why.

When an item is being added or removed, TreeSet does a binary search through the available elements using compareTo().

In your case,

After adding first 3 elements, set looks like this.<br/>
[{appl, 1000d}, {msft, 2000d}, {face, 3000d}]

Now when you try to remove the element {face, 50d},

It starts searching at {msft, 2000d},
From compareTo() result it determines {face, 50d} should come before {msft, 2000d}.

And continues to search towards start of the elements ( checking with {appl, 1000d} next).

Since the search doesn't find {face, 3000d}, that element remains without being removed.

Next when you add the element {face,50}, similar search happens and since the search does not find {face, 3000},
It adds {face, 50} to the beginning.

Now the set looks like this.<br/>
[{face, 50}, {appl, 1000d}, {msft, 2000d}, {face, 3000d}]

Now the problem here is that compareTo() isn't capable of considering both symbol and turnover for a sensible sorting.
TreeSet can be used for getting a sorted collection of unique elements.

If you need to get a sorted collection of different objects with a particular sorting criteria, in this case turnover value, you can use a PriorityQueue

Update: Using a List and a Set in custom data structure

The problem here is that we have to maintain two conditions.

  1. Symbol has to be unique
  2. Collection should be sorted by turnover value

compareTo() in Quote can check one at a time and not both.
So in this case we may have to go for a custom data structure.

First use only turnover in compareTo();

    @Override
public int compareTo(Quote o) {
return Double.compare(o.turnover, turnover);
}

Then implement the custom data structure.
Note that we are using a HashSet to keep track of the symbol alone.
Using a list so that duplicate turnover values can be kept.

static class QuoteCollection {
Set&lt;String&gt; symbols = new HashSet&lt;&gt;();
List&lt;Quote&gt; quotes = new LinkedList&lt;&gt;();
public void onQuoteUpdate(Quote q) {
if (symbols.contains(q.getSymbol())) {
// this requires quotes.equals() to be implemented
quotes.remove(q);
} else {
symbols.add(q.getSymbol());
}
insertToCollection(q);
}
// inserting at correct position to remain sorted
private void insertToCollection(Quote q) {
int index = Collections.binarySearch(quotes, q);
if (index &lt; 0)
index = ~index; // bitwise compliment to find insert position if it is not available in the list
quotes.add(index, q);
}
public List&lt;Quote&gt; getQuotes() {
return quotes;
}
}

Then use it in the main(). Note that printTopStocks() has been changed a little.

public static void main(String args[]) {
Main test = new Main();
QuoteCollection quoteCollection = new QuoteCollection();
quoteCollection.onQuoteUpdate(new Quote(&quot;appl&quot;, 1000d));
quoteCollection.onQuoteUpdate(new Quote(&quot;msft&quot;, 2000d));
quoteCollection.onQuoteUpdate(new Quote(&quot;face&quot;, 3000d));
test.printTopStocks(quoteCollection.getQuotes());
quoteCollection.onQuoteUpdate(new Quote(&quot;face&quot;, 50d));
test.printTopStocks(quoteCollection.getQuotes());
}
public void printTopStocks(List&lt;Quote&gt; quotes) {
System.out.println(&quot;--Top Stocks By Turnover--&quot;);
for (final Quote quote : quotes) {
System.out.println(quote);
}
}

This approach does involve data duplication. However a sorted collection can be maintained at linear time complexity(since it uses 'List.remove()')

答案2

得分: 0

以下是翻译好的部分:

一些要点:
1. 即使在第一次添加元素时,也试图移除元素。
2. 在更新时,您试图移除 TreeSet 中不存在的新元素。在这里,您正在构建一个不存在的新元素 `Quote("face", "50d")`,当您调用 `quotes.remove(quote);` 时,它不存在。
以下是解决该问题的一种方法,我在这里硬编码了 oldQuote 以保持简洁,但您可以进行更新:
public void onAdd(String symbol, double turnover) {
final Quote quote = new Quote(symbol, turnover);
quotes.remove(quote);
quotes.add(quote);
}
public void onQuoteUpdate(String symbol, double turnover) {
final Quote newQuote = new Quote(symbol, turnover);
final Quote oldQuote = new Quote("face", 3000d);
quotes.remove(oldQuote);
quotes.add(quote);
} 
public static void main(String args[]) {        
TreeSetTest test = new TreeSetTest();
test.onAdd("appl", 1000d);
test.onAdd("msft", 2000d);
test.onAdd("face", 3000d);
test.printTopStocks();
test.onQuoteUpdate("face", 50d);
test.printTopStocks();
} 
英文:

Couple of points :

  1. Trying to remove elements even when you are adding it first time.

  2. While updating you are trying to remove new element which does not exist in TreeSet. final Quote quote = new Quote(symbol, turnover); here you are building new element which is Quote(&quot;face&quot;,&quot;50d&quot;) which does not exist when you are calling quotes.remove(quote);

Below is the one of the way to solve it, I am hard coding oldQuote to keep it short but you can update it:

   public void onAdd(String symbol, double turnover) {
final Quote quote = new Quote(symbol, turnover);
quotes.remove(quote);
quotes.add(quote);
}
public void onQuoteUpdate(String symbol, double turnover) {
final Quote newQuote = new Quote(symbol, turnover);
final Quote oldQuote = new Quote(&quot;face&quot;, 3000d);
quotes.remove(oldQuote);
quotes.add(quote);
} 
public static void main(String args[]) {        
TreeSetTest test = new TreeSetTest();
test.onAdd(&quot;appl&quot;, 1000d);
test.onAdd(&quot;msft&quot;, 2000d);
test.onAdd(&quot;face&quot;, 3000d);
test.printTopStocks();
test.onQuoteUpdate(&quot;face&quot;, 50d);
test.printTopStocks();
} 

huangapple
  • 本文由 发表于 2020年4月3日 21:08:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/61012631.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定