具有出现次数和排序的字符串列表

时间:2015-05-15 12:39:17

标签: java string sorting collections

我正在开发一个 Java 应用程序,它读取了很多字符串数据,如下所示:

1 cat (first read)
2 dog
3 fish
4 dog
5 fish
6 dog
7 dog
8 cat
9 horse
...(last read)

我需要一种方法来保持所有情侣[字符串,事件] 从上次读取到第一次读取的顺序。

字符串出现
马1(第一次印刷)
猫2 狗4 鱼2(最后一次印刷)

实际上我使用两个列表:
1)List<string> input;我添加所有数据
在我的例子中:

input.add("cat");
input.add("dog");
input.add("fish");
...

2)List<string> possibilities;我以这种方式插入字符串一次:

if(possibilities.contains("cat")){
    possibilities.remove("cat");
}
possibilities.add("cat");

通过这种方式,我有一个排序列表,其中包含所有可能性。 我这样用它:

int occurrence;
for(String possible:possibilities){
    occurrence = Collections.frequency(input, possible);
    System.out.println(possible + " " + occurrence);
}

这个技巧很好但是太慢了(我有数百万的输入)...任何帮助?
(英语不是我的第一语言,所以请原谅任何错误。)

5 个答案:

答案 0 :(得分:1)

使用Map<String, Integer>@radoslaw指出,使插入排序使用LinkedHashMap,而不是here所述的TreeMap

  

LinkedHashMap保持按键的顺序,而TreeMap则通过比较器或元素的自然可比较顺序进行排序。

想象一下,你有一些数组中的所有字符串,称之为listOfAllStrings,迭代这个数组并在地图中使用字符串key,如果它不存在,放在地图中,如果存在,则将1加到实际结果中......

Map<String, Integer> results = new LinkedHashMap<String, Integer>();
for (String s : listOfAllStrings) {
    if (results.get(s) != null) {
        results.put(s, results.get(s) + 1);
    } else {
        results.put(s, 1);
    }
}

答案 1 :(得分:0)

使用TreeMap,它将按照MyStringComparator类的compare指定的键来处理密钥,处理MyString类,它包装了String添加插入索引,如下所示:

// this better be immutable
class MyString {
   private MyString() {}
   public static MyString valueOf(String s, Long l) { ... }
   private String string;
   private Long index;
   public hashcode(){ return string.hashcode(); }
   public boolean equals() { // return rely on string.equals() }
}

class MyStringComparator implements Comparator<MyString> {
   public int compare(MyString s1, MyString s2) {
       return -s1.getIndex().compareTo(s2.gtIndex()); 
  }
}

在构建地图时传递比较器:

Map<MyString,Integer> map = new TreeMap<>(new MyStringComparator());

然后,在解析输入时,执行

Long counter = 0;
while (...) {
   MyString item = MyString.valueOf(readString, counter++);
   if (map.contains(item)) {
      map.put(map.get(item)+1);
   } else {
      map.put(item,1);
   }
}

由于不可变类会有很多实例化,并且比较器与equals不一致,但它应该有效。

免责声明:这是未经测试的代码,只是为了展示我所做的事情,当我开始使用编译器时,我会回来并重新检查它。

答案 2 :(得分:0)

以下是您问题的完整解决方案,

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class DataDto implements Comparable<DataDto>{

    public int count = 0;
    public String string;
    public long lastSeenTime;

    public DataDto(String string) {
        this.string = string;
        this.lastSeenTime = System.currentTimeMillis();
    }

    public boolean equals(Object object) {
        if(object != null && object instanceof DataDto) {
            DataDto temp = (DataDto) object;
            if(temp.string != null && temp.string.equals(this.string)) {
                return true;
            }
        }
        return false;
    }

    public int hashcode() {
        return string.hashCode();
    }

    public int compareTo(DataDto o) {
        if(o != null) {
            return o.lastSeenTime < this.lastSeenTime ? -1 : 1; 
        }
        return 0;
    }

    public String toString() {
        return this.string + " : " + this.count;
    }

    public static final void main(String[] args) {
        String[] listOfAllStrings = {"horse", "cat", "dog", "fish", "cat", "fish", "dog", "cat", "horse", "fish"}; 
        Map<String, DataDto> results = new HashMap<String, DataDto>();
        for (String s : listOfAllStrings) {
            DataDto dataDto = results.get(s);
            if(dataDto != null) {
                dataDto.count = dataDto.count + 1;
                dataDto.lastSeenTime = System.nanoTime();
            } else {
                dataDto = new DataDto(s);
                results.put(s, dataDto);
            }
        }
        List<DataDto> finalResults = new ArrayList<DataDto>(results.values());
        System.out.println(finalResults);
        Collections.sort(finalResults);
        System.out.println(finalResults);
    }
}

[horse : 1, cat : 2, fish : 2, dog : 1]
[fish : 2, horse : 1, cat : 2, dog : 1]

我认为此解决方案适合您的要求。

答案 3 :(得分:0)

如果您在将数据全部读入内存时知道数据不会超出内存容量,那么解决方案很简单 - 使用LinkedList或a和LinkedHashMap

例如,如果您使用链接列表:

LinkedList<String> input = new LinkedList();

然后按照原来的方式继续使用input.add()。但是当输入列表已满时,您基本上使用Jordi Castilla的解决方案 - 但是将条目放在链接列表中的逆序中。要做到这一点,你可以:

