首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Java Streams API的Javascript等效项

Java Streams API的Javascript等效项
EN

Stack Overflow用户
提问于 2017-04-14 16:34:31
回答 3查看 10.3K关注 0票数 10

我喜欢Java8的流式API。有很多有用的中间和终端方法来转换和收集流。我说的是像distinct()这样的中间方法,或者像collect()这样的终端方法。我发现Collector应用编程接口特别有用,它可以将流减少到深度分组映射。

Java流API的javascript等价物是什么?我知道有像mapfilterreduce这样的基本函数,但我没有发现javascript原生提供的更通用的接口来查询或分组集合中的数据。是否有一些可用于生产的库与Java流API相匹配?

EN

回答 3

Stack Overflow用户

发布于 2017-11-16 05:11:11

Java8stream()与lodash chain()相同

Java8Collect()与lodash value()相同

Java8 distinct()与lodash uniq()相同

Java8map()与lodash map()相同

lodash更全面,因为它存在的时间更长。

票数 12
EN

Stack Overflow用户

发布于 2018-07-27 02:09:57

从api级别来看,lodash/RxJS/stream.js可以满足需求,但Java Stream的强大之处在于它可以利用现代cpu多核体系结构来并行化作业。然而,这些纯js库中的任何一个都不能解决这个问题,最终,这些js仍然在单线程运行时运行,并且同时有一个核心使用。

我想JS引擎需要提供支持来实现性能目标。

票数 2
EN

Stack Overflow用户

发布于 2021-12-24 15:34:55

JavaScript没有并行性,因此流始终是顺序的,收集器不需要组合器。

我在这里尝试在JavaScript中模仿Stream API,但去掉了几个功能。尽管如此,我仍然认为它有一些关键的特性。

当您关注Collectors时,我添加了一个带有构造函数和静态方法的Collector类,它们大致对应于Collectors (复数)接口。Stream类有一个构造函数,它接受一个迭代值。它具有Java语言中的大多数静态方法,但在reduceiterator方面有所不同,以使其更符合JavaScript的实践。

还包含了一个Stream.Builder类。

最后,这段代码运行了几个示例。如果您了解Java API,我认为它看起来非常熟悉:

代码语言:javascript
运行
复制
class Collector {
    constructor(supplier, accumulator, finisher=a => a) {
        this.supplier = supplier;
        this.accumulator = accumulator;
        this.finisher = finisher;
    }
    static toArray() {
        return self._toArray ??= new Collector(Array, (acc, item) => (acc.push(item), acc));
    }
    static toSet() {
        return self._toSet ??= new Collector(() => new Set, (acc, item) => acc.add(item));
    }
    static toMap(keyMapper, valueMapper) {
        return new Collector(() => new Map, (acc, item) => acc.add(keyMapper(item), valueMapper(item)));
    }
    static toObject(keyMapper, valueMapper) {
        return new Collector(Object, (acc, item) => acc[keyMapper(item)] = valueMapper(item));
    }
    static averaging(mapper=a => a) {
        return new Collector(
            () => [0, 0], 
            ([sum, count], value) => [sum + mapper.call(value, value), count + 1],
            ([sum, count]) => sum / count
        );
    }
    static collectingAndThen({supplier, accumulator, finisher}, nextFinisher) {
        return new Collector(
            supplier, 
            accumulator,
            value => (prev => nextFinisher.call(prev, prev))(finisher.call(value, value))
        );
    }
    static counting() {
        return this._counting ??= new Collector(Number, (count, value) => count + 1);
    }
    static summing(mapper=Number) {
        return new Collector(Number, (sum, value) => sum + mapper.call(value, value));
    }
    static joining(delimiter=",", prefix="", postfix="") {
        return this.collectingAndThen(Collector.toArray(), arr => prefix + arr.join(delimiter) + postfix);
    }
    // No implementation of partitioningBy, as it is the same as groupingBy with a boolean classifier
    static groupingBy(classifier, {supplier, accumulator, finisher} = Collector.toArray()) {
        return new Collector(
            () => new Map,
            (map, value) => {
                const key = classifier.call(value, value);
                let result = map.get(key) ?? supplier();
                return map.set(key, accumulator(result, value));
            },
            map => {
                map.forEach((value, key) => map.set(key, finisher.call(value, value)));
                return map;
            }
        );
    }
    static mapping(mapper, {supplier, accumulator, finisher}) {
        return new Collector(
            supplier,
            (acc, value) => accumulator(acc, mapper.call(value, value)),
            finisher
        );
    }
    static maxBy(comparator) {
        return new Collector(
            () => undefined,
            (acc, value) => acc === undefined || comparator(acc, value) < 0 ? value : acc
        );
    }
    static minBy(comparator) {
        return new Collector(
            () => undefined,
            (acc, value) => acc === undefined || comparator(acc, value) > 0 ? value : acc
        );
    }
    static reducing(binaryOperator, identity, mapper=a => a) {
        return new Collector(
            () => identity,
            (acc, value) => acc === undefined ? mapper.call(value, value) : binaryOperator(acc, mapper.call(value, value))
        );
    }
}

