【问题标题】:Typescript: Avoid comparison by reference打字稿:避免通过引用进行比较
【发布时间】:2014-02-19 19:09:26
【问题描述】:

我需要存储一个点列表并检查该列表中是否已经包含一个新点

class Point {
    x: number;
    y: number;
    constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
    }
}

window.onload = () => {
    var points : Point[] = [];
    points.push(new Point(1,1));
    var point = new Point(1,1);
    alert(points.indexOf(point)); // -1 
}

显然打字稿使用引用比较,但在这种情况下没有意义。在 Java 或 C# 中,我会在 typescript 中重载 equals 方法,这似乎是不可能的。

我考虑使用foreach 循环遍历数组并检查每个条目是否相等,但这似乎相当复杂并且会使代码膨胀。

打字稿中有类似equals 的东西吗?如何实现自己的比较?

【问题讨论】:

  • Typescript 是一种元语言,而不是一种语言。你应该问“javascript 中是否有类似 equals 的东西”,而不是 typescript。
  • 您在哪里/如何在脚本中进行比较?
  • @danludwig,打字稿有泛型。他们可以实现自定义容器类来检查类似的东西。一个接口 icomparable 和一个通用类 arraylist 可以完成这项工作。
  • 我不是专家,但我认为 dart 提供了类似 hashcode 的东西,你可以覆盖它。 pchalin.blogspot.com/2014/04/…
  • 可能是 Typescript 的一个有价值的替代品?

标签: javascript web reference comparison typescript


【解决方案1】:

使用Set 的解决方案

我认为 Set 非常适合此类问题,因为:

Set 中的一个值只能出现一次;它在 Set 的系列中是独一无二的。 — MDN

但是,您仍然可以将 [0, 1, 2, 3] 多次添加到 Set(请参阅 this answer),因为 Set 使用 SameValueZero(x, y) 比较算法来比较值(另请参阅 this answer)。这就是为什么您必须实现自己的 Set 版本。

自定义集实现

我将 Johnny Bueti's solution 移植到 Sets。我不会覆盖任何原型,因为它被认为是 bad practice 并且可能会产生不必要的副作用。

内部util.ts

export interface Equatable {
    /**
    * Returns `true` if the two objects are equal, `false` otherwise.
    */
    equals(object: any): boolean
}

export class SetCustomEquals<T extends Equatable> extends Set<T>{

    add(value: T) {
        if (!this.has(value)) {
            super.add(value);
        }
        return this;
    }

    has(otherValue: T): boolean {
        for (const value of this.values()) {
            if (otherValue.equals(value)) {
                return true;
            }
        }
        return false;
    }
}

用法

import { Equatable, SetCustomEquals } from "./util";

class MyClass implements Equatable {

    equals(other: MyClass): boolean {
        ... (your custom code)
    }

}

const mySet= new SetCustomEquals<MyClass>();
const myObject = new MyClass();
mySet.add(myObject);

