web-dev-qa-db-fra.com

Rendement en retour Java

J'ai créé une liste chaînée en Java en utilisant des génériques, et maintenant je veux pouvoir parcourir tous les éléments de la liste. En C # j'utiliserais yield return à l'intérieur de la liste chaînée en parcourant la liste des éléments contenus dans la liste.

Comment pourrais-je créer une Java de ce qui précède où je peux parcourir tous les éléments contenus dans la liste liée?

Je cherche à pouvoir écrire du code ala

LinkedList<something> authors = new LinkedList<something>();
for (Iterator<something> i = authors.Values ; i.HasNext())
      doSomethingWith(i.Value);

Et pensait que la "propriété"/méthode Value consisterait en un code ressemblant à

LinkedListObject<something> current = first;
While (current != null){
 yield return current.getValue();
 current = current.getNext()
}

Edit: notez que je ne suis pas intéressé à utiliser des API tierces. Built-in Java fonctionnalité uniquement.

51
Kasper Holdum

Vous pouvez renvoyer une implémentation anonyme d'Iterable. Les effets sont assez similaires, juste que c'est beaucoup plus verbeux.

public Iterable<String> getStuff() {
    return new Iterable<String>() {

        @Override
        public Iterator<String> iterator() {
            return new Iterator<String>() {

                @Override
                public boolean hasNext() {
                    // TODO code to check next
                }

                @Override
                public String next() {
                    // TODO code to go to next
                }

                @Override
                public void remove() {
                    // TODO code to remove item or throw exception
                }

            };
        }
    };
}
36
voidvector

"yield return" est une astuce de compilation très sophistiquée. Il vous permet essentiellement d'implémenter déclarativement IEnumerable sans aucun détail gênant pour "comprendre" comment construire votre itérateur. Le malheur est qu'il ne se traduit pas bien dans d'autres langues car très peu de compilateurs ont une telle capacité. À certains égards, le "rendement du rendement" est aussi accablant que révolutionnaire.

Fondamentalement en C #, le compilateur générera deux implémentations de IEnumerable et IEnumerator (de T). Il le fait en réalisant essentiellement les variables locales de votre "méthode" en tant que champs d'instance dans les classes d'implémentation générées ainsi qu'en examinant les trames contenant un artefact "return return". Une fois que vous le savez, il devrait être possible pour un développeur bien équilibré d'accomplir la même chose explicitement ... mais pas de manière aussi concise. Pour démontrer, je vais CONCAT!

public static <T> Iterable<T> concat(Iterable<T> x, Iterable<T> y)
{
    for(T e: x)
    {
        yield return e;
    }

    for(T e: y)
    {
        yield return e;
    }
}

// becomes ....

public static <E> Iterator<E> concat_(Iterable<E> x, Iterator<E> y)
{
    T e1, e2;
    Iterator<E> i1, i2;

    Iterator<E> s;
    Iterator<E> s4 = new Iterator<E>()
    {
        public bool hasNext()
        {
            return false;
        }

        public E next()
        {
            throw ... ;
        }

        public void remove()
        {
            throw ... ;
        }
    }

    Iterator<E> s3 = new Iterator<E>()
    {
        Iterator<E> act()
        {
            if(i2.hasNext())
            {
                return i2;
            }

            i2 = y.iterator();
            return (s = s4);
        }

        public bool hasNext()
        {
            return act().hasNext();
        }

        public E next()
        {
            return act().next();
        }

        public void remove()
        {
            return i2.remove();
        }
    }

    Iterator<E> s2 = new Iterator<E>()
    {
        Iterator<E> act()
        {
            if(i1.hasNext())
            {
                return i1;
            }

            i2 = y.iterator();
            return (s = s3);
        }

        public bool hasNext()
        {
            return act().hasNext();
        }

        public E next()
        {
            return act().next();
        }

        public void remove()
        {
            return i1.remove();
        }
    };

    Iterator<E> s1 = new Iterator<E>()
    {
        Iterator<E> act()
        {
            i1 = x.iterator();
            return s = s2;
        }

        public bool hasNext()
        {
            return act().hasNext();
        }

        public E next()
        {
            return act().next();
        }

        public void remove()
        {
            return act().remove();
        }
    };

    s = s1;
    return new Iterator<T>()
    {
        public bool hasNext()
        {
            return s.hasNext();
        }

        public E next()
        {
            return s.next();
        }

        public void remove()
        {
            return s.remove();
        }
    };
}