class Stream {
    static of(...args) {
        return new Stream(args);
    }
    static fromGenerator(generator, ...args) {
        return new Stream(generator.call(null, ...args));
    }
    static empty() {
        return this.of();
    }
    static Builder = class Builder {
        _items = [];
        // Chainable
        add(item) {
            this.accept(item);
            return this;
        }
        // Other
        accept(item) {
            if (!this._items) throw new ValueError("The builder has already transitioned to the built state");
            this._items.push(item);
        }
        build() {
            if (!this._items) throw new ValueError("The builder has already transitioned to the built state");
            let {_items} = this;
            this._items = null;
            return new Stream(_items);
        }
    }
    static builder() {
        return new this.Builder();
    }
    static iterate(value, produceNextFromLast) {
        return this.fromGenerator(function* () {
            yield value;
            while (true) yield value = produceNextFromLast.call(value, value);
        });
    }
    static generate(produceNext) {
        return this.fromGenerator(function* () {
            while (true) yield produceNext();
        });
    }
    static concat(a, b) {
        return this.fromGenerator(function* () {
            yield* a;
            yield* b;
        });
    }
    static range(start, end) {
        return this.fromGenerator(function* () {
            while (start < end) yield start++;
        });
    }
    static rangeClosed(start, last) {
        return this.range(start, last + 1);
    }

    constructor(iterable) {
        this._iterable = iterable;
    }
    // Intermediate (Chainable, pipe) methods
    _chain(generator) { // Private helper method
        return Stream.fromGenerator(generator, this);
    }
    filter(predicate) {
        return this._chain(function* (previous) {
            for (const item of previous) {
                if (predicate.call(item, item)) yield item;
            }
        });
    }
    distinct() {
        const set = new Set;
        return this.filter(item => !set.has(item) && set.add(item));
    }
    map(mapper) {
        return this._chain(function* (previous) {
            for (const item of previous) yield mapper.call(item, item);
        });
    }
    flatMap(mapper) {
        return this._chain(function* (previous) {
            for (const item of previous) yield* mapper.call(item, item);
        });
    }
    peek(action) { // Only for debugging
        return this._chain(function* (previous) {
            for (const item of previous) {
                action.call(item, item);
                yield item;
            }
        });
    }
    sorted(comparator=(a, b) => (a > b) - (a < b)) {
        return this._chain(function* (previous) {
            yield* [...previous].sort(comparator);
        });
    }
    dropWhile(predicate) {
        let active = false;
        return this.filter(item => active ||= !predicate.call(item, item));
    }
    skip(n) {
        return this.dropWhile(() => n > 0 && n--);
    }
    takeWhile(predicate) {
        return this._chain(function* (previous) {
            for (const item of previous) {
                if (!predicate.call(item, item)) break;
                yield item;
            }
        });
    }
    limit(maxSize) {
        return this.takeWhile(() => maxSize > 0 && maxSize--);
    }
    // Terminal operations below: these do not return a Stream
    *[Symbol.iterator]() {  // Use JS symbol convention instead of "iterator" method
        const iterable = this._iterable;
        this.close();
        yield* iterable;
    }
    close() {
        if (!this._iterable) throw TypeError("stream has already been operated on or closed");
        this._iterable = null;
    }
    forEach(callback) {
        for (const item of this) callback.call(item, item);
    }
    toArray() {
        return [...this];
    }
    findFirst() {
        for (const item of this) return item;
    }
    allMatch(predicate) {
        for (const item of this) {
            if (!predicate.call(item, item)) return false;
        }
        return true;
    }
    anyMatch(predicate) {
        for (const item of this) {
            if (predicate.call(item, item)) return true;
        }
        return false;
    }
    noneMatch(predicate) {
        return !this.anyMatch(predicate);
    }
    collect(supplier, accumulator, finisher=a => a) {
        // Can be called with Collector instance as first argument
        if (arguments.length === 1) {
            ({supplier, accumulator, finisher} = supplier);
        }
        const reduced = this.reduce(accumulator, supplier());
        return finisher.call(reduced, reduced);
    }
    reduce(accumulator, initialValue) {  // interface like Array#reduce
        let done, result = initialValue;
        const iterator = this[Symbol.iterator]();
        if (arguments.length == 1) {
            ({done, value: result} = iterator.next());
            if (done) throw new TypeError("reduce of empty stream without initial value");
        }
        for (const item of iterator) {
            result = accumulator(result, item);
        }
        return result;
    }
    count() {
        return this.reduce(count => count + 1, 0);
    }
    max(comparator=(a, b) => (a > b) - (a < b)) {
        return this.reduce((a, b) => comparator(a, b) < 0 ? b : a);
    }
    min(comparator=(a, b) => (a > b) - (a < b)) {
        return this.reduce((a, b) => comparator(a, b) < 0 ? a : b);
    }
    sum() { // Will sum numbers or concatenate strings
        return this.reduce((a, b) => a + b, 0);
    }
    average() {
        return this.reduce(([count, sum], b) => [count + 1, sum + b], [0, 0])
                   .reduce((count, sum) => sum / count);
    }
}