【讨论】:

    【解决方案2】:

    我会实现自己的比较方法。在这种情况下,我正在扩展 Array.prototype - 除非您确切知道自己在做什么,否则我不会建议您这样做 - 但您可以很好地创建自己的类,该类继承自 Array 并实现 .contains() 方法,例如下面定义的那个。

    interface Equatable {
      /**
      * Returns `true` if the two objects are equal, `false` otherwise.
      */
      equals(object: any): boolean
    }
    
    class Point implements Equatable {
      public equals(toPoint: Point): boolean {
        return this.x === toPoint.x && this.y === toPoint.y;
      }
    }
    
    // Augment the global scope
    declare global {
      interface Array<T extends Equatable> {
        /**
        * Returns `true` if an object is found, `false` otherwise. This method uses the `.equals()` method to compare `Equatable` objects for equality.
        */
        contains(object: T): boolean
      }
    }
    
    // Extend the Array.prototype.
    Array.prototype.contains = function(object: Equatable) {
      return this.findIndex(element => {
        return element.equals(object);
      }) !== -1;
    }
    

    Equatable 接口允许您将此行为扩展到任何其他对象:

    class User implements Equatable {
      public equals(toUser: User): boolean {
        return this.uuid === toUser.uuid;
      }
    }
    

    【讨论】:

      【解决方案3】:

      Typescript 不会向 JavaScript 添加任何功能。它只是“输入”和一些语法改进。

      因此,没有办法以与您在 C# 中所做的等效的方式覆盖 equals

      但是,您最终可能会在 C# 中使用Hash 或强类型Dictionary 来进行有效的查找(除了可能的数组之外),而不是使用“索引”函数。

      为此,我只是建议您使用关联数组结构来存储Points。

      你会做这样的事情:

      class Point {
          constructor(public x:Number = 0, 
              public y:Number = 0 ) {     
          }   
          public toIndexString(p:Point):String {
              return Point.pointToIndexString(p.x, p.y);  
          }       
          static pointToIndexString(x:Number, y:Number):String {
              return x.toString() + "@" + y.toString();   
          }       
      }
      
      var points:any = {};
      var p: Point = new Point(5, 5);
      points[p.toIndexString()] = p;
      

      如果Point 不存在,检查points 关联数组将返回undefined

      包装数组的函数很简单:

      function findPoint(x:Number, y:Number):Point {
          return points[Point.pointToIndexString(x, y)];
      }
      

      遍历所有点很容易:

      // define the callback (similar in concept to defining delegate in C#)
      interface PointCallback {
          (p:Point):void;
      }
      
      function allPoints(callback:PointCallback):void {
          for(var k in points) {
              callback(points[k]);
          }
      }
      
      allPoints((p) => {
         // do something with a Point...
      });
      

      【讨论】:

      • 太好了,感谢您的回答。我最终实现了一种自定义列表类型,例如 steve fentons answer 中的那种。但这看起来也不错
      • 如果您进行大量搜索,他的解决方案目前是 O(n)。从性能的角度来看并不理想。关联数组会非常快。
      • 关联数组怎么能比 O(n) 快?它也必须检查所有键。他不是比较字符串,而是比较点。如果我错了,请纠正我,但我看不出有什么区别。
      • 关联数组使用哈希,通常为 O(1)。
      【解决方案4】:

      您可以将集合包装在 PointList 中,它只允许通过 add 方法添加 Point 对象,该方法会检查以确保没有添加重复项。

      这样做的好处是将“无重复”规则封装在一个地方,而不是希望所有调用代码在添加重复之前都进行检查,这样会在很多地方重复该规则。

      class Point {
          constructor(public x: number, public y: number) {
          }
      }
      
      class PointList {
          private points: Point[] = [];
      
          get length() {
              return this.points.length;
          }
      
          add(point: Point) {
              if (this.exists(point)) {
                  // throw 'Duplicate point';
                  return false;
              }
      
              this.points.push(point);
              return true;
          }
      
          exists(point: Point) {
              return this.findIndex(point) > -1;
          }
      
          findIndex(point: Point) {
              for (var i = 0; i < this.points.length; i++) {
                  var existingPoint = this.points[i];
                  if (existingPoint.x === point.x && existingPoint.y === point.y) {
                      return i;
                  }
              }
      
              return -1;
          }
      }
      
      var pointList = new PointList();
      
      var pointA = new Point(1, 1);
      var pointB = new Point(1, 1);
      var pointC = new Point(1, 2);
      
      pointList.add(pointA);
      alert(pointList.length); // 1
      
      pointList.add(pointB);
      alert(pointList.length); // 1
      
      pointList.add(pointC);
      alert(pointList.length); // 2
      

      【讨论】:

      • 谢谢,这很好,虽然我宁愿称这个类为 PointSet 而不是PointList,因为它的功能正是一个集合的功能。但是,您必须将 length() 更改为 size() 并将 exists() 更改为 has() 以更好地匹配 Typescript 集的可用方法。
      【解决方案5】:

      您可以做的一件事是尝试linq.js。有了它,您可以执行以下操作:

      var foundPoint = Enumerable.From(points)
          .SingleOrDefault(function(p) {
              return p.x == targetX && p.y == targety;
          });
      

      ...然后你可以在你的对象上实现这个函数

      class Point {
          x: number;
          y: number;
          constructor(x: number, y: number) {
              this.x = x;
              this.y = y;
          }
          static equals(points: Point[], candidate: Point): boolean {
              var foundPoint = Enumerable.From(points)
                  .SingleOrDefault((p: Point): boolean => {
                      return p.x == candidate.x && p.y == candidate.y;
                   });
              return foundPoint != null;
          }
      }
      

      ...并像这样使用它

      var points = createPointsArray();
      var point = getSomePoint();
      // is point already in array?
      var isPointInPoints = Point.equals(points, point)
      

      【讨论】:

      猜你喜欢
      • 2019-02-02
      • 2019-04-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-04
      • 1970-01-01
      • 2021-10-23
      • 1970-01-01
      相关资源
      最近更新 更多