public static <T> Iterable<T> concat(Iterable<T> x, Iterable<T> y)
{
    return new Iterable<T>()
    {
        public Iterator<T> iterator()
        {
            return concat_(x, y)
        }
    };
}

// tada!

Si vous pardonnez tous à mon pseudo Java 3 heures du matin ...

21
Garth Pickell

essaye ça

consultez également cet article pour un exemple d'implémentation:

14
Asad Butt

Je ne comprends pas pourquoi les gens parlent de threads ... y a-t-il quelque chose que je ne sais pas sur le rendement?

À ma connaissance, return return enregistre simplement la pile de méthodes et la restaure ultérieurement. Pour implémenter le retour de rendement, il vous suffit d'enregistrer l'état manuellement. Voir les classes d'itérateur Java pour plus de détails, mais pour une liste chaînée, vous pouvez simplement vous en sortir en enregistrant l'élément en cours. Pour un tableau, vous avez juste besoin de l'index.

5
CurtainDog

Juste pour aider les lecteurs à comprendre les petits détails.

Si vous créez une nouvelle liste contenant tous les éléments résultants et renvoyez la liste, alors c'est une bonne implémentation, assez simple à coder. Vous pouvez avoir une structure de données aussi intéressante que vous le souhaitez, et lors de la numérisation pour les bonnes entrées, renvoyez simplement une liste de toutes les correspondances, et votre client itérera sur la liste.

Si vous souhaitez enregistrer un état, cela peut être plus compliqué. Vous devrez vous rendre là où vous vous êtes rendu chaque fois que votre fonction est appelée. Sans parler des problèmes de rentrée, etc.

La solution avec les threads ne crée pas de nouvelle liste. Et c'est aussi simple que la première solution. Le seul problème est que vous impliquez une synchronisation de threads qui est un peu plus difficile à coder et qui a ses inconvénients de performances.

Donc, oui, le rendement est excellent et manque à Java. Pourtant, il existe des solutions de contournement.

1
Oren ZBM

une opération de rendement peut être considérée comme

  1. mettre un point de contrôle là-bas
  2. écrire une valeur quelque part
  3. lorsque le CV est acquis, passez à l'instruction à côté.

par conséquent, je l'implémente en tant que classe de machine à états, coroutine. dans ce mécanisme, chaque instruction a son pointeur d'instruction, son index et l'instruction peut avoir une étiquette avec elle, nous pouvons donc utiliser jmp (étiquette) pour passer à l'étiquette.

  1. ajouter un mécanisme pour obtenir une syntaxe goto: addInstruction (..) et jmp ()
  2. et stocker l'état/la variable quelque part: setVariable (nom, valeur), yield (valeur)
  3. un moyen de suspendre/reprendre temporairement: exec ()

par exemple:

public class FibbonaciCoroutine implements Iterator<BigInteger> {
    BigInteger[] bucket = { new BigInteger("1"), new BigInteger("1"), new BigInteger("0") };
    int idx = 2;
    Coroutine coroutine = new Coroutine((pthis) -> {

        pthis.addInstruction("_label1", (me) -> {
            int p1 = idx - 2;
            int p2 = idx - 1;
            if (p1 < 0)
                p1 += 3;
            if (p2 < 0)
                p2 += 3;
            bucket[idx] = bucket[p1].add(bucket[p2]);
            idx = (idx + 1) % bucket.length;

            me.yield(bucket[idx]);

        });
        // goto
        pthis.addInstruction((me) -> {
            me.jmp("_label1");
        });
        pthis.start();
    });

    @Override
    public boolean hasNext() {
        return !coroutine.isStopped();
    }

    @Override
    public BigInteger next() {
        while (coroutine.exec())
            ;
        return coroutine.getYieldValue();
    }

    public static void main(String[] argv) {
        FibbonaciCoroutine cor = new FibbonaciCoroutine();
        for (int i = 0; i < 100 && cor.hasNext(); ++i) {
            System.out.printf("%d ", cor.next());
        }
    }

}

voir FibonacciCoroutine.Java

0
sunneo