// Some example uses....

const first = Stream.iterate(1, i => i + 1)
                 .flatMap(i => Stream.iterate(i, j => j + 100).limit(2))
                 .limit(4);

const second  = Stream.builder().add(9).add(8).add(7).build().peek(console.log);

console.log("concat", Stream.concat(first, second).toArray());
console.log("average", Stream.range(1, 10).average());
console.log("sum", Stream.range(1, 10).sum());
console.log("random", Stream.generate(Math.random).limit(10).toArray());
console.log("skip & limit", Stream.range(1, 10).skip(4).limit(4).toArray());
console.log("first", Stream.range(1, 10).findFirst());
console.log("min, max", Stream.of(..."fabulous").min(), Stream.of(..."fabulous").max());
console.log("count", Stream.range(1, 10).count());
console.log("distinct and sorted", Stream.of(1, 5, 1, 2, 4, 2).distinct().sorted().toArray());

class Person {
    constructor(name, department, salary) {
        this.name = name;
        this.department = department;
        this.salary = salary;
    }
    getName() { return this.name }
    getDepartment() { return this.department }
    getSalary() { return this.salary }
    toString() { return `Hi ${this.name}!` }
}

let people = [
    new Person("John", "reception", 1000), 
    new Person("Mary", "stocks", 1500),
    new Person("Bob", "stocks", 1400),
];
console.log(Stream.of(...people)
                  .map(Person.prototype.getName)
                  .collect(Collector.toArray()));
console.log(Stream.of(...people)
                  .map(Person.prototype.getName)
                  .collect(Collector.toSet()));
console.log(Stream.of(...people)
                  .collect(Collector.joining(", ")));
console.log(Stream.of(...people)
                  .collect(Collector.summing(Person.prototype.getSalary)));
console.log(...Stream.of(...people)
                     .collect(Collector.groupingBy(Person.prototype.getDepartment)));
console.log(...Stream.of(...people)
                     .collect(Collector.groupingBy(Person.prototype.getDepartment,
                                                   Collector.averaging(Person.prototype.getSalary))));
console.log(...Stream.of(...people)
                     .collect(Collector.groupingBy(person => person.getSalary() >= 1300)));

// Fibonnacci
console.log(Stream.iterate([0, 1], ([a, b]) => [b, a+b]) 
                  .map(([a]) => a)
                  .takeWhile(a => a < 30)
                  .dropWhile(a => a < 2)
                  .toArray());

// Accumulate object keys
console.log(Stream.range(0, 10).collect(Object, (acc, a) => Object.assign(acc, {[a]: 1}))); 

// Build complete binary tree in breadth-first order
console.log(Stream.iterate(0, i => i + 1)
    .limit(10)
    .collect(
        () => (root => [[root], root])({ left: "x" }), 
        (acc, value) => {
            let [level] = acc;
            level.push(level[0].left ? (level.shift().right = {value}) : (level[0].left = {value}))
            return acc;
        },
        acc => acc.pop().right
    )
);

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/43408033

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档