Change WeightedTypes to contain maps (#795)

This will make it easier to re-organize the weight components so that
the WeightedTypes have a single source of truth, as described in
https://github.com/sourcecred/sourcecred/pull/792#issuecomment-419234721

Test plan: Unit tests suffice.
This commit is contained in:
Dandelion Mané 2018-09-06 16:58:40 -07:00 committed by GitHub
parent ad5ea761ea
commit ead0157960
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 106 additions and 80 deletions

View File

@ -1,7 +1,7 @@
// @flow // @flow
import React from "react"; import React from "react";
import deepEqual from "lodash.isequal"; import * as MapUtil from "../../../util/map";
import {NodeTypeConfig} from "./NodeTypeConfig"; import {NodeTypeConfig} from "./NodeTypeConfig";
import {EdgeTypeConfig} from "./EdgeTypeConfig"; import {EdgeTypeConfig} from "./EdgeTypeConfig";
import {StaticPluginAdapter} from "../../adapters/pluginAdapter"; import {StaticPluginAdapter} from "../../adapters/pluginAdapter";
@ -37,15 +37,18 @@ export class PluginWeightConfig extends React.Component<Props, State> {
} }
_renderNodeWeightControls() { _renderNodeWeightControls() {
return this.state.weights.nodes.map((wnt: WeightedNodeType) => { return Array.from(this.state.weights.nodes.values()).map(
(wnt: WeightedNodeType) => {
const onChange = (newType: WeightedNodeType) => { const onChange = (newType: WeightedNodeType) => {
const index = this.state.weights.nodes.findIndex((x) => this.setState(
deepEqual(x.type, wnt.type) (state) => {
const newNodes = MapUtil.copy(state.weights.nodes);
newNodes.set(newType.type.prefix, newType);
const weights = {...state.weights, nodes: newNodes};
return {weights};
},
() => this.fire()
); );
const newNodes = this.state.weights.nodes.slice();
newNodes[index] = newType;
const weights = {nodes: newNodes, edges: this.state.weights.edges};
this.setState({weights}, () => this.fire());
}; };
return ( return (
<NodeTypeConfig <NodeTypeConfig
@ -54,19 +57,23 @@ export class PluginWeightConfig extends React.Component<Props, State> {
onChange={onChange} onChange={onChange}
/> />
); );
}); }
);
} }
_renderEdgeWeightControls() { _renderEdgeWeightControls() {
return this.state.weights.edges.map((wnt: WeightedEdgeType) => { return Array.from(this.state.weights.edges.values()).map(
(wnt: WeightedEdgeType) => {
const onChange = (newType: WeightedEdgeType) => { const onChange = (newType: WeightedEdgeType) => {
const index = this.state.weights.edges.findIndex((x) => this.setState(
deepEqual(x.type, wnt.type) (state) => {
const newEdges = MapUtil.copy(state.weights.edges);
newEdges.set(newType.type.prefix, newType);
const weights = {...state.weights, edges: newEdges};
return {weights};
},
() => this.fire()
); );
const newEdges = this.state.weights.edges.slice();
newEdges[index] = newType;
const weights = {nodes: this.state.weights.nodes, edges: newEdges};
this.setState({weights}, () => this.fire());
}; };
return ( return (
<EdgeTypeConfig <EdgeTypeConfig
@ -75,7 +82,8 @@ export class PluginWeightConfig extends React.Component<Props, State> {
onChange={onChange} onChange={onChange}
/> />
); );
}); }
);
} }
render() { render() {

View File

@ -55,8 +55,10 @@ describe("src/app/credExplorer/weights/PluginWeightConfig", () => {
const newWeightedType = {...nodes[0], weight: 707}; const newWeightedType = {...nodes[0], weight: 707};
const newNodes = [newWeightedType, ...nodes.slice(1)]; const newNodes = [newWeightedType, ...nodes.slice(1)];
const expected = { const expected = {
nodes: newNodes, nodes: new Map(newNodes.map((x) => [x.type.prefix, x])),
edges: adapter.edgeTypes().map(defaultWeightedEdgeType), edges: new Map(
adapter.edgeTypes().map((x) => [x.prefix, defaultWeightedEdgeType(x)])
),
}; };
ntc.props().onChange(newWeightedType); ntc.props().onChange(newWeightedType);
expect(onChange).toHaveBeenCalledTimes(2); expect(onChange).toHaveBeenCalledTimes(2);
@ -69,8 +71,10 @@ describe("src/app/credExplorer/weights/PluginWeightConfig", () => {
const newWeightedType = {...edges[0], weight: 707}; const newWeightedType = {...edges[0], weight: 707};
const newEdges = [newWeightedType, ...edges.slice(1)]; const newEdges = [newWeightedType, ...edges.slice(1)];
const expected = { const expected = {
nodes: adapter.nodeTypes().map(defaultWeightedNodeType), nodes: new Map(
edges: newEdges, adapter.nodeTypes().map((x) => [x.prefix, defaultWeightedNodeType(x)])
),
edges: new Map(newEdges.map((x) => [x.type.prefix, x])),
}; };
ntc.props().onChange(newWeightedType); ntc.props().onChange(newWeightedType);
expect(onChange).toHaveBeenCalledTimes(2); expect(onChange).toHaveBeenCalledTimes(2);

View File

@ -1,6 +1,7 @@
// @flow // @flow
import {NodeAddress, EdgeAddress} from "../../../core/graph"; import * as MapUtil from "../../../util/map";
import {type NodeAddressT, type EdgeAddressT} from "../../../core/graph";
import type {NodeType, EdgeType} from "../../adapters/pluginAdapter"; import type {NodeType, EdgeType} from "../../adapters/pluginAdapter";
import type {StaticPluginAdapter} from "../../adapters/pluginAdapter"; import type {StaticPluginAdapter} from "../../adapters/pluginAdapter";
import type {StaticAdapterSet} from "../../adapters/adapterSet"; import type {StaticAdapterSet} from "../../adapters/adapterSet";
@ -14,8 +15,9 @@ export type WeightedEdgeType = {|
|}; |};
export type WeightedTypes = {| export type WeightedTypes = {|
+nodes: $ReadOnlyArray<WeightedNodeType>, // Map from the weighted type's prefix to the type
+edges: $ReadOnlyArray<WeightedEdgeType>, +nodes: Map<NodeAddressT, WeightedNodeType>,
+edges: Map<EdgeAddressT, WeightedEdgeType>,
|}; |};
export function defaultWeightedNodeType(type: NodeType): WeightedNodeType { export function defaultWeightedNodeType(type: NodeType): WeightedNodeType {
@ -37,34 +39,22 @@ export function defaultWeightsForAdapter(
adapter: StaticPluginAdapter adapter: StaticPluginAdapter
): WeightedTypes { ): WeightedTypes {
return { return {
nodes: adapter.nodeTypes().map(defaultWeightedNodeType), nodes: new Map(
edges: adapter.edgeTypes().map(defaultWeightedEdgeType), adapter.nodeTypes().map((x) => [x.prefix, defaultWeightedNodeType(x)])
),
edges: new Map(
adapter.edgeTypes().map((x) => [x.prefix, defaultWeightedEdgeType(x)])
),
}; };
} }
export function combineWeights( export function combineWeights(
ws: $ReadOnlyArray<WeightedTypes> ws: $ReadOnlyArray<WeightedTypes>
): WeightedTypes { ): WeightedTypes {
const seenPrefixes = new Set(); return {
const nodes = [].concat(...ws.map((x) => x.nodes)); nodes: MapUtil.merge(ws.map((x) => x.nodes)),
for (const { edges: MapUtil.merge(ws.map((x) => x.edges)),
type: {prefix}, };
} of nodes) {
if (seenPrefixes.has(prefix)) {
throw new Error(`Duplicate prefix: ${NodeAddress.toString(prefix)}`);
}
seenPrefixes.add(prefix);
}
const edges = [].concat(...ws.map((x) => x.edges));
for (const {
type: {prefix},
} of edges) {
if (seenPrefixes.has(prefix)) {
throw new Error(`Duplicate prefix: ${EdgeAddress.toString(prefix)}`);
}
seenPrefixes.add(prefix);
}
return {nodes, edges};
} }
export function defaultWeightsForAdapterSet( export function defaultWeightsForAdapterSet(

View File

@ -34,8 +34,12 @@ describe("app/credExplorer/weights/weights", () => {
it("works on the demo adapter", () => { it("works on the demo adapter", () => {
const adapter = new FactorioStaticAdapter(); const adapter = new FactorioStaticAdapter();
const expected = { const expected = {
nodes: adapter.nodeTypes().map(defaultWeightedNodeType), nodes: new Map(
edges: adapter.edgeTypes().map(defaultWeightedEdgeType), adapter.nodeTypes().map((x) => [x.prefix, defaultWeightedNodeType(x)])
),
edges: new Map(
adapter.edgeTypes().map((x) => [x.prefix, defaultWeightedEdgeType(x)])
),
}; };
expect(defaultWeightsForAdapter(adapter)).toEqual(expected); expect(defaultWeightsForAdapter(adapter)).toEqual(expected);
}); });
@ -43,15 +47,27 @@ describe("app/credExplorer/weights/weights", () => {
describe("combineWeights", () => { describe("combineWeights", () => {
const defaultWeights = () => const defaultWeights = () =>
defaultWeightsForAdapter(new FactorioStaticAdapter()); defaultWeightsForAdapter(new FactorioStaticAdapter());
const emptyWeights = () => ({nodes: [], edges: []}); const emptyWeights = () => ({nodes: new Map(), edges: new Map()});
it("successfully combines WeightedTypes", () => { it("successfully combines WeightedTypes", () => {
const weights1 = { const weights1 = {
nodes: [defaultWeightedNodeType(inserterNodeType)], nodes: new Map().set(
edges: [defaultWeightedEdgeType(assemblesEdgeType)], inserterNodeType.prefix,
defaultWeightedNodeType(inserterNodeType)
),
edges: new Map().set(
assemblesEdgeType.prefix,
defaultWeightedEdgeType(assemblesEdgeType)
),
}; };
const weights2 = { const weights2 = {
nodes: [defaultWeightedNodeType(machineNodeType)], nodes: new Map().set(
edges: [defaultWeightedEdgeType(transportsEdgeType)], machineNodeType.prefix,
defaultWeightedNodeType(machineNodeType)
),
edges: new Map().set(
transportsEdgeType.prefix,
defaultWeightedEdgeType(transportsEdgeType)
),
}; };
expect(combineWeights([weights1, weights2])).toEqual(defaultWeights()); expect(combineWeights([weights1, weights2])).toEqual(defaultWeights());
}); });
@ -62,20 +78,26 @@ describe("app/credExplorer/weights/weights", () => {
}); });
it("errors on duplicate edge prefix", () => { it("errors on duplicate edge prefix", () => {
const weights = { const weights = {
nodes: [], nodes: new Map(),
edges: [defaultWeightedEdgeType(assemblesEdgeType)], edges: new Map().set(
assemblesEdgeType.prefix,
defaultWeightedEdgeType(assemblesEdgeType)
),
}; };
expect(() => combineWeights([weights, weights])).toThrowError( expect(() => combineWeights([weights, weights])).toThrowError(
"Duplicate prefix" "duplicate key"
); );
}); });
it("errors on duplicate node prefix", () => { it("errors on duplicate node prefix", () => {
const weights = { const weights = {
nodes: [defaultWeightedNodeType(inserterNodeType)], nodes: new Map().set(
edges: [], inserterNodeType.prefix,
defaultWeightedNodeType(inserterNodeType)
),
edges: new Map(),
}; };
expect(() => combineWeights([weights, weights])).toThrowError( expect(() => combineWeights([weights, weights])).toThrowError(
"Duplicate prefix" "duplicate key"
); );
}); });
}); });

View File

@ -7,11 +7,11 @@ import {NodeTrie, EdgeTrie} from "../../../core/trie";
export function weightsToEdgeEvaluator(weights: WeightedTypes): EdgeEvaluator { export function weightsToEdgeEvaluator(weights: WeightedTypes): EdgeEvaluator {
const nodeTrie = new NodeTrie(); const nodeTrie = new NodeTrie();
for (const {type, weight} of weights.nodes) { for (const {type, weight} of weights.nodes.values()) {
nodeTrie.add(type.prefix, weight); nodeTrie.add(type.prefix, weight);
} }
const edgeTrie = new EdgeTrie(); const edgeTrie = new EdgeTrie();
for (const {type, forwardWeight, backwardWeight} of weights.edges) { for (const {type, forwardWeight, backwardWeight} of weights.edges.values()) {
edgeTrie.add(type.prefix, {forwardWeight, backwardWeight}); edgeTrie.add(type.prefix, {forwardWeight, backwardWeight});
} }

View File

@ -38,6 +38,7 @@ describe("app/credExplorer/weights/weightsToEdgeEvaluator", () => {
{weight: NullUtil.orElse(machine, 1), type: machineNodeType}, {weight: NullUtil.orElse(machine, 1), type: machineNodeType},
{weight: NullUtil.orElse(baseNode, 1), type: fallbackNodeType}, {weight: NullUtil.orElse(baseNode, 1), type: fallbackNodeType},
]; ];
const nodesMap = new Map(nodes.map((x) => [x.type.prefix, x]));
const edges = [ const edges = [
{ {
forwardWeight: NullUtil.orElse(assemblesForward, 1), forwardWeight: NullUtil.orElse(assemblesForward, 1),
@ -50,7 +51,8 @@ describe("app/credExplorer/weights/weightsToEdgeEvaluator", () => {
type: fallbackEdgeType, type: fallbackEdgeType,
}, },
]; ];
return {nodes, edges}; const edgesMap = new Map(edges.map((x) => [x.type.prefix, x]));
return {nodes: nodesMap, edges: edgesMap};
} }
function exampleEdgeWeights(weightArgs: WeightArgs) { function exampleEdgeWeights(weightArgs: WeightArgs) {
const ws = weights(weightArgs); const ws = weights(weightArgs);