    Iterator<String> iter = list.descendingIterator();
    LinkedHashMap<String,Integer> map = new LinkedHashMap<>();

    while (iter.hasNext()) {
        String s = iter.next();
        if ( map.containsKey(s)) {
            map.put( s, map.get(s) + 1);
        } else {
            map.put(s, 1);
        }
    }

现在,他的解决方案与我的解决方案之间唯一真正的区别在于我使用list.descendingIterator()这是LinkedList中的一种方法,它以向后的顺序为您提供条目,从“马”到“马”猫”。

LinkedHashMap将保持正确的顺序 - 首先输入的内容将首先打印,因为我们以相反的顺序输入内容,所以最后读取的内容将首先打印。因此,如果您打印map,结果将是:

{horse=1, cat=2, dog=4, fish=2}

如果你有一个很长的文件,并且你无法将整个字符串列表加载到内存中,那么最好只保留频率图。在这种情况下,为了保持输入顺序,我们将使用如下对象:

private static class Entry implements Comparable<Entry> {

    private static long nextOrder = Long.MIN_VALUE;
    private String str;
    private int frequency = 1;
    private long order = nextOrder++;
    public Entry(String str) {
        this.str = str;
    }

    public String getString() {
        return str;
    }

    public int getFrequency() {
        return frequency; 
    }

    public void updateEntry() {
        frequency++;
        order = nextOrder++;
    }

    @Override
    public int compareTo(Entry e) {
        if ( order > e.order )
            return -1;
        if ( order < e.order )
            return 1;
        return 0;
    }

    @Override
    public String toString() {
        return String.format( "%s: %d", str, frequency );
    }
}

这里的技巧是每次更新条目(在频率上加1)时,它也会更新订单。但是compareTo()方法从顺序(稍后更新/插入)到顺序(先前更新/插入)命令Entry个对象。

现在,您可以使用简单的HashMap<String,Entry>在阅读时存储信息(我假设您正在阅读某种扫描仪):

    Map<String,Entry> m = new HashMap<>();

    while ( scanner.hasNextLine() ) {
        String str = scanner.nextLine();
        Entry entry = m.get(str);
        if ( entry == null ) {
            entry = new Entry(str);
            m.put(str, entry);
        } else {
            entry.updateEntry();
        }
    }

    Scanner.close();

现在您可以对条目的值进行排序:

    List<Entry> orderedList = new ArrayList<Entry>(m.values());
    m = null;
    Collections.sort(orderedList);

正在运行System.out.println(orderedList)会给您:

[horse: 1, cat: 2, dog: 4, fish: 2]

原则上,你可以使用一个TreeMap,其中的键包含“order”内容,而不是像这样的普通HashMap,然后进行排序,但我不想在地图中使用任何可变键,也不断改变密钥。这里我们只是在填充地图时更改,每个键只插入一次地图。

答案 4 :(得分:0)

你能做什么:

  1. 使用反转列表的顺序 Collections.reverse(input)。这以线性时间运行 - O(n);
  2. 从输入列表中创建Set。 A Set保证独特性。 要保留广告订单,您需要LinkedHashSet;
  3. 就像你上面那样迭代这个集合。
  4. 代码:

    /* I don't know what logic you use to create the input list,
     * so I'm using your input example. */
    List<String> input = Arrays.asList("cat", "dog", "fish", "dog",
                "fish", "dog", "dog", "cat", "horse");
    /* by the way, this changes the input list!
     * Copy it in case you need to preserve the original input. */
    Collections.reverse(input);
    Set<String> possibilities = new LinkedHashSet<String>(strings);
    
    for (String s : possibilities) {
        System.out.println(s + " " + Collections.frequency(strings, s));
    }
    

    输出:

    horse 1
    cat 2
    dog 4
    fish 2